-------------------------------------------------------------
-- MSS copyright 2015
--	Filename:  CLOCK_RECOVERY.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 0
--	Date last modified: 4/16/15
-- Inheritance: 	n/a
--
-- description:  regenerate an input clock using a frequency measurement and 2nd order PLL to track the input clock.
-- Also generate x2,x4 and x8 frequency versions. The recovered clock has a duty cycle as close to 50% as possible.
-- There is no need to tell this component about the input clock frequency, it will measure it after a SYNC_RESET.
--
-- Usage: send a SYNC_RESET pulse, wait for about 300 clock periods before using the re-generated output clocks
----- Implementation size -----------------------
-- Spartan-3 -4 speed grade Maximum Frequency: 73 MHz
-- Registers: 245
-- LUT: 439
-- BUFG: 1
---------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;

entity CLOCK_RECOVERY is
    port ( 
		--GLOBAL CLOCKS, RESET
        CLK: in std_logic;	-- master clock for this FPGA, internal to the module
        SYNC_RESET: in std_logic;	-- synchronous reset

		--// Asynchronous clock input
		CLK_IN: in std_logic;
		CLK_IN_VALID: in std_logic;	
			-- '0' will freeze the internal tracking loop (to prevent it from drifting during a
			-- temporary absence of asynchronous clock input)

        --// recovered clock
        CLK_OUT: out std_logic;
        CLKx2_OUT: out std_logic;
        CLKx4_OUT: out std_logic;
        CLKx8_OUT: out std_logic;
            -- 1 CLK-wide pulse at the input clock frequency, x1,x2,x4,x8
            -- All pulses aligned.
        
        --// Monitoring
        CLK_FREQ_OUT: out std_logic_vector(31 downto 0);
            -- measured input clock frequency. expressed as fclk/2^32 frequency units.
        OUTPUT_READY: out std_logic;
            -- goes high when frequency acquisition is complete and PLL is tracking.
            -- 512 clock periods after reset.
        
		-- Test Points
		TP: out std_logic_vector(10 downto 1)	
			-- bit 0-9: undefined

			);
end entity;

architecture behavioral of CLOCK_RECOVERY is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT PC_16
	PORT(
		A : IN std_logic_vector(15 downto 0);          
		O : OUT std_logic_vector(4 downto 0)
		);
	END COMPONENT;
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
-- Suffix _D indicates a one CLK delayed version of the net with the same name
-- Suffix _E indicates an extended precision version of the net with the same name
-- Suffix _N indicates an inverted version of the net with the same name

-- reclock inputs
signal CLK_IN_D: std_logic := '0';
signal CLK_IN_D2: std_logic := '0';

--//-- ASYNCHRONOUS INPUT -------------------------------
signal CLK_CNTR: unsigned(31 downto 0) := (others => '0');
signal SYMBOL_CNTR: unsigned(9 downto 0) := (others => '0');
signal ASYNC_IN_STATE: integer range 0 to 3 := 0;
signal N_ONES_LSB: std_logic_vector(4 downto 0):= (others => '0');
signal N_ONES_LSB_D: std_logic_vector(4 downto 0):= (others => '0');
signal N_ONES_MSB: std_logic_vector(4 downto 0):= (others => '0');
signal N_ONES_MSB_D: std_logic_vector(4 downto 0):= (others => '0');
signal N_ONES_D: unsigned(4 downto 0):= (others => '0');
signal SHIFT_LEFT: integer range 0 to 31 := 0; 
signal NCO_FREQ_SHIFT: integer range 0 to 31 := 0;
signal NCO_FREQ_REG: unsigned(31 downto 0) := (others => '0');
signal NCO_FREQ: unsigned(31 downto 0) := (others => '0');
signal NCO_ACC: signed(31 downto 0) := (others => '0');
signal NCO_ACC_D: signed(31 downto 0) := (others => '0');
signal PD: std_logic := '0';
signal PD_SIGN: std_logic := '0';
signal LOOP1: signed(31 downto 0) := (others => '0');
signal LOOP2: signed(31 downto 0) := (others => '0');
signal NCO_ADJ: signed(31 downto 0) := (others => '0');
signal NCO_CONTROL: signed(31 downto 0) := (others => '0');
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin

-- reclock inputs
RECLOCK_001: process(CLK)
begin
	if rising_edge(CLK) then
		CLK_IN_D <= CLK_IN;
		CLK_IN_D2 <= CLK_IN_D;
	end if;
end process;

--//-- ASYNCHRONOUS INPUT -------------------------------
-- count the number of symbol clocks over a fixed duration of 2,4,8,...2^n CLK periods.
-- stop when the number of symbol clocks is greater than 200.
ASYNC_IN_001: process(CLK)
begin
	if rising_edge(CLK) then
		if(ASYNC_IN_STATE = 0) then
			CLK_CNTR <= (1 => '1', others => '0');	-- start at 2 since we stop counting at 2^n+1
		elsif(ASYNC_IN_STATE = 1) then
			CLK_CNTR <= CLK_CNTR + 1;
		end if;
	end if;
end process;

ASYNC_IN_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(ASYNC_IN_STATE = 0) then
			SYMBOL_CNTR <= (others => '0');
		elsif(ASYNC_IN_STATE = 1) and (CLK_IN_D = '1') and (CLK_IN_D2 = '0') then
            SYMBOL_CNTR <= SYMBOL_CNTR + 1;
		elsif(ASYNC_IN_STATE = 2) and (CLK_IN_D = '1') and (CLK_IN_D2 = '0') then
            SYMBOL_CNTR <= SYMBOL_CNTR + 1;
        end if;
	end if;
end process;

ASYNC_IN_003: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			ASYNC_IN_STATE <= 0;
		elsif(ASYNC_IN_STATE = 0) and (CLK_IN_D = '1') and (CLK_IN_D2 = '0') then
			-- 1st symbol. Start measuring the frequency
			ASYNC_IN_STATE <= 1;
		elsif(ASYNC_IN_STATE = 1) and (SYMBOL_CNTR > 255) and (N_ONES_D = 1) then
			-- enough input symbols to assess approximate symbol rate (within 1% or so)
			-- wait until the next 2^n CLK boundary (simplifies division)
			ASYNC_IN_STATE <= 2;
		elsif(ASYNC_IN_STATE = 2) and (SYMBOL_CNTR > 511) and (NCO_ACC(31 downto 29) = "011") then
		    -- PLL had time to stabilize. Output is ready to use.
		    -- Enable output just before the next CLK_OUT is due.
			ASYNC_IN_STATE <= 3;
		end if;	
	end if;
end process;
OUTPUT_READY <= '1' when (ASYNC_IN_STATE = 3) else '0';

-- 2^n CLK boundary (simplifies division)
ASYNC_IN_004: process(CLK)
begin
	if rising_edge(CLK) then
		N_ONES_LSB_D <= N_ONES_LSB;
		N_ONES_MSB_D <= N_ONES_MSB;
		
		if(ASYNC_IN_STATE = 1) then
			if(CLK_CNTR(10) = '1') then
				SHIFT_LEFT <= 22;
			elsif(CLK_CNTR(11) = '1') then
				SHIFT_LEFT <= 21;
			elsif(CLK_CNTR(12) = '1') then
				SHIFT_LEFT <= 20;
			elsif(CLK_CNTR(13) = '1') then
				SHIFT_LEFT <= 19;
			elsif(CLK_CNTR(14) = '1') then
				SHIFT_LEFT <= 18;
			elsif(CLK_CNTR(15) = '1') then
				SHIFT_LEFT <= 17;
			elsif(CLK_CNTR(16) = '1') then
				SHIFT_LEFT <= 16;
			elsif(CLK_CNTR(17) = '1') then
				SHIFT_LEFT <= 15;
			elsif(CLK_CNTR(18) = '1') then
				SHIFT_LEFT <= 14;
			elsif(CLK_CNTR(19) = '1') then
				SHIFT_LEFT <= 13;
			elsif(CLK_CNTR(20) = '1') then
				SHIFT_LEFT <= 12;
			elsif(CLK_CNTR(21) = '1') then
				SHIFT_LEFT <= 11;
			elsif(CLK_CNTR(22) = '1') then
				SHIFT_LEFT <= 10;
			elsif(CLK_CNTR(23) = '1') then
				SHIFT_LEFT <= 9;
			elsif(CLK_CNTR(24) = '1') then
				SHIFT_LEFT <= 8;
			elsif(CLK_CNTR(25) = '1') then
				SHIFT_LEFT <= 7;
			elsif(CLK_CNTR(26) = '1') then
				SHIFT_LEFT <= 6;
			elsif(CLK_CNTR(27) = '1') then
				SHIFT_LEFT <= 5;
			elsif(CLK_CNTR(28) = '1') then
				SHIFT_LEFT <= 4;
			elsif(CLK_CNTR(29) = '1') then
				SHIFT_LEFT <= 3;
			elsif(CLK_CNTR(30) = '1') then
				SHIFT_LEFT <= 2;
			else
				SHIFT_LEFT <= 0;
			end if;
		end if;
	end if;
end process;

-- count the number of 1's in CLK_CNTR_INC.
PC_16_001: PC_16 PORT MAP(
	A => std_logic_vector(CLK_CNTR(15 downto 0)),
	O => N_ONES_LSB
);

PC_16_002: PC_16 PORT MAP(
	A => std_logic_vector(CLK_CNTR(31 downto 16)),
	O => N_ONES_MSB
);
N_ONES_D <= unsigned(N_ONES_LSB_D) + unsigned(N_ONES_MSB_D);
	-- total number of 'ones' in CLK_CNTR


-- shift NCO_FREQ_REG left
ASYNC_IN_005: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			NCO_FREQ_REG <= (others => '0');
		elsif(ASYNC_IN_STATE = 1) and (SYMBOL_CNTR > 255) and (N_ONES_D = 1) then
			-- done measuring. 
			NCO_FREQ_REG <= resize(SYMBOL_CNTR, NCO_FREQ_REG'length);
			NCO_FREQ_SHIFT <= SHIFT_LEFT;
		elsif(NCO_FREQ_SHIFT /= 0) then
			-- shift left
			NCO_FREQ_SHIFT <= NCO_FREQ_SHIFT - 1;
			NCO_FREQ_REG(31 downto 0) <= NCO_FREQ_REG(30 downto 0) & "0";
		end if;
	end if;
end process;

-- latch in the NCO_FREQ when finished shifting left
ASYNC_IN_006: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			NCO_FREQ <= (others => '0');
		elsif(ASYNC_IN_STATE = 2) and (NCO_FREQ_SHIFT = 1) then
			-- done shifting. 
			NCO_FREQ(31 downto 0) <= NCO_FREQ_REG(30 downto 0) & "0";
		end if;
	end if;
end process;

-- NCO
NCO_ACC_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		NCO_ACC_D(31 downto 29) <= NCO_ACC(31 downto 29);
		
		if(SYNC_RESET = '1') then
			NCO_ACC <= (others => '0');
		else
			NCO_ACC <= NCO_ACC + NCO_CONTROL;
		end if;
	end if;
end process;
CLK_OUT <= '1' when (NCO_ACC(31) = '1') and (NCO_ACC_D(31) = '0') else '0';
	-- recovered clock x1
CLKx2_OUT <= '1' when (NCO_ACC(31) /= NCO_ACC_D(31)) else '0';
CLKx4_OUT <= '1' when (NCO_ACC(31 downto 30) /= NCO_ACC_D(31 downto 30)) else '0';
CLKx8_OUT <= '1' when (NCO_ACC(31 downto 29) /= NCO_ACC_D(31 downto 29)) else '0';

-- measured input clock frequency
CLK_FREQ_OUT <= std_logic_vector(NCO_CONTROL);

-- phase detector
PD <= NCO_ACC(31) xor CLK_IN;

-- phase detector sign
-- 0 when DATA1_CLK_IN leads the recovered clock NCO_ACC(31), 1 when lagging
PD_SIGN_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1') then
			PD_SIGN <= '0';
		elsif(NCO_ACC(31) = '1') and (NCO_ACC_D(31) = '0') then
			-- at the rising edge of the recovered clock
			PD_SIGN <= CLK_IN;
		end if;
	end if;
end process;

-- Phase error 
PE_GEN: process(PD, PD_SIGN)
begin

	if(CLK_IN_VALID = '1') and (PD = '1') and (PD_SIGN = '0') then
		-- non aligned and DATA1_CLK_IN leads the recovered clock NCO_ACC(31)
		-- Decrease recovered clock frequency (by 1/16 of a period)
		LOOP1 <= -signed(resize(NCO_FREQ(31 downto 4),32));
	elsif(CLK_IN_VALID = '1') and (PD = '1') and (PD_SIGN = '1') then
		-- non aligned and DATA1_CLK_IN lags the recovered clock NCO_ACC(31)
		-- Increase recovered clock frequency (by 1/16 of a period)
		LOOP1 <= signed(resize(NCO_FREQ(31 downto 4),32));
	else
		LOOP1 <= (others => '0');
	end if;
end process;

-- PLL. 2nd order loop
PLL_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		NCO_ADJ <= LOOP1 + LOOP2;
		NCO_CONTROL <= signed(std_logic_vector(NCO_FREQ)) + NCO_ADJ;

		if(SYNC_RESET = '1') then
		  LOOP2 <= (others => '0');
        else
          LOOP2 <= LOOP2 + resize(LOOP1(31 downto 9),32);
		end if;
  end if;
end process;



end architecture;
