--=============================================================================
--! @file logic_clocks_rtl.vhd
--=============================================================================
--
-------------------------------------------------------------------------------
-- --
-- University of Bristol, High Energy Physics Group.
-- --
------------------------------------------------------------------------------- --
-- VHDL Architecture fmc_mTLU_lib.logic_clocks.rtl
--
--------------------------------------------------------------------------------
-- 
-- 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___320.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;

--! @brief Generates 160MHz , 320MHz clocks from an incoming 40MHz clock.
--! Also generates strobes, (one for 160MHz , one for 320MHz clock ) that
--! strobe once for each cycle of 40MHz
--!
--! @author David Cussans , David.Cussans@bristol.ac.uk
--
--! @date 14:20:26 11/14/12
--
--! @version v0.1
--
--! @details
--! \br <b> IPBus Address map:</b>
--! \br (decode 2 bits)
--! \li 0x00000000 - control/status register:
--! \li             bit-0 - PLL locked ( 1 = locked )
--! \li 0x00000001 - reset logic. Write to bit-zero to send reset.
--!
--!
ENTITY logic_clocks IS
    GENERIC( 
        g_USE_EXTERNAL_CLK : integer := 1
    );
    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 derived from onboard xtal
        clk_8x_logic_o       : OUT    std_logic;   --! 640MHz clock
        clk_4x_logic_o        : OUT    std_logic;   --! 160MHz clock
        ipbus_o               : OUT    ipb_rbus;
        strobe_8x_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
        DUT_clk_o             : OUT    std_logic;   --! 40MHz to DUTs
        logic_clocks_locked_o : OUT    std_logic;   --! Goes high if clocks locked.
        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 : std_logic;
  constant C_NUM_STROBE_TAPS : positive := 2;  --! Adjust to shift strobes relative to 40MHz clock edge
    signal s_clk40_delayed_160 : std_logic_vector(C_NUM_STROBE_TAPS downto 0); --! Shift register used to generate clock_4x strobe. Adjust length for correct alignment with incoming clock
    signal s_clk40_delayed_320 : std_logic_vector((2*C_NUM_STROBE_TAPS)+1 downto 0);  --! Shift register used to generate clock_8x strobe. Adjust length for correct alignment with incoming clock
    signal s_clk160 ,s_clk160_internal : std_logic;
    signal s_clk320 , s_clk320_internal : std_logic;

    signal s_locked_pll : std_logic;
    
    signal s_clk : std_logic;
    signal s_DUT_Clk : std_logic;

    signal s_clkfbout : std_logic;
    
    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 , s_logic_reset_d4 : std_logic := '0'; --! reset signal clocked onto logic-clock domain.
    attribute SHREG_EXTRACT: string;
    attribute SHREG_EXTRACT of s_logic_reset_d1: signal is "no"; -- Synchroniser not to be optimised into shreg
    attribute SHREG_EXTRACT of s_logic_reset_d2: signal is "no"; -- Synchroniser not to be optimised into shreg
    attribute SHREG_EXTRACT of s_logic_reset_d3: signal is "no"; -- Synchroniser not to be optimised into shreg
    attribute SHREG_EXTRACT of s_logic_reset_d4: signal is "no"; -- Synchroniser not to be optimised into shreg
    attribute SHREG_EXTRACT of s_clk40_delayed_160: signal is "no"; -- delay to help timing not to be optimised into shreg
    attribute SHREG_EXTRACT of s_clk40_delayed_320: signal is "no"; -- delay to help timing not to be optimised into shreg
    signal s_ipbus_ack : std_logic := '0';

    signal s_reset_pll : std_logic := '0';     -- ! PLL Reset signal
    
    signal s_clock_status_ipb : std_logic_vector( ipbus_o.ipb_rdata'range );   --! contains 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 "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" & '0' & '0' & '0' & s_locked_pll; -- The only useful bit is now the PLL lock status. 
    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_d4 <= s_logic_reset_d2 and ( not s_logic_reset_d3); 
        s_logic_reset <= s_logic_reset_d4;
    end if;
    end process p_reset;
    
    logic_reset_o <= s_logic_reset;

    logic_clocks_locked_o <= s_locked_pll;

    s_DUT_Clk <= clk_logic_xtal_i; 
  
    --! Clocking primitive
    -------------------------------------
    --! Instantiation of the PLL primitive
    pll_base_inst : PLL_BASE
    generic map
       (BANDWIDTH            => "OPTIMIZED",
        CLK_FEEDBACK         => "CLKFBOUT",
        COMPENSATION         => "SYSTEM_SYNCHRONOUS",
        DIVCLK_DIVIDE        => 1,
        CLKFBOUT_MULT        => 16,  -- Run PLL at 640MHz
        CLKFBOUT_PHASE       => 0.000,
        CLKOUT0_DIVIDE       => 2, -- CLK0 = 320MHz
        CLKOUT0_PHASE        => 0.000,
        CLKOUT0_DUTY_CYCLE   => 0.500,
        CLKOUT1_DIVIDE       => 4, -- CLK1 = 160MHz
        CLKOUT1_PHASE        => 0.000,
        CLKOUT1_DUTY_CYCLE   => 0.500,
        CLKOUT2_DIVIDE       => 16, -- CLK2 = 40MHz
        CLKOUT2_PHASE        => +16.900, -- Shift 40MHz clock later to avoid negative hold time in strobe generator
        CLKOUT2_DUTY_CYCLE   => 0.500,
        CLKIN_PERIOD         => 25.000,
        REF_JITTER           => 0.010)
    port map(
        -- Output clocks
        CLKFBOUT            => s_clkfbout,
        CLKOUT0             => s_clk320,
        CLKOUT1             => s_clk160,
        CLKOUT2             => s_clk40,
        CLKOUT3             => open,
        CLKOUT4             => open,
        CLKOUT5             => open,
        -- Status and control signals
        LOCKED              => s_locked_pll,
        RST                 => s_reset_pll,
        -- Input clock control
        CLKFBIN             => s_clkfbout,
        CLKIN               => s_DUT_clk);


    s_reset_pll <= Reset_i or s_logic_reset; 


    DUT_clk_o <= s_DUT_clk;


  
  -- Generate a strobe signal for 160MHz clock
    generate_4x_strobe: process (s_clk160_internal, s_clk40)
    begin  -- process generate_4x_strobe
      if rising_edge(s_clk160_internal) then
        s_clk40_delayed_160 <= s_clk40_delayed_160(s_clk40_delayed_160'left-1 downto 0) & s_clk40;
        strobe_4x_logic_o   <=  s_clk40_delayed_160(s_clk40_delayed_160'left-1) and not s_clk40_delayed_160(s_clk40_delayed_160'left);
      end if;
    end process generate_4x_strobe;

  -- Generate a strobe signal for 160MHz clock
    generate_8x_strobe: process (s_clk160_internal, s_clk40)
    begin  -- process generate_4x_strobe
      if rising_edge(s_clk320_internal) then
        s_clk40_delayed_320 <= s_clk40_delayed_320(s_clk40_delayed_320'left-1 downto 0) & s_clk40;
        strobe_8x_logic_o   <=  s_clk40_delayed_320(s_clk40_delayed_320'left-1) and not s_clk40_delayed_320(s_clk40_delayed_320'left);
      end if;
    end process generate_8x_strobe;
        

  -- 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 320MHz (8x) clock
  --------------------------------------    
    clk320_o_buf: BUFG
    port map (
        I => s_clk320,
        O => s_clk320_internal    
    );
    
    clk_8x_logic_o <= s_clk320_internal;
    
END ARCHITECTURE rtl;

