----------------------------------------------
-- MSS copyright 2017
-- Filename: AFC2.VHD
-- VHDL code for Automatic frequency control loop
-- Author: Alain Zarembowitch / MSS
-- Edit date:  8/27/17
-- Revision: 0
-- Inheritance: n/a
--
-- Algorithm: http://www.ijeeee.org/Papers/307-A0021.pdf
-- In effect, rotate one sample by the previous sample. The result on I is proportional to the phase difference. 
-- (This first part is performed outside this component as it may be common code, with ST loop for example).
-- Then average the levels for 0's and for 1's over approximately 1024 symbols. Keep separate averages as we are never 
-- guaranteed a perfectly equal distribution of 0s and 1s.
-- Assumes that the bits are fairly random (i.e. not long sequences of 0's or 1's).
-- Expected input: modulation frequency, 2x symbol rate
----------------------------------------------      
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.numeric_std.all;

entity AFC2 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;	-- can also be used as enable/disable
	
		-- INPUT SAMPLES
		FREQ_IN  : in  std_logic_vector(17 downto 0);
			-- read FREQ_IN when SAMPLE_CLK_IN = '1'.
		SAMPLE_CLK_IN: in std_logic;
			-- typically 4x symbol rate

		--// CONTROLS
		FREEZE: in std_logic;
			-- '1' to freeze current (non-zero) frequency correction output

		--// OUTPUTS
		NCO_CONTROL: out std_logic_vector(31 downto 0);
			-- signed 2's complement. Frequency error.
			-- updated once per symbol. 
			-- Maximum theoretical control: +/- symbol rate / 2. In practice, somewhat less. 
			-- Better result if NCO is updated 4x per symbol with NCO_CONTROL.
		NCO_CONTROL_CLK: out std_logic;
		AFC_LOCK: out std_logic
			-- '1' when the remaining frequency error is less than (symbol rate/16)

);
end entity;  

architecture behavioral of AFC2 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
-----------------------------------------------------------------
-- 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 SAMPLE_CLK_IN_D: std_logic := '0';
signal SAMPLE_CLK_IN_D2: std_logic := '0';
signal FREQ0_ACC: signed(27 downto 0) := (others => '0');
signal FREQ1_ACC: signed(27 downto 0) := (others => '0');
signal FE: signed(17 downto 0) := (others => '0');
signal NCO_CONTROL_tentative: signed(31 downto 0) :=(others => '0');
signal NCO_CONTROL_local: signed(31 downto 0) :=(others => '0');
signal FE_ACC: signed(28 downto 0) := (others => '0');

--//-- AFC LOCK DETECTION -------------------------------------------------
signal FREQ_ERROR_AVERAGE: signed(17 downto 0) := (others => '0');

constant	MAX_ABS_FREQ: signed(31 downto 0) := x"10000000";	-- TODO OPTIMIZE 
		-- AFC boundary. This helps avoiding a deadlock whereby the AFC loop has drifted so far out that
		-- the resulting received signal no longer convey any meaningful frequency/phase information.
		-- Note: expressed relative to the symbol rate: max/fs*2^32

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

-- Average the levels for 0's and for 1's over approximately 256 symbols. Keep separate averages as we are never 
-- guaranteed a perfectly equal distribution of 0s and 1s.
AFC_AVR0: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_IN_D <= SAMPLE_CLK_IN;
		
		if(SYNC_RESET = '1') then
			FREQ0_ACC <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') and (FREQ_IN(17) = '0') then
			FREQ0_ACC <= FREQ0_ACC + resize(signed(FREQ_IN),28) - resize(signed(FREQ0_ACC(27 downto 10)),28);
		end if;
		if(SYNC_RESET = '1') then
			FREQ1_ACC <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') and (FREQ_IN(17) = '1') then
			FREQ1_ACC <= FREQ1_ACC + resize(signed(FREQ_IN),28) - resize(signed(FREQ1_ACC(27 downto 10)),28);
		end if;
	end if;
end process;

-- Difference is proportional to the frequency error 
FE <= signed(FREQ0_ACC(27 downto 10)) + signed(FREQ1_ACC(27 downto 10));


-- scaling and summing. Step 1 (no limit)
NCO_CONTROL_GEN_001: process(CLK) 
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_IN_D <= SAMPLE_CLK_IN;

		if(SYNC_RESET = '1') or ((SIMULATION = '1') and (FE(FE'left) = 'X')) then
			-- reset and prevent X propagation during simulation
			NCO_CONTROL_tentative <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') then
			-- accumulate average frequency error. Scale for loop convergence.
			--NCO_CONTROL_tentative <= NCO_CONTROL_tentative - resize(signed(FE(17 downto 2)),NCO_CONTROL_tentative'length);
			NCO_CONTROL_tentative <= NCO_CONTROL_tentative - resize(signed(FE(17 downto 0)),NCO_CONTROL_tentative'length);
		end if;
	end if;
end process;

---- step 2. Confirmation after checking boundaries
--NCO_CONTROL_GEN_002: process(CLK) 
--begin
--	if rising_edge(CLK) then
--		if(SYNC_RESET = '1') or ((SIMULATION = '1') and (NCO_CONTROL_tentative(31) = 'X')) then
--			-- reset and prevent X propagation during simulation
--			NCO_CONTROL_local <= (others => '0');
--		elsif(SAMPLE_CLK_IN_D4 = '1') and (FREEZE = '0') then
--			if(NCO_CONTROL_tentative(31) = '0') then
--				-- positive
--				if (NCO_CONTROL_tentative > MAX_ABS_FREQ) then
--					-- above limit
--					NCO_CONTROL_local <= MAX_ABS_FREQ;
--				else
--					-- within boundaries
--					NCO_CONTROL_local <= NCO_CONTROL_tentative;
--				end if;
--			else
--				-- negative
--				if (NCO_CONTROL_tentative < -MAX_ABS_FREQ) then
--					-- above limit
--					NCO_CONTROL_local <= -MAX_ABS_FREQ;
--				else
--					-- within boundaries
--					NCO_CONTROL_local <= NCO_CONTROL_tentative;
--				end if;
--			end if;
--		end if;
--	end if;
--end process;




NCO_CONTROL <= std_logic_vector(NCO_CONTROL_tentative);
NCO_CONTROL_CLK <= SAMPLE_CLK_IN_D;
--
--//-- AFC LOCK DETECTION -------------------------------------------------
-- Select best estimate for remaining frequency error (relative to symbol rate)
-- Threshold is set empirically at TBD% of the symbol rate

-- Compute mean 
-- Method: leaky integrator. Running average over 2048 samples (512 symbols)
FREQ_MEAN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if (SYNC_RESET = '1') or ((SIMULATION = '1') and (FE(FE'left) = 'X')) then
			-- prevent X propagation during simulation
			-- No need for reset (as this is a running average, thus intrinsically self-resetting)
			FE_ACC <= (others => '0');
		elsif(SAMPLE_CLK_IN = '1') then
			FE_ACC <= FE_ACC + resize(signed(FE),29) - resize(signed(FE_ACC(28 downto 11)),29);
		end if;
	end if;
end process;
FREQ_ERROR_AVERAGE <= signed(FE_ACC(28 downto 11));	


AFC_LOCK_001: process(CLK) 
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			AFC_LOCK <= '0';
		elsif(SAMPLE_CLK_IN = '1') then
			if(abs(FREQ_ERROR_AVERAGE) <= 128) then
				-- Threshold is set empirically at TBD% of the symbol rate
				AFC_LOCK <= '1';
			else
				AFC_LOCK <= '0';
			end if;
		end if;
	end if;
end process;

--// simulation. send to a tab-delimited text file for subsequent (Matlab) plot.
SIM2OUTFILE_X: if(SIMULATION = '1') generate

end generate;

end behavioral;					