Mots-clés : bascule, verrou

La signification du verrou

Un verrou est un élément de mémorisation sensible au niveau. L'action de mémorisation des données dépend de la valeur de niveau du signal d'horloge (ou d'activation) en entrée. Ce n'est que lorsque le verrou est à l'état activé que la sortie change avec l'entrée de données.

Lorsque le signal de niveau est inactif, le signal de sortie change avec le signal d'entrée, comme s'il traversait un tampon ; lorsque le niveau est actif, le signal de sortie est verrouillé. Toute modification du signal d'excitation provoquera directement un changement de l'état de sortie du verrou, ce qui est très susceptible de provoquer une oscillation en raison de caractéristiques transitoires instables.

Le schéma de principe d'un verrou est le suivant :

Une bascule est un élément de mémorisation sensible aux fronts. L'action de mémorisation des données (transition d'état) est synchronisée par le front montant ou descendant d'un signal (ce qui limite la transition d'état de l'élément de mémorisation à une très courte période de temps).

Le schéma de principe d'une bascule est le suivant :

Un registre est une variable en Verilog utilisée pour stocker temporairement les données impliquées dans les opérations et les résultats d'opération. Lorsqu'une variable est déclarée comme registre, elle peut être synthétisée en bascule, en verrou, ou même en variable de type wire. Dans la plupart des cas, cependant, nous nous attendons à ce qu'elle soit synthétisée en bascule, mais parfois, en raison de problèmes d'écriture de code, elle peut être synthétisée en une structure de verrou non souhaitée.

Les principaux dangers des verrous sont :

  • 1) L'état d'entrée peut changer plusieurs fois, ce qui provoque facilement des glitches et augmente l'incertitude du circuit de l'étage suivant ;
  • 2) Dans la plupart des ressources FPGA, implémenter une structure de verrou peut nécessiter plus de ressources qu'une bascule ;
  • 3) La présence de verrous rend l'analyse temporelle statique plus complexe.

Les verrous sont principalement utilisés pour le contrôle d'horloge. Dans la conception générale, nous devons éviter la génération de verrous.

Structure if incomplète

En logique combinatoire, une structure if-else incomplète génère un verrou.

Par exemple, dans le modèle suivant, l'instruction if n'a pas de structure else. Par défaut, la valeur du registre q reste inchangée dans la branche else, ce qui signifie qu'il a la fonction de stocker des données. Par conséquent, le registre q sera synthétisé en une structure de verrou.

Exemple

module module1_latch1(
    input       data,
    input       en ,
    output reg  q) ;
   
    always @(*) begin
        if (en) q = data ;
    end

endmodule

Il existe principalement deux méthodes pour éviter ces verrous : l'une consiste à compléter la structure if-else, ou à attribuer des valeurs initiales au signal.

Par exemple, l'instruction always dans le modèle ci-dessus peut être modifiée sous les deux formes suivantes :

Exemple

    // Compléter la structure de branchement conditionnel
    always @(*) begin
        if (en)  q = data ;
        else     q = 1'b0 ;
    end

    // Attribuer la valeur initiale
    always @(*) begin
        q = 1'b0 ;
        if (en) q = data ; // Si en est valide, réécrire la valeur de q ; sinon q restera 0.
    end

Cependant, en logique séquentielle, une structure if-else incomplète ne générera pas de verrou, comme le montre le modèle suivant.

C'est parce que le registre q a une capacité de stockage et que sa valeur ne change qu'au front d'horloge, ce qui est exactement la caractéristique d'une bascule.

Exemple

module module1_ff(
    input       clk ,
    input       data,
    input       en ,
    output reg  q) ;
   
    always @(posedge clk) begin
        if (en) q <= data ;
    end

endmodule

En logique combinatoire, lorsqu'il y a de nombreuses instructions d'affectation dans une instruction conditionnelle, des instructions d'affectation incomplètes sous chaque condition de branche généreront également un verrou.

En fait, du point de vue de la décomposition logique de chaque signal, cela équivaut à une structure if-else incomplète, où le signal de registre associé n'a pas de comportement d'affectation sous d'autres conditions. Par exemple :

Exemple

module module1_latch11(
    input       data1,
    input       data2,
    input       en ,
    output reg  q1 ,
    output reg  q2) ;
   
    always @(*) begin
        if (en)   q1 = data1 ;
        else      q2 = data2 ;
    end

endmodule

Cette situation peut également être évitée en complétant les instructions d'affectation ou en attribuant des valeurs initiales. Par exemple :

Exemple

    always @(*) begin
        // q1 = 0; q2 = 0; // ou attribuez ici des valeurs initiales à q1/q2
        if (en)  begin
            q1 = data1 ;
            q2 = 1'b0 ;
        end
        else begin
            q1 = 1'b0 ;
            q2 = data2 ;
        end
    end

Structure case incomplète

Le principe des latches générés par les instructions case est presque le même que pour les instructions if. En logique combinatoire, lorsque la liste des éléments case est incomplète et qu'aucun mot-clé default n'est ajouté, ou que plusieurs instructions d'affectation sont incomplètes, un latch sera également généré. Par exemple :

Exemple

module module1_latch2(
    input       data1,
    input       data2,
    input [1:0] sel ,
    output reg  q ) ;
   
    always @(*) begin
        case(sel)
            2'b00:  q = data1 ;
            2'b01:  q = data2 ;
        endcase
    end

endmodule

Bien sûr, il existe également deux méthodes pour éliminer de tels latches : compléter la liste des éléments case, ou attribuer des valeurs initiales au signal.

Lorsque vous complétez la liste des éléments case, vous pouvez lister tous les cas d'élément, ou utiliser le mot-clé default pour couvrir les autres cas d'élément.

Par exemple, l'instruction always ci-dessus peut être modifiée des deux manières suivantes.

Exemple

    always @(*) begin
        case(sel)
            2'b00:    q = data1 ;
            2'b01:    q = data2 ;
            default:  q = 1'b0 ;
        endcase
    end

    always @(*) begin
        case(sel)
            2'b00:  q = data1 ;
            2'b01:  q = data2 ;
            2'b10, 2'b11 :  
                    q = 1'b0 ;
        endcase
    end

Affectation ou jugement du signal d'origine

En logique combinatoire, si la source d'une affectation de signal inclut le signal lui-même, ou si la condition de jugement contient une logique pour le signal lui-même, un latch sera également généré. Cela est dû au fait que le signal doit aussi avoir une capacité de stockage, mais aucune horloge ne le pilote. De tels problèmes peuvent survenir dans les instructions if, les instructions case et les expressions ternaires, par exemple :

Exemple

    //signal lui-même comme partie de la condition
    reg a, b ;
    always @(*) begin
        if (a & b)  a = 1'b1 ;   //a -> latch
        else a = 1'b0 ;
    end
   
    //le signal lui-même est la source d'affectation
    reg        c;
    wire [1:0] sel ;
    always @(*) begin
        case(sel)
            2'b00:    c = c ;    //c -> latch
            2'b01:    c = 1'b1 ;
            default:  c = 1'b0 ;
        endcase
    end

    //le signal lui-même comme partie d'une condition dans "? expression"
    wire      d, sel2;
    assign    d =  (sel2 && d) ? 1'b0 : 1'b1 ;  //d -> latch

Il n'y a qu'une seule façon d'éviter de tels latches : évitez ce style de codage dans la logique combinatoire. N'affectez pas un signal à partir de lui-même, et n'utilisez pas le signal affecté lui-même dans la logique conditionnelle.

Par exemple, si une sortie immédiate n'est pas nécessaire, le signal peut être retardé d'un cycle d'horloge avant de combiner la logique associée. Le premier code ci-dessus qui a généré un latch peut être décrit comme suit :

Exemple

    reg   a, b ;
    reg   a_r ;
   
    always (@posedge clk)
        a_r  <= a ;
       
    always @(*) begin
        if (a_r & b)  a = 1'b1 ;   //il n'y a pas de latch
        else a = 1'b0 ;
    end

Liste des signaux sensibles incomplète

Si la liste de sensibilité dans le bloc always@() de la logique combinatoire n'est pas complète, et que celui-ci n'est pas déclenché quand il devrait l'être, le registre associé conservera encore le résultat de sortie précédent, générant ainsi un latch.

Dans ce cas, compléter la liste des signaux sensibles ou utiliser directement always@(*) éliminera le latch.

Résumé

En bref, pour éviter la génération de latches, il faut noter les points suivants en logique combinatoire :

  • 1) La structure des instructions if-else ou case doit être complète.
  • 2) Ne placez pas le signal affecté dans la source d'affectation ni dans les conditions de jugement.
  • 3) Il est recommandé d'utiliser always@(*) pour la liste des signaux sensibles.

Téléchargement du code source

Téléchargement