Add asynchronous FIFO with AXI-like interface

Implements an asynchronous FIFO with independent read/write clocks and Gray code pointers for clock domain crossing. Refactors internal logic to use components like PipelineRegister and GrayCounter for improved synchronization and readability. Includes a testbench to validate functionality with data integrity checks.

Relates to version 1.1.2 updates.
This commit is contained in:
2025-04-16 17:30:42 +00:00
parent ffbf5c4984
commit 57ad2aa377
3 changed files with 344 additions and 0 deletions

0
src/AsyncFIFO.ucf Normal file
View File

224
src/AsyncFIFO.vhd Normal file
View File

@@ -0,0 +1,224 @@
----------------------------------------------------------------------------------
--@ @brief Asynchronous FIFO with AXI like interface
--@ @version 1.1.2
--@ @author Maximilian Passarello ([Blog](mpassarello.de))
--@ @copyright [MIT](LICENSE)
--@
--@ Asynchronous FIFO with Gray Code Read & Write Pointer
--@ and AXI like interface.
--@
--@ ## History:
--@ ### 1.1.2 (2024-03-10) Refactored the code:
--@ - Redesigned the code to use the `PipelineRegister` component for the
--@ synchronization of the read and write pointers.
--@ - Renamed the signals to be more descriptive.
--@ ### 1.1.1 (2024-03-10) Complete overhaul of AsyncFIFO:
--@ - Added GrayCounter as a component
--@ - Optimized port definitions
--@ - Added timing diagrams
--@ - Create async. Flag generation
--@ ### 1.1.0 (2009-05-16) Initial version
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.NUMERIC_STD.all;
use ieee.math_real.all;
entity AsyncFIFO is
generic (
--@ FIFO Word width: Data width of the FIFO
G_Width : integer := 32;
--@ FIFO depth: Number of words the FIFO can store
G_Depth : integer := 4;
--@ Implementation of the RAM: "Block" or "Distributed"
G_RamTypeFifo : string := "Distributed"
);
port (
--@ @virtualbus Write-Interface @dir in FIFO write interface
--@ Write clock; indipendent from the read clock. **Rising edge sensitive**
I_Write_CLK : in std_logic;
--@ Write clock enable: Used for the `Write`- and `WriteGrayCounter`-process. **Active high**
I_Write_CE : in std_logic := '1';
--@ Data input: Must be valid at the rising edge of the write clock if the write enable signal is set.
I_Write_Data : in std_logic_vector(G_Width - 1 downto 0);
--@ Enable the write of the data to the FIFO, if the FIFO is not full. **Active high**
I_Write_Valid : in std_logic := '0';
--@ Full flag: Indicates if the FIFO is full. **Active high**
O_Write_Ready : out std_logic := '0';
--@ @end
--@ @virtualbus Read-Interface @dir out FIFO read interface
--@ Read clock; indipendent from the write clock. **Rising edge sensitive**
I_Read_CLK : in std_logic;
--@ Read clock enable: Used for the `Read`- and `ReadGrayCounter`-process. **Active high**
I_Read_CE : in std_logic := '1';
--@ Data output: The data is valid at the rising edge of the read clock
--@ one cycle after the read enable signal is set.
O_Read_Data : out std_logic_vector(G_Width - 1 downto 0) := (others => '-');
--@ Enable the read of the data from the FIFO, if the FIFO is not empty. **Active high**
I_Read_Ready : in std_logic := '0';
--@ Empty flag: Indicates if the FIFO is empty. **Active high**
O_Read_Valid : out std_logic := '0'
--@ @end
);
end AsyncFIFO;
architecture Behavioral of AsyncFIFO is
--@ FIFO memory address width:
--@ The address width is calculated from the depth of the FIFO.
constant K_AddressWidth : integer := integer(ceil(log2(real(G_Depth))));
--@ Define the number of synchronization stages for the write and read pointer
--@ crossing the clock domain.
constant K_SyncStages : integer := 2;
--@ FIFO memory type: `Depth` x `Width`
type T_FifoType is array(G_Depth - 1 downto 0)
of std_logic_vector(G_Width - 1 downto 0);
--@ FIFO memory
--@ The FIFO memory is implemented as a RAM with the specified `RamTypeFifo`:
--@ "Block" or "Distributed".
signal S_Fifo : T_FifoType;
attribute RAM_STYLE : string;
attribute RAM_STYLE of S_Fifo : signal is G_RamTypeFifo;
--@ Points to the current write address of the FIFO.
signal R_Write_Pointer : std_logic_vector(K_AddressWidth - 1 downto 0);
--@ With read clk synchronized write pointer.
signal R_Write_PointerSync : std_logic_vector(K_AddressWidth - 1 downto 0);
--@ Points to the next write address of the FIFO.
signal R_Write_LAPointer : std_logic_vector(K_AddressWidth - 1 downto 0);
--@ Shows that the FIFO is not full and therefore writeable.
signal C_Write_Ready : std_logic := '0';
--@ Enable the write pointer to count up.
signal C_Write_PointerEnable : std_logic := '0';
--@ Points to the current read address of the FIFO.
signal R_Read_Pointer : std_logic_vector(K_AddressWidth - 1 downto 0);
--@ With write clk synchronized read pointer.
signal R_Read_PointerSync : std_logic_vector(K_AddressWidth - 1 downto 0);
--@ Shows that the FIFO ist not empty and therefore readable.
signal C_Read_Valid : std_logic := '0';
--@ Enable the read pointer to count up.
signal C_Read_PointerEnable : std_logic := '0';
begin
C_Write_PointerEnable <= C_Write_Ready and I_Write_Valid;
--@ Write pointer as a Gray counter with a look ahead value of `+1`.
I_WritePointer : entity work.GrayCounter
generic map(
G_Width => K_AddressWidth,
G_LookAhead => 1
)
port map
(
I_CLK => I_Write_CLK,
I_CE => I_Write_CE,
I_CountEnable => C_Write_PointerEnable,
O_Value => R_Write_Pointer,
O_LAValue => R_Write_LAPointer
);
--@ Write pointer synchronized to the read clock.
--@ The write pointer is synchronized to the read clock to avoid metastability.
--@ Use `K_SyncStages` to set the number of synchronization stages.
I_WritePointerSync : entity work.PipelineRegister
generic map(
G_PipelineStages => K_SyncStages,
G_Width => K_AddressWidth,
G_RegisterBalancing => "no"
)
port map
(
I_CLK => I_Read_CLK,
I_Enable => '1',
I_Data => R_Write_Pointer,
O_Data => R_Write_PointerSync
);
--@ The write AXI flag is generated by comparing the write pointer look ahead value
--@ with the read pointer synchronized to the write clock.
--@ If the write pointer look ahead value is equal to the read pointer
--@ the FIFO is full and the `Ready` signal is set to `0`.
P_WriteAXIFlag : process (R_Write_LAPointer, R_Read_PointerSync)
begin
if R_Write_LAPointer = R_Read_PointerSync then
C_Write_Ready <= '0';
else
C_Write_Ready <= '1';
end if;
end process P_WriteAXIFlag;
O_Write_Ready <= C_Write_Ready;
--@ The write process is triggered by every rising edge of the write clock
--@ if the write clock enable signal is set and AXI indicates writeable.
P_Write : process (I_Write_CLK)
begin
if rising_edge(I_Write_CLK) then
if I_Write_CE = '1' then
if (C_Write_Ready and I_Write_Valid) = '1' then
S_Fifo(to_integer(unsigned(R_Write_Pointer))) <= I_Write_Data;
end if;
end if;
end if;
end process;
C_Read_PointerEnable <= C_Read_Valid and I_Read_Ready;
--@ Read pointer as a Gray counter.
I_ReadPointer : entity work.GrayCounter
generic map(
G_Width => K_AddressWidth
)
port map
(
I_CLK => I_Read_CLK,
I_CE => I_Read_CE,
I_CountEnable => C_Read_PointerEnable,
O_Value => R_Read_Pointer
);
--@ Read pointer synchronized to the write clock.
--@ The read pointer is synchronized to the write clock to avoid metastability.
--@ Use `K_SyncStages` to set the number of synchronization stages.
I_ReadPointerSync : entity work.PipelineRegister
generic map(
G_PipelineStages => K_SyncStages,
G_Width => K_AddressWidth,
G_RegisterBalancing => "no"
)
port map
(
I_CLK => I_Write_CLK,
I_Enable => '1',
I_Data => R_Read_Pointer,
O_Data => R_Read_PointerSync
);
--@ The read AXI flag is generated by comparing the read pointer with the write pointer
--@ synchronized to the read clock.
--@ If the read pointer is equal to the write pointer
--@ the FIFO is empty and the `Valid` signal is set to `0`.
P_ReadAXIFlag : process (R_Read_Pointer, R_Write_PointerSync)
begin
if R_Read_Pointer = R_Write_PointerSync then
C_Read_Valid <= '0';
else
C_Read_Valid <= '1';
end if;
end process P_ReadAXIFlag;
O_Read_Valid <= C_Read_Valid;
--@ The read process is triggered by every rising edge of the read clock
--@ if the read clock enable signal is set.
P_Read : process (I_Read_CLK)
begin
if rising_edge(I_Read_CLK) then
if I_Read_CE = '1' then
O_Read_Data <= S_Fifo(to_integer(unsigned(R_Read_Pointer)));
end if;
end if;
end process;
end Behavioral;

120
tests/AsyncFIFO_tb.vhd Normal file
View File

@@ -0,0 +1,120 @@
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.math_real.all;
entity AsyncFIFO_tb is
-- The testbench does not require any ports
end entity AsyncFIFO_tb;
architecture behavior of AsyncFIFO_tb is
signal WriteCLK, ReadCLK, WriteRST, ReadRST, WriteCE, ReadCE : std_logic := '0';
signal DataIn, DataOut : std_logic_vector(31 downto 0) := (others => '0');
signal ReadReady, ReadValid : std_logic := '0';
signal WriteReady, WriteValid : std_logic := '0';
constant WriteClkPeriod : time := 50 ns;
constant ReadClkPeriod : time := 5 ns;
constant TotalValues : integer := 128; -- Total number of values to write/read
signal TestEnd : boolean := false;
type TestDataArray is array(0 to 15) of std_logic_vector(31 downto 0);
constant TestData : TestDataArray := (
x"FAAAAAAA", x"BBBBBBBB", x"CCCCCCCC", x"DDDDDDDD",
x"EEEEEEEE", x"FFFFFFFF", x"11111111", x"22222222",
x"33333333", x"44444444", x"55555555", x"66666666",
x"77777777", x"88888888", x"99999999", x"AAAAAAAF"
);
begin
AsyncFIFO_inst : entity work.AsyncFIFO
generic map(
G_Width => 32,
G_Depth => 4,
G_RamTypeFifo => "Block"
)
port map
(
I_Write_CLK => WriteCLK,
I_Write_CE => WriteCE,
I_Write_Data => DataIn,
I_Write_Valid => WriteValid,
O_Write_Ready => WriteReady,
I_Read_CLK => ReadCLK,
I_Read_CE => ReadCE,
O_Read_Data => DataOut,
I_Read_Ready => ReadReady,
O_Read_Valid => ReadValid
);
WriteClkProcess : process
begin
while TestEnd /= true loop
WriteCLK <= '0';
wait for WriteClkPeriod/2;
WriteCLK <= '1';
wait for WriteClkPeriod/2;
end loop;
end process;
ReadClkProcess : process
begin
while TestEnd /= true loop
ReadCLK <= '0';
wait for ReadClkPeriod/2;
ReadCLK <= '1';
wait for ReadClkPeriod/2;
end loop;
end process;
WriteProcess : process
variable writeCount : integer := 0; -- Variable f�r die Anzahl der geschriebenen Werte
begin
WriteRST <= '1';
wait for WriteClkPeriod * 2;
WriteRST <= '0';
WriteCE <= '1';
while writeCount < TotalValues loop
wait until rising_edge(WriteCLK);
if WriteReady = '1' then
DataIn <= TestData(writeCount mod 16) after WriteClkPeriod/2;
WriteValid <= '1' after WriteClkPeriod/2;
wait until rising_edge(WriteCLK);
WriteValid <= '0' after WriteClkPeriod/2;
writeCount := writeCount + 1; -- Nur erh�hen, wenn tats�chlich geschrieben wurde
end if;
end loop;
wait;
end process WriteProcess;
ReadProcess : process
variable readCount : integer := 0; -- Variable f�r die Anzahl der gelesenen Werte
begin
ReadRST <= '1';
wait for ReadClkPeriod * 2;
ReadRST <= '0';
ReadCE <= '1';
while readCount < TotalValues loop
wait until rising_edge(ReadCLK);
if ReadValid = '1' then
ReadReady <= '1' after ReadClkPeriod/2;
wait until rising_edge(ReadCLK);
ReadReady <= '0' after ReadClkPeriod/2;
wait until rising_edge(ReadCLK); -- Warten auf Datenstabilisierung
-- Assert to check the data correctness
if DataOut = TestData(readCount mod 16) then
report "Data Match!"
severity note;
else
assert FALSE
report "Data Mismatch. Expected " & integer'image(to_integer(unsigned(TestData(readCount mod 16)))) &
" but got " & integer'image(to_integer(unsigned(DataOut)))
severity error;
end if;
readCount := readCount + 1; -- Nur erh�hen, wenn tats�chlich gelesen wurde
end if;
end loop;
-- Simulation beenden
TestEnd <= true;
wait;
end process ReadProcess;
end architecture behavior;