Menu

Bases du testbench Verilog : comment vérifier un module

Comment écrire un testbench Verilog - génération d'horloge, séquence de reset, stimulus, observation, et le squelette standard qui pilote chaque simulation que tu lanceras.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Ce qu'est vraiment un testbench

Un testbench est juste un module Verilog. La différence avec un module synthétisable, c'est ce qu'il y a dedans :

  • Il n'a pas de ports - c'est le sommet de la hiérarchie de simulation.
  • Il instancie le design under test.
  • Il pilote les entrées du DUT depuis les blocs initial et always.
  • Il observe les sorties du DUT avec $display, $monitor, ou en dumpant un VCD.
  • Il termine la simulation avec $finish.

C'est tout le boulot. Il n'y a pas de mot-clé spécial « testbench » - la façon de dire que quelque chose est un testbench est de lire ce qu'il fait.

Le squelette standard

C'est tout le motif. Cinq sections, faciles à copier-coller, faciles à adapter.

Pour les DUT combinatoires (comme l'additionneur), tu n'as pas besoin d'horloge. Pour les DUT séquentiels, oui - et c'est le niveau du dessus.

Génération d'horloge pour DUT séquentiels

Quand le DUT a une entrée clk, le testbench doit en produire une. Le one-liner standard :

reg clk = 0;
always #5 clk = ~clk;

Ça bascule clk toutes les 5 unités de temps. Chaque bascule est la moitié de la période d'horloge, donc la période complète est 10 unités (un cycle = haut pendant 5 + bas pendant 5). Si ton timescale est 1ns / 1ps (défaut dans la plupart des simulateurs), c'est une horloge 100 MHz.

Choisis la demi-période selon ce que tu veux modéliser. Pour une horloge 10 MHz au même timescale, utilise #50. Pour 200 MHz, #2.5 (ou change le timescale).

Séquence de reset

Les designs synchrones ont besoin d'un reset qui est maintenu haut pendant quelques cycles après le temps 0, puis relâché. La forme conventionnelle :

reg reset = 1;     // démarre asserté

initial begin
    // Maintient reset pendant quelques horloges.
    #20 reset = 0;
end

Ça garde reset haut jusqu'au temps 20, puis le baisse. Au moment où reset tombe, l'horloge a fait quelques cycles, chaque flip-flop a capturé la valeur reset, et le design est dans un état connu.

Pour un reset actif-bas (reset_n au lieu de reset), inverse la valeur initiale :

reg reset_n = 0;   // actif-bas : 0 veut dire asserté

initial begin
    #20 reset_n = 1;
end

Un testbench séquentiel complet

Combinant horloge, reset et stimulus :

C'est un testbench complet pour un compteur 4 bits. Appuie sur Run et tu verras le compteur sortir du reset, compter pendant qu'il est enable, pauser quand enable tombe, reprendre quand il monte, puis s'arrêter.

Trois choses à remarquer sur la structure :

  1. Trois loci d'activité distincts : le générateur d'horloge (un always), le stimulus (un initial) et le monitor (un autre always). Chacun a un boulot.
  2. Les délais # dans le bloc de stimulus sont mesurés en unités de temps - pas en cycles d'horloge. Si ta période d'horloge est de 10 unités, alors #10 est exactement un cycle. Certains testbenches utilisent @(posedge clk) à la place, qui avance d'un cycle quelle que soit la période.
  3. Le monitor utilise $display depuis un bloc always @(posedge clk). Ça affiche chaque cycle. Pour une sortie plus sophistiquée, passe à $monitor (couvert ensuite).

Stimulus basé sur les cycles

Parfois « attends N cycles » se lit mieux que « attends N unités de temps » :

initial begin
    @(posedge clk);     // attend le prochain front d'horloge
    reset = 0;

    repeat (5) @(posedge clk);   // attends 5 cycles de plus
    enable = 1;

    repeat (8) @(posedge clk);
    enable = 0;

    repeat (10) @(posedge clk);
    $finish;
end

@(posedge clk) bloque jusqu'au prochain front montant d'horloge. repeat (N) @(posedge clk); attend N cycles. Ce style est indépendant de la période d'horloge - si tu changes la fréquence d'horloge, le stimulus fait toujours la même chose en termes de cycles.

Pour les premiers testbenches les délais # sont plus simples. Pour les testbenches de production qui peuvent tourner à plusieurs vitesses d'horloge, basé sur les cycles est le style plus sûr.

Tests self-checking

Le testbench jusqu'ici affiche ce qui s'est passé, te laissant lire la sortie. Un testbench self-checking vérifie plutôt la sortie et rapporte pass/fail :

initial begin
    #1;
    a = 10; b = 20;
    #1;
    if (sum !== 30) begin
        $display("FAIL: 10 + 20 = %0d (expected 30)", sum);
        $finish;
    end
    a = 250; b = 5;
    #1;
    if (sum !== 255) begin
        $display("FAIL: 250 + 5 = %0d (expected 255)", sum);
        $finish;
    end

    $display("PASS");
    $finish;
end

Utilise !== (l'opérateur d'inégalité case) pour la sécurité - il ne retourne pas x quand une opérande a des bits inconnus. Le motif : pilote les entrées, attends que les choses se stabilisent, compare aux attentes, rapporte pass ou fail.

Le self-checking est inestimable dans les suites de regression : des milliers de tests peuvent tourner sans surveillance, et seules les échecs ont besoin d'attention.

La suite

Tu as maintenant la forme de testbench qui suffit pour n'importe quel module. Les prochains documents couvrent les outils qui vont à l'intérieur du testbench : Display et monitor pour une sortie texte plus riche, Dumpfile et VCD pour le debug graphique de waveform, et Timescale et délais pour contrôler exactement comment le temps de simulation se rapporte au temps horloge mural.

Questions fréquentes

Qu'est-ce qu'un testbench en Verilog ?

Un testbench est un module Verilog - typiquement sans ports - dont le seul rôle est d'exercer un design under test (DUT). Il instancie le DUT, génère une horloge, pilote le stimulus via des blocs initial et always, observe les sorties avec $display/$monitor, optionnellement dumpe un waveform VCD, et termine la simulation avec $finish.

Comment générer une horloge dans un testbench Verilog ?

Le motif standard est une seule ligne : always #5 clk = ~clk; (avec reg clk = 0; déclaré avant). Ça bascule clk toutes les 5 unités de temps de simulation, donnant une période de 10 unités (une horloge 100 MHz si ton timescale est en nanosecondes). Le #5 est la demi-période - la moitié du temps clk est haut, l'autre moitié bas.

Qu'est-ce qu'un DUT en Verilog ?

DUT veut dire Design Under Test - le module que le testbench exerce. Par convention on nomme l'instance DUT dut ou u_dut dans le testbench : my_module dut(.clk(clk), .reset(reset), .in(in), .out(out));. Le nom n'est qu'une étiquette ; ce qui compte c'est qu'il soit instancié, que ses ports soient connectés aux signaux du testbench, et que le testbench pilote ces signaux.

Combien de temps une simulation Verilog devrait-elle durer ?

Assez longtemps pour exercer tout ce que tu veux vérifier, puis $finish. La plupart des testbenches fixent une limite de temps explicite (#1000 $finish) pour que la simulation ne puisse pas pendre en attendant un événement qui ne vient jamais. À l'intérieur de cette fenêtre, pilote ton stimulus, laisse le DUT se stabiliser, et idéalement inclus quelques instructions if self-checking qui affichent FAIL si la sortie ne matche pas les attentes.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER