-- MSS copyright 2002-2014
-- Filename: CODE_TRACKING2.VHD
-- VHDL code for spread-spectrum code tracking loop
-- Author: Alain Zarembowitch / MSS
-- Edit date: 4/28/14
-- Revision: 1
-- Inheritance: CODE_TRACKING.VHD COM-1418 3-18-10
--
-- Description:  Code tracking loop. 
-- Input samples are (coherent integration & dump)^2 after despreading
-- with early and late codes. I&D period is typically 1/2 a data symbol: so there is 
-- a significant noise due to the fact that we do not know nor remove the data modulation.
-- Code for both 1st order loop (memoryless) and 2nd order loop (remembers the clock drift frequency)
--
-- Rev 2 3/31/14 AZ
-- Clamp the error to 1/16 chip
-- switched to numeric_std library
--
-- Rev 3 4/28/14 AZ
-- further clamp the error correction to 1/64th of a chip (1/16 is still unstable in some cases)
----------------------------------------------      
library IEEE;
use IEEE.std_logic_1164.all; 
use IEEE.numeric_std.ALL;
use IEEE.STD_LOGIC_SIGNED.ALL;

entity CODE_TRACKING2 is
	port (  
		SYNC_RESET: in std_logic;
			-- global reset, active high.
		CLK: in std_logic;
			-- reference clock for synchronous design

	  	--// INPUTS
		DATA_EARLY: in std_logic_vector(13 downto 0);
			-- early power sample. Read at rising edge of CLK when SAMPLE_CLK = '1'
		DATA_LATE: in std_logic_vector(13 downto 0);
			-- late power sample. Read at rising edge of CLK when SAMPLE_CLK = '1'
			-- analogous to power
		SAMPLE_CLK: in std_logic;
			-- input sample clock. 1 CLK wide.
			-- Marks the start of the code period (equivalent to bit/symbol clock). 1 CLK wide.
		FREEZE_CODE_TRACKING: in std_logic;
			-- 1 during acquisition, 0 during tracking
		LOOP_GAIN: in std_logic_vector(1 downto 0);
			-- x4 steps

		--// OUTPUTS
		NCO_TIMING_CONTROL: out std_logic_vector(31 downto 0);
			-- code frequency error (NCO control). 
			-- Expressed as NCO_TIMING_CONTROL*fs/2^32
			-- Does not include the bias for the nominal chip rate *4, just the error.
			-- The control value is NOT continuous. It is an IMPULSE, once per
			-- bit clock. This is to eliminate the dependency of the loop gain on the 
			-- spreading code length (processing gain).
		NCO_TIMING_CONTROL_CLK: out std_logic;
			-- 1 CLK-wide impulse. Read NCO_TIMING_CONTROL at the rising edge of CLK when NCO_TIMING_CONTROL_CLK = '1'

		CS1: out std_logic_vector(7 downto 0); -- accumulated code tracking phase correction
		CS1_CLK: out std_logic

 );
end entity;  

architecture CODE_TRACKING_ARCH of CODE_TRACKING2 is
-----------------------------------------------------------------
-- COMPONENTS
-----------------------------------------------------------------
	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
-----------------------------------------------------------------
signal TIMING_ERROR: std_logic_vector(14 downto 0) := (others => '0'); 
signal TIMING_ERROR_ACC: std_logic_vector(31 downto 0) := (others => '0');
signal TIMING_ERROR_X: std_logic_vector(31 downto 0) := (others => '0');
signal TIMING_ERROR_CLK: std_logic := '0';
signal TIMING_ERROR_CLK_D: std_logic := '0';
signal LOOP_ERROR1A: std_logic_vector(17 downto 0) := (others => '0');

signal LOOP_ERROR1: std_logic_vector(31 downto 0) := (others => '0');
signal LOOP_ERROR2: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL_CLK_LOCAL: std_logic := '0';
signal NCO_TIMING_CONTROL_local: std_logic_vector(31 downto 0) := (others => '0');
signal NCO_TIMING_CONTROL_ACC: std_logic_vector(31 downto 0) := x"00000000";

constant SIMULATION: std_logic := '0';	-- enable/disable file log
-----------------------------------------------------------------
-- IMPLEMENTATION
-----------------------------------------------------------------
begin   

-- compute the timing error
-- The difference is valid when the late sample comes in
TE_GEN_001: process(CLK)
begin
  	if rising_edge(CLK) then
		TIMING_ERROR_CLK <= SAMPLE_CLK;
		
		if(SYNC_RESET = '1') then
			TIMING_ERROR <= (others => '0');
		elsif(SAMPLE_CLK = '1') then
			-- just received the late sample
			TIMING_ERROR <= (DATA_EARLY(13) & DATA_EARLY) - (DATA_LATE(13) & DATA_LATE);  -- sign is correct. reconfirmed by simulation 8/11/08
	  	end if;
  	end if;
end process;

-- integrate the timing error for the 2nd order loop
-- accumulate error
TIMING_ERROR_X(31 downto 15) <= (others => TIMING_ERROR(14));
TIMING_ERROR_X(14 downto 0) <= TIMING_ERROR;
TE_ACC_001: process(CLK)
begin
	if rising_edge(CLK) then
		TIMING_ERROR_CLK_D <= TIMING_ERROR_CLK;
		
		if(SYNC_RESET = '1') then
			TIMING_ERROR_ACC <= (others => '0');
		elsif(TIMING_ERROR_CLK = '1') then
			TIMING_ERROR_ACC <= TIMING_ERROR_ACC + TIMING_ERROR_X;
	  	end if;
  	end if;
end process;
		
-- Adjust loop gain here.
-- Loop 1st order.
LOOP_GAIN_ADJ_001: process(LOOP_GAIN, TIMING_ERROR)
begin
	case LOOP_GAIN is 
		when "00" => 
			LOOP_ERROR1A <= TIMING_ERROR_X(17 downto 0);
		when "01" => 
			LOOP_ERROR1A <= TIMING_ERROR_X(16 downto 0) & "0";
		when "10" => 
			LOOP_ERROR1A <= TIMING_ERROR_X(15 downto 0) & "00";
		when others => 
			LOOP_ERROR1A <= TIMING_ERROR_X(14 downto 0) & "000";
	end case;
end process;

-- clamp to 1/64 chip
-- This means the maximum chip rate error that can be corrected by this 1st order loop is 
-- 1/64 every half symbol => +/-15ppm for a 2047 spreading factor, +/-30ppm for a 1023 spreading factor, etc.
CLAMP_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(TIMING_ERROR_CLK = '1') then
			if(LOOP_ERROR1A(17) = '0') and (LOOP_ERROR1A(16 downto 8) /= 0) then
				-- clamp to +1/64 period NCO positive phase correction
				LOOP_ERROR1 <= x"04000000";
			elsif(LOOP_ERROR1A(17) = '1') and ((not LOOP_ERROR1A(16 downto 8)) /= 0) then
				-- clamp to -1/64 period NCO negative phase correction
				LOOP_ERROR1 <= x"FC000000";
			else
				LOOP_ERROR1 <= LOOP_ERROR1A(13 downto 0) & "00" & x"0000";
			end if;
		end if;
  	end if;
end process;


--
--LOOP_GAIN_ADJ: process(LOOP_GAIN, TIMING_ERROR)
--begin
--	case LOOP_GAIN is 
--		when "00" => 
--			LOOP_ERROR1(31 downto 20) <= TIMING_ERROR(11 downto 0);
--			LOOP_ERROR1(19 downto 0) <= (others => '0');
--		when "01" => 
--			LOOP_ERROR1(31 downto 21) <= TIMING_ERROR(10 downto 0);
--			LOOP_ERROR1(20 downto 0) <= (others => '0');
--		when "10" => 
--			LOOP_ERROR1(31 downto 22) <= TIMING_ERROR(9 downto 0);
--			LOOP_ERROR1(21 downto 0) <= (others => '0');
--		when others => 
--			LOOP_ERROR1(31 downto 23) <= TIMING_ERROR(8 downto 0);	-- maximum loop gain before it becomes unstable at high SNR
--			LOOP_ERROR1(22 downto 0) <= (others => '0');
--	end case;
--end process;
--
---- Loop 2nd order (about 1/1000 wrt 1st order loop)
----LOOP_ERROR2(23 downto 19) <= (others => TIMING_ERROR_ACC(24));
----LOOP_ERROR2(18 downto 0) <= TIMING_ERROR_ACC(24 downto 6);
----LOOP_ERROR2 <= (others => '0');
--
-- code frequency error (NCO control). 
-- Expressed as NCO_TIMING_CONTROL*fs/2^32
-- Does not include the bias for the nominal chip rate, just the error.
-- The control value is NOT continuous. It is an IMPULSE, once per
-- bit clock. This is to eliminate the dependency of the loop gain on the 
-- spreading code length (processing gain).
TIMING_CONTROL_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			NCO_TIMING_CONTROL_local <= (others => '0');
		elsif(TIMING_ERROR_CLK_D = '1') and (FREEZE_CODE_TRACKING = '0') then
			NCO_TIMING_CONTROL_CLK_LOCAL <= '1';
			NCO_TIMING_CONTROL_local <= LOOP_ERROR1;  -- 1st order loop
--			NCO_TIMING_CONTROL <= LOOP_ERROR1 + LOOP_ERROR2;  -- 2nd order loop
	  	else
			-- impulse, one data symbol/bit long. At all other times, reset to zero.
			NCO_TIMING_CONTROL_CLK_LOCAL <= '0';
			NCO_TIMING_CONTROL_local <= (others => '0');
		end if;
	end if;
end process;
NCO_TIMING_CONTROL <= NCO_TIMING_CONTROL_local;
NCO_TIMING_CONTROL_CLK <= NCO_TIMING_CONTROL_CLK_LOCAL;


-- 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(NCO_TIMING_CONTROL_CLK_LOCAL = '1') and (FREEZE_CODE_TRACKING = '0') 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);  -- accumulated code tracking phase correction
CS1_CLK <= NCO_TIMING_CONTROL_CLK_LOCAL;

--// simulation. send to a tab-delimited text file for subsequent (Matlab) plot.
SIM2OUTFILE_X: if(SIMULATION = '1') generate
	Inst_SIM2OUTFILE1: SIM2OUTFILE 
	generic map(
		FILENAME => "ct.txt"
	)
	PORT MAP(
		CLK => CLK,
		DATA1 => NCO_TIMING_CONTROL_local(31 downto 20),
		DATA2 => NCO_TIMING_CONTROL_ACC(31 downto 20),
		DATA3 => x"000",	
		SAMPLE_CLK_IN => NCO_TIMING_CONTROL_CLK_LOCAL
	);
end generate;


				
end architecture;
