-------------------------------------------------------------
-- MSS copyright 2015-2016
--	Filename:  ELASTIC_BUFFER_NRAMB2.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 2
--	Date last modified: 1/15/16 
-- Inheritance: 	ELASTIC_BUFFER_NRAMB.VHD
--
-- description:  elastic buffer to ease flow-control timing requirements. 
-- Buffer depth and data width are parametric.
-- Input and output signals use the same synchronous clock. Cross-domain is not supported here.
-- usage: SYNC_RESET pulse is mandatory
-- 
-- design note: generic (not specific to Xilinx). Dual-port memory is inferred.
--
-- Rev 2 1/15/16 AZ
-- Corrected bug... SAMPLE_CLK_OUT_E <= SAMPLE_CLK_OUT_E_local (and SAMPLE_CLK_OUT_REQ DELETED);
-- (background: should work, but did not, like ELASTIC_BUFFER_NRAMB when receiving M&C over TCP)
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity ELASTIC_BUFFER_NRAMB2 is
	generic (
		DATA_WIDTH: integer := 8;
			-- data width in bits
		ADDR_WIDTH: integer;
			-- buffer depth, expressed in number of address bits. 
			-- For example, if ADDR_WIDTH=10 and DATA_WIDTH=8, buffer size is 10KB.

		READ_AHEAD_1ADDRESS: std_logic := '0'
			-- Some applications need the read pointer to be pre-positioned one address ahead so as to alleviate the 
			-- 1 CLK read delay. However, when the buffer is empty, the memory location sent to the output is meaningless.
	);
    port ( 
		--GLOBAL CLOCKS, RESET
		CLK : in std_logic;				-- synchronous clock
		SYNC_RESET: in std_logic;	-- MANDATORY!

		--// Input data stream
		DATA_IN: in std_logic_vector((DATA_WIDTH-1) downto 0);
			-- Read at rising edge of CLK when SAMPLE_CLK_IN  = '1'.
		SAMPLE_CLK_IN: in std_logic;
			-- one CLK wide pulse.
			-- Maximum rate: one SAMPLE_CLK_IN per CLK.
		SAMPLE_CLK_IN_REQ: out std_logic;
			-- requests data bits from the module upstream when RAMB has at least 64 bytes free
			-- = Clear To Send CTS

		--// Output data stream
		DATA_OUT: out std_logic_vector((DATA_WIDTH-1) downto 0);
			-- Read at rising edge of CLK when SAMPLE_CLK_OUT  = '1'.
		SAMPLE_CLK_OUT: out std_logic;
			-- one CLK-wide pulse
		SAMPLE_CLK_OUT_E: out std_logic;
			-- 1 CLK earlier sample clk (if needed)
		SAMPLE_CLK_OUT_REQ: in std_logic;
			-- module downstream requests data.
			-- = Clear To Send CTS
			-- Latency between request and SAMPLE_CLK_OUT is one CLK IF data is available.
		BUFFER_EMPTY: out std_logic;
			-- '1' when buffer is empty, '0' otherwise
		USED: out std_logic_vector((ADDR_WIDTH-1) downto 0)
			-- number of buffer cells used (0 for empty)

			);
end entity;

architecture behavioral of ELASTIC_BUFFER_NRAMB2 is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT BRAM_DP2
	 GENERIC(
		DATA_WIDTHA: integer;
		ADDR_WIDTHA: integer;
		DATA_WIDTHB: integer;
		ADDR_WIDTHB: integer
	 );
	 PORT(
		CSA : IN std_logic;
		CLKA   : in  std_logic;
		WEA    : in  std_logic;
		OEA : IN std_logic;
		ADDRA  : in  std_logic_vector(ADDR_WIDTHA-1 downto 0);
		DIA   : in  std_logic_vector(DATA_WIDTHA-1 downto 0);
		DOA  : out std_logic_vector(DATA_WIDTHA-1 downto 0);
		CSB : IN std_logic;
		CLKB   : in  std_logic;
		WEB    : in  std_logic;
		OEB : IN std_logic;
		ADDRB  : in  std_logic_vector(ADDR_WIDTHB-1 downto 0);
		DIB   : in  std_logic_vector(DATA_WIDTHB-1 downto 0);
		DOB  : out std_logic_vector(DATA_WIDTHB-1 downto 0)
		  );
	 END COMPONENT;
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
-- Suffix _D indicates a one CLK delayed version of the net with the same name
-- Suffix _X indicates an extended precision version of the net with the same name
-- Suffix _N indicates an inverted version of the net with the same name

signal RESET_UNTIL_1STSAMPLEIN_N: std_logic := '0';
signal WPTR: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal RPTR: unsigned((ADDR_WIDTH-1) downto 0) := (others => '1');
signal RPTR2: unsigned((ADDR_WIDTH-1) downto 0) := (others => '1');
signal BUF_SIZE: unsigned((ADDR_WIDTH-1) downto 0) := (others => '0');
signal AVAILABLE_BUF_SPACE:  unsigned((ADDR_WIDTH-1) downto 0) := (others => '1');
signal SAMPLE_CLK_OUT_E_local: std_logic := '0';
signal SAMPLE_CLK_OUT_REQ_D: std_logic := '0';
signal SAMPLE_CLK_OUT_local: std_logic := '0';
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin

-- write pointer management.
WPTR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			RESET_UNTIL_1STSAMPLEIN_N <= '0';
			WPTR <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') then
			WPTR <= (WPTR + 1);
			RESET_UNTIL_1STSAMPLEIN_N <= '1';
		end if;
	end if;
end process;

-- Port A is write-only, Port B is read-only
BRAM_DP2_001: BRAM_DP2 
GENERIC MAP(
    DATA_WIDTHA => DATA_WIDTH,
    ADDR_WIDTHA => ADDR_WIDTH,	
    DATA_WIDTHB => DATA_WIDTH,
    ADDR_WIDTHB => ADDR_WIDTH
)
PORT MAP(
    CSA => '1',
    CLKA => CLK,
    WEA => SAMPLE_CLK_IN,
	 OEA => '0',
    ADDRA => std_logic_vector(WPTR),
    DIA => DATA_IN,
    DOA => open,
    CSB => '1',
    CLKB => CLK,
    WEB => '0',
	 OEB => '1',
    ADDRB => std_logic_vector(RPTR2),
    DIB => (others => '0'),
    DOB => DATA_OUT
);

-- read ahead one address?
RPTR2 <= (RPTR + 1) when (READ_AHEAD_1ADDRESS = '1') else RPTR;

BUF_SIZE <= (WPTR + not(RPTR));
USED <= std_logic_vector(BUF_SIZE);	-- report used space
AVAILABLE_BUF_SPACE <= not BUF_SIZE;
BUFFER_EMPTY <= '1' when (BUF_SIZE = 0) else '0';

-- ask data source for more data when RAMB has at least 1/4 bytes free)
TX_CTS_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			SAMPLE_CLK_IN_REQ <= '0';
		elsif (AVAILABLE_BUF_SPACE(AVAILABLE_BUF_SPACE'left downto (AVAILABLE_BUF_SPACE'left-1)) /= 0)  then
			SAMPLE_CLK_IN_REQ <= '1';
		else
			SAMPLE_CLK_IN_REQ <= '0';
		end if;
	end if;
end process;

-- read pointer management
RPTR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			RPTR <= (others => '1');
			SAMPLE_CLK_OUT_E_local <= '0';
		else
			SAMPLE_CLK_OUT_local <= SAMPLE_CLK_OUT_E_local;
			SAMPLE_CLK_OUT_REQ_D <= SAMPLE_CLK_OUT_REQ;
			
			if(RESET_UNTIL_1STSAMPLEIN_N = '0') then
				-- initialize read pointer at power up until first input sample (i.e. don't wait for ASYNC_RESET which could be delayed,
				-- resulting in a spurious burst of zero output samples)
				RPTR <= (others => '1');
			elsif(BUF_SIZE /= 0) and (SAMPLE_CLK_OUT_REQ = '1') then
				-- two conditions must be met to read data from the RAMB:
				-- there is at least one byte in the RAMB and
				-- destination requests more bits 
				RPTR <= (RPTR + 1);
				SAMPLE_CLK_OUT_E_local <= '1';
			else
				-- nothing to read
				SAMPLE_CLK_OUT_E_local <= '0';
			end if;
		end if;
	end if;
end process;

SAMPLE_CLK_OUT <= SAMPLE_CLK_OUT_local;
--SAMPLE_CLK_OUT_E <= SAMPLE_CLK_OUT_E_local and SAMPLE_CLK_OUT_REQ;  -- corrected 1/15/16
SAMPLE_CLK_OUT_E <= SAMPLE_CLK_OUT_E_local;
end architecture;
