-- $Id: sync_fifo.vhd,v 1.19 2006/11/02 19:13:41 tres Exp $ 
-------------------------------------------------------------------------------
-- LPM style synch fifo using blockram instead of registers
-- For Quartus, Leo, ISE infer dp_ram and counters.
-- Standard Libraries, generic address and data size
-- M. Treseler

-- FIFO sync inputs PUSH, POP activated by '0', '1' sequence. No
-- bursting on this version. Could add a burst input to select level
-- or edge triggering.  Data out is held during pushes and only
-- updated after a pop '0', '1' sequence. There is a one tick
-- delay from pop rising to valid data_q.

-- You cannot read and write the same location simultaneously with 
-- dual port ram. This is not a problem for a FIFO because when the
-- read (head) and write (tail) addresses are the same, the FIFO
-- is full or empty and access is blocked.

-------------------------------------------------------------------------
-- FIFO operation: read at head, write at tail
-------------------------------------------------------------------------
--        head 0123456789 tail      Output   Status
-- init        U               
--           rd^                      U      empty
--             ^--------wr
-- push 5:     01234                  U
--           rd^    ^---wr   
-- pop 2 :       234                  1
--           rd--^  ^---wr   
-- push 3:       234567               1
--           rd--^     ^wr
-- pop 5              7               6
--           rd-------^^wr
-- pop 1                              7      empty
--           rd--------^
--                     ^wr
-------------------------------------------------------------------------
-- WAVE SIM COMMAND:
-- vsim test_sync_fifo \
-- -do "add wave -radix hexadecimal \
-- -r sim:/test_sync_fifo/*;run -all"
-- TEXT SIM COMMAND:
-- vsim -c test_sync_fifo -do "run -all"
-------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity sync_fifo is
   generic (dat_length : natural := 16;
            add_length : natural := 8   -- 7 for 127x16  8 for 255x16
            );

   port (clk     : in  std_ulogic;
          rst    : in  std_ulogic;      -- powerup init of pointers, flags
          init   : in  std_ulogic;      -- synch   init of pointers, flags
                                        --  (but not data)
          push   : in  std_ulogic;      -- data_i latched on edge after push
                                        -- ok to push and pop at same time
          pop    : in  std_ulogic;      -- hold one tick, read data_q while low
          data_i : in  std_logic_vector(dat_length-1 downto 0);
          data_q : out std_logic_vector(dat_length-1 downto 0);  -- readback
          full   : out std_ulogic;
          empty  : out std_ulogic);

end sync_fifo;
-------------------------------------------------------------------------------
architecture synth of sync_fifo is
   
   constant mem_size : natural := 2**add_length;
   type     mem_type is array (mem_size-1 downto 0) of
      std_logic_vector(dat_length-1 downto 0);
   subtype  ptr_type is unsigned(add_length-1 downto 0);  -- unsigned wraps
   signal   mem           : mem_type;
   signal   we            : std_ulogic;  -- write signal to block ram   
   signal   pop_head_ptr  : ptr_type;   -- both _ptr must init same 
   signal   push_tail_ptr : ptr_type;   -- each counts up appropriate cycles
   signal   full_ctr      : ptr_type;   -- inits to FF,
                                        -- increment for write,
                                        -- decrement for read
   constant ptr_base      : ptr_type := (ptr_type'range => '0');
   signal   valid         : boolean;    -- 
begin 
-------------------------------------------------------------------------------
-- Leo Infers ram_dq_da_inclock_out_clock_16_8_256 from this
-- template.  Xilinx Virtex makes a RAM_B4_S16_S16 library XCV.
-- Altera uses altsyncram DPRAM_16_8_256_6_0 interface LPM library
-- OPERATORS.  If you add a reset to the ram_access process, you break
-- the template and get lots of registers instead of RAM.
-------------------------------------------------------------------------------
   ram_access : process (clk) is   
   begin
      if rising_edge(clk) then
         if we = '1' then
            mem(to_integer(push_tail_ptr)) <= (data_i); 
         end if;                                      -- raw address   
         data_q <= mem(to_integer(pop_head_ptr));
                                                      -- mem data after pop low
      end if;
   end process ram_access;
-------------------------------------------------------------------------------

   fifo : process (clk, rst) is
      variable pushed  : boolean;       -- pushed last time?
      variable popped  : boolean;       -- popped last time?
      variable full_v  : std_ulogic;    -- full flag
      variable empty_v : std_ulogic;    -- full flag      
-------------------------------------------------------------------------------
      procedure do_flags is
      begin
         empty <= empty_v;
         full  <= full_v;
      end procedure do_flags;
-------------------------------------------------------------------------------
      procedure init_fifo is
         -- purpose: Initialize fifo flags and pointers
      begin
         full_v        := '0';
         empty_v       := '1';
         pop_head_ptr  <= ptr_base-1;   -- both _ptr must init to same value
         push_tail_ptr <= ptr_base-1;   -- first address is FF++ = 0
         full_ctr      <= ptr_base;     -- must compare base +-1
         we            <= '0';
         pushed        := false;
         popped        := false;
         do_flags;
      end procedure init_fifo;
-------------------------------------------------------------------------------
      impure function push_is_ok
         return boolean is
      begin
         return push = '1' and full_v = '0' and not pushed;
      end function push_is_ok;
-------------------------------------------------------------------------------
      impure function pop_is_ok
         return boolean is
      begin
         return pop = '1' and empty_v = '0' and not popped;
      end function pop_is_ok;
-------------------------------------------------------------------------------      
   begin  -- process fifo
      read_write : if rst = '1' then    -- asynchronous reset (active high)
         init_fifo;
      elsif rising_edge(clk) then
         sync_init : if init = '1' then
            init_fifo;
         else
            -- we want to push/pop only on a '0' to '1' transition
            if pop = '0' then popped  := false; end if;
            if push = '0' then pushed := false; end if;
            -- Write and Read buffer, fullness unchanged, both ptr ++
            if
               push_is_ok and pop_is_ok
            then
               we            <= '1';
               pushed        := true;
               push_tail_ptr <= push_tail_ptr + 1;  -- write tail
               popped        := true;
               pop_head_ptr  <= pop_head_ptr + 1;   -- read head
               -- Just write to the tail (hold last reading)
               -- can't get empty but might get full 
            elsif push_is_ok
            then
               we            <= '1';
               pushed        := true;
               push_tail_ptr <= push_tail_ptr + 1;  -- grow tail
               full_ctr      <= full_ctr + 1;       -- getting fuller  
               empty_v       := '0';    -- just wrote, can't be empty
               if full_ctr = ptr_base-1 then  -- last count was -1              
                  full_v := '1';        -- this upcount will make zero
               end if;
               -- Just read from the head
               -- Can't get full, but might get empty 
            elsif
               pop_is_ok
            then
               we           <= '0';
               popped       := true;
               pop_head_ptr <= pop_head_ptr + 1;    -- bite head        
               full_ctr     <= full_ctr - 1;  -- getting emptier
               full_v       := '0';     -- just read, can't be full
               if full_ctr = ptr_base+1 then  -- last count was +1
                  empty_v := '1';       -- this downcount will make zero
               end if;
            else
               we <= '0';
            end if;
            do_flags;                   -- update full, empty
         end if sync_init;
      end if read_write;
   end process fifo;
end architecture synth;