Een AXI FIFO implementeren in Block RAM met Ready/Valid Handshake
Toen ik voor het eerst de AXI-interface tegenkwam tijdens het ontwerpen van een module, was ik verrast door de gestroomlijnde stroomcontrole-benadering. In tegenstelling tot traditionele protocollen die vertrouwen op busy/valid, full/valid of empty/valid paren, gebruikt AXI slechts twee signalen: ready en valid. Wat begon als frustratie werd snel fascinerend.
AXI sluit stroomcontrole direct in zijn handshake in, wat de noodzaak voor extra statuslijnen elimineert. Hoewel de basisregels eenvoudig zijn, is zorgvuldige aandacht vereist bij het afbeelden van het protocol op FPGA-hardware. In deze tutorial loop ik door een VHDL-implementatie van een AXI FIFO die op block RAM draait.
AXI lost het uitgesteld-door-één-cyclus-probleem op
Over-lezen en overschrijven voorkomen is een veelvoorkomende uitdaging wanneer twee kloksynchrone modules datastromen uitwisselen. De kern van het probleem is dat elke module de uitgang van de ander pas één klokcyclus later kan observeren.
Het diagram hierboven illustreert een sequentiële module die schrijft naar een FIFO die het klassieke write enable/full signaleringsschema gebruikt. De schrijver assert wr_en om data in de FIFO te duwen, terwijl de FIFO full ophaalt wanneer geen ruimte meer beschikbaar is. Het probleem is dat full precies op de stijgende klokflank geassert wordt, zodat de schrijver er niet meer in dezelfde cyclus op kan reageren. De oplossing is om een look-ahead signaal zoals almost_empty te bieden, wat de upstream-logica een voorsprong geeft.
De ready/valid handshake
AXI-streaminterfaces lossen dit timing-dilemma op met een twee-seinhandshake: ready en valid. De ontvanger stuurt ready; een hoge waarde geeft aan dat het klaar is om een nieuw datum te accepteren. De zender stuurt valid; een hoge waarde betekent dat de data op de bus stabiel is en gesampled kan worden. Een overdracht vindt alleen plaats wanneer beide signalen hoog zijn op dezelfde stijgende flank.
De waveform demonstreert een enkele data-itemoverdracht. Data wordt gesampled op de stijgende klokflank, net als in conventionele synchrone ontwerpen.
Implementatie
Er zijn verschillende manieren om een AXI FIFO in VHDL te bouwen. Hoewel een schuifregister zou werken, is een ringbuffer ondersteund door block RAM de meest efficiënte en schaalbare keuze. Hieronder presenteer ik een modulaire ontwerp die de logica scheidt in speciale processen voor duidelijkheid en synthese-vriendelijkheid.
De Entiteit
De entiteit definieert twee generieke parameters – ram_width en ram_depth – die de woordbreedte en de FIFO-diepte instellen. De capaciteit is effectief ram_depth - 1, omdat één slot gereserveerd is om een volle FIFO van een lege te differentiëren.
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 interface volgt AXI-conventies: de invoerkant gebruikt in_ready en in_valid, terwijl de uitvoerkant de signalen spiegelt met omgekeerde richtingen. Deze symmetrie laat meerdere FIFO's naadloos aan elkaar koppelen.
Signaaldeclaraties
Eerst definiëren we een RAM-type dat overeenkomt met de generieke breedte en diepte, en instantieren we de array:
-- De FIFO is vol wanneer de RAM ram_depth - 1 elementen bevat type ram_type is array (0 to ram_depth - 1) of std_logic_vector(in_data'range); signal ram : ram_type;
Vervolgens declareren we een index-subtype dat elk adres in de RAM kan vertegenwoordigen, en vier kernsignalen: head, tail, count, en een vertraagde kopie count_p1:
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;
We bewaren ook interne versies van de ready en valid outputs om het lezen van out signalen binnen de architectuur te vermijden:
signal in_ready_i : std_logic; signal out_valid_i : std_logic;
Een hulp-signaal volgt wanneer een gelijktijdige read en write in de vorige cyclus plaatsvond, wat cruciaal is voor het correct instellen van out_valid wanneer de FIFO bijna leeg is:
signal read_while_write_p1 : std_logic;
Subprogramma's
De next_index functie bepaalt het volgende adres gebaseerd op of een read of write handshake net voltooid is. Het wrapt around wanneer het einde van de array bereikt is.
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;
De index_proc procedure inkapselt de synchrone logica die ofwel de head of tail pointer update. Dit vermijdt dubbele code en houdt de architectuur netjes.
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;
Interne Signalen Kopiëren naar de Uitgang
in_ready <= in_ready_i; out_valid <= out_valid_i;
Head en Tail Updaten
We instantieren de hulp-procedure tweemaal — eenmaal voor de schrijf-pointer (head) en eenmaal voor de lees-pointer (tail):
PROC_HEAD : index_proc(clk, rst, head, in_ready_i, in_valid); PROC_TAIL : index_proc(clk, rst, tail, out_ready, out_valid_i);
Block RAM Infereren
Block RAM in de meeste FPGA's is synchroon. Om de synthese-tool een RAM-primitief te laten infereren, houden we de read- en write-operaties binnen een geklokt proces en vermijden we expliciete enable-signalen:
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;
De schrijving vindt onvoorwaardelijk plaats op de cyclus wanneer in_valid hoog is, en de leesdata wordt elke cyclus geüpdatet zodat de consument een geldig woord kan observeren wanneer de FIFO data bevat.
Het Aantal Elementen in de FIFO Tellen
Het count signaal weerspiegelt de huidige bezetting. Wanneer head voor tail ligt, is de telling simpelweg het verschil; als de head gewrapt is, tellen we de totale diepte op:
PROC_COUNT : process(head, tail) begin if head < tail then count <= head - tail + ram_depth; else count <= head - tail; end if; end process;
De vertraagde kopie count_p1 is nodig om te detecteren wanneer de FIFO in de vorige cyclus leeg was, wat out_valid beïnvloedt:
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;
De Ready-uitgang Updaten
De schrijver moet verteld worden wanneer de FIFO meer data kan accepteren. Het ready-signaal is hoog wanneer de telling minder is dan ram_depth - 1:
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;
Gelijktijdige Read en Write Detecteren
Wanneer zowel een read als een write in dezelfde cyclus plaatsvindt, verandert de FIFO niet in bezetting, maar de interne staat moet weerspiegelen dat een waarde pas na twee cycli beschikbaar komt wanneer de FIFO bijna leeg is. We vangen dit evenement daarom op in read_while_write_p1:
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;
De Valid-uitgang Updaten
Het out_valid signaal vertelt downstream-logica wanneer out_data een bruikbaar woord bevat. Het combinatoire proces hieronder zet het standaard hoog, maar wist het in twee hoekgevallen:
PROC_OUT_VALID : process(count, count_p1, read_while_write_p1) begin out_valid_i <= '1'; -- Als de FIFO leeg is of in de vorige cyclus leeg was if count = 0 or count_p1 = 0 then out_valid_i <= '0'; end if; -- Als een gelijktijdige read/write plaatsvond toen de FIFO maar één item had if count = 1 and read_while_write_p1 = '1' then out_valid_i <= '0'; end if; end process;
De eerste voorwaarde garandeert dat out_valid laag blijft tijdens de voortplantingsvertraging na de eerste schrijf. De tweede bewaakt tegen een situatie waar de FIFO eruitziet alsof het vol blijft voor één cyclus, maar eigenlijk geen data heeft om uit te geven.
Synthese in Vivado
Om dit ontwerp in Xilinx Vivado te synthetiseren, geef concrete waarden voor de generics op. In het Settings-dialoogvenster (General → Generics/Parameters), kies een diepte die matcht met een RAMB36E1-primitief voor het doel-Zynq-apparaat. Na implementatie is de resource-gebruik bescheiden — één block RAM plus een handjevol LUTs en FFs.


AXI is meer dan ready/valid
AXI (Advanced eXtensible Interface) is een component van ARM's AMBA-busarchitectuur en biedt een rijke feature-set buiten de simpele ready/valid-handshake. Voor dieper begrip, overweeg de volgende bronnen:
- Wikipedia: AXI
- ARM AXI introductie
- Xilinx AXI introductie
- AXI4-specificatie
VHDL
- Industrial Internet: Asset Management revolutioneren door connectiviteit, analytics en gebruikerservaring
- Asset-tracking best practices: De waarde van uw assetdata ontgrendelen
- Data-gestuurde Supply Chains: De Logistiek van Morgen Aandrijven
- Betrouwbare supply-chain-data waarborgen: een bewezen strategie
- Hoe AI 'Vuil' Data-problemen in Supply Chains Oplost
- De Cloud Revolutie: Hoe Cloud Computing IT, Beveiliging en Carrières Transformeert
- IoT-data Ontgrendelen: Hoe Business Rules Management Enterprise-waarde Drijft
- Uw Productieoperaties Voor AI Klaarmaken Met IoT
- Inzicht In Het Internet of Things Verbeteren: Data Visualisatie En Grafdatabases Benutten
- IoT in de Horeca Maximaliseren: Efficiëntie Verhogen, Afval Verminderen en Klanten Verblijden
- FPGA Block RAM initialiseren vanuit tekstbestanden met VHDL's TEXTIO-bibliotheek