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)
5.1 KiB
ADC Clock MMCM Integration Guide
Overview
adc_clk_mmcm.v is a drop-in MMCME2_ADV wrapper that replaces the direct BUFG
on the 400 MHz ADC data clock output with a jitter-cleaning PLL loop.
Current clock path (Build 18)
adc_dco_p/n → IBUFDS → BUFIO (drives IDDR only — near-zero delay)
→ BUFG (drives all fabric 400 MHz logic)
New clock path (with MMCM)
adc_dco_p/n → IBUFDS → BUFIO (unchanged — drives IDDR only)
→ MMCME2 CLKIN1 → CLKOUT0 → BUFG (fabric 400 MHz)
→ CLKFBOUT → BUFG → CLKFBIN (feedback)
Expected Timing Improvement
| Parameter | Before (Build 18) | After (estimated) |
|---|---|---|
| Input jitter | 50 ps | 50 ps (unchanged) |
| Output jitter (MMCM) | N/A | ~20-30 ps |
| Clock uncertainty | 43 ps | ~25 ps |
| WNS (setup, 400 MHz) | +0.062 ns | ~+0.08 to +0.10 ns |
| WHS (hold, 100 MHz) | +0.059 ns | unchanged |
The improvement comes from reduced clock uncertainty in the Vivado timing analysis. The MMCM's PLL loop attenuates the input jitter, so Vivado deducts less uncertainty from the timing budget.
MMCM Configuration
CLKIN1 = 400 MHz (2.500 ns)
DIVCLK_DIVIDE = 1
CLKFBOUT_MULT_F = 2.0 → VCO = 800 MHz
CLKOUT0_DIVIDE_F = 2.0 → Output = 400 MHz
BANDWIDTH = HIGH (maximum jitter filtering)
VCO at 800 MHz is well within the Artix-7 -2 speed grade range (600–1200 MHz).
Resource Cost
| Resource | Count | Notes |
|---|---|---|
| MMCME2_ADV | 1 | Was 0/10, now 1/10 |
| BUFG | +1 (feedback) | Was 4/32, now 5/32 |
| FFs | 0 | No additional fabric registers |
Integration Steps
Step 1: Modify ad9484_interface_400m.v
Replace the BUFG instantiation with adc_clk_mmcm:
// REMOVE these lines (65-69):
// BUFG bufg_dco (
// .I(adc_dco),
// .O(adc_dco_buffered)
// );
// assign adc_dco_bufg = adc_dco_buffered;
// ADD this instead:
wire mmcm_locked;
wire adc_dco_mmcm;
adc_clk_mmcm mmcm_inst (
.clk_in (adc_dco), // From IBUFDS output
.reset_n (reset_n),
.clk_400m_out (adc_dco_mmcm), // Jitter-cleaned 400 MHz
.mmcm_locked (mmcm_locked)
);
// Use MMCM output for all fabric logic
wire adc_dco_buffered = adc_dco_mmcm;
assign adc_dco_bufg = adc_dco_buffered;
Step 2: Gate reset on MMCM lock (recommended)
In ad9484_interface_400m.v, modify the reset synchronizer to require MMCM lock:
// Change the reset synchronizer input from:
// always @(posedge adc_dco_buffered or negedge reset_n) begin
// if (!reset_n)
// reset_sync_400m <= 2'b00;
// else
// reset_sync_400m <= {reset_sync_400m[0], 1'b1};
// end
// To:
wire reset_n_gated = reset_n & mmcm_locked;
always @(posedge adc_dco_buffered or negedge reset_n_gated) begin
if (!reset_n_gated)
reset_sync_400m <= 2'b00;
else
reset_sync_400m <= {reset_sync_400m[0], 1'b1};
end
This ensures the 400 MHz domain stays in reset until the MMCM has locked and the clock is stable. Without this, the first ~10 µs after power-up (before MMCM lock) could produce glitchy clock edges.
Step 3: Add constraint file
Add constraints/adc_clk_mmcm.xdc to the Vivado project. Uncomment the
constraints and adjust hierarchy paths based on your actual instantiation.
Key constraints to uncomment:
create_generated_clock(or verify Vivado auto-creates it)set_max_delaybetweenadc_dco_pandclk_400m_mmcmset_false_pathbetweenclk_400m_mmcmand other clock domainsset_false_pathonLOCKEDoutput
Step 4: Add to build script
In the Tcl build script, add the new source file:
read_verilog adc_clk_mmcm.v
read_xdc constraints/adc_clk_mmcm.xdc
Step 5: Verify
After building:
- Check
report_clocks— should show the new MMCM-derived clock - Check
report_clock_interaction— verify no unexpected crossings - Check WNS on the
adc_dco_p/ MMCM clock group — should improve - Check MMCM locked in ILA during bring-up
BUFIO Compatibility Note
The BUFIO path for IDDR capture is not affected by this change. BUFIO drives only IOB primitives (IDDR) and cannot go through an MMCM. The BUFIO continues to use the raw IBUFDS output with near-zero insertion delay, which is correct for source-synchronous DDR capture.
The re-registration from BUFIO domain to BUFG domain (lines 105-108 of
ad9484_interface_400m.v) now crosses from the raw adc_dco_p clock to the
MMCM-derived clock. Since both are frequency-matched and the MMCM is locked
to the input, this is a safe single-register transfer. The set_max_delay
constraint in the XDC ensures Vivado verifies this.
Simulation
Under SIMULATION define (iverilog), the module passes the clock straight
through with a simulated lock delay of ~4096 cycles. This matches the
current testbench behavior — no changes to any testbenches needed.
Rollback
To revert: simply restore the original BUFG in ad9484_interface_400m.v and
remove adc_clk_mmcm.v + constraints/adc_clk_mmcm.xdc from the project.
No other files are affected.