-------------------------------------------------------------
--	Filename:  SYMBOL_TIMING_LOOP5.VHD 
-- Author: Alain Zarembowitch / MSS
--	Version: 2
-- Last modified: 10/17/17
-- Inheritance: 	COM-1202 SYMBOL_TIMING_LOOP4.VHD Rev4 1/3/10
--
-- description:  Symbol bit timing loop.
-- First order loop. (i.e memoryless)
-- Operates at 4 samples/symbol. Gain is set to accommodate a bit timing error of
-- at least +/- 200ppm. Energy maximization algorithm.
--
-- Rev 2 10/25/12 AZ scale 2nd order loop (divide by 4096)  
--
-- 10/17/17 minor tweaks
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;	

entity SYMBOL_TIMING_LOOP5 is 
	generic (
		SIMULATION: std_logic := '0'
			-- 1 during simulation. 
	);
	port( 
	--// GLOBAL CLOCKS
   CLK : in std_logic;				-- System clock for this FPGA
	SYNC_RESET: in std_logic;		-- Synchronous reset active high (use only if 2nd order loop, otherwise ignore)

  	--// INPUTS
	SYMBOL_CLK_IN: in std_logic;
	SYMBOL_CLKx2_IN: in std_logic;
		-- Input samples are at x1 and x2 symbol rate.
	DATA_IN: in std_logic_vector(17 downto 0);
		-- input sample. 2's complement. aligned with SAMPLE_CLK_IN


	FREEZE_SYMBOL_TIMING: in std_logic;
		-- 1 to freeze.
	LOOP_GAIN: in std_logic_vector(7 downto 0);
		-- in order to tweak the loop gain. Normally fixed after optimization. 
		-- Recommended value: x01 (warning: higher gains could be unstable).
		-- Bit 7: '1' to enable 2nd order loop (recommended)

	--// 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 BEHAVIOR of SYMBOL_TIMING_LOOP5 is 
------------------------------------------------------
--			COMPONENTS
------------------------------------------------------ 
	component MULT18X18SIGNED is port( 
		A	: in STD_LOGIC_VECTOR (17 downto 0);
		 B	: in STD_LOGIC_VECTOR (17 downto 0);
		P : out STD_LOGIC_VECTOR (35 downto 0) );

	end component;

	component SIM2OUTFILE is
		 Generic (
			FILENAME: string
		 );
		 Port ( 
			--// CLK, RESET
			CLK: in std_logic;
				-- Reference clock
			DATA1: in std_logic_vector(11 downto 0);
			DATA2: in std_logic_vector(11 downto 0);
			DATA3: in std_logic_vector(11 downto 0);
			SAMPLE_CLK_IN: in std_logic
				-- DATA_IN is read at the rising edge of CLK when SAMPLE_CLK_IN = '1'

	);
	END COMPONENT;
------------------------------------------------------
--			ATTRIBUTES
------------------------------------------------------  
------------------------------------------------------
--			SIGNALS
------------------------------------------------------  
signal DATA_IN2: std_logic_vector(17 downto 0) := (others => '0');
signal DATA_IN_CENTER: std_logic_vector(17 downto 0) := (others => '0');
signal DATA_IN_EARLY: std_logic_vector(17 downto 0) := (others => '0');
signal DATA_IN_LATE: std_logic_vector(17 downto 0) := (others => '0');
signal SAMPLE1_CLK: std_logic := '0';

signal ERROR_A: std_logic_vector(17 downto 0) := (others => '0');
signal ERROR_BE: std_logic_vector(18 downto 0) := (others => '0');
signal SAMPLE1_CLK_D: std_logic := '0';

signal ERROR_AB: std_logic_vector(35 downto 0) := (others => '0');
signal ERROR_AB_D: std_logic_vector(31 downto 0) := (others => '0');
signal STE: std_logic_vector(31 downto 0) := (others => '0'); 
signal SAMPLE1_CLK_D2: std_logic := '0';

signal SAMPLE1_CLK_D3: std_logic := '0';
signal SAMPLE1_CLK_D4: std_logic := '0';
signal NCO_TIMING_CONTROL_ACC: std_logic_vector(31 downto 0) := (others => '0');	-- for test purposes
signal NCO_TIMING_CONTROL1: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL2: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL_LOCAL: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1_ACC: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1_ACC_NEW: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL1B: std_logic_vector(31 downto 0) := (others => '0'); 
------------------------------------------------------
--			IMPLEMENTATION
------------------------------------------------------       
begin

-- avoid simulation infinite loop
DATA_IN2 <= (others => '0') when ((DATA_IN(DATA_IN'left) /= '0') and (DATA_IN(DATA_IN'left) /= '1')) else DATA_IN;


-- identify and save the magnitude signals at 4 locations within a symbol
SAVE_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYMBOL_CLK_IN = '1') then
			DATA_IN_CENTER <= DATA_IN2; 	-- sample(4i)  -- optimum sampling instant
		end if;
		if(SYMBOL_CLKx2_IN = '1') and (SYMBOL_CLK_IN = '0')  then
			DATA_IN_EARLY <= DATA_IN2;	-- sample(4i-2)
			DATA_IN_LATE <= DATA_IN_EARLY;	-- sample(4i+2)
			SAMPLE1_CLK <= '1';				-- all 3 magnitude samples collected... proceed.
		else 
			SAMPLE1_CLK <= '0';
		end if;
	end if;
end process;


-- compute the error signal: 
-- MAGNITUDE_IN1 * (MAGNITUDE_IN_LATE - MAGNITUDE_IN_EARLY)
ERROR_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE1_CLK_D <= SAMPLE1_CLK;
		if(SAMPLE1_CLK = '1') then
			ERROR_A <= DATA_IN_CENTER;	-- generally large when loop is tracking
			ERROR_BE <= (DATA_IN_LATE(17) & DATA_IN_LATE) - (DATA_IN_EARLY(17) & DATA_IN_EARLY);	
				-- extend precision to avoid overflow
				-- generally small and negative/positive when the loop is tracking
		end if;
	end if;
end process;

MULT18X18SIGNED_001: MULT18X18SIGNED port map(
	A => ERROR_A,
	B => ERROR_BE(18 downto 1),
	P => ERROR_AB
	);


-- reclock for best timing
-- (sign of error). + when lagging, - when leading
ERROR_GEN_002: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE1_CLK_D2 <= SAMPLE1_CLK_D;
		ERROR_AB_D <= ERROR_AB(35 downto 4);
	end if;
end process;

STE <= ERROR_AB_D;

-- Loop gain (comment out the mux once the loop optimization is complete).
LOOP_GAIN_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE1_CLK_D3 <= SAMPLE1_CLK_D2;
		if(SAMPLE1_CLK_D2 = '1') then
			if(LOOP_GAIN(3 downto 0) = 0) then
				-- nominal
				NCO_TIMING_CONTROL1(28 downto 0) <= 	STE(31 downto 3);
				NCO_TIMING_CONTROL1(31 downto 29) <= (others => STE(31));
			elsif(LOOP_GAIN(3 downto 0) = 1) then
				NCO_TIMING_CONTROL1(29 downto 0) <= 	STE(31 downto 2);
				NCO_TIMING_CONTROL1(31 downto 30) <= (others => STE(31));
			elsif(LOOP_GAIN(3 downto 0) = 2) then
				NCO_TIMING_CONTROL1(30 downto 0) <= 	STE(31 downto 1);
				NCO_TIMING_CONTROL1(31 downto 31) <= (others => STE(31));
			elsif(LOOP_GAIN(3 downto 0) = 3) then
				NCO_TIMING_CONTROL1(31 downto 0) <= 	STE(31 downto 0);
			elsif(LOOP_GAIN(3 downto 0) = 4) then
				NCO_TIMING_CONTROL1(31 downto 0) <= 	STE(30 downto 0) & "0";
			elsif(LOOP_GAIN(3 downto 0) = 5) then
				NCO_TIMING_CONTROL1(31 downto 0) <= 	STE(29 downto 0) & "00";
			elsif(LOOP_GAIN(3 downto 0) = 6) then
				NCO_TIMING_CONTROL1(31 downto 0) <= 	STE(28 downto 0) & "000";

--			elsif(LOOP_GAIN(3 downto 0) = 4) then
--				NCO_TIMING_CONTROL1(24 downto 0) <= 	STE(31 downto 7);
--				NCO_TIMING_CONTROL1(31 downto 25) <= (others => STE(31));
--			elsif(LOOP_GAIN(3 downto 0) = 5) then
--				NCO_TIMING_CONTROL1(22 downto 0) <= 	STE(31 downto 9);
--				NCO_TIMING_CONTROL1(31 downto 23) <= (others => STE(31));
			end if;
		end if;
	end if;
end process;

-- 2nd order loop  
-- scale 2nd order loop (divide by 4096)  10/25/12
-- 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(18 downto 0) <= 	STE(31 downto 13);
NCO_TIMING_CONTROL1B(31 downto 19) <= (others => STE(31));

NCO_TIMING_CONTROL1_ACC_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(SAMPLE1_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 (LOOP_GAIN(7) = '0') then  
			-- use above statement to disable 2nd order branch
			NCO_TIMING_CONTROL1_ACC <= (others => '0');
		elsif(SAMPLE1_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
		SAMPLE1_CLK_D4 <= SAMPLE1_CLK_D3;
		if(SAMPLE1_CLK_D3 = '1') then
			NCO_TIMING_CONTROL_LOCAL <= NCO_TIMING_CONTROL1 +  NCO_TIMING_CONTROL2;
		end if;	
	end if;
end process; 

NCO_TIMING_CONTROL_CLK	<= SAMPLE1_CLK_D4 when (FREEZE_SYMBOL_TIMING = '0') else '0';
NCO_TIMING_CONTROL <= NCO_TIMING_CONTROL_LOCAL;	-- send to output (after changing the sign for the NCO control)
-- TEST TEST TEST
--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(SIMULATION = '1') and (NCO_TIMING_CONTROL_LOCAL(31) = 'X') then
			NCO_TIMING_CONTROL_ACC <= (others => '0');
		elsif(SAMPLE1_CLK_D4 = '1') then
			NCO_TIMING_CONTROL_ACC <= NCO_TIMING_CONTROL_ACC + NCO_TIMING_CONTROL_LOCAL;
		end if;	
	end if;
end process; 

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

--// SIM2OUTFILE --------------------------------------------
-- simulation. send to a tab-delimited text file for subsequent (Matlab) plot.
--
SIM2OUTFILE_X: if(SIMULATION = '1') generate
--	SIM2OUTFILE_001: SIM2OUTFILE 
--	generic map(
--		FILENAME => "st5.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => NCO_TIMING_CONTROL_ACC(31 downto 20),
--		DATA2 => DATA_IN2(17 downto 6),
--		DATA3 => STE(31 downto 20),
--		SAMPLE_CLK_IN => SYMBOL_CLK_IN
--	);
end generate;

end BEHAVIOR;
