Files
overwatch/9_Firmware/9_2_FPGA/tb/tb_ad9484_xsim.v
Jason b823d83feb Add new testbenches and fix USB clock forwarding test
New testbenches:
- tb_latency_buffer.v: 13/13 tests for BRAM delay line (P1-3)
- tb_cdc_modules.v: 27/27 tests for all 3 CDC primitives (P1-4)
- tb_ad9484_xsim.v: XSim testbench for AD9484 with Xilinx BUFIO/IDDR
- tb_nco_xsim.v: XSim testbench for NCO DSP48E1 path verification

Fixes:
- tb_usb_data_interface.v: updated test 33 from divide-by-2 check to
  ODDR-style clock forwarding verification (39/39 pass)
- rx_final_doppler_out.csv: updated golden reference after bug fixes
2026-03-16 22:24:34 +02:00

316 lines
14 KiB
Verilog

`timescale 1ns / 1ps
// ============================================================================
// tb_ad9484_xsim.v — XSim testbench for ad9484_interface_400m.v
//
// Tests the REAL module with Xilinx UNISIM primitives (IBUFDS, BUFG, IDDR).
// Must be compiled with xvlog/xelab/xsim (not iverilog).
//
// Key things tested:
// 1. Differential LVDS data capture (IBUFDS)
// 2. DDR data capture (IDDR, SAME_EDGE_PIPELINED mode)
// 3. Reset synchronizer (P1-7 fix: async assert, sync de-assert)
// 4. Data integrity through full pipeline
// 5. Phase interleaving (rising/falling edge multiplexing)
// ============================================================================
module tb_ad9484_xsim;
// ── Parameters ─────────────────────────────────────────────
localparam DCO_PERIOD = 2.5; // 400 MHz
localparam SYS_PERIOD = 10.0; // 100 MHz
// ── Signals ────────────────────────────────────────────────
// LVDS pairs (differential)
reg [7:0] adc_d_p;
wire [7:0] adc_d_n;
reg adc_dco_p;
wire adc_dco_n;
// System
reg sys_clk;
reg reset_n;
// Outputs
wire [7:0] adc_data_400m;
wire adc_data_valid_400m;
wire adc_dco_bufg;
// Differential complements
assign adc_d_n = ~adc_d_p;
assign adc_dco_n = ~adc_dco_p;
// ── Test bookkeeping ───────────────────────────────────────
integer pass_count;
integer fail_count;
integer test_num;
integer i;
// ── Clocks ─────────────────────────────────────────────────
always #(DCO_PERIOD/2) adc_dco_p = ~adc_dco_p;
always #(SYS_PERIOD/2) sys_clk = ~sys_clk;
// ── DUT ────────────────────────────────────────────────────
ad9484_interface_400m uut (
.adc_d_p (adc_d_p),
.adc_d_n (adc_d_n),
.adc_dco_p (adc_dco_p),
.adc_dco_n (adc_dco_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)
);
// ── Check task ─────────────────────────────────────────────
task check;
input cond;
input [511:0] label;
begin
test_num = test_num + 1;
if (cond) begin
$display("[PASS] Test %0d: %0s", test_num, label);
pass_count = pass_count + 1;
end else begin
$display("[FAIL] Test %0d: %0s", test_num, label);
fail_count = fail_count + 1;
end
end
endtask
// ── Stimulus ───────────────────────────────────────────────
initial begin
$display("\n=== AD9484 Interface XSim Testbench ===");
$display(" Testing REAL Xilinx primitives (IBUFDS, BUFG, IDDR)");
$display(" Testing reset synchronizer (P1-7 fix)\n");
// Init
adc_dco_p = 0;
sys_clk = 0;
adc_d_p = 8'h00;
reset_n = 0;
pass_count = 0;
fail_count = 0;
test_num = 0;
// ════════════════════════════════════════════════════════
// TEST GROUP 1: Reset behaviour
// ════════════════════════════════════════════════════════
$display("--- Test Group 1: Reset Behaviour ---");
#50; // let clocks run during reset
check(adc_data_valid_400m === 1'b0, "valid = 0 during reset");
check(adc_data_400m === 8'h00, "data = 0 during reset");
// ════════════════════════════════════════════════════════
// TEST GROUP 2: BUFG clock output
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 2: Clock Buffering ---");
// adc_dco_bufg should follow adc_dco_p (through BUFG)
// Can't check exact timing but can verify it toggles
begin : bufg_test
reg saw_high, saw_low;
saw_high = 0;
saw_low = 0;
for (i = 0; i < 20; i = i + 1) begin
#(DCO_PERIOD/4);
if (adc_dco_bufg) saw_high = 1;
else saw_low = 1;
end
check(saw_high && saw_low, "adc_dco_bufg toggles (BUFG functional)");
end
// ════════════════════════════════════════════════════════
// TEST GROUP 3: Reset de-assertion synchronization (P1-7)
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 3: Reset Synchronizer (P1-7) ---");
// De-assert reset BETWEEN dco edges (worst case for metastability)
// The synchronizer should delay de-assertion by 2 dco cycles
@(negedge adc_dco_p);
#(DCO_PERIOD * 0.3); // mid-cycle
reset_n = 1;
// valid should NOT assert immediately (needs 2 sync stages)
@(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
check(adc_data_valid_400m === 1'b1,
"valid asserts after reset sync pipeline completes");
// ════════════════════════════════════════════════════════
// TEST GROUP 4: Data capture via IDDR
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 4: IDDR Data Capture ---");
// Reset and restart
reset_n = 0;
adc_d_p = 8'h00;
#100;
reset_n = 1;
// Wait for reset sync pipeline
repeat (5) @(posedge adc_dco_p);
// Feed a known pattern on rising edges
// IDDR in SAME_EDGE_PIPELINED mode captures:
// Q1 = data at rising edge (1 cycle pipelined)
// Q2 = data at falling edge (pipelined to align with Q1)
// The module alternates output between Q1 and Q2 via dco_phase
// Drive known data: alternate 0xAA on rise, 0x55 on fall
begin : iddr_test
reg [7:0] captured [0:31];
integer cap_count;
integer saw_aa, saw_55;
cap_count = 0;
saw_aa = 0;
saw_55 = 0;
for (i = 0; i < 20; i = i + 1) begin
// Set data before rising edge
adc_d_p = 8'hAA;
@(posedge adc_dco_p);
// Set data before falling edge
#0.1;
adc_d_p = 8'h55;
@(negedge adc_dco_p);
#0.1;
// Capture output
if (adc_data_valid_400m && cap_count < 32) begin
captured[cap_count] = adc_data_400m;
if (adc_data_400m == 8'hAA) saw_aa = saw_aa + 1;
if (adc_data_400m == 8'h55) saw_55 = saw_55+ 1;
cap_count = cap_count + 1;
end
end
$display(" Captured %0d samples, saw 0xAA: %0d times, 0x55: %0d times",
cap_count, saw_aa, saw_55);
check(cap_count > 0, "IDDR produces output samples");
// With DDR capture, we should see both rise and fall data
check(saw_aa > 0 || saw_55 > 0, "IDDR captures at least one known value");
end
// ════════════════════════════════════════════════════════
// TEST GROUP 5: Sequential data integrity
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 5: Sequential Data Integrity ---");
reset_n = 0;
#100;
reset_n = 1;
repeat (5) @(posedge adc_dco_p);
// Feed incrementing pattern: 0, 1, 2, ... on each half-cycle
begin : seq_test
reg [7:0] outputs [0:63];
integer out_count;
reg saw_nonzero;
reg monotonic;
out_count = 0;
saw_nonzero = 0;
for (i = 0; i < 40; i = i + 1) begin
adc_d_p = i[7:0];
@(posedge adc_dco_p); #0.1;
if (adc_data_valid_400m && out_count < 64) begin
outputs[out_count] = adc_data_400m;
if (adc_data_400m != 0) saw_nonzero = 1;
out_count = out_count + 1;
end
end
$display(" Sequential: captured %0d outputs, saw_nonzero=%b",
out_count, saw_nonzero);
check(out_count > 10, "Produces substantial output stream");
check(saw_nonzero, "Output contains non-zero values");
end
// ════════════════════════════════════════════════════════
// TEST GROUP 6: Reset mid-operation
// ════════════════════════════════════════════════════════
$display("\n--- Test Group 6: Reset Mid-Operation ---");
// Data should be flowing now
adc_d_p = 8'hFF;
repeat (5) @(posedge adc_dco_p);
// Assert reset asynchronously (should take effect immediately)
reset_n = 0;
// The async assert should clear valid within 1 cycle
repeat (2) @(posedge adc_dco_p); #0.1;
check(adc_data_valid_400m === 1'b0, "Async reset assertion clears valid immediately");
check(adc_data_400m === 8'h00, "Async reset assertion clears data to 0");
// De-assert and verify sync pipeline
#30;
reset_n = 1;
// Should NOT be valid yet (2-stage sync)
@(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;
check(adc_data_valid_400m === 1'b1,
"valid reasserts after sync pipeline completes");
// ════════════════════════════════════════════════════════
// TEST GROUP 7: ADC power-down output
// ════════════════════════════════════════════════════════
// adc_pwdn is not part of this module (it's in radar_system_top)
// Just verify the module port list is complete
// ════════════════════════════════════════════════════════
// Summary
// ════════════════════════════════════════════════════════
$display("");
$display("========================================");
$display(" AD9484 INTERFACE XSIM RESULTS");
$display(" PASSED: %0d / %0d", pass_count, test_num);
$display(" FAILED: %0d / %0d", fail_count, test_num);
if (fail_count == 0)
$display(" ** ALL TESTS PASSED **");
else
$display(" ** SOME TESTS FAILED **");
$display("========================================");
$display("");
#100;
$finish;
end
endmodule