--=============================================================================
--! @file logic_clocks_rtl.vhd
--=============================================================================
--
-------------------------------------------------------------------------------
-- --
-- University of Bristol, High Energy Physics Group.
-- --
------------------------------------------------------------------------------- --
-- VHDL Architecture fmc_mTLU_lib.logic_clocks.rtl
--
--! @brief Generates 160MHz , 640MHz clocks from an incoming 40MHz clock. \n
--! Can switch between clock generated from on-board Xtal ( clk_logic_xtal ) and external clock\n
--! Can also output clock to external clock pins.
--!
--! @author David Cussans , David.Cussans@bristol.ac.uk
--
--! @date 14:20:26 11/14/12
--
--! @version v0.1
--
--! @details
--! Address map:
--! 0x00000000 - control/status register:
--!              bit-0 - PLL locked ( 1 = locked )
--!              bit-1 - buff-PLL locked ( 1 = locked )
--!              bit-2 - use xtal for logic ( 1 = xtal , 0= external)
--!              bit-3 - clock connector is an input ( 1=input , 0 = output)
--! 0x00000001 - reset logic. Write to bit-zero to send reset.
--!
--!
--! <b>Dependencies:</b>\n
--!
--! <b>References:</b>\n
--!
--! <b>Modified by:</b>\n
--! Author: 
-------------------------------------------------------------------------------
--! \n\n<b>Last changes:</b>\n
-------------------------------------------------------------------------------
--! @todo <next thing to do> \n
--! <another thing to do> \n
--
--------------------------------------------------------------------------------
-- 
-- Created using using Mentor Graphics HDL Designer(TM) 2010.3 (Build 21)
--
-- Based on output of Xilinx Coregen and Alvro Dosil TLU code.
------------------------------------------------------------------------------
-- "Output    Output      Phase     Duty      Pk-to-Pk        Phase"
-- "Clock    Freq (MHz) (degrees) Cycle (%) Jitter (ps)  Error (ps)"
------------------------------------------------------------------------------
-- CLK_OUT1___640.000______0.000______50.0______175.916____213.982
-- CLK_OUT2___160.000______0.000______50.0______223.480____213.982
-- CLK_OUT3____40.000______0.000______50.0______306.416____213.982
--
------------------------------------------------------------------------------
-- "Input Clock   Freq (MHz)    Input Jitter (UI)"
------------------------------------------------------------------------------
-- __primary__________40.000____________0.010

LIBRARY ieee;
USE ieee.std_logic_1164.all;
USE ieee.numeric_std.all;

USE work.ipbus.all;

library unisim;
use unisim.vcomponents.all;

ENTITY logic_clocks IS
   PORT( 
      ipbus_clk_i           : IN     std_logic;
      ipbus_i               : IN     ipb_wbus;
      ipbus_reset_i         : IN     std_logic;
      Reset_i               : IN     std_logic;
      clk_logic_xtal_i      : IN     std_logic;   -- ! 40MHz clock from onboard xtal
      clk_16x_logic_o       : OUT    std_logic;   -- 640MHz clock
      clk_4x_logic_o        : OUT    std_logic;   -- 160MHz clock
      ipbus_o               : OUT    ipb_rbus;
      strobe_16x_logic_o    : OUT    std_logic;   -- strobes once every 4 cycles of clk_16x
      strobe_4x_logic_o     : OUT    std_logic;   -- one pulse every 4 cycles of clk_4x
      extclk_p_b            : INOUT  std_logic;   -- either external clock in, or a clock being driven out
      extclk_n_b            : INOUT  std_logic;
      DUT_clk_o	          : OUT    std_logic;
      logic_clocks_locked_o : OUT    std_logic;
      logic_reset_o         : OUT    std_logic    -- Goes high to reset counters etc. Sync with clk_4x_logic
   );

-- Declarations

END ENTITY logic_clocks ;

--
ARCHITECTURE rtl OF logic_clocks IS

  signal s_clk40 , s_clk40_internal : std_logic;
  signal s_clk160 ,s_clk160_internal : std_logic;
  signal s_clk640 , s_clk640_internal : std_logic;
  signal s_clk40_out : std_logic;       -- Clock generated by DDR register to feed out of chip.
--  signal s_clk40_copy : std_logic;       -- Clock generated by DDR register to feed out of chip.
  
  -- Eventually connect up clock control & status lines to IPBus
  --signal s_extclk_is_input : std_logic := '0';
--  signal s_extclk_is_input_buf : std_logic := '1';
  signal s_clk_is_xtal, s_clk_is_ext_buf : std_logic := '1';
--  signal s_logic_clk_rst : std_logic := '0';
  signal s_locked_pll, s_locked_bufpll : std_logic;
    
  signal s_clk : std_logic;
  signal s_DUT_Clk, s_DUT_Clk_o, s_DUT_ClkG : std_logic;
  signal s_extclk, s_extclkG : std_logic;
  -- signal s_clk_d1 , s_strobe_4x_p1 , s_strobe_4x_logic  : std_logic;
  signal s_clkfbout_buf , s_clkfbout : std_logic;

  signal s_strobe_generator  : std_logic_vector(3 downto 0) := "1000";  -- ! Store state of ring buffer to generate strobe
  signal s_logic_clk_generator : std_logic_vector(3 downto 0) := "1100";  --! Stores state of 40MHz "clock"
  signal s_strobe_fb : std_logic := '0';

  signal s_logic_reset_ipb, s_logic_reset_ipb_d1 : std_logic := '0';  
                                        -- ! Reset signal in IPBus clock domain
  signal s_logic_reset , s_logic_reset_d1 , s_logic_reset_d2 , s_logic_reset_d3 : std_logic := '0';  
                                        -- ! reset signal clocked onto logic-clock domain.
  signal s_ipbus_ack : std_logic := '0';
  
  signal s_reset_pll : std_logic := '0';
  
  --signal s_Reset : std_logic := '0';  
    -- ! Global Reset signal

  signal  s_extclk_internal  : std_logic := '0';
    
  signal s_clock_status_ipb : std_logic_vector( ipbus_o.ipb_rdata'range );   --! Hold status of clocks
  
BEGIN
	
  -----------------------------------------------------------------------------
  -- IPBus write
  -----------------------------------------------------------------------------
  ipbus_write: process (ipbus_clk_i)
  begin  -- process ipb_clk_i
    if rising_edge(ipbus_clk_i) then

      s_logic_reset_ipb <= '0';
      
       if (ipbus_i.ipb_strobe = '1' and ipbus_i.ipb_write = '1') then

         case ipbus_i.ipb_addr(1 downto 0) is
           when "00" =>
             s_clk_is_xtal <= ipbus_i.ipb_wdata(2) ; -- select clock source
             
           when "01" =>
             s_logic_reset_ipb <= ipbus_i.ipb_wdata(0) ; -- write to reset
           when others => null;
         end case;
          
       end if;

      -- register reset signal to aid timing.
      s_logic_reset_ipb_d1 <= s_logic_reset_ipb;
      
      s_ipbus_ack <= ipbus_i.ipb_strobe and not s_ipbus_ack;

      -- register the clock status signals onto IPBus domain.
      --s_clock_status_ipb <=  x"0000000" & s_extclk_is_input & s_clk_is_xtal & s_locked_bufpll & s_locked_pll;
		s_clock_status_ipb <=  x"0000000" & '0' & s_clk_is_xtal & s_locked_bufpll & s_locked_pll;
    end if;
  end process ipbus_write;

  ipbus_o.ipb_ack <= s_ipbus_ack;
  ipbus_o.ipb_err <= '0';

  -----------------------------------------------------------------------------
  -- IPBUS read
  -----------------------------------------------------------------------------
  with ipbus_i.ipb_addr(1 downto 0) select
    ipbus_o.ipb_rdata <=
     s_clock_status_ipb  when "00",
    (others => '1')      when others;


  -----------------------------------------------------------------------------
  -- Generate reset signal on logic-clock domain
  -- This relies on the IPBus clock being much slower than the 4x logic clock.
  -----------------------------------------------------------------------------
  p_reset: process (s_clk160_internal)
  begin  -- process p_reset
    if rising_edge(s_clk160_internal) then
      s_logic_reset_d1 <= s_logic_reset_ipb_d1;
      s_logic_reset_d2 <= s_logic_reset_d1;
      s_logic_reset_d3 <= s_logic_reset_d2;
      s_logic_reset <= s_logic_reset_d2 and ( not s_logic_reset_d3); 
    end if;
  end process p_reset;

  logic_reset_o <= s_logic_reset;
  logic_clocks_locked_o <= s_locked_bufpll and s_locked_pll;


   ext_clk_obuf : IOBUFDS
   generic map (
      IOSTANDARD => "BLVDS_25")
   port map (
      O => s_extclk,     			-- Buffer output
      IO => extclk_p_b,   			-- Diff_p inout (connect directly to top-level port)
      IOB => extclk_n_b, 			-- Diff_n inout (connect directly to top-level port)
      I => s_DUT_Clk_o,    		-- Buffer input
      T => s_clk_is_ext_buf     -- 3-state enable input, high=input, low=output
   );
  
  
  ddr_for_extclk_output : 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 => s_DUT_Clk_o, -- 1-bit output data
      C0 => clk_logic_xtal_i, -- 1-bit clock input
      C1 => not clk_logic_xtal_i, -- 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
   );
	
	--When an ODDR2 primitive is used in conjunction with a 3-state output, the T control pin must
	--also use an ODDR2 primitive configured in the same mode as the ODDR2 primitive used for data
	--output.
	ddr_for_40MHz_tristate : 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 => s_clk_is_ext_buf, -- 1-bit output data
      C0 => clk_logic_xtal_i, -- 1-bit clock input
      C1 => not clk_logic_xtal_i, --not s_clk160_internal, -- 1-bit clock input
      CE => '1',  -- 1-bit clock enable input
      D0 => not s_clk_is_xtal,   -- 1-bit data input (associated with C0)
      D1 => '0', --not s_clk_is_xtal,   -- 1-bit data input (associated with C1)
      R => '0',    -- 1-bit reset input
      S => '0'     -- 1-bit set input
   );
  
  
  --! Clock selection
  clock_mux : BUFGMUX
   generic map (
      CLK_SEL_TYPE => "SYNC"  -- Glitchles ("SYNC") or fast ("ASYNC") clock switch-over
   )
   port map (
      O => s_DUT_Clk,   		-- 1-bit output: Clock buffer output
      I0 => s_extclkG, 			-- 1-bit input: Clock buffer input (S=0)
      I1 => clk_logic_xtal_i, -- 1-bit input: Clock buffer input (S=1)
      S => s_clk_is_xtal    	-- 1-bit input: Clock buffer select
   );
  
  extclk_buf : BUFG
  port map(
    O => s_extclkG,
    I => s_extclk);
  
-- IBUFG_inst : IBUFG
--   generic map (
--      IBUF_LOW_PWR => TRUE, -- Low power (TRUE) vs. performance (FALSE) setting for referenced I/O standards
--      IOSTANDARD => "DEFAULT")
--   port map (
--      O => s_Reset, -- Clock buffer output
--      I => Reset_i  -- Clock buffer input (connect directly to top-level port)
--   );
  
  
  --! Clocking primitive
  -------------------------------------
  --! Instantiation of the PLL primitive
  pll_base_inst : PLL_BASE
  generic map
   (BANDWIDTH            => "OPTIMIZED",
    --CLK_FEEDBACK         => "CLKOUT0", --"CLKFBOUT",
    CLK_FEEDBACK         => "CLKFBOUT",
    COMPENSATION         => "SYSTEM_SYNCHRONOUS",
    DIVCLK_DIVIDE        => 1,
    CLKFBOUT_MULT        => 16,
    CLKFBOUT_PHASE       => 0.000,
    CLKOUT0_DIVIDE       => 1, -- 1-->2 move from 640 to 320
    CLKOUT0_PHASE        => 0.000,
    CLKOUT0_DUTY_CYCLE   => 0.500,
    CLKOUT1_DIVIDE       => 4, -- 4-->8 move from 160 to 80
    CLKOUT1_PHASE        => 0.000,
    CLKOUT1_DUTY_CYCLE   => 0.500,
    CLKOUT2_DIVIDE       => 16, -- 16--> 32 move from 40 to 20
    CLKOUT2_PHASE        => 0.000,
    CLKOUT2_DUTY_CYCLE   => 0.500,
    CLKIN_PERIOD         => 25.000,
    REF_JITTER           => 0.010)
  port map(
    -- Output clocks
    CLKFBOUT            => s_clkfbout,
    CLKOUT0             => s_clk640,
    CLKOUT1             => s_clk160,
    CLKOUT2             => s_clk40,
    CLKOUT3             => open,
    CLKOUT4             => open,
    CLKOUT5             => open,
    -- Status and control signals
    LOCKED              => s_locked_pll,
--    RST                 => s_logic_clk_rst,
    RST                 => s_reset_pll,
    -- Input clock control
--    CLKFBIN             => s_clkfbout_buf,
    CLKFBIN             => s_clkfbout,
    CLKIN               => clk_logic_xtal_i);

 s_reset_pll <= Reset_i or s_logic_reset; 
 
  -- Buffer the 16x clock and generate the ISERDES strobe signal
   BUFPLL_inst : BUFPLL
   generic map (
      DIVIDE => 4)
   port map (
      IOCLK  => s_clk640_internal,          -- 1-bit output: Output I/O clock
      LOCK   => s_locked_bufpll,            -- 1-bit output: Synchronized LOCK output
      SERDESSTROBE => strobe_16x_logic_O,   -- 1-bit output: Output SERDES strobe (connect to ISERDES2/OSERDES2)
      GCLK   => s_clk160_internal,          -- 1-bit input: BUFG clock input
      LOCKED => s_locked_pll,               -- 1-bit input: LOCKED input from PLL
      PLLIN  => s_clk640                    -- 1-bit input: Clock input from PLL
   );

	clk_16x_logic_o <= s_clk640_internal;
	DUT_clk_o <= s_DUT_clk;


  
  -- Generate a copy of the 
  -- purpose: generates a strobe in time with rising edge of s_clk
  -- type   : combinational
  -- inputs : s_clk160 , s_clk
  -- outputs: strobe_4x_logic_o
  -- Can't use a clock signal as a combinatorial signal. Hence the baroque
  -- method of generating a strobe. Add a mechanism to restart if the '1' gets
  -- lost ....
  generate_4x_strobe: process (s_clk160)-- , s_clk40_out)
  begin  -- process generate_4x_strobe
    if rising_edge(s_clk160) then
      if s_logic_reset = '1' then
        s_strobe_generator <= "1000";
        s_logic_clk_generator <= "1100";
      else
        s_strobe_generator <= s_strobe_generator(2 downto 0) & s_strobe_generator(3);       
        s_logic_clk_generator <= s_logic_clk_generator(2 downto 0) & s_logic_clk_generator(3);
      end if;

    end if;
  end process generate_4x_strobe;
  
  strobe_4x_logic_o <=  s_strobe_generator(3);
  s_clk40_out <= s_logic_clk_generator(3);

  -- Try fbout out again....
  -- buffer feedback clock
  -------------------------------------
  --clkf_buf : BUFG
  --port map(
  --  O => s_clkfbout_buf,
  --  I => s_clkfbout);
  ---
  
--  clkf_buf : BUFIO2FB
--  port map (
--    O => s_clkfbout_buf, -- 1-bit output: Output feedback clock (connect to feedback input of DCM/PLL)
--    I => s_clk640_internal  -- 1-bit input: Feedback clock input (connect to input port)
--  );

  -- buffer 160MHz (4x) clock
  --------------------------------------
  clk160_o_buf : BUFG
  port map(
    O   => s_clk160_internal,
    I   => s_clk160);
    
  clk_4x_logic_o <= s_clk160_internal;
 
--   -- buffer 40MHz (1x) clock
--  --------------------------------------
--  clk40_o_buf : BUFG
--  port map(
--    O   => s_clk40_internal,
--    I   => s_clk40);

--  clk_logic_o <= s_clk40_out;

END ARCHITECTURE rtl;

