Menu

Assignation blocking vs non-blocking en Verilog : quand utiliser = vs <=

Le sujet le plus confus pour les débutants en Verilog. Ce que = et <= font vraiment à l'intérieur d'un bloc always, et la règle qui prévient la plupart des race conditions.

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

Les deux opérateurs

À l'intérieur des blocs always et initial, Verilog a deux opérateurs d'assignation :

  • = est une assignation blocking. Met à jour le LHS maintenant, avant de passer à l'instruction suivante.
  • <= est une assignation non-blocking. Évalue le RHS maintenant, planifie la mise à jour du LHS à la fin du pas de temps courant.

En dehors des blocs procéduraux (dans assign) seul = existe - assign a <= b est une erreur de syntaxe. À l'intérieur des blocs procéduraux, les deux sont légaux, et choisir le bon est la décision la plus importante pour un débutant en Verilog.

Ce que blocking fait

Blocking est ce à quoi tu t'attendrais d'un langage logiciel : ligne par ligne, du haut vers le bas. L'instruction N termine avant que N+1 ne démarre :

C'est purement séquentiel. Chaque instruction voit les effets de celles au-dessus. Ça correspond à l'intuition logicielle.

Ce que non-blocking fait

Non-blocking est en forme de matériel. Tous les côtés droits s'évaluent avec les valeurs au début du pas de temps. Tous les côtés gauches se mettent à jour à la fin du pas de temps. L'ordre des instructions dans le source ne change pas les dépendances entre signaux :

Ce snippet implémente une rotation à 3 voies a → b → c → a en un seul pas. Avec blocking, il te faudrait une variable temporaire pour éviter d'en écraser une. Avec non-blocking, l'échange arrive atomiquement parce que chaque RHS lit les valeurs pré-pas.

C'est exactement comme ça que trois flip-flops se comportent sur un front d'horloge : ils capturent tous leurs entrées au même instant, indépendamment des dépendances entre eux.

La règle qui te sauve

Utilise <= dans les blocs cadencés. Utilise = dans les blocs combinatoires.

C'est tout. Mémorise-la. Code-la sans réfléchir. La plupart des race conditions, mismatches simulation/synthèse, et bugs « ça marche chez moi mais pas en synthèse » viennent de la violation de cette règle.

La règle a une raison matérielle : les blocs cadencés modélisent des flip-flops qui échantillonnent tous leurs entrées simultanément. Les blocs combinatoires modélisent de la logique qui se propage aussi vite qu'elle peut. La sémantique d'opérateur d'assignation correspond à ces comportements.

Chaque <= lit la valeur courante du registre source et planifie le registre destination pour prendre cette valeur à la fin du pas de temps. L'effet net est exactement ce que fait un registre à décalage matériel : chaque flip-flop capture la valeur de son voisin, tous sur le même front d'horloge, pas de race.

Considère maintenant ce qui serait arrivé avec une assignation blocking :

// FAUX - ce n'est pas un registre à décalage !
always @(posedge clk) begin
    out[3] = out[2];   // out[3] devient out[2]
    out[2] = out[1];   // out[2] devient out[1], qu'on vient de fixer au-dessus
    out[1] = out[0];
    out[0] = in;
end

Chaque instruction écrase la source avant que l'instruction suivante ne la lise. En un seul cycle d'horloge, in se propagerait tout du long jusqu'à out[3], parce que chaque ligne voit la valeur fraîchement écrite de celle au-dessus. Le comportement sur le vrai matériel (qui utilise la sémantique non-blocking) serait complètement différent de ce que le simulateur a montré.

Blocs combinatoires : blocking est bon

Pour always @(*), l'assignation blocking est correcte. Pas de flip-flops, pas de règle de capture simultanée à imposer, et les variables intermédiaires sont utiles :

sum est calculé en premier, puis result utilise la valeur fraîchement calculée. La logique combinatoire s'aplatit en un seul morceau de matériel : result = ~(a + b). Aucun flip-flop n'apparaît parce qu'il n'y a pas d'horloge.

Si tu utilisais <= ici, le simulateur mettrait quand même à jour sum avant que result soit évalué contre lui (parce que les deux mises à jour arrivent en fin de pas), mais l'ordre serait subtilement différent et beaucoup d'outils de synthèse se plaignent. Ne mélange pas ; choisis l'opérateur qui correspond au type de bloc.

L'erreur qui fait le plus mal

La voici : un bloc cadencé avec assignation blocking.

// BUG : race condition qui n'attend que d'arriver
always @(posedge clk) begin
    a = b;
    b = c;
    c = a;
end

En simulation, le simulateur peut donner a d'abord, puis b, puis c, produisant un ensemble de valeurs. Le matériel produira un autre ensemble, parce que les vrais flip-flops capturent simultanément. Les deux divergent silencieusement et tu perdras une journée à trouver le bug. Utilise <= dans les blocs cadencés.

Pourquoi deux opérateurs existent du tout

Les concepteurs de Verilog auraient pu choisir une seule sémantique d'assignation et s'y tenir. Ils ne l'ont pas fait, parce que le langage doit modéliser deux comportements matériels distincts :

  • Logique combinatoire : les signaux se propagent en continu, les dépendances comptent, « que calcule cette porte » a du sens.
  • Logique séquentielle : un front d'horloge arrive, chaque flip-flop capture simultanément, les dépendances entre entrées et sorties de flip-flop sont découplées.

Blocking est pour le premier. Non-blocking est pour le second. L'opérateur choisit la sémantique ; le simulateur fait le reste.

La suite

Tu as maintenant les règles pour écrire n'importe quel bloc procédural correctement. Le prochain chapitre passe des blocs individuels aux constructions de control-flow qui vont à l'intérieur : if/else, case et boucles for. Les règles de blocking vs non-blocking s'appliquent toujours à l'intérieur d'eux.

Questions fréquentes

Quelle est la différence entre assignation blocking et non-blocking en Verilog ?

Blocking (=) met à jour la cible immédiatement, avant que l'instruction suivante ne tourne. Elle modélise l'exécution séquentielle. Non-blocking (<=) planifie la mise à jour pour la fin du pas de temps courant - chaque RHS non-blocking est évalué avec les anciennes valeurs de tous les signaux, puis chaque LHS est mis à jour en un seul pas coordonné. Le non-blocking est ainsi que les flip-flops se comportent réellement.

Quand utiliser = vs <= en Verilog ?

Règle : utilise <= dans les blocs cadencés always @(posedge clk), et utilise = dans les blocs combinatoires always @(*). Cette règle unique évite toute la classe de race conditions que les assignations mixtes produisent. À l'intérieur des blocs initial de testbench, = est le choix normal ; <= y apparaît rarement.

Pourquoi l'assignation non-blocking est-elle nécessaire en Verilog ?

Parce que les flip-flops matériels capturent tous leurs entrées simultanément sur un front d'horloge. Si tu utilisais = (blocking) dans du code cadencé, l'ordre des instructions dans ton fichier changerait quel signal voit la nouvelle valeur de quel autre signal - une race condition entre la simulation et le vrai matériel. <= correspond au comportement matériel en évaluant tous les RHS d'abord, puis en faisant toutes les mises à jour.

Que se passe-t-il si on mélange = et <= dans le même bloc always Verilog ?

Tu obtiens une race condition. Le mélange produit du matériel dont le comportement dépend des internes du simulateur (ordre de planification d'événements), et pire, la simulation peut ne pas correspondre à ce que la synthèse produit. La plupart des outils de lint signalent ça comme une erreur. La correction est de s'engager sur l'un ou l'autre selon le rôle du bloc - non-blocking pour cadencé, blocking pour combinatoire.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER