O que um testbench de fato é
Um testbench é apenas um module Verilog. A diferença em relação a um module sintetizável é o que está dentro:
- Não tem ports - é o topo da hierarquia de simulação.
- Instancia o design under test.
- Aciona as entradas do DUT a partir de blocos
initialealways. - Observa as saídas do DUT com
$display,$monitorou dumpando um VCD. - Termina a simulação com
$finish.
Esse é o trabalho inteiro. Não há palavra-chave especial "testbench" - a forma de saber que algo é um testbench é lendo o que ele faz.
O esqueleto padrão
Esse é o padrão inteiro. Cinco seções, fácil de copy-paste, fácil de adaptar.
Para DUTs combinacionais (como o adder), você não precisa de um clock. Para DUTs sequenciais, sim - e essa é a próxima camada acima.
Geração de clock para DUTs sequenciais
Quando o DUT tem uma entrada clk, o testbench tem que produzi-la. A one-liner padrão:
reg clk = 0;
always #5 clk = ~clk;
Isso alterna clk a cada 5 unidades de tempo. Cada toggle é meio período de clock, então o período inteiro é 10 unidades (um ciclo = alto por 5 + baixo por 5). Se seu timescale é 1ns / 1ps (padrão na maioria dos simuladores), isso é um clock de 100 MHz.
Escolha o meio-período com base no que quer modelar. Para um clock de 10 MHz no mesmo timescale, use #50. Para um clock de 200 MHz, #2.5 (ou mude o timescale).
Sequência de reset
Designs síncronos precisam de um reset que fica alto por alguns ciclos depois do tempo 0 e depois é liberado. A forma convencional:
reg reset = 1; // comeca asserido
initial begin
// Segura reset por alguns clocks.
#20 reset = 0;
end
Isso mantém reset alto até o tempo 20, depois abaixa. Quando reset cai, o clock já deu alguns ciclos, cada flip-flop capturou o valor de reset e o design está em um estado conhecido.
Para reset active-low (reset_n em vez de reset), inverta o valor inicial:
reg reset_n = 0; // active-low: 0 significa asserido
initial begin
#20 reset_n = 1;
end
Um testbench sequencial por inteiro
Combinando clock, reset e estímulo:
Esse é um testbench completo para um contador de 4 bits. Pressione Run e você verá o contador sair do reset, contar enquanto habilitado, pausar quando enable cai, retomar quando sobe e depois parar.
Três coisas para notar sobre a estrutura:
- Três locais distintos de atividade: o gerador de clock (um
always), o estímulo (uminitial) e o monitor (outroalways). Cada um tem um trabalho. - Os delays
#no bloco de estímulo são medidos em unidades de tempo - não em ciclos de clock. Se seu período de clock é 10 unidades, então#10é exatamente um ciclo. Alguns testbenches usam@(posedge clk)no lugar, que avança um ciclo independente do período. - O monitor usa
$displaya partir de um blocoalways @(posedge clk). Isso imprime a cada ciclo. Para saída mais sofisticada, troque para$monitor(coberto a seguir).
Estímulo baseado em ciclos
Às vezes "esperar N ciclos" lê melhor que "esperar N unidades de tempo":
initial begin
@(posedge clk); // espera proxima borda de clock
reset = 0;
repeat (5) @(posedge clk); // espera mais 5 ciclos
enable = 1;
repeat (8) @(posedge clk);
enable = 0;
repeat (10) @(posedge clk);
$finish;
end
@(posedge clk) bloqueia até a próxima borda de subida de clock. repeat (N) @(posedge clk); espera N ciclos. Esse estilo é independente do período do clock - se você mudar a frequência do clock, o estímulo ainda faz a mesma coisa em termos de ciclos.
Para testbenches iniciais, delays # são mais simples. Para testbenches de produção que podem rodar em múltiplas velocidades de clock, baseado em ciclos é o estilo mais seguro.
Testes auto-verificáveis
O testbench até agora imprime o que aconteceu, deixando você ler a saída. Um testbench auto-verificável em vez disso verifica a saída e reporta pass/fail:
initial begin
#1;
a = 10; b = 20;
#1;
if (sum !== 30) begin
$display("FAIL: 10 + 20 = %0d (esperado 30)", sum);
$finish;
end
a = 250; b = 5;
#1;
if (sum !== 255) begin
$display("FAIL: 250 + 5 = %0d (esperado 255)", sum);
$finish;
end
$display("PASS");
$finish;
end
Use !== (o operador case-inequality) por segurança - ele não retorna x quando um operando tem bits desconhecidos. O padrão: acionar entradas, esperar coisas se estabilizarem, comparar com esperado, reportar pass ou fail.
Auto-verificação é inestimável em suítes de regressão: milhares de testes podem rodar sem atendimento, e só falhas precisam de atenção.
O que vem a seguir
Você agora tem a forma de testbench que basta para qualquer module. Os próximos docs cobrem as ferramentas que vão dentro do testbench: Display and Monitor para saída de texto mais rica, Dumpfile and VCD para debug visual com forma de onda, e Timescale and Delays para controlar exatamente como o tempo de simulação se relaciona com tempo de relógio.
Perguntas frequentes
O que é um testbench em Verilog?
Um testbench é um module Verilog - tipicamente sem ports - cujo único propósito é exercitar um design under test (DUT). Ele instancia o DUT, gera um clock, aciona estímulo através de blocos initial e always, observa saídas com $display/$monitor, opcionalmente dumpa uma forma de onda VCD e termina a simulação com $finish.
Como gero um clock em um testbench Verilog?
O padrão padrão é uma única linha: always #5 clk = ~clk; (com reg clk = 0; declarado antes). Isso alterna clk a cada 5 unidades de tempo de simulação, dando um período de 10 unidades (um clock de 100 MHz se seu timescale está em nanosegundos). O #5 é o meio-período - metade do tempo clk está alto, metade está baixo.
O que é um DUT em Verilog?
DUT significa Design Under Test - o module que o testbench está exercitando. Convenção é nomear a instância do DUT como dut ou u_dut no testbench: my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));. O nome é só um rótulo; o que importa é que está instanciado, seus ports estão conectados a sinais do testbench e o testbench aciona esses sinais.
Quanto tempo uma simulação Verilog deve rodar?
O suficiente para exercitar tudo que você quer verificar, depois $finish. A maioria dos testbenches define um limite de tempo explícito (#1000 $finish) para que a simulação não trave esperando por um evento que nunca chega. Dentro dessa janela, acione seu estímulo, deixe o DUT se estabilizar e idealmente inclua algumas declarações if de auto-verificação que imprimem FAIL se a saída não combinar com as expectativas.