Menu

Verilog Testbench Basics: Como verificar um module

Como escrever um testbench Verilog - geração de clock, sequência de reset, estímulo, observação e o esqueleto padrão que aciona cada simulação que você vai rodar.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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 initial e always.
  • Observa as saídas do DUT com $display, $monitor ou 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:

  1. Três locais distintos de atividade: o gerador de clock (um always), o estímulo (um initial) e o monitor (outro always). Cada um tem um trabalho.
  2. 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.
  3. O monitor usa $display a partir de um bloco always @(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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR