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