Mots-clés : affectation bloquante, affectation non bloquante, parallèle

Une affectation procédurale est une affectation à l’intérieur d’un bloc d’instructions initial ou always, et les objets d’affectation sont des types tels que les registres, les entiers, les réels, etc.

Après l’affectation de ces variables, leurs valeurs restent inchangées jusqu’à ce qu’elles reçoivent de nouvelles valeurs.

L'affectation continue est toujours active ; toute modification d'un opérande affectera le résultat de l'expression. L'affectation procédurale ne prend effet que lorsque l'instruction est exécutée. C'est la différence entre l'affectation continue et l'affectation procédurale.

L'affectation procédurale Verilog comprend deux types d'instructions : l'affectation bloquante et l'affectation non bloquante.

Affectation bloquante

L'affectation bloquante est une exécution séquentielle, ce qui signifie qu'avant que l'instruction suivante ne soit exécutée, l'instruction courante doit être terminée.

Les instructions d'affectation bloquante utilisent le signe égal=comme opérateur d'affectation.

Dans les simulations précédentes, les instructions d'affectation dans les blocs initial utilisaient toutes l'affectation bloquante.

Affectation non bloquante

L'affectation non bloquante est une instruction d'exécution parallèle, ce qui signifie que l'instruction suivante s'exécute simultanément avec l'instruction courante ; elle ne bloque pas l'exécution des instructions suivantes dans le même bloc.

Les instructions d'affectation non bloquante utilisent le signe inférieur ou égal<=comme opérateur d'affectation.

Utilisez le code suivant pour simuler les affectations bloquante et non bloquante afin d'illustrer la différence entre les deux affectations procédurales.

Exemple

`timescale 1ns/1ns
 
module test ;
    reg [3:0]   ai, bi ;
    reg [3:0]   ai2, bi2 ;
    reg [3:0]   value_blk ;
    reg [3:0]   value_non ;
    reg [3:0]   value_non2 ;
 
    initial begin
        ai            = 4'd1 ;   //(1)
        bi            = 4'd2 ;   //(2)
        ai2           = 4'd7 ;   //(3)
        bi2           = 4'd8 ;   //(4)
        #20 ;                    //(5)
 
        //assignation non bloquante avec assignation bloquante
        ai            = 4'd3 ;     //(6)
        bi            = 4'd4 ;     //(7)
        value_blk     = ai + bi ;  //(8)
        value_non     <= ai + bi ; //(9)
 
        //assignation non bloquante elle-même
        ai2           <= 4'd5 ;           //(10)
        bi2           <= 4'd6 ;           //(11)
        value_non2    <= ai2 + bi2 ;      //(12)
    end
 
   //arrêter la simulation
    always begin
        #10 ;
        if ($time >= 1000) $finish ;
    end
 
endmodule

Les résultats de simulation sont les suivants :

Les instructions (1)-(8) sont toutes des assignations bloquantes et s'exécutent séquentiellement.

Avant 20ns, les valeurs des signaux ai et bi changent. En raison des caractéristiques de l'affectation procédurale, value_blk = ai + bi n'a pas été exécuté, donc avant 20ns, la valeur de value_blk est X (état inconnu).

Après 20ns, les valeurs des signaux ai et bi changent à nouveau. Lorsque value_blk = ai + bi est exécuté, le signal value_blk utilise les nouvelles valeurs des signaux ai et bi pour obtenir le résultat du calcul 7.

Les instructions (9)-(12) sont toutes des affectations non bloquantes et s'exécutent en parallèle.

Premièrement, bien que (9)-(12) soient exécutées simultanément, leur ordre d'exécution reste après (8), donc lorsque le signal value_non = ai + bi est calculé, il utilise également les nouvelles valeurs des signaux ai et bi, ce qui donne 7.

Deuxièmement, (10)-(12) sont exécutées simultanément, donc lorsque value_non2 = ai2 + bi2 est calculé, il ne tient pas compte des derniers résultats d'affectation non bloquante des signaux ai2 et bi2. Autrement dit, value_non2 utilise les anciennes valeurs des signaux ai2 et bi2, et le résultat est 4'hF.

Utiliser l'affectation non bloquante pour éviter les conditions de course

Le code de simulation ci-dessus vise uniquement à aider les lecteurs à mieux comprendre la différence entre les affectations bloquantes et non bloquantes. Dans une conception Verilog réelle, rappelez-vous de ne pas mélanger les affectations bloquantes et non bloquantes au sein de la même structure procédurale. Lorsque les deux styles d'affectation sont mélangés, le timing est difficile à contrôler et des résultats inattendus peuvent facilement se produire.

Le plus souvent, lors de la conception de circuits, l'affectation non bloquante est principalement utilisée dans les blocs always de logique séquentielle, et l'affectation bloquante est principalement utilisée dans les blocs always de logique combinatoire. Lors de la simulation de circuits, l'affectation bloquante est généralement utilisée dans les blocs initial.

Comme montré ci-dessous, pour implémenter la fonction d'échange des valeurs de deux registres sur le front montant de l'horloge, une affectation bloquante est utilisée dans deux blocs always.

Parce que les instructions dans les deux blocs always s'exécutent simultanément, mais que l'ordre d'exécution de a=b et b=a ne peut pas être déterminé, cela crée une condition de course.

Mais quel que soit celui qui s'exécute en premier (en lien avec le compilateur, etc.), sans tenir compte des problèmes de temporisation, leur ordre d'exécution est toujours séquentiel, et au final les valeurs de a et b sont toujours égales. L'effet d'échange des deux valeurs de registre n'est pas obtenu.

Exemple

always @(posedge clk) begin
    a = b ;
end
 
always @(posedge clk) begin
    b = a;
end

Cependant, si l'affectation non bloquante est utilisée dans les blocs always, la condition de course ci-dessus peut être évitée.

Comme montré ci-dessous, les instructions dans les deux blocs always s'exécutent en parallèle. Les opérandes du côté droit de l'affectation utilisent les anciennes valeurs du cycle d'horloge précédent. À ce moment, a<=b et b<=a peuvent s'exécuter sans interférer l'un avec l'autre, ce qui permet d'atteindre l'objectif d'échanger les valeurs de registre.

Exemple

always @(posedge clk) begin
    a <= b ;
end
 
always @(posedge clk) begin
    b <= a;
end

Bien sûr, le code suivant peut également réaliser la fonction d'échange des valeurs de registre, mais il n'est clairement pas aussi simple et intuitif que l'utilisation directe de l'affectation non bloquante dans les blocs always.

Exemple

always @(posedge clk) begin
    temp    = a ;
    a       = b ;
    b       = temp ;
end

Téléchargement du code source

Téléchargement