mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-23 08:11:54 +00:00
test(fpga): PR-X.1 F-7.4 — gate tb_ad9484_xsim on MMCM lock (closes PR-N #86)
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).
This commit is contained in:
@@ -21,7 +21,13 @@ module ad9484_interface_400m (
|
|||||||
output wire adc_dco_bufg, // Buffered 400MHz DCO clock for downstream use
|
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
|
// Audit F-0.1: OR flag, clk_400m domain. High on any sample in the
|
||||||
// current 400 MHz cycle where the ADC reports overrange.
|
// 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
|
// LVDS to single-ended conversion
|
||||||
@@ -80,15 +86,18 @@ BUFIO bufio_dco (
|
|||||||
// MMCME2 jitter-cleaning wrapper replaces the direct BUFG.
|
// MMCME2 jitter-cleaning wrapper replaces the direct BUFG.
|
||||||
// The PLL feedback loop attenuates input jitter from ~50 ps to ~20-30 ps,
|
// 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.
|
// 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 (
|
adc_clk_mmcm mmcm_inst (
|
||||||
.clk_in (adc_dco), // 400 MHz from IBUFDS output
|
.clk_in (adc_dco), // 400 MHz from IBUFDS output
|
||||||
.reset_n (reset_n),
|
.reset_n (reset_n),
|
||||||
.clk_400m_out (adc_dco_buffered), // Jitter-cleaned 400 MHz on BUFG
|
.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 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
|
// 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").
|
// (LVDS—SDR)"; p.16: "data outputs are valid on the rising edge of DCO").
|
||||||
|
|||||||
@@ -301,6 +301,9 @@ wire adc_valid; // Data valid signal
|
|||||||
assign adc_pwdn = host_adc_pwdn;
|
assign adc_pwdn = host_adc_pwdn;
|
||||||
|
|
||||||
wire adc_overrange_400m;
|
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 (
|
ad9484_interface_400m adc (
|
||||||
.adc_d_p(adc_d_p),
|
.adc_d_p(adc_d_p),
|
||||||
.adc_d_n(adc_d_n),
|
.adc_d_n(adc_d_n),
|
||||||
@@ -313,7 +316,8 @@ ad9484_interface_400m adc (
|
|||||||
.adc_data_400m(adc_data_cmos),
|
.adc_data_400m(adc_data_cmos),
|
||||||
.adc_data_valid_400m(adc_valid),
|
.adc_data_valid_400m(adc_valid),
|
||||||
.adc_dco_bufg(clk_400m),
|
.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.
|
// Audit F-0.1: stickify the 400 MHz OR pulse, then CDC to clk_100m via 2FF.
|
||||||
|
|||||||
@@ -40,7 +40,11 @@ module ad9484_interface_400m (
|
|||||||
output wire [7:0] adc_data_400m,
|
output wire [7:0] adc_data_400m,
|
||||||
output wire adc_data_valid_400m,
|
output wire adc_data_valid_400m,
|
||||||
output wire adc_dco_bufg,
|
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)
|
// Pass-through clock (no BUFG needed in simulation)
|
||||||
@@ -74,4 +78,16 @@ always @(posedge adc_dco_p or negedge reset_n) begin
|
|||||||
end
|
end
|
||||||
assign adc_overrange_400m = adc_overrange_400m_reg;
|
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
|
endmodule
|
||||||
|
|||||||
@@ -38,6 +38,17 @@ module tb_ad9484_xsim;
|
|||||||
wire [7:0] adc_data_400m;
|
wire [7:0] adc_data_400m;
|
||||||
wire adc_data_valid_400m;
|
wire adc_data_valid_400m;
|
||||||
wire adc_dco_bufg;
|
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
|
// Differential complements
|
||||||
assign adc_d_n = ~adc_d_p;
|
assign adc_d_n = ~adc_d_p;
|
||||||
@@ -59,13 +70,29 @@ module tb_ad9484_xsim;
|
|||||||
.adc_d_n (adc_d_n),
|
.adc_d_n (adc_d_n),
|
||||||
.adc_dco_p (adc_dco_p),
|
.adc_dco_p (adc_dco_p),
|
||||||
.adc_dco_n (adc_dco_n),
|
.adc_dco_n (adc_dco_n),
|
||||||
|
.adc_or_p (adc_or_p),
|
||||||
|
.adc_or_n (adc_or_n),
|
||||||
.sys_clk (sys_clk),
|
.sys_clk (sys_clk),
|
||||||
.reset_n (reset_n),
|
.reset_n (reset_n),
|
||||||
.adc_data_400m (adc_data_400m),
|
.adc_data_400m (adc_data_400m),
|
||||||
.adc_data_valid_400m(adc_data_valid_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 ─────────────────────────────────────────────
|
// ── Check task ─────────────────────────────────────────────
|
||||||
task check;
|
task check;
|
||||||
input cond;
|
input cond;
|
||||||
@@ -134,40 +161,18 @@ module tb_ad9484_xsim;
|
|||||||
#(DCO_PERIOD * 0.3); // mid-cycle
|
#(DCO_PERIOD * 0.3); // mid-cycle
|
||||||
reset_n = 1;
|
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;
|
@(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,
|
check(adc_data_valid_400m === 1'b0,
|
||||||
"valid stays 0 for 1 cycle after reset de-assert (sync stage 1)");
|
"valid stays 0 for 1 cycle after reset de-assert (sync stage 1)");
|
||||||
|
|
||||||
@(posedge adc_dco_p); #0.1;
|
// Wait for the MMCM SIM model to lock and the 2-FF reset-sync
|
||||||
// After 2 dco cycles: reset_sync_400m = 2'b11, reset_n_400m = 1
|
// pipeline to drain, then confirm the data pipeline starts producing.
|
||||||
// But the data pipeline has its own 1-cycle delay
|
wait_for_adc_ready();
|
||||||
// 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
|
|
||||||
check(adc_data_valid_400m === 1'b1,
|
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
|
// TEST GROUP 4: Data capture via IDDR
|
||||||
@@ -179,8 +184,9 @@ module tb_ad9484_xsim;
|
|||||||
adc_d_p = 8'h00;
|
adc_d_p = 8'h00;
|
||||||
#100;
|
#100;
|
||||||
reset_n = 1;
|
reset_n = 1;
|
||||||
// Wait for reset sync pipeline
|
// F-7.4: every reset cycle re-arms the MMCM lock countdown, so
|
||||||
repeat (5) @(posedge adc_dco_p);
|
// wait for lock + sync drain before driving the test pattern.
|
||||||
|
wait_for_adc_ready();
|
||||||
|
|
||||||
// Feed a known pattern on rising edges
|
// Feed a known pattern on rising edges
|
||||||
// IDDR in SAME_EDGE_PIPELINED mode captures:
|
// IDDR in SAME_EDGE_PIPELINED mode captures:
|
||||||
@@ -232,7 +238,7 @@ module tb_ad9484_xsim;
|
|||||||
reset_n = 0;
|
reset_n = 0;
|
||||||
#100;
|
#100;
|
||||||
reset_n = 1;
|
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
|
// Feed incrementing pattern: 0, 1, 2, ... on each half-cycle
|
||||||
begin : seq_test
|
begin : seq_test
|
||||||
@@ -280,15 +286,15 @@ module tb_ad9484_xsim;
|
|||||||
// De-assert and verify sync pipeline
|
// De-assert and verify sync pipeline
|
||||||
#30;
|
#30;
|
||||||
reset_n = 1;
|
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;
|
@(posedge adc_dco_p); #0.1;
|
||||||
check(adc_data_valid_400m === 1'b0,
|
check(adc_data_valid_400m === 1'b0,
|
||||||
"valid stays 0 during reset sync de-assertion");
|
"valid stays 0 during reset sync de-assertion");
|
||||||
|
|
||||||
// Wait for full pipeline
|
// F-7.4: wait for MMCM lock + sync drain, then confirm reassert.
|
||||||
repeat (5) @(posedge adc_dco_p); #0.1;
|
wait_for_adc_ready();
|
||||||
check(adc_data_valid_400m === 1'b1,
|
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
|
// TEST GROUP 7: ADC power-down output
|
||||||
@@ -317,7 +323,7 @@ module tb_ad9484_xsim;
|
|||||||
adc_d_p = 8'h00;
|
adc_d_p = 8'h00;
|
||||||
#100;
|
#100;
|
||||||
reset_n = 1;
|
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
|
begin : sdr_ramp
|
||||||
reg [7:0] ramp_value;
|
reg [7:0] ramp_value;
|
||||||
|
|||||||
Reference in New Issue
Block a user