-------------------------------------------------------------
-- MSS copyright 2011
--	Filename:  AD9627_ADC_DRIVER.VHD
-- Author: 
--		Alain Zarembowitch / MSS, 
--	Version: 0
--	Date last modified: 11-19-10
-- Inheritance: 	none
--
-- description:  Driver for the 150 MSamples/s 12-bit ADCs.
-- Input samples are delayed so that the CLK reference clock rising edge is at the
-- center of the delayed input sample. A control loop adjusts the delay automatically
-- by aligning a similarly delayed DCO clock and the CLK reference clock.
--  
----------------------------------------------------------------------------------
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
library UNISIM;
use UNISIM.VComponents.all;

entity AD9627_ADC_DRIVER is
	 generic (
		ADC_NBITS: in integer := 12
	 );
    port ( 
			--// Clocks Resets
			CLK: in std_logic;
				-- reference clock for high-speed ADCs (x1) 
			
			--// User interface : received ADC samples. Synchronous with CLK.
			-- If using this ADC within a COM-3504 module, format is always unsigned (offset binary). 
			ADC1_DOUT: out std_logic_vector((ADC_NBITS-1) downto 0);
			ADC2_DOUT: out std_logic_vector((ADC_NBITS-1) downto 0);
			
			--// AD9627 Dual 150 MS/s A/D Converter (connect directly to top-level port)
			-- FPGA - high-speed ADC interface
			ADC1_DIN: in std_logic_vector((ADC_NBITS-1) downto 0);
			ADC2_DIN: in std_logic_vector((ADC_NBITS-1) downto 0);
				-- AD9627 data samples
			ADC_DCO: in std_logic_vector(1 downto 0);
				-- two data strobe signals (one for each ADC)
			
			ADC_CLKOUT_P: out std_logic;
			ADC_CLKOUT_N: out std_logic;
				-- LVCMOS (not LVDS!) differential ADC sampling clock 150 MHz max
				
			--// Test points
			TP: out std_logic_vector(10 downto 1)
			
		
			);
end entity;

architecture Behavioral of AD9627_ADC_DRIVER is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
-- Suffix _D indicates a one clock delay

signal IODELAY2_CAL: std_logic := '0';
signal IODELAY2_CE: std_logic_vector(1 downto 0) := "00";
signal IODELAY2_INC: std_logic_vector(1 downto 0) := "00";
signal IODELAY2_DCO_BUSY: std_logic_vector(1 downto 0) := "00";
signal ADC_DCO_D: std_logic_vector(1 downto 0) := "00";
signal ADC1_DIN_D: std_logic_vector((ADC_NBITS - 1) downto 0);
signal ADC2_DIN_D: std_logic_vector((ADC_NBITS - 1) downto 0);
signal CAL_STATE: std_logic_vector(1 downto 0) := "00";
signal ADC_DCO_D2: std_logic_vector(1 downto 0) := "00";
signal TOA_LATE: std_logic_vector(1 downto 0) := "00";
signal CLK_N: std_logic;


--------------------------------------------------------
--      IMPLEMENTATION
--------------------------------------------------------
begin
CLK_N <= not CLK;

-- ADC clock output
-- It is recommended to only use a BUFG resource to drive clock loads.
-- It is recommended to use a Clock Forwarding technique to create a reliable and repeatable 
-- low skew solution, i.e. use ODDR2 to regenerate the output clock.
ADC_CLKOUT_GEN_001P : ODDR2
generic map(
	DDR_ALIGNMENT => "NONE", -- Sets output alignment to "NONE", "C0", "C1" 
	INIT => '0', -- Sets initial state of the Q output to '0' or '1'
	SRTYPE => "SYNC") -- Specifies "SYNC" or "ASYNC" set/reset
port map (
	Q => ADC_CLKOUT_P, -- 1-bit output data
	C0 => CLK, -- 1-bit clock input
	C1 => CLK_N, -- 1-bit clock input
	CE => '1',  -- 1-bit clock enable input
	D0 => '1',   -- 1-bit data input (associated with C0)
	D1 => '0',   -- 1-bit data input (associated with C1)
	R => '0',    -- 1-bit reset input
	S => '0'     -- 1-bit set input
);

-- fake differential output (LVDS_33 is incompatible with LVCMOS33 in the same bank!)
ADC_CLKOUT_GEN_001N : ODDR2
generic map(
	DDR_ALIGNMENT => "NONE", -- Sets output alignment to "NONE", "C0", "C1" 
	INIT => '0', -- Sets initial state of the Q output to '0' or '1'
	SRTYPE => "SYNC") -- Specifies "SYNC" or "ASYNC" set/reset
port map (
	Q => ADC_CLKOUT_N, -- 1-bit output data
	C0 => CLK, -- 1-bit clock input
	C1 => CLK_N, -- 1-bit clock input
	CE => '1',  -- 1-bit clock enable input
	D0 => '0',   -- 1-bit data input (associated with C0)
	D1 => '1',   -- 1-bit data input (associated with C1)
	R => '0',    -- 1-bit reset input
	S => '0'     -- 1-bit set input
);
 
-- calibrate out round-trip delay  ADC_CLK_OUT FPGA -> ADC -> FPGA ADC_DCO
-- Create an input delay so that the input samples are stable at the rising edge of CLK

--// RECEIVED DATA/DATA STROBE REALIGNMENT --------------------------------------
DATA_DELAY_x: for I in 0 to (ADC_NBITS - 1) generate   
	-- programmable delay applied to the input data samples from the ADC
	IODELAY2_001 : IODELAY2
	generic map (
		COUNTER_WRAPAROUND => "STAY_AT_LIMIT", -- STAY_AT_LIMIT or WRAPAROUND
		DATA_RATE => "SDR",                 -- SDR (calibrate delay to 1 bit = 1 clock period)
		DELAY_SRC => "IDATAIN",             -- IO, ODATAIN or IDATAIN (output is not delayed)
		IDELAY_MODE => "NORMAL",            -- Unsupported
		IDELAY_TYPE => "VARIABLE_FROM_ZERO",-- FIXED, DEFAULT, VARIABLE_FROM_ZERO, VARIABLE_FROM_HALF_MAX or
														-- DIFF_PHASE_DETECTOR
		IDELAY_VALUE => 0,						-- Amount of input delay (0-255) 
		IDELAY2_VALUE => 0,                 -- Amount of secondary input Delay (0-255) 
		ODELAY_VALUE => 0,                	-- Amount of output delay (0-255)
		SERDES_MODE => "NONE",              -- NONE, MASTER or SLAVE
		SIM_TAPDELAY_VALUE => 50            -- Amount of delay used for simulation in pS
	)
	port map (
		-- IOB interface
		IDATAIN => ADC1_DIN(I), -- Data Signal from IOB
		DOUT => open,  				-- Delayed signal to IOB
		TOUT => open,  	 			-- Delayed Tristate signal to IOB
		-- logic interface
		ODATAIN => '0', 				-- Data from OLOGIC or OSERDES.
		DATAOUT => ADC1_DIN_D(I),-- Delayed data to D pin of ILOGIC2 or ISERDES2
		T => '1',              		-- Tristate control signal (input/output flow selection)
		DATAOUT2 => open, 	 		-- Secondary delayed data output. Unused.
		-- m&c
		CAL => IODELAY2_CAL,			-- Initiate calibration command
		BUSY => open,         		-- Busy after CAL. Unused.  Just for test purposes.
		CE => IODELAY2_CE(0),		-- 1-bit Enable increment/decrement
		CLK => CLK,           		-- Global clock input
		INC => IODELAY2_INC(0),		-- 1-bit Increment / Decrement input
		IOCLK0 => CLK,		 			-- primary clock input
		IOCLK1 => '0',	 				-- secondary clock input/frequency doubler.
		RST => '0'     				-- Reset to zero or 1/2 of total period
	);


	IODELAY2_002 : IODELAY2
	generic map (
		COUNTER_WRAPAROUND => "STAY_AT_LIMIT", -- STAY_AT_LIMIT or WRAPAROUND
		DATA_RATE => "SDR",                 -- SDR (calibrate delay to 1 bit = 1 clock period)
		DELAY_SRC => "IDATAIN",             -- IO, ODATAIN or IDATAIN (output is not delayed)
		IDELAY_MODE => "NORMAL",            -- Unsupported
		IDELAY_TYPE => "VARIABLE_FROM_ZERO",-- FIXED, DEFAULT, VARIABLE_FROM_ZERO, VARIABLE_FROM_HALF_MAX or
														-- DIFF_PHASE_DETECTOR
		IDELAY_VALUE => 0,						-- Amount of input delay (0-255) 
		IDELAY2_VALUE => 0,                 -- Amount of secondary input Delay (0-255) 
		ODELAY_VALUE => 0,                	-- Amount of output delay (0-255)
		SERDES_MODE => "NONE",              -- NONE, MASTER or SLAVE
		SIM_TAPDELAY_VALUE => 50            -- Amount of delay used for simulation in pS
	)
	port map (
		-- IOB interface
		IDATAIN => ADC2_DIN(I), -- Data Signal from IOB
		DOUT => open,  				-- Delayed signal to IOB
		TOUT => open,  	 			-- Delayed Tristate signal to IOB
		-- logic interface
		ODATAIN => '0', 				-- Data from OLOGIC or OSERDES.
		DATAOUT => ADC2_DIN_D(I),-- Delayed data to D pin of ILOGIC2 or ISERDES2
		T => '1',              		-- Tristate control signal (input/output flow selection)
		DATAOUT2 => open, 	 		-- Secondary delayed data output. Unused.
		-- m&c
		CAL => IODELAY2_CAL,			-- Initiate calibration command
		BUSY => open,         		-- Busy after CAL. Unused.  Just for test purposes.
		CE => IODELAY2_CE(1),		-- 1-bit Enable increment/decrement
		CLK => CLK,           		-- Global clock input
		INC => IODELAY2_INC(1), 	-- 1-bit Increment / Decrement input
		IOCLK0 => CLK,		 			-- primary clock input
		IOCLK1 => '0',	 				-- secondary clock input/frequency doubler.
		RST => '0'     				-- Reset to zero or 1/2 of total period
	);
end generate;

-- reclock the delayed samples for use by the user
RECLOCK_001: process(CLK)
begin
	if rising_edge(CLK) then
		ADC1_DOUT <= ADC1_DIN_D;
		ADC2_DOUT <= ADC2_DIN_D;
	end if;
end process;

--// ROUND-TRIP DELAY CALIBRATION --------------------------------------
-- Calibration is performed in two steps:
-- (1) IODELAY calibration which normalizes the IODELAY2 maximum delay to one bit period. 
-- The trigger is IODELAY2_CAL. It ends when IODELAY2/BUSY goes low. 
-- (2) The RT delay cancellation algorithm then takes over and adjust the IODELAY2
-- so that the rising edge of the ADC data strobe after IODELAY2 is aligned with the rising edge of CLK
-- This calibration is performed only once at power up or reset.

-- for calibration purposes, delay the strobe signal from the ADC by the same
-- amount as the incoming data through a IODELAY2 component. 
-- Then route the resulting signal out for comparison with reference clock
DCO_DELAY_x: for I in 0 to 1 generate	-- two ADCs
	IODELAY2_002b : IODELAY2
	generic map (
		COUNTER_WRAPAROUND => "STAY_AT_LIMIT", -- STAY_AT_LIMIT or WRAPAROUND
		DATA_RATE => "SDR",                 -- SDR (calibrate delay to 1 bit = 1 clock period)
		DELAY_SRC => "IDATAIN",             -- IO, ODATAIN or IDATAIN (output is not delayed)
		IDELAY_MODE => "NORMAL",            -- Unsupported
		IDELAY_TYPE => "VARIABLE_FROM_ZERO",             -- FIXED, DEFAULT, VARIABLE_FROM_ZERO, VARIABLE_FROM_HALF_MAX or
														-- DIFF_PHASE_DETECTOR
		IDELAY_VALUE => 0,						-- Amount of input delay (0-255) 
		IDELAY2_VALUE => 0,                 -- Amount of secondary input Delay (0-255) 
		ODELAY_VALUE => 0,                	-- Amount of output delay (0-255)
		SERDES_MODE => "NONE",              -- NONE, MASTER or SLAVE
		SIM_TAPDELAY_VALUE => 50            -- Amount of delay used for simulation in pS
	)
	port map (
		-- IOB interface
		IDATAIN => ADC_DCO(I), -- Data Signal from IOB
		DOUT => open,  				-- Delayed signal to IOB
		TOUT => open,  	 			-- Delayed Tristate signal to IOB
		-- logic interface
		ODATAIN => '0', 				-- Data from OLOGIC or OSERDES.
		DATAOUT => ADC_DCO_D(I),	-- Delayed data to D pin of ILOGIC2 or ISERDES2
		T => '1',      				-- Tristate control signal (input/output flow selection)
		DATAOUT2 => open, 	 		-- Secondary delayed data output. Unused.
		-- m&c
		CAL => IODELAY2_CAL,   		-- Initiate calibration command
		BUSY => IODELAY2_DCO_BUSY(I),  -- Busy after CAL
		CE => IODELAY2_CE(I),      -- 1-bit Enable increment/decrement
		CLK => CLK,           		-- Global clock input
		INC => IODELAY2_INC(I),    -- 1-bit Increment / Decrement input
		IOCLK0 => CLK,			 		-- primary clock input
		IOCLK1 => '0',	 				-- secondary clock input/frequency doubler. Needed for DDR.
		RST => '0'     				-- Reset to zero or 1/2 of total period
	);
		
	-- reclock the delayed samples 
	RECLOCK_002: process(CLK)
	begin
		if rising_edge(CLK) then
			ADC_DCO_D2(I) <= ADC_DCO_D(I);
		end if;
	end process;
		
end generate;

--//------- ROUND TRIP DELAY CALIBRATION ----------------------------------------------
-- The objective is to find out the best input delay to sample the (delayed) read data
-- at the optimum time (i.e. in the middle of the data bit).

-- The algorithm's first step is to build a time-of-arrival discriminator with a large output change when 
-- the delayed input data strobe is sampled at the optimum sampling instant. 
-- Resampling DCO data strobe slightly too early will generate a 0 whereas 
-- resampling slightly too late will generate a 1. 
-- This happens when resampling DCO data strobe with CLK 

CALIBRATION_001: for I in 0 to 1 generate   

	TOA_LATE(I) <= '1' when (ADC_DCO_D2(I) = '1') else '0';
									
	-- The self-calibration algorithm next step is to increase/decrease the input delay around 
	-- a center value where the discriminator detects a large output change from TOA early to TOA late
	-- The same delay is applied to the input data and input data strobe.
	DELAY_INC_GEN: process(CLK)
	begin
		if rising_edge(CLK) then
			if (CAL_STATE = 2) then
				if (TOA_LATE(I) = '1') then
					-- decrease delay until we reach a satisfactory timing
					IODELAY2_CE(I) <= '1';
					IODELAY2_INC(I) <= '0';	-- decrement IODELAY2
				else
					-- increase delay until we reach a satisfactory timing
					-- Always increase delay unless it is clear decreasing is required (solves some start conditions)
					IODELAY2_CE(I) <= '1';
					IODELAY2_INC(I) <= '1';	-- increment IODELAY2
				end if;
			else
				IODELAY2_CE(I) <= '0';
			end if;
		end if;
	end process;
end generate;


-- trigger the IODELAY2 calibration once at power up or after reset. 
IODELAY2_TRIGGER1: process (CLK)
begin
	if rising_edge(CLK) then
		if (CAL_STATE = 0) then
			-- Start IODELAY2's internal calibration. 
			CAL_STATE <= "01";	-- trigger IODELAY2_CAL
		elsif (CAL_STATE = 1) and (IODELAY2_DCO_BUSY(0) = '0') then
			-- completed IODELAY2 internal calibration. Continuously calibrate.
			CAL_STATE <= "10";
		end if;
	end if;
end process;

IODELAY2_TRIGGER2: process (CLK)
begin
	if rising_edge(CLK) then
		if (CAL_STATE = 0) then
			-- Start IODELAY2's internal calibration. 
			IODELAY2_CAL <= '1' ;
		else
			IODELAY2_CAL <= '0' ;
		end if;
	end if;
end process;

--// TEST POINTS -------------------
TP(1) <= ADC_DCO_D(0);
TP(2) <= ADC_DCO_D(1);
TP(3) <= CLK;


end Behavioral;

