Des modules dans des modules
Un design Verilog est un arbre de modules. Le module top-level (ton testbench, ou le wrapper top-level d'une puce) instancie des modules de plus bas niveau, qui instancient eux-mêmes des modules encore plus bas, jusqu'aux primitives de portes fournies par le constructeur. L'instantiation est la syntaxe de cet emboîtement.
Tu as déjà vu la forme - on l'a utilisée dans Ton premier module :
and_gate dut(.a(a), .b(b), .y(y));
Cette ligne crée une instance unique de and_gate, la nomme dut, et connecte ses ports à des signaux locaux. Décortiquons chaque morceau.
La forme d'une instantiation
module_name instance_name (port_connections);
module_namedoit correspondre au nom d'une déclarationmodulequelque part dans ton projet. Verilog est sensible à la casse.instance_nameest une étiquette que tu choisis - généralement descriptive du rôle de cette instance. Tu l'utiliseras dans les chemins hiérarchiques et les vues de formes d'onde.port_connectionscâble les ports de l'instance à des signaux locaux. Il y a deux façons d'écrire ça.
Connexions de ports nommées (utilise celles-ci)
La forme nommée ressemble à :
my_module instance_name(
.clk (clk),
.reset (reset_n),
.data_in(in_bus),
.data_out(out_bus),
.valid (out_valid)
);
Chaque paire .port(signal) dit « connecte le port de cette instance appelé port au signal local appelé signal ». L'ordre n'a pas d'importance. Si tu ajoutes un nouveau port à la déclaration du module, les instantiations existantes ne se cassent pas tant que tu donnes une valeur par défaut au nouveau port ou que tu mets à jour chaque site.
Deux notes pratiques :
- Le nom du port (à gauche des parenthèses) doit correspondre exactement à la déclaration du module.
- Le nom du signal (à l'intérieur des parenthèses) est local à l'endroit où vit l'instantiation - généralement le module parent.
Si un port n'est pas connecté, laisse l'intérieur vide : .optional_port(). Le signal flotte (z) à l'intérieur de l'instance. Quelques outils de synthèse warnent ; la plupart acceptent.
Connexions de ports positionnelles (évite celles-ci)
La forme concise liste les signaux dans l'ordre de la liste de ports :
my_module instance_name(clk, reset_n, in_bus, out_bus, out_valid);
C'est plus court mais fragile. Réordonner la liste de ports du module (un vrai refactor qui arrive) et chaque instantiation positionnelle est silencieusement mal câblée. Ne sors le positionnel que si la liste de ports a exactement un ou deux membres et qu'elle a peu de chances de changer.
Là où c'est encore acceptable : les minuscules modules utilitaires où l'ordre des ports fait partie de l'API. Une porte à deux entrées en positionnel, ça va. Un contrôleur mémoire à 30 ports, c'est chercher les ennuis.
Un exemple hiérarchique complet
C'est une vraie hiérarchie à trois niveaux : test → full_adder → deux instances half_adder. Chaque instance a sa propre copie des portes à l'intérieur de half_adder ; l'outil de synthèse émettra un circuit par instantiation.
Plusieurs instances du même module
Quand tu instancies le même module plusieurs fois, chaque instance est du matériel indépendant. Elles ne partagent pas d'état. Elles ne partagent pas de portes. Imagine chaque instance comme une copie fraîche estampée sur le design.
adder add0(.a(a0), .b(b0), .sum(s0));
adder add1(.a(a1), .b(b1), .sum(s1));
adder add2(.a(a2), .b(b2), .sum(s2));
adder add3(.a(a3), .b(b3), .sum(s3));
Ce sont quatre additionneurs séparés qui tournent en parallèle. Si adder contenait un registre, chaque instance aurait sa propre copie de ce registre, avec son propre état.
Boucles generate : créer du matériel répété
Écrire quatre instances à la main, c'est OK. En écrire 64, c'est fastidieux. Le bloc generate laisse l'élaborateur taper à ta place :
Trois morceaux de syntaxe nouvelle :
genvar idéclare une variable de boucle utilisable dansgenerate. Ce n'est pas un signal runtime - il n'existe qu'à l'élaboration.generate ... endgenerateentoure la boucle. Certains outils acceptent les boucles generate sans le mot-clégenerateexplicite, mais l'écrire rend l'intention évidente.begin : invert_loopétiquette la portée du generate. L'étiquette devient une partie du nom hiérarchique de chaque instance générée (dut.invert_loop[0].u_inv,dut.invert_loop[1].u_inv, etc.).
Le synthétiseur déroule la boucle et produit WIDTH copies de bit_inverter. Chaque copie est du matériel indépendant.
Override de paramètres à l'instantiation
Si le module a des paramètres, tu peux les surcharger avec #(.PARAM(value)) entre le nom du module et le nom de l'instance :
counter #(.WIDTH(16)) c16 (.clk(clk), .count(out16));
counter #(.WIDTH(32)) c32 (.clk(clk), .count(out32));
Les deux instances utilisent le même source counter mais ont des largeurs différentes. On a couvert la syntaxe dans Paramètres ; elle s'insère proprement dans l'instantiation.
Noms hiérarchiques
Une fois que tu as une hiérarchie, chaque signal a un chemin hiérarchique :
test.dut.ha0.sum
Ça se lit : dans le module test, à l'intérieur de l'instance dut, à l'intérieur de l'instance ha0, le signal nommé sum. Tu verras ces chemins dans les visualiseurs de formes d'onde, les messages d'erreur, et l'occasionnel appel $display qui plonge profondément dans un sous-module depuis un testbench :
$display("internal carry1 = %b", dut.carry1);
Les références hiérarchiques comme ça ne sont que pour les testbenches et le debug - le RTL synthétisable ne touche pas dans d'autres modules.
Erreurs courantes
Erreur de nom de port. .clk_in(clk) connecte le clk local à un port appelé clk_in. Si le port du module est en fait clk, le parser te le dira (certains outils plus clairement que d'autres).
Erreur de largeur sur un port. Connecter un signal 4 bits à un port 8 bits zéro-étend silencieusement ; l'inverse tronque silencieusement. La plupart des outils warnent ; si tu ne vois pas de warning, regarde plus attentivement.
Oublier le # du paramètre. counter (.WIDTH(8)) c(.clk(clk)) a l'air d'un override mais ne l'est pas - le parser essaie de traiter (.WIDTH(8)) comme une connexion de port et échoue. Correct : counter #(.WIDTH(8)) c(.clk(clk)).
Réutiliser un nom d'instance. Deux instances ne peuvent pas avoir le même nom dans la même portée. Le message d'erreur est généralement clair ; c'est la tentation du copier-coller qui te piège.
La suite
Tu peux maintenant câbler des modules ensemble en une vraie hiérarchie. Le prochain document complète le côté structurel de Verilog - Assignation continue - et va plus loin sur l'instruction assign qu'on utilise librement depuis le premier chapitre.
Questions fréquentes
Comment instancier un module en Verilog ?
Écris le nom du module, puis un nom d'instance, puis une liste de connexions de ports entre parenthèses : my_module instance_name(.port(signal), ...);. Le style le plus courant utilise des connexions nommées (.port(signal)), qui s'apparient par nom de port indépendamment de l'ordre. Le style positionnel plus concis (my_module instance(signal1, signal2)) dépend de l'ordre de la liste de ports et est dangereux à maintenir.
Quelle est la différence entre connexions de ports nommées et positionnelles ?
Les connexions positionnelles listent les signaux dans le même ordre que la liste de ports du module - le premier signal se connecte au premier port, le second au second, etc. Les connexions nommées utilisent .port_name(signal_name), en associant par nom. Le nommé est verbeux mais immunisé contre les réordonnancements de ports et s'auto-documente à l'appel. Utilise le nommé pour tout ce qui dépasse deux ou trois ports.
Peut-on instancier le même module Verilog plusieurs fois ?
Oui - c'est tout l'intérêt. Chaque instance est du matériel indépendant avec son propre état. Si tu as un module adder, tu peux l'instancier 64 fois dans une unité SIMD, chacune avec des entrées différentes. La boucle generate est la syntaxe standard quand les instances sont similaires et indexées.
Qu'est-ce qu'un bloc generate en Verilog ?
generate ... endgenerate est une construction à la compilation qui crée du matériel répété. Une boucle for à l'intérieur d'un generate crée N instances de ce qui est dans le corps. generate tourne au moment de l'élaboration, avant que la simulation ne commence - ce n'est pas une boucle runtime, c'est un générateur de code pour le synthétiseur.