// Copyright (C) 2020-2021  The SymbiFlow Authors.
//
// Use of this source code is governed by a ISC-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/ISC
//
// SPDX-License-Identifier:ISC

// ============================================================================
// CMT

`define PLL_FRAC_PRECISION 10
`define PLL_FIXED_WIDTH 32

// Rounds a fixed point number to a given precision
function [`PLL_FIXED_WIDTH:1] pll_round_frac(input [`PLL_FIXED_WIDTH:1] decimal,
                                             input [`PLL_FIXED_WIDTH:1] precision);

  if (decimal[(`PLL_FRAC_PRECISION-precision)] == 1'b1) begin
    pll_round_frac = decimal + (1'b1 << (`PLL_FRAC_PRECISION - precision));
  end else begin
    pll_round_frac = decimal;
  end

endfunction

// Computes content of the PLLs divider registers
function [13:0] pll_divider_regs(input [7:0] divide,  // Max divide is 128
                                 input [31:0] duty_cycle   // Duty cycle is multiplied by 100,000
);

  reg [`PLL_FIXED_WIDTH:1] duty_cycle_fix;
  reg [`PLL_FIXED_WIDTH:1] duty_cycle_min;
  reg [`PLL_FIXED_WIDTH:1] duty_cycle_max;

  reg [               6:0] high_time;
  reg [               6:0] low_time;
  reg                      w_edge;
  reg                      no_count;

  reg [`PLL_FIXED_WIDTH:1] temp;

  if (divide >= 64) begin
    duty_cycle_min = ((divide - 64) * 100_000) / divide;
    duty_cycle_max = (645 / divide) * 100_00;
    if (duty_cycle > duty_cycle_max) duty_cycle = duty_cycle_max;
    if (duty_cycle < duty_cycle_min) duty_cycle = duty_cycle_min;
  end

  duty_cycle_fix = (duty_cycle << `PLL_FRAC_PRECISION) / 100_000;

  if (divide == 7'h01) begin
    high_time = 7'h01;
    w_edge    = 1'b0;
    low_time  = 7'h01;
    no_count  = 1'b1;

  end else begin
    temp = pll_round_frac(duty_cycle_fix*divide, 1);

    high_time = temp[`PLL_FRAC_PRECISION+7:`PLL_FRAC_PRECISION+1];
    w_edge    = temp[`PLL_FRAC_PRECISION];

    if (high_time == 7'h00) begin
      high_time = 7'h01;
      w_edge    = 1'b0;
    end

    if (high_time == divide) begin
      high_time = divide - 1;
      w_edge    = 1'b1;
    end

    low_time = divide - high_time;
    no_count = 1'b0;
  end

  pll_divider_regs = {w_edge, no_count, high_time[5:0], low_time[5:0]};
endfunction

// Computes the PLLs phase shift registers
function [10:0] pll_phase_regs(input [7:0] divide, input signed [31:0] phase);

  reg [`PLL_FIXED_WIDTH:1] phase_in_cycles;
  reg [`PLL_FIXED_WIDTH:1] phase_fixed;
  reg [1:0] mx;
  reg [5:0] delay_time;
  reg [2:0] phase_mux;

  reg [`PLL_FIXED_WIDTH:1] temp;

  if (phase < 0) begin
    phase_fixed = ((phase + 360000) << `PLL_FRAC_PRECISION) / 1000;
  end else begin
    phase_fixed = (phase << `PLL_FRAC_PRECISION) / 1000;
  end

  phase_in_cycles = (phase_fixed * divide) / 360;
  temp            = pll_round_frac(phase_in_cycles, 3);

  mx              = 2'b00;
  phase_mux       = temp[`PLL_FRAC_PRECISION:`PLL_FRAC_PRECISION-2];
  delay_time      = temp[`PLL_FRAC_PRECISION+6:`PLL_FRAC_PRECISION+1];

  pll_phase_regs  = {mx, phase_mux, delay_time};
endfunction


// Given PLL/MMCM divide, duty_cycle and phase calculates content of the
// CLKREG1 and CLKREG2.
function [37:0] pll_clkregs(input [7:0] divide,  // Max divide is 128
                            input [31:0] duty_cycle,  // Multiplied by 100,000
                            input signed [31:0] phase       // Phase is given in degrees (-360,000 to 360,000)
);

  reg [13:0] pll_div;  // EDGE, NO_COUNT, HIGH_TIME[5:0], LOW_TIME[5:0]
  reg [10:0] pll_phase;  // MX, PHASE_MUX[2:0], DELAY_TIME[5:0]

  pll_div = pll_divider_regs(divide, duty_cycle);
  pll_phase = pll_phase_regs(divide, phase);

  pll_clkregs = {
    // CLKREG2: RESERVED[6:0], MX[1:0], EDGE, NO_COUNT, DELAY_TIME[5:0]
    6'h00,
    pll_phase[10:9],
    pll_div[13:12],
    pll_phase[5:0],
    // CLKREG1: PHASE_MUX[3:0], RESERVED, HIGH_TIME[5:0], LOW_TIME[5:0]
    pll_phase[8:6],
    1'b0,
    pll_div[11:0]
  };

endfunction

// This function takes the divide value and outputs the necessary lock values
function [39:0] pll_lktable_lookup(input [6:0] divide // Max divide is 64
);

  reg [2559:0] lookup;

  lookup = {
    // This table is composed of:
    // LockRefDly_LockFBDly_LockCnt_LockSatHigh_UnlockCnt
    40'b00110_00110_1111101000_1111101001_0000000001,
    40'b00110_00110_1111101000_1111101001_0000000001,
    40'b01000_01000_1111101000_1111101001_0000000001,
    40'b01011_01011_1111101000_1111101001_0000000001,
    40'b01110_01110_1111101000_1111101001_0000000001,
    40'b10001_10001_1111101000_1111101001_0000000001,
    40'b10011_10011_1111101000_1111101001_0000000001,
    40'b10110_10110_1111101000_1111101001_0000000001,
    40'b11001_11001_1111101000_1111101001_0000000001,
    40'b11100_11100_1111101000_1111101001_0000000001,
    40'b11111_11111_1110000100_1111101001_0000000001,
    40'b11111_11111_1100111001_1111101001_0000000001,
    40'b11111_11111_1011101110_1111101001_0000000001,
    40'b11111_11111_1010111100_1111101001_0000000001,
    40'b11111_11111_1010001010_1111101001_0000000001,
    40'b11111_11111_1001110001_1111101001_0000000001,
    40'b11111_11111_1000111111_1111101001_0000000001,
    40'b11111_11111_1000100110_1111101001_0000000001,
    40'b11111_11111_1000001101_1111101001_0000000001,
    40'b11111_11111_0111110100_1111101001_0000000001,
    40'b11111_11111_0111011011_1111101001_0000000001,
    40'b11111_11111_0111000010_1111101001_0000000001,
    40'b11111_11111_0110101001_1111101001_0000000001,
    40'b11111_11111_0110010000_1111101001_0000000001,
    40'b11111_11111_0110010000_1111101001_0000000001,
    40'b11111_11111_0101110111_1111101001_0000000001,
    40'b11111_11111_0101011110_1111101001_0000000001,
    40'b11111_11111_0101011110_1111101001_0000000001,
    40'b11111_11111_0101000101_1111101001_0000000001,
    40'b11111_11111_0101000101_1111101001_0000000001,
    40'b11111_11111_0100101100_1111101001_0000000001,
    40'b11111_11111_0100101100_1111101001_0000000001,
    40'b11111_11111_0100101100_1111101001_0000000001,
    40'b11111_11111_0100010011_1111101001_0000000001,
    40'b11111_11111_0100010011_1111101001_0000000001,
    40'b11111_11111_0100010011_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001,
    40'b11111_11111_0011111010_1111101001_0000000001
  };

  pll_lktable_lookup = lookup[((64-divide)*40)+:40];
endfunction

// This function takes the divide value and the bandwidth setting of the PLL
// and outputs the digital filter settings necessary.
function [9:0] pll_table_lookup(input [6:0] divide,  // Max divide is 64
                                input [8*9:0] BANDWIDTH);

  reg [639:0] lookup_low;
  reg [639:0] lookup_high;
  reg [639:0] lookup_optimized;

  reg [  9:0] lookup_entry;

  lookup_low = {
    // CP_RES_LFHF
    10'b0010_1111_00,
    10'b0010_1111_00,
    10'b0010_0111_00,
    10'b0010_1101_00,
    10'b0010_0101_00,
    10'b0010_0101_00,
    10'b0010_1001_00,
    10'b0010_1110_00,
    10'b0010_1110_00,
    10'b0010_0001_00,
    10'b0010_0001_00,
    10'b0010_0110_00,
    10'b0010_0110_00,
    10'b0010_0110_00,
    10'b0010_0110_00,
    10'b0010_1010_00,
    10'b0010_1010_00,
    10'b0010_1010_00,
    10'b0010_1010_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_1100_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0010_0010_00,
    10'b0011_1100_00,
    10'b0011_1100_00,
    10'b0011_1100_00,
    10'b0011_1100_00,
    10'b0011_1100_00,
    10'b0011_1100_00,
    10'b0011_1100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00
  };

  lookup_high = {
    // CP_RES_LFHF
    10'b0011_0111_00,
    10'b0011_0111_00,
    10'b0101_1111_00,
    10'b0111_1111_00,
    10'b0111_1011_00,
    10'b1101_0111_00,
    10'b1110_1011_00,
    10'b1110_1101_00,
    10'b1111_1101_00,
    10'b1111_0111_00,
    10'b1111_1011_00,
    10'b1111_1101_00,
    10'b1111_0011_00,
    10'b1110_0101_00,
    10'b1111_0101_00,
    10'b1111_0101_00,
    10'b1111_0101_00,
    10'b1111_0101_00,
    10'b0111_0110_00,
    10'b0111_0110_00,
    10'b0111_0110_00,
    10'b0111_0110_00,
    10'b0101_1100_00,
    10'b0101_1100_00,
    10'b0101_1100_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b0100_0010_00,
    10'b0100_0010_00,
    10'b0100_0010_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0011_0100_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00
  };

  lookup_optimized = {
    // CP_RES_LFHF
    10'b0011_0111_00,
    10'b0011_0111_00,
    10'b0101_1111_00,
    10'b0111_1111_00,
    10'b0111_1011_00,
    10'b1101_0111_00,
    10'b1110_1011_00,
    10'b1110_1101_00,
    10'b1111_1101_00,
    10'b1111_0111_00,
    10'b1111_1011_00,
    10'b1111_1101_00,
    10'b1111_0011_00,
    10'b1110_0101_00,
    10'b1111_0101_00,
    10'b1111_0101_00,
    10'b1111_0101_00,
    10'b1111_0101_00,
    10'b0111_0110_00,
    10'b0111_0110_00,
    10'b0111_0110_00,
    10'b0111_0110_00,
    10'b0101_1100_00,
    10'b0101_1100_00,
    10'b0101_1100_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b1100_0001_00,
    10'b0100_0010_00,
    10'b0100_0010_00,
    10'b0100_0010_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0011_0100_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0010_1000_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0100_1100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00,
    10'b0010_0100_00
  };

  if (BANDWIDTH == "LOW") begin
    pll_table_lookup = lookup_low[((64-divide)*10)+:10];
  end else if (BANDWIDTH == "HIGH") begin
    pll_table_lookup = lookup_high[((64-divide)*10)+:10];
  end else if (BANDWIDTH == "OPTIMIZED") begin
    pll_table_lookup = lookup_optimized[((64-divide)*10)+:10];
  end

endfunction

// ............................................................................
// IMPORTANT NOTE: Due to lack of support for real type parameters in Yosys
// the PLL parameters that define duty cycles and phase shifts have to be
// provided as integers! The DUTY_CYCLE is expressed as % of high time times
// 1000 whereas the PHASE is expressed in degrees times 1000.

// PLLE2_ADV
module PLLE2_ADV (
    input CLKFBIN,
    input CLKIN1,
    input CLKIN2,
    input CLKINSEL,

    output CLKFBOUT,
    output CLKOUT0,
    output CLKOUT1,
    output CLKOUT2,
    output CLKOUT3,
    output CLKOUT4,
    output CLKOUT5,

    input  PWRDWN,
    input  RST,
    output LOCKED,

    input         DCLK,
    input         DEN,
    input         DWE,
    output        DRDY,
    input  [ 6:0] DADDR,
    input  [15:0] DI,
    output [15:0] DO
);

  parameter _TECHMAP_CONSTMSK_CLKINSEL_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKINSEL_ = 0;

  parameter _TECHMAP_CONSTMSK_RST_ = 0;
  parameter _TECHMAP_CONSTVAL_RST_ = 0;
  parameter _TECHMAP_CONSTMSK_PWRDWN_ = 0;
  parameter _TECHMAP_CONSTVAL_PWRDWN_ = 0;

  parameter _TECHMAP_CONSTMSK_CLKFBOUT_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKFBOUT_ = 0;
  parameter _TECHMAP_CONSTMSK_CLKOUT0_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKOUT0_ = 0;
  parameter _TECHMAP_CONSTMSK_CLKOUT1_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKOUT1_ = 0;
  parameter _TECHMAP_CONSTMSK_CLKOUT2_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKOUT2_ = 0;
  parameter _TECHMAP_CONSTMSK_CLKOUT3_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKOUT3_ = 0;
  parameter _TECHMAP_CONSTMSK_CLKOUT4_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKOUT4_ = 0;
  parameter _TECHMAP_CONSTMSK_CLKOUT5_ = 0;
  parameter _TECHMAP_CONSTVAL_CLKOUT5_ = 0;

  parameter _TECHMAP_CONSTMSK_DCLK_ = 0;
  parameter _TECHMAP_CONSTVAL_DCLK_ = 0;
  parameter _TECHMAP_CONSTMSK_DEN_ = 0;
  parameter _TECHMAP_CONSTVAL_DEN_ = 0;
  parameter _TECHMAP_CONSTMSK_DWE_ = 0;
  parameter _TECHMAP_CONSTVAL_DWE_ = 0;

  parameter IS_CLKINSEL_INVERTED = 1'b0;
  parameter IS_RST_INVERTED = 1'b0;
  parameter IS_PWRDWN_INVERTED = 1'b0;

  parameter BANDWIDTH = "OPTIMIZED";
  parameter STARTUP_WAIT = "FALSE";
  parameter COMPENSATION = "ZHOLD";

  parameter CLKIN1_PERIOD = 0.0;
  parameter REF_JITTER1 = 0.01;
  parameter CLKIN2_PERIOD = 0.0;
  parameter REF_JITTER2 = 0.01;

  parameter [5:0] DIVCLK_DIVIDE = 1;

  parameter [5:0] CLKFBOUT_MULT = 1;
  parameter CLKFBOUT_PHASE = 0;

  parameter [6:0] CLKOUT0_DIVIDE = 1;
  parameter CLKOUT0_DUTY_CYCLE = 50000;
  parameter signed CLKOUT0_PHASE = 0;

  parameter [6:0] CLKOUT1_DIVIDE = 1;
  parameter CLKOUT1_DUTY_CYCLE = 50000;
  parameter signed CLKOUT1_PHASE = 0;

  parameter [6:0] CLKOUT2_DIVIDE = 1;
  parameter CLKOUT2_DUTY_CYCLE = 50000;
  parameter signed CLKOUT2_PHASE = 0;

  parameter [6:0] CLKOUT3_DIVIDE = 1;
  parameter CLKOUT3_DUTY_CYCLE = 50000;
  parameter signed CLKOUT3_PHASE = 0;

  parameter [6:0] CLKOUT4_DIVIDE = 1;
  parameter CLKOUT4_DUTY_CYCLE = 50000;
  parameter signed CLKOUT4_PHASE = 0;

  parameter [6:0] CLKOUT5_DIVIDE = 1;
  parameter CLKOUT5_DUTY_CYCLE = 50000;
  parameter signed CLKOUT5_PHASE = 0;

  // Compute PLL's registers content
  localparam CLKFBOUT_REGS = pll_clkregs(CLKFBOUT_MULT, 50000, CLKFBOUT_PHASE);
  localparam DIVCLK_REGS = pll_clkregs(DIVCLK_DIVIDE, 50000, 0);

  localparam CLKOUT0_REGS = pll_clkregs(CLKOUT0_DIVIDE, CLKOUT0_DUTY_CYCLE, CLKOUT0_PHASE);
  localparam CLKOUT1_REGS = pll_clkregs(CLKOUT1_DIVIDE, CLKOUT1_DUTY_CYCLE, CLKOUT1_PHASE);
  localparam CLKOUT2_REGS = pll_clkregs(CLKOUT2_DIVIDE, CLKOUT2_DUTY_CYCLE, CLKOUT2_PHASE);
  localparam CLKOUT3_REGS = pll_clkregs(CLKOUT3_DIVIDE, CLKOUT3_DUTY_CYCLE, CLKOUT3_PHASE);
  localparam CLKOUT4_REGS = pll_clkregs(CLKOUT4_DIVIDE, CLKOUT4_DUTY_CYCLE, CLKOUT4_PHASE);
  localparam CLKOUT5_REGS = pll_clkregs(CLKOUT5_DIVIDE, CLKOUT5_DUTY_CYCLE, CLKOUT5_PHASE);

  // Handle inputs that should have certain logic levels when left unconnected
  localparam INV_CLKINSEL = (_TECHMAP_CONSTMSK_CLKINSEL_ == 1) ? !_TECHMAP_CONSTVAL_CLKINSEL_ :
			     (_TECHMAP_CONSTVAL_CLKINSEL_ == 0) ? IS_CLKINSEL_INVERTED :
			     IS_CLKINSEL_INVERTED;
  generate
    if (_TECHMAP_CONSTMSK_CLKINSEL_ == 1) begin
      wire clkinsel = 1'b1;
    end else if (_TECHMAP_CONSTVAL_CLKINSEL_ == 0) begin
      wire clkinsel = 1'b1;
    end else begin
      wire clkinsel = CLKINSEL;
    end
  endgenerate

  localparam INV_PWRDWN = (_TECHMAP_CONSTMSK_PWRDWN_ == 1) ? !_TECHMAP_CONSTVAL_PWRDWN_ :
			  (_TECHMAP_CONSTVAL_PWRDWN_ == 0) ? ~IS_PWRDWN_INVERTED :
			  IS_PWRDWN_INVERTED;
  generate
    if (_TECHMAP_CONSTMSK_PWRDWN_ == 1) begin
      wire pwrdwn = 1'b1;
    end else if (_TECHMAP_CONSTVAL_PWRDWN_ == 0) begin
      wire pwrdwn = 1'b1;
    end else begin
      wire pwrdwn = PWRDWN;
    end
  endgenerate

  localparam INV_RST = (_TECHMAP_CONSTMSK_RST_ == 1) ? !_TECHMAP_CONSTVAL_PWRDWN_ :
		       (_TECHMAP_CONSTVAL_RST_ == 0) ? ~IS_RST_INVERTED : IS_RST_INVERTED;
  generate
    if (_TECHMAP_CONSTMSK_RST_ == 1) begin
      wire rst = 1'b1;
    end else if (_TECHMAP_CONSTVAL_RST_ == 0) begin
      wire rst = 1'b1;
    end else begin
      wire rst = RST;
    end
  endgenerate

  generate
    if (_TECHMAP_CONSTMSK_DCLK_ == 1) wire dclk = _TECHMAP_CONSTVAL_DCLK_;
    else if (_TECHMAP_CONSTVAL_DCLK_ == 0) wire dclk = 1'b0;
    else wire dclk = DCLK;
  endgenerate

  generate
    if (_TECHMAP_CONSTMSK_DEN_ == 1) wire den = _TECHMAP_CONSTVAL_DEN_;
    else if (_TECHMAP_CONSTVAL_DEN_ == 0) wire den = 1'b0;
    else wire den = DEN;
  endgenerate

  generate
    if (_TECHMAP_CONSTMSK_DWE_ == 1) wire dwe = _TECHMAP_CONSTVAL_DWE_;
    else if (_TECHMAP_CONSTVAL_DWE_ == 0) wire dwe = 1'b0;
    else wire dwe = DWE;
  endgenerate

  // The substituted cell
  PLLE2_ADV_VPR #(
      // Inverters
      .INV_CLKINSEL(INV_CLKINSEL),
      .ZINV_PWRDWN (INV_PWRDWN),
      .ZINV_RST    (INV_RST),

      // Straight mapped parameters
      .STARTUP_WAIT(STARTUP_WAIT == "TRUE"),

      // Lookup tables
      .LKTABLE(pll_lktable_lookup(CLKFBOUT_MULT)),
      .TABLE  (pll_table_lookup(CLKFBOUT_MULT, BANDWIDTH)),

      // FIXME: How to compute values the two below ?
      .FILTREG1_RESERVED(12'b0000_00001000),
      .LOCKREG3_RESERVED(1'b1),

      // Clock feedback settings
      .CLKFBOUT_CLKOUT1_HIGH_TIME (CLKFBOUT_REGS[11:6]),
      .CLKFBOUT_CLKOUT1_LOW_TIME  (CLKFBOUT_REGS[5:0]),
      .CLKFBOUT_CLKOUT1_PHASE_MUX (CLKFBOUT_REGS[15:13]),
      .CLKFBOUT_CLKOUT2_DELAY_TIME(CLKFBOUT_REGS[21:16]),
      .CLKFBOUT_CLKOUT2_EDGE      (CLKFBOUT_REGS[23]),
      .CLKFBOUT_CLKOUT2_NO_COUNT  (CLKFBOUT_REGS[22]),

      // Internal VCO divider settings
      .DIVCLK_DIVCLK_HIGH_TIME(DIVCLK_REGS[11:6]),
      .DIVCLK_DIVCLK_LOW_TIME (DIVCLK_REGS[5:0]),
      .DIVCLK_DIVCLK_NO_COUNT (DIVCLK_REGS[22]),
      .DIVCLK_DIVCLK_EDGE     (DIVCLK_REGS[23]),

      // CLKOUT0
      .CLKOUT0_CLKOUT1_HIGH_TIME (CLKOUT0_REGS[11:6]),
      .CLKOUT0_CLKOUT1_LOW_TIME  (CLKOUT0_REGS[5:0]),
      .CLKOUT0_CLKOUT1_PHASE_MUX (CLKOUT0_REGS[15:13]),
      .CLKOUT0_CLKOUT2_DELAY_TIME(CLKOUT0_REGS[21:16]),
      .CLKOUT0_CLKOUT2_EDGE      (CLKOUT0_REGS[23]),
      .CLKOUT0_CLKOUT2_NO_COUNT  (CLKOUT0_REGS[22]),

      // CLKOUT1
      .CLKOUT1_CLKOUT1_HIGH_TIME (CLKOUT1_REGS[11:6]),
      .CLKOUT1_CLKOUT1_LOW_TIME  (CLKOUT1_REGS[5:0]),
      .CLKOUT1_CLKOUT1_PHASE_MUX (CLKOUT1_REGS[15:13]),
      .CLKOUT1_CLKOUT2_DELAY_TIME(CLKOUT1_REGS[21:16]),
      .CLKOUT1_CLKOUT2_EDGE      (CLKOUT1_REGS[23]),
      .CLKOUT1_CLKOUT2_NO_COUNT  (CLKOUT1_REGS[22]),

      // CLKOUT2
      .CLKOUT2_CLKOUT1_HIGH_TIME (CLKOUT2_REGS[11:6]),
      .CLKOUT2_CLKOUT1_LOW_TIME  (CLKOUT2_REGS[5:0]),
      .CLKOUT2_CLKOUT1_PHASE_MUX (CLKOUT2_REGS[15:13]),
      .CLKOUT2_CLKOUT2_DELAY_TIME(CLKOUT2_REGS[21:16]),
      .CLKOUT2_CLKOUT2_EDGE      (CLKOUT2_REGS[23]),
      .CLKOUT2_CLKOUT2_NO_COUNT  (CLKOUT2_REGS[22]),

      // CLKOUT3
      .CLKOUT3_CLKOUT1_HIGH_TIME (CLKOUT3_REGS[11:6]),
      .CLKOUT3_CLKOUT1_LOW_TIME  (CLKOUT3_REGS[5:0]),
      .CLKOUT3_CLKOUT1_PHASE_MUX (CLKOUT3_REGS[15:13]),
      .CLKOUT3_CLKOUT2_DELAY_TIME(CLKOUT3_REGS[21:16]),
      .CLKOUT3_CLKOUT2_EDGE      (CLKOUT3_REGS[23]),
      .CLKOUT3_CLKOUT2_NO_COUNT  (CLKOUT3_REGS[22]),

      // CLKOUT4
      .CLKOUT4_CLKOUT1_HIGH_TIME (CLKOUT4_REGS[11:6]),
      .CLKOUT4_CLKOUT1_LOW_TIME  (CLKOUT4_REGS[5:0]),
      .CLKOUT4_CLKOUT1_PHASE_MUX (CLKOUT4_REGS[15:13]),
      .CLKOUT4_CLKOUT2_DELAY_TIME(CLKOUT4_REGS[21:16]),
      .CLKOUT4_CLKOUT2_EDGE      (CLKOUT4_REGS[23]),
      .CLKOUT4_CLKOUT2_NO_COUNT  (CLKOUT4_REGS[22]),

      // CLKOUT5
      .CLKOUT5_CLKOUT1_HIGH_TIME (CLKOUT5_REGS[11:6]),
      .CLKOUT5_CLKOUT1_LOW_TIME  (CLKOUT5_REGS[5:0]),
      .CLKOUT5_CLKOUT1_PHASE_MUX (CLKOUT5_REGS[15:13]),
      .CLKOUT5_CLKOUT2_DELAY_TIME(CLKOUT5_REGS[21:16]),
      .CLKOUT5_CLKOUT2_EDGE      (CLKOUT5_REGS[23]),
      .CLKOUT5_CLKOUT2_NO_COUNT  (CLKOUT5_REGS[22]),

      // Clock output enable controls
      .CLKFBOUT_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKFBOUT_ === 1'bX),

      .CLKOUT0_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKOUT0_ === 1'bX),
      .CLKOUT1_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKOUT1_ === 1'bX),
      .CLKOUT2_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKOUT2_ === 1'bX),
      .CLKOUT3_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKOUT3_ === 1'bX),
      .CLKOUT4_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKOUT4_ === 1'bX),
      .CLKOUT5_CLKOUT1_OUTPUT_ENABLE(_TECHMAP_CONSTVAL_CLKOUT5_ === 1'bX)
  ) _TECHMAP_REPLACE_ (
      .CLKFBIN (CLKFBIN),
      .CLKIN1  (CLKIN1),
      .CLKIN2  (CLKIN2),
      .CLKFBOUT(CLKFBOUT),
      .CLKOUT0 (CLKOUT0),
      .CLKOUT1 (CLKOUT1),
      .CLKOUT2 (CLKOUT2),
      .CLKOUT3 (CLKOUT3),
      .CLKOUT4 (CLKOUT4),
      .CLKOUT5 (CLKOUT5),

      .CLKINSEL(clkinsel),

      .PWRDWN(pwrdwn),
      .RST   (rst),
      .LOCKED(LOCKED),

      .DCLK (dclk),
      .DEN  (den),
      .DWE  (dwe),
      .DRDY (DRDY),
      .DADDR(DADDR),
      .DI   (DI),
      .DO   (DO)
  );

endmodule

// PLLE2_BASE
module PLLE2_BASE (
    input CLKFBIN,
    input CLKIN,

    output CLKFBOUT,
    output CLKOUT0,
    output CLKOUT1,
    output CLKOUT2,
    output CLKOUT3,
    output CLKOUT4,
    output CLKOUT5,

    input  RST,
    output LOCKED
);

  parameter IS_CLKINSEL_INVERTED = 1'b0;
  parameter IS_RST_INVERTED = 1'b0;

  parameter BANDWIDTH = "OPTIMIZED";
  parameter STARTUP_WAIT = "FALSE";

  parameter CLKIN1_PERIOD = 0.0;
  parameter REF_JITTER1 = 0.1;

  parameter [5:0] DIVCLK_DIVIDE = 1;

  parameter [5:0] CLKFBOUT_MULT = 1;
  parameter signed CLKFBOUT_PHASE = 0;

  parameter [6:0] CLKOUT0_DIVIDE = 1;
  parameter CLKOUT0_DUTY_CYCLE = 50000;
  parameter signed CLKOUT0_PHASE = 0;

  parameter [6:0] CLKOUT1_DIVIDE = 1;
  parameter CLKOUT1_DUTY_CYCLE = 50000;
  parameter signed CLKOUT1_PHASE = 0;

  parameter [6:0] CLKOUT2_DIVIDE = 1;
  parameter CLKOUT2_DUTY_CYCLE = 50000;
  parameter signed CLKOUT2_PHASE = 0;

  parameter [6:0] CLKOUT3_DIVIDE = 1;
  parameter CLKOUT3_DUTY_CYCLE = 50000;
  parameter signed CLKOUT3_PHASE = 0;

  parameter [6:0] CLKOUT4_DIVIDE = 1;
  parameter CLKOUT4_DUTY_CYCLE = 50000;
  parameter signed CLKOUT4_PHASE = 0;

  parameter [6:0] CLKOUT5_DIVIDE = 1;
  parameter CLKOUT5_DUTY_CYCLE = 50000;
  parameter signed CLKOUT5_PHASE = 0;

  // The substituted cell
  PLLE2_ADV #(
      .IS_CLKINSEL_INVERTED(IS_CLKINSEL_INVERTED),
      .IS_RST_INVERTED(IS_RST_INVERTED),
      .IS_PWRDWN_INVERTED(1'b0),

      .BANDWIDTH(BANDWIDTH),
      .STARTUP_WAIT(STARTUP_WAIT),

      .CLKIN1_PERIOD(CLKIN1_PERIOD),
      .REF_JITTER1  (REF_JITTER1),

      .DIVCLK_DIVIDE(DIVCLK_DIVIDE),

      .CLKFBOUT_MULT (CLKFBOUT_MULT),
      .CLKFBOUT_PHASE(CLKFBOUT_PHASE),

      .CLKOUT0_DIVIDE(CLKOUT0_DIVIDE),
      .CLKOUT0_DUTY_CYCLE(CLKOUT0_DUTY_CYCLE),
      .CLKOUT0_PHASE(CLKOUT0_PHASE),

      .CLKOUT1_DIVIDE(CLKOUT1_DIVIDE),
      .CLKOUT1_DUTY_CYCLE(CLKOUT1_DUTY_CYCLE),
      .CLKOUT1_PHASE(CLKOUT1_PHASE),

      .CLKOUT2_DIVIDE(CLKOUT2_DIVIDE),
      .CLKOUT2_DUTY_CYCLE(CLKOUT2_DUTY_CYCLE),
      .CLKOUT2_PHASE(CLKOUT2_PHASE),

      .CLKOUT3_DIVIDE(CLKOUT3_DIVIDE),
      .CLKOUT3_DUTY_CYCLE(CLKOUT3_DUTY_CYCLE),
      .CLKOUT3_PHASE(CLKOUT3_PHASE),

      .CLKOUT4_DIVIDE(CLKOUT4_DIVIDE),
      .CLKOUT4_DUTY_CYCLE(CLKOUT4_DUTY_CYCLE),
      .CLKOUT4_PHASE(CLKOUT4_PHASE),

      .CLKOUT5_DIVIDE(CLKOUT5_DIVIDE),
      .CLKOUT5_DUTY_CYCLE(CLKOUT5_DUTY_CYCLE),
      .CLKOUT5_PHASE(CLKOUT5_PHASE)
  ) _TECHMAP_REPLACE_ (
      .CLKFBIN (CLKFBIN),
      .CLKIN1  (CLKIN),
      .CLKINSEL(1'b1),

      .CLKFBOUT(CLKFBOUT),
      .CLKOUT0 (CLKOUT0),
      .CLKOUT1 (CLKOUT1),
      .CLKOUT2 (CLKOUT2),
      .CLKOUT3 (CLKOUT3),
      .CLKOUT4 (CLKOUT4),
      .CLKOUT5 (CLKOUT5),

      .PWRDWN(1'b0),
      .RST(RST),
      .LOCKED(LOCKED),

      .DCLK(1'b0),
      .DEN(1'b0),
      .DWE(1'b0),
      .DRDY(),
      .DADDR(7'd0),
      .DI(16'd0),
      .DO()
  );

endmodule
