mirror of
https://github.com/fpganinja/taxi.git
synced 2025-12-07 16:28:40 -08:00
333 lines
8.0 KiB
Systemverilog
333 lines
8.0 KiB
Systemverilog
// SPDX-License-Identifier: CERN-OHL-S-2.0
|
|
/*
|
|
|
|
Copyright (c) 2024-2025 FPGA Ninja, LLC
|
|
|
|
Authors:
|
|
- Alex Forencich
|
|
|
|
*/
|
|
|
|
`resetall
|
|
`timescale 1ns / 1ps
|
|
`default_nettype none
|
|
|
|
/*
|
|
* Fractional MMCM
|
|
*/
|
|
module taxi_mmcm_frac #(
|
|
parameter MMCM_INPUT_CLK_PERIOD = 8.0,
|
|
parameter MMCM_INPUT_REF_JITTER = 0.010,
|
|
parameter MMCM_INPUT_DIV = 1,
|
|
parameter MMCM_MULT = 8,
|
|
parameter MMCM_OUTPUT_DIV = MMCM_MULT,
|
|
parameter logic PSCLK_EN = 1'b0,
|
|
parameter PSCLK_DIV = MMCM_OUTPUT_DIV,
|
|
parameter logic EXTRA_MMCM = 1'b1,
|
|
parameter MMCM2_CASC_DIV = MMCM_OUTPUT_DIV,
|
|
parameter MMCM2_INPUT_DIV = 1,
|
|
parameter MMCM2_MULT = MMCM_OUTPUT_DIV,
|
|
parameter MMCM2_OUTPUT_DIV = MMCM2_MULT,
|
|
parameter OFFSET_NUM = 1,
|
|
parameter OFFSET_DENOM = 65536
|
|
)
|
|
(
|
|
input wire input_clk,
|
|
input wire input_rst,
|
|
|
|
output wire output_clk,
|
|
output wire output_offset_clk,
|
|
output wire locked
|
|
);
|
|
|
|
localparam MMCM_OUTPUT_CLK_PERIOD = MMCM_INPUT_CLK_PERIOD*MMCM_INPUT_DIV/MMCM_MULT*MMCM_OUTPUT_DIV;
|
|
localparam MMCM2_INPUT_CLK_PERIOD = MMCM_INPUT_CLK_PERIOD*MMCM_INPUT_DIV/MMCM_MULT*MMCM2_CASC_DIV;
|
|
|
|
localparam PSCLK_DIV_INT = PSCLK_EN ? PSCLK_DIV : MMCM_OUTPUT_DIV;
|
|
|
|
// 1 phase shifter tap is 1/56th of the VCO period
|
|
// To shift the frequency by OFFSET_NUM/OFFSET_DENOM, we need to shift the VCO output
|
|
// by OFFSET_NUM VCO periods (56*OFFSET_NUM taps) every OFFSET_DENOM VCO periods.
|
|
// Since the PSCLK is divided with respect to the VCO, rescale numerator by division factor
|
|
localparam DIR = OFFSET_NUM >= 0;
|
|
localparam NUM_1 = (DIR ? OFFSET_NUM : -OFFSET_NUM)*56*PSCLK_DIV_INT;
|
|
localparam DENOM_1 = OFFSET_DENOM;
|
|
|
|
localparam MMCM_MIN_PSCLK_CYCLES = 12;
|
|
|
|
if (DENOM_1 / NUM_1 < MMCM_MIN_PSCLK_CYCLES)
|
|
$fatal(0, "Error: requested offset is too large for MMCM dynamic phase shifter");
|
|
|
|
localparam CNT_W = $clog2(DENOM_1)+1;
|
|
|
|
wire mmcm_locked;
|
|
wire mmcm2_locked;
|
|
|
|
wire mmcm_clkfb;
|
|
wire mmcm_clk_out;
|
|
wire mmcm_clk_bufg;
|
|
wire mmcm_offset_clk_out;
|
|
wire mmcm_offset_clk_bufg;
|
|
wire mmcm_ps_clk_out;
|
|
wire mmcm_ps_clk_bufg;
|
|
|
|
wire mmcm2_clkfb;
|
|
wire mmcm2_offset_clk_out;
|
|
wire mmcm2_offset_clk_bufg;
|
|
|
|
assign locked = mmcm_locked && mmcm2_locked;
|
|
|
|
assign output_clk = mmcm_clk_bufg;
|
|
assign output_offset_clk = EXTRA_MMCM ? mmcm2_offset_clk_bufg : mmcm_offset_clk_bufg;
|
|
|
|
wire ps_clk = PSCLK_EN ? mmcm_ps_clk_bufg : output_clk;
|
|
|
|
logic [CNT_W-1:0] cnt_reg = '0;
|
|
|
|
logic ps_en_reg = 1'b0;
|
|
|
|
always_ff @(posedge ps_clk) begin
|
|
ps_en_reg <= 1'b0;
|
|
|
|
if (cnt_reg + NUM_1 >= DENOM_1) begin
|
|
cnt_reg <= cnt_reg + NUM_1 - DENOM_1;
|
|
ps_en_reg <= 1'b1;
|
|
end else begin
|
|
cnt_reg <= cnt_reg + NUM_1;
|
|
end
|
|
end
|
|
|
|
MMCME3_ADV #(
|
|
// input clocks
|
|
.CLKIN1_PERIOD(MMCM_INPUT_CLK_PERIOD),
|
|
.REF_JITTER1(MMCM_INPUT_REF_JITTER),
|
|
.CLKIN2_PERIOD(0.000),
|
|
.REF_JITTER2(0.010),
|
|
// divide for PFD input
|
|
// US/US+: range 10 MHz to 500 MHz
|
|
.DIVCLK_DIVIDE(MMCM_INPUT_DIV),
|
|
// multiply for VCO output
|
|
// US: range 600 MHz to 1440 MHz
|
|
// US+: range 800 MHz to 1600 MHz
|
|
.CLKFBOUT_MULT_F(MMCM_MULT),
|
|
.CLKFBOUT_PHASE(0),
|
|
.CLKFBOUT_USE_FINE_PS("FALSE"),
|
|
// divide
|
|
.CLKOUT0_DIVIDE_F(MMCM_OUTPUT_DIV),
|
|
.CLKOUT0_DUTY_CYCLE(0.5),
|
|
.CLKOUT0_PHASE(0),
|
|
.CLKOUT0_USE_FINE_PS("FALSE"),
|
|
// divide and phase shift
|
|
.CLKOUT1_DIVIDE(EXTRA_MMCM ? MMCM2_CASC_DIV : MMCM_OUTPUT_DIV),
|
|
.CLKOUT1_DUTY_CYCLE(0.5),
|
|
.CLKOUT1_PHASE(0),
|
|
.CLKOUT1_USE_FINE_PS("TRUE"),
|
|
// phase shifter clock
|
|
.CLKOUT2_DIVIDE(PSCLK_EN ? PSCLK_DIV : 1),
|
|
.CLKOUT2_DUTY_CYCLE(0.5),
|
|
.CLKOUT2_PHASE(0),
|
|
.CLKOUT2_USE_FINE_PS("FALSE"),
|
|
// Not used
|
|
.CLKOUT3_DIVIDE(1),
|
|
.CLKOUT3_DUTY_CYCLE(0.5),
|
|
.CLKOUT3_PHASE(0),
|
|
.CLKOUT3_USE_FINE_PS("FALSE"),
|
|
// Not used
|
|
.CLKOUT4_DIVIDE(1),
|
|
.CLKOUT4_DUTY_CYCLE(0.5),
|
|
.CLKOUT4_PHASE(0),
|
|
.CLKOUT4_USE_FINE_PS("FALSE"),
|
|
.CLKOUT4_CASCADE("FALSE"),
|
|
// Not used
|
|
.CLKOUT5_DIVIDE(1),
|
|
.CLKOUT5_DUTY_CYCLE(0.5),
|
|
.CLKOUT5_PHASE(0),
|
|
.CLKOUT5_USE_FINE_PS("FALSE"),
|
|
// Not used
|
|
.CLKOUT6_DIVIDE(1),
|
|
.CLKOUT6_DUTY_CYCLE(0.5),
|
|
.CLKOUT6_PHASE(0),
|
|
.CLKOUT6_USE_FINE_PS("FALSE"),
|
|
// no spread spectrum
|
|
.SS_EN("FALSE"),
|
|
.SS_MODE("CENTER_HIGH"),
|
|
.SS_MOD_PERIOD(10000),
|
|
|
|
.COMPENSATION("AUTO"),
|
|
// optimized bandwidth
|
|
.BANDWIDTH("OPTIMIZED"),
|
|
// don't wait for lock during startup
|
|
.STARTUP_WAIT("FALSE")
|
|
)
|
|
clk_mmcm_inst (
|
|
// input clocks
|
|
.CLKIN1(input_clk),
|
|
.CLKIN2(1'b0),
|
|
// select CLKIN1
|
|
.CLKINSEL(1'b1),
|
|
// direct clkfb feedback
|
|
.CLKFBIN(mmcm_clkfb),
|
|
.CLKFBOUT(mmcm_clkfb),
|
|
.CLKFBOUTB(),
|
|
// output (no offset)
|
|
.CLKOUT0(mmcm_clk_out),
|
|
.CLKOUT0B(),
|
|
// offset output
|
|
.CLKOUT1(mmcm_offset_clk_out),
|
|
.CLKOUT1B(),
|
|
// phase shifter clock
|
|
.CLKOUT2(mmcm_ps_clk_out),
|
|
.CLKOUT2B(),
|
|
// Not used
|
|
.CLKOUT3(),
|
|
.CLKOUT3B(),
|
|
// Not used
|
|
.CLKOUT4(),
|
|
// Not used
|
|
.CLKOUT5(),
|
|
// Not used
|
|
.CLKOUT6(),
|
|
// reset input
|
|
.RST(input_rst),
|
|
// don't power down
|
|
.PWRDWN(1'b0),
|
|
// DRP
|
|
.DADDR(7'd0),
|
|
.DI(16'd0),
|
|
.DWE(1'b0),
|
|
.DEN(1'b0),
|
|
.DCLK(1'b0),
|
|
.DO(),
|
|
// dynamic phase shift
|
|
.PSINCDEC(DIR ? 1'b0 : 1'b1),
|
|
.PSEN(ps_en_reg),
|
|
.PSCLK(ps_clk),
|
|
.PSDONE(),
|
|
// locked output
|
|
.LOCKED(mmcm_locked),
|
|
// input status
|
|
.CLKINSTOPPED(),
|
|
.CLKFBSTOPPED(),
|
|
// CDDC
|
|
.CDDCREQ(1'b0),
|
|
.CDDCDONE()
|
|
);
|
|
|
|
BUFG
|
|
mmcm_clk_bufg_inst (
|
|
.I(mmcm_clk_out),
|
|
.O(mmcm_clk_bufg)
|
|
);
|
|
|
|
BUFG
|
|
mmcm_offset_clk_bufg_inst (
|
|
.I(mmcm_offset_clk_out),
|
|
.O(mmcm_offset_clk_bufg)
|
|
);
|
|
|
|
BUFG
|
|
mmcm_ps_clk_bufg_inst (
|
|
.I(mmcm_ps_clk_out),
|
|
.O(mmcm_ps_clk_bufg)
|
|
);
|
|
|
|
if (EXTRA_MMCM) begin : extra_mmcm
|
|
|
|
// MMCM instance
|
|
MMCME3_BASE #(
|
|
// 125 MHz input
|
|
.CLKIN1_PERIOD(MMCM2_INPUT_CLK_PERIOD),
|
|
.REF_JITTER1(MMCM_INPUT_REF_JITTER),
|
|
// divide for PFD input
|
|
// US/US+: range 10 MHz to 500 MHz
|
|
.DIVCLK_DIVIDE(MMCM2_INPUT_DIV),
|
|
// multiply for VCO output
|
|
// US: range 600 MHz to 1440 MHz
|
|
// US+: range 800 MHz to 1600 MHz
|
|
.CLKFBOUT_MULT_F(MMCM2_MULT),
|
|
.CLKFBOUT_PHASE(0),
|
|
// divide
|
|
.CLKOUT0_DIVIDE_F(MMCM2_OUTPUT_DIV),
|
|
.CLKOUT0_DUTY_CYCLE(0.5),
|
|
.CLKOUT0_PHASE(0),
|
|
// Not used
|
|
.CLKOUT1_DIVIDE(1),
|
|
.CLKOUT1_DUTY_CYCLE(0.5),
|
|
.CLKOUT1_PHASE(0),
|
|
// Not used
|
|
.CLKOUT2_DIVIDE(1),
|
|
.CLKOUT2_DUTY_CYCLE(0.5),
|
|
.CLKOUT2_PHASE(0),
|
|
// Not used
|
|
.CLKOUT3_DIVIDE(1),
|
|
.CLKOUT3_DUTY_CYCLE(0.5),
|
|
.CLKOUT3_PHASE(0),
|
|
// Not used
|
|
.CLKOUT4_DIVIDE(1),
|
|
.CLKOUT4_DUTY_CYCLE(0.5),
|
|
.CLKOUT4_PHASE(0),
|
|
.CLKOUT4_CASCADE("FALSE"),
|
|
// Not used
|
|
.CLKOUT5_DIVIDE(1),
|
|
.CLKOUT5_DUTY_CYCLE(0.5),
|
|
.CLKOUT5_PHASE(0),
|
|
// Not used
|
|
.CLKOUT6_DIVIDE(1),
|
|
.CLKOUT6_DUTY_CYCLE(0.5),
|
|
.CLKOUT6_PHASE(0),
|
|
|
|
// optimized bandwidth
|
|
.BANDWIDTH("OPTIMIZED"),
|
|
// don't wait for lock during startup
|
|
.STARTUP_WAIT("FALSE")
|
|
)
|
|
clk_mmcm2_inst (
|
|
// 125 MHz input
|
|
.CLKIN1(mmcm_offset_clk_bufg),
|
|
// direct clkfb feeback
|
|
.CLKFBIN(mmcm2_clkfb),
|
|
.CLKFBOUT(mmcm2_clkfb),
|
|
.CLKFBOUTB(),
|
|
// 125 MHz, 0 degrees
|
|
.CLKOUT0(mmcm2_offset_clk_out),
|
|
.CLKOUT0B(),
|
|
// Not used
|
|
.CLKOUT1(),
|
|
.CLKOUT1B(),
|
|
// Not used
|
|
.CLKOUT2(),
|
|
.CLKOUT2B(),
|
|
// Not used
|
|
.CLKOUT3(),
|
|
.CLKOUT3B(),
|
|
// Not used
|
|
.CLKOUT4(),
|
|
// Not used
|
|
.CLKOUT5(),
|
|
// Not used
|
|
.CLKOUT6(),
|
|
// reset input
|
|
.RST(input_rst || !mmcm_locked),
|
|
// don't power down
|
|
.PWRDWN(1'b0),
|
|
// locked output
|
|
.LOCKED(mmcm2_locked)
|
|
);
|
|
|
|
BUFG
|
|
mmcm2_offset_clk_bufg_inst (
|
|
.I(mmcm2_offset_clk_out),
|
|
.O(mmcm2_offset_clk_bufg)
|
|
);
|
|
|
|
end else begin
|
|
|
|
assign mmcm2_locked = mmcm_locked;
|
|
assign mmcm2_offset_clk_bufg = mmcm_offset_clk_bufg;
|
|
|
|
end
|
|
|
|
endmodule
|
|
|
|
`resetall
|