-------------------------------------------------------------
-- MSS copyright 2014-2018
-- Filename:  TC_ENCODER_DVB_RCS2.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: 2
-- Last modified: 5/19/18

-- Inheritance: 	N/A
--
-- description:  DVB-RCS2 turbo code encoder 

-- Occupancy (Xilinx Artix7-100T)
--Registers	691	<1%
--LUTs	1251	1%
--Block RAM/FIFO	4	2%
--DSP48	1	0%
--GCLKs	1	<1%
--DCMs	0	0%
--PLL_ADVs	0	0%

-- Maximum speed (Xilinx Artix7-100T, -2 speed grade)
-- Minimum period: 4.705ns (Maximum Frequency: 212.562MHz)
--
-- Rev 2 5/19/18 AZ
-- Corrected rate7/8 Y puncturing period bug
-------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;

entity TC_ENCODER_DVB_RCS2 is
	generic (
		SIMULATION: std_logic := '0'
			-- 1 during simulation, 0 for release	
	);
    Port ( 
		--GLOBAL CLOCKS
		CLK: std_logic;
		SYNC_RESET: in std_logic;
		
		-- INPUTS
		DATA_IN: in std_logic_vector(1 downto 0);
			-- DATA_IN(0) = A
			-- DATA_IN(1) = B
		DATA_IN_VALID: in std_logic;
			-- input byte is read at the rising edge of CLK when DATA_IN_VALID = '1'
		SOF_IN: in std_logic;
			-- 1 CLK pulse indicating the first DATA_IN in the frame. Aligned with DATA_IN_VALID pulse
		-- NOTE: no need for EOF_IN, as the BURST_PAYLOAD_SIZE tells unambiguously when the EOF_IN occurs.
		CTS_OUT: out std_logic;
			-- flow control. Always check the Clear-To-Send flag before sending a new DATA_IN dibit.
			-- IMPORTANT: relying on CTS_OUT for flow control may not be sufficient because of latency in 
			-- stopping the flow. NEVER send the next SOF_IN when CTS_OUT = '0'. This implies the sender must 
			-- count the data symbol in a frame, stop at N and wait 2 CLKs at least before checking CTS_OUT again.
			
		
		-- CONTROLS
		-- See ETSI EN 300 545 table 9-2
		-- one MUST apply a SYNC_RESET pulse to activate these controls
		BURST_PAYLOAD_SIZE: in std_logic_vector(9 downto 0);
			-- range 1 - 1000 BYTES
		R1: in std_logic_vector(2 downto 0);
		R2: in std_logic_vector(3 downto 0);
			-- encoding rate R1/R2
			-- Valid entries  1/3,1/2,2/3,3/4,4/5,5/6,6/7,7/8
		P: in std_logic_vector(6 downto 0);
			-- range 9 - 81
		Q0: in std_logic_vector(3 downto 0);	
		Q1: in std_logic_vector(3 downto 0);
		Q2: in std_logic_vector(3 downto 0);
			-- range 0 - 15
		Q3: in std_logic_vector(2 downto 0);
			-- range 0 - 5
		Y_PUNCTURING_PERIOD: in std_logic_vector(4 downto 0);
			-- 1 - 28 bits
		Y_PUNCTURING_PATTERN: in std_logic_vector(27 downto 0);
			-- read from right (bit 0) to left (bit Y_PUNCTURING_PERIOD-1) 
			-- right-aligned. MSbs may be unused, depending on the period.
			-- therefore the patterns in Annex A must be flipped.
		W_PUNCTURING_PATTERN: in std_logic;
		CRC16_EN: in std_logic;
			-- enable insertion of a 16-bit CRC 
			
		-- OUTPUTS
		DATA_OUT: out std_logic_vector(1 downto 0);
			-- DATA_OUT(0) = A
			-- DATA_OUT(1) = B
		DATA_OUT_VALID: out std_logic;
		SOF_OUT: out std_logic;
		EOF_OUT: out std_logic;
		CTS_IN: in std_logic;
		
		-- MONITORING
		N_PARITY_BITS: out std_logic_vector(11 downto 0);
		N_PARITY_BITS_RDY: out std_logic;
			-- Y1,W1 only, after puncturing. Double to account for Y2,W2.
		TP: out std_logic_vector(10 downto 1)  
			  );
end entity;

architecture Behavioral of TC_ENCODER_DVB_RCS2 is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
-- Size optimization possible if encoder/decoder are configured identically. Use PERMUTATION_TABLE at top level and share.
--	COMPONENT PERMUTATION_TABLE
--	PORT(
--		CLK : IN std_logic;
--		SYNC_RESET : IN std_logic;
--		BURST_PAYLOAD_SIZE : IN std_logic_vector(9 downto 0);
--		R1 : IN std_logic_vector(2 downto 0);
--		R2 : IN std_logic_vector(3 downto 0);
--		P : IN std_logic_vector(6 downto 0);
--		Q0 : IN std_logic_vector(3 downto 0);
--		Q1 : IN std_logic_vector(3 downto 0);
--		Q2 : IN std_logic_vector(3 downto 0);
--		Q3 : IN std_logic_vector(2 downto 0);
--		J_IN : IN std_logic_vector(11 downto 0);
--		JPERM_IN : IN std_logic_vector(11 downto 0);          
--		JPERM_OUT : OUT std_logic_vector(11 downto 0);
--		J_OUT : OUT std_logic_vector(11 downto 0);
--		TABLE_READY : OUT std_logic
--		);
--	END COMPONENT;

 COMPONENT ARITH
 PORT(
		CLK : IN  std_logic;
		SYNC_RESET : IN  std_logic;
		OPERAND_A : IN  std_logic_vector(17 downto 0);
		OPERAND_A_VALID : IN  std_logic;
		OPERAND_B : IN  std_logic_vector(17 downto 0);
		OPERAND_B_VALID : IN  std_logic;
		OP : IN  std_logic_vector(3 downto 0);
		OP_VALID : IN  std_logic;
		RESULT : OUT  std_logic_vector(35 downto 0);
		RESULT_VALID : OUT  std_logic;
		READY : OUT  std_logic
	  );
 END COMPONENT;
 
	COMPONENT CRC16_8b
	PORT(
		CLK : IN std_logic;
		SYNC_RESET : IN std_logic;
		CRC16_IN : IN std_logic_vector(15 downto 0);
		DATA_IN : IN std_logic_vector(7 downto 0);
		DATA_VALID_IN : IN std_logic;          
		CRC16_OUT : OUT std_logic_vector(15 downto 0);
		CRC16_VALID : OUT std_logic
		);
	END COMPONENT;
 
	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;	-- chip select, active high
		WEB    : in  std_logic;	-- write enable, active high
		OEB : in std_logic;	-- output enable, active high
		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

--// COMPUTE INITIAL PERMUTATION INDICES  -------------------------
signal N0: unsigned(11 downto 0):= (others => '0');
signal N: unsigned(11 downto 0):= (others => '0');
signal N_ENC: unsigned(13 downto 0) := (others => '0');
signal IPI0: unsigned(11 downto 0):= (others => '0');
signal IPI1: unsigned(11 downto 0):= (others => '0');
signal IPI2: unsigned(11 downto 0):= (others => '0');
signal IPI3: unsigned(11 downto 0):= (others => '0');
signal N_MOD15_MINUSONE: unsigned(3 downto 0):= (others => '0');
signal ARITH_STATE: integer range 0 to 14 := 0;
signal ARITH_OPERAND_A : std_logic_vector(17 downto 0) := (others => '0');
signal ARITH_OPERAND_B : std_logic_vector(17 downto 0) := (others => '0');
signal ARITH_OPERAND_A_VALID : std_logic := '0';
signal ARITH_OPERAND_B_VALID : std_logic := '0';
signal ARITH_OP : std_logic_vector(3 downto 0) := (others => '0');
signal ARITH_OP_VALID: std_logic := '0';
signal ARITH_RESULT : std_logic_vector(35 downto 0) := (others => '0');
signal ARITH_RESULT_VALID : std_logic := '0';
signal ARITH_READY : std_logic := '0';
signal ARITH_DONE : std_logic := '0';
signal ENC_RESET: std_logic := '0';

--//-- INSERT 16-BIT CRC --------------------------
signal CRC_CNTR: unsigned(1 downto 0) := "00";
signal DATA0: std_logic_vector(7 downto 0) := x"00";
signal DATA0_VALID: std_logic := '0';
signal DATA0_VALID_D: std_logic := '0';
signal CRC_SOF_FLAG: std_logic := '0';
signal CRC_SOF: std_logic := '0';
signal CRC_SYNC_RESET: std_logic := '0';
signal CRC_IN: std_logic_vector(15 downto 0) := x"0000";
signal CRC_OUT: std_logic_vector(15 downto 0) := x"0000";
signal CRC_SHIFT: std_logic_vector(15 downto 0) := x"0000";

--// INPUT ELASTIC BUFFER -------------------------
signal DATA_IN_VALID2: std_logic := '0';
signal DATA_IN_D: std_logic_vector(1 downto 0) := (others => '0');
signal DATA_IN_D2: std_logic_vector(1 downto 0) := (others => '0');
signal FLIPPED_DATA_IN_D: std_logic_vector(1 downto 0) := (others => '0');
signal DATA_IN_VALID_D: std_logic := '0';
signal EOF_IN_D: std_logic := '0';
signal WPTR: unsigned(12 downto 0) := (others => '0');
signal WPTR_INC: unsigned(12 downto 0) := (others => '0');
signal WPTR2: unsigned(12 downto 0) := (others => '0');
signal RPTR1: unsigned(12 downto 0) := (others => '0');
signal RPTR2: unsigned(12 downto 0) := (others => '0');
signal I0: unsigned(12 downto 0):= (others => '0');
signal I1: unsigned(12 downto 0):= (others => '0');
signal I2: unsigned(12 downto 0):= (others => '0');
signal I3: unsigned(12 downto 0):= (others => '0');
signal DATA1: std_logic_vector(1 downto 0) := (others => '0');
signal DATA2: std_logic_vector(1 downto 0) := (others => '0');
signal DATA1A_VALID: std_logic := '0';
signal DATA1B_VALID: std_logic := '0';
signal DATA2A_VALID: std_logic := '0';
signal DATA2B_VALID: std_logic := '0';

--// ENCODER SEQUENCER -------------------------
signal EVENT1: std_logic := '0';
signal EVENT2: std_logic := '0';
signal EVENT3: std_logic := '0';
signal EVENT4: std_logic := '0';
signal ENC_STATE: integer range 0 to 5 := 0;
signal CNTR: unsigned(11 downto 0) := (others => '0');
signal ENC_STATE2: integer range 0 to 4 := 0;
signal CNTR2: unsigned(11 downto 0) := (others => '0');

--// ENCODER CORE #1 (NOT INTERLEAVED) ---------------------------------
signal S1: std_logic_vector(3 downto 0) := (others => '0');
signal DATA31: std_logic_vector(1 downto 0) := "00";
signal DATA31A_VALID: std_logic := '0';
signal DATA31B_VALID: std_logic := '0';

--// ENCODER CORE #2 (INTERLEAVED) ---------------------------------
signal S2: std_logic_vector(3 downto 0) := (others => '0');
signal DATA32: std_logic_vector(1 downto 0) := "00";
signal DATA32A_VALID: std_logic := '0';
signal DATA32B_VALID: std_logic := '0';

signal DATA4: std_logic_vector(1 downto 0) := "00";
signal DATA4_VALID: std_logic := '0';

--// CIRCULATION STATES ---------------------------------
signal C1: std_logic_vector(3 downto 0) := (others => '0');
signal C2: std_logic_vector(3 downto 0) := (others => '0');
type CIRC_TBL_TYPE is array ((14*16-1) downto 0 ) of integer range 0 to 15;
constant CIRC_TBL: CIRC_TBL_TYPE := (
0, 14, 3, 13, 7, 9, 4, 10, 15, 1, 12, 2, 8, 6, 11, 5,
0, 11, 13, 6, 10, 1, 7, 12, 5, 14, 8, 3, 15, 4, 2, 9,
0, 8, 9, 1, 2, 10, 11, 3, 4, 12, 13, 5, 6, 14, 15, 7,
0, 3, 4, 7, 8, 11, 12, 15, 1, 2, 5, 6, 9, 10, 13, 14,
0, 12, 5, 9, 11, 7, 14, 2, 6, 10, 3, 15, 13, 1, 8, 4,
0, 4, 12, 8, 9, 13, 5, 1, 2, 6, 14, 10, 11, 15, 7, 3,
0, 6, 10, 12, 5, 3, 15, 9, 11, 13, 1, 7, 14, 8, 4, 2,
0, 7, 8, 15, 1, 6, 9, 14, 3, 4, 11, 12, 2, 5, 10, 13,
0, 5, 14, 11, 13, 8, 3, 6, 10, 15, 4, 1, 7, 2, 9, 12,
0, 13, 7, 10, 15, 2, 8, 5, 14, 3, 9, 4, 1, 12, 6, 11,
0, 2, 6, 4, 12, 14, 10, 8, 9, 11, 15, 13, 5, 7, 3, 1,
0, 9, 11, 2, 6, 15, 13, 4, 12, 5, 7, 14, 10, 3, 1, 8,
0, 10, 15, 5, 14, 4, 1, 11, 13, 7, 2, 8, 3, 9, 12, 6,
0, 15, 1, 14, 3, 12, 2, 13, 7, 8, 6, 9, 4, 11, 5, 10
);
signal CI1: integer range 0 to 223 := 0;
signal CI2: integer range 0 to 223 := 0;

--// PUNCTURING -------------------------
signal Y_PUNCTURING: std_logic_vector(28 downto 0) := (others => '0');
signal DATA5: std_logic_vector(1 downto 0) := "00";
signal DATA5_VALID: std_logic := '0';
signal LEFT_OVER_BIT: std_logic := '0';
signal LEFT_OVER: std_logic := '0';
signal N_PARITY_BITS_local: unsigned(11 downto 0) := (others => '0');

--// OUTPUT -------------------------
signal RPTR2MSB_DELAY: std_logic_vector(6 downto 0) := (others => '0');
signal RPTR2MSB_D7: std_logic := '0';
signal WPTR61: unsigned(13 downto 0) := (others => '0');
signal WPTR62: unsigned(13 downto 0) := (others => '0');
signal RPTR71: unsigned(13 downto 0) := (others => '0');
signal RPTR72: unsigned(13 downto 0) := (others => '0');
signal RPTR71_INC: unsigned(13 downto 0) := (others => '0');
signal RPTR72_INC: unsigned(13 downto 0) := (others => '0');
signal BUF61_SIZE: unsigned(13 downto 0) := (others => '0');
signal BUF62_SIZE: unsigned(13 downto 0) := (others => '0');
signal DATA61: std_logic_vector(1 downto 0) := (others => '0');
signal DATA62: std_logic_vector(1 downto 0) := (others => '0');
signal DATA61_VALID: std_logic := '0';
signal DATA62_VALID: std_logic := '0';
signal CTS6: std_logic := '0';
signal CTS6_D: std_logic := '0';
signal DATA71: std_logic_vector(1 downto 0) := (others => '0');
signal DATA72: std_logic_vector(1 downto 0) := (others => '0');
signal DATA71_VALID_E: std_logic := '0';
signal DATA72_VALID_E: std_logic := '0';
signal DATA71_VALID: std_logic := '0';
signal DATA72_VALID: std_logic := '0';
signal SOF71_E: std_logic := '0';
signal SOF71: std_logic := '0';
signal SOF72_E: std_logic := '0';
signal SOF72: std_logic := '0';
signal SOF7: std_logic := '0';
signal EOF71_E: std_logic := '0';
signal EOF71: std_logic := '0';
signal EOF72_E: std_logic := '0';
signal EOF72: std_logic := '0';
signal EOF7: std_logic := '0';
signal DATA7_VALID: std_logic := '0';
signal OUTPUT_BUF_READ_SEL: std_logic := '0';

signal PERM_TBL_READY: std_logic := '0';
signal JPERM_OUT: std_logic_vector(11 downto 0) := (others => '0');
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin

----//-- PERMUTATION INDICES ---------------------------------------
-- Size optimization possible if encoder/decoder are configured identically. Use PERMUTATION_TABLE at top level and share.
--PERMUTATION_TABLE_001: PERMUTATION_TABLE PORT MAP(
--	CLK => CLK,
--	SYNC_RESET => SYNC_RESET,
--	BURST_PAYLOAD_SIZE => BURST_PAYLOAD_SIZE,
--	R1 => R1,
--	R2 => R2,
--	P => P,
--	Q0 => Q0,
--	Q1 => Q1,
--	Q2 => Q2,
--	Q3 => Q3,
--	J_IN => std_logic_vector(WPTR(11 downto 0)),
--	JPERM_OUT => JPERM_OUT,
--	JPERM_IN => (others => '0'),
--	J_OUT => open,
--	TABLE_READY => PERM_TBL_READY
--);

--// COMPUTE INITIAL PERMUTATION INDICES -------------------------
-- 4 increments: 3, (4Q1+3) modulo N, (4Q2+3 + 4Q0P) modulo N, (4Q3+3 + 4Q0P) modulo N

-- number of N couples (K/2), includes optional CRC16 field appended after the payload. 
N0 <= unsigned(BURST_PAYLOAD_SIZE & "00"); 	-- no CRC16
N_GEN: process(N0, CRC16_EN)
begin
	if(CRC16_EN = '0') then
		N <= N0;	-- no CRC16 
	else
		N <= N0 + to_unsigned(8,N'length);	-- CRC16 appended after payload.
	end if;
end process;

-- Sequence arithmetics
ARITH_SEQUENCER_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			-- start computing (4Q1+3 + P) modulo N
			ARITH_STATE <= 1;
			ARITH_OPERAND_A(5 downto 0) <= Q1 & "00";
			ARITH_OPERAND_A(17 downto 6) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B <= "00" & x"0003";
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0011";
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 1) and (ARITH_RESULT_VALID = '1') then
			-- +P
			ARITH_STATE <= 2;
			ARITH_OPERAND_A(6 downto 0) <= P;
			ARITH_OPERAND_A(17 downto 7) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B <= (others => '0');
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0100";  -- RESULT = RESULT + A + B
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 2) and (ARITH_RESULT_VALID = '1') then
			-- modulo N
			ARITH_STATE <= 3;
			ARITH_OPERAND_A(11 downto 0) <= std_logic_vector(N);
			ARITH_OPERAND_A(17 downto 12) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OP <= "0110";  -- RESULT = RESULT modulo A
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 3) and (ARITH_RESULT_VALID = '1') then
			-- start computing (4Q2+3 + 4Q0P + 2P) modulo N
			ARITH_STATE <= 4;
			ARITH_OPERAND_A(5 downto 0) <= Q2 & "00";
			ARITH_OPERAND_A(17 downto 6) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B <= "00" & x"0003";
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0011";	-- A*B
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 4) and (ARITH_RESULT_VALID = '1') then
			ARITH_STATE <= 5;
			ARITH_OPERAND_A(5 downto 0) <= Q0 & "00";
			ARITH_OPERAND_A(17 downto 6) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B(6 downto 0) <= P;
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0010";	-- multiply accumulate
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 5) and (ARITH_RESULT_VALID = '1') then
			-- +2P
			ARITH_STATE <= 6;
			ARITH_OPERAND_A(7 downto 0) <= P & "0";
			ARITH_OPERAND_A(17 downto 8) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B <= (others => '0');
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0100";  -- RESULT = RESULT + A + B
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 6) and (ARITH_RESULT_VALID = '1') then
			ARITH_STATE <= 7;
			ARITH_OPERAND_A(11 downto 0) <= std_logic_vector(N);
			ARITH_OPERAND_A(17 downto 12) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OP <= "0110";  -- RESULT = RESULT modulo A
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 7) and (ARITH_RESULT_VALID = '1') then
			-- start computing (4Q3+3 + 4Q0P +3P) modulo N
			ARITH_STATE <= 8;
			ARITH_OPERAND_A(4 downto 0) <= Q3 & "00";
			ARITH_OPERAND_A(17 downto 5) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B <= "00" & x"0003";
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0011";	-- A*B
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 8) and (ARITH_RESULT_VALID = '1') then
			ARITH_STATE <= 9;
			ARITH_OPERAND_A(5 downto 0) <= Q0 & "00";
			ARITH_OPERAND_A(17 downto 6) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B(6 downto 0) <= P;
			ARITH_OPERAND_B(17 downto 7) <= (others => '0');
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0010";	-- multiply accumulate
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 9) and (ARITH_RESULT_VALID = '1') then
			-- +3P
			ARITH_STATE <= 10;
			ARITH_OPERAND_A(7 downto 0) <= P & "0";
			ARITH_OPERAND_A(17 downto 8) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B(6 downto 0) <= P;
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0100";  -- RESULT = RESULT + A + B
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 10) and (ARITH_RESULT_VALID = '1') then
			ARITH_STATE <= 11;
			ARITH_OPERAND_A(11 downto 0) <= std_logic_vector(N);
			ARITH_OPERAND_A(17 downto 12) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OP <= "0110";  -- RESULT = RESULT modulo A
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 11) and (ARITH_RESULT_VALID = '1') then
			-- N mod 15
			ARITH_STATE <= 12;
			ARITH_OPERAND_A <= "00" & x"000F";
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OPERAND_B(11 downto 0) <= std_logic_vector(N);
			ARITH_OPERAND_B(17 downto 12) <= (others => '0');
			ARITH_OPERAND_B_VALID <= '1';
			ARITH_OP <= "0111";  -- RESULT = B modulo A
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 12) and (ARITH_RESULT_VALID = '1') then
			-- N*R2/R1 (part 1/2)
			ARITH_STATE <= 13;
			ARITH_OPERAND_A(3 downto 0) <= R2;
			ARITH_OPERAND_A(17 downto 4) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OP <= "0001";  -- RESULT = A*B, RESULT copied to A (MSBs) and B (LSBs)
			ARITH_OP_VALID <= '1';
		elsif(ARITH_STATE = 13) and (ARITH_RESULT_VALID = '1') then
			-- N*R2/R1 (part 2/2)
			ARITH_STATE <= 14;
			ARITH_OPERAND_A(2 downto 0) <= R1;
			ARITH_OPERAND_A(17 downto 3) <= (others => '0');
			ARITH_OPERAND_A_VALID <= '1';
			ARITH_OP <= "0101";  -- RESULT = RESULT / A
			ARITH_OP_VALID <= '1';
			
		else
			ARITH_OPERAND_A_VALID <= '0';
			ARITH_OPERAND_B_VALID <= '0';
			ARITH_OP_VALID <= '0';
		end if;
	end if;
end process;

ARITH_001: ARITH PORT MAP (
		 CLK => CLK,
		 SYNC_RESET => SYNC_RESET,
		 OPERAND_A => ARITH_OPERAND_A,
		 OPERAND_A_VALID => ARITH_OPERAND_A_VALID,
		 OPERAND_B => ARITH_OPERAND_B,
		 OPERAND_B_VALID => ARITH_OPERAND_B_VALID,
		 OP => ARITH_OP,
		 OP_VALID => ARITH_OP_VALID,
		 RESULT => ARITH_RESULT,
		 RESULT_VALID => ARITH_RESULT_VALID,
		 READY => ARITH_READY
	  );

-- read initial permuted indices 
IPI0 <= "000000000011";
IPI_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ARITH_RESULT_VALID = '1') then
			if(ARITH_STATE = 3) then
				--(4Q1+3+P) modulo N
				IPI1 <= unsigned(ARITH_RESULT(11 downto 0));	
			end if;
			if(ARITH_STATE = 7) then
				-- (4Q2+3 + 4Q0P +2P) modulo N
				IPI2 <= unsigned(ARITH_RESULT(11 downto 0));
			end if;
			if(ARITH_STATE = 11) then
				-- (4Q3+3 + 4Q0P +3P) modulo N
				IPI3 <= unsigned(ARITH_RESULT(11 downto 0));	
			end if;
			if(ARITH_STATE = 12) then
				-- N mod 15
				N_MOD15_MINUSONE <= unsigned(ARITH_RESULT(3 downto 0)) -1;	
			end if;
			if(ARITH_STATE = 14) then
				-- N*R2/R1  (encoded frame size in symbols, after puncturing)
				N_ENC <= unsigned(ARITH_RESULT(N_ENC'left downto 0));	
			end if;
		end if;
	end if;
end process;

-- prevent any encoding until ALL pre-requisite arithmetics is complete
ARITH_DONE_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			ARITH_DONE <= '0';
		elsif(ARITH_RESULT_VALID = '1') and (ARITH_STATE = 14) then
			ARITH_DONE <= '1';
		end if;
	end if;
end process;

-- all subsequent processes must be in reset until arith is done
ENC_RESET <= (not ARITH_DONE) or SYNC_RESET;

--//-- INSERT 16-BIT CRC --------------------------
CRC_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		DATA0_VALID_D <= DATA0_VALID;

		if(SYNC_RESET = '1') or (CRC16_EN = '0') then
			CRC_CNTR <= (others => '0');
			DATA0_VALID <= '0';
		elsif(ENC_STATE = 0) and (SOF_IN = '1') then
			-- first two bits
			DATA0 <= DATA0(5 downto 0) & DATA_IN(0) & DATA_IN(1);	-- shift into byte, MSb (A) first
			CRC_CNTR <= (others => '0');
			DATA0_VALID <= '0';
		elsif(DATA_IN_VALID = '1') and (ENC_STATE = 1)  then
			DATA0 <= DATA0(5 downto 0) & DATA_IN(0) & DATA_IN(1);	-- shift into byte, MSb (A) first
			CRC_CNTR <= CRC_CNTR + 1;
			if(CRC_CNTR = 2) then
				DATA0_VALID <= '1';
			else
				DATA0_VALID <= '0';
			end if;
		else
			DATA0_VALID <= '0';
		end if;
	end if;
end process;



CRC_GEN_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') or (CRC16_EN = '0') then
			CRC_SOF_FLAG <= '0';
		elsif(ENC_STATE = 0) and (SOF_IN = '1') then
			-- first two bits
			CRC_SOF_FLAG <= '1';
		elsif(DATA0_VALID = '1') then
			CRC_SOF_FLAG <= '0';
		end if;
	end if;
end process;

CRC_SOF <= CRC_SOF_FLAG and DATA0_VALID;

-- 1 CLK latency
CRC_IN <= CRC_OUT when (CRC_SOF = '0') else (others => '1');
CRC_SYNC_RESET <= SYNC_RESET or (not CRC16_EN);
CRC_GEN_003: CRC16_8b PORT MAP(
	CLK => CLK,
	SYNC_RESET => CRC_SYNC_RESET,
	CRC16_IN => CRC_IN,
	DATA_IN => DATA0,
	DATA_VALID_IN => DATA0_VALID,
	CRC16_OUT => CRC_OUT,
	CRC16_VALID => open
);

-- save CRC for subsequent shift and read
CRC_GEN_004: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') or (CRC16_EN = '0') then
			CRC_SHIFT <= (others => '0');
		elsif (DATA0_VALID_D = '1') then
			-- save CRC16
			CRC_SHIFT <= CRC_OUT; 	
		elsif(ENC_STATE = 1) and (DATA_IN_VALID_D = '0') and (CNTR < 9) then	
			-- shift CRC, read 2 bits at a time
			CRC_SHIFT(15 downto 2) <= CRC_SHIFT(13 downto 0); 
		end if;
	end if;
end process;

--// INPUT ELASTIC BUFFER -------------------------

-- WPTR management for input buffer.
-- Input buffer is sized for two maximum size frames (1000 BYTES each)
-- WPTR meaning: last written cell (not the usual 'next cell to be written', because of the need to compute interleaving index)
WPTR_INC <= WPTR + 1;

-- encoder input data valid: includes both payload data and optional CRC
--DATA_IN_VALID2 <= '1' when (EVENT1 = '1') and (DATA_IN_VALID = '1') else
DATA_IN_VALID2 <= '1' when (EVENT1 = '1') else  -- *051818
						'1' when (ENC_STATE = 1) and (DATA_IN_VALID = '1') and (WPTR_INC(11 downto 0) < N0) else -- payload data
						'1' when (ENC_STATE = 1) and (CRC16_EN = '1') and (DATA_IN_VALID_D = '0') and (CNTR < 9) and (DATA0_VALID_D = '0')  else -- CRC
						'0';
						
WPTR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			WPTR(11 downto 0) <= (others => '0');
			WPTR(12) <= '1';	-- will be flipped at the first SOF_IN
			DATA_IN_VALID_D <= '0';
		elsif(EVENT1 = '1') then
			WPTR(11 downto 0) <= (others => '0');
			WPTR(12) <= not WPTR(12);	-- alternate every frame
			DATA_IN_D <= DATA_IN;
			DATA_IN_VALID_D <= DATA_IN_VALID;
		elsif(ENC_STATE = 1) then	-- **100316
			if(DATA_IN_VALID = '1') and (WPTR_INC(11 downto 0) < N0) then
				-- defensive code: do not write past the declared frame size  **100216
				WPTR <= WPTR_INC;
				DATA_IN_D <= DATA_IN;
				DATA_IN_VALID_D <= DATA_IN_VALID;
			elsif(CRC16_EN = '1') and (DATA_IN_VALID_D = '0') and (CNTR < 9) and (DATA0_VALID_D = '0') then
				-- append CRC16, 2 bits at a time
				WPTR <= WPTR_INC;
				DATA_IN_D <= CRC_SHIFT(14) & CRC_SHIFT(15);
				DATA_IN_VALID_D <= '1';
			else
				DATA_IN_VALID_D <= '0';
			end if;
		else
			DATA_IN_VALID_D <= '0';
		end if;
	end if;
end process;

-- compute the permutation index
PERMUTATION_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			WPTR2 <= (others => '1');
		elsif(EVENT1 = '1') then
			-- initialize the i index (divided into 4 groups)
			I0 <= IPI0 + unsigned("0000" & P & "00");	-- add 4*P
			I1 <= "0" & IPI1;
			I2 <= "0" & IPI2;
			I3 <= "0" & IPI3;
			WPTR2(12) <= not WPTR(12);  -- align pointer to start of alternate buffer
			WPTR2(11 downto 0) <= IPI0;
		elsif(DATA_IN_VALID2 = '1') and (ENC_STATE = 1) then	
			case(WPTR(1 downto 0)) is
				when "00" => WPTR2(11 downto 0) <= I1(11 downto 0);
				when "01" => WPTR2(11 downto 0) <= I2(11 downto 0);
				when "10" => WPTR2(11 downto 0) <= I3(11 downto 0);
				when others => WPTR2(11 downto 0) <= I0(11 downto 0);
			end case;
			
			-- prepare index in advance of next round(step 1/2)
			-- step 2/2. modulo N (enough clock cycles to go through 3 times)
			if(WPTR(1 downto 0) = "00") then
				I1 <= I1 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I1 >= N) then
				I1 <= I1 - N;
			end if;
			
			if(WPTR(1 downto 0) = "01") then
				I2 <= I2 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I2 >= N) then
				I2 <= I2 - N;
			end if;
			
			if(WPTR(1 downto 0) = "10") then
				I3 <= I3 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I3 >= N) then
				I3 <= I3 - N;
			end if;

			if(WPTR(1 downto 0) = "11") then
				I0 <= I0 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I0 >= N) then
				I0 <= I0 - N;
			end if;
			
		end if;
		
		
	end if;
end process;


-- regenerate EOF_IN
EOF_IN_D <= '1' when (DATA_IN_VALID_D = '1') and (WPTR_INC(11 downto 0) = N) else '0';

-- flow control
-- rule #1: no input until pre-requisite arithmetics is complete
-- rule #2: when read is idle, ok to receive input data as long as there is room in the output buffer
CTS_OUT <= '0' when (ARITH_DONE = '0') or (EOF_IN_D = '1') else
				'1' when ((ENC_STATE = 0) and (CTS6 = '1')) else	-- start input frame if idle and room in output buffer
				'1' when (ENC_STATE = 1) and (WPTR_INC(11 downto 0) < N0) else -- no slowing the input frame once started
				'0'; 

-- write simultaneously to two RAMB, one in the natural order, the other in the interleaved order
-- Buffer is large enough for 2 maximum-size (1000 BYTES) input frames.
INPUT_BUF_001: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => 2,		
	ADDR_WIDTHA => 13,
	DATA_WIDTHB => 2,		 
	ADDR_WIDTHB => 13
)
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	WEA => DATA_IN_VALID_D,      
	ADDRA => std_logic_vector(WPTR),  
	DIA => DATA_IN_D,      
	OEA => '0',
	DOA => open,
	CLKB => CLK,
	CSB => '1',
	WEB => '0',
	ADDRB => std_logic_vector(RPTR1),  
	DIB => "00",      
	OEB => '1',
	DOB => DATA1      
);

-- interleaved order
-- if j mod 2 = 1, interchange Aj and Bj, i.e. let (Aj,Bj) = (Bj,Aj)
FLIPPED_DATA_IN_D <= DATA_IN_D when (WPTR(0) = '0') else (DATA_IN_D(0) & DATA_IN_D(1));

INPUT_BUF_002: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => 2,		
	ADDR_WIDTHA => 13,
	DATA_WIDTHB => 2,		 
	ADDR_WIDTHB => 13
)
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	WEA => DATA_IN_VALID_D,      
	ADDRA => std_logic_vector(WPTR2),  
	DIA => FLIPPED_DATA_IN_D,      
	OEA => '0',
	DOA => open,
	CLKB => CLK,
	CSB => '1',
	WEB => '0',
	ADDRB => std_logic_vector(RPTR2),  
	DIB => "00",      
	OEB => '1',
	DOB => DATA2      
);


-- output data 1 CLK after the RPTR is stable
DATA_VALID_001: process(CLK)
begin
	if rising_edge(CLK) then
		CTS6_D <= CTS6;
		DATA_IN_D2 <= DATA_IN_D;
		
		
		-- special case encoder pass #1: 
		-- input data goes directly from the input to the encoder
		if(ENC_RESET = '1') then
			DATA1A_VALID <= '0';
		elsif(ENC_STATE = 1)  then
			DATA1A_VALID <= DATA_IN_VALID_D;	-- encoder pass #1
		else
			DATA1A_VALID <= '0';
		end if;
		
		if(ENC_RESET = '1') then
			DATA1B_VALID <= '0';
		elsif(ENC_STATE = 3)  then
			DATA1B_VALID <= CTS6_D;	-- encoder pass #2
		else
			DATA1B_VALID <= '0';
		end if;
		if(ENC_RESET = '1') then
			DATA2A_VALID <= '0';
		elsif(ENC_STATE2 = 1) then
			DATA2A_VALID <= '1';	-- encoder pass #3
		else
			DATA2A_VALID <= '0';
		end if;
		if(ENC_RESET = '1') then
			DATA2B_VALID <= '0';
		elsif(ENC_STATE2 = 3) then
			DATA2B_VALID <= CTS6_D;	-- encoder pass #4
		else
			DATA2B_VALID <= '0';
		end if;
	end if;
end process;

			
--// ENCODER SEQUENCER -------------------------
-- Main state is ENC_STATE.
-- Slightly different but related state is ENC_STATE2 for the interleaved sequence encoding.

EVENT1 <= '1' when (ENC_STATE = 0) and (SOF_IN = '1') else '0';
	-- Start collecting and encoding new frame (input + encoder pass #1)
EVENT2 <= '1' when ((ENC_STATE = 1) or (ENC_STATE = 5)) and (CNTR = 0) and (ENC_STATE2 = 0) else '0';
	-- Start encoder pass #3 on interleaved input (concurrent with encoder pass #2 on direct input data)
EVENT3 <= '1' when (ENC_STATE = 4) and (CNTR = 0)  else '0';
	-- Completed encoding natural order input
EVENT4 <= '1' when (ENC_STATE2 = 4) and (CNTR2 = 0)  else '0';
	-- Completed encoding interleaved order input
	
ENC_STATE_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			ENC_STATE <= 0;
			CNTR <= (others => '0');	-- 0 = timer expired
			RPTR1 <= (others => '0');
		elsif(EVENT1 = '1') then
			-- Start collecting and encoding new frame (input + encoder pass #1)
			-- start encoder pass #1: natural order
			ENC_STATE <= 1;
			CNTR <= N-1;
			RPTR1(12) <= not WPTR(12);	-- alternate every frame
		elsif(ENC_STATE = 1) and (CNTR = 0) then
			-- completed encoder pass #1
			-- Do not proceed from state 1 to state 2 until the parallel pass #4 (interleaved order)
			-- is complete.
			if(ENC_STATE2 = 0) then
				-- start initializing circulation state C1
				ENC_STATE <= 2;
				CNTR <= to_unsigned(3, CNTR'length);
			else
				ENC_STATE <= 5;
				-- wait for completion of second core encoder
			end if;
		elsif(ENC_STATE = 5) and (ENC_STATE2 = 0) then
			-- start initializing circulation state C1
			ENC_STATE <= 2;
			CNTR <= to_unsigned(3, CNTR'length);
		elsif(ENC_STATE = 1) then
			if(DATA_IN_VALID = '1')	or ((CRC16_EN = '1') and  (DATA_IN_VALID_D = '0') and (CNTR < 9) and (DATA0_VALID_D = '0'))then
				-- encoder pass #1 
				CNTR <= CNTR - 1;
			end if;
		elsif(ENC_STATE = 2) and (CNTR = 0) then
			if(CTS6 = '1') then
				-- start encoder pass #2: natural order
				ENC_STATE <= 3;
				CNTR <= N-1;
				RPTR1(11 downto 0) <= (others => '0');
			end if;
		elsif(ENC_STATE = 3) and (CNTR = 0) then
			-- gap to get the encoder output tail
			ENC_STATE <= 4;
			CNTR <= to_unsigned(3, CNTR'length);
		elsif(EVENT3 = '1') then
			-- completed encoder pass #2
			-- back to idle
			ENC_STATE <= 0;
		elsif(ENC_STATE = 3) then
			-- encoder pass #2 
			-- regulate flow control during encoding states with outputs
			if(CTS6 = '1') then
				CNTR <= CNTR - 1;
				RPTR1 <= RPTR1 + 1;
			end if;
		elsif(CNTR /= 0) then
			CNTR <= CNTR - 1;
		end if;
	end if;
end process;

-- interleaved sequence encoding
ENC_STATE2_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			ENC_STATE2 <= 0;
			CNTR2 <= (others => '0');	-- 0 = timer expired
			RPTR2 <= (others => '0');
		elsif(EVENT2 = '1') then
			-- all input data saved in interleaved input elastic buffer. 
			-- start encoder pass #3 interleaved order
			-- This is concurrent with encoder pass #2 natural order
			ENC_STATE2 <= 1;
			CNTR2 <= N-1;
			RPTR2(11 downto 0) <= (others => '0');
			RPTR2(12) <= WPTR(12);	-- alternate every frame
		elsif(ENC_STATE2 = 1) and (CNTR2 /= 0) then
			-- encoder pass #3
			CNTR2 <= CNTR2 - 1;
			RPTR2 <= RPTR2 + 1;
		--elsif(ENC_STATE2 = 1) and (CNTR2 = 0) then
		elsif(ENC_STATE2 = 1) then
			-- completed encoder pass #3. Compute & Initialize circulation state C2
			ENC_STATE2 <= 2;
			CNTR2 <= to_unsigned(3, CNTR2'length);
		elsif(ENC_STATE2 = 2) and (CNTR2 = 0) then
			-- core encoder 2 is done with pass #3 always before core encoder 1 is done with pass #2.
			-- Wait for core encoder to be complete (otherwise possible contention with output buffer)
			if(ENC_STATE = 0) and (CTS6 = '1') then
				-- start core encoder 2 pass #4, interleaved order
				ENC_STATE2 <= 3;
				CNTR2 <= N-1;
				RPTR2(11 downto 0) <= (others => '0');
			end if;
		elsif(ENC_STATE2 = 3) and (CNTR2 /= 0) then
			-- core encoder 2 is doing pass #4,
			if(CTS6 = '1') then
				CNTR2 <= CNTR2 - 1;
				RPTR2 <= RPTR2 + 1;
			end if;
--		elsif(ENC_STATE2 = 3) and (CNTR2 = 0) then
		elsif(ENC_STATE2 = 3) then
			-- core encoder 2 has completed pass #4
			-- finishing
			ENC_STATE2 <= 4;
			CNTR2 <= to_unsigned(3, CNTR'length);
		elsif(EVENT4 = '1') then
			-- done encoding. Ready to process next input frame
			ENC_STATE2 <= 0;
		elsif(CNTR2 /= 0) then
			CNTR2 <= CNTR2 - 1;
		end if;
	end if;
end process;


--// ENCODER CORE #1 (NOT INTERLEAVED) ---------------------------------
-- multiplex 
DATA31 <= DATA_IN_D2 when (DATA1A_VALID = '1') else -- encoder pass #1
			DATA1; 	-- encoder pass #2
DATA31A_VALID <= DATA1A_VALID;
DATA31B_VALID <= DATA1B_VALID;

ENC_CORE1_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') or (EVENT1 = '1') then
			-- The encoder is initialised by the state S=0.
			S1 <= (others => '0');
		elsif(ENC_STATE = 2)  and (CNTR = 0) then
			-- Initialize circulation state C1
			-- see S definition in specs (0 is MSb)
			S1 <= C1(0) & C1(1) & C1(2) & C1(3);
		elsif(DATA31A_VALID = '1') or (DATA31B_VALID = '1') then
			S1(0) <= DATA31(0) xor DATA31(1) xor S1(2) xor S1(3);
			S1(1) <= DATA31(1) xor S1(0);
			S1(2) <= S1(1);
			S1(3) <= DATA31(1) xor S1(2);
		end if;
	end if;
end process;

--// ENCODER CORE #2 (INTERLEAVED) ---------------------------------
-- multiplex 
DATA32 <= DATA2; 	-- encoder passes #3 and #4
DATA32A_VALID <= DATA2A_VALID;
DATA32B_VALID <= DATA2B_VALID;

ENC_CORE2_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') or (EVENT2 = '1') then
			-- initialization prior to starting encoder pass #3 on interleaved data
			S2 <= (others => '0');
		elsif(ENC_STATE2 = 2)  and (CNTR = 0) then	
			-- Initialize circulation state C2
			-- see S definition in specs (0 is MSb)
			S2 <= C2(0) & C2(1) & C2(2) & C2(3);
		elsif(DATA32A_VALID = '1') or (DATA32B_VALID = '1') then
			S2(0) <= DATA32(0) xor DATA32(1) xor S2(2) xor S2(3);
			S2(1) <= DATA32(1) xor S2(0);
			S2(2) <= S2(1);
			S2(3) <= DATA32(1) xor S2(2);
		end if;
	end if;
end process;

ENC_CORE2_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then	-- **100316
			DATA4_VALID <= '0';
		else
			DATA4_VALID <= DATA31B_VALID or DATA32B_VALID;	
			-- keep only encoder passes #2 and #4
			-- the other passes are just for tailbiting
		end if;
		
		if(DATA31B_VALID = '1') then
			-- encoder pass #2
			DATA4(0) <= DATA31(0) xor DATA31(1) xor S1(0) xor S1(1) xor S1(2);
			DATA4(1) <= DATA31(0) xor DATA31(1) xor S1(1);
		elsif(DATA32B_VALID = '1') then
			-- encoder pass #4
			DATA4(0) <= DATA32(0) xor DATA32(1) xor S2(0) xor S2(1) xor S2(2);
			DATA4(1) <= DATA32(0) xor DATA32(1) xor S2(1);
		end if;
	end if;
end process;


--// CIRCULATION STATES ---------------------------------
-- save the last encoder states
LAST_STATE_001: process(CLK)
begin
	if rising_edge(CLK) then
		-- direct input
		if(ENC_STATE = 2) and (CNTR = 2) then
			-- Table 7-13: Initial circulation state as a function of last encoder state S and N mod 15
			-- table index 
			CI1 <= to_integer(unsigned(N_MOD15_MINUSONE & S1(0) & S1(1) & S1(2) & S1(3)));
		end if;
		
		-- interleaved input
		if(ENC_STATE2 = 2) and (CNTR2 = 2) then
			-- Table 7-13: Initial circulation state as a function of last encoder state S and N mod 15
			-- table index 
			CI2 <= to_integer(unsigned(N_MOD15_MINUSONE & S2(0) & S2(1) & S2(2) & S2(3)));
		end if;
	
		if(ENC_RESET = '1') then
			C1 <= (others => '0');
		elsif(ENC_STATE = 2)  and (CNTR = 1) then
			-- look-up table
			-- Table 7-13: Initial circulation state as a function of last encoder state S and N mod 15
			C1 <= std_logic_vector(to_unsigned(CIRC_TBL(223-CI1), C1'length));
		end if;

		if(ENC_RESET = '1') then
			C2 <= (others => '0');
		elsif(ENC_STATE2 = 2)  and (CNTR2 = 1) then
			-- look-up table
			-- Table 7-13: Initial circulation state as a function of last encoder state S and N mod 15
			C2 <= std_logic_vector(to_unsigned(CIRC_TBL(223-CI2), C2'length));
		end if;
	
	end if;
end process;

--// PUNCTURING -------------------------
Y_PUNCTURING_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') or (EVENT2 = '1') then
			Y_PUNCTURING <= "0" & Y_PUNCTURING_PATTERN;  -- one spare bit on the left to prevent syntax error below
		elsif(DATA4_VALID = '1') then
			-- shift right
			for I in 0 to 27 loop
				if(I = (unsigned(Y_PUNCTURING_PERIOD)-1)) then
					Y_PUNCTURING(I) <= Y_PUNCTURING(0);
				else
					Y_PUNCTURING(I) <= Y_PUNCTURING(I+1);
				end if;
			end loop;
		end if;
	end if;
end process;

-- number of bits after puncturing is 0,1,or2.
-- pack bits into complete 2-bit word (DATA5)
PACK_AFTER_PUNCTURING_001: process(CLK) 
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') or (EVENT2 = '1') then
			DATA5_VALID <= '0';
			LEFT_OVER <= '0';
			N_PARITY_BITS_local <= (others => '0');
		elsif(DATA4_VALID = '1') then
			-- number of bits after puncturing is 0,1,or2.
			if(Y_PUNCTURING(0) = '1') and (W_PUNCTURING_PATTERN = '1') then
				-- 2 new bits
				if(LEFT_OVER = '1') then
					DATA5(0) <= LEFT_OVER_BIT;
					DATA5(1) <= DATA4(0);	-- punctured Y
					LEFT_OVER_BIT <= DATA4(1);	-- W
				else
					DATA5 <= DATA4;	
				end if;
				DATA5_VALID <= '1';
				N_PARITY_BITS_local <= N_PARITY_BITS_local + 2;
			elsif(Y_PUNCTURING(0) = '1') and (W_PUNCTURING_PATTERN = '0') then
				-- 1 new Y bit
				if(LEFT_OVER = '1') then
					DATA5(0) <= LEFT_OVER_BIT;
					DATA5(1) <= DATA4(0);	-- punctured Y
					DATA5_VALID <= '1';
					LEFT_OVER <= '0';
				else
					LEFT_OVER <= '1';
					LEFT_OVER_BIT <= DATA4(0);	-- punctured Y
					DATA5_VALID <= '0';
				end if;
				N_PARITY_BITS_local <= N_PARITY_BITS_local + 1;
			elsif(Y_PUNCTURING(0) = '0') and (W_PUNCTURING_PATTERN = '1') then
				-- 1 new W bit 
				if(LEFT_OVER = '1') then
					DATA5(0) <= LEFT_OVER_BIT;
					DATA5(1) <= DATA4(1);	-- W
					DATA5_VALID <= '1';
					LEFT_OVER <= '0';
				else
					LEFT_OVER <= '1';
					LEFT_OVER_BIT <= DATA4(1);	-- W
					DATA5_VALID <= '0';
				end if;
				N_PARITY_BITS_local <= N_PARITY_BITS_local + 1;
			else
				DATA5_VALID <= '0';
			end if;
		else
--			if (ENC_STATE = 8) and (CNTR = 2) and (LEFT_OVER = '1') then	-- TODO  LEFT OVER CODE ENC_STATE = 8 ILLEGAL
--				-- one last write at the end of frame for the last leftover bit
--				DATA5(0) <= LEFT_OVER_BIT;
--				DATA5(1) <= '0';	-- pad with zero
--				DATA5_VALID <= '1';
--				LEFT_OVER <= '0';
--			else
				DATA5_VALID <= '0';
--			end if;
		end if;
	end if;
end process;

-- report number of parity bits (for the systematic section only)
N_PARITY_BITS_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			N_PARITY_BITS <= (others => '0');
			N_PARITY_BITS_RDY <= '0';
		elsif(EVENT3 = '1') then
			N_PARITY_BITS <= std_logic_vector(N_PARITY_BITS_local);
			N_PARITY_BITS_RDY <= '1';
		else
			N_PARITY_BITS_RDY <= '0';
		end if;
	end if;
end process;


--// OUTPUT -------------------------f
-- Two (A/B) output buffers are needed because of the concurrent transfers:
-- systematic data for frame k+1 and interleaved encoding pass #4 for frame k.

-- delay RPTR2(MSb) by 7 CLKs. to route parity bits to the proper A/B output buffer
RPTR2MSB_DELAY_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			RPTR2MSB_DELAY(6 downto 0) <= (others => '0');
		else
			RPTR2MSB_DELAY(6 downto 1) <= RPTR2MSB_DELAY(5 downto 0);
			RPTR2MSB_DELAY(0) <= RPTR2(12);
		end if;
	end if;
end process;
RPTR2MSB_D7 <= RPTR2MSB_DELAY(6);

DATA61_VALID <= 	'1' when (DATA_IN_VALID_D = '1') and (WPTR(12) = '0') else
						'1' when (DATA5_VALID = '1') and (RPTR2MSB_D7 = '0') else 
						'0';

DATA62_VALID <= 	'1' when (DATA_IN_VALID_D = '1') and (WPTR(12) = '1') else
						'1' when (DATA5_VALID = '1') and (RPTR2MSB_D7 = '1') else 
						'0';

DATA61 <= 	DATA_IN_D when (DATA_IN_VALID_D = '1') and (WPTR(12) = '0') else 
								DATA5;

DATA62 <= 	DATA_IN_D when (DATA_IN_VALID_D = '1') and (WPTR(12) = '1') else 
								DATA5;
				
						
WPTR61_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') or ((EVENT1 = '1') and (WPTR(12) = '1')) or ((OUTPUT_BUF_READ_SEL = '0') and (EOF71_E = '1')) then
			WPTR61 <= (others => '0');
		elsif(DATA61_VALID = '1') then
			WPTR61 <= WPTR61 + 1;
		end if;
	end if;
end process;

WPTR62_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') or ((EVENT1 = '1') and (WPTR(12) = '0')) or ((OUTPUT_BUF_READ_SEL = '1') and (EOF72_E = '1'))then
			WPTR62 <= (others => '0');
		elsif(DATA62_VALID = '1') then
			WPTR62 <= WPTR62 + 1;
		end if;
	end if;
end process;

-- 2 output elastic buffers (A/B), each large enough to store a full encoded frame at rate 1/3
OUTBUF_001: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => 2,		
	ADDR_WIDTHA => 14,	-- 8000 bits * 3 (rate 1/3) / 2 (dibits)
	DATA_WIDTHB => 2,		 
	ADDR_WIDTHB => 14
)
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	WEA => DATA61_VALID,      
	ADDRA => std_logic_vector(WPTR61),  
	DIA => DATA61,      
	OEA => '0',
	DOA => open,
	CLKB => CLK,
	CSB => '1',
	WEB => '0',
	ADDRB => std_logic_vector(RPTR71),  
	DIB => "00",      
	OEB => '1',
	DOB => DATA71      
);

OUTBUF_002: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => 2,		
	ADDR_WIDTHA => 14,
	DATA_WIDTHB => 2,		 
	ADDR_WIDTHB => 14
)
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	WEA => DATA62_VALID,      
	ADDRA => std_logic_vector(WPTR62),  
	DIA => DATA62,      
	OEA => '0',
	DOA => open,
	CLKB => CLK,
	CSB => '1',
	WEB => '0',
	ADDRB => std_logic_vector(RPTR72),  
	DIB => "00",      
	OEB => '1',
	DOB => DATA72      
);

-- flow control. Aim is to prevent starting a new frame encoding if the opposite output buffer is fully empty.
CTS6 <= 	'0' when ((ENC_STATE = 4) or (ENC_STATE = 0)) and (WPTR(12) = '1') and (BUF61_SIZE /= 0) else
			'0' when ((ENC_STATE = 4) or (ENC_STATE = 0)) and (WPTR(12) = '0') and (BUF62_SIZE /= 0) else
			ARITH_DONE;
			
DATA7_VALID	<= DATA71_VALID or DATA72_VALID;	
SOF7 <= SOF71 or SOF72;
EOF7 <= EOF71 or EOF72;

DATA_OUT <= DATA71 when (DATA71_VALID = '1') else 
				DATA72; --when (DATA72_VALID = '1')  
DATA_OUT_VALID <= DATA7_VALID;
SOF_OUT <= SOF7;
EOF_OUT <= EOF7;
--
-- occupied memory
BUF61_SIZE <= WPTR61 + not(RPTR71);
BUF62_SIZE <= WPTR62 + not(RPTR72);


-- select which output buffer to read from
OUTPUT_BUF_READ_SEL_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ENC_RESET = '1') then
			OUTPUT_BUF_READ_SEL <= '0';
		elsif(EOF71_E = '1') or (EOF72_E = '1') then	-- **100216
			OUTPUT_BUF_READ_SEL <= not OUTPUT_BUF_READ_SEL;
		end if;
	end if;
end process;

-- rptr managment
RPTR71_INC <= RPTR71 + 1;
RPTR72_INC <= RPTR72 + 1;

RPTR71_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		DATA71_VALID <= DATA71_VALID_E;	-- 1 clk delay to read data from ram block
		
		if(ENC_RESET = '1') then
			RPTR71 <= (others => '1');
			DATA71_VALID_E <= '0';
		elsif(EOF71_E = '1') then
			RPTR71 <= (others => '1');
			DATA71_VALID_E <= '0';
		elsif(BUF61_SIZE /= 0) and (OUTPUT_BUF_READ_SEL = '0') and (CTS_IN = '1') then
			DATA71_VALID_E <= '1';
			RPTR71 <= RPTR71_INC;
		else
			DATA71_VALID_E <= '0';
		end if;
	end if;
end process;

SOF71_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		SOF71 <= SOF71_E;
		
		if(ENC_RESET = '1') then
			SOF71_E <= '0';
		elsif(BUF61_SIZE /= 0) and (OUTPUT_BUF_READ_SEL = '0') and (CTS_IN = '1') and (RPTR71_INC = 0) then
			SOF71_E <= '1';
		else
			SOF71_E <= '0';
		end if;
	end if;
end process;


EOF71_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		EOF71 <= EOF71_E;
		
		if(ENC_RESET = '1') then
			EOF71_E <= '0';
		elsif(BUF61_SIZE /= 0) and (OUTPUT_BUF_READ_SEL = '0') and (CTS_IN = '1') and (RPTR71_INC = N_ENC -1) then
			EOF71_E <= '1';
		else
			EOF71_E <= '0';
		end if;
	end if;
end process;

RPTR72_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		DATA72_VALID <= DATA72_VALID_E;	-- 1 clk delay to read data from ram block
		
		if(ENC_RESET = '1') then
			RPTR72 <= (others => '1');
			DATA72_VALID_E <= '0';
		elsif(EOF72_E = '1') then
			RPTR72 <= (others => '1');
			DATA72_VALID_E <= '0';
		elsif(BUF62_SIZE /= 0) and (OUTPUT_BUF_READ_SEL = '1')  and (CTS_IN = '1') then
			DATA72_VALID_E <= '1';
			RPTR72 <= RPTR72_INC;
		else
			DATA72_VALID_E <= '0';
		end if;
	end if;
end process;

SOF72_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		SOF72 <= SOF72_E;
		
		if(ENC_RESET = '1') then
			SOF72_E <= '0';
		elsif(BUF62_SIZE /= 0) and (OUTPUT_BUF_READ_SEL = '1') and (CTS_IN = '1') and (RPTR72_INC = 0) then
			SOF72_E <= '1';
		else
			SOF72_E <= '0';
		end if;
	end if;
end process;

EOF72_GEN: process(CLK) 
begin
	if rising_edge(CLK) then
		EOF72 <= EOF72_E;
		
		if(ENC_RESET = '1') then
			EOF72_E <= '0';
		elsif(BUF62_SIZE /= 0) and (OUTPUT_BUF_READ_SEL = '1') and (CTS_IN = '1') and (RPTR72_INC = N_ENC -1) then
			EOF72_E <= '1';
		else
			EOF72_E <= '0';
		end if;
	end if;
end process;

--//-- TEST POINTS ----------------------------------
TP(1) <= WPTR(12);
TP(2) <= ENC_RESET;
TP(3) <= CTS6;
TP(4) <= '1' when (ENC_STATE = 0) else '0';
TP(5) <= '1' when (ENC_STATE = 1) else '0';
TP(6) <= '1' when (BUF61_SIZE /= 0) else '0';
TP(7) <= '1' when (BUF62_SIZE /= 0) else '0';
TP(8) <= OUTPUT_BUF_READ_SEL;
TP(9) <= CTS_IN;

end Behavioral;

