www.mikrocontroller.net

Forum: FPGA, VHDL & Co. FIFO Implementierung in BlockRAM


Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,
ich habe nun ein Beispiel für ein FIFO im BlockRAM gesucht, bin aber 
nicht wirklich fündig geworden. Hat jemand ein Beispiel für mich? ;-)
(Ziel: 16bit breit, ca. 16Stellen tief)

Ansonsten habe ich mir überlegt, das FIFO in etwa wie folgt aufzubauen:

Aufbau:
- vollständig synchron, gleicher Takt für Lesen und Schreiben
- nicht zeitkritisch, full/empty usw. Signale müssen nicht schon 
innerhalb von einem Taktzyklus oder so aktualisiert sein
- BlockRAM als Speicher nehmen: RAMB_S16_S16
- überzählige RAM Zellen einfach nicht adressieren (oder FIFO ohne 
Zusatzaufwand bis zu 1k Tiefe auslegen)

Lesen/Schreiben:
- FIFO Count mit Zählbereich der FIFO Tiefe
- Schreibadresse 2^n Bit breit
- Leseadresse 2^n Bit breit
- Write Impuls zum Schreiben von Daten
- bei jedem Write Impuls: Write Adresse um 1 erhöhen -> darf auch 
überlaufen auf 0, FIFO Count um 1 erhöhen
- Leseadresse 2^n Bit breit
- Read Impuls zum Lesen von Daten -> in Register übernehmen
- bei jedem Read Impuls: Read Adresse um 1 erhöhen -> darf auch 
überlaufen auf 0, FIFO Count um 1 vermindern

Flusskontrolle:
- Empty Flag: 0 wenn FIFO Count = 0, sonst 1
- Full Flag: 1 wenn FIFO Count = Maxwert, sonst 0
- Sperren der weiteren Write Impulse bei Full, Setzen eines Overflow 
Flags
- Sperren der weiteren Read Impulse bei Empty, 0 als Resultat liefern

Initialzustand:
- beide Adressen auf 0
- Counter auf 0
- Empty Flag auf 1
- Full Flag auf 0

Würde das FIFO so wohl einigermassen gut funktionieren?
Habe ich etwas Wichtiges vergessen?
Viel einfacher kann man das wohl nicht aufbauen.
Hat jemand für dieses FIFO ein besseres Konzept?
Oder hat gar jemand eine Vorlage für mich ;-)

Gruss, Martin Kohler

Autor: Gast (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Martin,

wenn du Webpack (Xilinx) oder Quartus (Altera) verwendest, dann kannst 
du mit dem IP-Wizard ein komplettes FIFO erzeugen lassen - ist eine 
Standardkomponente.

Wenn's Hausaufgabe für die Uni sein soll, dann sehen die fertige 
IP-Cores wohl weniger gerne ;-)

Grüße
Thomas

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Gast wrote:
> Wenn's Hausaufgabe für die Uni sein soll, dann sehen die fertige
> IP-Cores wohl weniger gerne ;-)
Wäre doch mal ein netter Versuch...

Nein, ich bin hier auf Arbeit dran, das FIFO zu entwerfen und zu 
implementieren.

Das Problem an den fertigen IP Cores ist, dass hier in der Firma die 
Meinung vorherrscht, dass möglichst vieles möglichst plattformunabhängig 
sein soll.

Der Chef sieht lieber ein in VHDL auscodiertes FIFO als ein Interface 
mit angehängtem .ncd File aus dem Core Genrator.
Naja, die Verfechter der reinen Lehre...
Mir kanns recht sein, so kann ich mich VHDL-technisch etwas lernen 
dabei, auch dank dieses Forums.

Autor: Christian R. (supachris)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Platformunabhängig, aber dann doch den BlockRam? Der wird doch auch bei 
jedem FPGA Hersteller leicht anders sein. Das ist ein unsinniges 
Argument. Und kostet massig Zeit, was ja auch massig Geld ist.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Christian R. wrote:
> Platformunabhängig, aber dann doch den BlockRam?
Wenn ich den FIFO Speicher als Array definiere, dann nehme ich an, dass 
XST dies in ein BlockRAM umsetzt. Das wäre dann die "noch reinere 
Lehre", als wenn ich RAMB_S16_S16 im VHDL Code verwende.
    type ram_type is array (255 downto 0) of STD_LOGIC_VECTOR(15 downto 0);
    signal RAM : ram_type := (others => (others => '0'));

Gibt es evtl. noch Rückmeldungen zum konzeptionellen Aufbau meines 
FIFOs? Oder hat gar einer einen VHDL Beispielcode?

Über Sinn oder Unsinn möchte ich mich nicht weiter unterhalten, wie 
gesagt, die "reine Lehre"...

Autor: Duke Scarring (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert

Autor: lala (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Wenn ich den FIFO Speicher als Array definiere, dann nehme ich an, dass XST dies 
in ein BlockRAM umsetzt.

Das wage ich zu bezweifeln.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Duke Scarring wrote:
> Sowas?:
> http://mysite.verizon.net/miketreseler/sync_fifo.vhd
Ja. Sieht auf den ersten Blick gut aus, ist evtl. noch etwas zu 
vereinfachen.

lala wrote:
> Das wage ich zu bezweifeln.

Begründung? Erfahrung?

-> meine Aussage basierte auf entsprechendem Erfahrungswert für das 
angegebene Array, welches 8mal vorhanden war in dem Design.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
>... FIFO Count um 1 erhöhen ...
>... FIFO Count um 1 vermindern ...
Aber aufpassen, wenn gleichzeitig gelesen und geschreiben wird. Die 
letzte Zuweisung im Prozess zählt ;-)
Lass den Fifo-Count weg, der bringt dich irgendwann in Bedrängnis.
Wenn du einen Schreib-Pointer hast, und einen Lese-Pointer, dann kannst 
du dir den Fifo-Count leicht aus den aktuellen Pointer-Werten 
ausrechnen.

Z.B.:
Fifo mit 16 Elementen (Zweierpotenzen sind gut für den Wrap-Around)

Wenn WR_Addr = RD_Addr  --> Fifo leer

Wenn WR_Addr /= RD_Addr --> mindestens 1 Element ist im Fifo

Wenn (WR_Addr>RD_Addr)
   Anzahl der Elemente <=      WR_Addr - RD_Addr
sonst
   Anzahl der Elemente <= 16 + WR_Addr - RD_Addr


> Wenn ich den FIFO Speicher als Array definiere, dann nehme ich an,
> dass XST dies in ein BlockRAM umsetzt.
Ein 16x16Bit tiefes Fifo im BRAM ist eigentlich Ressourcenverschwendung.
Wie auch immer:
Im ISE Menüpunkt EDIT --> Language Templates    und dann
VHDL --> Synthesis Constructs --> Coding Examples --> RAM --> Block Ram 
--> Dual Port findet sich ein DP-RAM mit 1 Schreib und Leseport.
Der dürfte eigentlich gut geeignet sein.

EDIT:
>> http://mysite.verizon.net/miketreseler/sync_fifo.vhd
> Sieht auf den ersten Blick gut aus, ist evtl. noch etwas zu vereinfachen.

Naja, der Mensch hat das mit dem Enable-Signalen noch nicht so richtig 
inne:
-- we want to push/pop only on a '0' to '1' transition
Meine Fifos pushen und poppen solange WR bzw. RD aktiv (enabled) sind, 
nicht auf irgendwelchen Flanken dieser Signale.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller wrote:
> Lass den Fifo-Count weg, der bringt dich irgendwann in Bedrängnis.
Mach ich.

> Wenn du einen Schreib-Pointer hast, und einen Lese-Pointer, dann kannst
> du dir den Fifo-Count leicht aus den aktuellen Pointer-Werten
> ausrechnen.
Wäre dann einfacher als einen Counter mitzuschleppen, ja.

> Wenn WR_Addr = RD_Addr  --> Fifo leer
empty = '1' when wr_addr = rd_adr else '0'
--> klar

> Wenn (WR_Addr>RD_Addr)
>    Anzahl der Elemente <=      WR_Addr - RD_Addr
> sonst
>    Anzahl der Elemente <= 16 + WR_Addr - RD_Addr
So was in der Art? (Pseudocode)
count = wr_addr - rd_addr when wr_addr > rd_adr else size + wr_addr - rd_addr
full lässt sich nun aber nicht einfach so beschreiben, da sind die 
Adressen ja auch wieder gleich.

> Ein 16x16Bit tiefes Fifo im BRAM ist eigentlich Ressourcenverschwendung.
ist aber immer noch besser, als das BRAM brach liegen zu lassen ;-)

> Im ISE Menüpunkt EDIT --> Language Templates    und dann
> VHDL --> Synthesis Constructs --> Coding Examples --> RAM --> Block Ram
> --> Dual Port findet sich ein DP-RAM mit 1 Schreib und Leseport.
> Der dürfte eigentlich gut geeignet sein.
???
Dort befindet sich die Vorlage für die Ansteuerung (r/w) des RAM, nicht 
aber das RAM selbst.

>
-- we want to push/pop only on a '0' to '1' transition
> Meine Fifos pushen und poppen solange WR bzw. RD aktiv (enabled) sind,
> nicht auf irgendwelchen Flanken dieser Signale.
Meine Designs arbeiten auch pegelgesteuert, zur Ansteuerung werden 
Triggerpulse generiert, welche genau 1 Clock lang sind.
--> ist einfacher als zu realisieren als flankengesteuert.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> So was in der Art? (Pseudocode)
So sollte es passen. Achte auf signed und unsigned bei den Adressen.

Ich lasse auch gern die Adressen auf 32 Bit laufen und nehme nur die 
unteren Bits zum Adressieren. Als "Abfallprodukt" ergibt sich dann die 
Zahl der übertragenen Daten. Aber auch hier mußt du auf den Überlauf 
achten.

> full lässt sich nun aber nicht einfach so beschreiben, da sind die
> Adressen ja auch wieder gleich.
Ja, dann melde FULL, wenn der Schreibindex eines hinter dem Leseindex 
ist ;-)
So richtig "saugend" voll sollte dein Fifo sowieso nicht werden, denn 
sonst reicht entweder die Fifo-Tiefe nicht oder die 
Verarbeitungsbandbreite ist zu gering.

> Dort befindet sich die Vorlage für die Ansteuerung (r/w) des RAM, nicht
> aber das RAM selbst.
Das RAM selbst ist auch in diesem Vorlagen-Zweig, es ist ein einfach 
Array von Vektoren.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller wrote:
> Achte auf signed und unsigned bei den Adressen.
Ich habe vor, die Adressen als std_logic_vector zu definieren und das 
DPRAM mit conv_integer(vector) zu addressieren.
RAM(conv_integer(AA(3 downto 0))) <= DA(15 downto 0);
Kann man eigentlich direkt mit std_logic_vector rechnen? Für das empty 
Flag passt das locker ( = Operator), der Test auf "full when rd = wr + 
1", geht das auch?

> Ich lasse auch gern die Adressen auf 32 Bit laufen und nehme nur die
> unteren Bits zum Adressieren. Als "Abfallprodukt" ergibt sich dann die
> Zahl der übertragenen Daten. Aber auch hier mußt du auf den Überlauf
> achten.
Gute Idee. Danke.

> Ja, dann melde FULL, wenn der Schreibindex eines hinter dem Leseindex
> ist ;-)
> So richtig "saugend" voll sollte dein Fifo sowieso nicht werden, denn
> sonst reicht entweder die Fifo-Tiefe nicht oder die
> Verarbeitungsbandbreite ist zu gering.
Klar.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Kann man eigentlich direkt mit std_logic_vector rechnen?
Nicht, wenn du die numeric_std Lib verwendest. Dafür gibts dann Casts 
nach unsigned/singend und Konvertierungen nach integer und zurück. Aber 
für einen Array-Zugriff brauchst du sowieso einen Integer. Warum nicht 
die Adressen gleich als Integer verwalten?

Good old school style mit
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
  :
  :
  RAM(conv_integer(AA(3 downto 0))) <= DA(15 downto 0);
mit der herstellerunabhängigen numeric_std
use IEEE.NUMERIC_STD.ALL;
  :
  :
  RAM(to_integer(unsigned(AA(3 downto 0)))) <= DA(15 downto 0);


> "full when rd = wr + 1", geht das auch?
Mit integer und unsigned geht das. Aber du solltest dir nochmal die 
Überlaufgrenzen ansehen. Denn auch hier kann z.B. rd=15 sein und wr=0.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller wrote:
> Warum nicht die Adressen gleich als Integer verwalten?
Wahrscheinlich weil ich bisher für Adressierungen immer std_logic_vector 
verwendet habe.
Würdest du die Adressen denn im gesamten Design als Integer verwalten 
oder nur im FIFO Modul drin?

> mit der herstellerunabhängigen numeric_std
herstellerunabhängig ist immer gut ;-)

> Aber du solltest dir nochmal die Überlaufgrenzen ansehen.
Hab ich noch nicht getan.

Ich werde dann mal ein FIFO Modul samt passender Testbench erstellen, 
dann sehen wir weiter.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Würdest du die Adressen denn im gesamten Design als Integer verwalten
> oder nur im FIFO Modul drin?
An den Schnittstellen habe ich immer std_logic.
Innerhalb der Module wird das dann sofort auf ein Integer-Signal 
zugewiesen, und mit dem weitergearbeitet.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller wrote:
> An den Schnittstellen habe ich immer std_logic.
Dann bin ich beruhigt ;-)

> Innerhalb der Module wird das dann sofort auf ein Integer-Signal
> zugewiesen, und mit dem weitergearbeitet.
Gut, werde ich auch so versuchen.

wobei - die Adressen brauch ich ja ausserhalb des Moduls gar nicht, da 
kann ich auch gleich durchgängig mit "integer" resp. "natural" arbeiten.

Gibt es einen Vorteil von natural gegenüber integer, wenn bei beiden der 
Range "0 to 255" definiert ist?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Gibt es einen Vorteil von natural gegenüber integer, wenn bei beiden der
> Range "0 to 255" definiert ist?
subtype Natural is Integer range 0 to Integer'high;
Du schränkst damit nur den eingeschränkten Wertebereich von natural noch 
weiter ein. Also Antwort: nein.

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Lothar Miller wrote:
>> Kann man eigentlich direkt mit std_logic_vector rechnen?
> Nicht, wenn du die numeric_std Lib verwendest. Dafür gibts dann Casts
> nach unsigned/singend und Konvertierungen nach integer und zurück. Aber
> für einen Array-Zugriff brauchst du sowieso einen Integer. Warum nicht
> die Adressen gleich als Integer verwalten?

Ich habe nun versucht, die Adressen mit Integer zu verwalten:
    generic (dat_length : natural := 16;
           add_length   : natural := 4
           );
...
    constant mem_size : integer := 2**add_length;
    type     mem_type is array (mem_size-1 downto 0) of
             std_logic_vector(dat_length-1 downto 0);
    signal  mem         : mem_type;
    signal  addr_rd     : integer range 0 to mem_size-1;
    signal  addr_wr     : integer range 0 to mem_size-1;
...
            -- write data to memory
            mem(addr_wr) <= data_in(dat_length-1 downto 0);
            -- go to next write addr
            addr_wr <= addr_wr + 1;
...
            -- go to next read addr
            addr_rd <= addr_rd + 1;

Das funktioniert in der Simulation auch ganz wunderbar - bis zu Adresse 
15.

Sehe ich das richtig, dass sich die als Integer verwalteten Adressen nur 
bis vor den Überlauf verwalten lassen? Der Überlauf führt im ModelSim zu 
Fehler
"# ** Fatal: (vsim-3421) Value 16 for addr_wr is out of range 0 to 15."

Wie kann ich nun das Design auch mit Überlauf simulieren?
Doch wieder std_logic_vector für die Adresse verwenden?

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Martin Kohler wrote:
> Wie kann ich nun das Design auch mit Überlauf simulieren?
> Doch wieder std_logic_vector für die Adresse verwenden?
Nein, nimm für den Überlauf einen unsigned (numeric_std)  ;-)
Probiers mal so:
   addr_wr <= to_integer(to_unsigned(addr_wr,4) + 1);
wenn du die alten Synopsys-Libs verwendest, mußt du auf std_logic_vector 
und zurück casten.

Alternativ kannst du auch schreiben:
if (addr_wr<15) then
   addr_wr <= addr_wr + 1;
else
   addr_wr <= 0;
end if;
Da wäre ich mal gespannt, ob der Synthesizer rafft, dass er einfach 
weiterzählen kann. Ich werde das in der Mittagspause mal probieren ;-)

Schreib ein Speicherarray statt:
    type     mem_type is array (mem_size-1 downto 0) of
besser so:
    type     mem_type is array (0 to mem_size-1) of
Dann kannst du das Speicherarray in der "gewohnten" Reihenfolge mit := 
initialisieren.

Autor: Lothar Miller (lkmiller) (Moderator) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hier wie versprochen der Test der beiden Beschreibungen:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;


entity Adresscounter is
    Port ( clk : in  STD_LOGIC;
           addrA : out  STD_LOGIC_VECTOR (3 downto 0);
           addrB : out  STD_LOGIC_VECTOR (3 downto 0));
end Adresscounter;

architecture Behavioral of Adresscounter is
signal addr_a : integer range 0 to 15;
signal addr_b : integer range 0 to 15;
begin

  process begin
    wait until rising_edge(clk);
    if (addr_a<15) then addr_a <= addr_a + 1;
    else                addr_a <= 0;
    end if;
  end process;
  
  process begin
    wait until rising_edge(clk);
    addr_b <= to_integer(to_unsigned(addr_b,4) + 1);
  end process;
  
  addrA <= std_logic_vector(to_unsigned(addr_b,4));
  addrB <= std_logic_vector(to_unsigned(addr_b,4));
  
end Behavioral;
Und das Ergebnis der Synthese (Screenshot): Der Synthesizer setzt wie 
erwartet die beiden Beschreibungen in einen 4-Bit-Zähler (mit 
Wrap-Around) um.

Autor: Martin Kohler (mkohler)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Ich bin nun endlich dazu gekommen, das FIFO zu codieren, inkl. Testbench 
natürlich...

Kommentare dazu?

Autor: Martin Kohler (mkohler)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
die Testbench

Autor: Martin Kohler (mkohler)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
... und noch die Control Signale

Autor: Martin Kohler (mkohler)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jetzt hab ich doch tatsächlich den lesenden Zugriff nicht synchron 
gestaltet, so dass der Synthesizer mir ein distributed RAM bauen 
wollte...

der process sieht nun so aus:
    process (clk)
    begin
        if rising_edge(clk) then
            if reset = '1' then
                -- sync reset
                addr_rd     <= (others =>'0');
                addr_wr     <= (others =>'0');
                underflow   <= '0';
                overflow    <= '0';
            else
                -- process inputs
                if push = '1' then
                    if full_sig = '0' then
                        -- write data to memory
                        -- go to next write addr
                        mem(to_integer(addr_wr)) <= data_in(dat_length-1 downto 0);
                        addr_wr <= addr_wr + 1;
                    else
                        -- set overflow flag
                        overflow <= '1';
                    end if;
                end if;
                if pop = '1' then
                    if empty_sig = '0' then
                        -- go to next read addr only if not empty
                        addr_rd <= addr_rd + 1;
                    else
                        -- set underflow flag
                        underflow <= '1';
                    end if;
                end if;
                -- data output
                data_out <= mem(to_integer(addr_rd));
            end if;
        end if;
    end process;

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [vhdl]VHDL-Code[/vhdl]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.