-------------------------------------------------------------
--	Filename:  RESAMPLING2.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 6b
--	Date last modified: 9/30/13
-- Inheritance: 	RESAMPLING32.VHD
--
-- Description: resample a complex waveform under the control of a numerically controlled
-- oscillator. Control part of a bit timing recovery loop. 32-bit NCO.
-- Key assumption1: the input samples are regularly spaced.
--
-- Implementation: linear interpolation. 
--
-- Rev 2  2/23/10 AZ
-- corrected bug in clamping.
--
-- Rev 3 4/25/12 AZ
-- Added generic for NBITS precision
-- 
-- Rev 4 9/8/12 AZ
-- Added simulation waveform export.
-- Added toggle prevention circuit
--
-- Rev 5 10/27/12 AZ
-- prevent XXXX propagation in the accumulator
-- 
-- Rev 6 3/26/13 Toggle prevention circuit timer was 2z too long
--
-- Rev 6b 9/30/14 Vivado tools misunderstood the statement (NCO_RESAMPLING_ACC(NCO_RESAMPLING_ACC'left) = 'X') as true
-- had to work around it.
-------------------------------------------------------------

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

entity RESAMPLING2 is
 	generic (
		NBITS: integer := 14	-- input/output precision
	);
    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 
	SAMPLE_CLK_IN: in std_logic;
		-- sample clock (1 CLK-wide pulse)
		-- KEY assumption: samples have to be equally spaced in time!

	--// 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);
	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 RESAMPLING2 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 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

signal SAMPLE_CLK_IN_D: std_logic := '0';
signal DATA_I_IN_D: std_logic_vector(17 downto 0) := (others => '0');
signal DATA_Q_IN_D: std_logic_vector(17 downto 0) := (others => '0');
signal DATA_I_IN_D2: std_logic_vector(17 downto 0) := (others => '0');
signal DATA_Q_IN_D2: std_logic_vector(17 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: std_logic_vector(17 downto 0) := (others => '0');
signal COEFF2: std_logic_vector(17 downto 0) := (others => '0');
signal DATA1_I: std_logic_vector(35 downto 0) := (others => '0');
signal DATA2_I: std_logic_vector(35 downto 0) := (others => '0');
signal DATA1_I_D: std_logic_vector(NBITS downto 0) := (others => '0');
signal DATA2_I_D: std_logic_vector(NBITS downto 0) := (others => '0');
signal DATA_INTERPOL_I: std_logic_vector(NBITS downto 0) := (others => '0');
signal DATA1_Q: std_logic_vector(35 downto 0) := (others => '0');
signal DATA2_Q: std_logic_vector(35 downto 0) := (others => '0');
signal DATA1_Q_D: std_logic_vector(NBITS downto 0) := (others => '0');
signal DATA2_Q_D: std_logic_vector(NBITS downto 0) := (others => '0');
signal DATA_INTERPOL_Q: std_logic_vector(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');

constant SIMULATION: std_logic := '0';
--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------

begin

--// 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(17 downto (18-NBITS)) <= DATA_I_IN;	-- 18-bit precision for multiplier
			DATA_I_IN_D((17-NBITS) downto 0) <= (others => '0');
			DATA_Q_IN_D(17 downto (18-NBITS)) <= DATA_Q_IN;	-- 18-bit precision for multiplier
			DATA_Q_IN_D((17-NBITS) downto 0) <= (others => '0');
			--// linear interpolation coefficients
			-- these coefficients are positive. and the sum is always 1 (or 1FFFF to be precise)
			COEFF1 <= '0' & NCO_RESAMPLING_ACC(30 downto 14);  -- coefficient for the former sample DATA_X_IN_D2
			COEFF2 <= '0' & (not NCO_RESAMPLING_ACC(30 downto 14));  -- coefficient for the latter sample DATA_X_IN_D
	
			DATA_I_IN_D2 <= DATA_I_IN_D;
			DATA_Q_IN_D2 <= DATA_Q_IN_D;
		end if;	
	end if;
end process;

--// linear interpolation
-- signed multipliers
MULT18X18SIGNED_001: MULT18X18SIGNED port map(
	A => DATA_I_IN_D2,
	B => COEFF1,
	P => DATA1_I
);

MULT18X18SIGNED_002: MULT18X18SIGNED port map(
	A => DATA_I_IN_D,
	B => COEFF2,
	P => DATA2_I
);


MULT18X18SIGNED_003: MULT18X18SIGNED port map(
	A => DATA_Q_IN_D2,
	B => COEFF1,
	P => DATA1_Q
);

MULT18X18SIGNED_004: MULT18X18SIGNED port map(
	A => DATA_Q_IN_D,
	B => COEFF2,
	P => DATA2_Q
);

-- 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 <= DATA1_I(35 downto (35-NBITS)); 
			DATA2_I_D <= DATA2_I(35 downto (35-NBITS));
			DATA1_Q_D <= DATA1_Q(35 downto (35-NBITS)); 
			DATA2_Q_D <= DATA2_Q(35 downto (35-NBITS));
		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
			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 <= 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 <= 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_OFFSET_SAMPLE_CLK = '0') then
			NCO_RESAMPLING_ACC <= NCO_RESAMPLING_ACC + ("0" & NOMINAL_RESAMPLING_RATE(31 downto 1));
				-- nominal NCO increment (no phase correction)
		else
			NCO_RESAMPLING_ACC <= NCO_RESAMPLING_ACC + ("0" & NOMINAL_RESAMPLING_RATE(31 downto 1)) + NCO_OFFSET;
				-- 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_RATE
TOGGLE_PREVENTION_001:  process(CLK)
begin
	if rising_edge(CLK) then
		if(NOMINAL_RESAMPLING_RATE(31) = '1') then
			MIN_INTERVAL <= 0;
		elsif(NOMINAL_RESAMPLING_RATE(30) = '1') then
			MIN_INTERVAL <= 0;
		elsif(NOMINAL_RESAMPLING_RATE(29) = '1') then
			MIN_INTERVAL <= 1;
		elsif(NOMINAL_RESAMPLING_RATE(28) = '1') then
			MIN_INTERVAL <= 3;
		elsif(NOMINAL_RESAMPLING_RATE(27) = '1') then
			MIN_INTERVAL <= 7;
		elsif(NOMINAL_RESAMPLING_RATE(26) = '1') then
			MIN_INTERVAL <= 15;
		elsif(NOMINAL_RESAMPLING_RATE(25) = '1') then
			MIN_INTERVAL <= 31;
		elsif(NOMINAL_RESAMPLING_RATE(24) = '1') then
			MIN_INTERVAL <= 63;
		elsif(NOMINAL_RESAMPLING_RATE(23) = '1') then
			MIN_INTERVAL <= 127;
		elsif(NOMINAL_RESAMPLING_RATE(22) = '1') then
			MIN_INTERVAL <= 255;
		else
		--elsif(NOMINAL_RESAMPLING_RATE(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 <= '1';	
			INTERVAL_CNTR <= (others => '0');
		else
			SAMPLE_CLK_OUT_E2 <= '0';
			-- 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_OFFSET(31 downto 20),
		DATA3 => x"000",
		SAMPLE_CLK_IN => '1'
	);
end generate;


end Behavioral;
