-------------------------------------------------------------
-- MSS copyright 2012
-- Filename:  DATA_SPLITTER.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: Rev 2
-- Last modified: 11/29/12 
-- Inheritance: 	N/A
--
-- description:  The data splitter chops the input data stream into overlapping blocks, for subsequent
-- parallel processing. Reference document: "A Block-Based Parallel Decoding Architecture for
-- Convolutional Codes", Chengyi Su, Yu Zhang, Changyong Pan, Xiaofeng Wan
-- State Key Laboratory on Microwave and Digital Communications
-- Tsinghua National Laboratory for Information Science and Technology
--
-- Each block includes three segments: a preamble, data, a tail of lengths L1,L2,L3 respectively.
-- The preamble is the end of the previous block data segment.
-- Likewise, the tail is the start of the next block data segment.
-- The lengths L1,L2 and L3 are defined as follows:
-- L1 is chosen to give enough time for the Viterbi algorithm to converge at the beginning of a block
-- The preamble length L1 = 48 bits > 5 times the constraint length K
-- The tail length L3 = 48 bits > constraint length K
-- The data segment length L2 = 2016, large enough to minimize the L1,L3 overhead 
-- L1,L2,L3 are selected to be multiple of 48 to keep block alignment with encoding/puncturing pattern period
--
-- Rev2 11/29/12 AZ
-- passed parameters through generic for more flexibility instead of common com1509pkg
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
use work.com1509pkg2.all;	-- defines global types, key configuration parameters to be defined at synthesis time.

entity DATA_SPLITTER is
	generic(
		NDEC: integer := 1;
		N_PARITY_BITS_MAX: integer := 3
	);
    Port ( 
		-- GLOBAL CLOCKS, RESET
		CLK: in std_logic;	-- synchronous clock
		SYNC_RESET: in std_logic;		-- synchronous reset. active high.
			-- MANDATORY to initialize internal variables
		
		-- 8 PARALLEL INPUT BITS
		-- All input samples are soft-quantized with 4 bits
		-- format:offset binary (0000 for strong '0', 1111 for strong '1')
		-- When using hard-decision decoding, input bits should be placed at indices 3+i*4.
		-- MSb is first (DATAIN(31 downto 28) is the first received soft-decision sample)
		DATAIN: in std_logic_vector(31 downto 0);
		DATAIN_VALID: in std_logic;
		DATAIN_READY: out std_logic;

		-- Skip one input bit for each pulse
		-- This feature is used to synchronize the input stream with the periodic encoding/puncturing pattern
		SKIP1BIT_PULSE: in std_logic;
		
		--// CONFIGURATION 
		-- The configuration parameters below can be changed dynamically at run-time.
		-- Use SYNC_RESET after such a configuration change.
		N_PARITY_BITS: in integer range 2 to 7;
			-- actual number of parity bits (i.e. generator polynomials).
			-- valid range: 2 - 7
			-- MUST be less than or equal to the hardware capabilities set by N_PARITY_BITS_MAX
		R1: in integer range 1 to 7;
		R2: in integer range 2 to 8;
			-- Convolutional encoding rate R1/R2
			-- Valid rates: 1/2, 1/3, 1/7, 2/3, 3/4, 5/6, 7/8
		STANDARD: in integer range 0 to 2;
			-- There are some minor variations among standards, such as different puncturing scheme or inversion
			-- 0 = Intelsat 
			-- 1 = CCSDS/DVB
			-- 2 = DVB rate 1/2 only
			-- others = yet TBD

		-- OUTPUT BITS
		-- groups of N_PARITY_BITS, 4-bit soft-quantized. One set for each parallel decoder.
		DATAOUT: out std_logic_vector((NDEC*N_PARITY_BITS_MAX*4 - 1) downto 0) := (others => '0'); 
			-- received (soft-quantized) bit, grouped in a N_PARITY_BITS group, x number of parallel decoders 
		ERASURES: out std_logic_vector((NDEC*N_PARITY_BITS_MAX - 1) downto 0) := (others => '0'); 
			-- erasures on individual bits of a parity group, for each parallel decoder ('1' indicates a puncture)	
			-- tells the BMU to ignore a specific bit or bits during distance computation.
		DATAOUT_VALID: out std_logic_vector(NDEC-1 downto 0)
			-- 1 CLK-wide pulse to tell each decoder whether a parity group + erasure information is available for BMU processing

		);
end entity;

architecture Behavioral of DATA_SPLITTER is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT BRAM_DP
	GENERIC(
		DATA_WIDTHA: integer;
		ADDR_WIDTHA: integer;
		DATA_WIDTHB: integer;
		ADDR_WIDTHB: integer
	);
	PORT(
	    CLKA   : in  std_logic;
	    WEA    : 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;
	    WEB    : 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 a one CLK early 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
-- Suffix G indicates a global clock
constant L1: integer := 48;	-- block preamble length (input bits)
constant L2: integer := 2016;	-- block data field length (input bits)
constant L3: integer := 48;	-- block tail length (input bits)

signal PUNCTURING_PATTERN: PUNCTURING2Dtype := (others => (others => '0'));
signal PUNCTURED_ENCODING_PERIOD: integer := 2;
signal N_PUNCTURING_PHASES: integer := 1;

--//-- WRITE TO DP RAM ------------------------------------------------------
signal INBIT_CNTR: std_logic_vector(11 downto 0) := (others => '0');	-- modulo 2048 counter
signal INBIT_CNTR2: std_logic_vector(3 downto 0) := (others => '0');	-- modulo PUNCTURED_ENCODING_PERIOD counter
signal WBUF_PTR: std_logic_vector(2 downto 0):= (others => '0');	-- modulo NDEC counter
signal WBUF_PTR_PREV: std_logic_vector(2 downto 0):= (others => '0');	-- modulo NDEC counter
signal WBUF_EN: std_logic_vector(NDEC-1 downto 0) := (0 => '1', others => '0');
signal WEA: std_logic_vector(NDEC-1 downto 0) := (0 => '1', others => '0');
type int7xNDECtype is array (integer range 0 to (NDEC-1)) of integer range 0 to 6;
type SLV4xNDECtype is array (integer range 0 to (NDEC-1)) of std_logic_vector(3 downto 0);
type SLV9xNDECtype is array (integer range 0 to (NDEC-1)) of std_logic_vector(8 downto 0);
type SLV12xNDECtype is array (integer range 0 to (NDEC-1)) of std_logic_vector(11 downto 0);
signal WPTR: SLV9xNDECtype := (others => (others => '0'));
signal BUF_SIZE: SLV12xNDECtype := (others => (others => '1'));
signal DATAIN_CTS: std_logic_vector(NDEC-1 downto 0) := (others => '1');
signal EVENT1: std_logic_vector(NDEC-1 downto 0) := (others => '0');	-- block write start pulse
signal EVENT2: std_logic_vector(NDEC-1 downto 0) := (others => '0');	-- block write end pulse

--//-- READ DP RAM ------------------------------------------------------
signal READ_STATE: std_logic_vector(NDEC-1 downto 0) := (others => '0');
signal PP_X: int7xNDECtype := (others => 0);	-- puncturing pattern X-axis: puncturing phase	0-6 = 0 - N_PUNCTURING_PHASES-1 
signal PP_Y: int7xNDECtype := (others => 0);	-- puncturing pattern Y-axis: parity bits = generator polynomial 0-6 = 0 - N_PARITY_BITS-1
signal PARITY_INDEX_E: int7xNDECtype := (others => 0);	-- maximum size is 7x7
signal PARITY_INDEX: int7xNDECtype := (others => 0);	-- maximum size is 7x7
signal SAMPLE1_CLK_E: std_logic_vector(NDEC-1 downto 0) := (others => '0');
signal SAMPLE1_CLK: std_logic_vector(NDEC-1 downto 0) := (others => '0');
signal ERASURE1_E: std_logic_vector(NDEC-1 downto 0) := (others => '0');
signal ERASURE1: std_logic_vector(NDEC-1 downto 0) := (others => '0');
signal RPTR: SLV12xNDECtype := (others => (others => '1'));
signal READ_BIT_CNTR: SLV12xNDECtype := (others => (others => '0'));
signal EVENT3: std_logic_vector(NDEC-1 downto 0) := (others => '0');	-- block write end pulse
signal DOB: SLV4xNDECtype := (others => (others => '0'));

--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin

-- define puncturing pattern (related to coding rate R1/R2)
-- Puncturing is used at the encoder to reduce the redundancy. 
-- It is described by a 2-dimensional array of maximum size 7(parity bits) x 7(puncturing phases)
PUNCTURING_PATTERN_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			PUNCTURING_PATTERN <= f_puncturing_pattern(R1,R2,STANDARD);
			PUNCTURED_ENCODING_PERIOD <= f_punctured_encoding_period(R1,R2);
		end if;
	end if;
end process;
N_PUNCTURING_PHASES <= R1;

-- segment input data into fixed-length and overlapping blocks.

-- keep track of the input bits alignment with the periodic encoding/puncturing pattern
	-- Rate 1/2, the encoding pattern is G0 G1 etc => pattern period 2 receive bits
	-- Rate 1/3, the encoding pattern is G0 G1 G2  etc => pattern period 3 receive bits
	-- Rate 1/7, the encoding pattern is G0 G1... G6  etc => pattern period 7 receive bits
	-- Rate 2/3, the encoding + puncturing pattern is G0 G1 G0 E  etc => pattern period 3 receive bits
	-- Rate 3/4, the encoding + puncturing pattern is G0 G1 G0 E E G1  etc => pattern period 4 receive bits
	-- Rate 5/6, the encoding + puncturing pattern is G0 G1 G0 E E G1 G0 E E G1 etc => pattern period 6 receive bits
	-- Rate 7/8, the encoding + puncturing pattern is G0 G1 G0 E GO E G0 E E G1 E G0 E G1  etc => pattern period 8 receive bits
	-- (E is for erasure, bit is not explicitely transmitted)
INBIT_CNTR2_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			INBIT_CNTR2 <= (others => '0');
		elsif(DATAIN_VALID = '1') then
			-- received 8 additional soft-quantized input bits
			case(PUNCTURED_ENCODING_PERIOD) is
				when 3 => 
					case(INBIT_CNTR2) is
						when "0000" => INBIT_CNTR2 <= "0010";
						when "0001" => INBIT_CNTR2 <= "0000";
						when others => INBIT_CNTR2 <= "0001";
					end case;
				when 6 => 
					case(INBIT_CNTR2) is
						when "0000" => INBIT_CNTR2 <= "0010";
						when "0001" => INBIT_CNTR2 <= "0011";
						when "0010" => INBIT_CNTR2 <= "0100";
						when "0011" => INBIT_CNTR2 <= "0101";
						when "0100" => INBIT_CNTR2 <= "0000";
						when others => INBIT_CNTR2 <= "0001";
					end case;
				when 7 => 
					case(INBIT_CNTR2) is
						when "0000" => INBIT_CNTR2 <= "0001";
						when "0001" => INBIT_CNTR2 <= "0010";
						when "0010" => INBIT_CNTR2 <= "0011";
						when "0011" => INBIT_CNTR2 <= "0100";
						when "0100" => INBIT_CNTR2 <= "0101";
						when "0101" => INBIT_CNTR2 <= "0110";
						when others => INBIT_CNTR2 <= "0000";
					end case;
				when others => null;	
					-- PUNCTURED_ENCODING_PERIOD = 2,4 or 8... 
					-- no change to INBIT_CNTR2 after adding 8 input bits modulo PUNCTURED_ENCODING_PERIOD
			end case;
		end if;
	end if;
end process;

-- keep track of the number of input bits in a block (preamble L1 + data L2, excluding tail L3), modulo 2064
-- The number 2064 was chosen being a multiple of 48.... always aligned with the periodic encoding/puncturing pattern
-- for all common PUNCTURED_ENCODING_PERIOD values (2,3,4,6,8)
INBIT_CNTR_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			INBIT_CNTR <= (others => '0');
		elsif(DATAIN_VALID = '1') then
			-- received 8 more soft-quantized input bits
			if(INBIT_CNTR = L1 + L2 -8) then
				INBIT_CNTR <= (others => '0');	-- modulo L1+L2
			else
				INBIT_CNTR <= INBIT_CNTR + 8;
			end if;
		end if;
	end if;
end process;

--//-- WRITE TO DP RAM ------------------------------------------------------
-- select which buffer(s) to write to. If NDEC>1, writing can occur simultaneously on 
-- 2 buffers because of the block overlaps.
-- Event1: INBIT_CNTR = 0, start of preamble, open the next buffer to write
-- Event2: INBIT_CNTR = 16, end of tail, close previous buffer to write
WR_BUF_SELECT_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			WBUF_PTR <= (others => '0');
			WBUF_PTR_PREV <= conv_std_logic_vector(NDEC-1,3);
		elsif(DATAIN_VALID = '1') and (INBIT_CNTR = L1 + L2 -8) then
			-- open next buffer for write, modulo NDEC
			WBUF_PTR_PREV <= WBUF_PTR;	-- remember last (not yet closed)
			if(WBUF_PTR = (NDEC-1)) then
				WBUF_PTR <= (others => '0');
			else
				WBUF_PTR <= WBUF_PTR + 1;
			end if;
		end if;
	end if;
end process;

-- enable write to one (or two during overlaping preamble and tail) input buffers
WR_BUF_EN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			WBUF_EN <= (0 => '1', others => '0');
		elsif(DATAIN_VALID = '1') and (INBIT_CNTR = L1 + L2 -8) then
			-- open next buffer for write, modulo NDEC. Start with preamble.
			if(WBUF_PTR = NDEC-1) then
				WBUF_EN(0) <= '1';
			else
				WBUF_EN(conv_integer(WBUF_PTR + 1)) <= '1';
			end if;
		elsif(DATAIN_VALID = '1') and (INBIT_CNTR = L3-8) and (NDEC /= 1) then
			-- close previous buffer to write after tail segment
			-- Special case: NDEC = 1 => one buffer, never close
			WBUF_EN(conv_integer(WBUF_PTR_PREV)) <= '0';
		end if;
	end if;
end process;

-- Instantiate one input elastic buffer per parallel decoder. 
BRAM_DP_001: for I in 0 to (NDEC -1) generate

	-- write enable, A-side of the elastic buffer
	WEA(I) <= '1' when (WBUF_EN(I) = '1') and (DATAIN_VALID = '1')  else '0';

	-- WPTR management
	WPTR_GEN_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				WPTR(I) <= (others => '0');
			elsif(WEA(I) = '1') then
				WPTR(I) <= WPTR(I) + 1;
			end if;
		end if;
	end process;
	
	-- Event1: block write start pulse
	EVENT1(I) <= '1' when (SYNC_RESET = '0') and (I = WBUF_PTR) and (WBUF_EN(I) = '1') 
							and (DATAIN_VALID = '1') and (INBIT_CNTR = 0) else '0';
	-- Event2: block write end pulse
	EVENT2(I) <= '1' when (SYNC_RESET = '0') and (I = WBUF_PTR_PREV) and (WBUF_EN(I) = '1') 
							and (DATAIN_VALID = '1') and (INBIT_CNTR = L3 - 8) else '0';


	-- 16Kbit buffer large enough for 2048 block * 4-bit soft-quantized samples
	Inst_BRAM_DP: BRAM_DP 
	GENERIC MAP(
		DATA_WIDTHA => 32,		-- A-side: 32*2^9 = 16Kbit buffer = 1 Xilinx block RAM
		ADDR_WIDTHA => 9,
		DATA_WIDTHB => 4,			-- B-side: 4*2^12 = 16Kbit buffer 
		ADDR_WIDTHB => 12

	)
	PORT MAP(
		CLKA => CLK,
		WEA => WEA(I),
		ADDRA => WPTR(I),	-- 9-bit address
		DIA => DATAIN,	-- 32-bit wide
		DOA => open,
		CLKB => CLK,
		WEB => '0',
		ADDRB => RPTR(I),	-- 12-bit address
		DIB => x"0",
		DOB => DOB(I)
	);


	-- how much data in buffer?
	BUF_SIZE(I) <= (WPTR(I) & "000") + not RPTR(I);

	-- clear to send? Ask for more data if at least 1/4 of buffer is free
	DATAIN_CTS(I) <= '0' when (BUF_SIZE(I)(11 downto 10) = "11") else '1';
end generate;

-- flow control. Stop inflow if any parallel buffer is getting too full.	
DATAIN_READY <= '1' when ((not DATAIN_CTS) = 0) else '0';


--//-- READ DP RAM ------------------------------------------------------
-- Read received bits. Insert erasures where puncturing occured at the encoder.
-- The encoding/puncturing pattern is represented as a two-dimensional matrix, with
-- X as number of parity bits (i.e. number of polynomials used for encoding, 2 or 3) and 
-- Y as puncturing phase index. 
-- Each group of N_PARITY_BITS is then passed to the BMU to compute the branch metric.

BRAM_DP_002: for I in 0 to (NDEC -1) generate

	-- Event3: starting a block read
	EVENT3(I) <= '1' when (READ_STATE(I)= '0') and (BUF_SIZE(I) /= 0) else '0';
	
	-- read state machine
	READ_STATE_00X: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SYNC_RESET = '1') then
				READ_STATE(I) <= '0';
			elsif(EVENT3(I) = '1') then
				-- start reading a new block
				-- can be concurrent with write
				READ_STATE(I) <= '1';
			elsif(READ_STATE(I)= '1') and (BUF_SIZE(I) /= 0) and (READ_BIT_CNTR(I) = 2111) and (PUNCTURING_PATTERN(PP_X(I),PP_Y(I)) = '1') then
				-- reading the last bit. block reading complete
				READ_STATE(I) <= '0';
			end if;
		end if;
	end process;

	-- RPTR management
	RPTR_GEN_001: process(CLK)
	begin
		if rising_edge(CLK) then
			SAMPLE1_CLK(I) <= SAMPLE1_CLK_E(I);
			ERASURE1(I) <= ERASURE1_E(I);
			PARITY_INDEX(I) <= PARITY_INDEX_E(I);
				-- 1 CLK latency to read data from the block ram
				
			if(SYNC_RESET = '1') then
				RPTR(I) <= (others => '1');
				SAMPLE1_CLK_E(I) <= '0';
				ERASURE1_E(I) <= '0';
				PP_X(I) <= 0;	-- puncturing phase index
				PP_Y(I) <= 0;	-- parity bit index
				READ_BIT_CNTR(I) <= (others => '0');
			elsif(EVENT3(I) = '1') then
				-- start reading a new block
				-- The total number of bits to read is known (fixed block length) L1+L2+L3 = 2112 bits
				READ_BIT_CNTR(I) <= (others => '0');
			elsif(READ_STATE(I)= '1') and (PUNCTURING_PATTERN(PP_X(I),PP_Y(I)) = '0') then
				-- next bit was erased by puncturing at the encoder
				SAMPLE1_CLK_E(I) <= '1';
				ERASURE1_E(I) <= '1';
				PARITY_INDEX_E(I) <= PP_Y(I);
				-- increment 2D pointer to PUNCTURING_PATTERN
				if(PP_Y(I) = N_PARITY_BITS -1 ) then	-- modulo N_PARITY_BITS counter
					PP_Y(I) <= 0;	
					if(PP_X(I) = N_PUNCTURING_PHASES -1 ) then	-- modulo N_PUNCTURING_PHASES counter
						PP_X(I) <= 0;	
					else
						PP_X(I) <= PP_X(I) + 1;	-- puncturing phase index
					end if;
				else
					PP_Y(I) <= PP_Y(I) + 1;	-- parity bit index
				end if;
			elsif(READ_STATE(I)= '1') and (BUF_SIZE(I) /= 0) then
				-- buffer is not empty
				RPTR(I) <= RPTR(I) + 1;
				READ_BIT_CNTR(I) <= READ_BIT_CNTR(I) + 1;	-- count the number of bits read from the stored block
				-- The total number of bits to read is known (fixed block length) L1+L2+L3 = 2112 bits
				SAMPLE1_CLK_E(I) <= '1';
				ERASURE1_E(I) <= '0';
				PARITY_INDEX_E(I) <= PP_Y(I);
				-- increment 2D pointer to PUNCTURING_PATTERN
				if(PP_Y(I) = N_PARITY_BITS -1 ) then	-- modulo N_PARITY_BITS counter
					PP_Y(I) <= 0;	
					if(PP_X(I) = N_PUNCTURING_PHASES -1 ) then	-- modulo N_PUNCTURING_PHASES counter
						PP_X(I) <= 0;	
					else
						PP_X(I) <= PP_X(I) + 1;	-- puncturing phase index
					end if;
				else
					PP_Y(I) <= PP_Y(I) + 1;	-- parity bit index
				end if;
			else
				SAMPLE1_CLK_E(I) <= '0';
				ERASURE1_E(I) <= '0';
			end if;
		end if;
	end process;
	
	-- form a parity group (including erasure information) for use by subsequent Branch Metric Unit (BMU)
	PG_001: process(CLK)
	begin
		if rising_edge(CLK) then
			if(SAMPLE1_CLK(I) = '1') then
				ERASURES(I*PARITY_INDEX(I)) <= ERASURE1(I);
				DATAOUT((4*I*PARITY_INDEX(I)+3) downto (4*I*PARITY_INDEX(I))) <= DOB(I);
				
				if(PARITY_INDEX(I) = N_PARITY_BITS - 1) then
					-- parity group is complete
					DATAOUT_VALID(I) <= '1';
				else
					-- parity group is still being assembled
					DATAOUT_VALID(I) <= '0';
				end if;
				
			end if;
		end if;
	end process;

end generate;



end Behavioral;

