Altifigence Academy

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.

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 CC : empty=(C=0)\mathrm{empty}=(C=0), full=(C=4)\mathrm{full}=(C=4). Pour la profondeur D=4D=4, la largeur d'adresse WaddrW_{\mathrm{addr}} et celle du compte WcountW_{\mathrm{count}} valent :

Waddr=⌈log⁡2D⌉=2Wcount=⌈log⁡2(D+1)⌉=3\begin{aligned} W_{\mathrm{addr}}&=\lceil\log_2 D\rceil=2\\ W_{\mathrm{count}}&=\lceil\log_2(D+1)\rceil=3 \end{aligned}

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 :

q=pop∧(C>0)p=push∧((C<4)∨q)C+=C+p−q\begin{aligned} q&=\mathrm{pop}\land(C>0)\\ p&=\mathrm{push}\land((C<4)\lor q)\\ C^{+}&=C+p-q \end{aligned}

À 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 frontpush seulpop seulpush et pop
C=0Stocker un élémentRefuspush seul, C=1
0<C<4C+1C−1Les deux, C inchangé
C=4RefusC=3Les 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é.

Parcourir un FIFO avec l'horloge

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

SystemVerilog
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
endmodule

Le 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.

Votre choix s’applique à ce navigateur. Modifiez-le à tout moment en bas de page.