Galeria de projetos e testbenches¶
Esta galeria reúne exemplos pequenos para praticar o fluxo completo de simulação na AURORA. Cada circuito ou algoritmo possui duas alternativas de teste: um testbench Verilog (.v) e um testbench cocotb (.py). Use apenas uma alternativa como Testbench Top em cada execução.
Como usar os exemplos¶
Para um exemplo Verilog:
Baixe ou copie os três arquivos do conjunto para a pasta do projeto.
Adicione o arquivo do circuito como fonte sintetizável.
Defina o arquivo do circuito como Top Level.
Adicione
tb_*.voutest_*.pycomo testbench.Defina o testbench escolhido como Testbench Top.
Execute Analisar Verilog (forma de onda) para abrir as formas de onda ou Execução rápida para conferir apenas as verificações.
Para um exemplo C±:
Crie no Hub de Processadores um processador com o mesmo nome indicado por
#PRNAME.Substitua o conteúdo do arquivo em
Softwarepelo exemplo.cmm.Execute C± para gerar o módulo em
Hardware/<nome>.ve os arquivos de memória necessários.Adicione o testbench
.vou.pycorrespondente ao projeto.Defina
Hardware/<nome>.vcomo Top Level e o teste escolhido como Testbench Top.Execute Analisar Verilog (forma de onda) ou Execução rápida.
Importante
Os testbenches dos exemplos C± exercitam o Verilog gerado pelo compilador. Compile o .cmm antes da simulação e mantenha os arquivos gerados pela AURORA no projeto. O arquivo Python não executa C± diretamente.
Porta AND em Verilog¶
Este primeiro conjunto cobre um circuito combinacional. Os quatro pares possíveis de entrada são aplicados, e cada saída é comparada com a tabela-verdade da operação AND.
Arquivos: porta_and.v, tb_porta_and.v e test_porta_and.py.
Circuito porta_and.v¶
1module porta_and (
2 input wire a,
3 input wire b,
4 output wire y
5);
6 assign y = a & b;
7endmodule
Testbench Verilog tb_porta_and.v¶
1`timescale 1ns/1ps
2
3module tb_porta_and;
4 reg a;
5 reg b;
6 wire y;
7
8 porta_and dut (
9 .a(a),
10 .b(b),
11 .y(y)
12 );
13
14 task verificar;
15 input valor_a;
16 input valor_b;
17 input esperado;
18 begin
19 a = valor_a;
20 b = valor_b;
21 #1;
22
23 if (y !== esperado) begin
24 $display(
25 "ERRO: a=%0b b=%0b esperado=%0b obtido=%0b",
26 a, b, esperado, y
27 );
28 $fatal(1);
29 end
30 end
31 endtask
32
33 initial begin
34 $dumpfile("tb_porta_and.vcd");
35 $dumpvars(0, tb_porta_and);
36
37 verificar(1'b0, 1'b0, 1'b0);
38 verificar(1'b0, 1'b1, 1'b0);
39 verificar(1'b1, 1'b0, 1'b0);
40 verificar(1'b1, 1'b1, 1'b1);
41
42 $display("SUCESSO: tabela verdade validada.");
43 $finish;
44 end
45endmodule
O bloco task verificar evita repetir a sequência de atribuição, espera e comparação. $fatal(1) encerra a execução com falha assim que um caso produz uma saída incorreta. $dumpfile e $dumpvars registram os sinais para o viewer de ondas.
Testbench cocotb test_porta_and.py¶
1# aurora-toplevel: porta_and
2
3import cocotb
4from cocotb.triggers import Timer
5
6
7async def verificar(dut, valor_a, valor_b, esperado):
8 dut.a.value = valor_a
9 dut.b.value = valor_b
10 await Timer(1, unit="ns")
11
12 obtido = int(dut.y.value)
13 assert obtido == esperado, (
14 f"a={valor_a} b={valor_b}: esperado={esperado}, obtido={obtido}"
15 )
16
17
18@cocotb.test()
19async def testa_tabela_verdade(dut):
20 casos = [
21 (0, 0, 0),
22 (0, 1, 0),
23 (1, 0, 0),
24 (1, 1, 1),
25 ]
26
27 for valor_a, valor_b, esperado in casos:
28 await verificar(dut, valor_a, valor_b, esperado)
A diretiva # aurora-toplevel: porta_and seleciona explicitamente o módulo testado. A função auxiliar aplica um caso, aguarda a propagação combinacional e inclui entradas, valor esperado e valor obtido na mensagem da asserção.
Contador de 4 bits em Verilog¶
Este conjunto introduz clock, reset e controle de habilitação. O teste confirma que o reset zera o estado, que o contador avança a cada borda quando enable vale 1 e que o valor permanece estável quando enable vale 0.
Arquivos: contador4.v, tb_contador4.v e test_contador4.py.
Circuito contador4.v¶
1module contador4 (
2 input wire clk,
3 input wire rst,
4 input wire enable,
5 output reg [3:0] valor
6);
7 always @(posedge clk) begin
8 if (rst)
9 valor <= 4'd0;
10 else if (enable)
11 valor <= valor + 4'd1;
12 end
13endmodule
Testbench Verilog tb_contador4.v¶
1`timescale 1ns/1ps
2
3module tb_contador4;
4 reg clk = 1'b0;
5 reg rst = 1'b1;
6 reg enable = 1'b0;
7 wire [3:0] valor;
8
9 contador4 dut (
10 .clk(clk),
11 .rst(rst),
12 .enable(enable),
13 .valor(valor)
14 );
15
16 always #5 clk = ~clk;
17
18 initial begin
19 $dumpfile("tb_contador4.vcd");
20 $dumpvars(0, tb_contador4);
21
22 repeat (2) @(posedge clk);
23 @(negedge clk);
24 rst = 1'b0;
25 enable = 1'b1;
26
27 repeat (4) @(posedge clk);
28 #1;
29 if (valor !== 4'd4) begin
30 $display("ERRO: esperado=4 obtido=%0d", valor);
31 $fatal(1);
32 end
33
34 @(negedge clk);
35 enable = 1'b0;
36 repeat (2) @(posedge clk);
37 #1;
38 if (valor !== 4'd4) begin
39 $display("ERRO: contador mudou com enable=0. obtido=%0d", valor);
40 $fatal(1);
41 end
42
43 $display("SUCESSO: reset, contagem e enable validados.");
44 $finish;
45 end
46endmodule
O clock alterna a cada 5 ns, portanto o período é de 10 ns. O reset é retirado na borda de descida para estar estável antes da próxima borda de subida. O atraso #1 após a borda permite observar o valor atualizado pela atribuição não bloqueante do contador.
Testbench cocotb test_contador4.py¶
1# aurora-toplevel: contador4
2
3import cocotb
4from cocotb.clock import Clock
5from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge
6
7
8async def ler_apos_borda(dut):
9 await RisingEdge(dut.clk)
10 await ReadOnly()
11 return int(dut.valor.value)
12
13
14@cocotb.test()
15async def testa_reset_contagem_e_enable(dut):
16 cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
17
18 dut.rst.value = 1
19 dut.enable.value = 0
20 await ler_apos_borda(dut)
21 assert await ler_apos_borda(dut) == 0, "O reset nao zerou o contador."
22
23 await FallingEdge(dut.clk)
24 dut.rst.value = 0
25 dut.enable.value = 1
26 for esperado in range(1, 5):
27 obtido = await ler_apos_borda(dut)
28 assert obtido == esperado, (
29 f"Contagem incorreta: esperado={esperado}, obtido={obtido}"
30 )
31
32 await FallingEdge(dut.clk)
33 dut.enable.value = 0
34 for _ in range(2):
35 obtido = await ler_apos_borda(dut)
36 assert obtido == 4, (
37 f"O contador mudou com enable=0: esperado=4, obtido={obtido}"
38 )
Clock gera o clock em uma corrotina separada. A função ler_apos_borda aguarda RisingEdge e depois ReadOnly, fase em que as atualizações daquele instante já podem ser lidas sem disputar com a lógica do simulador. As mudanças de rst e enable são feitas após FallingEdge, fora da fase somente de leitura e antes da próxima borda ativa.
Soma de constantes em C±¶
Este exemplo gera um processador chamado soma_constantes. O algoritmo soma 3 + 4, escreve 7 na porta de saída 0 e alcança #TOAQUI, que permite ao hardware indicar o término pelo sinal cheguei.
Arquivos: soma_constantes.cmm, tb_soma_constantes.v e test_soma_constantes.py.
Algoritmo soma_constantes.cmm¶
1#PRNAME soma_constantes
2#NUBITS 32
3#NBMANT 23
4#NBEXPO 8
5#NDSTAC 8
6#SDEPTH 8
7#NUIOIN 1
8#NUIOOU 1
9#NUGAIN 128
10
11void main()
12{
13 int a = 3;
14 int b = 4;
15 int resultado;
16
17 resultado = a + b;
18 out(0, resultado);
19
20 #TOAQUI
21}
Testbench Verilog tb_soma_constantes.v¶
1`timescale 1ns/1ps
2
3module tb_soma_constantes;
4 reg clk = 1'b0;
5 reg rst = 1'b1;
6 wire signed [31:0] out;
7 wire [0:0] out_en;
8 wire cheguei;
9
10 integer ciclos = 0;
11 integer recebeu_saida = 0;
12 integer terminou = 0;
13
14 soma_constantes dut (
15 .clk(clk),
16 .rst(rst),
17 .out(out),
18 .out_en(out_en),
19 .cheguei(cheguei)
20 );
21
22 always #5 clk = ~clk;
23
24 always @(posedge clk) begin
25 if (!rst && (out_en == 1)) begin
26 recebeu_saida = 1;
27 if ($signed(out) !== 32'sd7) begin
28 $display("ERRO: esperado=7 obtido=%0d", $signed(out));
29 $fatal(1);
30 end
31 end
32
33 if (cheguei === 1'b1)
34 terminou = 1;
35 end
36
37 initial begin
38 $dumpfile("tb_soma_constantes.vcd");
39 $dumpvars(0, tb_soma_constantes);
40
41 repeat (2) @(posedge clk);
42 @(negedge clk);
43 rst = 1'b0;
44
45 while (!terminou && (ciclos < 200)) begin
46 @(posedge clk);
47 ciclos = ciclos + 1;
48 end
49
50 repeat (2) @(posedge clk);
51 #1;
52
53 if (!recebeu_saida) begin
54 $display("ERRO: nenhuma escrita foi observada na porta 0.");
55 $fatal(1);
56 end
57
58 if (!terminou) begin
59 $display("ERRO: tempo limite antes de #TOAQUI.");
60 $fatal(1);
61 end
62
63 $display("SUCESSO: saida 7 e termino do algoritmo validados.");
64 $finish;
65 end
66endmodule
O bloco acionado na borda de subida observa out_en == 1, condição que identifica uma escrita na porta 0. A captura ocorre na própria borda porque esse sinal funciona como um pulso de habilitação da escrita. O limite de 200 ciclos evita uma simulação infinita se o algoritmo não produzir a saída ou não alcançar #TOAQUI.
Testbench cocotb test_soma_constantes.py¶
1# aurora-toplevel: soma_constantes
2
3import cocotb
4from cocotb.clock import Clock
5from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge
6
7
8@cocotb.test()
9async def testa_soma_e_termino(dut):
10 cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
11
12 dut.rst.value = 1
13 for _ in range(2):
14 await RisingEdge(dut.clk)
15 await FallingEdge(dut.clk)
16 dut.rst.value = 0
17
18 saidas = []
19 ciclos_apos_termino = 0
20 for _ in range(200):
21 await FallingEdge(dut.clk)
22 await ReadOnly()
23
24 if int(dut.out_en.value) == 1:
25 saidas.append(int(dut.out.value))
26
27 if int(dut.cheguei.value) == 1 and ciclos_apos_termino == 0:
28 ciclos_apos_termino = 1
29 elif ciclos_apos_termino:
30 ciclos_apos_termino += 1
31
32 if ciclos_apos_termino >= 3:
33 break
34 else:
35 raise AssertionError("Tempo limite antes de o algoritmo atingir #TOAQUI.")
36
37 assert saidas == [7], f"Esperado [7] na porta 0, obtido {saidas}."
O teste amostra out_en, out e cheguei na borda de descida do clock, quando os sinais produzidos na borda ativa já estão estáveis. Ele exige a lista exata [7]; dessa forma, detecta ausência de saída, valor incorreto e escritas adicionais inesperadas. O limite de ciclos impede uma espera infinita.
Sequência de quadrados em C±¶
Este exemplo usa um laço while para escrever 0, 1, 4 e 9 na porta 0. Além do valor de cada amostra, os testes verificam a ordem e a quantidade de escritas.
Arquivos: sequencia_quadrados.cmm, tb_sequencia_quadrados.v e test_sequencia_quadrados.py.
Algoritmo sequencia_quadrados.cmm¶
1#PRNAME sequencia_quadrados
2#NUBITS 32
3#NBMANT 23
4#NBEXPO 8
5#NDSTAC 8
6#SDEPTH 8
7#NUIOIN 1
8#NUIOOU 1
9#NUGAIN 128
10
11void main()
12{
13 int indice = 0;
14
15 while (indice < 4) {
16 out(0, indice * indice);
17 indice = indice + 1;
18 }
19
20 #TOAQUI
21}
Testbench Verilog tb_sequencia_quadrados.v¶
1`timescale 1ns/1ps
2
3module tb_sequencia_quadrados;
4 reg clk = 1'b0;
5 reg rst = 1'b1;
6 wire signed [31:0] out;
7 wire [0:0] out_en;
8 wire cheguei;
9
10 integer ciclos = 0;
11 integer indice = 0;
12 integer terminou = 0;
13 integer esperado [0:3];
14
15 sequencia_quadrados dut (
16 .clk(clk),
17 .rst(rst),
18 .out(out),
19 .out_en(out_en),
20 .cheguei(cheguei)
21 );
22
23 always #5 clk = ~clk;
24
25 always @(posedge clk) begin
26 if (!rst && (out_en == 1)) begin
27 if (indice > 3) begin
28 $display("ERRO: o processador produziu saidas extras.");
29 $fatal(1);
30 end
31
32 if ($signed(out) !== esperado[indice]) begin
33 $display(
34 "ERRO: indice=%0d esperado=%0d obtido=%0d",
35 indice, esperado[indice], $signed(out)
36 );
37 $fatal(1);
38 end
39
40 indice = indice + 1;
41 end
42
43 if (cheguei === 1'b1)
44 terminou = 1;
45 end
46
47 initial begin
48 esperado[0] = 0;
49 esperado[1] = 1;
50 esperado[2] = 4;
51 esperado[3] = 9;
52
53 $dumpfile("tb_sequencia_quadrados.vcd");
54 $dumpvars(0, tb_sequencia_quadrados);
55
56 repeat (2) @(posedge clk);
57 @(negedge clk);
58 rst = 1'b0;
59
60 while (!terminou && (ciclos < 400)) begin
61 @(posedge clk);
62 ciclos = ciclos + 1;
63 end
64
65 repeat (2) @(posedge clk);
66 #1;
67
68 if (indice !== 4) begin
69 $display("ERRO: esperadas=4 saidas_recebidas=%0d", indice);
70 $fatal(1);
71 end
72
73 if (!terminou) begin
74 $display("ERRO: tempo limite antes de #TOAQUI.");
75 $fatal(1);
76 end
77
78 $display("SUCESSO: sequencia 0, 1, 4, 9 validada.");
79 $finish;
80 end
81endmodule
O vetor esperado funciona como um modelo de referência simples. Cada pulso de out_en consome uma posição. O teste falha se houver valor incorreto, quantidade diferente de quatro saídas ou ausência do sinal de término.
Testbench cocotb test_sequencia_quadrados.py¶
1# aurora-toplevel: sequencia_quadrados
2
3import cocotb
4from cocotb.clock import Clock
5from cocotb.triggers import FallingEdge, ReadOnly, RisingEdge
6
7
8@cocotb.test()
9async def testa_sequencia_e_termino(dut):
10 cocotb.start_soon(Clock(dut.clk, 10, unit="ns").start())
11
12 dut.rst.value = 1
13 for _ in range(2):
14 await RisingEdge(dut.clk)
15 await FallingEdge(dut.clk)
16 dut.rst.value = 0
17
18 saidas = []
19 ciclos_apos_termino = 0
20 for _ in range(400):
21 await FallingEdge(dut.clk)
22 await ReadOnly()
23
24 if int(dut.out_en.value) == 1:
25 saidas.append(int(dut.out.value))
26
27 if int(dut.cheguei.value) == 1 and ciclos_apos_termino == 0:
28 ciclos_apos_termino = 1
29 elif ciclos_apos_termino:
30 ciclos_apos_termino += 1
31
32 if ciclos_apos_termino >= 3:
33 break
34 else:
35 raise AssertionError("Tempo limite antes de o algoritmo atingir #TOAQUI.")
36
37 assert saidas == [0, 1, 4, 9], (
38 f"Esperado [0, 1, 4, 9] na porta 0, obtido {saidas}."
39 )
A lista Python torna a comparação da sequência inteira direta. Esse padrão pode ser ampliado para filtros, máquinas de estados e processadores que produzam séries de amostras.
Escolher entre os dois tipos de testbench¶
Situação |
Testbench recomendado |
|---|---|
Aprender eventos e tarefas do Verilog |
|
Controlar diretamente o dump de sinais |
|
Percorrer muitos casos de entrada |
|
Criar listas, modelos matemáticos e mensagens detalhadas |
|
Depurar a integração inicial de um módulo |
Comece com |
Os dois formatos verificam o mesmo hardware. A escolha altera a linguagem e a organização do teste, não o circuito sintetizável. Para o procedimento de execução e o diagnóstico de falhas, consulte Simular com Icarus, Verilator ou cocotb.