fix(fpga): frame-boundary atomic commit for USB timing regs

C-4: GUI Waveform Timing opcodes (0x10..0x14) write one parameter per
USB packet with arbitrary inter-opcode latency. Previously each opcode
landed directly on a live register consumed by radar_mode_controller,
so a mid-scan reconfiguration exposed the RX FSM to torn sets (e.g.
new long_chirp with old long_listen), and a value below the running
timer truncated the current chirp and corrupted one range-Doppler
frame.

Fix: split each of the five timing regs into pending (USB write target)
and live (FSM consumer). radar_mode_controller exports a new
cfg_commit_strobe that is HIGH while scanning is idle (S_IDLE) and
pulses for 1 cycle at every elevation/azimuth boundary in S_ADVANCE.
radar_system_top snapshots pending -> live on the strobe, guaranteeing
the FSM always sees a self-consistent set. No CDC added: strobe is in
clk_100m same as the USB-cmd path.

Regression: tb_radar_mode_controller adds 3 checks for strobe
semantics (idle-HIGH, scanning-LOW, one pulse per elevation boundary).
32/32 full regression PASS; Radar Mode Controller TB grows from 78 to
81 checks.
This commit is contained in:
Jason
2026-04-23 04:43:02 +05:45
parent 52a3497ea2
commit 5617d552df
4 changed files with 128 additions and 23 deletions
@@ -44,6 +44,7 @@ module tb_radar_mode_controller;
wire [5:0] azimuth_count;
wire scanning;
wire scan_complete;
wire cfg_commit_strobe;
// Test bookkeeping
integer pass_count;
@@ -60,6 +61,7 @@ module tb_radar_mode_controller;
integer elevation_toggles;
integer azimuth_toggles;
integer scan_completes;
integer commit_pulses;
// Saved values for toggle checks
reg saved_mc_new_chirp;
@@ -104,7 +106,8 @@ module tb_radar_mode_controller;
.elevation_count (elevation_count),
.azimuth_count (azimuth_count),
.scanning (scanning),
.scan_complete (scan_complete)
.scan_complete (scan_complete),
.cfg_commit_strobe (cfg_commit_strobe)
);
// Check task
@@ -173,6 +176,12 @@ module tb_radar_mode_controller;
check(azimuth_count === 6'd0, "azimuth_count=0 after reset");
check(scanning === 1'b0, "scanning=0 after reset");
check(scan_complete === 1'b0, "scan_complete=0 after reset");
// C-4: cfg_commit_strobe must be HIGH while idle so a timing-reg
// opcode written before the first trigger is latched into the
// live set immediately (no stale default on first chirp).
check(cfg_commit_strobe === 1'b1,
"cfg_commit_strobe=1 while idle (pre-scan commit window)");
reset_n = 1;
@(posedge clk); #1;
@@ -249,11 +258,18 @@ module tb_radar_mode_controller;
elevation_toggles = 0;
azimuth_toggles = 0;
scan_completes = 0;
commit_pulses = 0;
// Check: scanning starts immediately
@(posedge clk); #1;
check(scanning === 1'b1, "Scanning starts immediately in auto mode");
// C-4: once scanning is active, cfg_commit_strobe must drop low
// (no longer in S_IDLE). Sample immediately after the IDLEchirp
// transition. It will pulse again only at elevation boundaries.
check(cfg_commit_strobe === 1'b0,
"cfg_commit_strobe=0 once scan has left IDLE");
// Run for enough cycles to complete one full scan
for (i = 0; i < 15000; i = i + 1) begin
@(posedge clk); #1;
@@ -266,6 +282,8 @@ module tb_radar_mode_controller;
azimuth_toggles = azimuth_toggles + 1;
if (scan_complete)
scan_completes = scan_completes + 1;
if (cfg_commit_strobe)
commit_pulses = commit_pulses + 1;
mc_new_chirp_prev = mc_new_chirp;
mc_new_elevation_prev = mc_new_elevation;
@@ -288,6 +306,11 @@ module tb_radar_mode_controller;
check(chirp_toggles >= SIM_CHIRPS * SIM_ELEVATIONS * SIM_AZIMUTHS,
"At least 24 chirp toggles in full scan");
// C-4: one commit pulse per elevation boundary (commit window).
// With SIM_ELEVATIONS*SIM_AZIMUTHS elevations visited per scan,
// the counter should see at least that many pulses.
check(commit_pulses >= SIM_ELEVATIONS * SIM_AZIMUTHS,
"cfg_commit_strobe pulses at every elevation boundary");
check(scan_completes >= 1,
"At least 1 scan completion detected");
check(elevation_toggles >= SIM_AZIMUTHS,