-------------------------------------------------------------
-- MSS copyright 2012-2013
--	Filename:  CLEAN_CLK2.VHD
-- Author: Alain Zarembowitch / MSS
--	Version: 0
--	Date last modified: 7/1/13
-- Inheritance: 	CLEAN_CLK.VHD Rev 1  7/1/13 
--
-- description:  'cleans' an input clock by making output clock cycles 
-- as regularly spaced as possible, with a duty cycle as close to 50% as possible.
-- Improved version of CLEAN_CLK. Minimizes the output clock correction gaps by using 
-- a higher precision PERIOD measurement.
---------------------------------------------------------------
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 CLEAN_CLK2 is
    port ( 
		--GLOBAL CLOCKS, RESET
		CLK : in std_logic;				-- synchronous clock
		SYNC_RESET: in std_logic;

		CLK_PULSE_IN: in std_logic;
			-- input clock (1-CLK-wide pulses)
		CLK_50DUTY_OUT: out std_logic;
			-- 'cleaned' output clock with regularly spaced cycles and duty cycle as close to 50% as possible
		CLK_PULSE_OUT: out std_logic;
			-- 'cleaned' output clock as short 1 CLK-wide pulses, as regularly spaced as possible
		
		ERROR: out std_logic
			-- 1 CLK pulse when input clock overtakes output clock. Abnormal condition.
			);
end entity;

architecture behavioral of CLEAN_CLK2 is
--------------------------------------------------------
--      COMPONENTS
--------------------------------------------------------
	COMPONENT MEASURE_INTERVAL
	PORT(
		CLK : IN std_logic;
		SYNC_RESET : IN std_logic;
		PULSE_IN : IN std_logic;          
		PERIOD : OUT std_logic_vector(15 downto 0);
		PERIOD_FULL_PRECISION: out std_logic_vector(23 downto 0);
		PERIOD_SAMPLE_CLK : OUT std_logic
		);
	END COMPONENT;
--------------------------------------------------------
--     SIGNALS
--------------------------------------------------------
signal PERIOD: std_logic_vector(23 downto 0) := (others => '0');
signal PERIOD_ADJ: std_logic_vector(23 downto 0) := (others => '0');
signal PERIOD_SAMPLE_CLK: std_logic := '0';
signal PERIOD_CNTR: std_logic_vector(23 downto 0) := (others => '0');
signal DIFF: std_logic_vector(15 downto 0) := (others => '0');
signal DIFF_D_MSB: std_logic := '0';
signal BLOCK_OUTPUT: std_logic := '0';
signal PERIOD_SAMPLE_CLK_D: std_logic := '0';
signal ERROR_local: std_logic := '0';
signal MEASURE_INTERVAL_RESET: std_logic := '0';

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

-- measure the average clock period
MEASURE_INTERVAL_RESET <= SYNC_RESET or ERROR_local;
MEASURE_INTERVAL_001: MEASURE_INTERVAL PORT MAP(
	CLK => CLK,
	SYNC_RESET => MEASURE_INTERVAL_RESET,
	PULSE_IN => CLK_PULSE_IN,
	PERIOD => open,
	PERIOD_FULL_PRECISION => PERIOD,	-- 16.8 format
	PERIOD_SAMPLE_CLK => PERIOD_SAMPLE_CLK	-- measurement is ready
);

-- remove 1/1024 or 1 to PERIOD to make the output clock always a little bit faster 
-- remove 1.0 so that PERIOD_CNTR(23:8) ranges from [0,int(PERIOD-1)]
-- than the input clock
ADJUST_PERIOD: process(CLK)
begin
	if rising_edge(CLK) then
		PERIOD_SAMPLE_CLK_D <= PERIOD_SAMPLE_CLK;
		
		if(PERIOD_SAMPLE_CLK = '1') then
			if(PERIOD(23 downto 10) = 0) then
				PERIOD_ADJ <= PERIOD - x"000101";
			else
				PERIOD_ADJ <= PERIOD - ext(PERIOD(23 downto 10), PERIOD'length) - x"000100";
			end if;
		end if;
	end if;
end process;

-- regenerate cleaner clock
CLK_GEN_001: process(CLK)
begin
	if rising_edge(CLK) then
		-- modulo-PERIOD counter
		if(PERIOD_SAMPLE_CLK_D = '1') then
			-- arm the counter
			PERIOD_CNTR <= PERIOD_ADJ;	
		elsif(PERIOD_CNTR(23 downto 8) = 0) then
			-- re-arm the counter
			PERIOD_CNTR <= PERIOD_CNTR + PERIOD_ADJ;	
			-- The output clock is a little bit faster as PERIOD is truncated. 
			-- The safety valve below will prevent the output clock from ever exceeding the input clock.
		else
			PERIOD_CNTR(23 downto 8) <= PERIOD_CNTR(23 downto 8) -1;
		end if;
	end if;
end process;

-- Safety valve to prevent the output clock from ever exceeding the input clock.
-- Track the difference between input and output cycles
SAFETY_001:  process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1')  or (PERIOD_SAMPLE_CLK_D = '1') then
			DIFF <= (others => '0');
		elsif(CLK_PULSE_IN = '1') and (PERIOD_CNTR(23 downto 8) = 0) and (BLOCK_OUTPUT = '0') then
			-- 1 clk in, 1 clk out. No change in the difference
		elsif(CLK_PULSE_IN = '1') then
			-- 1 clk in, no clk out.
			DIFF <= DIFF + 1;
		elsif(PERIOD_CNTR(23 downto 8) = 0) and (BLOCK_OUTPUT = '0') then
			-- no clk in, 1 clk out
			DIFF <= DIFF - 1;
		end if;
	end if;
end process;

-- block periodic output CLK if it would make the difference negative (i.e. more output clks than input clks).
BLOCK_OUTPUT <= '1' when (DIFF = 0) or (DIFF(15) = '1') else '0';

CLK_GEN_002: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1')  or (PERIOD_SAMPLE_CLK_D = '1') or (PERIOD_ADJ(23 downto 8) = 0) then
			CLK_50DUTY_OUT <= '0';
		elsif(PERIOD_CNTR(23 downto 8) = 0) and (BLOCK_OUTPUT = '0') then
			-- new period
			CLK_50DUTY_OUT <= '1';
		elsif(PERIOD_CNTR(22 downto 8) = PERIOD(23 downto 9)) then
			-- half-way point
			CLK_50DUTY_OUT <= '0';
		end if;
	end if;
end process;

CLK_GEN_003: process(CLK)
begin
	if rising_edge(CLK) then
		if(SYNC_RESET = '1')  or (PERIOD_SAMPLE_CLK_D = '1') or (PERIOD_ADJ(23 downto 8) = 0) then
			CLK_PULSE_OUT <= '0';
		elsif(PERIOD_CNTR(23 downto 8) = 0) and (BLOCK_OUTPUT = '0') then
			-- new period
			CLK_PULSE_OUT <= '1';
		else
			CLK_PULSE_OUT <= '0';
		end if;
	end if;
end process;

-- safety reset
-- If for any reason, DIFF increases beyond the ceiling value of 0x80, something is really wrong.
-- It should never happen. Maybe the input clock drifted and noone told us.
-- => Redo PERIOD computation.
RESET_GEN: process(CLK)
begin
	if rising_edge(CLK) then
		DIFF_D_MSB <= DIFF(15);
		if(DIFF_D_MSB = '0') and (DIFF(15) = '1') then
			-- output clock too slow w.r.t. input clock. something is wrong. Redo PERIOD computation.
			ERROR_local <= '1';
		else
			ERROR_local <= '0';
		end if;
	end if;
end process;
ERROR <= ERROR_local;

end architecture;
