209 lines
6.0 KiB
VHDL
209 lines
6.0 KiB
VHDL
--
|
|
-- 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
|
|
);
|
|
|
|
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;
|
|
|
|
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 => "block",
|
|
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;
|