-------------------------------------------------------------
-- MSS copyright 2016
-- Filename:  PERMUTATION_TABLE.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: Rev 0
-- Last modified: 10/20/16

-- Inheritance: 	N/A
--
-- description:  Generate permutation and inverse permutation tables
-------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity PERMUTATION_TABLE is
    Port ( 
		--GLOBAL CLOCKS
		CLK: std_logic;
		SYNC_RESET: in std_logic;	-- unused here.
		
		-- 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, 5/6
		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
			
		-- INDICES
		J_IN: in std_logic_vector(11 downto 0);
		JPERM_OUT: out std_logic_vector(11 downto 0);
			-- permutated index output, 1 CLK latency after natural index input J_IN

		JPERM_IN: in std_logic_vector(11 downto 0);
		J_OUT: out std_logic_vector(11 downto 0);
			-- natural index output, 1 CLK latency after permuated index input JPERM_IN
		
		
		-- Monitoring
		TABLE_READY: out std_logic
			-- '0' during initialization


  );
end entity;

architecture Behavioral of PERMUTATION_TABLE is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
 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 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 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 ARITH_DONE_D : std_logic := '0';

signal J: unsigned(11 downto 0) := (others => '0');
signal J_INC: unsigned(11 downto 0) := (others => '0');
signal JPERM: unsigned(11 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 WEA : std_logic := '0';

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

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

N <= unsigned(BURST_PAYLOAD_SIZE & "00");	-- number of N couples (K/2)

-- 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;

	
-- compute the permutation index
J_INC <= J + 1;
PERMUTATION_001: process(CLK)
begin
	if rising_edge(CLK) then
		ARITH_DONE_D <= ARITH_DONE;
		
		if(SYNC_RESET = '1') or (ARITH_DONE = '0') then
			TABLE_READY <= '0';
			J <= (others => '0');
			WEA <= '0';
		elsif(ARITH_DONE = '1') and (ARITH_DONE_D = '0') then
			J <= (others => '0');
			-- initialize the permutated index (divided into 4 groups)
			I0 <= IPI0 + unsigned("0000" & P & "00");	-- add 4*P
			I1 <= "0" & IPI1;
			I2 <= "0" & IPI2;
			I3 <= "0" & IPI3;
			JPERM <= IPI0;
			WEA <= '1';
		elsif(J_INC /= N) then
			J <= J_INC;
			
			case(J(1 downto 0)) is
				when "00" => JPERM <= I1(11 downto 0);
				when "01" => JPERM <= I2(11 downto 0);
				when "10" => JPERM <= I3(11 downto 0);
				when others => JPERM <= 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(J(1 downto 0) = "00") then
				I1 <= I1 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I1 >= N) then
				I1 <= I1 - N;
			end if;
			
			if(J(1 downto 0) = "01") then
				I2 <= I2 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I2 >= N) then
				I2 <= I2 - N;
			end if;
			
			if(J(1 downto 0) = "10") then
				I3 <= I3 + unsigned("0000" & P & "00");	-- add 4*P
			elsif(I3 >= N) then
				I3 <= I3 - N;
			end if;

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

		else
			-- done initializing the table
			TABLE_READY <= '1';
			WEA <= '0';
		end if;
	end if;
end process;

-- save permutated and inverse permutated indices in table
PERM_TBL_001: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => 12,		
	ADDR_WIDTHA => 12,
	DATA_WIDTHB => 12,		 
	ADDR_WIDTHB => 12
)
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	WEA => WEA,      
	ADDRA => std_logic_vector(J),  
	DIA => std_logic_vector(JPERM),      
	OEA => '0',
	DOA => open,
	CLKB => CLK,
	CSB => '1',
	WEB => '0',
	ADDRB => J_IN,  
	DIB => (others => '0'),      
	OEB => '1',
	DOB => JPERM_OUT      
);

INV_PERM_TBL_001: BRAM_DP2 
GENERIC MAP(
	DATA_WIDTHA => 12,		
	ADDR_WIDTHA => 12,
	DATA_WIDTHB => 12,		 
	ADDR_WIDTHB => 12
)
PORT MAP(
	CLKA => CLK,
	CSA => '1',
	WEA => WEA,      
	ADDRA => std_logic_vector(JPERM),  
	DIA => std_logic_vector(J),      
	OEA => '0',
	DOA => open,
	CLKB => CLK,
	CSB => '1',
	WEB => '0',
	ADDRB => JPERM_IN,  
	DIB => (others => '0'),      
	OEB => '1',
	DOB => J_OUT      
);


end Behavioral;

