----------------------------------------------
-- MSS copyright 1999-2015
-- Filename: CARRIER_TRACKING.VHD
-- VHDL code for Costas carrier tracking loop
-- Author: Alain Zarembowitch / MSS
-- Edit date:  1/30/15
-- Revision: 9
-- Inheritance: COSTAS1B.VHD 4-2-04 rev6, CARRIER_TRACKING.vhd  COM_1202  3/5/09
--
-- Acquisition and tracking circuit for carrier frequency
-- (Costas tracking loop).	
-- 
-- Rev 2. 5-24-07 AZ 
-- Minimized truncation. 
--
-- Rev 3 7/6/08 AZ
-- Optimized loop gain
-- Switched to unsigned library?
-- 
-- Rev 4 8/11/11 AZ
-- Minor: added signal initialization for simulation.
--
-- Rev 5 4/20/12 AZ
-- Reduced phase computation to 20-bit
--
-- Rev 6 9/22/12 AZ
-- Added simulation capture 
-- Moved AFC algorithm to AFC.VHD (better algorithm)
--
-- Rev 7 3/28/13 AZ
-- initialize at threshold (to avoid a prolongued period of 'locked' while the average is being computed
-- 2nd order 1/512 instead of 1/2048
-- + more lock threshold options at lower SNRs
-- + pipeline threshold
--
-- Rev 8 4/13/13 AZ
-- increased PHASE_ERROR_ACC to prevent overflow
--
-- Rev 9 1/30/15 AZ
-- Modified for Vivado
----------------------------------------------      
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity CARRIER_TRACKING 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;
	
	--// INPUTS
   OPTIMUM_SAMPLING_CLK: in std_logic;
     	-- One CLK long pulse indicating that the input phase error
     	-- is associated with the optimum sampling instant. 
	PHASE_ERROR: in std_logic_vector(10 downto 0);
		-- 2's complement phase error.
		-- Read at the rising edge of CLK when OPTIMUM_SAMPLING_CLK = '1'	
		-- Expected range: 360deg = 2048
	LOOP_GAIN: in std_logic_vector(1 downto 0);
		-- 00 = x1 nominal
		-- 01 = x2
		-- 10 = x4
		-- 11 = x8
		-- for test purposes. Generally commented out within.
	ACCELERATED_CT_RESPONSE: in std_logic;
		-- '1' to clear all memory-based loops (2nd order PLL), for example when external AFC loop is enabled during acquisition
		-- Also temporarily increase the 1st order PLL gain (more noisy, but faster acquisition)
		-- Does not reset rms phase error averaging.
	LOCK_THRESHOLD: in std_logic_vector(3 downto 0);
		-- select the rms phase error threshold based on which the carrier tracking lock 
		-- status will be generated
		-- 0 = 1 deg
		-- 1 = 2 deg
		-- 2 = 5 deg
		-- 3 = 10 deg
		-- 4 = 15 deg
		-- 5 = 20 deg
		-- 6 = 25 deg
		-- 7 = 30 deg
		-- 8 = 35 deg
		-- 9 = 40 deg
		-- 10 = 45 deg

	--// OUTPUTS
	NCO_CONTROL: out std_logic_vector(19 downto 0);
		-- signed 2's complement. Frequency error.
		-- updated once per symbol. 
	NCO_CONTROL_CLK: out std_logic;
		-- update NCO once per symbol.
		-- Latency is 2 CLKs after OPTIMUM_SAMPLING_CLK

	--// MONITORING
	CARRIER_LOCK: out std_logic;
		-- flag indicating with some degree of certainty that the carrier tracking loop is in lock.
		-- '1' for in lock, '0' for out of lock.  Averaging over 1024 symbols approx.
	CS1:  out std_logic_vector(7 downto 0);
	CS1_CLK: out std_logic;
	CS2:  out std_logic_vector(7 downto 0);
	CS2_CLK: out std_logic;
	CS3:  out std_logic_vector(7 downto 0);
	CS3_CLK: out std_logic
);
end entity;  

architecture behavioral of CARRIER_TRACKING 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;

-----------------------------------------------------------------
-- SIGNALS
-----------------------------------------------------------------
-- Suffix _D denotes one CLK clock delayed version of the name with the same name
-- Suffix _N denotes the inverted (not) version of the net with the same name
-- Suffix _X denotes the precision extended version of the net with the same name
signal PHASE_ERROR_X: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_X_D: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_ACC: std_logic_vector(26 downto 0) := (others => '0');   -- set initial offset for simulation

signal LOOP_ERROR1: std_logic_vector(19 downto 0) := (others => '0');
signal LOOP_ERROR2: std_logic_vector(19 downto 0) := (others => '0');
signal OPTIMUM_SAMPLING_CLK_D: std_logic := '0';	
signal OPTIMUM_SAMPLING_CLK_D2: std_logic := '0';				
signal OPTIMUM_SAMPLING_CLK_D3: std_logic := '0';				
signal PHASE_ERROR_D: std_logic_vector(10 downto 0) := (others => '0');
signal PHASE_ERROR_X2: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_ACC2: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_ACC2_DIV512: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_MINUS_BIAS: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_MINUS_BIAS_D: std_logic_vector(19 downto 0) := (others => '0');
signal PHASE_ERROR_SQUARE: std_logic_vector(35 downto 0) := (others => '0');
signal PHASE_ERROR_SQUARE_D: std_logic_vector(35 downto 0) := (others => '0');
signal PHASE_ERROR_SQUARE_ACC: std_logic_vector(29 downto 0) := (others => '0');
signal PHASE_ERROR_SQUARE_ACC_DIV512: std_logic_vector(29 downto 0) := (others => '0');
signal PHASE_ERROR_SQUARE_THRESHOLD: std_logic_vector(20 downto 0) := (others => '0');
signal PHASE_ERROR_SQUARE_THRESHOLD_D: std_logic_vector(20 downto 0) := (others => '0');
signal CARRIER_LOCK_LOCAL: std_logic := '0';
-----------------------------------------------------------------
-- IMPLEMENTATION
-----------------------------------------------------------------
begin   

--// scale phase error, depending on the loop gain.
-- Different loop gain under user control.
-- Gives the user the control to tradeoff phase noise and thermal noise.
-- Also increases the frequency acquisition range.




-- Nominal tracking loop gain is optimized for a maximum BER performance degradation < 0.5dB 


-- Note: increasing the loop gain may cause instabilities.
-- See second gain adjustment below (LOOP_ERROR1/2/3).
--LOOP_GAIN_001: process(LOOP_GAIN, PHASE_ERROR)
--begin
-- 	case(LOOP_GAIN) is
--		when "00" => 	-- nominal loop gain
--			PHASE_ERROR_X <= sxt(PHASE_ERROR,20);
--		when "01" => 	-- x2 loop gain
--			PHASE_ERROR_X <= sxt(PHASE_ERROR & "0",20);
--		when "10" => 	-- x4 loop gain
--			PHASE_ERROR_X <= sxt(PHASE_ERROR & "00",20);
--		when others  => 	
--		--when "11" => 	-- x8 loop gain
--			PHASE_ERROR_X <= sxt(PHASE_ERROR & "000",20);
--	end case;
--end process;

LOOP_GAIN_001: process(ACCELERATED_CT_RESPONSE, PHASE_ERROR)
begin
	if(ACCELERATED_CT_RESPONSE = '1') then
		-- x4 loop gain during acquisition
		PHASE_ERROR_X <= sxt(PHASE_ERROR & "00",20);
	else
		-- nominal loop gain
		PHASE_ERROR_X <= sxt(PHASE_ERROR,20);	
	end if;
end process;

-- reclock to realign with other two branches
RECLOCK_001: process(CLK, PHASE_ERROR_X)
begin       
	if rising_edge(CLK) then
		OPTIMUM_SAMPLING_CLK_D <= OPTIMUM_SAMPLING_CLK;

		if (OPTIMUM_SAMPLING_CLK = '1') then
			PHASE_ERROR_X_D <= PHASE_ERROR_X;
			PHASE_ERROR_D <= PHASE_ERROR;
		end if;
	end if;
end process;
 
--// accumulated phase error (2nd order carrier tracking loop)
-- Accumulate the phase error. 26-bit accumulator
PHASE_ERROR_ACCUMULATE_001: process(CLK)
begin       
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			-- clear the accumulated phase error (i.e. frequency memory) 
			PHASE_ERROR_ACC <= (others => '0');
		elsif(OPTIMUM_SAMPLING_CLK = '1') and (ACCELERATED_CT_RESPONSE = '1')  then
			-- progressively unwind the frequency memory (over 512 symbols) when turning ACCELERATED_CT_RESPONSE on 
			PHASE_ERROR_ACC <= PHASE_ERROR_ACC - sxt(PHASE_ERROR_ACC(26 downto 9),PHASE_ERROR_ACC'length);
		elsif (OPTIMUM_SAMPLING_CLK = '1') and ((SIMULATION = '0') or (PHASE_ERROR_X(0) /= 'X')) then
			-- optimum sampling instant. Prevent X propagation in the accumulator.
			-- freeze accumulated phase error (i.e. frequency memory) when loop is unlocked.
			PHASE_ERROR_ACC <= PHASE_ERROR_ACC + sxt(PHASE_ERROR_X, PHASE_ERROR_ACC'length);
	 	end if;
	end if;
end process;


-- Adjust the loop gain here. Two different branches: 
-- (a) phase error
-- (b) accumulated phase error (1/512 coeff)
LOOP_ERROR1 <= PHASE_ERROR_X_D(17 downto 0) & "00";  -- truncated but cannot possibly overflow
LOOP_ERROR2 <= PHASE_ERROR_ACC(26 downto 7); 

-- Sum all three branches
-- Add test mode (open loop)
SUM_001: process(CLK)
begin       
	if rising_edge(CLK) then
		NCO_CONTROL_CLK <= OPTIMUM_SAMPLING_CLK_D;

		if(OPTIMUM_SAMPLING_CLK_D = '1') then
			NCO_CONTROL <= LOOP_ERROR1 + LOOP_ERROR2;	-- 2nd order 
		end if;
	end if;
end process;


--// LOCK STATUS
-- Lock status is based on the rms phase error
-- The mean(phase_error) can be non-zero when the AFC is enabled (2nd order loop is disabled). Therefore, we have to 
-- compute the average(phase_error) and subtract it before squaring. 
-- Averaging is performed over 512 symbols.

-- precision extension to 20 bit before the leaky integrator
PHASE_ERROR_X2 <= sxt(PHASE_ERROR_D, PHASE_ERROR_X2'length);

-- Average phase error (mean)
PHASE_ERROR_ACC2_DIV512 <= sxt(PHASE_ERROR_ACC2(19 downto 9), PHASE_ERROR_ACC2_DIV512'length);

-- Remove the mean before squaring for computing the variance.
PHASE_ERROR_MINUS_BIAS <= PHASE_ERROR_X2 - PHASE_ERROR_ACC2_DIV512;

-- Average the phase error
LOCK_STATUS_001: process(CLK)
begin       
	if rising_edge(CLK) then
		OPTIMUM_SAMPLING_CLK_D2 <= OPTIMUM_SAMPLING_CLK_D;

		if(OPTIMUM_SAMPLING_CLK_D = '1') and ((SIMULATION = '0') or (PHASE_ERROR_MINUS_BIAS(0) /= 'X')) then
			-- Prevent X propagation in the accumulator.
			PHASE_ERROR_ACC2 <= PHASE_ERROR_ACC2 +  PHASE_ERROR_MINUS_BIAS;
			PHASE_ERROR_MINUS_BIAS_D <= PHASE_ERROR_MINUS_BIAS; 	-- reclock for timing before squaring.
		end if;
	end if;
end process;

-- compute phase error ^2
MULT18X18SIGNED_001: MULT18X18SIGNED port map(
	A => PHASE_ERROR_MINUS_BIAS_D(17 downto 0),
	B => PHASE_ERROR_MINUS_BIAS_D(17 downto 0),
	P => PHASE_ERROR_SQUARE  -- format 8.10*8.10 = 16.20
);

-- reclock for timing 
LOCK_STATUS_002: process(CLK)
begin       
	if rising_edge(CLK) then
		OPTIMUM_SAMPLING_CLK_D3 <= OPTIMUM_SAMPLING_CLK_D2;

		if(OPTIMUM_SAMPLING_CLK_D2 = '1') then
			PHASE_ERROR_SQUARE_D <= PHASE_ERROR_SQUARE;  	
		end if;
	end if;
end process;

-- Leaky integrator
-- Averages phase error over 512 successive symbols
PHASE_ERROR_SQUARE_ACC_DIV512(20 downto 0) <= PHASE_ERROR_SQUARE_ACC(29 downto 9);
PHASE_ERROR_SQUARE_ACC_DIV512(29 downto 21) <= (others => '0');

LOCK_STATUS_003: process(CLK)
begin       
	if rising_edge(CLK) then
		PHASE_ERROR_SQUARE_THRESHOLD_D <= PHASE_ERROR_SQUARE_THRESHOLD;
		
		if(SYNC_RESET = '1') then
			-- initialize at threshold (to avoid a prolongued period of 'locked' while the average is being computed
			PHASE_ERROR_SQUARE_ACC <= PHASE_ERROR_SQUARE_THRESHOLD_D & "000000000";
		elsif(OPTIMUM_SAMPLING_CLK_D3 = '1') then
			PHASE_ERROR_SQUARE_ACC <= PHASE_ERROR_SQUARE_ACC +  PHASE_ERROR_SQUARE_D(29 downto 0) - PHASE_ERROR_SQUARE_ACC_DIV512;
		end if;
	end if;
end process;

-- theshold comparison for detecting lock 
-- Units: (angle in deg / 360 * 2048)^2
LOCK_STATUS_004: process(LOCK_THRESHOLD)
begin  
	case (LOCK_THRESHOLD) is
		when "0000" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000000000000100000";	-- 1 deg threshold (squared)	
		when "0001" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000000000010000001";	-- 2 deg ^2
		when "0010" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000000001100101001";	-- 5 deg ^2
		when "0011" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000000110010100100";	-- 10 deg ^2
		when "0100" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000001110001110010";	-- 15 deg ^2
		when "0101" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000011001010010001";	-- 20 deg ^2
		when "0110" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000100111100000011";	-- 25 deg ^2
		when "0111" => PHASE_ERROR_SQUARE_THRESHOLD <= "000000111000111000100";	-- 30 deg ^2
		when "1000" => PHASE_ERROR_SQUARE_THRESHOLD <= "000001001101011011101";	-- 35 deg ^2
		when "1001" => PHASE_ERROR_SQUARE_THRESHOLD <= "000001100101001000101";	-- 40 deg ^2
		when others => PHASE_ERROR_SQUARE_THRESHOLD <= "000010000000000000000";	-- 45 deg ^2
	end case;
end process;


LOCK_STATUS_005: process(CLK)
begin       
	if rising_edge(CLK) then
		if(PHASE_ERROR_SQUARE_ACC_DIV512(20 downto 0) <= PHASE_ERROR_SQUARE_THRESHOLD) then
			CARRIER_LOCK_LOCAL <= '1';	-- carrier tracking loop locked	
		else
			CARRIER_LOCK_LOCAL <= '0';	-- carrier tracking loop unlocked
		end if;
	end if;
end process;

CARRIER_LOCK <= CARRIER_LOCK_LOCAL;

--// COMSCOPE TEST POINTS
CS1 <= PHASE_ERROR(10 downto 3);
CS1_CLK <= OPTIMUM_SAMPLING_CLK;
CS2 <= PHASE_ERROR_ACC(19 downto 12);
CS2_CLK <= OPTIMUM_SAMPLING_CLK;
CS3 <= PHASE_ERROR_SQUARE_ACC_DIV512(16 downto 9);
CS3_CLK <= OPTIMUM_SAMPLING_CLK_D3;
--S4_2 <= PHASE_ERROR_ACC(15 downto 8);
--S4_2_CLK <= OPTIMUM_SAMPLING_CLK_D;

--// simulation. send to a tab-delimited text file for subsequent (Matlab) plot.
SIM2OUTFILE_X: if(SIMULATION = '1') generate
--
--	SIM2OUTFILE1: SIM2OUTFILE 
--	generic map(
--		FILENAME => "ct1.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => LOOP_ERROR1(19 downto 8),
--		DATA2 => LOOP_ERROR2(19 downto 8),
--		DATA3 => PHASE_ERROR_ACC2(19 downto 8),	-- bias
--		SAMPLE_CLK_IN => OPTIMUM_SAMPLING_CLK_D
--	);
--
--	SIM2OUTFILE2: SIM2OUTFILE 
--	generic map(
--		FILENAME => "ct2.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => PHASE_ERROR_SQUARE_D(29 downto 18),
--		DATA2 => PHASE_ERROR_SQUARE_ACC(29 downto 18),
--		DATA3 => PHASE_ERROR_SQUARE_ACC(21 downto 10),
--		SAMPLE_CLK_IN => OPTIMUM_SAMPLING_CLK
--	);
--	SIM2OUTFILE3: SIM2OUTFILE 
--	generic map(
--		FILENAME => "ct3.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => PHASE_ERROR_X2(11 downto 0),	
--		DATA2 => PHASE_ERROR_ACC2_DIV512(11 downto 0),	
--		DATA3 => PHASE_ERROR_SQUARE_ACC_DIV512(16 downto 5),
--		SAMPLE_CLK_IN => OPTIMUM_SAMPLING_CLK
--	);
end generate;

end behavioral;					