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
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
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
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
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
// 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
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
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
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_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