-------------------------------------------------------------
-- MSS copyright 2015-2018
--	Filename:  TCP_RXBUFNDEMUX2.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 3
--	Date last modified: 12/9/18 AZ
-- Inheritance: 	TCP_RXBUFNDEMUX.vhd rev4 1/31/14
--
-- description:  This component has two objectives:
-- (1) tentatively hold a received TCP frame on the fly until its validity is confirmed at the end of frame.
-- Discard if invalid or further process if valid.
-- (2) demultiplex multiple TCP streams, based on the destination port number
-- 1G version. Portable.
--
-- Because of the TCP protocol, data can only be validated at the end of a packet.
-- So the buffer management has to be able to backtrack, discard previous data and 
-- reposition pointer. 
--
-- The overall buffer size (which affects overall throughput) is user selected in the generic section.
-- This component is written of a single TCP stream.
--
-- This component is written for NTCPSTREAMS TCP tx streams. Adjust as needed in the com5402pkg package.
-- 
-- Note: This component should work in all application case, at the expense of many block RAM. 
-- Use the more efficient TCP_RXBUFNDEMUX only when the application is reading data faster than the data source
-- and when RAMBs are at a premium.

-- Rev 2 12/8/15 AZ
-- Replaced BRAM_DP.vhd with generic dual-port RAM BRAM_DP2.vhd
--
-- Rev 3 12/9/18 AZ
-- use numeric_std library. 
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use work.com5402pkg.all;	-- defines global types, number of TCP streams, etc

entity TCP_RXBUFNDEMUX2 is
	generic (
		NTCPSTREAMS: integer := 1;  
			-- number of concurrent TCP streams handled by this component
		ADDR_WIDTH: integer range 8 to 12:= 11
			-- size of the dual-port RAM buffers instantiated within for each stream = 8b * 2^ADDR_WIDTH
			-- Trade-off buffer depth and overall TCP throughput.
			-- Recommended value for 1GbE: at least 11 
	);
    Port ( 
		--// CLK, RESET
		SYNC_RESET: in std_logic;
		CLK: in std_logic;		-- synchronous clock	
			-- Must be global clocks. No BUFG instantiation within this component.

		--// TCP RX protocol -> RX BUFFER 
		RX_DATA: in std_logic_vector(7 downto 0);
			-- TCP payload data field when RX_DATA_VALID = '1'
		RX_DATA_VALID: in std_logic;
			-- delineates the TCP payload data field
		RX_SOF: in std_logic;
			-- 1st byte of RX_DATA
			-- Read ancillary information at this time:
			-- (a) destination RX_STREAM_NO (based on the destination TCP port)
		RX_TCP_STREAM_SEL: in std_logic_vector((NTCPSTREAMS-1) downto 0);
			-- stream number based on the destination TCP port
		RX_EOF: in std_logic;
			-- 1 CLK pulse indicating that RX_DATA is the last byte in the TCP data field.
			-- ALWAYS CHECK RX_DATA_VALID at the end of packet (RX_EOF = '1') to confirm
			-- that the TCP packet is valid. 
			-- Note: All packet information stored is tentative until
			-- the entire frame is confirmed (RX_EOF = '1') and (RX_DATA_VALID = '1').
			-- MSbs are dropped.
			-- If the frame is invalid, the data and ancillary information just received is discarded.
			-- Reason: we only knows about bad TCP packets at the end.
		RX_FREE_SPACE: out SLV16xNTCPSTREAMStype;
			-- buffer available space, expressed in bytes. 
			-- Beware of delay (as data may be in transit and information is slightly old).
		
		--// RX BUFFER -> APPLICATION INTERFACE
		-- NTCPSTREAMS can operate independently and concurrently. No scheduling arbitration needed here.
		RX_APP_DATA: out SLV8xNTCPSTREAMStype;
		RX_APP_DATA_VALID: out std_logic_vector((NTCPSTREAMS-1) downto 0);
		RX_APP_RTS: out std_logic_vector((NTCPSTREAMS-1) downto 0);
		RX_APP_CTS: in std_logic_vector((NTCPSTREAMS-1) downto 0);

		TP: out std_logic_vector(10 downto 1)

			);
end entity;

architecture Behavioral of TCP_RXBUFNDEMUX2 is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT BRAM_DP2
	GENERIC(
		DATA_WIDTHA: integer;
		ADDR_WIDTHA: integer;
		DATA_WIDTHB: integer;
		ADDR_WIDTHB: integer
	);
	PORT(
		CLKA   : in  std_logic;
		CSA: 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);
		CLKB   : in  std_logic;
		CSB: 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
--------------------------------------------------------
signal RESETn: std_logic := '0';
signal RESET_CNTR: unsigned(2 downto 0)  := (others => '0');

-- freeze ancilliary input data at the SOF
signal RX_TCP_STREAM_SEL_D: std_logic_vector(NTCPSTREAMS-1 downto 0) := (others => '0');

--//-- ELASTIC BUFFER ---------------------------
signal RX_DATA_D: std_logic_vector(7 downto 0) := (others => '0');
signal RX_DATA_VALID_D: std_logic := '0';
signal RX_SOF_D: std_logic := '0';
signal RX_EOF_D: std_logic := '0';
signal WPTR: unsigned(ADDR_WIDTH-1 downto 0) := (others => '0');
signal ADDRA: std_logic_vector(ADDR_WIDTH-1 downto 0) := (others => '0');
signal WEA: std_logic_vector(NTCPSTREAMS-1 downto 0) := (others => '0');
signal DIA: std_logic_vector(7 downto 0) := (others => '0');
type PTRtype is array (integer range 0 to (NTCPSTREAMS-1)) of unsigned(ADDR_WIDTH-1 downto 0);
signal WPTR0: PTRtype := (others => (others => '0'));
signal WPTR_CONFIRMED: PTRtype := (others => (others => '0'));
signal RPTR: PTRtype := (others => (others => '0'));
signal BUF_SIZE: PTRtype := (others => (others => '0'));
type DOBtype is array (integer range 0 to (NTCPSTREAMS-1)) of std_logic_vector(7 downto 0);
signal DOB: DOBtype;
signal DOB_VALID_E: std_logic_vector((NTCPSTREAMS-1) downto 0) := (others => '0');
signal DOB_VALID: std_logic_vector((NTCPSTREAMS-1) downto 0) := (others => '0');

--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin
-- create a local reset (because we want RPTR to be initialized to all '1's among other things
RESETN_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(RESET_CNTR(RESET_CNTR'left) = '0') then
			RESET_CNTR <= RESET_CNTR + 1;
		end if;
	end if;
end process;
RESETn <= RESET_CNTR(RESET_CNTR'left);

-- freeze ancilliary data at the SOF
FREEZE_INPUT: process(CLK) 
begin
	if rising_edge(CLK) then
		if(RX_SOF = '1') then
			RX_TCP_STREAM_SEL_D <= RX_TCP_STREAM_SEL;
		end if;
	end if;
end process;

--//-- ELASTIC BUFFER ---------------------------
-- write pointer management. 
-- Definition: next memory location to be written to.
WPTR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		RX_DATA_D <= RX_DATA;
		RX_DATA_VALID_D <= RX_DATA_VALID;
		RX_SOF_D <= RX_SOF;
		RX_EOF_D <= RX_EOF;

		if(SYNC_RESET = '1') then
			WPTR <= (others => '0');
		elsif(RX_SOF = '1') then
			-- for each received frame, position the write pointer as per the last confirmed pointer position
			for I in 0 to NTCPSTREAMS-1 loop
				if(RX_TCP_STREAM_SEL(I) = '1') then
					WPTR <= WPTR_CONFIRMED(I);
				end if;
			end loop;
		elsif(RX_DATA_VALID = '1') then
			WPTR <= WPTR + 1;
		end if;
		 
	end if;
end process;

-- confirmed valid write pointer
WPTR_GEN_002x: FOR I in 0 to (NTCPSTREAMS-1) generate
	WPTR_GEN_002: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				WPTR_CONFIRMED(I) <= (others => '0');
			elsif(RX_EOF_D = '1') and (RX_DATA_VALID_D = '1') then
				-- last frame confirmed valid. Remember the writer position (next location to write to)
				if(RX_TCP_STREAM_SEL_D(I) = '1') then
					WPTR_CONFIRMED(I) <= WPTR + 1;
				end if;
			end if;
		end if;
	end process;
end generate;

-- remember the wptr for each stream (we need it to compute free space)
WPTR_GEN_003x: FOR I in 0 to (NTCPSTREAMS-1) generate
	WPTR_GEN_002: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				WPTR0(I) <= (others => '0');
			elsif(RX_TCP_STREAM_SEL_D(I) = '1') and (RX_DATA_VALID_D = '1') then
				WPTR0(I) <= WPTR + 1;
			end if;
		end if;
	end process;
end generate;

-- select which RAMBlock to write to.
WEA_GENx: process(RX_TCP_STREAM_SEL_D, RX_DATA_VALID_D)
begin
    for I in 0 to (NTCPSTREAMS-1) loop
        if(RX_TCP_STREAM_SEL_D(I) = '1') then
				if(RX_DATA_VALID_D = '1') then
					-- Write byte 
					WEA(I) <= '1';
				else
					WEA(I) <= '0';
				end if;
        else
            WEA(I) <= '0';
        end if;
    end loop;
end process;

-- Latency 1 CLK from ADDRx to DOx
DIA <= RX_DATA_D;
ADDRA <= std_logic_vector(WPTR);
BRAM_DP2_X: for I in 0 to (NTCPSTREAMS-1) generate
	 
    BRAM_DP2_001: BRAM_DP2
    GENERIC MAP(
        DATA_WIDTHA => 8,		
        ADDR_WIDTHA => ADDR_WIDTH,
        DATA_WIDTHB => 8,		 
        ADDR_WIDTHB => ADDR_WIDTH

    )
    PORT MAP(
        CSA => '1',
        CLKA => CLK,
        WEA => WEA(I),      -- Port A Write Enable Input
		  OEA => '0',
        ADDRA => ADDRA,  -- Port A  Address Input
        DIA => DIA,      -- Port A Data Input
        DOA => open,
        CSB => '1',
        CLKB => CLK,
        WEB => '0',
		  OEB => '1',
        ADDRB => std_logic_vector(RPTR(I)(ADDR_WIDTH-1 downto 0)),  -- Port B Address Input
        DIB => (others => '0'),      -- Port B Data Input
        DOB => DOB(I)      -- Port B Data Output
    );
end generate;

-- How many bytes are waiting to be read? 
RX_BUFFER_SIZE_GENx: for I in 0 to (NTCPSTREAMS - 1) generate
	BUF_SIZE(I) <= WPTR_CONFIRMED(I) + (not RPTR(I));
	-- tell the application when data is available to read
	RX_APP_RTS(I) <= '0' when (BUF_SIZE(I) = 0) else '1';
end generate;

-- read pointer management
-- Rule #1: RPTR points to the next memory location to be read
RPTR_GENx: FOR I in 0 to (NTCPSTREAMS-1) generate
	RPTR_GEN_001: process(CLK)
	begin
		if rising_edge(CLK) then
			DOB_VALID(I) <= DOB_VALID_E(I);
			
			if(SYNC_RESET = '1') then
				RPTR(I) <= (others => '1');
			elsif(BUF_SIZE(I) /= 0) and (RX_APP_CTS(I) = '1') then
				RPTR(I) <= RPTR(I) + 1;
				DOB_VALID_E(I) <= '1';
			else
				DOB_VALID_E(I) <= '0';
			end if;
		end if;
	end process;
end generate;

-- mux
RECLOCK_OUTPUT_00X: FOR I in 0 to (NTCPSTREAMS-1) generate
	RECLOCK_OUTPUT_001: process(CLK)
		begin
			if rising_edge(CLK) then
				RX_APP_DATA(I) <= DOB(I);
				RX_APP_DATA_VALID(I) <= DOB_VALID(I);
				
				-- report the worst case available space to the TCP engine (including space currently occupied by invalid frames)
				RX_FREE_SPACE(I) <= std_logic_vector(resize(RPTR(I) - WPTR0(I),16));
			end if;
	end process;
end generate;

end Behavioral;
