Industriële fabricage
Industrieel internet der dingen | Industriële materialen | Onderhoud en reparatie van apparatuur | Industriële programmering |
home  MfgRobots >> Industriële fabricage >  >> Industrial programming >> VHDL

Hoe maak je een AXI FIFO in blok RAM met behulp van de ready/valid handshake?

Ik ergerde me een beetje aan de eigenaardigheden van de AXI-interface toen ik voor het eerst logica moest maken om een ​​AXI-module te koppelen. In plaats van de reguliere bezet/geldig, vol/geldig of leeg/geldig stuursignalen, gebruikt de AXI-interface twee stuursignalen genaamd "gereed" en "geldig". Mijn frustratie veranderde al snel in ontzag.

De AXI interface heeft een ingebouwde flow control zonder gebruik te maken van extra stuursignalen. De regels zijn eenvoudig genoeg om te begrijpen, maar er zijn een paar valkuilen waarmee u rekening moet houden bij het implementeren van de AXI-interface op een FPGA. Dit artikel laat zien hoe je een AXI FIFO maakt in VHDL.

AXI lost het probleem met één cyclusvertraging op

Het voorkomen van overlezen en overschrijven is een veelvoorkomend probleem bij het maken van datastroominterfaces. Het probleem is dat wanneer twee geklokte logische modules communiceren, elke module de uitgangen van zijn tegenhanger slechts met één klokcyclusvertraging kan lezen.

De afbeelding hierboven toont het timingdiagram van een sequentiële module die schrijft naar een FIFO die de write enable/full gebruikt signaleringsschema. Een interfacemodule schrijft gegevens naar de FIFO door de wr_en . te bevestigen signaal. De FIFO bevestigt de full signaleren wanneer er geen ruimte is voor een ander gegevenselement, waardoor de gegevensbron wordt gevraagd te stoppen met schrijven.

Helaas kan de interfacemodule niet op tijd stoppen zolang deze alleen geklokte logica gebruikt. De FIFO verhoogt de full vlag precies op de stijgende flank van de klok. Tegelijkertijd probeert de interfacemodule het volgende data-element te schrijven. Het kan niet samplen en reageren op de full signaal voordat het te laat is.

Een oplossing is het toevoegen van een extra almost_empty signaal, hebben we dit gedaan in de tutorial Hoe maak je een ringbuffer FIFO in VHDL-zelfstudie. Het extra signaal gaat vooraf aan de empty signaal, waardoor de interfacemodule tijd heeft om te reageren.

De klaar/geldige handdruk

Het AXI-protocol implementeert stroomregeling met slechts twee stuursignalen in elke richting, één genaamd ready en de andere valid . De ready signaal wordt aangestuurd door de ontvanger, een logische '1' waarde op dit signaal betekent dat de ontvanger klaar is om een ​​nieuw gegevensitem te accepteren. De valid signaal, aan de andere kant, wordt gecontroleerd door de afzender. De afzender stelt valid . in tot '1' wanneer de op de databus gepresenteerde gegevens geldig zijn voor bemonstering.

Hier komt het belangrijkste deel: gegevensoverdracht vindt alleen plaats wanneer zowel ready en valid zijn '1' op dezelfde klokcyclus. De ontvanger informeert wanneer het klaar is om gegevens te accepteren, en de afzender plaatst de gegevens eenvoudigweg daar wanneer het iets te verzenden heeft. Overdracht vindt plaats wanneer beide akkoord gaan, wanneer de afzender klaar is om te verzenden en de ontvanger klaar is om te ontvangen.

De bovenstaande golfvorm toont een voorbeeldtransactie van één gegevensitem. Sampling vindt plaats op de stijgende klokflank, zoals meestal het geval is bij geklokte logica.

Implementatie

Er zijn veel manieren om een ​​AXI FIFO in VHDL te implementeren. Het kan een schuifregister zijn, maar we zullen een ringbufferstructuur gebruiken omdat dit de meest eenvoudige manier is om een ​​FIFO in blok-RAM te maken. Je kunt het allemaal creëren in één gigantisch proces met behulp van variabelen en signalen, of je kunt de functionaliteit opsplitsen in meerdere processen.

Deze implementatie gebruikt aparte processen voor de meeste signalen die geüpdatet moeten worden. Alleen de processen die synchroon moeten zijn, zijn gevoelig voor de klok, de andere gebruiken combinatorische logica.

De entiteit

De entiteitsverklaring bevat een generieke poort die wordt gebruikt voor het instellen van de breedte van de invoer- en uitvoerwoorden, evenals het aantal slots waarvoor ruimte in het RAM moet worden gereserveerd. De capaciteit van de FIFO is gelijk aan de RAM-diepte min één. Er wordt altijd één slot leeg gehouden om onderscheid te maken tussen een volle en een lege FIFO.

entity axi_fifo is
  generic (
    ram_width : natural;
    ram_depth : natural
  );
  port (
    clk : in std_logic;
    rst : in std_logic;

    -- AXI input interface
    in_ready : out std_logic;
    in_valid : in std_logic;
    in_data : in std_logic_vector(ram_width - 1 downto 0);

    -- AXI output interface
    out_ready : in std_logic;
    out_valid : out std_logic;
    out_data : out std_logic_vector(ram_width - 1 downto 0)
  );
end axi_fifo; 

De eerste twee signalen in de poortdeclaratie zijn de klok- en reset-ingangen. Deze implementatie maakt gebruik van synchrone reset en is gevoelig voor de stijgende flank van de klok.

Er is een AXI-stijl ingangsinterface die gebruik maakt van de gereed/geldige stuursignalen en een ingangsdatasignaal van generieke breedte. Eindelijk komt de AXI-uitgangsinterface met soortgelijke signalen als de ingang, alleen met omgekeerde richtingen. Signalen die bij de invoer- en uitvoerinterface horen, worden voorafgegaan door in_ of out_ .

De uitgang van de ene AXI FIFO kon direct worden aangesloten op de ingang van een andere, de interfaces sluiten perfect op elkaar aan. Hoewel, een betere oplossing dan ze te stapelen zou zijn om de ram_depth . te verhogen generiek als u een grotere FIFO wilt.

Signaalverklaringen

De eerste twee verklaringen in het declaratieve gebied van het VHDL-bestand verklaren het RAM-type en het signaal ervan. Het RAM wordt dynamisch gedimensioneerd op basis van de generieke ingangen.

-- The FIFO is full when the RAM contains ram_depth - 1 elements
type ram_type is array (0 to ram_depth - 1)
  of std_logic_vector(in_data'range);
signal ram : ram_type;

Het tweede codeblok declareert een nieuw geheeltallig subtype en vier signalen daaruit. De index_type is bemeten om precies de diepte van het RAM-geheugen weer te geven. De head signaal geeft altijd de RAM-sleuf aan die bij de volgende schrijfbewerking zal worden gebruikt. De tail signaal wijst naar de sleuf die bij de volgende leesbewerking zal worden gebruikt. De waarde van de count signaal is altijd gelijk aan het aantal elementen dat momenteel in de FIFO is opgeslagen, en count_p1 is een kopie van hetzelfde signaal vertraagd met één klokcyclus.

-- Newest element at head, oldest element at tail
subtype index_type is natural range ram_type'range;
signal head : index_type;
signal tail : index_type;
signal count : index_type;
signal count_p1 : index_type;

Dan komen er twee signalen genaamd in_ready_i en out_valid_i . Dit zijn slechts kopieën van de entiteitsuitgangen in_ready en out_valid . De _i postfix betekent gewoon intern , het maakt deel uit van mijn codeerstijl.

-- Internal versions of entity signals with mode "out"
signal in_ready_i : std_logic;
signal out_valid_i : std_logic;

Ten slotte declareren we een signaal dat zal worden gebruikt om gelijktijdig lezen en schrijven aan te geven. Ik zal het doel later in dit artikel uitleggen.

-- True the clock cycle after a simultaneous read and write
signal read_while_write_p1 : std_logic;

Subprogramma's

Na de signalen declareren we een functie voor het verhogen van onze aangepaste index_type . De next_index functie kijkt naar de read en valid parameters om te bepalen of er een lopende lees- of lees-/schrijftransactie is. Als dat het geval is, wordt de index verhoogd of ingepakt. Zo niet, dan wordt de ongewijzigde indexwaarde geretourneerd.

function next_index(
  index : index_type;
  ready : std_logic;
  valid : std_logic) return index_type is
begin
  if ready = '1' and valid = '1' then
    if index = index_type'high then
      return index_type'low;
    else
      return index + 1;
    end if;
  end if;

  return index;
end function;

Om ons te behoeden voor herhaaldelijk typen, creëren we de logica voor het bijwerken van de head en tail signalen in een procedure, in plaats van als twee identieke processen. De update_index procedure neemt de klok en reset signalen, een signaal van index_type , een ready signaal, en een valid signaal als ingangen.

procedure index_proc(
  signal clk : in std_logic;
  signal rst : in std_logic;
  signal index : inout index_type;
  signal ready : in std_logic;
  signal valid : in std_logic) is
begin
    if rising_edge(clk) then
      if rst = '1' then
        index <= index_type'low;
      else
        index <= next_index(index, ready, valid);
      end if;
    end if;
end procedure;

Dit volledig synchrone proces gebruikt de next_index functie om de index . bij te werken signaal wanneer de module niet meer kan worden gereset. In reset, de index signaal wordt ingesteld op de laagste waarde die het kan vertegenwoordigen, wat altijd 0 is vanwege de manier waarop index_type en ram_type wordt verklaard. We hadden 0 als resetwaarde kunnen gebruiken, maar ik probeer zoveel mogelijk harde codering te vermijden.

Kopieer interne signalen naar de uitgang

Deze twee gelijktijdige instructies kopiëren de interne versies van de uitgangssignalen naar de daadwerkelijke uitgangen. We moeten werken met interne kopieën omdat VHDL ons niet toestaat entiteitssignalen te lezen met modus out binnenkant van de module. Een alternatief zou zijn geweest om in_ready . te declareren en out_valid met modus inout , maar de meeste coderingsnormen van bedrijven beperken het gebruik van inout entiteitssignalen.

in_ready <= in_ready_i;
out_valid <= out_valid_i;

Kop en staart bijwerken

We hebben het al gehad over de index_proc procedure die wordt gebruikt om de head . bij te werken en tail signalen. Door de juiste signalen toe te wijzen aan de parameters van dit subprogramma, krijgen we het equivalent van twee identieke processen, één voor het aansturen van de FIFO-invoer en één voor de uitvoer.

-- Update head index on write
PROC_HEAD : index_proc(clk, rst, head, in_ready_i, in_valid);

-- Update tail index on read
PROC_TAIL : index_proc(clk, rst, tail, out_ready, out_valid_i);

Aangezien zowel de head en de tail zijn ingesteld op dezelfde waarde door de resetlogica, zal de FIFO aanvankelijk leeg zijn. Dat is hoe deze ringbuffer werkt, wanneer beide naar dezelfde index wijzen, betekent dit dat de FIFO leeg is.

Blok RAM afleiden

In de meeste FPGA-architecturen zijn de blok-RAM-primitieven volledig synchrone componenten. Dit betekent dat als we willen dat de synthesetool blok-RAM afleidt uit onze VHDL-code, we de lees- en schrijfpoorten in een geklokt proces moeten plaatsen. Er kunnen ook geen reset-waarden zijn gekoppeld aan blok-RAM.

PROC_RAM : process(clk)
begin
  if rising_edge(clk) then
    ram(head) <= in_data;
    out_data <= ram(next_index(tail, out_ready, out_valid_i));
  end if;
end process;

Er is geen lezen inschakelen of schrijf inschakelen hier zou dat te traag zijn voor AXI. In plaats daarvan schrijven we continu naar het RAM-slot waarnaar wordt verwezen door de head inhoudsopgave. Wanneer we vervolgens vaststellen dat er een schrijftransactie heeft plaatsgevonden, gaan we eenvoudigweg de head . vooruit om de geschreven waarde vast te leggen.

Evenzo, out_data wordt bij elke klokcyclus bijgewerkt. De tail aanwijzer gaat gewoon naar de volgende sleuf wanneer er wordt gelezen. Merk op dat de next_index functie wordt gebruikt om het adres voor de leespoort te berekenen. We moeten dit doen om ervoor te zorgen dat het RAM snel genoeg reageert na het lezen en begint met het uitvoeren van de volgende waarde.

Tel het aantal elementen in de FIFO

Het aantal elementen in het RAM tellen is gewoon een kwestie van de head van de tail . Als de head is ingepakt, moeten we dit compenseren met het totale aantal slots in het RAM. We hebben toegang tot deze informatie via de ram_depth constante van de generieke invoer.

PROC_COUNT : process(head, tail)
begin
  if head < tail then
    count <= head - tail + ram_depth;
  else
    count <= head - tail;
  end if;
end process;

We moeten ook de vorige waarde van de count . bijhouden signaal. Het onderstaande proces maakt er een versie van die met één klokcyclus is vertraagd. De _p1 postfix is ​​een naamgevingsconventie om dit aan te geven.

PROC_COUNT_P1 : process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      count_p1 <= 0;
    else
      count_p1 <= count;
    end if;
  end if;
end process;

Update de klaar uitvoer

De in_ready signaal zal '1' . zijn wanneer deze module klaar is om een ​​ander gegevensitem te accepteren. Dit zou het geval moeten zijn zolang de FIFO niet vol is, en dat is precies wat de logica van dit proces zegt.

PROC_IN_READY : process(count)
begin
  if count < ram_depth - 1 then
    in_ready_i <= '1';
  else
    in_ready_i <= '0';
  end if;
end process;

Detecteer gelijktijdig lezen en schrijven

Vanwege een hoekgeval dat ik in de volgende sectie zal uitleggen, moeten we gelijktijdige lees- en schrijfbewerkingen kunnen identificeren. Elke keer dat er geldige lees- en schrijftransacties zijn tijdens dezelfde klokcyclus, stelt dit proces de read_while_write_p1 in signaal naar '1' op de volgende klokcyclus.

PROC_READ_WHILE_WRITE_P1: process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      read_while_write_p1 <= '0';

    else
      read_while_write_p1 <= '0';
      if in_ready_i = '1' and in_valid = '1' and
        out_ready = '1' and out_valid_i = '1' then
        read_while_write_p1 <= '1';
      end if;
    end if;
  end if;
end process;

Update de geldige uitvoer

De out_valid signaal geeft aan downstream-modules aan dat de gegevens gepresenteerd op out_data is geldig en kan op elk moment worden bemonsterd. De out_data signaal komt rechtstreeks van de RAM-uitgang. Implementatie van de out_valid signaal is een beetje lastig vanwege de extra klokcyclusvertraging tussen blok RAM-invoer en -uitvoer.

De logica wordt geïmplementeerd in een combinatieproces, zodat het zonder vertraging kan reageren op het veranderende ingangssignaal. De eerste regel van het proces is een standaardwaarde die de out_valid . instelt signaal naar '1' . Dit is de geldende waarde als geen van de twee volgende If-statements wordt geactiveerd.

PROC_OUT_VALID : process(count, count_p1, read_while_write_p1)
begin
  out_valid_i <= '1';

  -- If the RAM is empty or was empty in the prev cycle
  if count = 0 or count_p1 = 0 then
    out_valid_i <= '0';
  end if;

  -- If simultaneous read and write when almost empty
  if count = 1 and read_while_write_p1 = '1' then
    out_valid_i <= '0';
  end if;

end process;

Het eerste If-statement controleert of de FIFO leeg is of leeg was in de vorige klokcyclus. Uiteraard is de FIFO leeg als er 0 elementen in zitten, maar we moeten ook kijken naar het vulniveau van de FIFO in de vorige klokcyclus.

Beschouw de golfvorm hieronder. Aanvankelijk is de FIFO leeg, zoals aangegeven door de count signaal is 0 . Dan vindt een schrijven plaats op de derde klokcyclus. RAM-slot 0 wordt bijgewerkt in de volgende klokcyclus, maar het duurt een extra cyclus voordat de gegevens op de out_data verschijnen uitvoer. Het doel van de or count_p1 = 0 verklaring is om ervoor te zorgen dat out_valid blijft '0' (rood omcirkeld) terwijl de waarde zich door het RAM voortplant.

De laatste If-verklaring beschermt tegen een ander hoekgeval. We hebben zojuist besproken hoe u omgaat met het speciale geval van 'write-on-empty' door de huidige en eerdere FIFO-vulniveaus te controleren. Maar wat gebeurt er als en we een gelijktijdige lees- en schrijfbewerking uitvoeren wanneer count is al 1 ?

De onderstaande golfvorm toont een dergelijke situatie. Aanvankelijk is er één data-item D0 aanwezig in de FIFO. Het is er al een tijdje, dus beide count en count_p1 zijn 0 . Dan komt er in de derde klokcyclus een gelijktijdig lezen en schrijven. Eén item verlaat de FIFO en een nieuwe komt erin, waardoor de tellers ongewijzigd blijven.

Op het moment van lezen en schrijven is er geen volgende waarde in het RAM die klaar is om te worden uitgevoerd, zoals dat het geval zou zijn als het vulniveau hoger was dan één. We moeten twee klokcycli wachten voordat de invoerwaarde op de uitvoer verschijnt. Zonder aanvullende informatie zou het onmogelijk zijn om dit hoekgeval en de waarde van out_valid . te detecteren bij de volgende klokcyclus (gemarkeerd als ononderbroken rood) zou ten onrechte worden ingesteld op '1' .

Daarom hebben we de read_while_write_p1 . nodig signaal. Het detecteert dat er gelijktijdig wordt gelezen en geschreven, en we kunnen hier rekening mee houden door out_valid in te stellen tot '0' in die klokcyclus.

Synthetiseren in Vivado

Om het ontwerp als een stand-alone module in Xilinx Vivado te implementeren, moeten we eerst waarden geven aan de generieke inputs. Dit kan in Vivado worden bereikt door de Instellingen . te gebruiken → AlgemeenAlgemeen/Parameters menu, zoals weergegeven in de onderstaande afbeelding.

De generieke waarden zijn gekozen om overeen te komen met de RAMB36E1-primitief in de Xilinx Zynq-architectuur die het doelapparaat is. Het gebruik van resources na de implementatie wordt weergegeven in de onderstaande afbeelding. De AXI FIFO gebruikt één blok RAM en een klein aantal LUT's en flip-flops.

AXI is meer dan klaar/geldig

AXI staat voor Advanced eXtensible Interface en maakt deel uit van ARM's Advanced Microcontroller Bus Architecture (AMBA) -standaard. De AXI-standaard is veel meer dan de read/valid handshake. Als je meer wilt weten over AXI, raad ik deze bronnen aan om verder te lezen:

  • Wikipedia:AXI
  • ARM AXI introductie
  • Introductie Xilinx AXI
  • AXI4-specificatie


VHDL

  1. Cloud en hoe het de IT-wereld verandert
  2. Hoe u het meeste uit uw gegevens haalt
  3. Hoe RAM vanuit een bestand te initialiseren met TEXTIO
  4. Hoe u zich voorbereidt op AI met behulp van IoT
  5. Hoe het industriële internet activabeheer verandert
  6. Praktische tips voor het bijhouden van activa:hoe u uw zuurverdiende activagegevens optimaal kunt benutten
  7. Hoe krijgen we een beter beeld van het IoT?
  8. Hoe u het meeste uit IoT haalt in de restaurantbranche
  9. Hoe data de supply chain van de toekomst mogelijk maakt
  10. Hoe u supply chain-gegevens betrouwbaar maakt
  11. Hoe AI het probleem van 'vuile' gegevens aanpakt