Décrire la logique séquentielle
Créer des registres cadencés, gérer reset et enable, puis comprendre les affectations non bloquantes.
Quand le circuit garde un état
La logique séquentielle mémorise une valeur d'un cycle à l'autre. Une bascule met généralement à jour sa sortie sur un front d'horloge.
module data_register (
input wire i_clk,
input wire i_reset,
input wire i_enable,
input wire [7:0] i_data,
output reg [7:0] o_data
);
always @(posedge i_clk) begin
if (i_reset)
o_data <= 8'h00;
else if (i_enable)
o_data <= i_data;
end
endmoduleposedge i_clk déclenche le bloc au front montant. Si i_enable vaut 0, aucune nouvelle affectation n'est faite et le registre garde sa valeur. C'est ici volontaire.
Reset synchrone
Dans l'exemple précédent, le reset est synchrone. o_data revient à zéro uniquement sur un front montant où i_reset vaut 1.
Le reset fait partie du comportement du circuit. Son niveau actif, son caractère synchrone ou asynchrone et les registres concernés doivent être définis dans les besoins du bloc.
Il n'est pas nécessaire de remettre à zéro chaque registre d'un design. Un reset inutile peut augmenter le routage, empêcher certaines inférences de mémoire ou compliquer le timing. Un registre doit surtout démarrer dans un état défini lorsque le fonctionnement l'exige.
Reset asynchrone
Un reset asynchrone apparaît dans la liste d'événements :
always @(posedge i_clk or negedge i_reset_n) begin
if (!i_reset_n)
o_data <= 8'h00;
else
o_data <= i_data;
endL'assertion peut alors agir sans attendre l'horloge. La désassertion doit respecter les recommandations de la technologie et de l'architecture d'horloge. Il ne faut pas remplacer un reset synchrone par ce modèle simplement par habitude.
Les affectations non bloquantes
Dans un bloc cadencé, utiliser <=. Les parties droites sont évaluées avec les valeurs présentes au début de l'événement, puis les mises à jour deviennent visibles ensemble.
module delay_line (
input wire i_clk,
input wire i_reset,
input wire i_bit,
output reg [2:0] o_history
);
always @(posedge i_clk) begin
if (i_reset) begin
o_history <= 3'b000;
end else begin
o_history[0] <= i_bit;
o_history[1] <= o_history[0];
o_history[2] <= o_history[1];
end
end
endmoduleAprès un front, chaque étage reçoit l'ancienne valeur de l'étage précédent. Le bloc décrit donc trois bascules en série.
Avec des affectations bloquantes =, la première ligne modifierait immédiatement o_history[0] dans la simulation, puis la seconde pourrait lire cette nouvelle valeur. Les trois étages ne représenteraient plus correctement la chaîne attendue.
Compteur avec priorité claire
module event_counter (
input wire i_clk,
input wire i_reset,
input wire i_clear,
input wire i_event,
output reg [7:0] o_count
);
always @(posedge i_clk) begin
if (i_reset)
o_count <= 8'h00;
else if (i_clear)
o_count <= 8'h00;
else if (i_event)
o_count <= o_count + 8'd1;
end
endmoduleSi i_clear et i_event valent 1 sur le même front, le compteur est effacé. L'ordre des branches if définit cette priorité.
Le dépassement est aussi défini par la largeur : après 8'hFF, l'addition sur 8 bits revient à 8'h00. Si ce comportement n'est pas souhaité, il faut ajouter une saturation ou un indicateur de retenue.
Ne pas mélanger les styles
Pour garder un code prévisible :
- utiliser
<=pour les registres mis à jour sur une horloge ; - utiliser
=pour les calculs combinatoires dansalways @*; - ne pas mélanger
=et<=sur le même signal ; - ne pas créer une horloge en RTL avec une simple porte logique ;
- traiter un passage entre domaines d'horloge comme un vrai problème de CDC.
À retenir
always @(posedge i_clk)décrit une mise à jour sur front montant.- Une absence d'affectation dans un bloc cadencé signifie que le registre conserve sa valeur.
- Les affectations non bloquantes font lire aux registres les anciennes valeurs du cycle.
- Reset, enable et priorité doivent être définis explicitement.
- La largeur du registre fixe aussi son comportement en cas de dépassement.
📝 Tester mes connaissances - Quiz du chapitre