/////////////////////////////////////////////////////////////////////
//
// Copyright 2017 Ettus Research, A National Instruments Company
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: n3xx_clocking.v
//
// Purpose:
//
// First, instantiate clock input buffers on all clocks to provide termination
// for the PCB traces. This file also includes the MMCM for generating meas_clk at
// specific rates and a global buffer for the reference clock to be able to use it
// directly within and outside of this module.
//
// Second, PPS inputs from the back panel (called external) and the GPSDO are captured by
// the Reference Clock. Selection is performed amongst these and the internally-generated
// options.
//
// NOTE: BUFGs are NOT instantiated on the following clocks, denoted by the _buf suffix:
//   wr_refclk_buf, netclk_buf, gige_refclk_buf, xgige_refclk_buf
//
//////////////////////////////////////////////////////////////////////

module n3xx_clocking (
  // Input buffers for clocks
  input  enable_ref_clk_async, // enables the ref_clk BUFG (driven async to ref_clk)
  input  FPGA_REFCLK_P, FPGA_REFCLK_N,
  output ref_clk,

  input  WB_20MHz_P, WB_20MHz_N,
  output wr_refclk_buf,

  input  NETCLK_REF_P, NETCLK_REF_N,
  output netclk_buf,

  input  NETCLK_P, NETCLK_N,
  output gige_refclk_buf,

  input  MGT156MHZ_CLK1_P, MGT156MHZ_CLK1_N,
  output xgige_refclk_buf,

  // Measurement Clock Generation
  input  misc_clks_ref,
  output meas_clk,
  output ddr3_dma_clk,
  input  misc_clks_reset,
  output reg misc_clks_locked,

  // CE Clock
  input clk200,
  output ce_clk,

  // PPS Capture & Selection
  input       ext_pps_from_pin,
  input       gps_pps_from_pin,
  input [3:0] pps_select,
  output reg  pps_refclk
  );

  // Clock Buffering and Generation : ///////////////////////////////////////////////////
  //
  // Manually instantiate input buffers on all clocks, and a global buffer on the
  // Reference Clock for use in the rest of the design. All other clocks must have
  // global buffers other places, since the declarations here are for SI purposes.
  //
  ///////////////////////////////////////////////////////////////////////////////////////

  wire ref_clk_buf;

  // FPGA Reference Clock Buffering
  //
  // Only require an IBUF and BUFG here, since an MMCM is (thankfully) not needed
  // to meet timing with the PPS signal.
  IBUFGDS ref_clk_ibuf (
    .O(ref_clk_buf),
    .I(FPGA_REFCLK_P),
    .IB(FPGA_REFCLK_N)
  );

  // BUFG ref_clk_bufg (
    // .I(ref_clk_buf),
    // .O(ref_clk)
  // );
  WrapBufg #(
     .kEnableIsAsync(1'b1)
  ) ref_clk_bufg (
     .ClkIn(ref_clk_buf),
     .aCe(enable_ref_clk_async),
     .ClkOut(ref_clk)
  );

  // Buffers for SI Purposes
  //
  // Instantiate buffers on each of these differential clock inputs with DONT_TOUCH
  // attributes in order to preserve the internal termination regardless of whether
  // these clocks are used in the design.  The lack of termination would place the
  // voltage swings for these pins outside the acceptable range for the FPGA inputs.
  (* dont_touch = "true" *) IBUFGDS wr_refclk_ibuf (
    .I (WB_20MHz_P),
    .IB(WB_20MHz_N),
    .O (wr_refclk_buf)
  );

  (* dont_touch = "true" *) IBUFGDS netclk_ref_ibuf (
    .I (NETCLK_REF_P),
    .IB(NETCLK_REF_N),
    .O (netclk_buf)
  );

  // Same deal for the MGT reference clock buffers.
  (* dont_touch = "true" *) IBUFDS_GTE2 gige_refclk_ibuf (
    .ODIV2(),
    .CEB  (1'b0),
    .I (NETCLK_P),
    .IB(NETCLK_N),
    .O (gige_refclk_buf)
  );

  (* dont_touch = "true" *) IBUFDS_GTE2 ten_gige_refclk_ibuf (
    .ODIV2(),
    .CEB  (1'b0),
    .I (MGT156MHZ_CLK1_P),
    .IB(MGT156MHZ_CLK1_N),
    .O (xgige_refclk_buf)
  );

  // Measurement Clock MMCM Instantiation
  //
  // This must be an MMCM to hit the weird rates we need for meas_clk. It takes the
  // 166.6667 MHz clock from the PS and provides the correct meas_clk rate for the TDC.
  // BUFG is embedded in the MMCM files.
  //----------------------------------------------------------------------------
  //  Output     Output      Phase    Duty Cycle   Pk-to-Pk     Phase
  //   Clock     Freq (MHz)  (degrees)    (%)     Jitter (ps)  Error (ps)
  //----------------------------------------------------------------------------
  //     meas_clk___198.413______0.000______50.0______113.755____141.292
  // ddr3_dma_clk___303.819______0.000______50.0______105.705____141.292
  //
  //----------------------------------------------------------------------------
  // Input Clock   Freq (MHz)    Input Jitter (UI)
  //----------------------------------------------------------------------------
  // __primary________166.666667____________0.010

  wire misc_locked;

  misc_clock_gen misc_clock_gen_i (
    .clk_in       (misc_clks_ref),
    .meas_clk     (meas_clk),
    .ddr3_dma_clk (ddr3_dma_clk),
    .reset        (misc_clks_reset),
    .locked       (misc_locked)
  );

  // Compute Engine Clock
  //
  //----------------------------------------------------------------------------
  //  Output     Output      Phase    Duty Cycle   Pk-to-Pk     Phase
  //   Clock     Freq (MHz)  (degrees)    (%)     Jitter (ps)  Error (ps)
  //----------------------------------------------------------------------------
  // __ce_clk__266.66667______0.000______50.0______112.125____110.663
  //
  //----------------------------------------------------------------------------
  // Input Clock   Freq (MHz)    Input Jitter (UI)
  //----------------------------------------------------------------------------
  // __primary_____________200____________0.010

  wire ce_gen_clkfbout;
  wire ce_gen_clkout0;
  wire ce_gen_locked;

  PLLE2_ADV #(
    .BANDWIDTH         ("OPTIMIZED"),
    .COMPENSATION      ("ZHOLD"    ),
    .STARTUP_WAIT      ("FALSE"    ),
    .DIVCLK_DIVIDE     (3          ),
    .CLKFBOUT_MULT     (16         ),
    .CLKFBOUT_PHASE    (0.000      ),
    .CLKOUT0_DIVIDE    (4          ),
    .CLKOUT0_PHASE     (0.000      ),
    .CLKOUT0_DUTY_CYCLE(0.500      ),
    .CLKIN1_PERIOD     (5.000      )
  ) ce_clk_gen_i (
    .CLKFBOUT(ce_gen_clkfbout),
    .CLKOUT0 (ce_gen_clkout0 ),
    .CLKOUT1 (               ),
    .CLKOUT2 (               ),
    .CLKOUT3 (               ),
    .CLKOUT4 (               ),
    .CLKOUT5 (               ),
    .CLKFBIN (ce_gen_clkfbout),
    .CLKIN1  (clk200         ),
    .CLKIN2  (1'b0           ),
    .CLKINSEL(1'b1           ),
    .DADDR   (7'h0           ),
    .DCLK    (1'b0           ),
    .DEN     (1'b0           ),
    .DI      (16'h0          ),
    .DO      (               ),
    .DRDY    (               ),
    .DWE     (1'b0           ),
    .LOCKED  (ce_gen_locked  ),
    .PWRDWN  (1'b0           ),
    .RST     (misc_clks_reset)
  );

  BUFG ce_clk_buf (
    .O(ce_clk        ),
    .I(ce_gen_clkout0)
  );

  wire misc_locked_clk200;
  wire ce_lockec_clk200;

  synchronizer synchronizer_misc (
    .clk(clk200            ),
    .rst(1'b0              ),
    .in (misc_locked       ),
    .out(misc_locked_clk200)
  );

  synchronizer synchronizer_ce (
    .clk(clk200          ),
    .rst(1'b0            ),
    .in (ce_gen_locked   ),
    .out(ce_locked_clk200)
  );

  always @(posedge clk200) begin
    misc_clks_locked <= misc_locked_clk200 & ce_locked_clk200;
  end

  // PPS Capture and Generation : ///////////////////////////////////////////////////////
  //
  // The following shows the support matrix for PPS with respect to the
  // reference clock source and rate.
  //               _______________________________
  //  ____________|              PPS              |
  // |   Clocks   | External | FPGA  | GPSDO | WR |
  // |--------------------------------------------|
  // |External 10 |    x     |  x    |  (x)  |    |
  // |Internal 25 |          |  x    |       | x  |
  // |GPSDO    20 |          |       |   x   |    |
  // |--------------------------------------------|
  //
  // A note on external clock + GPSDO timing: The hardware doesn't prohibit this
  // use case, but GPSDO and external clock are guaranteed to be not matched, so
  // this will certainly cause a non-deterministic time alignment between devices.
  // Using this combination will still allow receiving samples, but there is no
  // guarantee regarding phase/time alignment whatsoever.
  //
  ///////////////////////////////////////////////////////////////////////////////////////

  wire pps_ext_refclk;
  wire pps_gps_refclk;
  wire [3:0] pps_select_refclk;

  // Generate two internal PPS signals, each with a 25% duty cycle, based on
  // 10 MHz and 25 MHz Reference Clock rates. Only one will be used at a time.
  wire int_pps_10mhz_refclk;
  pps_generator #(
     .CLK_FREQ(32'd10_000_000), .DUTY_CYCLE(25)
  ) pps_gen_10 (
     .clk(ref_clk), .reset(1'b0), .pps(int_pps_10mhz_refclk)
  );
  wire int_pps_25mhz_refclk;
  pps_generator #(
     .CLK_FREQ(32'd25_000_000), .DUTY_CYCLE(25)
  ) pps_gen_25 (
     .clk(ref_clk), .reset(1'b0), .pps(int_pps_25mhz_refclk)
  );

  // Capture the external PPSs with a FF before sending them to the mux. To be safe,
  // we double-synchronize the external signals. If we meet timing (which we should)
  // then this is a two-cycle delay. If we don't meet timing, then it's 1-2 cycles
  // and our system timing is thrown off--but at least our downstream logic doesn't
  // go metastable!
  synchronizer #(
    .FALSE_PATH_TO_IN(0)
  ) ext_pps_dsync (
    .clk(ref_clk), .rst(1'b0), .in(ext_pps_from_pin), .out(pps_ext_refclk)
  );
  // Same deal with the GPSDO PPS input. Double-sync, then use it.
  synchronizer #(
    .FALSE_PATH_TO_IN(0)
  ) gps_pps_dsync (
    .clk(ref_clk), .rst(1'b0), .in(gps_pps_from_pin), .out(pps_gps_refclk)
  );

  // Synchronize the select bits over to the reference clock as well. Note that this is
  // a vector, so we could have some non-one-hot values creep through when changing.
  // See the note below as to why this is safe.
  synchronizer #(
    .FALSE_PATH_TO_IN(1),
    .WIDTH(4)
  ) pps_select_dsync (
    .clk(ref_clk), .rst(1'b0), .in(pps_select), .out(pps_select_refclk)
  );

  // Bit locations for the pps_select vector.
  localparam BIT_PPS_SEL_INT_10 = 0;
  localparam BIT_PPS_SEL_INT_25 = 1;
  localparam BIT_PPS_SEL_EXT    = 2;
  localparam BIT_PPS_SEL_GPSDO  = 3;

  // PPS MUX - selects internal or external PPS.
  always @(posedge ref_clk) begin

    // Encoding is one-hot on these bits. It is possible when the vector is being double-
    // synchronized to the reference clock domain that there could be multiple bits
    // asserted simultaneously. This is not problematic because the order of operations
    // in the following selection mux should take over and only one PPS should win.
    // This could result in glitches, but that is expected during ANY PPS switchover
    // since the switch is performed asynchronously to the PPS signal.
    if (pps_select_refclk[BIT_PPS_SEL_INT_10])
      pps_refclk <= int_pps_10mhz_refclk;
    else if (pps_select_refclk[BIT_PPS_SEL_INT_25])
      pps_refclk <= int_pps_25mhz_refclk;
    else if (pps_select_refclk[BIT_PPS_SEL_EXT])
      pps_refclk <= pps_ext_refclk;
    else if (pps_select_refclk[BIT_PPS_SEL_GPSDO])
      pps_refclk <= pps_gps_refclk;
    else
      pps_refclk <= pps_ext_refclk; // Compatibility with old SW stacks, pps_select_refclk = 0 = external

  end

endmodule
