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:
0
src/AsyncFIFO.ucf
Normal file
0
src/AsyncFIFO.ucf
Normal file
224
src/AsyncFIFO.vhd
Normal file
224
src/AsyncFIFO.vhd
Normal 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
120
tests/AsyncFIFO_tb.vhd
Normal 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;
|
Reference in New Issue
Block a user