-------------------------------------------------------------------------
-- Project: SDRAM controller
-- This file: Clock generator (Virtex-4 specific)
--
-- Copyright (C) 2007 J. Ahrensfeld
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- For questions and ideas, please contact the author at jens@jayfield.org
--
--------------------------------------------------------------------------

library ieee, work;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.NUMERIC_STD.ALL;

use work.sdram_config.all;
use work.sdram_types.all;

Library UNISIM;
use UNISIM.vcomponents.all;

entity clockgen is
	generic
	(
		read_phaseshift : integer := 0;
		frequency_hz : integer := 100E6
	);
	port 
	(
		-- Clocks and Reset
		rst				: in std_logic;   -- external async reset, low active
		clk_in			: in std_logic;   -- system clock (e.g. 100MHz), from board
		clk_fb_in		: in std_logic;   -- feedback clock
		read_clk0_out	: out std_logic;  -- Clock for DDR-SDRAM data capture, dcm#1 output 0°
		read_clk270_out	: out std_logic;  -- Clock for DDR-SDRAM data capture, dcm#1 output 270°
		sys_clk0_out	: out std_logic;  -- System clock, dcm#0 output 0°
		sys_clk270_out	: out std_logic;  -- System clock, dcm#0 output 270°
		locked_out		: out std_logic;  -- DCM locked status
		error_out		: out std_logic   -- indicates DCM Errors
	);

end;

architecture tech of clockgen is

	constant CLKIN_PER 				: real := real(1000E6/frequency_hz);

	signal locked					: unsigned(1 downto 0);
	signal dcm_rst					: unsigned(1 downto 0);
	signal read_clk0				: std_logic;
	signal read_clk270				: std_logic;
	signal read_clk0_bufg			: std_logic;
	signal read_clk270_bufg			: std_logic;
	signal sys_clk0					: std_logic;
	signal sys_clk270				: std_logic;
	signal sys_clk0_bufg			: std_logic;
	signal sys_clk270_bufg			: std_logic;
	signal dcm1_locked, dcm2_locked	: unsigned(2 downto 0);
	signal cnt_q 					: unsigned(4 downto 0);

	type STATE_TYPE is (s0, s1, s2, s3, s4);
	signal state_q : STATE_TYPE;

begin 

	read_clk0_out	<= read_clk0_bufg;
	read_clk270_out	<= read_clk270_bufg;
	sys_clk0_out	<= sys_clk0_bufg;
	sys_clk270_out	<= sys_clk270_bufg;

bufg11: bufg
	port map
	(
		o  => sys_clk0_bufg,  
		i  => sys_clk0
	);

bufg12: bufg
	port map (
		o  => sys_clk270_bufg,  
		i  => sys_clk270
	);

bufg13: bufg
	port map
	(
		o  => read_clk0_bufg,  
		i  => read_clk0
	);

bufg14: bufg
	port map (
		o  => read_clk270_bufg,  
		i  => read_clk270
	);

	-------------------------------------------------------------------------------------------------------
	-- Clock generation
	-------------------------------------------------------------------------------------------------------
   
inst_DCM_BASE_0 : DCM_BASE
	generic map (
		CLKDV_DIVIDE => 2.0, -- Divide by: 1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5,6.0,6.5
				   --   7.0,7.5,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0 or 16.0
		CLKFX_DIVIDE => 1,   -- Can be any interger from 1 to 32
		CLKFX_MULTIPLY => 4, -- Can be any integer from 2 to 32
		CLKIN_DIVIDE_BY_2 => FALSE, -- TRUE/FALSE to enable CLKIN divide by two feature
		CLKIN_PERIOD => CLKIN_PER, -- Specify period of input clock in ns from 1.25 to 1000.00
		CLKOUT_PHASE_SHIFT => "FIXED", -- Specify phase shift mode of NONE or FIXED
		CLK_FEEDBACK => "1X",         -- Specify clock feedback of NONE or 1X
		DCM_AUTOCALIBRATION => TRUE, -- DCM calibrartion circuitry TRUE/FALSE
		DCM_PERFORMANCE_MODE => "MAX_SPEED",   -- Can be MAX_SPEED or MAX_RANGE
		DESKEW_ADJUST => "SOURCE_SYNCHRONOUS", -- SOURCE_SYNCHRONOUS, SYSTEM_SYNCHRONOUS or
									 --   an integer from 0 to 15
		DFS_FREQUENCY_MODE => "LOW",   -- LOW or HIGH frequency mode for frequency synthesis
		DLL_FREQUENCY_MODE => "LOW",   -- LOW, HIGH, or HIGH_SER frequency mode for DLL
		DUTY_CYCLE_CORRECTION => TRUE, -- Duty cycle correction, TRUE or FALSE
		FACTORY_JF => X"F0F0",          -- FACTORY JF Values Suggested to be set to X"F0F0" 
		PHASE_SHIFT => 0, -- Amount of fixed phase shift from -255 to 1023
		STARTUP_WAIT => FALSE) -- Delay configuration DONE until DCM LOCK, TRUE/FALSE
	port map (
		CLK0 => sys_clk0,         -- 0 degree DCM CLK ouptput
		CLK180 => open,     -- 180 degree DCM CLK output
		CLK270 => sys_clk270,     -- 270 degree DCM CLK output
		CLK2X => open,       -- 2X DCM CLK output
		CLK2X180 => open, -- 2X, 180 degree DCM CLK out
		CLK90 => open,       -- 90 degree DCM CLK output
		CLKDV => open,       -- Divided DCM CLK out (CLKDV_DIVIDE)
		CLKFX => open,       -- DCM CLK synthesis out (M/D)
		CLKFX180 => open, -- 180 degree CLK synthesis out
		LOCKED => locked(0),     -- DCM LOCK status output
		CLKFB => clk_fb_in,       -- DCM clock feedback
		CLKIN => clk_in,       -- Clock input (from IBUFG, BUFG or DCM)
		RST => dcm_rst(0)            -- DCM asynchronous reset input
	);

	inst_DCM_BASE_1 : DCM_BASE
	generic map (
		CLKDV_DIVIDE => 2.0, -- Divide by: 1.5,2.0,2.5,3.0,3.5,4.0,4.5,5.0,5.5,6.0,6.5
				   --   7.0,7.5,8.0,9.0,10.0,11.0,12.0,13.0,14.0,15.0 or 16.0
		CLKFX_DIVIDE => 1,   -- Can be any interger from 1 to 32
		CLKFX_MULTIPLY => 4, -- Can be any integer from 2 to 32
		CLKIN_DIVIDE_BY_2 => FALSE, -- TRUE/FALSE to enable CLKIN divide by two feature
		CLKIN_PERIOD => CLKIN_PER, -- Specify period of input clock in ns from 1.25 to 1000.00
		CLKOUT_PHASE_SHIFT => "FIXED", -- Specify phase shift mode of NONE or FIXED
		CLK_FEEDBACK => "1X",         -- Specify clock feedback of NONE or 1X
		DCM_AUTOCALIBRATION => TRUE, -- DCM calibrartion circuitry TRUE/FALSE
		DCM_PERFORMANCE_MODE => "MAX_SPEED",   -- Can be MAX_SPEED or MAX_RANGE
		DESKEW_ADJUST => "SYSTEM_SYNCHRONOUS", -- SOURCE_SYNCHRONOUS, SYSTEM_SYNCHRONOUS or
									 --   an integer from 0 to 15
		DFS_FREQUENCY_MODE => "LOW",   -- LOW or HIGH frequency mode for frequency synthesis
		DLL_FREQUENCY_MODE => "LOW",   -- LOW, HIGH, or HIGH_SER frequency mode for DLL
		DUTY_CYCLE_CORRECTION => TRUE, -- Duty cycle correction, TRUE or FALSE
		FACTORY_JF => X"F0F0",          -- FACTORY JF Values Suggested to be set to X"F0F0" 
		PHASE_SHIFT => read_phaseshift, -- Amount of fixed phase shift from -255 to 1023
		STARTUP_WAIT => FALSE) -- Delay configuration DONE until DCM LOCK, TRUE/FALSE
	port map (
		CLK0 => read_clk0,         -- 0 degree DCM CLK ouptput
		CLK180 => open,     -- 180 degree DCM CLK output
		CLK270 => read_clk270,     -- 270 degree DCM CLK output
		CLK2X => open,       -- 2X DCM CLK output
		CLK2X180 => open, -- 2X, 180 degree DCM CLK out
		CLK90 => open,       -- 90 degree DCM CLK output
		CLKDV => open,       -- Divided DCM CLK out (CLKDV_DIVIDE)
		CLKFX => open,       -- DCM CLK synthesis out (M/D)
		CLKFX180 => open, -- 180 degree CLK synthesis out
		LOCKED => locked(1),     -- DCM LOCK status output
		CLKFB => read_clk0_bufg,       -- DCM clock feedback
		CLKIN => clk_fb_in,       -- Clock input (from IBUFG, BUFG or DCM)
		RST => dcm_rst(1)            -- DCM asynchronous reset input
	);

	dcm_rst(1) <= not locked(0);
	
dcm_fsm:
	process(rst, clk_in)
	begin
		if rst = '1' then
			state_q <= s0;
			dcm_rst(0) <= '0';
			cnt_q <= "11111";
			error_out <= '0';
			locked_out <= '0';
			dcm1_locked <= (others => '0');
			dcm2_locked <= (others => '0');
		elsif rising_edge(clk_in) then
			case state_q is
			when s0 =>
				state_q <= s1;

			when s1 =>
			cnt_q <= cnt_q - 1;
				if cnt_q = 0 then
					dcm_rst(0) <= '1';
					state_q <= s2;
				end if;

			when s2 =>
				cnt_q <= cnt_q - 1;
				if cnt_q = 0 then
					dcm_rst(0) <= '0';
					state_q <= s3;
				end if;

			when s3 =>
				if dcm1_locked(2)='1' and dcm2_locked(2)='1' then
					state_q <= s4;
					error_out <= '0';
				end if;

			when s4 =>
				if dcm1_locked(2)='1' and dcm2_locked(2)='1' then
					locked_out <= '1';
				else
					error_out <= '1';
				end if;
			end case;

			-- synchronize 'dcm1_locked'
			dcm1_locked <= dcm1_locked(1 downto 0) & locked(0);
			dcm2_locked <= dcm2_locked(1 downto 0) & locked(1);

		end if;
	end process;

end architecture tech;


