--=============================================================================
--! @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

  signal s_enable_strobes : std_logic;  -- take high to start strobes running.

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 or (not s_locked_pll);

  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.875,  -- 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);

  -- TODO: Replace PLL_BASE ( Spartan6 etc. with PLLE2_ADV - synthesis tools
  -- seem to cope but ........
-- plle2_adv_instL PLLE2_ADV
  --  generic map
  --  (BANDWIDTH             => "OPTIMIZED",
  --    COMPENSATION          => "ZHOLD",
  --    STARTUP_WAIT          => "FALSE",
  --    DIVCLK_DIVIDE         => 1,
  --    CLKFBOUT_MULT         => 24,
  --    CLKFBOUT_PHASE        => 0.000,
  --    CLKOUT0_DIVIDE        => 3,
  --    CLKOUT0_PHASE         => 0.000,
  --    CLKOUT0_DUTY_CYCLE    => 0.500,
  --    CLKOUT1_DIVIDE        => 6,
  --    CLKOUT1_PHASE         => 0.000,
  --    CLKOUT1_DUTY_CYCLE    => 0.500,
  --    CLKOUT2_DIVIDE        => 24,
  --    CLKOUT2_PHASE         => 16.875,
  --    CLKOUT2_DUTY_CYCLE    => 0.500,
  --    CLKIN1_PERIOD         => 25.000)
  --   port map (
  --    .CLKFBOUT            => s_clkfbout , 
  --    .CLKOUT0             => s_clk320,
  --    .CLKOUT1             => s_clk160,
  --    .CLKOUT2             => s_clk40,
  --    .CLKOUT3             => open,
  --    .CLKOUT4             => open,
  --    .CLKOUT5             => open,
  --     // Input clock control
  --    .CLKFBIN             => s_clkfbout , 
  --    .CLKIN1              => s_DUT_clk,
  --    .CLKIN2              => '0',
  --    -- Tied to always select the primary input clock
  --    .CLKINSEL            => '1',
  --    -- Ports for dynamic reconfiguration
  --    .DADDR               => (others => '0') , 
  --    .DCLK                => '0',
  --    .DEN                 => '0',
  --    .DI                  => (others => '0') , 
  --    .DO                  => open , 
  --    .DRDY                => open, 
  --    .DWE                 => '0', 
  --    -- Other control and status signals
  --    .LOCKED              => s_locked_pll,
  --    .PWRDWN              => '0',
  --    .RST                 => s_clkfbout );


  s_reset_pll <= Reset_i or s_logic_reset;


  DUT_clk_o <= s_DUT_clk;


  s_enable_strobes <= s_locked_pll when rising_edge(s_clk40);

  -- 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
      if (s_enable_strobes = '0') then
        s_clk40_delayed_160 <= (others => '0');
        strobe_4x_logic_o   <= '0';
      else
        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 if;
  end process generate_4x_strobe;

  -- Generate a strobe signal for 320MHz clock
  generate_8x_strobe : process (s_clk320_internal, s_clk40)
  begin  -- process generate_8x_strobe
    if rising_edge(s_clk320_internal) then
      if (s_enable_strobes = '0') then
        s_clk40_delayed_320 <= (others => '0');
        strobe_8x_logic_o   <= '0';
      else
        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 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;

