/*******************************************************************************
 * Module: sens_hispi_clock
 * Date:2015-10-13  
 * Author: andrey     
 * Description: Recover iclk/iclk2x from the HiSPi differntial clock
 *
 * Copyright (c) 2015 Elphel, Inc .
 * sens_hispi_clock.v 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.
 *
 *  sens_hispi_clock.v 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/> .
 *******************************************************************************/
`timescale 1ns/1ps

module  sens_hispi_clock#(
    
    parameter SENS_PHASE_WIDTH=            8,      // number of bits for te phase counter (depends on divisors)
    parameter SENS_BANDWIDTH =             "OPTIMIZED",  //"OPTIMIZED", "HIGH","LOW"
    parameter CLKIN_PERIOD_SENSOR =        3.000, // input period in ns, 0..100.000 - MANDATORY, resolution down to 1 ps
    parameter CLKFBOUT_MULT_SENSOR =       3,      // 330 MHz --> 990 MHz
    parameter CLKFBOUT_PHASE_SENSOR =      0.000,  // CLOCK FEEDBACK phase in degrees (3 significant digits, -360.000...+360.000)
    parameter IPCLK_PHASE =                0.000,
    parameter IPCLK2X_PHASE =              0.000,
    parameter BUF_IPCLK =                 "BUFR",
    parameter BUF_IPCLK2X =               "BUFR",  

    parameter SENS_DIVCLK_DIVIDE =         1,            // Integer 1..106. Divides all outputs with respect to CLKIN
    parameter SENS_REF_JITTER1   =         0.010,        // Expected jitter on CLKIN1 (0.000..0.999)
    parameter SENS_REF_JITTER2   =         0.010,
    parameter SENS_SS_EN         =        "FALSE",      // Enables Spread Spectrum mode
    parameter SENS_SS_MODE       =        "CENTER_HIGH",//"CENTER_HIGH","CENTER_LOW","DOWN_HIGH","DOWN_LOW"
    parameter SENS_SS_MOD_PERIOD =         10000,        // integer 4000-40000 - SS modulation period in ns
    // Used with delay
    parameter IODELAY_GRP =             "IODELAY_SENSOR", // may need different for different channels?
    parameter integer IDELAY_VALUE =     0,
    parameter real REFCLK_FREQUENCY =    200.0,
    parameter HIGH_PERFORMANCE_MODE =   "FALSE",
    
    parameter HISPI_DELAY_CLK =           "FALSE",      
    parameter HISPI_MMCM =                "TRUE",
    parameter HISPI_CAPACITANCE =         "DONT_CARE",
    parameter HISPI_DIFF_TERM =           "TRUE",
    parameter HISPI_DQS_BIAS =            "TRUE",
    parameter HISPI_IBUF_DELAY_VALUE =    "0",
    parameter HISPI_IBUF_LOW_PWR =        "TRUE",
    parameter HISPI_IFD_DELAY_VALUE =     "AUTO",
    parameter HISPI_IOSTANDARD =          "DIFF_SSTL18_I" //"DIFF_SSTL18_II" for high current (13.4mA vs 8mA)

)(
    input        mclk,
    input        mrst,
    input  [7:0] phase,
    input        set_phase,
    input        load,      // only used when delay, not phase
    input        rst_mmcm,
    input        clp_p,
    input        clk_n,
    output       ipclk,   // 165 MHz
    output       ipclk2x, // 330 MHz
    output       ps_rdy,          // output
    output [7:0] ps_out,          // output[7:0] reg 
    output       locked_pxd_mmcm,
    output       clkin_pxd_stopped_mmcm, // output
    output       clkfb_pxd_stopped_mmcm // output
);
    wire         ipclk_pre;
    wire         ipclk2x_pre;     // output
    wire         clk_fb;
    wire         prst = mrst;
    wire         clk_in;
    wire         clk_int;
    wire         set_phase_w = (HISPI_DELAY_CLK == "TRUE") ? 1'b0: set_phase;
    wire   [7:0] phase_w  =    (HISPI_DELAY_CLK == "TRUE") ? 8'b0: phase;
    wire         ps_rdy_w;
    wire   [7:0] ps_out_w; 
    
    assign ps_rdy = (HISPI_DELAY_CLK == "TRUE") ? 1'b1 : ps_rdy_w;
    assign ps_out = (HISPI_DELAY_CLK == "TRUE") ? 8'b0 : ps_out_w;
    
    ibufds_ibufgds #(
        .CAPACITANCE      (HISPI_CAPACITANCE),
        .DIFF_TERM        (HISPI_DIFF_TERM),
        .DQS_BIAS         (HISPI_DQS_BIAS),
        .IBUF_DELAY_VALUE (HISPI_IBUF_DELAY_VALUE),
        .IBUF_LOW_PWR     (HISPI_IBUF_LOW_PWR),
        .IFD_DELAY_VALUE  (HISPI_IFD_DELAY_VALUE),
        .IOSTANDARD       (HISPI_IOSTANDARD)
    ) ibufds_ibufgds0_i (
        .O    (clk_int),      // output
        .I    (clp_p), // input
        .IB   (clk_n)  // input
    );
    generate
        if (HISPI_DELAY_CLK == "TRUE") begin
            idelay_nofine # (
                .IODELAY_GRP           (IODELAY_GRP),
                .DELAY_VALUE           (IDELAY_VALUE),
                .REFCLK_FREQUENCY      (REFCLK_FREQUENCY),
                .HIGH_PERFORMANCE_MODE (HIGH_PERFORMANCE_MODE)
            ) clk_dly_i(
                .clk          (mclk),
                .rst          (mrst),
                .set          (set_phase),
                .ld           (load),
                .delay        (phase[4:0]),
                .data_in      (clk_int),
                .data_out     (clk_in)
            );
        end else begin
            assign clk_in =  clk_int;
        end
    endgenerate

    // generate phase-shifterd pixel clock (and 2x version) from either the internal clock (that is output to the sensor) or from the clock
    // received from the sensor (may need to reset MMCM after resetting sensor)
    
    generate
        if (HISPI_MMCM == "TRUE") begin
            mmcm_phase_cntr #(
                .PHASE_WIDTH         (SENS_PHASE_WIDTH),
                .CLKIN_PERIOD        (CLKIN_PERIOD_SENSOR),
                .BANDWIDTH           (SENS_BANDWIDTH),
                .CLKFBOUT_MULT_F     (CLKFBOUT_MULT_SENSOR), // 4
                .DIVCLK_DIVIDE       (SENS_DIVCLK_DIVIDE),
                .CLKFBOUT_PHASE      (CLKFBOUT_PHASE_SENSOR),
                .CLKOUT0_PHASE       (IPCLK_PHASE),
                .CLKOUT1_PHASE       (IPCLK2X_PHASE),
                .CLKFBOUT_USE_FINE_PS("FALSE"),
                .CLKOUT0_USE_FINE_PS ("TRUE"),
                .CLKOUT1_USE_FINE_PS ("TRUE"),
                .CLKOUT0_DIVIDE_F    (CLKFBOUT_MULT_SENSOR * 2),  // 6  // 8.000),
                .CLKOUT1_DIVIDE      (CLKFBOUT_MULT_SENSOR ), // 3 // 4),
                .COMPENSATION        ("ZHOLD"),
                .REF_JITTER1         (SENS_REF_JITTER1),
                .REF_JITTER2         (SENS_REF_JITTER2),
                .SS_EN               (SENS_SS_EN),
                .SS_MODE             (SENS_SS_MODE),
                .SS_MOD_PERIOD       (SENS_SS_MOD_PERIOD),
                .STARTUP_WAIT        ("FALSE")
            ) mmcm_or_pll_i (
                .clkin1              (clk_in),          // input
                .clkin2              (1'b0),            // input
                .sel_clk2            (1'b0),            // input
                .clkfbin             (clk_fb),          // input
                .rst                 (rst_mmcm),        // input
                .pwrdwn              (1'b0),            // input
                
                .psclk               (mclk),            // input
                .ps_we               (set_phase_w),     // input
                .ps_din              (phase_w),         // input[7:0] 
                .ps_ready            (ps_rdy_w),        // output
                .ps_dout             (ps_out_w),        // output[7:0] reg 
                
                .clkout0             (ipclk_pre),       // output
                .clkout1             (ipclk2x_pre),     // output
                .clkout2(), // output
                .clkout3(), // output
                .clkout4(), // output
                .clkout5(), // output
                .clkout6(), // output
                .clkout0b(), // output
                .clkout1b(), // output
                .clkout2b(), // output
                .clkout3b(), // output
                .clkfbout            (clk_fb), // output
                .clkfboutb(), // output
                .locked              (locked_pxd_mmcm),
                .clkin_stopped       (clkin_pxd_stopped_mmcm), // output
                .clkfb_stopped       (clkfb_pxd_stopped_mmcm) // output
                 // output
            );
        end else begin
            pll_base #(
                .CLKIN_PERIOD        (CLKIN_PERIOD_SENSOR),
                .BANDWIDTH           (SENS_BANDWIDTH),
                .CLKFBOUT_MULT       (CLKFBOUT_MULT_SENSOR), // 4
                .DIVCLK_DIVIDE       (SENS_DIVCLK_DIVIDE),
                .CLKFBOUT_PHASE      (CLKFBOUT_PHASE_SENSOR),
                .CLKOUT0_PHASE       (IPCLK_PHASE),
                .CLKOUT1_PHASE       (IPCLK2X_PHASE),
                .CLKOUT0_DIVIDE      (CLKFBOUT_MULT_SENSOR * 2),  // 6  // 8.000),
                .CLKOUT1_DIVIDE      (CLKFBOUT_MULT_SENSOR ), // 3 // 4),
                .REF_JITTER1         (SENS_REF_JITTER1),
                .STARTUP_WAIT        ("FALSE")
            ) mmcm_or_pll_i (
                .clkin               (clk_in),          // input
                .clkfbin             (clk_fb),          // input
                .rst                 (rst_mmcm),        // input
                .pwrdwn              (1'b0),            // input
                .clkout0             (ipclk_pre),       // output
                .clkout1             (ipclk2x_pre),     // output
                .clkout2(), // output
                .clkout3(), // output
                .clkout4(), // output
                .clkout5(), // output
                .clkfbout            (clk_fb), // output
                .locked              (locked_pxd_mmcm)
                 // output
            );
            assign clkin_pxd_stopped_mmcm = 0;
            assign clkfb_pxd_stopped_mmcm = 0;
            assign ps_rdy_w = 1;
            assign ps_out_w = 0; // alternatively - register delay written
        end
    endgenerate
    
    generate
        if      (BUF_IPCLK == "BUFR2") BUFR  #(.BUFR_DIVIDE(2)) clk1x_i (.O(ipclk), .I(ipclk2x_pre), .CE(1'b1), .CLR(rst_mmcm));
        else if (BUF_IPCLK == "BUFG")  BUFG  clk1x_i (.O(ipclk),   .I(ipclk_pre));
        else if (BUF_IPCLK == "BUFH")  BUFH  clk1x_i (.O(ipclk),   .I(ipclk_pre));
        else if (BUF_IPCLK == "BUFR")  BUFR  clk1x_i (.O(ipclk),   .I(ipclk_pre), .CE(1'b1), .CLR(prst));
        else if (BUF_IPCLK == "BUFMR") BUFMR clk1x_i (.O(ipclk),   .I(ipclk_pre));
        else if (BUF_IPCLK == "BUFIO") BUFIO clk1x_i (.O(ipclk),   .I(ipclk_pre));
        else assign ipclk = ipclk_pre;
    endgenerate

    generate
        if      (BUF_IPCLK2X == "BUFG")  BUFG  clk2x_i (.O(ipclk2x), .I(ipclk2x_pre));
        else if (BUF_IPCLK2X == "BUFH")  BUFH  clk2x_i (.O(ipclk2x), .I(ipclk2x_pre));
        else if (BUF_IPCLK2X == "BUFR")  BUFR  clk2x_i (.O(ipclk2x), .I(ipclk2x_pre), .CE(1'b1), .CLR(prst));
        else if (BUF_IPCLK2X == "BUFMR") BUFMR clk2x_i (.O(ipclk2x), .I(ipclk2x_pre));
        else if (BUF_IPCLK2X == "BUFIO") BUFIO clk2x_i (.O(ipclk2x), .I(ipclk2x_pre));
        else assign ipclk2x = ipclk2x_pre;
    endgenerate





endmodule