VHDL coding tips and tricks: VHDL: Generic FIFO with testbench

Wednesday, March 10, 2010

VHDL: Generic FIFO with testbench

   CODE UPDATED AFTER DEBUGGING ON 9th Mar 2024!

FIFO's(first in first out) are generally used as a buffer for data transfer between two modules operating at different speeds. The maximum number of elements a FIFO can hold at a time is known as the depth of the FIFO. This must be set based on the difference in reading and writing rate of the FIFO. The larger the difference, the greater the FIFO depth must be.

This particular FIFO has two enable signals, enw for writing and enr for reading, respectively. They must be high to write to the FIFO or read from it. There are two output bits, fifo_full and fifo_empty, which act as status bits to indicate whether the FIFO is full or empty. Users are supposed to check these status signals before performing read or write operations on the FIFO.

The width of the FIFO entity shared here is adjustable via a generic parameter called DEPTH. The width of the data input or output cannot be changed via a generic parameter, but its quite easy to make the necessary changes if you have basic knowledge of VHDL.

fifo.vhd:


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity fifo is
generic (DEPTH : integer := 16);  --depth of fifo
port (clk : in std_logic;
      reset : in std_logic;
      enr : in std_logic;   --enable read,should be '0' when not in use.
      enw : in std_logic;    --enable write,should be '0' when not in use.
      data_in : in std_logic_vector (7 downto 0);     --input data
      data_out : out std_logic_vector(7 downto 0);    --output data
      fifo_empty : out std_logic;     --set as '1' when the queue is empty
      fifo_full : out std_logic     --set as '1' when the queue is full
     );
end fifo;

architecture Behavioral of fifo is

type memory_type is array (0 to DEPTH-1) of std_logic_vector(7 downto 0);
signal memory : memory_type :=(others => (others => '0'));   --memory for queue.
signal readptr,writeptr : integer := 0;  --read and write pointers.
signal empty,full : std_logic := '0';

begin

fifo_empty <= empty;
fifo_full <= full;

process(Clk,reset)
--this is the number of elements stored in fifo at a time.
--this variable is used to decide whether the fifo is empty or full.
variable num_elem : integer := 0;  
begin
if(rising_edge(Clk)) then
    if(reset = '1') then
        data_out <= (others => '0');
        empty <= '0';
        full <= '0';
        readptr <= 0;
        writeptr <= 0;
        num_elem := 0;
    else
        if(enr = '1' and empty = '0') then  --read
            data_out <= memory(readptr);
            readptr <= readptr + 1;      
            num_elem := num_elem-1;
        end if;
        if(enw ='1' and full = '0') then    --write
            memory(writeptr) <= data_in;
            writeptr <= writeptr + 1;  
            num_elem := num_elem+1;
        end if;
        --rolling over of the indices.
        if(readptr = DEPTH-1) then      --resetting read pointer.
            readptr <= 0;
        end if;
        if(writeptr = DEPTH-1) then        --resetting write pointer.
            writeptr <= 0;
        end if; 
        --setting empty and full flags.
        if(num_elem = 0) then
            empty <= '1';
        else
            empty <= '0';
        end if;
        if(num_elem = DEPTH) then
            full <= '1';
        else
            full <= '0';
        end if;
    end if;
end if; 
end process;

end Behavioral;

The program above demonstrates the working of a simple FIFO. In actual projects where its not mandatory to implement a FIFO from scratch, I recommend utilizing CoreGen software from Xilinx, which is capable of generating code for complex FIFOs with a lot of customizable properties. 

The following testbench tests the FIFO code shared above. Please be aware that the code may still contain bugs. If you encounter any issues, kindly leave a comment and I will look into it as soon as possible. 

fifo_tb.vhd:


LIBRARY ieee;
USE ieee.std_logic_1164.ALL;
USE ieee.std_logic_arith.ALL;

ENTITY fifo_tb IS
END fifo_tb;

ARCHITECTURE behavior OF fifo_tb IS 

   --Inputs and outputs
   signal Clk,reset,enr,enw,empty,full : std_logic := '0';
   signal data_in,data_out : std_logic_vector(7 downto 0) := (others => '0');
   --temporary signals
   signal i : integer := 0;
   -- Clock period definitions
   constant Clk_period : time := 10 ns;
   constant DEPTH : integer := 16;  --specify depth of fifo here.

BEGIN

   -- Instantiate the Unit Under Test (UUT)
   uut: entity work.fifo 
            generic map(DEPTH => DEPTH) 
            PORT MAP (clk => clk,
                    reset => reset,
                    enr => enr,
                    enw => enw,
                    data_in => data_in,
                    data_out => data_out,
                    fifo_empty => empty,
                    fifo_full => full);

   -- Clock process definitions
   Clk_process :process
   begin
      Clk <= '0';
      wait for Clk_period/2;
      Clk <= '1';
      wait for Clk_period/2;
   end process;

   -- Stimulus process
   stim_proc: process
   begin        
      reset <= '1';  --apply reset for one clock cycle.
      wait for clk_period;
      reset <= '0';
      wait for clk_period*3;  --wait for 3 clock periods(simply)
      enw <= '1';     enr <= '0';         --write 10 values to fifo.
      for i in 1 to 10 loop  
         data_in <= conv_std_logic_vector(i,8);
         wait for clk_period;
      end loop; 
      enw <= '0';     enr <= '1';         --read 4 values from fifo.
      wait for clk_period*4;
      enw <= '1';     enr <= '1';         --read and write at the same time for 2 clock cycles
      wait for clk_period*2;
      enw <= '0';     enr <= '0'; 			 --neither read nor write
      wait for clk_period*10;  --wait for some clock cycles.
      enw <= '1';     enr <= '0';         --write 10 values to fifo.
      for i in 11 to 20 loop  
         data_in <= conv_std_logic_vector(i,8);
         wait for clk_period;
      end loop; 
      enw <= '0';     enr <= '0'; 			 --neither read nor write
      wait for clk_period*10;  --wait for some clock cycles.
      enw <= '0';     enr <= '1';         --read 4 values from fifo.
      wait for clk_period*4;
      enw <= '0';     enr <= '0';      --neither read nor write
      wait for clk_period;
      enw <= '0';     enr <= '1';         --read 4 values from fifo.
      wait for clk_period*8;
      enw <= '0';     enr <= '0'; 		 --neither read nor write
      wait for clk_period;
      enw <= '0';     enr <= '1';         --read 8 values from fifo.
      wait for clk_period*4;
      enw <= '0';     enr <= '0';         --neither read nor write
      wait for clk_period;
      enw <= '0';     enr <= '1';         --read 4 values from fifo.
      wait for clk_period*4;
      enw <= '0';     enr <= '0';          --neither read nor write
      wait;
   end process;

END;

Simulation Waveform:

The following waveform was obtained from Xilinx Vivado 2023.2.

simulation waveform of fifo.vhd using xilinx vivado software


The design was tested successfully using Xilinx ISE 14.6. Synthesizing the design for Virtex 6 fpga, showed a maximum clock frequency of 250 MHz.  

14 comments:

  1. --these are evil:
    use IEEE.STD_LOGIC_ARITH.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;

    --use this instead:
    use ieee.numeric_std.all;

    ReplyDelete
  2. @anonymous : please see this post,
    http://vhdlguru.blogspot.com/2010/03/why-library-numericstd-is-preferred.html

    ReplyDelete
  3. library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_ARITH.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;

    entity fifo is
    GENERIC
    (
    ADDRESS_WIDTH : integer:=8;---8 bit
    DATA_WIDTH : integer:=32 ---32 bit
    );

    port ( clk : in std_logic;
    reset : in std_logic;
    enr : in std_logic; --enable read,should be '0' when not in use.
    enw : in std_logic; --enable write,should be '0' when not in use.
    dataout : out std_logic_vector(DATA_WIDTH-1 downto 0); --output data
    datain : in std_logic_vector (DATA_WIDTH-1 downto 0); --input data
    empty : out std_logic; --set as '1' when the queue is empty
    err : out std_logic;
    full : out std_logic --set as '1' when the queue is full
    );
    end fifo;

    architecture Behavioral of fifo is

    type memory_type is array (0 to ((2**ADDRESS_WIDTH)-1)) of std_logic_vector(DATA_WIDTH-1 downto 0);


    -----distributed-------
    signal memory : memory_type ;-- :=(others => (others => '0')); --memory for queue.-----
    signal readptr,writeptr : std_logic_vector(ADDRESS_WIDTH-1 downto 0); --read and write pointers.
    signal full0 : std_logic;
    signal empty0 : std_logic;

    begin
    full <= full0;
    empty <= empty0;

    fifo0: process(clk,reset)
    begin
    if reset='1' then

    readptr <= (others => '0');
    writeptr <= (others => '0');
    empty0 <='1';
    full0<='0';
    err<='0';


    elsif rising_edge(clk) then

    if (writeptr + '1' = readptr) then
    full0<='1';
    else
    full0<='0';
    end if ;

    if (readptr = writeptr ) then
    empty0<='1';
    else
    empty0<='0';
    end if ;

    if (empty0='0' and enr='1') or (full0='0' and enw='1') then
    err<='1';
    end if ;

    if enw='1' and full0='0' then
    memory (conv_integer(writeptr)) <= datain ;
    writeptr <= writeptr + '1' ;
    end if ;

    if enr='1' and empty0='0' then
    dataout <= memory (conv_integer(readptr));
    readptr <= readptr + '1' ;
    end if ;

    end if;

    end process;
    end Behavioral;




    fix fifo doal port ram

    ReplyDelete
  4. it is ram block and not distributed in my comment

    ReplyDelete
  5. This example is wrong, please correct or remove it; many people have copied it from your website and are wasting a lot of time wondering why it does not work.

    One of the reasons why it is wrong: to generate the "empty" and "full" flags, signals "readptr" and "writeptr" should be compared with one another, and NOT with absolute values. Cohen's example above is on the right direction.

    To be fair, the presented code is a FIFO in the sense that the first value in is the first value out, but it requires a reset every 256 elements. This is *not* what people want, in 99.99% of the cases.

    ReplyDelete
  6. Agree with Jasinski. The FIFO full/empty conditions should be more like the following: (note that in this example, the pointers are definted as integers by the ieee.numeric.std)

    if(writeptr = readptr) then
    FIFO_EMPTY <='1';
    else
    FIFO_EMPTY <='0';
    end if;

    if(writeptr = (readptr - 1)) then
    FIFO_FULL <='1';
    else
    FIFO_FULL <='0';
    end if;

    The empty condition occurs when you read the last value written, hence comparing them to each other. Full occurs when you write so many values that you fill up to one less than the last value read.

    ReplyDelete
  7. roei cohen variant is mostly correct except one thing: active "full" value will be sensed by wrighting block on 1 clk later, so it will try to write data to FIFO when it's already restricted.

    ReplyDelete
  8. What is the use of signal error here ?

    ReplyDelete
  9. I mean why error <= '0'; is required ?

    ReplyDelete
  10. I think in stead of error <=0; ,enr<=0; will be there;

    ReplyDelete
  11. hi I need a READY QUEUE with priority any help?????????

    ReplyDelete
  12. --rolling over of the indices !!!!!!!! ERROR !!!!!!!!!
    resetting indices do not analyze enw and enr signals

    right code for readptr:

    if(enr = '1' and empty = '0') then --read
    data_out <= memory(readptr);
    if(readptr = depth-1) then
    readptr <= 0;
    else readptr <= readptr + 1;
    end if;
    num_elem := num_elem-1;
    end if;

    ReplyDelete
  13. your fifo code has bug. The pointers will roll over from depth-1 to 0 when both write n read are '0'

    ReplyDelete