Le pilier du Verilog comportemental
assign décrit de la logique combinatoire à équation unique. Dès que tu as besoin de if/else, case ou de mémoire, tu vas chercher always. Un bloc always est un morceau de code procédural qui se ré-exécute quand des signaux spécifiques changent. Les signaux qui déclenchent la ré-exécution sont la liste de sensibilité.
Il y a deux formes de always que tu verras le plus souvent :
always @(*)- se ré-exécute quand n'importe quel signal lu dans le bloc change. Construit de la logique combinatoire.always @(posedge clk)- se ré-exécute seulement au front montant declk. Construit de la logique séquentielle cadencée (flip-flops).
D'autres formes existent (@(a or b), @(negedge clk), @(posedge clk or negedge reset_n)) mais les deux ci-dessus représentent presque tous les blocs synthétisables que tu écriras.
always @(*) combinatoire
Trois choses à remarquer :
outestreg. Tout ce qui est assigné à l'intérieur dealwaysdoit êtrereg. Le mot-clé ne veut pas dire « c'est un flip-flop » ; ici ça veut juste dire « je suis écrit depuis un bloc procédural ».always @(*). Le*dit « réveille-toi quand quoi que ce soit que je lis change ». Le simulateur calcule automatiquement la liste de sensibilité. Tu peux écrire la liste manuellement -always @(sel)- mais@(*)est plus sûr car oublier un signal est une source classique de bugs.- Pas d'horloge. Ce bloc décrit de la logique combinatoire. Le synthétiseur produit une portion de logique qui calcule
outà partir deseldirectement - pas de flip-flop, pas de broche d'horloge nécessaire.
Le cas default n'est pas optionnel dans l'esprit même s'il est optionnel dans la syntaxe. Omets-le et n'importe quelle valeur d'entrée non spécifiée laisse out conserver sa valeur précédente - ce qui se synthétise en un latch involontaire. Inclus toujours le default.
always @(posedge clk) séquentiel
Les différences clés par rapport à la version combinatoire :
always @(posedge clk). Le bloc ne se ré-exécute qu'au front montant declk. Rien ne se passe entre les fronts.- Assignation non-blocking
<=. À l'intérieur d'un bloc cadencé, c'est le bon opérateur. Il dit « planifiecountpour prendre sa nouvelle valeur à la fin du pas de temps », ce qui est exactement le comportement d'un flip-flop. Le pourquoi et l'alternative sont dans Blocking vs Non-blocking. - Pas besoin de
default. Leifcouvre les deux branches (reset et non-reset). Pas de risque de latch.
Le synthétiseur voit cette forme - sensibilité cadencée, assignation non-blocking - et produit un registre 4 bits (quatre flip-flops) plus la logique combinatoire qui calcule count + 1 et le mux qui choisit entre reset et incrément.
La distinction de synthèse
Le même source de module peut décrire deux morceaux de matériel complètement différents selon la forme du bloc always :
| Bloc | Matériel |
|---|---|
always @(*) y = expr; | Logique combinatoire pure. Pas de mémoire. |
always @(posedge clk) y <= expr; | Flip-flop. Capture expr une fois par cycle d'horloge. |
always @(*) if (en) y = expr; | Latch - généralement un bug. Le cas « else » garde l'ancienne valeur. |
always @(posedge clk) if (en) y <= expr; | Flip-flop avec enable. Ne capture que quand en est haut. |
Le troisième cas est le piège du latch. Un latch est une cellule mémoire transparente qui maintient sa sortie quand l'entrée n'est pas assertée - utile dans des designs spécifiques, presque toujours un bug quand accidentel. La plupart des outils de synthèse warnent fort quand ils infèrent un latch que tu n'as pas demandé. Traite le warning comme une erreur.
Variantes de liste de sensibilité
Tu verras quelques listes de sensibilité moins courantes :
always @(a or b or c)- liste explicite. Verilog-2001 a ajouté le séparateur,:always @(a, b, c). L'un comme l'autre marche.always @(posedge clk or negedge reset_n)- reset asynchrone. Le bloc tourne sur un front montant d'horloge ou un front descendant de reset. Utilisé quand le reset doit prendre effet immédiatement, sans attendre la prochaine horloge.always @(negedge clk)- cadencement sur front descendant. Rare ; certains designs l'utilisent pour des flip-flops « déclenchés par front descendant » qui capturent sur le front descendant au lieu du montant.
Pour les nouveaux designs, préfère always @(*) pour le combinatoire et always @(posedge clk) pour le séquentiel. Ne tends la main vers le reset asynchrone que quand le design en a vraiment besoin.
Deux blocs sont deux morceaux de matériel
Plusieurs blocs always dans le même module sont indépendants - chacun devient son propre morceau de matériel :
Le bloc cadencé produit un registre flip-flop. Le bloc combinatoire produit une porte XOR. Ils vivent côte à côte ; aucun ne connaît l'autre. Les deux sorties changent selon des plannings complètement différents.
Ce que les blocs always ne peuvent pas faire
Quelques choses qui paraissent tentantes mais ne sont pas permises :
- Assigner à un
wire: la cible doit êtrereg. Le compilateur l'impose. - Assigner au même
regdepuis deux blocsalwaysdifférents : produit un comportement indéfini en simulation et ne se synthétisera pas. Un pilote par signal. - Lire et écrire le même signal dans le même bloc combinatoire d'une façon qui crée une boucle de feedback :
always @(*) x = x + 1;est une boucle à délai zéro que le simulateur ne peut pas résoudre.
Les deux premiers, le compilateur les attrape. Le troisième n'apparaît parfois qu'à la simulation comme un hang.
La suite
Le prochain document - Bloc initial - couvre le frère de always : un bloc qui tourne exactement une fois au démarrage de la simulation. C'est le pilier des testbenches. Ensuite, les règles de blocking vs non-blocking qui décident si ton bloc cadencé fait ce que tu voulais.
Questions fréquentes
Qu'est-ce qu'un bloc always en Verilog ?
always introduit un bloc procédural qui se ré-exécute chaque fois que les signaux de sa liste de sensibilité changent. Il y a deux saveurs : always @(*) construit de la logique combinatoire (se ré-exécute quand n'importe quelle entrée change), et always @(posedge clk) construit de la logique séquentielle (se ré-exécute à chaque front montant de clk). Le corps d'un bloc always peut contenir if, case, for et des assignations procédurales.
Quelle est la différence entre always @(*) et always @(posedge clk) ?
always @(*) est sensible à tout signal lu dans le bloc ; il produit de la logique combinatoire sans mémoire. always @(posedge clk) n'est sensible qu'au front montant de clk ; il produit des flip-flops qui capturent l'état une fois par cycle d'horloge. Le premier n'a ni horloge ni registre ; le second a les deux.
Qu'est-ce qu'une liste de sensibilité en Verilog ?
La liste de signaux après @ qui détermine quand un bloc always se ré-exécute. @(*) est un raccourci pour « chaque signal lu dans le bloc ». @(posedge clk) ne tourne que sur le front montant de clk. @(posedge clk or negedge reset_n) tourne sur l'un ou l'autre événement - utilisé pour les resets asynchrones. Se tromper sur la liste de sensibilité est l'une des sources les plus courantes de mismatch simulation/synthèse.
Peut-on assigner à un wire à l'intérieur d'un bloc always ?
Non. Les blocs always ne peuvent assigner qu'à reg (ou logic en SystemVerilog). Le compilateur l'impose. Si tu veux qu'un wire soit la sortie d'une logique procédurale, déclare un reg intermédiaire, pilote-le à l'intérieur de always, et assign le wire depuis le reg à l'extérieur - ou change simplement le wire en reg.