57 / 63 · Concept
FIFO mono-horloge : pointeurs et politiques aux limites
Réaliser pointeurs et occupation d'un FIFO quatre places, avec acceptation explicite à vide et à plein.
Les leçons sont gratuites. Inscrivez-vous pour enregistrer vos progrès.
Préserver l'ordre sans déplacer les données
Un FIFOFIFO File où la première donnée entrée sort en premier. Les règles à plein, à vide et en lecture/écriture simultanée font partie du contrat. ressort d'abord les données entrées en premier. Ici, un seul front d'horloge décide lectures et écritures, avec DEPTH=4 et huit bits par donnée. rptr désigne le prochain élément à sortir, wptr le prochain emplacement à écrire. Leurs deux bits rebouclent naturellement de 3 à 0. L'occupation 0–4 nécessite trois bits.
rptr=wptr est possible à vide comme à plein. En déduire empty sur ce seul critère ferait perdre les données d'un FIFO plein. Gardons le compte : , . Pour la profondeur , la largeur d'adresse et celle du compte valent :
Distinguer requête et acceptation aux limites
push et pop sont des requêtes. Notons p et q leurs acceptations effectives, calculées sur l'état avant un front sans reset :
À vide, deux requêtes n'acceptent que push, car aucun ancien élément n'est disponible ; la nouvelle donnée ne sort pas immédiatement. À plein, pop libère la place de l'ancien head, donc les deux sont acceptés. full signifie « quatre éléments actuellement présents », pas une interdiction absolue de push.
| État avant le front | push seul | pop seul | push et pop |
|---|---|---|---|
| C=0 | Stocker un élément | Refus | push seul, C=1 |
| 0<C<4 | C+1 | C−1 | Les deux, C inchangé |
| C=4 | Refus | C=3 | Les deux, C=4 |
Vérifier les limites en avançant l'horloge
Commencez par push et pop simultanés à vide : seul push doit être accepté. Désactivez ensuite pop pour remplir les quatre places. Réactivez les deux avec une nouvelle donnée : l'occupation reste 4, l'ancien head sort et le nouveau tail est stocké.
FIFO à quatre cases. Initialement vide ; pointeurs de lecture et d'écriture à 0. Accepter pop si le FIFO n'est pas vide. Accepter push s'il reste une place ou si un pop est accepté sur le même front.
Le modèle montre les états après chaque front montantFront montant Instant où l'horloge passe de 0 à 1, distinct du niveau désignant tout l'intervalle CLK=1. En savoir plus. Un pop accepté met output valid à 1 ; sans pop accepté au front suivant, la donnée enregistréeRegistre Circuit stockant plusieurs bits d'état. Les registres synchrones du cours capturent leur entrée définie sur un front d'horloge. En savoir plus reste mais valid tombe à 0. Les anciens bits des emplacements vides sont masqués. « Recommencer » réinitialise toute la manipulation et ne représente pas un resetReset Commande ramenant l'état à la valeur initiale spécifiée. Son caractère synchrone/asynchrone et sa priorité sur les autres commandes doivent être définis. En savoir plus de la mémoire RTL.
RTL avec sortie enregistrée
module fifo4 (
input logic clk, rst, push, pop,
input logic [7:0] din,
output logic [7:0] dout,
output logic dout_valid, full, empty,
output logic [2:0] occupancy
);
timeunit 1ns;
timeprecision 1ps;
logic [7:0] mem [0:3];
logic [1:0] rptr, wptr;
logic take_push, take_pop;
assign empty = (occupancy == 3'd0);
assign full = (occupancy == 3'd4);
assign take_pop = !rst && pop && !empty;
assign take_push = !rst && push && (!full || take_pop);
always_ff @(posedge clk) begin
if (rst) begin
rptr <= 2'd0;
wptr <= 2'd0;
occupancy <= 3'd0;
dout_valid <= 1'b0;
end else begin
dout_valid <= take_pop;
if (take_push) begin
mem[wptr] <= din;
wptr <= wptr + 2'd1;
end
if (take_pop) begin
dout <= mem[rptr];
rptr <= rptr + 2'd1;
end
case ({take_push, take_pop})
2'b10: occupancy <= occupancy + 3'd1;
2'b01: occupancy <= occupancy - 3'd1;
default: occupancy <= occupancy;
endcase
end
end
endmoduleLe registre occupancy n'est pas incrémenté et décrémenté par deux if indépendants : deux affectations non bloquantesAffectation non bloquante Affectation <= du RTL séquentiel : le membre droit est évalué puis la mise à jour planifiée, permettant aux registres du même front de calculer à partir de l'ancien état. En savoir plus au même registre laisseraient la dernière gagner. Chaque pointeur avance sur son acceptation ; le compte traite les quatre cas ensemble. Les conditions empêchent aussi de décrémenter 0 ou d'incrémenter 4.
Lire et écrire la même case à plein
Après A,B,C,D, rptr=wptr=0 et occupancy=4. Avec pop=push=1,din=E, après le front dout=A,dout_valid=1 et la file logique est [B,C,D,E]. Les pointeurs deviennent 1 et le compte reste 4. mem[0] devient E, mais dout lit l'ancien A au membre droit non bloquant. Cette collision read-first est indispensable au remplacement à plein.
Un pop seul suivant sort B et réduit le compte à 3. À l'inverse, à vide, les deux requêtes avec din=Z donnent occupancy=1,dout_valid=0 ; un pop au front suivant est nécessaire pour sortir Z. Passage immédiat à vide et remplacement à plein sont deux choix distincts.
Le reset synchrone actif haut initialise pointeurs, compte et dout_valid, pas la mémoire ni dout. Après reset, empty interdit le pop des anciens bits et seuls les emplacements nouvellement écrits sont relus : effacer tout le tableau n'est pas nécessaire au fonctionnement normal. Ne consommez pas dout lorsque valid=0. valid=1 répond à un pop accepté ; sans ready de sortie, le consommateur doit prendre cette réponse.
Une profondeur non puissance de deux ne peut pas utiliser le même rebouclage naturel : à profondeur 3, l'adresse 2 doit revenir explicitement à 0. Si la synthèse utilise une RAM, vérifiez son comportement old-data en collision. Ce code est mono-horloge ; il ne remplace pas la synchronisation de pointeurs d'un FIFO asynchrone.
Essayez vous-même
Après reset, appliquez par front : (1) push A et pop, (2) push B, (3) push C, (4) push D, (5) push E et pop, (6) pop. Donnez file, occupation, dout_valid et donnée valide après chaque étape. Pourquoi read-first est-il nécessaire à l'étape 5 ?
Lire l’explication
(1) [A],1,valid=0. (2) [A,B],2,valid=0. (3) [A,B,C],3,valid=0. (4) [A,B,C,D],4,valid=0. (5) [B,C,D,E],4,valid=1,dout=A. (6) [C,D,E],3,valid=1,dout=B. Au premier pas, aucun ancien élément n'existe : pop est refusé. Au cinquième, rptr=wptr=0 et la même case est lue/écrite : l'ancien A doit sortir. Retourner E en write-first détruirait l'ordre et le contenu FIFO. Les sorties valid=0 ne sont pas évaluées.