Syntaxe familière, modèle mental différent
if/else ressemble exactement à C :
if (condition) begin
// ... instructions ...
end else begin
// ... instructions ...
end
Mais les règles sont différentes parce que Verilog n'est pas du logiciel. Deux choses à garder en tête :
if/elsene vit qu'à l'intérieur d'un bloc procédural. Tu ne peux pas écrire unifautonome au niveau module.- Ce en quoi se synthétise le
if/elsedépend du type de bloc. Dans un bloc combinatoire, ça devient un multiplexeur ou encodeur de priorité. Dans un bloc cadencé, ça devient un flip-flop avec une logique de mise à jour conditionnelle.
À l'intérieur d'un bloc combinatoire
Le bloc combinatoire always @(*) se ré-exécute chaque fois que a, b ou c change. La chaîne if/else choisit une branche, assigne max, et le bloc se termine. Comme max est toujours assigné (chaque chemin a une assignation), le synthétiseur produit de la logique combinatoire pure - pas de latch.
Note que max est déclaré reg même s'il n'y a pas de flip-flop dans le matériel. Même règle qu'avant : tout ce qui est assigné à l'intérieur de always doit être reg.
Le piège du latch
C'est le bug le plus courant dans le code combinatoire débutant :
// FAUX - infère un latch
always @(*) begin
if (enable)
out = data;
// pas de else ! quand `enable` est bas, que fait `out` ?
end
Le synthétiseur lit « quand enable est bas, out n'est pas assigné » et décide que out doit se souvenir de sa valeur précédente. Se souvenir d'une valeur nécessite une cellule de stockage, donc l'outil insère un latch. Les latches dans les designs synchrones causent des problèmes de timing, sont durs à reset, et ne sont presque jamais ce que tu voulais dire.
Deux façons de corriger :
Les deux produisent le même matériel combinatoire - un mux 2 vers 1. Le motif « défaut en haut » passe mieux à l'échelle quand tu as beaucoup d'assignations conditionnelles vers le même signal.
À l'intérieur d'un bloc cadencé
Remarque ce qui est différent du cas combinatoire :
- Le bloc est
always @(posedge clk)- territoire flip-flop. - L'assignation utilise
<=(non-blocking). - Pas de
elsepour le cas « ni reset ni enable ». C'est OK. Dans un bloc cadencé, quand aucune branche ne tire, le flip-flop garde simplement sa valeur précédente - ce qui est exactement ce qu'un flip-flop fait physiquement. Aucun latch n'est inféré parce que le signal est déjà un registre.
C'est le seul endroit où omettre un else est sûr. En dehors des blocs cadencés, gère toujours chaque chemin.
else if chaînés : un encodeur de priorité
Une chaîne d'instructions else if a une priorité implicite - les conditions plus anciennes l'emportent sur les plus récentes :
requests[0] a la priorité la plus haute - s'il est mis, le grant est 0 quelle que soit l'action des bits plus haut. Le synthétiseur transforme la chaîne en mux en cascade : vérifie bit 0 d'abord, puis bit 1, puis bit 2, puis bit 3. Chaque niveau ajoute un petit délai.
Si les conditions sont mutuellement exclusives - disons décoder une entrée one-hot - une instruction case (prochain document) produit du matériel plus plat et plus rapide qu'une chaîne else if. Utilise la forme case quand il n'y a pas d'exigence de priorité véritable.
if sans else dans le code cadencé
Un bloc cadencé n'a pas besoin d'un else parce que « garder la valeur précédente » est la valeur par défaut. C'est ainsi qu'on construit des enables :
always @(posedge clk) begin
if (load) target <= incoming;
// pas de else : quand load est bas, target garde sa valeur
end
C'est un registre à load enable. La plupart des registres de pipeline, compteurs et registres de configuration utilisent ce motif.
begin/end et instructions uniques
Comme en C, tu peux omettre begin/end pour une seule instruction :
if (a) out = 1;
else out = 0;
Pour tout ce qui dépasse une instruction, utilise le bloc :
if (a) begin
out = 1;
flag = 1;
end else begin
out = 0;
flag = 0;
end
Les deux motifs se mélangent librement. Les guides de style recommandent généralement d'utiliser toujours begin/end pour rendre l'ajout d'une seconde instruction indolore.
La suite
Le prochain document - Instruction case - couvre case, qui est le bon outil pour le décodage multi-voies (machines à états, dispatch d'opcodes, tables ROM). Ensuite, les boucles for, qui sont subtilement différentes de leurs cousines logicielles parce qu'elles sont déroulées à l'élaboration.
Questions fréquentes
Comment fonctionne une instruction if en Verilog ?
if (cond) statement; exécute statement quand cond est non-zéro. Tu peux enrober plusieurs instructions dans begin ... end. Ajoute else statement; pour la branche alternative, ou chaîne avec else if (other_cond) .... if/else n'existe qu'à l'intérieur des blocs procéduraux - initial ou always - pas au niveau supérieur d'un module.
Qu'est-ce qu'un latch inféré en Verilog ?
Un latch que l'outil de synthèse a créé sans que tu le demandes, parce que ton bloc always combinatoire n'a pas assigné un signal dans chaque chemin. L'outil voit « if a then out = 1 » sans else, décide que le cas non assigné doit se souvenir de la valeur précédente, et produit un latch. Les latches sont presque toujours faux ; la correction est de donner une valeur par défaut à chaque signal en haut du bloc ou un else explicite.
Comment éviter les latches inférés en Verilog ?
Dans un bloc combinatoire always @(*), assure-toi que chaque reg de sortie est assigné dans chaque chemin de code. Le motif le plus propre est de mettre des défauts en haut du bloc et de surcharger conditionnellement ensuite. Le compilateur warne généralement quand il infère un latch - traite le warning comme une erreur.
Que synthétise une chaîne if-else en Verilog ?
Un encodeur de priorité. Le premier if a la priorité la plus haute, le else if suivant n'est vérifié que si le premier est faux, et ainsi de suite. En matériel ça devient une chaîne de muxes avec l'ordre de priorité gravé. Si les conditions sont mutuellement exclusives, une instruction case avec la même logique se synthétise souvent en matériel plus plat et se lit plus clairement.