-- Synchronous FIFO with a protocol similar to AXI -- -- The outputs are generated combinationally from the inputs -- in order to allow for back-to-back transfers with the type -- of flow control used by busses lite AXI, pipelined WB or -- LiteDRAM native port when the FIFO is full. -- -- That means that care needs to be taken by the user not to -- generate the inputs combinationally from the outputs otherwise -- it would create a logic loop. -- -- If breaking that loop is required, a stash buffer could be -- added to break the flow control "loop" between the read and -- the write port. -- library ieee; use ieee.std_logic_1164.all; library work; use work.utils.all; entity sync_fifo is generic( -- Fifo depth in entries DEPTH : natural := 64; -- Fifo width in bits WIDTH : natural := 32; -- When INIT_ZERO is set, the memory is pre-initialized to 0's INIT_ZERO : boolean := false ); port( -- Control lines: clk : in std_ulogic; reset : in std_ulogic; -- Write port wr_ready : out std_ulogic; wr_valid : in std_ulogic; wr_data : in std_ulogic_vector(WIDTH - 1 downto 0); -- Read port rd_ready : in std_ulogic; rd_valid : out std_ulogic; rd_data : out std_ulogic_vector(WIDTH - 1 downto 0) ); end entity sync_fifo; architecture behaviour of sync_fifo is subtype data_t is std_ulogic_vector(WIDTH - 1 downto 0); type memory_t is array(0 to DEPTH - 1) of data_t; function init_mem return memory_t is variable m : memory_t; begin if INIT_ZERO then for i in 0 to DEPTH - 1 loop m(i) := (others => '0'); end loop; end if; return m; end function; signal memory : memory_t := init_mem; subtype index_t is integer range 0 to DEPTH - 1; signal rd_idx : index_t; signal rd_next : index_t; signal wr_idx : index_t; signal wr_next : index_t; function next_index(idx : index_t) return index_t is variable r : index_t; begin if ispow2(DEPTH) then r := (idx + 1) mod DEPTH; else r := idx + 1; if r = DEPTH then r := 0; end if; end if; return r; end function; type op_t is (OP_POP, OP_PUSH); signal op_prev : op_t := OP_POP; signal op_next : op_t; signal full, empty : std_ulogic; signal push, pop : std_ulogic; begin -- Current state at last clock edge empty <= '1' when rd_idx = wr_idx and op_prev = OP_POP else '0'; full <= '1' when rd_idx = wr_idx and op_prev = OP_PUSH else '0'; -- We can accept new data if we aren't full or we are but -- the read port is going to accept data this cycle wr_ready <= rd_ready or not full; -- We can provide data if we aren't empty or we are but -- the write port is going to provide data this cycle rd_valid <= wr_valid or not empty; -- Internal control signals push <= wr_ready and wr_valid; pop <= rd_ready and rd_valid; -- Next state rd_next <= next_index(rd_idx) when pop = '1' else rd_idx; wr_next <= next_index(wr_idx) when push = '1' else wr_idx; with push & pop select op_next <= OP_PUSH when "10", OP_POP when "01", op_prev when others; -- Read port output rd_data <= memory(rd_idx) when empty = '0' else wr_data; -- Read counter reader: process(clk) begin if rising_edge(clk) then if reset = '1' then rd_idx <= 0; else rd_idx <= rd_next; end if; end if; end process; -- Write counter and memory write producer: process(clk) begin if rising_edge(clk) then if reset = '1' then wr_idx <= 0; else wr_idx <= wr_next; if push = '1' then memory(wr_idx) <= wr_data; end if; end if; end if; end process; -- Previous op latch used for generating empty/full op: process(clk) begin if rising_edge(clk) then if reset = '1' then op_prev <= OP_POP; else op_prev <= op_next; end if; end if; end process; end architecture behaviour;