----------------------------------------------
-- MSS copyright 2011-2016
-- Filename: CROSS_CLK_DOMAINS_NODATALOSS.VHD
-- Authors: 	Alain Zarembowitch / MSS
-- Edit date: 12/9/16
-- Revision: 5
-- Inheritance: n/a
--
-- Description: Elastic buffer, 8-bit wide, crosses clock domains. No data loss.
-- See also CROSS_CLK_DOMAINS.vhd when resampling waveforms across clock domains (with possible loss of samples).
--
-- Rev 2 5/19/12 AZ
-- Connected SAMPLE_CLK_IN_REQ (previously missing)
--
-- Rev 3 12/3/12 AZ
-- Encapsulate Xilinx dual-port RAM primitive into generic VHDL component for portability.
-- Reclock SAMPLE_CLK_IN_REQ with CLK_A instead of CLK_B
--
-- Rev 4 12/19/13 AZ
-- Added a low-latency mode, available when the output clock is faster than the input clock.
--
-- Rev 5 12/9/16 AZ
-- Added parametric data width (DATA_WIDTH)
-- Switched to inferred memory BRAM_DP2
----------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
Library UNISIM;
use UNISIM.vcomponents.all;

entity CROSS_CLK_DOMAINS_NODATALOSS is
	Generic (
		DATA_WIDTH: integer := 8;
			-- data width
		ADDR_WIDTH: integer := 7;
			-- memory depth (2^ADDR_WIDTH)
		LOW_LATENCY: std_logic := '1'	
		-- when output clock is faster than input clock, one can achieve lower latency going through this component
	);
    port ( 

	--// GLOBAL CLOCKS
   CLK_A : in std_logic;				-- 'from' clock domain
   CLK_B : in std_logic;				-- 'to' clock domain
	ASYNC_RESET: in std_logic;

	--// INPUTS 
	-- Synchronous with CLK_A
	DATA_IN: in std_logic_vector(DATA_WIDTH-1 downto 0);
	SAMPLE_CLK_IN: in std_logic;
		-- read DATA_IN at the rising_edge of CLK_A when SAMPLE_CLK_IN = '1'
	SOF_IN: in std_logic;
	EOF_IN: in std_logic;
		-- optional start/end of frame indication. aligned with SAMPLE_CLK_IN
	SAMPLE_CLK_IN_REQ: out std_logic := '0';
		-- flow control
		
	--// OUTPUTS 
	-- synchronous with CLK_B
	DATA_OUT: out std_logic_vector(DATA_WIDTH-1 downto 0);
	SAMPLE_CLK_OUT: out std_logic;
		-- read DATA_OUT at the rising_edge of CLK_B when SAMPLE_CLK_OUT = '1'
	SOF_OUT: out std_logic;
	EOF_OUT: out std_logic;
		-- optional start/end of frame indication. aligned with SAMPLE_CLK_OUT
	SAMPLE_CLK_OUT_REQ: in std_logic
		-- flow control

	);
end entity;

architecture behavioral of CROSS_CLK_DOMAINS_NODATALOSS 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 _E indicates an extended precision version of the net with the same name
-- Suffix _N indicates an inverted version of the net with the same nameend Behavioral;
signal WPTR: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal WPTR_D: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal WPTR_D2: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal WPTR_D3: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal WPTR_STABLE: std_logic := '0';
signal WPTR_STABLE_D: std_logic := '0';
signal COUNTER8: std_logic_vector(2 downto 0) := "000";
signal COUNTER3: std_logic_vector(1 downto 0) := "00";
signal DIA: std_logic_vector(DATA_WIDTH+1 downto 0) := (others => '0');
signal DOB: std_logic_vector(DATA_WIDTH+1 downto 0) := (others => '0');
signal RPTR: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal BUF_SIZE: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal SAMPLE_CLK_OUT_E: std_logic := '0';
signal SAMPLE_CLK_OUT_local: std_logic := '0';
signal HALF_FULL: std_logic := '0';
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin

--//  ELASTIC BUFFER ----------------------------------------------------------
-- The purpose of the elastic buffer is two-fold:
-- (a) a transition between the CLK_A and CLK_B synchronous clocks
-- (b) temporary storage to absorb traffic peaks
-- The elastic buffer is 1K samples.

-- write pointer management
LOW_LATENCY0: if(LOW_LATENCY = '0') generate
	WPTR_001: process(ASYNC_RESET, CLK_A, HALF_FULL)
	begin
		if(ASYNC_RESET = '1') then
			WPTR <= (others => '0');
			WPTR_D <= (others => '0');
			WPTR_STABLE <= '0';
		elsif rising_edge(CLK_A) then
			COUNTER8 <= COUNTER8 + 1;
			SAMPLE_CLK_IN_REQ <= not HALF_FULL;	-- reclock to alleviate glitches
			
			if(SAMPLE_CLK_IN = '1') then
				WPTR <= WPTR + 1;
			end if;

			if(COUNTER8 = 7) then
				WPTR_D <= WPTR;
			end if;
			
			-- allow WPTR reclocking with another clock, as long as it is away from the transition area
			if(COUNTER8 < 6) then
				WPTR_STABLE <= '1';
			else 
				WPTR_STABLE <= '0';
			end if;
		end if;
	end process;
end generate;

LOW_LATENCY1: if(LOW_LATENCY = '1') generate
	WPTR_001: process(ASYNC_RESET, CLK_A, HALF_FULL)
	begin
		if(ASYNC_RESET = '1') then
			WPTR <= (others => '0');
			WPTR_D <= (others => '0');
			WPTR_STABLE <= '0';
			COUNTER3 <= "00";
		elsif rising_edge(CLK_A) then
			if(COUNTER3 = 2) then
				COUNTER3 <= "00";
			else
				COUNTER3 <= COUNTER3 + 1;
			end if;
			SAMPLE_CLK_IN_REQ <= not HALF_FULL;	-- reclock to alleviate glitches
			
			if(SAMPLE_CLK_IN = '1') then
				WPTR <= WPTR + 1;
			end if;

			if(COUNTER3 = 2) then
				WPTR_D <= WPTR;
			end if;
			
			-- allow WPTR reclocking with another clock, as long as it is away from the transition area
			if(COUNTER3 < 1) then
				WPTR_STABLE <= '1';
			else 
				WPTR_STABLE <= '0';
			end if;
		end if;
	end process;
end generate;


-- No need for initialization
DIA <= EOF_IN & SOF_IN & DATA_IN;
-- 16Kbit buffer(s) 
BRAM_DP2_001: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => DATA_WIDTH+2,		
	ADDR_WIDTHA => ADDR_WIDTH,
	DATA_WIDTHB => DATA_WIDTH+2,		 
	ADDR_WIDTHB => ADDR_WIDTH
)
PORT MAP(
	CSA => '1',
	CLKA => CLK_A,
	WEA => SAMPLE_CLK_IN,
	ADDRA => WPTR,	-- ADDR_WIDTH address
	DIA => DIA,
	OEA => '0',
	DOA => open,
	CSB => '1',
	CLKB => CLK_B,
	WEB => '0',
	ADDRB => RPTR,	-- ADDR_WIDTH address
	DIB => (others => '0'),
	OEB => '1',
	DOB => DOB
);



-- CLK_B zone. Reclock WPTR
WPTR_002: process(ASYNC_RESET, CLK_B)
begin
	if(ASYNC_RESET = '1') then
		WPTR_D2 <= (others => '0');
		WPTR_D3 <= (others => '0');
		WPTR_STABLE_D <= '0';
	elsif rising_edge(CLK_B) then
		WPTR_STABLE_D <= WPTR_STABLE;
		WPTR_D2 <= WPTR_D;
		
		if(WPTR_STABLE_D = '1') then
			-- WPTR is stable. OK to resample with the CLK_A clock.
			WPTR_D3 <= WPTR_D2;
		end if;
	end if;
end process;

BUF_SIZE <= WPTR_D3 + not(RPTR);
-- occupied tx buffer size

-- ask for more input data if 1/2 space is available
-- reclock to alleviate glitches
HALF_FULL <= BUF_SIZE(BUF_SIZE'left);


-- manage read pointer
RPTR_001: process(ASYNC_RESET, CLK_B)
begin
	if(ASYNC_RESET = '1') then
		RPTR <= (others => '1');
	elsif rising_edge(CLK_B) then
		SAMPLE_CLK_OUT_local <= SAMPLE_CLK_OUT_E;  -- it takes one CLK to read data from the RAMB
	
		if(SAMPLE_CLK_OUT_REQ = '1') and (BUF_SIZE /= 0) then
			-- user requests output data and the buffer is not empty
			RPTR <= RPTR + 1;
			SAMPLE_CLK_OUT_E <= '1';
		else
			SAMPLE_CLK_OUT_E <= '0';
		end if;
	end if;
end process;

-- output to user
DATA_OUT <= DOB(DATA_WIDTH-1 downto 0);
SOF_OUT <= DOB(DATA_WIDTH) and SAMPLE_CLK_OUT_local;
EOF_OUT <= DOB(DATA_WIDTH+1) and SAMPLE_CLK_OUT_local;
SAMPLE_CLK_OUT <= SAMPLE_CLK_OUT_local;


end behavioral;
