From 57ad2aa3777c04167936e600912f5d99035ae4f7 Mon Sep 17 00:00:00 2001 From: MaxP Date: Wed, 16 Apr 2025 17:30:42 +0000 Subject: [PATCH] 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. --- src/AsyncFIFO.ucf | 0 src/AsyncFIFO.vhd | 224 +++++++++++++++++++++++++++++++++++++++++ tests/AsyncFIFO_tb.vhd | 120 ++++++++++++++++++++++ 3 files changed, 344 insertions(+) create mode 100644 src/AsyncFIFO.ucf create mode 100644 src/AsyncFIFO.vhd create mode 100644 tests/AsyncFIFO_tb.vhd diff --git a/src/AsyncFIFO.ucf b/src/AsyncFIFO.ucf new file mode 100644 index 0000000..e69de29 diff --git a/src/AsyncFIFO.vhd b/src/AsyncFIFO.vhd new file mode 100644 index 0000000..c43544b --- /dev/null +++ b/src/AsyncFIFO.vhd @@ -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; \ No newline at end of file diff --git a/tests/AsyncFIFO_tb.vhd b/tests/AsyncFIFO_tb.vhd new file mode 100644 index 0000000..bdbece7 --- /dev/null +++ b/tests/AsyncFIFO_tb.vhd @@ -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;