From 6738f12e54fdfa387ec37e7bb28d7f19d050502f Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Tue, 5 May 2026 11:59:30 +0545 Subject: [PATCH] =?UTF-8?q?test(fpga):=20PR-X.1=20F-7.4=20=E2=80=94=20gate?= =?UTF-8?q?=20tb=5Fad9484=5Fxsim=20on=20MMCM=20lock=20(closes=20PR-N=20#86?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage-7 ADC chain audit. The MMCM SIMULATION model in adc_clk_mmcm.v takes 4096 DCO cycles (~10 µs at 400 MHz) to assert mmcm_locked. The existing TB waited only ~5 cycles after each reset deassert, so the gated reset_n_400m never released and adc_data_valid_400m stayed low for every test group past the initial reset checks. Expose mmcm_locked as a new module output on ad9484_interface_400m (real path) and ad9484_interface_400m_stub (sim path; tied high one DCO cycle after reset deassert since the stub has no MMCM). Connect it through to tb_ad9484_xsim.v and add a `wait_for_adc_ready` task that waits on the lock signal plus 6 DCO cycles for the 2-FF lock-sync, 2-FF reset-sync, and pipeline drain. Apply the task at each of the five reset cycles in the testbench. radar_receiver_final.v: tie the new port off (.mmcm_locked()) — host status pipeline doesn't surface lock yet, deferred for a future status-word widening. Local iverilog regression (36/0/7) clean. xsim verification of the xsim-only TB itself is pending (remote Vivado host). --- 9_Firmware/9_2_FPGA/ad9484_interface_400m.v | 15 +++- 9_Firmware/9_2_FPGA/radar_receiver_final.v | 6 +- .../9_2_FPGA/tb/ad9484_interface_400m_stub.v | 18 +++- 9_Firmware/9_2_FPGA/tb/tb_ad9484_xsim.v | 82 ++++++++++--------- 4 files changed, 78 insertions(+), 43 deletions(-) diff --git a/9_Firmware/9_2_FPGA/ad9484_interface_400m.v b/9_Firmware/9_2_FPGA/ad9484_interface_400m.v index a5ee54b..390a532 100644 --- a/9_Firmware/9_2_FPGA/ad9484_interface_400m.v +++ b/9_Firmware/9_2_FPGA/ad9484_interface_400m.v @@ -21,7 +21,13 @@ module ad9484_interface_400m ( 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 + output wire adc_overrange_400m, + // Audit F-7.4: MMCM lock indicator. Surfaces the internal mmcm_locked + // wire so testbenches (and, in future, the host status pipeline) can + // gate on a stable jitter-cleaned 400 MHz clock instead of guessing at + // a fixed wait. Combinational MMCME2 output — synchronize before use + // outside the clk_400m domain. + output wire mmcm_locked ); // LVDS to single-ended conversion @@ -80,15 +86,18 @@ BUFIO bufio_dco ( // 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; +// Audit F-7.4: mmcm_locked is now exposed as a module output — see the +// port-list comment above. +wire mmcm_locked_int; 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) + .mmcm_locked (mmcm_locked_int) ); assign adc_dco_bufg = adc_dco_buffered; +assign mmcm_locked = mmcm_locked_int; // AUDIT-C4 (2026-05-01): AD9484 outputs SDR LVDS (datasheet p.5: "Output // (LVDS—SDR)"; p.16: "data outputs are valid on the rising edge of DCO"). diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index 3ca50f7..b8b0fb7 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -301,6 +301,9 @@ wire adc_valid; // Data valid signal assign adc_pwdn = host_adc_pwdn; wire adc_overrange_400m; +// Audit F-7.4: mmcm_locked exposed as a port for sim-side gating; not +// surfaced to the host status pipeline yet (deferred for a future status +// widening). Leave unconnected here. ad9484_interface_400m adc ( .adc_d_p(adc_d_p), .adc_d_n(adc_d_n), @@ -313,7 +316,8 @@ ad9484_interface_400m adc ( .adc_data_400m(adc_data_cmos), .adc_data_valid_400m(adc_valid), .adc_dco_bufg(clk_400m), - .adc_overrange_400m(adc_overrange_400m) + .adc_overrange_400m(adc_overrange_400m), + .mmcm_locked() ); // Audit F-0.1: stickify the 400 MHz OR pulse, then CDC to clk_100m via 2FF. diff --git a/9_Firmware/9_2_FPGA/tb/ad9484_interface_400m_stub.v b/9_Firmware/9_2_FPGA/tb/ad9484_interface_400m_stub.v index cf0f401..5b63505 100644 --- a/9_Firmware/9_2_FPGA/tb/ad9484_interface_400m_stub.v +++ b/9_Firmware/9_2_FPGA/tb/ad9484_interface_400m_stub.v @@ -40,7 +40,11 @@ module ad9484_interface_400m ( output wire [7:0] adc_data_400m, output wire adc_data_valid_400m, output wire adc_dco_bufg, - output wire adc_overrange_400m + output wire adc_overrange_400m, + // Audit F-7.4: stub has no MMCM, but must mirror the real module's + // port shape. Tie the lock indicator HIGH after reset deassert so + // testbenches that gate on it (e.g. tb_ad9484_xsim) don't stall. + output wire mmcm_locked ); // Pass-through clock (no BUFG needed in simulation) @@ -74,4 +78,16 @@ always @(posedge adc_dco_p or negedge reset_n) begin end assign adc_overrange_400m = adc_overrange_400m_reg; +// Audit F-7.4: mock the real MMCM lock indicator. The synthesizable +// path takes ~4096 DCO cycles to lock; here we just track reset_n so +// callers see "locked" within one DCO edge of reset deassert. +reg mmcm_locked_stub; +always @(posedge adc_dco_p or negedge reset_n) begin + if (!reset_n) + mmcm_locked_stub <= 1'b0; + else + mmcm_locked_stub <= 1'b1; +end +assign mmcm_locked = mmcm_locked_stub; + endmodule diff --git a/9_Firmware/9_2_FPGA/tb/tb_ad9484_xsim.v b/9_Firmware/9_2_FPGA/tb/tb_ad9484_xsim.v index 4d88ed2..a63d988 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_ad9484_xsim.v +++ b/9_Firmware/9_2_FPGA/tb/tb_ad9484_xsim.v @@ -38,6 +38,17 @@ module tb_ad9484_xsim; wire [7:0] adc_data_400m; wire adc_data_valid_400m; wire adc_dco_bufg; + // Audit F-7.4: mmcm_locked exposed by ad9484_interface_400m so the TB + // can wait deterministically for the MMCM SIM model to lock (~4096 + // DCO cycles) instead of guessing at a fixed 5-cycle delay. + wire mmcm_locked; + + // Audit F-7.4: AD9484 OR (overrange) pair — TB doesn't drive overrange, + // so tie complementary pair to a quiescent low/high so IBUFDS sees + // valid differential input. + reg adc_or_p = 1'b0; + wire adc_or_n = ~adc_or_p; + wire adc_overrange_400m; // Differential complements assign adc_d_n = ~adc_d_p; @@ -59,13 +70,29 @@ module tb_ad9484_xsim; .adc_d_n (adc_d_n), .adc_dco_p (adc_dco_p), .adc_dco_n (adc_dco_n), + .adc_or_p (adc_or_p), + .adc_or_n (adc_or_n), .sys_clk (sys_clk), .reset_n (reset_n), .adc_data_400m (adc_data_400m), .adc_data_valid_400m(adc_data_valid_400m), - .adc_dco_bufg (adc_dco_bufg) + .adc_dco_bufg (adc_dco_bufg), + .adc_overrange_400m(adc_overrange_400m), + .mmcm_locked (mmcm_locked) ); + // ── F-7.4: gate post-reset waiting on the MMCM lock indicator ── + // The MMCM SIMULATION model takes 4096 DCO cycles (~10 µs at 400 MHz) + // to assert mmcm_locked. The interface's reset synchronizer then + // requires 2 more cycles for the 2-FF lock sync, plus 2 cycles for the + // 2-FF reset sync. Allow a couple more for the data pipeline to fill. + task wait_for_adc_ready; + begin + wait (mmcm_locked === 1'b1); + repeat (6) @(posedge adc_dco_p); + end + endtask + // ── Check task ───────────────────────────────────────────── task check; input cond; @@ -134,40 +161,18 @@ module tb_ad9484_xsim; #(DCO_PERIOD * 0.3); // mid-cycle reset_n = 1; - // valid should NOT assert immediately (needs 2 sync stages) + // F-7.4: valid stays 0 while the MMCM is locking. Verifying it + // immediately after deassert covers the 2-FF reset-sync window + // and is also vacuously true throughout the MMCM lock phase. @(posedge adc_dco_p); #0.1; - // After 1 dco cycle: reset_sync_400m[0] = 1, [1] still = 0 - // So reset_n_400m should still be 0 check(adc_data_valid_400m === 1'b0, "valid stays 0 for 1 cycle after reset de-assert (sync stage 1)"); - @(posedge adc_dco_p); #0.1; - // After 2 dco cycles: reset_sync_400m = 2'b11, reset_n_400m = 1 - // But the data pipeline has its own 1-cycle delay - // So valid might assert this cycle or next - - // Wait one more cycle for pipeline - @(posedge adc_dco_p); #0.1; - - // By now (3 dco cycles after reset de-assert), valid should be 1 - // Allow one more for IDDR pipeline - begin : wait_valid - reg saw_valid; - saw_valid = 0; - for (i = 0; i < 5; i = i + 1) begin - @(posedge adc_dco_p); #0.1; - if (adc_data_valid_400m) begin - saw_valid = 1; - $display(" valid asserted %0d dco cycles after reset de-assert", i + 4); - disable wait_valid; - end - end - if (!saw_valid) begin - $display(" [WARN] valid did not assert within 8 dco cycles"); - end - end + // Wait for the MMCM SIM model to lock and the 2-FF reset-sync + // pipeline to drain, then confirm the data pipeline starts producing. + wait_for_adc_ready(); check(adc_data_valid_400m === 1'b1, - "valid asserts after reset sync pipeline completes"); + "valid asserts after MMCM lock + reset sync pipeline completes"); // ════════════════════════════════════════════════════════ // TEST GROUP 4: Data capture via IDDR @@ -179,8 +184,9 @@ module tb_ad9484_xsim; adc_d_p = 8'h00; #100; reset_n = 1; - // Wait for reset sync pipeline - repeat (5) @(posedge adc_dco_p); + // F-7.4: every reset cycle re-arms the MMCM lock countdown, so + // wait for lock + sync drain before driving the test pattern. + wait_for_adc_ready(); // Feed a known pattern on rising edges // IDDR in SAME_EDGE_PIPELINED mode captures: @@ -232,7 +238,7 @@ module tb_ad9484_xsim; reset_n = 0; #100; reset_n = 1; - repeat (5) @(posedge adc_dco_p); + wait_for_adc_ready(); // F-7.4: wait for MMCM lock + sync drain // Feed incrementing pattern: 0, 1, 2, ... on each half-cycle begin : seq_test @@ -280,15 +286,15 @@ module tb_ad9484_xsim; // De-assert and verify sync pipeline #30; reset_n = 1; - // Should NOT be valid yet (2-stage sync) + // Should NOT be valid yet (2-stage sync + MMCM lock countdown) @(posedge adc_dco_p); #0.1; check(adc_data_valid_400m === 1'b0, "valid stays 0 during reset sync de-assertion"); - // Wait for full pipeline - repeat (5) @(posedge adc_dco_p); #0.1; + // F-7.4: wait for MMCM lock + sync drain, then confirm reassert. + wait_for_adc_ready(); check(adc_data_valid_400m === 1'b1, - "valid reasserts after sync pipeline completes"); + "valid reasserts after MMCM lock + sync pipeline completes"); // ════════════════════════════════════════════════════════ // TEST GROUP 7: ADC power-down output @@ -317,7 +323,7 @@ module tb_ad9484_xsim; adc_d_p = 8'h00; #100; reset_n = 1; - repeat (8) @(posedge adc_dco_p); + wait_for_adc_ready(); // F-7.4: wait for MMCM lock + sync drain begin : sdr_ramp reg [7:0] ramp_value;