Un module est l'unité de Verilog
Tout en Verilog vit à l'intérieur d'un module. Un module encapsule un morceau de circuit : il déclare quels signaux entrent, quels signaux sortent, et quels wires/registres/logiques se placent entre eux. Chaque puce que tu as vue est un arbre de modules qui instancient d'autres modules, jusqu'aux primitives de portes fournies par le constructeur.
La forme est toujours la même :
module name(port_list);
// déclarations : wire, reg, parameter
// corps : assigns, instantiations, blocs always
endmodule
On va construire un module complet par étapes.
Le plus petit module utile : une porte AND à deux entrées
Il nous faut quelque chose de simple et autonome. Une porte AND à deux entrées est parfaite : deux entrées, une sortie, une ligne de logique.
Appuie sur Run. Tu devrais voir les quatre lignes d'une table de vérité AND. Passons en revue chaque morceau.
Lire la déclaration du module
module and_gate(
input wire a,
input wire b,
output wire y
);
module and_gatedéclare un module nomméand_gate. C'est par ce nom que les autres modules vont l'instancier.- La liste entre parenthèses est la liste de ports - les signaux visibles depuis l'extérieur.
input wire a-aest un port d'entrée ; c'est unwire(piloté depuis l'extérieur).output wire y-yest un port de sortie piloté par quelque chose à l'intérieur du module.
Si tu voulais être plus concis, tu pourrais écrire input a au lieu de input wire a - la direction seule fixe le type par défaut à wire. Mais être explicite est une bonne habitude à prendre. Ports de module couvre l'ensemble des formes de ports.
Le corps
assign y = a & b;
C'est une assignation continue. Elle dit « chaque fois que a ou b change, recalcule y comme le AND bit-à-bit des deux ». Pas d'horloge, pas de timing - la relation est toujours vraie. C'est de la pure logique combinatoire.
endmodule ferme le bloc. Le module est terminé.
Le testbench
Tu ne peux pas exécuter and_gate seul. Il te faut un second module qui pilote ses entrées et observe ses sorties. C'est le testbench, et par convention on l'appelle test, tb ou <design>_tb.
module test;
reg a, b;
wire y;
and_gate dut(.a(a), .b(b), .y(y));
...
endmodule
Trois choses à remarquer :
- Pas de liste de ports. Un testbench est au sommet de la simulation - rien n'est au-dessus.
reg a, betwire y. Les entrées du design under test (DUT) sont desregdans le testbench parce qu'on les pilote depuis un bloc procédural. La sortie est unwireparce que le DUT la pilote.and_gate dut(.a(a), .b(b), .y(y)). C'est l'instantiation. On crée une copie deand_gateet on l'appelledut(nom courant - « design under test »). La syntaxe.a(a)dit « connecte le port nomméasur l'instance au signal local nomméa». Instantiation de module va plus loin.
Le stimulus
initial begin
a = 0; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 0; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 0; #1 $display("a=%b b=%b y=%b", a, b, y);
a = 1; b = 1; #1 $display("a=%b b=%b y=%b", a, b, y);
$finish;
end
Le bloc initial s'exécute une fois au démarrage de la simulation. À l'intérieur :
a = 0; b = 0;pilote les entrées. Ce sont des assignations blocking - l'ordre compte, et chacune se produit avant la suivante.#1avance le temps simulé d'1 unité. On en a besoin pour queyait le temps de se stabiliser après le changement des entrées. Sans le#1,$displayafficherait l'ancienne valeur dey.$display(...)affiche dans la console de simulation. La chaîne de format fonctionne commeprintfde C :%bc'est binaire,%dc'est décimal,%hc'est hex,%tc'est le temps simulé.$finishtermine la simulation. Sans ça, le simulateur continuerait à faire avancer le temps indéfiniment en attendant un événement qui ne vient jamais.
Essaie de casser ça
Modifie le module pour en faire une porte OR (change & en |) et relance. La table de vérité change. Maintenant essaie un XOR :
Même squelette. Opérateur différent. C'est tout le jeu pour la logique combinatoire - déclarer les ports, écrire les assigns, balayer les entrées, observer les sorties.
Un module à deux sorties
Les modules peuvent avoir plus d'une sortie. Voici un demi-additionneur - deux entrées, somme et retenue sortante :
Les deux instructions assign vivent côte à côte mais se passent en parallèle - pas de « d'abord calculer sum puis calculer carry ». Les deux sont toujours vraies. C'est la concurrence dont on parlait dans Matériel vs logiciel, rendue concrète.
Ce que tu sais maintenant
Tu as vu tout le squelette d'un fichier Verilog : un module de design avec des ports déclarés et un corps, plus un module testbench qui l'instancie, pilote ses entrées dans un bloc initial et rapporte les résultats. Presque chaque fichier source Verilog que tu liras tient dans ce template. Le reste du langage n'est que du remplissage avec de la logique plus riche, des signaux multi-bits, du comportement cadencé et des testbenches plus gros.
Prochaine étape : les commentaires et le style de code, pour que tes modules restent lisibles quand ils grossissent.
Questions fréquentes
Quel est le module Verilog le plus simple ?
Le plus petit module Verilog légal est juste module name; endmodule - pas de ports, pas de corps. Le plus petit utile est un module à une sortie : module and_gate(input wire a, input wire b, output wire y); assign y = a & b; endmodule. C'est un vrai morceau de logique combinatoire que tu peux insérer dans n'importe quel design plus large.
Comment exécuter un module Verilog ?
Tu ne peux pas exécuter un module tout seul - c'est une description de circuit, pas un programme. Tu écris un testbench qui instancie ton design et pilote ses entrées, puis tu compiles les deux avec iverilog -o sim design.v test.v et tu lances vvp sim. L'éditeur en ligne sur cette page fait les deux étapes pour toi quand tu appuies sur Run.
Qu'est-ce qu'un testbench en Verilog ?
Un testbench est un second module - généralement sans ports - dont le rôle est d'exercer ton design. Il instancie le design, agite ses entrées via un bloc initial, observe les sorties avec $display ou $monitor, et appelle $finish quand c'est fini. Les testbenches ne sont pas synthétisables ; ils n'existent que pour vérifier le comportement.
Pourquoi mon code Verilog a-t-il besoin de $finish ?
Parce que le matériel ne s'arrête jamais. Un simulateur fait semblant que le temps passe, et sans $finish explicite il continuerait à avancer indéfiniment en attendant de nouveaux événements. $finish dit au simulateur « c'est fini, sors proprement ». Dans un testbench c'est la dernière ligne du bloc initial - lance le test, puis termine.