test(fpga): F-2.2 adversarial mid-frame reset sweep + F-0.1 TB plumbing

G9B adds a 4-iteration reset sweep on top of the existing e2e harness:
- Reset is injected at four offsets (3/7/12/18 us) into a steady-state
  auto-scan burst, with mixed short/long hold durations (20-120 clk_100m)
  to exercise asynchronous assert paths through the FSM + CDCs.
- Each iteration asserts: system_status drops to 0 during reset,
  new_chirp_frame resumes post-release, and obs_range_valid_count
  advances — proving the full DDC->MF chain recovers, not just the
  transmitter FSM.

The stub and three existing testbenches are updated to drive the new
adc_or_p/n ports tied to 1'b0/1'b1, matching the F-0.1 RTL change.
This commit is contained in:
Jason
2026-04-20 15:37:06 +05:45
parent 70067c6121
commit b588e89f67
4 changed files with 123 additions and 1 deletions
@@ -19,6 +19,10 @@ module ad9484_interface_400m (
input wire [7:0] adc_d_n, input wire [7:0] adc_d_n,
input wire adc_dco_p, input wire adc_dco_p,
input wire adc_dco_n, input wire adc_dco_n,
// Audit F-0.1: AD9484 OR (overrange) LVDS pair stub treats adc_or_p as
// the single-ended overrange flag, adc_or_n is ignored.
input wire adc_or_p,
input wire adc_or_n,
// System Interface // System Interface
input wire sys_clk, input wire sys_clk,
@@ -27,7 +31,8 @@ module ad9484_interface_400m (
// Output at 400MHz domain // Output at 400MHz domain
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
); );
// Pass-through clock (no BUFG needed in simulation) // Pass-through clock (no BUFG needed in simulation)
@@ -50,4 +55,15 @@ end
assign adc_data_400m = adc_data_400m_reg; assign adc_data_400m = adc_data_400m_reg;
assign adc_data_valid_400m = adc_data_valid_400m_reg; assign adc_data_valid_400m = adc_data_valid_400m_reg;
// Audit F-0.1: 1-cycle pipeline of adc_or_p to match the real IDDR+register
// capture path. TB drives adc_or_p directly with the overrange flag.
reg adc_overrange_400m_reg;
always @(posedge adc_dco_p or negedge reset_n) begin
if (!reset_n)
adc_overrange_400m_reg <= 1'b0;
else
adc_overrange_400m_reg <= adc_or_p;
end
assign adc_overrange_400m = adc_overrange_400m_reg;
endmodule endmodule
+2
View File
@@ -487,6 +487,8 @@ radar_system_top #(
.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(1'b0),
.adc_or_n(1'b1),
.adc_pwdn(adc_pwdn), .adc_pwdn(adc_pwdn),
// STM32 Control // STM32 Control
@@ -139,6 +139,8 @@ radar_receiver_final dut (
// ADC "LVDS" -- stub treats adc_d_p as single-ended data // ADC "LVDS" -- stub treats adc_d_p as single-ended data
.adc_d_p(adc_data), .adc_d_p(adc_data),
.adc_d_n(~adc_data), // Complement (ignored by stub) .adc_d_n(~adc_data), // Complement (ignored by stub)
.adc_or_p(1'b0), // F-0.1: no overrange stimulus in this TB
.adc_or_n(1'b1),
.adc_dco_p(clk_400m), // 400 MHz clock .adc_dco_p(clk_400m), // 400 MHz clock
.adc_dco_n(~clk_400m), // Complement (ignored by stub) .adc_dco_n(~clk_400m), // Complement (ignored by stub)
.adc_pwdn(), .adc_pwdn(),
+102
View File
@@ -427,6 +427,8 @@ radar_system_top #(
.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(1'b0),
.adc_or_n(1'b1),
.adc_pwdn(adc_pwdn), .adc_pwdn(adc_pwdn),
.stm32_new_chirp(stm32_new_chirp), .stm32_new_chirp(stm32_new_chirp),
@@ -938,6 +940,106 @@ initial begin
$display(""); $display("");
// ================================================================
// GROUP 9B: Adversarial reset sweep (audit F-2.2)
// ================================================================
// Drive the same auto-scan pipeline, then inject reset at four distinct
// offsets relative to a known-good start of operation. For each offset
// the system must:
// (a) present system_status == 0 while held in reset
// (b) produce at least one additional new_chirp_frame within the
// observation window after reset release
// (c) advance obs_range_valid_count (confirms full DDC+MF chain resumes)
// The four offsets are chosen to hit mid-chirp, mid-listen, and around
// the short/long chirp boundary, which covers the interesting FSM and
// CDC transitions in the pipeline.
$display("--- Group 9B: Adversarial reset sweep (F-2.2) ---");
begin : reset_sweep
integer sweep_i;
integer sweep_baseline_range;
integer sweep_baseline_chirp;
integer sweep_offsets [0:3];
integer sweep_holds [0:3];
reg sweep_ok;
// Reset injection offsets (ns) after the last auto-scan reconfigure.
// 3 us / 7 us / 12 us / 18 us sprayed across a short-chirp burst.
sweep_offsets[0] = 3000;
sweep_offsets[1] = 7000;
sweep_offsets[2] = 12000;
sweep_offsets[3] = 18000;
// Reset-assert durations mix short (~20 clk_100m) and long (~120)
sweep_holds[0] = 200;
sweep_holds[1] = 1200;
sweep_holds[2] = 400;
sweep_holds[3] = 800;
for (sweep_i = 0; sweep_i < 4; sweep_i = sweep_i + 1) begin
// Re-seed auto-scan from a clean base each iteration
reset_n = 0;
bfm_rx_wr_ptr = 0;
bfm_rx_rd_ptr = 0;
#200;
reset_n = 1;
#500;
stm32_mixers_enable = 1;
ft601_txe = 0;
bfm_send_cmd(8'h04, 8'h00, 16'h0001);
#500;
bfm_send_cmd(8'h01, 8'h00, 16'h0001);
bfm_send_cmd(8'h10, 8'h00, 16'd100);
bfm_send_cmd(8'h11, 8'h00, 16'd200);
bfm_send_cmd(8'h12, 8'h00, 16'd100);
bfm_send_cmd(8'h13, 8'h00, 16'd20);
bfm_send_cmd(8'h14, 8'h00, 16'd100);
bfm_send_cmd(8'h15, 8'h00, 16'd4);
// Let the pipeline reach steady-state and capture a baseline
#30000;
sweep_baseline_range = obs_range_valid_count;
sweep_baseline_chirp = obs_chirp_frame_count;
// Wait out the configured offset, then assert reset asynchronously
#(sweep_offsets[sweep_i]);
reset_n = 0;
#(sweep_holds[sweep_i]);
sweep_ok = (system_status == 4'b0000);
check(sweep_ok,
"G9B.a: system_status drops to 0 during injected reset");
// Release reset, re-configure (regs are cleared), allow recovery
reset_n = 1;
#500;
stm32_mixers_enable = 1;
ft601_txe = 0;
bfm_send_cmd(8'h04, 8'h00, 16'h0001);
#500;
bfm_send_cmd(8'h01, 8'h00, 16'h0001);
bfm_send_cmd(8'h10, 8'h00, 16'd100);
bfm_send_cmd(8'h11, 8'h00, 16'd200);
bfm_send_cmd(8'h12, 8'h00, 16'd100);
bfm_send_cmd(8'h13, 8'h00, 16'd20);
bfm_send_cmd(8'h14, 8'h00, 16'd100);
bfm_send_cmd(8'h15, 8'h00, 16'd4);
sweep_baseline_range = obs_range_valid_count;
sweep_baseline_chirp = obs_chirp_frame_count;
#60000; // 60 us — two+ short-chirp frames
check(obs_chirp_frame_count > sweep_baseline_chirp,
"G9B.b: new_chirp_frame resumes after injected reset");
check(obs_range_valid_count > sweep_baseline_range,
"G9B.c: range pipeline resumes after injected reset");
$display(" [F-2.2] iter=%0d offset=%0dns hold=%0dns chirps=+%0d ranges=+%0d",
sweep_i, sweep_offsets[sweep_i], sweep_holds[sweep_i],
obs_chirp_frame_count - sweep_baseline_chirp,
obs_range_valid_count - sweep_baseline_range);
end
end
$display("");
// ================================================================ // ================================================================
// GROUP 10: STREAM CONTROL (Gap 2) // GROUP 10: STREAM CONTROL (Gap 2)
// ================================================================ // ================================================================