01signal.com

Timing-Verbesserung bei FIFOs durch zusätzliche Register

Überblick

Diese Seite ist die letzte in einer Serie über FIFOs. Sie zeigt, wie sich ein vorhandener FIFO in einen anderen umbauen lässt. Zuerst wird ein „Standard-FIFO“ in ein FWFT-FIFO umgewandelt, und danach umgekehrt. Anschließend folgen fortgeschrittenere Methoden, mit denen sich das Timing des FIFO verbessern lässt – also die Frequenz, mit der er betrieben werden kann.

Praktisch gesehen ist es ziemlich sinnlos, diese Seite zu lesen, wenn Sie nicht ein Problem mit der Einhaltung der Timing-Constraints (timing constraints) haben und dieses Problem mit einem FIFO zusammenhängt. Der Inhalt dieser Seite ist anspruchsvoll und für den üblichen Einsatz von FIFOs nicht nötig. Trotzdem kann sich die Mühe als Übung lohnen, um die Denkmuskeln zu trainieren, die man beim Entwurf von Logik braucht – insbesondere von Logik, die Daten verarbeitet.

Nicht direkt damit zusammenhängend gibt es eine weitere Seite, die zeigt, wie man mit Hilfe eines externen Speichers ein sehr tiefes FIFO erzeugt (üblicherweise DDR-Speicher, aber auch alles andere mit einer AXI-Schnittstelle ist möglich). Das Schöne an diesem Trick ist, dass ein solch riesiges FIFO zwar so tief sein kann wie der verwendete externe Speicher, aber für die Anwendungslogik dennoch völlig transparent bleibt: Es hat dieselbe Schnittstelle wie ein Basis-FIFO.

Ich sollte erwähnen, dass ich den Verilog-Code auf dieser Seite vor vielen Jahren geschrieben habe; der Codestil weicht daher ein wenig vom heutigen ab.

Vom Standard-FIFO zum FWFT-FIFO

Zur kurzen Wiederholung aus dem früheren Artikel zum Thema FWFT-FIFO: Bei einem „Standard-FIFO“ bedeutet ein low-Pegel am Port @empty (deutsch: „leer“), dass gültige Daten am Ausgang des FIFO anliegen, nachdem @rd_en bei einer steigenden Taktflanke high war. Ein FWFT-FIFO legt die Daten aus, sobald sie verfügbar sind; ein low-Pegel an @empty (deutsch: „leer“) bedeutet also, dass die Daten am Ausgang gültig sind.

Auch die Bedeutung von @rd_en ist anders: Bei einem „Standard-FIFO“ bedeutet es „Gib mir Daten“. Bei einem FWFT-FIFO bedeutet es ungefähr „Ich habe die Daten gerade übernommen, bring mir die nächsten Daten, falls du welche hast“.

Hier ist also das Modul, das ein „Standard-FIFO“ in ein FWFT-FIFO verwandelt. Es ist wenig überraschend, dass es lediglich @rd_en und @empty (deutsch: „leer“) manipuliert. Alle übrigen Signale werden einfach durchgereicht.

module basic_fwft_fifo(rst,
                       rd_clk, rd_en, dout, empty,
                       wr_clk, wr_en, din, full);

   parameter width = 8;

   input                 rst;
   input                 rd_clk;
   input                 rd_en;
   input                 wr_clk;
   input                 wr_en;
   input [(width-1):0]   din;
   output                empty;
   output                full;
   output [(width-1):0]  dout;

   reg                   dout_valid;
   wire                  fifo_rd_en, fifo_empty;

   // orig_fifo is just a normal (non-FWFT) synchronous or asynchronous FIFO
   fifo orig_fifo
      (
       .rst(rst),
       .rd_clk(rd_clk),
       .rd_en(fifo_rd_en),
       .dout(dout),
       .empty(fifo_empty),
       .wr_clk(wr_clk),
       .wr_en(wr_en),
       .din(din),
       .full(full)
       );

   assign fifo_rd_en = !fifo_empty && (!dout_valid || rd_en);
   assign empty = !dout_valid;

   always @(posedge rd_clk or posedge rst)
      if (rst)
         dout_valid <= 0;
      else
         begin
            if (fifo_rd_en)
               dout_valid <= 1;
            else if (rd_en)
               dout_valid <= 0;
         end
endmodule

Normalerweise würde ich den Code hier erklären, aber das würde nur die Erläuterungen wiederholen, die auf der vorherigen Seite zum FWFT-FIFO gegeben wurden.

Vom FWFT-FIFO zum Standard-FIFO

Das ist wirklich einfach. Da ein low-Pegel an @empty (deutsch: „leer“) eines FWFT-FIFO bedeutet, dass am Ausgangsport Daten anliegen, verwendet man ein Register, das diese Daten übernimmt, wenn @rd_en high ist.

Also einfach Folgendes:

module standard_fifo(rst,
                     rd_clk, rd_en, dout, empty,
                     wr_clk, wr_en, din, full);

   parameter width = 8;

   input                 rst;
   input                 rd_clk;
   input                 rd_en;
   input                 wr_clk;
   input                 wr_en;
   input [(width-1):0]   din;
   output                empty;
   output                full;
   output [(width-1):0]  dout;

   reg [(width-1):0]     dout;
   wire [(width-1):0]    dout_w;

   always @(posedge rd_clk)
     if (rd_en && !empty)
       dout <= dout_w;

   fwft_fifo wrapper
     (
      .wr_clk(wr_clk),
      .rd_clk(rd_clk),
      .rst(rst),
      .din(din),
      .wr_en(wr_en),
      .rd_en(rd_en && !empty),
      .dout(dout_w),
      .full(full),
      .empty(empty)
      );
endmodule

Beachten Sie, dass nur @dout verändert wird. @empty (deutsch: „leer“) wird unverändert durchgereicht: Ist es high, ist @dout_w ungültig, sodass @dout daraus keinen Wert übernehmen kann.

Tricks zur Timing-Verbesserung

Willkommen zum großen Finale dieser vier Seiten über FIFOs. Dieser Teil ist mit Abstand der schwierigste zu lesen.

Immer wieder kommt es vor, dass sich beim Versuch herauszufinden, warum ein FPGA-Design die Timing-Constraints nicht erreicht (also nicht die gewünschte Taktfrequenz erreicht), herausstellt, dass der kritische Pfad (critical path) an einem FIFO beginnt und/oder endet. Gehen wir zunächst die Fälle durch, die leicht zu lösen sind, und beenden den Abschnitt mit der harten Nuss.

Wenn @empty und/oder @full im kritischen Pfad liegen

Das @empty-Signal (deutsch: „leer“) und das @full-Signal (deutsch: „voll“) können im kritischen Pfad auftauchen, insbesondere wenn @wr_en und @rd_en kombinatorische Funktionen dieser Signale sind. Das liegt vor allem daran, dass diese Signale oft nicht nur Schreib- oder Leseoperationen am FIFO anfordern, sondern auch als Freigabesignale für die Anwendungslogik dienen, die Daten verbraucht oder erzeugt: Wenn die Daten nicht fließen, bleibt auch die Logik stehen.

Daher gibt es oft viele Logikgleichungen, die von @wr_en und @rd_en abhängen, und die Verknüpfungen sind häufig ziemlich kompliziert. Das Ergebnis ist ein hoher Fan-out (fan-out). All das führt zu einer problematischen Ausbreitungsverzögerung (propagation delay).

@empty (deutsch: „leer“) und @full (deutsch: „voll“) sind bei jedem ordentlich geschriebenen FIFO Ausgänge von Flip-Flops; daran lässt sich also nicht viel verbessern. Da die FPGA-Software den FIFO jedoch oft als synthetisierte Netzliste liefert, ist es unmöglich (oder zumindest schwierig), diese Register zur Verringerung ihres Fan-outs (fan-out) zu duplizieren. Außerdem kann auf der Logikarchitektur des FPGA eine große physische Distanz zwischen diesen Registern und der Anwendungslogik liegen, die ihre Ausgangswerte verwendet. Bei großen FPGAs kann das den entscheidenden Beitrag zur Verzögerung auf den Pfaden leisten.

Die Lösung für dieses Problem wurde bereits auf dieser Seite bei der Besprechung von @almost_empty (deutsch: „fast leer“) und @almost_full (deutsch: „fast voll“) angegeben. Durch die Verwendung dieser Ports können @wr_en und @rd_en als Registerausgänge realisiert werden. Das löst das Problem mit den kombinatorischen Funktionen und ermöglicht es außerdem, den Fan-out dieser Signale zu beherrschen. Darüber hinaus hilft es den Werkzeugen, diese Register näher an der Logik zu platzieren, die ihre Werte verbraucht, und trägt so zur Verringerung der Ausbreitungsverzögerung bei.

Wenn @wr_en und/oder @din im kritischen Pfad liegen

Diese Situation ist mit Abstand am einfachsten zu lösen. Man fügt einfach eine Registerstufe hinzu, etwa so:

always @(posedge wr_clk)
  begin
    wr_en_reg <= wr_en;
    din_reg <= din;
  end

und verbindet dann @wr_en_reg und @din_reg anstelle der ursprünglichen Signale mit dem FIFO. Um einen Überlauf (overflow) des FIFO zu verhindern, sollte @almost_full (deutsch: „fast voll“) anstelle von @full (deutsch: „voll“) verwendet werden. Allgemeiner gesagt sollte die Füllschwelle des FIFO um eins abgesenkt werden.

Wenn @rd_en und/oder @dout im kritischen Pfad liegen

Jetzt wird es ernst. Dieses Problem ist nicht nur relativ schwer zu lösen, sondern es tritt auch am wahrscheinlichsten auf. Dafür gibt es mehrere Gründe:

Was @dout betrifft:

Das Ziel ist es also, den kombinatorischen Pfad zwischen @rd_en und der Logik des FIFO zu beseitigen – und dasselbe für @dout zu erreichen.

Nur den kombinatorischen Pfad von @dout ablösen

Ich möchte das nicht wirklich als Lösung präsentieren, aber die Erörterung kann helfen, das als Vorbereitung für den nächsten Schritt zu verstehen. Falls Sie das nur verwirrt, überspringen Sie diesen Abschnitt.

Angenommen, wir wollten nur den kombinatorischen Pfad von @dout ablösen. Das Wrapper-Modul, das ein FWFT-FIFO in ein „Standard-FIFO“ umwandelt (wie oben gezeigt), tut genau das: Es fügt ein Register hinzu und beendet damit den kombinatorischen Pfad von @dout. Allerdings setzt das ein FWFT-FIFO als Ausgangspunkt voraus.

Aber es gab doch ein Wrapper-Modul, das ein „Standard-FIFO“ in ein FWFT-FIFO umwandelt. Also vielleicht den FIFO erst in die eine und dann in die andere Richtung umwandeln? Oder ein einziges Modul schreiben, das die entsprechende Funktion erfüllt? In jedem Fall verschlechtert eine Lösung dieser Bauart die Situation mit @rd_en.

Dennoch lohnt sich ein genauerer Blick auf diese Lösung: Die Umwandlung in ein FWFT-FIFO bestand lediglich darin, zu verfolgen, wann @dout des eingebetteten FIFO gültig war, und @fifo_rd_en high zu halten, wenn @dout nicht gültig war (und/oder wenn das externe @rd_en high war).

Die Rückumwandlung in ein „Standard-FIFO“ erfolgte, indem der Wert von @dout des eingebetteten FIFO bei high-Pegel von @rd_en in ein Register kopiert wurde.

Insgesamt hielt der erste Mechanismus @dout des eingebetteten FIFO so oft wie möglich gültig, und der zweite Mechanismus kopierte @dout bei Anforderung durch das externe @rd_en in ein weiteres Register.

Aber das löst das Problem mit dem kombinatorischen Pfad von @rd_en nicht: Damit kontinuierlich gelesen werden kann, muss in jedem Takt, in dem das externe @rd_en high ist, ein Wort aus dem ursprünglichen FIFO gelesen werden. Andernfalls wird @dout des FWFT-FIFO ungültig, weil es verbraucht, aber nicht aktualisiert wurde. Das interne @rd_en dieses FIFO muss also eine kombinatorische Funktion des externen @rd_en sein. Wenn wir das ändern wollen, muss dem Pfad von @dout ein weiteres Register hinzugefügt werden, wie im Folgenden gezeigt.

Beide kombinatorischen Pfade mit reg_fifo ablösen

Ohne weitere Umschweife: Dies ist das Modul reg_fifo, das die kombinatorischen Pfade für @rd_en und @dout ablöst:

module reg_fifo(rst,
                rd_clk, rd_en, dout, empty,
                wr_clk, wr_en, din, full);

   parameter width = 8;

   input                 rst;
   input                 rd_clk;
   input                 rd_en;
   input                 wr_clk;
   input                 wr_en;
   input [(width-1):0]   din;
   output                empty;
   output                full;
   output [(width-1):0]  dout;

   reg                   fifo_valid, middle_valid;
   reg [(width-1):0]     dout, middle_dout;

   wire [(width-1):0]    fifo_dout;
   wire                  fifo_empty, fifo_rd_en;
   wire                  will_update_middle, will_update_dout;

   // orig_fifo is "standard" (non-FWFT) FIFO
   fifo orig_fifo
      (
       .rst(rst),
       .rd_clk(rd_clk),
       .rd_en(fifo_rd_en),
       .dout(fifo_dout),
       .empty(fifo_empty),
       .wr_clk(wr_clk),
       .wr_en(wr_en),
       .din(din),
       .full(full)
       );

   assign will_update_middle = fifo_valid && (middle_valid == will_update_dout);
   assign will_update_dout = rd_en && !empty;
   assign fifo_rd_en = !fifo_empty && !(middle_valid && fifo_valid);
   assign empty = !(fifo_valid || middle_valid);

   always @(posedge rd_clk)
      if (rst)
         begin
            fifo_valid <= 0;
            middle_valid <= 0;
            dout <= 0;
            middle_dout <= 0;
         end
      else
         begin
            if (will_update_middle)
               middle_dout <= fifo_dout;

            if (will_update_dout)
               dout <= middle_valid ? middle_dout : fifo_dout;

            if (fifo_rd_en)
               fifo_valid <= 1;
            else if (will_update_middle || will_update_dout)
               fifo_valid <= 0;

            if (will_update_middle)
               middle_valid <= 1;
            else if (will_update_dout)
               middle_valid <= 0;
         end
endmodule

Zunächst ist festzuhalten, dass @dout ein Register ist, das in diesem Modul definiert wird und dass @rd_en dessen Aktualisierung auslöst. Wichtig ist, diese beiden nicht mit den ähnlich benannten Signalen zu verwechseln, die mit dem internen FIFO verbunden sind: @fifo_dout und @fifo_rd_en.

Nun zur Funktionsweise dieses Moduls.

Die Pipeline verstehen

Diese Struktur ist im Wesentlichen ein Skid-Buffer (skid buffer): eine zweistufige Pipeline (pipeline) mit Gültigkeits-Flags, die das externe @rd_en von der Lese-Freigabe des internen FIFO entkoppelt, aber dennoch einen kontinuierlichen Datenfluss erlaubt.

Wie bei der Umwandlung in ein FWFT-FIFO gibt es eine Instanziierung (instantiation) eines normalen FIFO, orig_fifo. Die Logik im Modul reg_fifo versucht, den Wert von @fifo_dout gültig zu halten, indem sie aus orig_fifo ein Wort liest, wenn @fifo_dout keinen gültigen Wert enthält. Darüber hinaus gibt es ein zweites Register namens @middle_dout. Auch dieses Register versucht die Logik gültig zu halten, indem sie nach Möglichkeit den Wert von @fifo_dout übernimmt.

Man kann @fifo_dout, @middle_dout und @dout also als eine Pipeline betrachten, die die Daten aus orig_fifo nach vorn schiebt.

Zwei Register verfolgen, wann diese Pipeline-Stufen gültig sind: @fifo_valid ist high, wenn @fifo_dout gültig ist, und @middle_valid ist high, wenn @middle_dout gültig ist.

Der Zweck dieser Pipeline ist die Möglichkeit, ihre mittlere Stufe zu überbrücken: Wenn @rd_en high ist (und @empty (deutsch: „leer“) low ist), übernimmt @dout seinen neuen Wert entweder von @middle_dout oder von @fifo_dout, bevorzugt jedoch immer @middle_dout. Mit anderen Worten: Wenn @middle_dout gültig ist, verwendet @dout @middle_dout, andernfalls verwendet @dout @fifo_dout. Wie genau das der Schlüssel zum Ablösen des kombinatorischen Pfads von @rd_en ist, wird weiter unten erklärt.

Sehen wir uns also zuerst die Details der Implementierung an. Es gibt zwei getrennte Pfade vom Datenausgang des FIFO zum Ausgangsregister von reg_fifo. Sie sind in dieser Zeichnung getrennt links und rechts dargestellt:

Data flow with extra registers

Wenn keine der beiden Pipeline-Stufen (@fifo_dout und @middle_dout) gültig ist, ist @empty (deutsch: „leer“) high; das zeigt an, dass keine Datenquelle vorhanden ist:

assign empty = !(fifo_valid || middle_valid);

Der Versuch, diese Pipeline-Stufen gültig zu halten, spiegelt sich in Folgendem wider:

assign fifo_rd_en = !fifo_empty && !(middle_valid && fifo_valid);

Das bedeutet: Wenn eine der beiden Pipeline-Stufen ungültig ist, wird nach Möglichkeit aus orig_fifo gelesen. Wenn @fifo_dout bereits gültig ist, wird sein Wert gleichzeitig mit der Aktualisierung von @fifo_dout in @middle_dout kopiert (dazu weiter unten mehr).

Nun betrachten wir die Definitionen des Paars @will_update_*:

assign will_update_middle = fifo_valid && (middle_valid == will_update_dout);
assign will_update_dout = rd_en && !empty;

Beachten Sie zuerst, dass @will_update_dout gleich @rd_en ist, zusätzlich abgesichert gegen das Lesen aus reg_fifo, wenn @empty (deutsch: „leer“) high ist.

Weiter haben wir @will_update_middle, das die Aktualisierung von @middle_dout wie folgt steuert:

always @(posedge rd_clk)
  if (will_update_middle)
     middle_dout <= fifo_dout;

Betrachtet man die Definition von @will_update_middle weiter oben, gibt es zwei Bedingungen für die Aktualisierung von @middle_dout: Die eine ist, dass der Wert von @fifo_dout gültig ist – das ist offensichtlich. Und dann gibt es den Ausdruck (middle_valid == will_update_dout). Zerlegen wir diesen Ausdruck in die vier möglichen Fälle; er erklärt nämlich, wie der gesamte Mechanismus funktioniert. Man muss dabei bedenken, dass all das nur eine Rolle spielt, wenn @fifo_dout gültig ist:

Beachten Sie, dass @fifo_rd_en low ist, wenn @middle_valid und @fifo_valid gleichzeitig high sind. In den Szenarien der beiden letzten Fälle wird daher kein Datenwort aus orig_fifo geholt.

Insbesondere wird bei gültigen beiden Pipeline-Stufen und high-Pegel von @rd_en der Wert von @fifo_dout nach @middle_dout kopiert. Da @fifo_rd_en low ist, wechselt @fifo_valid im nächsten Taktzyklus auf low. Das ist in Ordnung, denn @middle_valid bleibt high und kann in diesem nächsten Taktzyklus bei Bedarf Daten liefern. Im darauffolgenden Taktzyklus ist @fifo_valid wieder high (sofern orig_fifo Daten enthält).

Warum ist @fifo_rd_en dann nicht so definiert, dass @fifo_dout in dieser speziellen Situation gültig bleibt? Weil @fifo_rd_en dann eine kombinatorische Funktion von @rd_en sein müsste – genau das soll diese zweistufige Pipeline ja vermeiden.

Damit ist es an der Zeit zu betrachten, wie @dout definiert ist. Abgesehen vom Reset lautet die Definition:

always @(posedge rd_clk)
  if (will_update_dout)
    dout <= middle_valid ? middle_dout : fifo_dout;

Setzt man @will_update_dout durch seine Definition ein, ergibt sich:

always @(posedge rd_clk)
  if (rd_en && !empty)
    dout <= middle_valid ? middle_dout : fifo_dout;

Das ähnelt der Umwandlung eines FWFT-FIFO in ein „Standard-FIFO“, nur dass hier zwei Quellen zur Auswahl stehen: Wenn @middle_dout einen gültigen Wert enthält, wird dieser übernommen; andernfalls @fifo_dout. Wenn keiner der beiden gültig ist, ist @empty (deutsch: „leer“) high, sodass ohnehin nichts passiert.

Warum hilft das? Was das Timing des Ausgangs betrifft, ist @dout eindeutig ein Register. Was @rd_en betrifft, hängt @fifo_rd_en nur von @middle_valid und @fifo_valid ab – beides Register – sowie von @fifo_empty, dem Ausgang von orig_fifo selbst (dieser kombinatorische Pfad ist unvermeidlich). @fifo_rd_en hängt also nicht vom externen @rd_en ab; folglich gibt es keinen kombinatorischen Pfad von @rd_en zu orig_fifo.

Verfolgung der Gültigkeit der Register in den Pipeline-Stufen

Nur um das Bild zu vervollständigen: Die beiden *_valid-Flags zeigen an, ob das zugehörige Register gültige Daten enthält. Zu @fifo_valid:

 if (fifo_rd_en)
   fifo_valid <= 1;
 else if (will_update_middle || will_update_dout)
   fifo_valid <= 0;

Das entspricht der Definition von @dout_valid bei der Umwandlung eines „Standard-FIFO“ in ein FWFT-FIFO weiter oben: Wenn @fifo_rd_en bei einer steigenden Flanke high ist, wird @fifo_valid dadurch high. Das ist sinnvoll, denn wenn aus orig_fifo gelesen wird, gilt der Ausgang dieses FIFO folglich als gültig. War @fifo_rd_en jedoch low und wurden Daten nach @middle_dout oder @dout kopiert, betrachten wir @fifo_dout nicht mehr als gültig: Der Ausgang des FIFO wurde gerade verwendet, und der FIFO hat ihn nicht durch neue Daten ersetzt.

@middle_valid folgt derselben Logik:

 if (will_update_middle)
   middle_valid <= 1;
 else if (will_update_dout)
   middle_valid <= 0;

Wenn @will_update_middle high ist, werden Daten nach @middle_dout kopiert; damit wird auch @middle_valid high. Andernfalls – und wenn die Daten in @middle_dout nach @dout kopiert werden – wechselt @middle_valid auf low. @will_update_dout ist dafür eine hinreichende Bedingung, denn wie oben bereits erwähnt, kopiert @dout nach Möglichkeit bevorzugt aus @middle_dout.

Funktioniert das überhaupt?

Dieses Modul ist so kompliziert, dass es für den Nachweis seiner Funktionsfähigkeit fast einen formalen Beweis braucht. Eine Möglichkeit, diese Frage zu beantworten, ist zu fragen, wie viele der beiden Pipeline-Stufen @fifo_dout und @middle_dout gültig sind. Dieser Wert ist nicht definiert im Modul reg_fifo, aber er hätte definiert werden können als

wire [1:0] valid_count;
assign valid_count = fifo_valid + middle_valid;

Diese gedachte Größe @valid_count kann offensichtlich die Werte 0, 1 oder 2 annehmen. Sie zählt wie folgt hoch oder herunter:

Schauen Sie sich die Logikgleichungen an und überzeugen Sie sich, dass diese drei Aussagen stimmen.

Sehen wir uns also an, was passiert, wenn orig_fifo Daten enthält und die Anwendungslogik kontinuierlich lesen möchte:

Die Logik von reg_fifo versucht, @valid_count durch Lesen aus orig_fifo in Richtung 2 zu erhöhen. Andererseits ist @empty (deutsch: „leer“) low, wenn @valid_count nicht null ist; @rd_en darf also schon dann high werden, sobald @valid_count den Wert 1 hat. Ist @valid_count gleich 1, ist @fifo_rd_en high, weil @valid_count nicht 2 ist.

Aber @valid_count wird den Wert 2 nicht erreichen, weil @rd_en das verhindert, indem es high bleibt. Die Daten fließen also, während @fifo_rd_en und @rd_en beide high bleiben und @valid_count bei 1 bleibt. Abgesehen vom Beginn werden die Daten von @fifo_dout nach @dout kopiert.

Diese Balance wird gebrochen, wenn orig_fifo empty (deutsch: „leer“) wird: In diesem Fall wird @valid_count zu null, weil @fifo_rd_en nicht mehr high sein darf. Ein weiterer Fall, der das Gleichgewicht kippt, ist, wenn der FIFO nicht empty (deutsch: „leer“) ist und @rd_en low wird, weil die Anwendungslogik nicht mehr lesen möchte: Dann steigt @valid_count auf 2 und bleibt dort.

Später jedoch, wenn @rd_en wieder high wird, sinkt @valid_count auf 1, und erst dann wechselt @fifo_rd_en auf high (außer orig_fifo ist empty (deutsch: „leer“)).

Noch einmal: @valid_count ist nur ein theoretisches Signal, das im Modul nicht implementiert ist. Hoffentlich hilft diese Erklärung zu verstehen, warum die beiden zusätzlichen Pipeline-Stufen einen kontinuierlichen Datenfluss garantieren.

Hinweise zur Verwendung

Dieses Modul kann ohne weitere Änderungen als Ersatz für das „Standard-FIFO“ verwendet werden, das es umhüllt. Funktional ändert sich nichts. orig_fifo sieht allerdings eine leichte Änderung im Verhalten von @rd_en, @dout und @empty (deutsch: „leer“); das spielt aber keine Rolle, solange sich orig_fifo korrekt als FIFO verhält (davon kann man getrost ausgehen). Die Ports, die das Schreiben von Daten betreffen, werden unverändert durchgereicht; an ihnen ändert sich also überhaupt nichts.

Da das Modul ein paar Pipeline-Stufen hinzufügt, können die Füllstandszähler von orig_fifo niedrigere Werte anzeigen, als der Gesamtzahl der in reg_fifo gespeicherten Wörter entspricht (also der Anzahl der Wörter in orig_fifo zusammen mit denen in den Pipeline-Stufen). Wenn also @almost_empty (deutsch: „fast leer“) oder ähnliche Ports an orig_fifo aktiviert sind, zeigen sie unter Umständen ein pessimistisches Bild.

Ein leichter Nachteil von reg_fifo ist, dass sein Ausgang @empty (deutsch: „leer“) kein Register ist, sondern eine kombinatorische Funktion zweier Register. Das ist für das Timing nicht optimal, hat aber in den meisten Anwendungsfällen nur minimale Auswirkungen. Man kann das beheben, indem man kombinatorische Zwischensignale wie @next_fifo_valid und @next_middle_valid definiert – im gleichen Geist wie @next_words_in_ram, wie auf dieser Seite gezeigt. Hier ist das nicht umgesetzt, hauptsächlich weil reg_fifo schon kompliziert genug ist.

Ein FWFT-FIFO mit verbessertem Timing

Wenn ein FWFT-FIFO mit verbessertem Timing benötigt wird, ist das einfach: Man verwendet das oben gezeigte Beispiel basic_fwft_fifo, aber statt eines normalen FIFO wird reg_fifo instanziiert. Diese Lösung benötigt ein weiteres Register, aber das ist ein geringer Preis.

Damit ist diese Serie über FIFOs abgeschlossen.

Diese Seite wurde maschinell aus dem Englischen übersetzt. Im Zweifelsfall siehe den Originaltext.
Copyright © 2021-2026. All rights reserved. (dcc38493)