-------------------------------------------------------------
-- Filename:  CPM_SYMBOL_TIMING_TRACKING.VHD
-- Authors: 
--		Alain Zarembowitch / MSS
-- Version: Rev 0
-- Last modified: 5/9/17

-- Inheritance: 	SYMBOL_TIMING_LOOP5x2CH.VHD Rev1b 11/9/14 (for the tracking part)
--
-- description:  Derotate three consecutive input samples spaced 1/2 a symbol apart and centered around the
-- symbol boundary. Phase rotation is based on the best end state from the VA (best knowledge at this time).
-- The resulting real section is fed into a classical Gardner symbol tracking loop.
--
-- design note: we are going for simplicity in this version, sacrificing noise performance. A more noise resillient 
-- algorithm would be to compute the matched filter for 1/2 symbol early and 1/2 symbol late, at the 
-- expense of more multiplier and rom. (averaging over 5 samples is always better than taking just 1 sample).
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
use work.CPM_MF_PKG.all;	-- defines types for this interface

entity CPM_SYMBOL_TIMING_TRACKING is
	generic (
		SIMULATION: std_logic := '0'
	);
    Port ( 
		--GLOBAL CLOCK, RESET
		CLK   : in  std_logic;
		SYNC_RESET: in std_logic;	
			-- synchronous reset
			-- must use SYNC_RESET after changing the modulation scheme
		
		-- INPUT SAMPLES
		DATA_I_IN  : in  std_logic_vector(9 downto 0);
		DATA_Q_IN  : in  std_logic_vector(9 downto 0);
		SYMBOL_CLKx2_IN: in std_logic;	
			-- received complex baseband signal. Sampled at 2x symbols. 
			-- read when SYMBOL_CLKx2_IN = '1'.
		SYMBOL_CLK_IN   : in  std_logic;
			-- aligned with SYMBOL_CLKx2_IN
		
		BEST_SYMBOL_I: in std_logic_vector(9 downto 0);
		BEST_SYMBOL_Q: in  std_logic_vector(9 downto 0);
		BEST_SYMBOL_CLK: in std_logic;
			-- best known constellation point for the last received symbol 
			-- Latency: 18 CLKs after SYMBOL_CLK_IN
		
		--//-- CONTROLS
		FREEZE_SYMBOL_TIMING: in std_logic;
			-- 1 to freeze.
		ACCELERATED_STLOOP_RESPONSE: in std_logic;
			-- tradeoff speed vs noise (faster speed during acquisition, smoother loop during tracking)
		
		--// OUTPUTS
		NCO_TIMING_CONTROL: out std_logic_vector(31 downto 0);
			-- symbol timing phase error Expressed as 32-bit phase offset. 
			-- Does not include the bias for the nominal sampling rate.
			-- Sign is already changed so as to connect directly to symbol timing NCO without sign inversion.
		NCO_TIMING_CONTROL_CLK: out std_logic;
			-- one CLK pulse indicating when NCO_TIMING_CONTROL information is ready
			-- to be read.

		--// MONITORING
		CS1:  out std_logic_vector(7 downto 0);
		CS1_CLK: out std_logic
			-- accumulated symbol timing phase error (8 MSB)
			-- full scale represents one symbol phase
		);
end entity;


architecture Behavioral of CPM_SYMBOL_TIMING_TRACKING is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
	COMPONENT DELAY2
	 Generic(
		ADDR_WIDTH: integer;	
		DATA_WIDTH: integer;
		NSAMPLES_DELAY: integer
	);
	PORT(
		CLK : IN std_logic;
		DATA_IN : IN std_logic_vector((DATA_WIDTH-1) downto 0);
		SAMPLE_CLK_IN : IN std_logic;          
		DATA_OUT : OUT std_logic_vector((DATA_WIDTH-1) downto 0);
		SAMPLE_CLK_OUT : OUT std_logic
		);
	END COMPONENT;

	component SIGNED_SIN_COS_TBL3 is
		generic (
			NADDR: integer
		);
		port (
			CLK: in std_logic;
			PHASE: in std_logic_vector(NADDR+1 downto 0);
			SIN_OUT: out std_logic_vector(17 downto 0);
			COS_OUT: out std_logic_vector(17 downto 0)
		);
	end component;
	
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
signal DATA1: std_logic_vector(21 downto 0) := (others => '0');
signal DATA2: std_logic_vector(21 downto 0) := (others => '0');
signal DATA2_I: std_logic_vector(9 downto 0) := (others => '0');
signal DATA2_Q: std_logic_vector(9 downto 0) := (others => '0');
signal DATA2_I_D: std_logic_vector(9 downto 0) := (others => '0');
signal DATA2_Q_D: std_logic_vector(9 downto 0) := (others => '0');
signal SYMBOL2_CLKx2: std_logic := '0';
signal SYMBOL2_CLK: std_logic := '0';
signal DATA3E_I: std_logic_vector(9 downto 0) := (others => '0');
signal DATA3E_Q: std_logic_vector(9 downto 0) := (others => '0');
signal DATA3C_I: std_logic_vector(9 downto 0) := (others => '0');
signal DATA3C_Q: std_logic_vector(9 downto 0) := (others => '0');
signal DATA3L_I: std_logic_vector(9 downto 0) := (others => '0');
signal DATA3L_Q: std_logic_vector(9 downto 0) := (others => '0');
signal SYMBOL3_CLK: std_logic := '0';
signal SYMBOL3_CLK_D: std_logic := '0';
signal SYMBOL3_CLK_D2: std_logic := '0';
signal SYMBOL3_CLK_D3: std_logic := '0';
signal SYMBOL3_CLK_D4: std_logic := '0';
signal BEST_SYMBOL_I_D: std_logic_vector(9 downto 0) := (others => '0');
signal BEST_SYMBOL_Q_D: std_logic_vector(9 downto 0) := (others => '0');
signal MULT1A: signed(9 downto 0) := (others => '0');
signal MULT1B: signed(9 downto 0) := (others => '0');
signal MULT2A: signed(9 downto 0) := (others => '0');
signal MULT2B: signed(9 downto 0) := (others => '0');
signal MULT1: signed(19 downto 0) := (others => '0');
signal MULT2: signed(19 downto 0) := (others => '0');
signal DATA4E_I: signed(9 downto 0) := (others => '0');
signal DATA4C_I: signed(9 downto 0) := (others => '0');
signal DATA4L_I: signed(9 downto 0) := (others => '0');
signal SYMBOL4_CLK: std_logic := '0';
 
--//-- TRACKING ------------------------------------------
signal ERROR_A: signed(9 downto 0) := (others => '0');
signal ERROR_BX: signed(10 downto 0) := (others => '0');
signal SYMBOL4_CLK_D: std_logic := '0';
signal SYMBOL4_CLK_D2: std_logic := '0';
signal SYMBOL4_CLK_D3: std_logic := '0';
signal SYMBOL4_CLK_D4: std_logic := '0';
signal STE: signed(20 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1: signed(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL2: signed(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1_ACC: signed(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1_ACC_NEW: signed(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1B: signed(31 downto 0) := (others => '0'); 
signal NCO_TIMING_CONTROL_local: signed(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL_ACC: signed(31 downto 0) := (others => '0');	-- for test purposes

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

-- delay input samples by 18 CLKs until the VA algorithm finds out the best symbol constellation
DATA1 <= SYMBOL_CLK_IN & SYMBOL_CLKx2_IN & DATA_I_IN & DATA_Q_IN;

DELAY2_001: DELAY2 
GENERIC MAP(
	ADDR_WIDTH => 5,	-- max delay 32 samples
	DATA_WIDTH => DATA1'length,	
	NSAMPLES_DELAY => 16	-- 18 CLK latency
)
PORT MAP(
	CLK => CLK,
	DATA_IN => DATA1,
	SAMPLE_CLK_IN => '1',
	DATA_OUT => DATA2,
	SAMPLE_CLK_OUT => open
);

DATA2_I <= DATA2(19 downto 10);
DATA2_Q <= DATA2(9 downto 0);
SYMBOL2_CLKx2 <= DATA2(20);
SYMBOL2_CLK <= DATA2(21);

-- collect Early/Center/Late samples. These samples will be stable for at least 4 CLKs after SYMBOL3_CLK
EARLY_LATE_001: process(CLK)
begin
	if rising_edge(CLK) then
		
		if(SYMBOL2_CLK = '1') then
			DATA2_I_D <= DATA2_I;
			DATA2_Q_D <= DATA2_Q;
		end if;
		if(SYMBOL2_CLKx2 = '1') and (SYMBOL2_CLK = '0') then
			SYMBOL3_CLK <= '1';
			DATA3L_I <= DATA2_I;
			DATA3L_Q <= DATA2_Q;
			DATA3C_I <= DATA2_I_D;
			DATA3C_Q <= DATA2_Q_D;
			DATA3E_I <= DATA3L_I;
			DATA3E_Q <= DATA3L_Q;
			-- realign the best constellation point with DATA3E/C/L prior to derotation
			BEST_SYMBOL_I_D <= BEST_SYMBOL_I;
			BEST_SYMBOL_Q_D <= BEST_SYMBOL_Q;
		else
			SYMBOL3_CLK <= '0';
		end if;
	end if;
end process;

-- derorate DATA3E/C/L and keep only the real component
-- We have 4 CLK to do 6 multiplications and 3 additions -> use two multipliers
ROTATE_001: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL3_CLK_D <= SYMBOL3_CLK;
		SYMBOL3_CLK_D2 <= SYMBOL3_CLK_D;
		SYMBOL3_CLK_D3 <= SYMBOL3_CLK_D2;
		
		if(SYMBOL3_CLK =  '1') then
			MULT1A <= signed(DATA3E_I);
			MULT1B <= signed(BEST_SYMBOL_I_D);
			MULT2A <= signed(DATA3E_Q);
			MULT2B <= signed(BEST_SYMBOL_Q_D);
		elsif(SYMBOL3_CLK_D =  '1') then
			MULT1A <= signed(DATA3C_I);
			MULT1B <= signed(BEST_SYMBOL_I_D);
			MULT2A <= signed(DATA3C_Q);
			MULT2B <= signed(BEST_SYMBOL_Q_D);
		elsif(SYMBOL3_CLK_D2 =  '1') then
			MULT1A <= signed(DATA3L_I);
			MULT1B <= signed(BEST_SYMBOL_I_D);
			MULT2A <= signed(DATA3L_Q);
			MULT2B <= signed(BEST_SYMBOL_Q_D);
		end if;
	end if;
end process;
		
ROTATE_002: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL3_CLK_D4 <= SYMBOL3_CLK_D3;
		
		MULT1 <= MULT1A * MULT1B;
		MULT2 <= MULT2A * MULT2B;
	end if;
end process;

ROTATE_003: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL4_CLK <= SYMBOL3_CLK_D4;
		
		if(SYMBOL3_CLK_D2 =  '1') then
			DATA4E_I <= MULT1(18 downto 9) + MULT2(18 downto 9);
		end if;
		if(SYMBOL3_CLK_D3 =  '1') then
			DATA4C_I <= MULT1(18 downto 9) + MULT2(18 downto 9);
		end if;
		if(SYMBOL3_CLK_D4 =  '1') then
			DATA4L_I <= MULT1(18 downto 9) + MULT2(18 downto 9);
		end if;
	end if;
end process;

--//-- TRACKING ------------------------------------------
-- Inherited from SYMBOL_TIMING_LOOP5x2CH.VHD 

-- compute the error signal: 
-- MAGNITUDE_IN1 * (MAGNITUDE_IN_LATE - MAGNITUDE_IN_EARLY)
ERROR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL4_CLK_D <= SYMBOL4_CLK;
		
		if(SYMBOL4_CLK = '1') then
			ERROR_A <= DATA4C_I;	-- generally large when loop is tracking
			ERROR_BX <= (resize(DATA4L_I,11)) - (resize(DATA4E_I,11));	
				-- extend precision to avoid overflow
		end if;
	end if;
end process;

-- reclock for best timing
-- (sign of error). + when lagging, - when leading
ERROR_GEN_002: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL4_CLK_D2 <= SYMBOL4_CLK_D;
		
		STE <= ERROR_A * ERROR_BX;
	end if;
end process;

LOOP_GAIN_001: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL4_CLK_D3 <= SYMBOL4_CLK_D2;
		
		if(SYMBOL4_CLK_D2 = '1') then
			-- adjust gain here to set the tracking range (+/- 200ppm target)
			NCO_TIMING_CONTROL1(31 downto 28) <= (others => STE(20));	-- sign extension
			NCO_TIMING_CONTROL1(27 downto 7) <= STE(20 downto 0);
			NCO_TIMING_CONTROL1(6 downto 0) <= (others => '0');
--			NCO_TIMING_CONTROL1(31 downto 24) <= (others => STE(20));	-- sign extension
--			NCO_TIMING_CONTROL1(23 downto 3) <= STE(20 downto 0);
--			NCO_TIMING_CONTROL1(2 downto 0) <= (others => '0');
		end if;
	end if;
end process;

-- 2nd order loop  
-- scale 2nd order loop (divide by 4096)  
-- Note: resulting overshoot depends on the signal delay through the filters. The longer the delays, the 
-- smaller the 2nd order loop gain should be to avoid major riging.
NCO_TIMING_CONTROL1B(15 downto 0) <= 	STE(20 downto 5);
NCO_TIMING_CONTROL1B(31 downto 16) <= (others => STE(20));

NCO_TIMING_CONTROL1_ACC_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYMBOL4_CLK_D3 = '1') then
			NCO_TIMING_CONTROL1_ACC_NEW <= NCO_TIMING_CONTROL1_ACC + NCO_TIMING_CONTROL1B;
		end if;
	end if;
end process; 

NCO_TIMING_CONTROL1_ACC_GEN_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') or (ACCELERATED_STLOOP_RESPONSE = '1') then  
			-- use above statement to disable 2nd order branch
			NCO_TIMING_CONTROL1_ACC <= (others => '0');
		elsif(SYMBOL4_CLK_D3 = '1') then
			-- limit 2nd order loop changes to +/- 1/128th of a symbol per symbol
			if(NCO_TIMING_CONTROL1_ACC_NEW(31) = '0') and (NCO_TIMING_CONTROL1_ACC_NEW(30 downto 24) /= "0000000") then
				-- reached ceiling
				NCO_TIMING_CONTROL1_ACC <= x"00FFFFFF";
			elsif(NCO_TIMING_CONTROL1_ACC_NEW(31) = '1') and (NCO_TIMING_CONTROL1_ACC_NEW(30 downto 24) /= "1111111") then
				-- reached floor
				NCO_TIMING_CONTROL1_ACC <= x"FF000000";
			else
				NCO_TIMING_CONTROL1_ACC <= NCO_TIMING_CONTROL1_ACC_NEW;
			end if;
		end if;	
	end if;
end process; 

NCO_TIMING_CONTROL2 <= NCO_TIMING_CONTROL1_ACC;

-- combine 1st order and 2nd order
NCO_TIMING_CONTROL_001: process(CLK)
begin
	if rising_edge(CLK) then
		SYMBOL4_CLK_D4 <= SYMBOL4_CLK_D3;
		
		if(SYMBOL4_CLK_D3 = '1') then
			NCO_TIMING_CONTROL_local <= NCO_TIMING_CONTROL1 +  NCO_TIMING_CONTROL2;
		end if;	
	end if;
end process; 

NCO_TIMING_CONTROL_CLK	<= SYMBOL4_CLK_D4 when (FREEZE_SYMBOL_TIMING = '0') else '0';
NCO_TIMING_CONTROL <= std_logic_vector(-NCO_TIMING_CONTROL_local);	-- send to output (after changing the sign for the NCO control)
--NCO_TIMING_CONTROL <= (others => '0');  -- open loop


-- For test purposes, accumulate the error.
-- Optimized out by synthesis tool. 
NCO_TIMING_CONTROL_ACC_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYMBOL4_CLK_D4 = '1') and ((SIMULATION = '0') or (NCO_TIMING_CONTROL_local(0) /= 'X')) then
			NCO_TIMING_CONTROL_ACC <= NCO_TIMING_CONTROL_ACC + NCO_TIMING_CONTROL_local;
		end if;	
	end if;
end process; 

-- to ComScope
CS1 <= std_logic_vector(NCO_TIMING_CONTROL_ACC(31 downto 24));
CS1_CLK <= SYMBOL4_CLK_D4;

end Behavioral;
