-- -- Simple FIFO with single clock. -- -- Joris van Rantwijk 2024 -- library ieee; use ieee.std_logic_1164.all; use ieee.numeric_std.all; library xpm; use xpm.vcomponents.all; use work.puzzlefw_pkg.all; entity simple_fifo is generic ( -- Word width. data_width: integer range 1 to 1024; -- Size of FIFO as 2-log of the number of words. fifo_depth_bits: integer range 2 to 16; -- True to use block RAM, False to let the synthesizer choose. block_ram: boolean ); port ( -- Main clock, active on rising edge. clk: in std_logic; -- Reset, active high, synchronous to main clock. -- After reset, the FIFO is empty. reset: in std_logic; -- Data input stream. in_valid: in std_logic; in_ready: out std_logic; in_data: in std_logic_vector(data_width - 1 downto 0); -- Data output stream. out_valid: out std_logic; out_ready: in std_logic; out_data: out std_logic_vector(data_width - 1 downto 0); -- Number of words currently in the FIFO. -- This excludes the word currently expressed on "out_ready" when "out_valid" is high. queue_length: out unsigned(fifo_depth_bits - 1 downto 0) ); end entity; architecture arch of simple_fifo is type regs_type is record in_ready: std_logic; out_valid: std_logic; nonempty: std_logic; waddr: unsigned(fifo_depth_bits - 1 downto 0); raddr: unsigned(fifo_depth_bits - 1 downto 0); qlen: unsigned(fifo_depth_bits - 1 downto 0); end record; constant regs_init: regs_type := ( in_ready => '0', out_valid => '0', nonempty => '0', waddr => (others => '0'), raddr => (others => '0'), qlen => (others => '0') ); signal r: regs_type := regs_init; signal rnext: regs_type; signal s_ram_wen: std_logic; signal s_ram_ren: std_logic; -- Determine the value of the MEMORY_PRIMITIVE attribute. function choose_memory_primitive return string is begin if block_ram then return "block"; else return "auto"; end if; end function; begin -- -- Xilinx Simple Dual Port RAM. -- ram_inst: xpm_memory_sdpram generic map ( ADDR_WIDTH_A => fifo_depth_bits, ADDR_WIDTH_B => fifo_depth_bits, AUTO_SLEEP_TIME => 0, BYTE_WRITE_WIDTH_A => data_width, CASCADE_HEIGHT => 0, CLOCKING_MODE => "common_clock", ECC_MODE => "no_ecc", MEMORY_INIT_FILE => "none", MEMORY_INIT_PARAM => "0", MEMORY_OPTIMIZATION => "true", MEMORY_PRIMITIVE => choose_memory_primitive, MEMORY_SIZE => data_width * 2**fifo_depth_bits, MESSAGE_CONTROL => 0, READ_DATA_WIDTH_B => data_width, READ_LATENCY_B => 1, READ_RESET_VALUE_B => "0", RST_MODE_A => "SYNC", RST_MODE_B => "SYNC", SIM_ASSERT_CHK => 0, USE_EMBEDDED_CONSTRAINT => 0, USE_MEM_INIT => 0, WAKEUP_TIME => "disable_sleep", WRITE_DATA_WIDTH_A => data_width, WRITE_MODE_B => "no_change" ) port map ( addra => std_logic_vector(r.waddr), addrb => std_logic_vector(r.raddr), clka => clk, clkb => clk, dbiterrb => open, dina => in_data, doutb => out_data, ena => s_ram_wen, enb => s_ram_ren, injectdbiterra => '0', injectsbiterra => '0', regceb => '1', rstb => reset, sbiterrb => open, sleep => '0', wea => "1" ); -- -- Drive output ports. -- in_ready <= r.in_ready; out_valid <= r.out_valid; queue_length <= r.qlen; -- -- Drive control signals to RAM. -- s_ram_wen <= in_valid and r.in_ready; s_ram_ren <= r.nonempty and (out_ready or (not r.out_valid)); -- -- Combinatorial process. -- process (all) is variable v: regs_type; begin -- Load current register values. v := r; -- Update write pointer on write. if (in_valid = '1') and (r.in_ready = '1') then v.waddr := r.waddr + 1; end if; -- Update input ready status. -- Note this uses the NEW value of the write pointer. if v.waddr + 1 = r.raddr then v.in_ready := '0'; else v.in_ready := '1'; end if; -- On read from RAM, update read pointer. if (r.nonempty = '1') and (out_ready = '1' or r.out_valid = '0') then v.raddr := r.raddr + 1; end if; -- Determine whether output word is valid in the next cycle. if r.nonempty = '1' then -- Keep output word or load a new word during this cycle. v.out_valid := '1'; elsif out_ready = '1' then -- Consume output word without refreshing it. v.out_valid := '0'; end if; -- Determine whether RAM will contain data on the next cycle. -- Note this uses the NEW values of the read and write pointers. if v.waddr = v.raddr then v.nonempty := '0'; else v.nonempty := '1'; end if; -- Calculate queue length. -- Note this uses the NEW values of "waddr" and "raddr". v.qlen := v.waddr - v.raddr; -- Synchronous reset. if reset = '1' then v := regs_init; end if; -- Drive new register values to synchronous process. rnext <= v; end process; -- -- Synchronous process. -- process (clk) is begin if rising_edge(clk) then r <= rnext; end if; end process; end architecture;