RTL (P0 pre-bringup findings R-1/R-2/R-3/R-5/R-6): - mti_canceller: add use_long_chirp input and waveform-boundary mute so the long->short transition in mode 01 no longer subtracts across heterogeneous waveforms (R-1). Prev buffer is overwritten in-flight at the boundary so the next same-waveform chirp subtracts cleanly. - ad9484_interface_400m: 2FF sync of mmcm_locked into the 400 MHz domain before gating reset_n_gated (R-6). - cic_decimator_4x_enhanced: correct max_fanout narrative (R-3). - ad9484_interface_400m: strip stale pblock comment, note 3.0 ns max_delay instead (R-2). - mti_canceller / doppler_processor: 200T-20km WARNING banners flagging the broken 4096-bin path (R-5). 9-bit BRAM address aliases silently until rewritten. - adc_clk_mmcm.xdc: relax set_max_delay from 2.700 -> 3.000 ns, closes WNS with headroom on 50T build. - radar_receiver_final: wire use_long_chirp into mti_inst. Architecture-bump finalization (2048-pt range FFT, 512 range bins, 32 Doppler bins -> 16384 output cells per frame): - tb/cosim/radar_scene.py: FFT_SIZE 1024 -> 2048, RANGE_BINS 64 -> 512. - tb/gen_mf_golden_ref.py: N 1024 -> 2048. - Regenerate all affected hex goldens (MF cases 1-4, Doppler inputs + py goldens, receiver integration golden_doppler.mem 2048 -> 16384). - tb_radar_receiver_final: widen range_bin_out 6 -> 9 bits, bump GOLDEN_ENTRIES 2048 -> 16384, expand bitmaps/arrays to 512 bins, update all check messages and thresholds. - tb_mti_canceller, tb_fullchain_mti_cfar_realdata: tie/pass use_long_chirp so compile still works after RTL port add. Test-suite hardening (coverage audit findings): - tb_mti_canceller T12: 10 new assertions exercising R-1 waveform- boundary mute across a long/long/short/short/long sequence. Catches a regression that re-enables subtraction across the boundary. - tb_fir_lowpass: replace tautological check(1'b1, ...) on coefficient symmetry with a real hierarchical check coeff[k]===coeff[31-k]; replace always-pass overflow check with a well-driven (not X/Z) assertion on filter_overflow. - tb_matched_filter_processing_chain: replace three always-pass peak- bin placeholders with peak-to-mean-|out| > 2x ratio checks (catches flat/zero output that the old tautologies silently accepted). - tb_cdc_modules M2: replace always-pass narrow-pulse check with a well-defined-output assertion on the synchronizer. - tb_nco_400m: replace always-pass freq-switch check with a swing + no-X assertion across 200 post-switch samples. - tb_system_e2e G12.1: replace check(1, ...) with test_num > 20 so it catches a stalled TB that skipped prior groups. - tb_multiseg_cosim TEST 4: replace always-pass placeholder with a bitmap that asserts segment_request visited all 4 values. - tb_mf_chain_synth and tb_fullchain_mti_cfar_realdata: add DEPRECATED headers plus \$fatal guards (ifndef ALLOW_STALE_*) so they cannot be silently re-enabled in CI with stale 1024-bin goldens against current 2048-pt RTL. Regression: 32 passed, 0 failed. MTI TB grew 30 -> 39 checks; receiver integration grew 17 -> 18 checks with 16384/16384 golden match at tolerance +/- 2 LSB.
250 lines
9.3 KiB
Verilog
250 lines
9.3 KiB
Verilog
module ad9484_interface_400m (
|
||
// ADC Physical Interface (LVDS)
|
||
input wire [7:0] adc_d_p, // ADC Data P
|
||
input wire [7:0] adc_d_n, // ADC Data N
|
||
input wire adc_dco_p, // Data Clock Output P (400MHz)
|
||
input wire adc_dco_n, // Data Clock Output N (400MHz)
|
||
// Audit F-0.1: AD9484 OR (overrange) LVDS pair, DDR like data.
|
||
// Routed on the 50T main board to bank 14 pins M6/N6. Asserts for any
|
||
// sample whose absolute value exceeds full-scale.
|
||
input wire adc_or_p,
|
||
input wire adc_or_n,
|
||
|
||
// System Interface
|
||
input wire sys_clk, // 100MHz system clock (for control only)
|
||
input wire reset_n,
|
||
|
||
// Output at 400MHz domain
|
||
output wire [7:0] adc_data_400m, // ADC data at 400MHz
|
||
output wire adc_data_valid_400m, // Valid at 400MHz
|
||
output wire adc_dco_bufg, // Buffered 400MHz DCO clock for downstream use
|
||
// Audit F-0.1: OR flag, clk_400m domain. High on any sample in the
|
||
// current 400 MHz cycle where the ADC reports overrange.
|
||
output wire adc_overrange_400m
|
||
);
|
||
|
||
// LVDS to single-ended conversion
|
||
wire [7:0] adc_data;
|
||
wire adc_dco;
|
||
|
||
// IBUFDS for each data bit
|
||
// NOTE: IOSTANDARD and DIFF_TERM are set via XDC constraints, not RTL
|
||
// parameters, to support multiple FPGA targets with different bank voltages:
|
||
// - XC7A200T (FBG484): Bank 14 VCCO = 2.5V → LVDS_25
|
||
// - XC7A50T (FTG256): Bank 14 VCCO = 3.3V → LVDS_33
|
||
genvar i;
|
||
generate
|
||
for (i = 0; i < 8; i = i + 1) begin : data_buffers
|
||
IBUFDS #(
|
||
.DIFF_TERM("FALSE"), // Overridden by XDC DIFF_TERM property
|
||
.IOSTANDARD("DEFAULT") // Overridden by XDC IOSTANDARD property
|
||
) ibufds_data (
|
||
.O(adc_data[i]),
|
||
.I(adc_d_p[i]),
|
||
.IB(adc_d_n[i])
|
||
);
|
||
end
|
||
endgenerate
|
||
|
||
// IBUFDS for DCO
|
||
IBUFDS #(
|
||
.DIFF_TERM("FALSE"), // Overridden by XDC DIFF_TERM property
|
||
.IOSTANDARD("DEFAULT") // Overridden by XDC IOSTANDARD property
|
||
) ibufds_dco (
|
||
.O(adc_dco),
|
||
.I(adc_dco_p),
|
||
.IB(adc_dco_n)
|
||
);
|
||
|
||
// ============================================================================
|
||
// Clock buffering strategy for source-synchronous ADC interface:
|
||
//
|
||
// BUFIO: Near-zero insertion delay, can only drive IOB primitives (IDDR).
|
||
// Used for IDDR clocking to match the data path delay through IBUFDS.
|
||
// This eliminates the hold violation caused by BUFG insertion delay.
|
||
//
|
||
// BUFG: Global clock buffer for fabric logic (downstream processing).
|
||
// Has ~4 ns insertion delay but that's fine for fabric-to-fabric paths.
|
||
// ============================================================================
|
||
wire adc_dco_bufio; // Near-zero delay — drives IDDR only
|
||
wire adc_dco_buffered; // BUFG output — drives fabric logic
|
||
|
||
BUFIO bufio_dco (
|
||
.I(adc_dco),
|
||
.O(adc_dco_bufio)
|
||
);
|
||
|
||
// MMCME2 jitter-cleaning wrapper replaces the direct BUFG.
|
||
// The PLL feedback loop attenuates input jitter from ~50 ps to ~20-30 ps,
|
||
// reducing clock uncertainty and improving WNS on the 400 MHz CIC path.
|
||
wire mmcm_locked;
|
||
|
||
adc_clk_mmcm mmcm_inst (
|
||
.clk_in (adc_dco), // 400 MHz from IBUFDS output
|
||
.reset_n (reset_n),
|
||
.clk_400m_out (adc_dco_buffered), // Jitter-cleaned 400 MHz on BUFG
|
||
.mmcm_locked (mmcm_locked)
|
||
);
|
||
assign adc_dco_bufg = adc_dco_buffered;
|
||
|
||
// IDDR for capturing DDR data
|
||
wire [7:0] adc_data_rise; // Data on rising edge (BUFIO domain)
|
||
wire [7:0] adc_data_fall; // Data on falling edge (BUFIO domain)
|
||
|
||
genvar j;
|
||
generate
|
||
for (j = 0; j < 8; j = j + 1) begin : iddr_gen
|
||
IDDR #(
|
||
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),
|
||
.INIT_Q1(1'b0),
|
||
.INIT_Q2(1'b0),
|
||
.SRTYPE("SYNC")
|
||
) iddr_inst (
|
||
.Q1(adc_data_rise[j]), // Rising edge data
|
||
.Q2(adc_data_fall[j]), // Falling edge data
|
||
.C(adc_dco_bufio), // BUFIO clock (near-zero insertion delay)
|
||
.CE(1'b1),
|
||
.D(adc_data[j]),
|
||
.R(1'b0),
|
||
.S(1'b0)
|
||
);
|
||
end
|
||
endgenerate
|
||
|
||
// ============================================================================
|
||
// Re-register IDDR outputs into BUFG domain
|
||
// IDDR with SAME_EDGE_PIPELINED produces outputs stable for a full clock cycle.
|
||
// BUFIO and BUFG are derived from the same source (adc_dco), so they are
|
||
// frequency-matched. This single register stage transfers from IOB (BUFIO)
|
||
// to fabric (BUFG) with guaranteed timing.
|
||
// ============================================================================
|
||
// Timing on the BUFIO→BUFG CDC edge is governed by a 3.000 ns
|
||
// set_max_delay in constraints/adc_clk_mmcm.xdc (1.2× the 2.500 ns period),
|
||
// which leaves the placer free and still fits inside the ADC data-valid
|
||
// window. IOB=TRUE and a pblock around the IDDR column were both tried
|
||
// and rejected: IOB packing fails because the BUFG clock on these
|
||
// capture FFs can't share the ILOGIC clock mux with the BUFIO-clocked
|
||
// IDDR, and the pblock pulled fanout logic into the I/O region and
|
||
// triggered router congestion on 51 unrelated paths.
|
||
reg [7:0] adc_data_rise_bufg;
|
||
reg [7:0] adc_data_fall_bufg;
|
||
|
||
always @(posedge adc_dco_buffered) begin
|
||
adc_data_rise_bufg <= adc_data_rise;
|
||
adc_data_fall_bufg <= adc_data_fall;
|
||
end
|
||
|
||
// Combine rising and falling edge data to get 400MSPS stream
|
||
reg [7:0] adc_data_400m_reg;
|
||
reg adc_data_valid_400m_reg;
|
||
reg dco_phase;
|
||
|
||
// ── Reset synchronizer ────────────────────────────────────────
|
||
// reset_n comes from the 100 MHz sys_clk domain. Assertion (going low)
|
||
// is asynchronous and safe — the FFs enter reset instantly. De-assertion
|
||
// (going high) must be synchronised to adc_dco_buffered to avoid
|
||
// metastability. This is the classic "async assert, sync de-assert" pattern.
|
||
//
|
||
// mmcm_locked gates de-assertion: the 400 MHz domain stays in reset until
|
||
// the MMCM PLL has locked and the jitter-cleaned clock is stable.
|
||
// mmcm_locked is a combinational MMCME2 output and can glitch; sync it
|
||
// into the 400 MHz domain with a 2-FF chain before using it in the
|
||
// async-reset branch below so a LOCKED blip doesn't asynchronously
|
||
// re-reset the domain. The chain is itself async-reset by the raw
|
||
// reset_n so it forces reset_n_gated=0 at power-up (no valid adc_dco
|
||
// edges exist yet to clock the sync chain).
|
||
(* ASYNC_REG = "TRUE" *) reg [1:0] mmcm_locked_sync_400m;
|
||
always @(posedge adc_dco_buffered or negedge reset_n) begin
|
||
if (!reset_n)
|
||
mmcm_locked_sync_400m <= 2'b00;
|
||
else
|
||
mmcm_locked_sync_400m <= {mmcm_locked_sync_400m[0], mmcm_locked};
|
||
end
|
||
wire mmcm_locked_400m = mmcm_locked_sync_400m[1];
|
||
|
||
(* ASYNC_REG = "TRUE" *) reg [1:0] reset_sync_400m;
|
||
wire reset_n_400m;
|
||
wire reset_n_gated = reset_n & mmcm_locked_400m;
|
||
|
||
always @(posedge adc_dco_buffered or negedge reset_n_gated) begin
|
||
if (!reset_n_gated)
|
||
reset_sync_400m <= 2'b00; // async assert (or MMCM not locked)
|
||
else
|
||
reset_sync_400m <= {reset_sync_400m[0], 1'b1}; // sync de-assert
|
||
end
|
||
assign reset_n_400m = reset_sync_400m[1];
|
||
|
||
always @(posedge adc_dco_buffered or negedge reset_n_400m) begin
|
||
if (!reset_n_400m) begin
|
||
adc_data_400m_reg <= 8'b0;
|
||
adc_data_valid_400m_reg <= 1'b0;
|
||
dco_phase <= 1'b0;
|
||
end else begin
|
||
dco_phase <= ~dco_phase;
|
||
|
||
if (dco_phase) begin
|
||
// Output falling edge data (completes the 400MSPS stream)
|
||
adc_data_400m_reg <= adc_data_fall_bufg;
|
||
end else begin
|
||
// Output rising edge data
|
||
adc_data_400m_reg <= adc_data_rise_bufg;
|
||
end
|
||
|
||
adc_data_valid_400m_reg <= 1'b1; // Always valid when ADC is running
|
||
end
|
||
end
|
||
|
||
assign adc_data_400m = adc_data_400m_reg;
|
||
assign adc_data_valid_400m = adc_data_valid_400m_reg;
|
||
|
||
// ============================================================================
|
||
// Audit F-0.1: AD9484 OR (overrange) capture
|
||
// OR is a DDR LVDS pair (same as data). Buffer it, capture both edges with an
|
||
// IDDR in the BUFIO domain, then OR the two phases into a single clk_400m
|
||
// flag. Register once for stability. No latching — downstream is expected to
|
||
// stickify in its own domain.
|
||
// ============================================================================
|
||
wire adc_or_raw;
|
||
IBUFDS #(
|
||
.DIFF_TERM("FALSE"),
|
||
.IOSTANDARD("DEFAULT")
|
||
) ibufds_or (
|
||
.O(adc_or_raw),
|
||
.I(adc_or_p),
|
||
.IB(adc_or_n)
|
||
);
|
||
|
||
wire adc_or_rise;
|
||
wire adc_or_fall;
|
||
IDDR #(
|
||
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),
|
||
.INIT_Q1(1'b0),
|
||
.INIT_Q2(1'b0),
|
||
.SRTYPE("SYNC")
|
||
) iddr_or (
|
||
.Q1(adc_or_rise),
|
||
.Q2(adc_or_fall),
|
||
.C(adc_dco_bufio),
|
||
.CE(1'b1),
|
||
.D(adc_or_raw),
|
||
.R(1'b0),
|
||
.S(1'b0)
|
||
);
|
||
|
||
reg adc_or_rise_bufg;
|
||
reg adc_or_fall_bufg;
|
||
always @(posedge adc_dco_buffered) begin
|
||
adc_or_rise_bufg <= adc_or_rise;
|
||
adc_or_fall_bufg <= adc_or_fall;
|
||
end
|
||
|
||
reg adc_overrange_r;
|
||
always @(posedge adc_dco_buffered or negedge reset_n_400m) begin
|
||
if (!reset_n_400m)
|
||
adc_overrange_r <= 1'b0;
|
||
else
|
||
adc_overrange_r <= adc_or_rise_bufg | adc_or_fall_bufg;
|
||
end
|
||
assign adc_overrange_400m = adc_overrange_r;
|
||
|
||
endmodule |