module pcie_7x_0_qpll_drp #
(

    parameter PCIE_GT_DEVICE   = "GTX",                     // PCIe GT device
    parameter PCIE_USE_MODE    = "3.0",                     // PCIe use mode
    parameter PCIE_PLL_SEL     = "CPLL",                    // PCIe PLL select for Gen1/Gen2 only
    parameter PCIE_REFCLK_FREQ = 0,                         // PCIe reference clock frequency
    parameter LOAD_CNT_MAX     = 2'd3,                      // Load max count
    parameter INDEX_MAX        = 3'd6                       // Index max count
        
)

(
    
    //---------- Input -------------------------------------
    input               DRP_CLK,
    input               DRP_RST_N,
    input               DRP_OVRD,
    input               DRP_GEN3,
    input               DRP_QPLLLOCK,
    input               DRP_START,
    input       [15:0]  DRP_DO,
    input               DRP_RDY,
    
    //---------- Output ------------------------------------
    output      [ 7:0]  DRP_ADDR,
    output              DRP_EN,  
    output      [15:0]  DRP_DI,   
    output              DRP_WE,
    output              DRP_DONE,
    output              DRP_QPLLRESET,
    output      [ 5:0]  DRP_CRSCODE,
    output      [ 8:0]  DRP_FSM
    
);

    //---------- Input Registers ---------------------------
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 ovrd_reg1;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 gen3_reg1;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 qplllock_reg1;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 start_reg1;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg         [15:0]  do_reg1;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 rdy_reg1;
    
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 ovrd_reg2;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 gen3_reg2;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 qplllock_reg2;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 start_reg2;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg         [15:0]  do_reg2;
(* ASYNC_REG = "TRUE", SHIFT_EXTRACT = "NO" *)    reg                 rdy_reg2;
    
    //---------- Internal Signals --------------------------
    reg         [ 1:0]  load_cnt =  2'd0;
    reg         [ 2:0]  index    =  3'd0;
    reg                 mode     =  1'd0;
    reg         [ 5:0]  crscode  =  6'd0;
    
    //---------- Output Registers --------------------------
    reg         [ 7:0]  addr    =  8'd0;
    reg         [15:0]  di      = 16'd0;
    reg                 done    =  1'd0;
    reg         [ 8:0]  fsm     =  7'd1;      
                        
    //---------- DRP Address -------------------------------  
    localparam          ADDR_QPLL_FBDIV               = 8'h36;
    localparam          ADDR_QPLL_CFG                 = 8'h32;
    localparam          ADDR_QPLL_LPF                 = 8'h31;  
    localparam          ADDR_CRSCODE                  = 8'h88;  
    localparam          ADDR_QPLL_COARSE_FREQ_OVRD    = 8'h35;
    localparam          ADDR_QPLL_COARSE_FREQ_OVRD_EN = 8'h36;  
    localparam          ADDR_QPLL_LOCK_CFG            = 8'h34;
    
    //---------- DRP Mask ----------------------------------
    localparam          MASK_QPLL_FBDIV               = 16'b1111110000000000;  // Unmask bit [ 9: 0] 
    localparam          MASK_QPLL_CFG                 = 16'b1111111110111111;  // Unmask bit [    6]
    localparam          MASK_QPLL_LPF                 = 16'b1000011111111111;  // Unmask bit [14:11]
    localparam          MASK_QPLL_COARSE_FREQ_OVRD    = 16'b0000001111111111;  // Unmask bit [15:10] 
    localparam          MASK_QPLL_COARSE_FREQ_OVRD_EN = 16'b1111011111111111;  // Unmask bit [   11]      
    localparam          MASK_QPLL_LOCK_CFG            = 16'b1110011111111111;  // Unmask bit [12:11]     

    //---------- DRP Data for Normal QPLLLOCK Mode ---------
    localparam          NORM_QPLL_COARSE_FREQ_OVRD    = 16'b0000000000000000;  // Coarse freq value
    localparam          NORM_QPLL_COARSE_FREQ_OVRD_EN = 16'b0000000000000000;  // Normal QPLL lock  
    localparam          NORM_QPLL_LOCK_CFG            = 16'b0000000000000000;  // Normal QPLL lock config 
     
    //---------- DRP Data for Optimize QPLLLOCK Mode -------
    localparam          OVRD_QPLL_COARSE_FREQ_OVRD    = 16'b0000000000000000;  // Coarse freq value
    localparam          OVRD_QPLL_COARSE_FREQ_OVRD_EN = 16'b0000100000000000;  // Override QPLL lock 
    localparam          OVRD_QPLL_LOCK_CFG            = 16'b0000000000000000;  // Override QPLL lock config 
    
    //---------- Select QPLL Feedback Divider --------------
    //  N = 100 for 100 MHz ref clk and 10Gb/s line rate
    //  N =  80 for 125 MHz ref clk and 10Gb/s line rate
    //  N =  40 for 250 MHz ref clk and 10Gb/s line rate
    //------------------------------------------------------
    //  N =  80 for 100 MHz ref clk and  8Gb/s line rate
    //  N =  64 for 125 MHz ref clk and  8Gb/s line rate
    //  N =  32 for 250 MHz ref clk and  8Gb/s line rate
    //------------------------------------------------------
    localparam          QPLL_FBDIV = (PCIE_REFCLK_FREQ == 2) && (PCIE_PLL_SEL == "QPLL") ? 16'b0000000010000000 : 
                                     (PCIE_REFCLK_FREQ == 1) && (PCIE_PLL_SEL == "QPLL") ? 16'b0000000100100000 : 
                                     (PCIE_REFCLK_FREQ == 0) && (PCIE_PLL_SEL == "QPLL") ? 16'b0000000101110000 : 
                                     (PCIE_REFCLK_FREQ == 2) && (PCIE_PLL_SEL == "CPLL") ? 16'b0000000001100000 : 
                                     (PCIE_REFCLK_FREQ == 1) && (PCIE_PLL_SEL == "CPLL") ? 16'b0000000011100000 : 16'b0000000100100000;
    
    localparam          GEN12_QPLL_FBDIV = (PCIE_REFCLK_FREQ == 2) ? 16'b0000000010000000 : 
                                           (PCIE_REFCLK_FREQ == 1) ? 16'b0000000100100000 : 16'b0000000101110000;

    localparam          GEN3_QPLL_FBDIV  = (PCIE_REFCLK_FREQ == 2) ? 16'b0000000001100000 : 
                                           (PCIE_REFCLK_FREQ == 1) ? 16'b0000000011100000 : 16'b0000000100100000;
     
    //---------- Select QPLL Configuration ---------------------------
    //  QPLL_CFG[6] = 0 for upper band
    //              = 1 for lower band
    //----------------------------------------------------------------
    localparam          GEN12_QPLL_CFG = (PCIE_PLL_SEL == "QPLL") ? 16'b0000000000000000 : 16'b0000000001000000;
    localparam          GEN3_QPLL_CFG  = 16'b0000000001000000;  
     
    //---------- Select QPLL LPF -------------------------------------
    localparam          GEN12_QPLL_LPF = (PCIE_PLL_SEL == "QPLL") ? 16'b0_0100_00000000000 : 16'b0_1101_00000000000;
    localparam          GEN3_QPLL_LPF  = 16'b0_1101_00000000000;  
     
     
     
    //---------- DRP Data ----------------------------------       
    wire        [15:0]  data_qpll_fbdiv;  
    wire        [15:0]  data_qpll_cfg;  
    wire        [15:0]  data_qpll_lpf;  
    wire        [15:0]  data_qpll_coarse_freq_ovrd;               
    wire        [15:0]  data_qpll_coarse_freq_ovrd_en; 
    wire        [15:0]  data_qpll_lock_cfg;      
           
    //---------- FSM ---------------------------------------  
    localparam          FSM_IDLE      = 9'b000000001;  
    localparam          FSM_LOAD      = 9'b000000010;                           
    localparam          FSM_READ      = 9'b000000100;
    localparam          FSM_RRDY      = 9'b000001000;
    localparam          FSM_WRITE     = 9'b000010000;
    localparam          FSM_WRDY      = 9'b000100000;    
    localparam          FSM_DONE      = 9'b001000000; 
    localparam          FSM_QPLLRESET = 9'b010000000; 
    localparam          FSM_QPLLLOCK  = 9'b100000000;


    
//---------- Input FF ----------------------------------------------------------
always @ (posedge DRP_CLK)
begin

    if (!DRP_RST_N)
        begin
        //---------- 1st Stage FF --------------------------
        ovrd_reg1     <=  1'd0;
        gen3_reg1     <=  1'd0;
        qplllock_reg1 <=  1'd0;
        start_reg1    <=  1'd0;
        do_reg1       <= 16'd0;
        rdy_reg1      <=  1'd0;
        //---------- 2nd Stage FF --------------------------
        ovrd_reg2     <=  1'd0;
        gen3_reg2     <=  1'd0;
        qplllock_reg2 <=  1'd0;
        start_reg2    <=  1'd0;
        do_reg2       <= 16'd0;
        rdy_reg2      <=  1'd0;
        end
        
    else
        begin
        //---------- 1st Stage FF --------------------------
        ovrd_reg1     <= DRP_OVRD;
        gen3_reg1     <= DRP_GEN3;
        qplllock_reg1 <= DRP_QPLLLOCK;
        start_reg1    <= DRP_START;
        do_reg1       <= DRP_DO;
        rdy_reg1      <= DRP_RDY;
        //---------- 2nd Stage FF --------------------------
        ovrd_reg2     <= ovrd_reg1;
        gen3_reg2     <= gen3_reg1;
        qplllock_reg2 <= qplllock_reg1;
        start_reg2    <= start_reg1;
        do_reg2       <= do_reg1;
        rdy_reg2      <= rdy_reg1;
        end
    
end  



//---------- Select DRP Data ---------------------------------------------------
assign data_qpll_fbdiv               = (gen3_reg2) ? GEN3_QPLL_FBDIV : GEN12_QPLL_FBDIV;
assign data_qpll_cfg                 = (gen3_reg2) ? GEN3_QPLL_CFG   : GEN12_QPLL_CFG;
assign data_qpll_lpf                 = (gen3_reg2) ? GEN3_QPLL_LPF   : GEN12_QPLL_LPF;
assign data_qpll_coarse_freq_ovrd    =  NORM_QPLL_COARSE_FREQ_OVRD;
assign data_qpll_coarse_freq_ovrd_en = (ovrd_reg2) ? OVRD_QPLL_COARSE_FREQ_OVRD_EN : NORM_QPLL_COARSE_FREQ_OVRD_EN;
assign data_qpll_lock_cfg            = (ovrd_reg2) ? OVRD_QPLL_LOCK_CFG            : NORM_QPLL_LOCK_CFG;


//---------- Load Counter ------------------------------------------------------
always @ (posedge DRP_CLK)
begin

    if (!DRP_RST_N)
        load_cnt <= 2'd0;
    else
    
        //---------- Increment Load Counter ----------------
        if ((fsm == FSM_LOAD) && (load_cnt < LOAD_CNT_MAX))
            load_cnt <= load_cnt + 2'd1;
            
        //---------- Hold Load Counter ---------------------
        else if ((fsm == FSM_LOAD) && (load_cnt == LOAD_CNT_MAX))
            load_cnt <= load_cnt;
            
        //---------- Reset Load Counter --------------------
        else
            load_cnt <= 2'd0;
        
end 



//---------- Update DRP Address and Data ---------------------------------------
always @ (posedge DRP_CLK)
begin

    if (!DRP_RST_N)
        begin
        addr    <=  8'd0;
        di      <= 16'd0;
        crscode <=  6'd0;
        end
    else
        begin
        
        case (index)

        //--------------------------------------------------    
        3'd0 :
            begin        
            addr    <= ADDR_QPLL_FBDIV;
            di      <= (do_reg2 & MASK_QPLL_FBDIV) | (mode ? data_qpll_fbdiv : QPLL_FBDIV);
            crscode <= crscode;
            end   
                
        //--------------------------------------------------    
        3'd1 :
            begin       
            addr    <= ADDR_QPLL_CFG;
            if (PCIE_GT_DEVICE == "GTX") 
                di <= (do_reg2 & MASK_QPLL_CFG) | data_qpll_cfg;
            else
                di <= (do_reg2 & 16'hFFFF) | data_qpll_cfg;
            crscode <= crscode;
            end       
            
        //--------------------------------------------------    
        3'd2 :
            begin        
            addr    <= ADDR_QPLL_LPF;
            if (PCIE_GT_DEVICE == "GTX") 
                di <= (do_reg2 & MASK_QPLL_LPF) | data_qpll_lpf;
            else
                di <= (do_reg2 & 16'hFFFF) | data_qpll_lpf;
            crscode <= crscode;
            end     
                
        //--------------------------------------------------
        3'd3 :
            begin        
            addr <= ADDR_CRSCODE;
            di   <= do_reg2;
            
            //---------- Latch CRS Code --------------------
            if (ovrd_reg2)
                crscode <= do_reg2[6:1];                 
            else
                crscode <= crscode;   
            end
           
        //--------------------------------------------------    
        3'd4 :
            begin        
            addr    <= ADDR_QPLL_COARSE_FREQ_OVRD;
            di      <= (do_reg2 & MASK_QPLL_COARSE_FREQ_OVRD) | {(crscode - 6'd1), data_qpll_coarse_freq_ovrd[9:0]};
            crscode <= crscode;
            end    
            
        //--------------------------------------------------    
        3'd5 :
            begin        
            addr    <= ADDR_QPLL_COARSE_FREQ_OVRD_EN;
            di      <= (do_reg2 & MASK_QPLL_COARSE_FREQ_OVRD_EN) | data_qpll_coarse_freq_ovrd_en;
            crscode <= crscode;
            end    
            
        //--------------------------------------------------    
        3'd6 :
            begin        
            addr    <= ADDR_QPLL_LOCK_CFG;
            di      <= (do_reg2 & MASK_QPLL_LOCK_CFG) | data_qpll_lock_cfg;
            crscode <= crscode;
            end       
            
        //--------------------------------------------------
        default : 
            begin
            addr    <=  8'd0;
            di      <= 16'd0;
            crscode <=  6'd0;
            end
            
        endcase
        
        end
        
end  



//---------- QPLL DRP FSM ------------------------------------------------------
always @ (posedge DRP_CLK)
begin

    if (!DRP_RST_N)
        begin
        fsm   <= FSM_IDLE;
        index <= 3'd0;
        mode  <= 1'd0;
        done  <= 1'd0;
        end
    else
        begin
        
        case (fsm)

        //---------- Idle State ----------------------------
        FSM_IDLE :  
          
            begin
            if (start_reg2)
                begin
                fsm   <= FSM_LOAD;
                index <= 3'd0;
                mode  <= 1'd0;
                done  <= 1'd0;
                end
            else if ((gen3_reg2 != gen3_reg1) && (PCIE_PLL_SEL == "QPLL"))
                begin
                fsm   <= FSM_LOAD;
                index <= 3'd0;
                mode  <= 1'd1;
                done  <= 1'd0;
                end
            else
                begin
                fsm   <= FSM_IDLE;
                index <= 3'd0;
                mode  <= 1'd0;
                done  <= 1'd1;
                end
            end    
            
        //---------- Load DRP Address  ---------------------
        FSM_LOAD :
        
            begin
            fsm   <= (load_cnt == LOAD_CNT_MAX) ? FSM_READ : FSM_LOAD;
            index <= index;
            mode  <= mode;
            done  <= 1'd0;
            end  
            
        //---------- Read DRP ------------------------------
        FSM_READ :
        
            begin
            fsm   <= FSM_RRDY;
            index <= index;
            mode  <= mode;
            done  <= 1'd0;
            end
            
        //---------- Read DRP Ready ------------------------
        FSM_RRDY :    
        
            begin
            fsm   <= (rdy_reg2 ? FSM_WRITE : FSM_RRDY);
            index <= index;
            mode  <= mode;
            done  <= 1'd0;
            end  
            
        //---------- Write DRP -----------------------------
        FSM_WRITE :    
        
            begin
            fsm   <= FSM_WRDY;
            index <= index;
            mode  <= mode;
            done  <= 1'd0;
            end       
            
        //---------- Write DRP Ready -----------------------
        FSM_WRDY :    
        
            begin
            fsm   <= (rdy_reg2 ? FSM_DONE : FSM_WRDY);
            index <= index;
            mode  <= mode;
            done  <= 1'd0;
            end        
             
        //---------- DRP Done ------------------------------
        FSM_DONE :
        
            begin
            if ((index == INDEX_MAX) || (mode && (index == 3'd2)))
                begin
                fsm   <= mode ? FSM_QPLLRESET : FSM_IDLE;
                index <= 3'd0;
                mode  <= mode;
                done  <= 1'd0;
                end
            else       
                begin
                fsm   <= FSM_LOAD;
                index <= index + 3'd1;
                mode  <= mode;
                done  <= 1'd0;
                end
            end     
        
        //---------- QPLL Reset ----------------------------      
        FSM_QPLLRESET :
        
            begin
            fsm   <= !qplllock_reg2 ? FSM_QPLLLOCK : FSM_QPLLRESET;
            index <= 3'd0;
            mode  <= mode;
            done  <= 1'd0;
            end
            
        //---------- QPLL Reset ----------------------------      
        FSM_QPLLLOCK :
        
            begin
            fsm   <= qplllock_reg2 ? FSM_IDLE : FSM_QPLLLOCK;
            index <= 3'd0;
            mode  <= mode;
            done  <= 1'd0;
            end
              
        //---------- Default State -------------------------
        default :
        
            begin      
            fsm   <= FSM_IDLE;
            index <= 3'd0;
            mode  <= 1'd0;
            done  <= 1'd0;
            end
            
        endcase
        
        end
        
end 



//---------- QPLL DRP Output ---------------------------------------------------
assign DRP_ADDR      = addr;
assign DRP_EN        = (fsm == FSM_READ) || (fsm == FSM_WRITE);
assign DRP_DI        = di;
assign DRP_WE        = (fsm == FSM_WRITE); // || (fsm == FSM_WRDY);
assign DRP_DONE      = done;
assign DRP_QPLLRESET = (fsm == FSM_QPLLRESET);
assign DRP_CRSCODE   = crscode;
assign DRP_FSM       = fsm;



endmodule