Files
PLFM_RADAR/9_Firmware/9_2_FPGA/adc_clk_mmcm.v
T
Jason c6103b37de Gap 7 MMCM jitter cleaner + CIC comb CREG pipeline + XDC clock-name fix
MMCM (Gap 7):
- Add adc_clk_mmcm.v: MMCME2_ADV wrapper (VCO=800MHz, CLKOUT0=400MHz)
- Modify ad9484_interface_400m.v: replace BUFG with MMCM path, gate reset on mmcm_locked
- Add adc_clk_mmcm.xdc: CDC false paths for clk_mmcm_out0 <-> clk_100m

XDC Fix (Build 19 WNS=-0.011 root cause):
- Remove conflicting create_generated_clock -name clk_400m_mmcm
- Replace all clk_400m_mmcm references with Vivado auto-generated clk_mmcm_out0
- CDC false paths now correctly apply to actual timing paths

CIC CREG Pipeline (Build 18 critical path fix):
- Explicit DSP48E1 for comb[0] with CREG=1/AREG=1/BREG=1/PREG=1
- Absorbs integrator_sampled_comb fabric FDRE into DSP48 C-port register
- Eliminates 0.643ns fabric->DSP routing delay (Build 18 tightest path)
- +1 cycle comb latency via data_valid_comb_0_out pipeline
- Move shared register declarations above ifndef SIMULATION (iverilog fix)
- Update golden data for +1 cycle CIC pipeline shift

Build scripts: build19_mmcm.tcl, build20_mmcm_creg.tcl
Regression: 18/18 FPGA pass, 20/20 MCU pass
Build 20 launched on remote Vivado (pending results)
2026-03-19 22:59:46 +02:00

225 lines
7.7 KiB
Verilog

`timescale 1ns / 1ps
// ============================================================================
// adc_clk_mmcm.v — MMCME2 Jitter-Cleaning Wrapper for AD9484 400 MHz DCO
//
// PURPOSE:
// Replaces the direct BUFG on the ADC data clock output (adc_dco) with an
// MMCME2_ADV configured for 1:1 frequency (400 MHz in → 400 MHz out) with
// jitter attenuation via the PLL feedback loop.
//
// CURRENT ARCHITECTURE (ad9484_interface_400m.v):
// adc_dco_p/n → IBUFDS → BUFIO (drives IDDR only, near-zero delay)
// → BUFG (drives all fabric 400 MHz logic)
//
// NEW ARCHITECTURE (this module replaces the BUFG path):
// adc_dco_p/n → IBUFDS → BUFIO (unchanged — drives IDDR only)
// → MMCME2 CLKIN1 → CLKOUT0 → BUFG (fabric 400 MHz)
//
// BENEFITS:
// 1. Jitter attenuation: MMCM PLL loop filters input jitter from ~50 ps
// to ~20-30 ps output jitter, reducing clock uncertainty by ~20 ps.
// 2. Phase control: CLKOUT0_PHASE can fine-tune phase offset if needed.
// 3. Locked indicator: mmcm_locked output enables proper reset sequencing.
// 4. Expected WNS improvement: +20-40 ps on the 400 MHz CIC critical path.
//
// MMCM CONFIGURATION (Artix-7 XC7A200T-2):
// CLKIN1 = 400 MHz (from IBUFDS output)
// DIVCLK_DIVIDE = 1
// CLKFBOUT_MULT_F = 2.0 → VCO = 400 * 2 = 800 MHz (range: 600-1200 MHz)
// CLKOUT0_DIVIDE_F = 2.0 → CLKOUT0 = 800 / 2 = 400 MHz
// CLKFBOUT → BUFG → CLKFBIN (internal feedback for best jitter performance)
//
// INTEGRATION:
// This module is a DROP-IN replacement for the BUFG in ad9484_interface_400m.v.
// See adc_clk_mmcm_integration.md for step-by-step instructions.
//
// SIMULATION:
// Under `ifdef SIMULATION, this module passes the clock through a simple
// BUFG (no MMCM primitive), matching the current behavior for iverilog.
//
// TARGET: XC7A200T-2FBG484I (Artix-7, speed grade -2, industrial temp)
// ============================================================================
module adc_clk_mmcm (
// Input: single-ended clock from IBUFDS output
input wire clk_in, // 400 MHz from IBUFDS (adc_dco after IBUFDS)
// System reset (active-low, from 100 MHz domain)
input wire reset_n,
// Outputs
output wire clk_400m_out, // Jitter-cleaned 400 MHz on BUFG (fabric logic)
output wire mmcm_locked // 1 = MMCM PLL is locked and clock is stable
);
`ifdef SIMULATION
// ============================================================================
// SIMULATION PATH — simple passthrough (no Xilinx primitives)
// ============================================================================
// iverilog and other simulators don't have MMCME2_ADV. Pass clock through
// with a locked signal that asserts after a brief delay matching real MMCM
// lock time (~10 us at 400 MHz = ~4000 cycles).
reg locked_sim;
reg [12:0] lock_counter;
initial begin
locked_sim = 1'b0;
lock_counter = 13'd0;
end
always @(posedge clk_in or negedge reset_n) begin
if (!reset_n) begin
locked_sim <= 1'b0;
lock_counter <= 13'd0;
end else begin
if (lock_counter < 13'd4096) begin
lock_counter <= lock_counter + 1;
end else begin
locked_sim <= 1'b1;
end
end
end
`ifdef SIMULATION_HAS_BUFG
// If the simulator supports BUFG (e.g., Vivado xsim)
BUFG bufg_sim (
.I(clk_in),
.O(clk_400m_out)
);
`else
// Pure behavioral — iverilog
assign clk_400m_out = clk_in;
`endif
assign mmcm_locked = locked_sim;
`else
// ============================================================================
// SYNTHESIS PATH — MMCME2_ADV with jitter-cleaning feedback loop
// ============================================================================
wire clk_mmcm_out0; // MMCM CLKOUT0 (unbuffered)
wire clk_mmcm_fb_out; // MMCM CLKFBOUT (unbuffered)
wire clk_mmcm_fb_bufg; // CLKFBOUT after BUFG (feedback)
wire mmcm_locked_int;
// ---- MMCME2_ADV Instance ----
// Configuration for 400 MHz 1:1 with jitter cleaning:
// VCO = CLKIN1 * CLKFBOUT_MULT_F / DIVCLK_DIVIDE = 400 * 2.0 / 1 = 800 MHz
// CLKOUT0 = VCO / CLKOUT0_DIVIDE_F = 800 / 2.0 = 400 MHz
// Bandwidth = "HIGH" for maximum jitter attenuation
MMCME2_ADV #(
// Input clock
.CLKIN1_PERIOD (2.500), // 400 MHz = 2.500 ns period
.CLKIN2_PERIOD (0.000), // Unused
.REF_JITTER1 (0.020), // 20 ps reference jitter (conservative)
.REF_JITTER2 (0.000), // Unused
// VCO configuration
.DIVCLK_DIVIDE (1), // Input divider = 1 (no division)
.CLKFBOUT_MULT_F (2.0), // Feedback multiplier → VCO = 800 MHz
.CLKFBOUT_PHASE (0.0), // No feedback phase shift
// Output 0: 400 MHz fabric clock
.CLKOUT0_DIVIDE_F (2.0), // 800 / 2.0 = 400 MHz
.CLKOUT0_PHASE (0.0), // Phase-aligned with input
.CLKOUT0_DUTY_CYCLE (0.5), // 50% duty cycle
// Unused outputs — disabled
.CLKOUT1_DIVIDE (1),
.CLKOUT1_PHASE (0.0),
.CLKOUT1_DUTY_CYCLE (0.5),
.CLKOUT2_DIVIDE (1),
.CLKOUT2_PHASE (0.0),
.CLKOUT2_DUTY_CYCLE (0.5),
.CLKOUT3_DIVIDE (1),
.CLKOUT3_PHASE (0.0),
.CLKOUT3_DUTY_CYCLE (0.5),
.CLKOUT4_DIVIDE (1),
.CLKOUT4_PHASE (0.0),
.CLKOUT4_DUTY_CYCLE (0.5),
.CLKOUT5_DIVIDE (1),
.CLKOUT5_PHASE (0.0),
.CLKOUT5_DUTY_CYCLE (0.5),
.CLKOUT6_DIVIDE (1),
.CLKOUT6_PHASE (0.0),
.CLKOUT6_DUTY_CYCLE (0.5),
// PLL filter bandwidth — HIGH for maximum jitter attenuation
.BANDWIDTH ("HIGH"),
// Compensation mode — BUFG on feedback path
.COMPENSATION ("BUF_IN"),
// Startup wait for configuration clock
.STARTUP_WAIT ("FALSE")
) mmcm_adc_400m (
// Clock inputs
.CLKIN1 (clk_in), // 400 MHz from IBUFDS
.CLKIN2 (1'b0), // Unused second input
.CLKINSEL (1'b1), // Select CLKIN1
// Feedback
.CLKFBOUT (clk_mmcm_fb_out), // Feedback output (unbuffered)
.CLKFBIN (clk_mmcm_fb_bufg), // Feedback input (from BUFG)
// Clock outputs
.CLKOUT0 (clk_mmcm_out0), // 400 MHz output (unbuffered)
.CLKOUT0B (), // Unused inverted
.CLKOUT1 (),
.CLKOUT1B (),
.CLKOUT2 (),
.CLKOUT2B (),
.CLKOUT3 (),
.CLKOUT3B (),
.CLKOUT4 (),
.CLKOUT5 (),
.CLKOUT6 (),
.CLKFBOUTB (), // Unused inverted feedback
// Control
.RST (~reset_n), // Active-high reset
.PWRDWN (1'b0), // Never power down
// Status
.LOCKED (mmcm_locked_int),
// Dynamic reconfiguration (unused — tie off)
.DADDR (7'd0),
.DCLK (1'b0),
.DEN (1'b0),
.DI (16'd0),
.DWE (1'b0),
.DO (),
.DRDY (),
// Phase shift (unused — tie off)
.PSCLK (1'b0),
.PSEN (1'b0),
.PSINCDEC (1'b0),
.PSDONE ()
);
// ---- Feedback BUFG ----
// Routes CLKFBOUT through a BUFG back to CLKFBIN.
// This is the standard "internal feedback" topology for best jitter performance.
// Vivado's clock network insertion delay is compensated by the MMCM feedback loop.
BUFG bufg_feedback (
.I(clk_mmcm_fb_out),
.O(clk_mmcm_fb_bufg)
);
// ---- Output BUFG ----
// Routes the jitter-cleaned 400 MHz CLKOUT0 onto a global clock network.
BUFG bufg_clk400m (
.I(clk_mmcm_out0),
.O(clk_400m_out)
);
assign mmcm_locked = mmcm_locked_int;
`endif
endmodule