-------------------------------------------------------------
--	Filename:  RESAMPLING31.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 0
--	Date last modified: 5/26/15
-- Inheritance: 	RESAMPLING3.VHD 5/25/15
--
-- Description: resample a complex waveform under the control of a numerically controlled
-- oscillator. Control part of a bit timing recovery loop. 32-bit NCO.
-- Recomputates the timetag after resampling.
-- Key assumption1: the input samples are regularly spaced at a known input sampling rate INPUT_SAMPLING_RATE.
-- Unlike RESAMPLING2/3.vhd, there is no restriction on the input sampling rate being larger than the output 
-- sampling rate.  
-- Unlike RESAMPLING2.vhd, this component behaves reliably in the special case of 
-- very high resampling rate  (> fclk/2) while using occasional NCO_OFFSET corrections
-- Same as RESAMPLING3.vhd except for the additional 
--
-- Implementation: linear interpolation. 
-- Algorithm: same as RESAMPLING2.VHD, but sampling frequency is inverted and sampling clock
-- is also inverted in the case of more than 1 sample every 2 clocks.
--
-- Usage limitations:
-- SAMPLING RATE range is limited by the multiplier precision: here 18-bits at 250ps/unit = 15.3 KSamples/s MIN
-------------------------------------------------------------

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.NUMERIC_STD.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;

entity RESAMPLING31 is
 	generic (
		NBITS: integer := 14;	-- input/output precision
		CLK_PERIOD: integer := 32;	-- CLK period duration expressed in timetag units (for example 250ps for SDDS)
		SIMULATION: std_logic := '0'
	);
    port ( 

	--// GLOBAL CLOCKS, RESET
   CLK : in std_logic;				-- System clock for this FPGA
	
	--// INPUT DATA STREAM
	DATA_I_IN: in std_logic_vector((NBITS-1) downto 0);
	DATA_Q_IN: in std_logic_vector((NBITS-1) downto 0);
		-- input samples, NBITS-bit precision
		-- read at rising edge of CLK when SAMPLE_CLK_IN = '1'.
		-- Format: 2's complement 
	TT_IN: in std_logic_vector(63 downto 0);
		-- timetag associated with the input sample. 
	SAMPLE_CLK_IN: in std_logic;
		-- sample clock (1 CLK-wide pulse)
		-- KEY assumption: samples have to be equally spaced in time!
	INPUT_SAMPLING_RATE: in std_logic_vector(31 downto 0);
		-- Input sample rate expressed as a function of the system clock fclk
 		-- input_sampling_rate*2^32 / fclk


	--// CONTROLS 
	NOMINAL_RESAMPLING_RATE: in std_logic_vector(31 downto 0);
		-- resampling rate expressed as a function of the system clock fclk:
		-- resampling_rate*2^32 / fclk
		-- Value must be changed if the master clock frequency or symbol rate changes.
	NCO_OFFSET: in std_logic_vector(31 downto 0);
		-- occasional phase corrections.
		-- 2's complement.
		-- read at rising edge of CLK when NCO_OFFSET_SAMPLE_CLK = '1'
	NCO_OFFSET_SAMPLE_CLK: in std_logic;
		-- The NCO_OFFSET phase correction can occur anytime. No special timing
		-- requirement with respect to inputs or outputs sample clocks.
		

	--// OUTPUT DATA STREAM
	-- Latency: 3 CLKs w.r.t. SAMPLE_CLK_IN.
	DATA_I_OUT: out std_logic_vector((NBITS-1) downto 0);
	DATA_Q_OUT: out std_logic_vector((NBITS-1) downto 0);
	TT_OUT: out std_logic_vector(63 downto 0);
		-- timetag associated with the output sample. 
	SAMPLE_CLK_OUT: out std_logic
		-- sample clock. 1 CLK-wide pulse. 
		-- Read the output data at the rising edge of CLK when SAMPLE_CLK_OUT = '1'
		-- As this is the resampling clock, it is also a convenient test point
		-- to compare transmit bit clock to receive bit clock when the bit timing loop is tracking.

			);
end entity;

architecture behavioral of RESAMPLING31 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 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

--// INPUT SAMPLING PARAMETERS
signal SP: unsigned(17 downto 0) := (others => '0');
signal SP_ACC: unsigned(17 downto 0) := (others => '0');
	-- SAMPLING RATE range is limited by the multiplier precision: here 18-bits at 250ps/unit = 15.3 KSamples/s MIN
signal NCO_INPUT_SAMPLING_ACC: unsigned(31 downto 0) := (others => '0');
signal NCO_INPUT_SAMPLING_ACC_PLUS: unsigned(31 downto 0) := (others => '0');

signal HIGH_SAMPLING_RATE: std_logic := '0';
signal NOMINAL_RESAMPLING_RATE2: std_logic_vector(31 downto 0) := (others => '1');
signal NCO_OFFSET2: std_logic_vector(31 downto 0) := (others => '1');
signal NCO_OFFSET2_SAMPLE_CLK: std_logic := '0';
signal SAMPLE_CLK_IN_D: std_logic := '0';
signal DATA_I_IN_D: signed((NBITS-1) downto 0) := (others => '0');
signal DATA_Q_IN_D: signed((NBITS-1) downto 0) := (others => '0');
signal DATA_I_IN_D2: signed((NBITS-1) downto 0) := (others => '0');
signal DATA_Q_IN_D2: signed((NBITS-1) downto 0) := (others => '0');
signal TT_IN_D: unsigned(63 downto 0) := (others => '0');
signal TT_IN_D2: unsigned(63 downto 0) := (others => '0');
signal TT: unsigned(63 downto 0) := (others => '0');


signal NCO_RESAMPLING_ACC: std_logic_vector(31 downto 0) := x"C0000000";	-- create initial timing offset during simulation
signal NCO_RESAMPLING_ACC_D_MSB: std_logic := '0';
--signal NCO_TIME: std_logic_vector(31 downto 0) := (others => '0');
signal COEFF1: signed(17 downto 0) := (others => '0');
signal COEFF2: signed(17 downto 0) := (others => '0');
signal DATA1_I: signed((NBITS+17) downto 0) := (others => '0');
signal DATA2_I: signed((NBITS+17) downto 0) := (others => '0');
signal DATA1_I_D: signed(NBITS downto 0) := (others => '0');
signal DATA2_I_D: signed(NBITS downto 0) := (others => '0');
signal DATA_INTERPOL_I: signed(NBITS downto 0) := (others => '0');
signal DATA1_Q: signed((NBITS+17) downto 0) := (others => '0');
signal DATA2_Q: signed((NBITS+17) downto 0) := (others => '0');
signal DATA1_Q_D: signed(NBITS downto 0) := (others => '0');
signal DATA2_Q_D: signed(NBITS downto 0) := (others => '0');
signal DATA_INTERPOL_Q: signed(NBITS downto 0) := (others => '0');
signal SAMPLE_CLK_OUT_E2: std_logic := '0';
signal SAMPLE_CLK_OUT_E: std_logic := '0';
signal MIN_INTERVAL: integer range 0 to 511 := 0;
signal INTERVAL_CNTR: std_logic_vector(9 downto 0) := (others => '0');

--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------

begin

--// INPUT SAMPLING PARAMETERS ---------------------------
-- sampling period: compute the period between successive input samples
-- aligned with SAMPLE_CLK_IN_D
SAMPLING_PERIOD_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SAMPLE_CLK_IN = '1') then
			SP <= SP_ACC + to_unsigned(CLK_PERIOD, SP_ACC'length); 
			SP_ACC <= (others => '0');
		else
			SP_ACC <= SP_ACC + to_unsigned(CLK_PERIOD, SP_ACC'length);
		end if;	
	end if;
end process;

-- reconstruct input sampling rate NCO (so as to assert its phase at the output sampling time)
-- aligned with SAMPLE_CLK_IN_D
NCO_INPUT_SAMPLING_ACC_PLUS <= NCO_INPUT_SAMPLING_ACC + unsigned(INPUT_SAMPLING_RATE);
INPUT_SAMPLING_NCO_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		if(SAMPLE_CLK_IN = '1') then
			NCO_INPUT_SAMPLING_ACC <= (others => '0');
		else
			NCO_INPUT_SAMPLING_ACC <= NCO_INPUT_SAMPLING_ACC_PLUS;
		end if;	
	end if;
end process;



--// Is sampling rate >= 50%, if so, invert
HIGH_SAMPLING_RATE <= NOMINAL_RESAMPLING_RATE(31);
HIGH_SAMPLING_RATE_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		NCO_OFFSET2_SAMPLE_CLK <= NCO_OFFSET_SAMPLE_CLK;
		
		if(HIGH_SAMPLING_RATE = '1') then
			-- invert nominal sampling rates and corrections
			NOMINAL_RESAMPLING_RATE2 <= not NOMINAL_RESAMPLING_RATE;
			NCO_OFFSET2 <= not NCO_OFFSET;
		else
			NOMINAL_RESAMPLING_RATE2 <= NOMINAL_RESAMPLING_RATE;
			NCO_OFFSET2 <= NCO_OFFSET;
		end if;	
	end if;
end process;


--// Reclock input signals twice for interpolation purposes. 
RECLOCK_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_IN_D <= SAMPLE_CLK_IN;

		if(SAMPLE_CLK_IN = '1') then
			DATA_I_IN_D <= signed(DATA_I_IN);
			DATA_Q_IN_D <= signed(DATA_Q_IN);
			DATA_I_IN_D2 <= DATA_I_IN_D;
			DATA_Q_IN_D2 <= DATA_Q_IN_D;
			TT_IN_D <= unsigned(TT_IN);
			TT_IN_D2 <= TT_IN_D;
		end if;
		
		--// linear interpolation coefficients
		-- these coefficients are positive. and the sum is always 1 (or 1FFFF to be precise)
		if(SAMPLE_CLK_IN = '1') then
			COEFF1 <= (COEFF1'left => '0', others => '1');	-- coefficient for the former sample DATA_X_IN_D2
			COEFF2 <= (others => '0');  -- coefficient for the latter sample DATA_X_IN_D
		else
			COEFF1 <= signed('0' & (not NCO_INPUT_SAMPLING_ACC_PLUS(31 downto 15)));  
			COEFF2 <= signed('0' & (NCO_INPUT_SAMPLING_ACC_PLUS(31 downto 15)));  -- coefficient for the latter sample DATA_X_IN_D
		end if;
	end if;
end process;


--// linear interpolation. Sampled at SAMPLE_CLK_OUT_E2
-- signed multipliers
DATA1_I <= DATA_I_IN_D2 * COEFF1;
DATA2_I <= DATA_I_IN_D * COEFF2;

DATA1_Q <= DATA_Q_IN_D2 * COEFF1;
DATA2_Q <= DATA_Q_IN_D * COEFF2;


-- reclock immediately after multiplication (for best timing)
INTERPOL_001: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_OUT_E <= SAMPLE_CLK_OUT_E2;
		if(SAMPLE_CLK_OUT_E2 = '1') then
			DATA1_I_D <= signed(DATA1_I((NBITS+17) downto 17)); 
			DATA2_I_D <= signed(DATA2_I((NBITS+17) downto 17)); 
			DATA1_Q_D <= signed(DATA1_Q((NBITS+17) downto 17)); 
			DATA2_Q_D <= signed(DATA2_Q((NBITS+17) downto 17)); 
			TT <= TT_IN_D2 + resize(SP_ACC, TT'length);
		end if;	
	end if;
end process;

DATA_INTERPOL_I <= DATA1_I_D + DATA2_I_D;
DATA_INTERPOL_Q <= DATA1_Q_D + DATA2_Q_D;

INTERPOL_002: process(CLK)
begin
	if rising_edge(CLK) then
		SAMPLE_CLK_OUT <= SAMPLE_CLK_OUT_E;
		if(SAMPLE_CLK_OUT_E = '1') then
			TT_OUT <= std_logic_vector(TT);
		
		
			if(DATA_INTERPOL_I(NBITS downto (NBITS-1)) = "01") then
				-- positive saturation. clamp
				DATA_I_OUT(NBITS-1) <= '0';
				DATA_I_OUT((NBITS-2) downto 0) <= (others => '1');
			elsif(DATA_INTERPOL_I(NBITS downto (NBITS-1)) = "10") then
				-- negative saturation. clamp
				DATA_I_OUT(NBITS-1) <= '1';
				DATA_I_OUT((NBITS-2) downto 1) <= (others => '0');
				DATA_I_OUT(0) <= '1';
			else
				DATA_I_OUT <= std_logic_vector(DATA_INTERPOL_I((NBITS-1) downto 0));
			end if;
	
			if(DATA_INTERPOL_Q(NBITS downto (NBITS-1)) = "01") then
				-- positive saturation. clamp
				DATA_Q_OUT(NBITS-1) <= '0';
				DATA_Q_OUT((NBITS-2) downto 0) <= (others => '1');
			elsif(DATA_INTERPOL_Q(NBITS downto (NBITS-1)) = "10") then
				-- negative saturation. clamp
				DATA_Q_OUT(NBITS-1) <= '1';
				DATA_Q_OUT((NBITS-2) downto 1) <= (others => '0');
				DATA_Q_OUT(0) <= '1';
			else
				DATA_Q_OUT <= std_logic_vector(DATA_INTERPOL_Q((NBITS-1) downto 0));
			end if;
		end if;
	end if;
end process;

-- NCO accumulator
-- The nominal value being accumulated depends on the system frequency and symbol rate
NCO1: process (CLK)
begin 
	if rising_edge(CLK) then 
	
		NCO_RESAMPLING_ACC_D_MSB <= NCO_RESAMPLING_ACC(31);
			-- remember the MSb for detecting changes (a change in the MSb means
			-- a new output sample is generated).
 
		if(SIMULATION = '1') and (NCO_RESAMPLING_ACC(NCO_RESAMPLING_ACC'left) = 'X') then
			-- prevent forever XXXXXXX condition during simulation
			NCO_RESAMPLING_ACC <= (others => '0');
		elsif(NCO_OFFSET2_SAMPLE_CLK = '0') then
			NCO_RESAMPLING_ACC <= NCO_RESAMPLING_ACC + ("0" & NOMINAL_RESAMPLING_RATE2(31 downto 1));
				-- nominal NCO increment (no phase correction)
		else
			NCO_RESAMPLING_ACC <= NCO_RESAMPLING_ACC + ("0" & NOMINAL_RESAMPLING_RATE2(31 downto 1)) + NCO_OFFSET2;
				-- NCO update with phase correction
		end if;
	end if;
end process;

-- toggle prevention 
-- as the NCO_OFFSET correction is positive or negative, it can occasionally creates multiple spurious 
-- output SAMPLE_CLK_OUT_E2 pulses. We add a timer to prevent successive toggling.
-- The principle is simple: we can determine the minimum number of CLKs between successive SAMPLE_CLK_OUT_E2
-- based on the NOMINAL_RESAMPLING_RATE2
TOGGLE_PREVENTION_001:  process(CLK)
begin
	if rising_edge(CLK) then
		if(NOMINAL_RESAMPLING_RATE2(31) = '1') then
			MIN_INTERVAL <= 0;
		elsif(NOMINAL_RESAMPLING_RATE2(30) = '1') then
			MIN_INTERVAL <= 0;
		elsif(NOMINAL_RESAMPLING_RATE2(29) = '1') then
			MIN_INTERVAL <= 1;
		elsif(NOMINAL_RESAMPLING_RATE2(28) = '1') then
			MIN_INTERVAL <= 3;
		elsif(NOMINAL_RESAMPLING_RATE2(27) = '1') then
			MIN_INTERVAL <= 7;
		elsif(NOMINAL_RESAMPLING_RATE2(26) = '1') then
			MIN_INTERVAL <= 15;
		elsif(NOMINAL_RESAMPLING_RATE2(25) = '1') then
			MIN_INTERVAL <= 31;
		elsif(NOMINAL_RESAMPLING_RATE2(24) = '1') then
			MIN_INTERVAL <= 63;
		elsif(NOMINAL_RESAMPLING_RATE2(23) = '1') then
			MIN_INTERVAL <= 127;
		elsif(NOMINAL_RESAMPLING_RATE2(22) = '1') then
			MIN_INTERVAL <= 255;
		else
		--elsif(NOMINAL_RESAMPLING_RATE2(21) = '1') then
			MIN_INTERVAL <= 511;
		end if;
 	end if;
end process;


-- The issue is to decide which of the reclocked and interpolated samples is best?
-- Decision based on the time elapsed since last SAMPLE_CLK_IN
RESAMPLING_001: process(CLK)
begin
	if rising_edge(CLK) then
		if (NCO_RESAMPLING_ACC(31) /= NCO_RESAMPLING_ACC_D_MSB) 
		and (INTERVAL_CNTR >= MIN_INTERVAL) then 
			-- take a new sample
			SAMPLE_CLK_OUT_E2 <= not HIGH_SAMPLING_RATE;	
			INTERVAL_CNTR <= (others => '0');
		else
			SAMPLE_CLK_OUT_E2 <= HIGH_SAMPLING_RATE;
			-- interval counter 0 to 512, then stay there.
			if(INTERVAL_CNTR(9) = '0') then
				INTERVAL_CNTR <= INTERVAL_CNTR + 1;
			end if;
		end if;
 	end if;
end process;

--// 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 => "rs.txt"
--	)
--	PORT MAP(
--		CLK => CLK,
--		DATA1 => NCO_RESAMPLING_ACC(31 downto 20),
--		DATA2 => NCO_OFFSET2(31 downto 20),
--		DATA3 => x"000",
--		SAMPLE_CLK_IN => '1'
--	);
--end generate;


end Behavioral;
