PR-AB.b expanded commit 5: Beam-ready handshake (RTL + MCU + GUI)

Wire a per-frame MCU→FPGA "beam pattern ready" handshake so the chirp
scheduler can stall between 48-chirp frames until the MCU finishes
writing the next ADAR1000 pattern. The legacy unused stm32_new_chirp
input on PD8 is repurposed as stm32_beam_ready; chirp_scheduler.v gets
a new S_BEAM_WAIT state entered after each frame_pulse and an 80 ms
watchdog so a missed MCU toggle degrades to wall-clock cadence with a
sticky telemetry bit rather than stalling the radar.

Cold-reset defaults the handshake off (host_handshake_enable=0, new
opcode 0x1A); the GUI opts in once the MCU PD8 wiring is verified on
the bench. Both the FT601 and FT2232H status word 4 paths get the new
beam_handshake_watchdog_fired sticky at bit [1] (reclaimed from the
range_mode retirement in commit 1).

RTL:
- chirp_scheduler.v: 2-FF ASYNC_REG sync on beam_ready_async; 1-cycle
  edge detect (any transition, MCU side uses HAL_GPIO_TogglePin); new
  S_BEAM_WAIT state entered at frame_pulse when host_handshake_enable=1;
  23-bit beam_watchdog counter with BEAM_WATCHDOG_MAX = 8_000_000 (~80 ms
  at 100 MHz, ~10 nominal frames); beam_handshake_watchdog_fired output
  sticky across mixers_enable cycles, cleared only by reset_n; mid-wait
  disable releases the FSM so dropping the opcode never strands the
  radar between frames.
- radar_receiver_final.v: thread stm32_beam_ready_async +
  host_handshake_enable + beam_handshake_watchdog_fired through the
  scheduler instance.
- radar_system_top.v: rename input port stm32_new_chirp → stm32_beam_ready;
  add host_handshake_enable register (cold-reset = 1'b0); opcode 0x1A
  dispatch (value[0]); add rx_beam_handshake_watchdog wire; pack into
  status_words[4][1] in both USB paths.
- radar_system_top_50t.v: rename wrapper port + sub-instance wiring.
- usb_data_interface.v + usb_data_interface_ft2232h.v: add
  status_beam_handshake_watchdog input + 2-FF level CDC (same convention
  as F-6.4 / F-1.2 stickies); refresh word-4 layout doc comment; pack
  beam_handshake_wd_sync_1 into status_words[4][1].

XDC:
- xc7a50t_ftg256.xdc + xc7a200t_fbg484.xdc: rename stm32_new_chirp port
  references to stm32_beam_ready (same PD8 pin, F13 on 50T / L18 on
  200T).

MCU:
- main.h: add FPGA_BEAM_READY_Pin = GPIO_PIN_8 + FPGA_BEAM_READY_GPIO_Port
  = GPIOD alongside the existing FPGA_FRAME_PULSE alias.
- main.cpp:runRadarPulseSequence: insert HAL_GPIO_TogglePin(GPIOD,
  GPIO_PIN_8) after each setCustomBeamPattern16(RX) — once after the
  per-azimuth broadside (vector_0), once after matrix1, once after
  matrix2 — between the SPI burst completion and waitForFramePulse.

GUI:
- radar_protocol.py: Opcode.HANDSHAKE_ENABLE = 0x1A;
  StatusResponse.beam_handshake_watchdog = 0 default; parse word 4 bit
  [1] in parse_status_packet; update word-4 layout comment.
- test_GUI_V65_Tk.py: add beam_handshake_watchdog kwarg to
  _make_status_packet (sets bit [1] of word 4); refresh
  test_parse_status_word4_layout_co_spec to cover the new bit (used+9=32);
  add test_parse_status_beam_handshake_watchdog round-trip;
  test_handshake_enable_opcode pins 0x1A; defaults / chirps_mismatch /
  agc-coexist tests gain a watchdog==0 assertion; bump
  test_all_rtl_opcodes_present expected set to include 0x17/0x18/0x19/0x1A.

TB:
- new tb_chirp_scheduler_handshake.v (16 checks): legacy open-loop, edge
  exit (rising + falling), 200-cycle idle hold, watchdog auto-advance
  via force on dut.beam_watchdog, sticky-survives-mixers_disable,
  mid-wait disable release, reset_n clears sticky.
- run_regression.sh: register the new TB in PHASE 1.
- tb_radar_receiver_final.v: tie the 3 new receiver ports off
  (beam_ready_async=0, handshake_enable=0, watchdog unconnected).
- tb_system_mechanics.v / tb_system_opcodes.v: explicit
  .stm32_beam_ready(1'b0) connection (the cold-reset
  host_handshake_enable=0 keeps the FSM out of S_BEAM_WAIT).
- tb_usb_data_interface.v / tb_usb_protocol_v2.v / tb_e2e_dsp_to_host.v /
  tb_ft2232h_frame_drop.v: tie .status_beam_handshake_watchdog(1'b0).

Ride-along ruff sweep (14 → 0 across the repo):
- tb/cosim/compare_independent.py: RUF003 — '5×' → 'at least 5x'.
- tb/cosim/gen_e2e_expected.py: noqa: E402 on the post-sys.path import;
  drop unused EXPECTED_RANGE_BIN + EXPECTED_DOPPLER_BIN_PER_SF imports;
  fold the detect-class slot if/else into a ternary (SIM108).
- tb/cosim/gen_e2e_stimulus.py: drop int() wrapping round() at four
  call sites (RUF046 — round() already returns int in Python 3);
  rewrite the range-bin derivation comment block from code-like
  `# range_bin = ...` to prose (ERA001); strip stray f from
  placeholder-free error string (F541).
- tb/cosim/tb_e2e_dsp_to_host_parse.py: open(path, 'r') → open(path)
  (UP015).
- v7/dashboard.py: '3×' → '3x' (RUF003); drop quotes from
  'StatusResponse | None' annotation (UP037, file already has
  `from __future__ import annotations`).

CI summary (all suites green pre-commit):
- ruff: All checks passed!
- FPGA regression (iverilog): 43 / 0 / 0 (incl. new handshake TB 16/16).
- MCU tests: 51 / 0 + 34 / 0 + 13 / 13 ADAR1000_AGC.
- GUI Tk (test_GUI_V65_Tk): 120 / 0.
- GUI v7 (test_v7): 152 / 0.

Production rollout note: bitstream cold-resets with host_handshake_enable=0
so existing flashes keep their open-loop cadence until the GUI sends
opcode 0x1A=1. Once enabled, the per-pattern dwell tracks both the chirp
ladder (PD14 frame_pulse from commit-3 work) and the MCU pattern-write
completion (PD8 toggle from this commit), eliminating drift from the SPI
burst timing.
This commit is contained in:
Jason
2026-05-11 12:07:08 +05:45
parent 2e2c10baeb
commit 4989c33aa6
26 changed files with 642 additions and 86 deletions
@@ -358,7 +358,7 @@ def check_mf_invariants(result: CheckResult):
f"twin={twin_peak}, ref={ref_peak}"
)
# Sidelobe behaviour: peak should be 5× the median magnitude. Under
# Sidelobe behaviour: peak should be at least 5x the median magnitude. Under
# scaled-mode at amp=4000 the peak rises to ~977 while sidelobes stay
# near the LSB floor, easily clearing the threshold.
twin_peak_val = float(twin_mag[delay])
@@ -62,7 +62,7 @@ import numpy as np
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, THIS_DIR)
from fpga_model import DopplerProcessor, run_cfar_ca
from fpga_model import DopplerProcessor, run_cfar_ca # noqa: E402
# Pull stimulus configuration verbatim so dimensions stay aligned.
from gen_e2e_stimulus import ( # noqa: E402
@@ -72,8 +72,6 @@ from gen_e2e_stimulus import ( # noqa: E402
CHIRPS_PER_FRAME,
RANGE_BINS,
HOST_DC_NOTCH_WIDTH,
EXPECTED_RANGE_BIN,
EXPECTED_DOPPLER_BIN_PER_SF,
EXPECTED_DETECT_CELLS,
)
@@ -263,10 +261,8 @@ def pack_bulk_frame(frame_number: int, flags: int,
packed = 0
for slot in range(4):
db = byte_idx * 4 + slot
if db < DOPPLER_TOTAL_BINS:
code = int(cfar_class[rb, db]) & 0x3
else:
code = 0 # padding
# padding for db >= DOPPLER_TOTAL_BINS lands on slot 3
code = int(cfar_class[rb, db]) & 0x3 if db < DOPPLER_TOTAL_BINS else 0
packed |= code << ((3 - slot) * 2)
out.append(packed)
@@ -98,16 +98,15 @@ HOST_DC_NOTCH_WIDTH = 1
# ============================================================================
# Target placement -> expected bin coordinates
# ============================================================================
# range_bin = round(2 * R / c * fs / decim)
# = round(2 * 100 / 3e8 * 400e6 / 4)
# = round(66.667) = 67
EXPECTED_RANGE_BIN = int(round(2.0 * TARGET_RANGE_M / C_LIGHT * RANGE_BIN_HZ))
# Range bin formula: round(2 * R / c * fs / decim). For R=100m, fs=400 MHz,
# decim=4 -> round(2 * 100 / 3e8 * 100e6) = round(66.667) = 67.
EXPECTED_RANGE_BIN = round(2.0 * TARGET_RANGE_M / C_LIGHT * RANGE_BIN_HZ)
# Per-sub-frame doppler bin (folding into 16-pt FFT). For our 5 m/s target
# this is intentionally non-folding -> 1 in all three sub-frames.
F_DOPPLER_HZ = 2.0 * TARGET_VEL_MPS * F_CARRIER / C_LIGHT
EXPECTED_DOPPLER_BIN_PER_SF = tuple(
int(round(F_DOPPLER_HZ * DOPPLER_FFT_SIZE * pri)) % DOPPLER_FFT_SIZE
round(F_DOPPLER_HZ * DOPPLER_FFT_SIZE * pri) % DOPPLER_FFT_SIZE
for pri in PRI_BY_SF
)
# Flat 48-bin doppler-axis expected cells (sub_frame << 4 | bin).
@@ -160,8 +159,8 @@ def generate_range_decim_frame(seed: int = SCENE_SEED) -> tuple[np.ndarray, np.n
# Target injection at the expected range bin.
phi = _target_phase_rad(c)
sig_i = int(round(TARGET_AMPLITUDE * np.cos(phi)))
sig_q = int(round(TARGET_AMPLITUDE * np.sin(phi)))
sig_i = round(TARGET_AMPLITUDE * np.cos(phi))
sig_q = round(TARGET_AMPLITUDE * np.sin(phi))
frame_i[c, EXPECTED_RANGE_BIN] += sig_i
frame_q[c, EXPECTED_RANGE_BIN] += sig_q
@@ -231,7 +230,7 @@ def main() -> int:
f"shape={frame_i.shape}")
if n_lines != expected_lines:
print(f" ERROR: line count mismatch", file=sys.stderr)
print(" ERROR: line count mismatch", file=sys.stderr)
return 1
# Sanity: target peak should dominate at the expected range bin.
@@ -95,7 +95,7 @@ class TestState:
def load_captured_frame_hex(path: str) -> bytes:
"""Read iverilog $writememh output (one byte per line, 2-hex-digit)."""
out = bytearray()
with open(path, 'r') as f:
with open(path) as f:
for line in f:
tok = line.strip()
if not tok or tok.startswith('//'):
@@ -0,0 +1,279 @@
`timescale 1ns / 1ps
// ============================================================================
// tb_chirp_scheduler_handshake.v — PR-AB.b expanded commit 5 unit TB
//
// Exercises the beam-ready handshake added to chirp_scheduler.v:
// - S_BEAM_WAIT entered on frame_pulse when host_handshake_enable=1
// - Exit on any beam_ready_async edge (toggle semantic)
// - Watchdog auto-advances + sets beam_handshake_watchdog_fired sticky
// - host_handshake_enable=0 keeps legacy open-loop cadence
// - Mid-wait disable releases the FSM
// - Reset clears the sticky
//
// Uses compressed cycle counts so a full 48-chirp frame completes in <1 ms
// of sim time. The production cycle counts (175/161/167 µs PRIs) would push
// the sim past the iverilog regression budget; the FSM logic exercised here
// is independent of the cycle-count values.
// ============================================================================
`include "radar_params.vh"
module tb_chirp_scheduler_handshake;
// ---- Clock (100 MHz, 10 ns) ----
reg clk = 1'b0;
always #5 clk = ~clk;
// ---- Compressed timing — 6 chirp/listen/guard cycles each per PRI ----
localparam [15:0] T_CHIRP = 16'd6;
localparam [15:0] T_LISTEN = 16'd6;
localparam [15:0] T_GUARD = 16'd6;
// Single chirp + listen + guard ≈ 20 cycles. With chirps_per_subframe=2 and
// 3 sub-frames active, a frame lands at ~120 cycles → 1.2 µs/frame in sim.
localparam [5:0] CHIRPS_PER_SUBFRAME = 6'd2;
// ---- DUT signals ----
reg reset_n = 1'b0;
reg mixers_enable = 1'b0;
reg [2:0] subframe_enable = 3'b111;
reg beam_ready_async = 1'b0;
reg handshake_enable = 1'b0;
wire [1:0] wave_sel;
wire chirp_pulse;
wire subframe_pulse;
wire frame_pulse;
wire [5:0] chirp_counter;
wire [1:0] subframe_id;
wire [15:0] cfg_chirp_cycles;
wire [15:0] cfg_listen_cycles;
wire [15:0] cfg_guard_cycles;
wire watchdog_fired;
chirp_scheduler dut (
.clk (clk),
.reset_n (reset_n),
.host_subframe_enable (subframe_enable),
.host_short_chirp_cycles (T_CHIRP),
.host_short_listen_cycles (T_LISTEN),
.host_medium_chirp_cycles (T_CHIRP),
.host_medium_listen_cycles (T_LISTEN),
.host_long_chirp_cycles (T_CHIRP),
.host_long_listen_cycles (T_LISTEN),
.host_guard_cycles (T_GUARD),
.host_chirps_per_subframe (CHIRPS_PER_SUBFRAME),
.mixers_enable (mixers_enable),
.beam_ready_async (beam_ready_async),
.host_handshake_enable (handshake_enable),
.wave_sel (wave_sel),
.chirp_pulse (chirp_pulse),
.subframe_pulse (subframe_pulse),
.frame_pulse (frame_pulse),
.chirp_counter (chirp_counter),
.subframe_id (subframe_id),
.cfg_chirp_cycles (cfg_chirp_cycles),
.cfg_listen_cycles (cfg_listen_cycles),
.cfg_guard_cycles (cfg_guard_cycles),
.beam_handshake_watchdog_fired(watchdog_fired)
);
// ---- Bookkeeping ----
integer pass = 0;
integer fail = 0;
integer frame_pulse_count = 0;
always @(posedge clk) if (frame_pulse) frame_pulse_count = frame_pulse_count + 1;
task check;
input [255:0] label;
input cond;
begin
if (cond) begin
$display(" [PASS] %0s", label);
pass = pass + 1;
end else begin
$display(" [FAIL] %0s", label);
fail = fail + 1;
end
end
endtask
// Wait for the FSM to enter S_BEAM_WAIT (state == 3'd5).
task wait_for_beam_wait;
input integer timeout_cycles;
integer i;
begin
i = 0;
while (dut.state !== 3'd5 && i < timeout_cycles) begin
@(posedge clk);
i = i + 1;
end
end
endtask
// Wait for state to leave S_BEAM_WAIT (timeout reports failure to caller).
task wait_for_beam_wait_exit;
input integer timeout_cycles;
integer i;
begin
i = 0;
while (dut.state === 3'd5 && i < timeout_cycles) begin
@(posedge clk);
i = i + 1;
end
end
endtask
// Wait for at least N frames to complete (frame_pulse_count >= target).
task wait_frames;
input integer target;
input integer timeout_cycles;
integer i;
begin
i = 0;
while (frame_pulse_count < target && i < timeout_cycles) begin
@(posedge clk);
i = i + 1;
end
end
endtask
// =========================================================================
// MAIN
// =========================================================================
initial begin
$dumpfile("tb_chirp_scheduler_handshake.vcd");
$dumpvars(0, tb_chirp_scheduler_handshake);
$display("============================================================");
$display(" CHIRP_SCHEDULER beam-ready handshake (PR-AB.b expanded c5)");
$display("============================================================");
// Reset
reset_n = 1'b0;
mixers_enable = 1'b0;
handshake_enable = 1'b0;
beam_ready_async = 1'b0;
repeat (4) @(posedge clk);
reset_n = 1'b1;
@(posedge clk);
check("T1: post-reset watchdog sticky low", watchdog_fired == 1'b0);
// ====================================================================
// T2: Legacy mode (handshake_enable=0) — frames advance back-to-back,
// S_BEAM_WAIT must never be visited.
// ====================================================================
$display("--- T2: legacy open-loop (handshake_enable=0) ---");
mixers_enable = 1'b1;
frame_pulse_count = 0;
wait_frames(3, 5000);
check("T2: at least 3 frames fired without handshake", frame_pulse_count >= 3);
check("T2: watchdog sticky still low", watchdog_fired == 1'b0);
// Park the scheduler in IDLE so the next test starts clean.
mixers_enable = 1'b0;
repeat (5) @(posedge clk);
frame_pulse_count = 0;
// ====================================================================
// T3: Handshake enabled — scheduler should enter S_BEAM_WAIT after
// the next frame_pulse and only exit on a beam_ready edge.
// ====================================================================
$display("--- T3: handshake enabled, MCU toggles before watchdog ---");
handshake_enable = 1'b1;
mixers_enable = 1'b1;
wait_for_beam_wait(5000);
check("T3a: FSM entered S_BEAM_WAIT after a frame_pulse",
dut.state == 3'd5);
// Sit in S_BEAM_WAIT for a deliberate number of cycles; the scheduler
// must stay parked until we toggle beam_ready_async.
repeat (200) @(posedge clk);
check("T3b: FSM still in S_BEAM_WAIT after 200 idle cycles",
dut.state == 3'd5);
check("T3b: watchdog has not fired", watchdog_fired == 1'b0);
// Toggle beam_ready_async and verify exit within a handful of clk
// edges (2-FF sync + 1-cycle edge latch + S_BEAM_WAIT → S_CHIRP).
@(posedge clk); beam_ready_async = 1'b1;
wait_for_beam_wait_exit(50);
check("T3c: FSM left S_BEAM_WAIT after PD8 toggle (rising)",
dut.state != 3'd5);
// ====================================================================
// T4: Second toggle exits a later wait (verifies edge-detect handles
// falling edges symmetrically — HAL_GPIO_TogglePin gives both).
// ====================================================================
$display("--- T4: second wait, falling-edge ack ---");
wait_for_beam_wait(5000);
check("T4a: FSM re-entered S_BEAM_WAIT on next frame", dut.state == 3'd5);
@(posedge clk); beam_ready_async = 1'b0; // falling edge
wait_for_beam_wait_exit(50);
check("T4b: FSM left S_BEAM_WAIT after PD8 toggle (falling)",
dut.state != 3'd5);
// ====================================================================
// T5: Watchdog timeout. The real 23-bit terminal value (8M cycles ≈
// 80 ms) is unreachable in iverilog sim time, so we force the
// FSM's counter to the terminal value and let one clk edge
// resolve the (counter >= BEAM_WATCHDOG_MAX) branch.
// ====================================================================
$display("--- T5: watchdog auto-advance + sticky latch ---");
wait_for_beam_wait(5000);
check("T5a: FSM in S_BEAM_WAIT (pre-force)", dut.state == 3'd5);
// Force the counter to BEAM_WATCHDOG_MAX so the FSM's >= comparison
// trips on the next posedge; then release immediately so the always
// block can drive normally on the same edge.
@(negedge clk);
force dut.beam_watchdog = 23'd8_000_000;
@(posedge clk); #1;
release dut.beam_watchdog;
check("T5b: watchdog sticky latched after timeout",
watchdog_fired == 1'b1);
check("T5c: FSM left S_BEAM_WAIT after watchdog",
dut.state != 3'd5);
// ====================================================================
// T6: Sticky survives mixers_enable=0 cycle (only reset_n clears it).
// ====================================================================
$display("--- T6: sticky watchdog is reset-only ---");
mixers_enable = 1'b0;
repeat (20) @(posedge clk);
check("T6a: watchdog sticky stays high across mixers_enable=0",
watchdog_fired == 1'b1);
mixers_enable = 1'b1;
// ====================================================================
// T7: Mid-wait disable releases the FSM (handshake_enable→0).
// ====================================================================
$display("--- T7: mid-wait host_handshake_enable=0 releases FSM ---");
wait_for_beam_wait(5000);
check("T7a: FSM in S_BEAM_WAIT for disable test", dut.state == 3'd5);
@(posedge clk); handshake_enable = 1'b0;
wait_for_beam_wait_exit(20);
check("T7b: FSM left S_BEAM_WAIT after handshake disable",
dut.state != 3'd5);
// ====================================================================
// T8: Full reset clears sticky.
// ====================================================================
$display("--- T8: reset_n clears watchdog sticky ---");
reset_n = 1'b0;
repeat (4) @(posedge clk);
check("T8: watchdog sticky cleared by reset_n", watchdog_fired == 1'b0);
reset_n = 1'b1;
repeat (4) @(posedge clk);
$display("============================================================");
$display("RESULTS: pass=%0d fail=%0d", pass, fail);
$display("============================================================");
if (fail == 0) $display("[OVERALL] PASS");
else $display("[OVERALL] FAIL");
$finish;
end
initial begin
#1_000_000; // 1 ms wall-clock safety
$display("[FATAL] timeout");
$finish;
end
endmodule
@@ -361,6 +361,7 @@ module tb_e2e_dsp_to_host;
.status_agc_enable (1'b0),
.status_range_decim_watchdog(1'b0),
.status_ddc_cic_fir_overrun (1'b0),
.status_beam_handshake_watchdog(1'b0), // commit 5 tied off, e2e path doesn't model handshake
.status_cfar_alpha_soft (TEST_CFAR_ALPHA_SOFT),
.status_detect_threshold_soft(17'd0),
.status_detect_count_cand (16'd0)
@@ -150,6 +150,8 @@ module tb_ft2232h_frame_drop;
// AUDIT-S10: control-fault flags tied off (frame-drop TB scope)
.status_range_decim_watchdog(1'b0),
.status_ddc_cic_fir_overrun(1'b0),
// PR-AB.b expanded commit 5: beam-handshake watchdog tied off
.status_beam_handshake_watchdog(1'b0),
// PR-G: 2-tier CFAR telemetry tied off
.status_cfar_alpha_soft(8'h18), // RP_DEF_CFAR_ALPHA_SOFT
.status_detect_threshold_soft(17'd0),
@@ -185,7 +185,14 @@ radar_receiver_final dut (
.doppler_frame_done_out(),
// PR-E: pin mixers_enable HIGH so the scheduler runs in this TB
.mixers_enable_100m(1'b1)
.mixers_enable_100m(1'b1),
// PR-AB.b expanded commit 5: beam-ready handshake — tie off for this TB
// (legacy open-loop cadence). Tests for the handshake live in their own
// dedicated tb_chirp_scheduler_handshake.v unit TB.
.stm32_beam_ready_async(1'b0),
.host_handshake_enable(1'b0),
.beam_handshake_watchdog_fired()
);
// ============================================================================
+6 -1
View File
@@ -16,7 +16,11 @@
// mixer-disable propagation)
// G7.3 TX chirp counter CDC (120MHz -> 100MHz)
// — G7.1 (STM32→FPGA chirp toggle CDC stress) retired in PR-AB.b
// expanded; stm32_new_chirp port is gone.
// expanded; the stm32_new_chirp port was renamed to
// stm32_beam_ready in commit 5 (beam-ready handshake) and is
// tied 1'b0 here so the cold-reset default of host_handshake_enable
// (=0) keeps the FSM out of S_BEAM_WAIT and the open-loop cadence
// intact.
//
// DUT is radar_system_top with USB_MODE=1 (production FT2232H path); the
// FT2232H ports are wired so a stream_control opcode (0x04) can be sent at
@@ -155,6 +159,7 @@ radar_system_top #(.USB_MODE(1)) dut (
.adc_or_p(1'b0), .adc_or_n(1'b1),
.adc_pwdn(adc_pwdn),
.stm32_beam_ready(1'b0), // commit 5: handshake disabled by cold-reset default
.stm32_mixers_enable(stm32_mixers_enable),
.ft601_data(ft601_data),
@@ -158,6 +158,7 @@ radar_system_top #(
.adc_or_p(1'b0), .adc_or_n(1'b1),
.adc_pwdn(adc_pwdn),
.stm32_beam_ready(1'b0), // commit 5: handshake gated off by host_handshake_enable cold-reset = 0
.stm32_mixers_enable(stm32_mixers_enable),
// FT601 ports — tied off / unused in USB_MODE=1
@@ -144,7 +144,9 @@ module tb_usb_data_interface;
.status_agc_enable (status_agc_enable),
// AUDIT-S10: control-fault flags tied off (pre-existing TB scope)
.status_range_decim_watchdog(1'b0),
.status_ddc_cic_fir_overrun (1'b0)
.status_ddc_cic_fir_overrun (1'b0),
// PR-AB.b expanded commit 5: beam-handshake watchdog tied off
.status_beam_handshake_watchdog(1'b0)
);
// ── Test bookkeeping ───────────────────────────────────────
@@ -157,6 +157,10 @@ module tb_usb_protocol_v2;
.status_agc_enable(status_agc_enable),
.status_range_decim_watchdog(status_range_decim_watchdog),
.status_ddc_cic_fir_overrun(status_ddc_cic_fir_overrun),
// PR-AB.b expanded commit 5: beam-handshake watchdog tied off here;
// exercised by tb_chirp_scheduler_handshake.v and word-4 layout test
// refreshed below.
.status_beam_handshake_watchdog(1'b0),
.status_cfar_alpha_soft(status_cfar_alpha_soft),
.status_detect_threshold_soft(status_detect_threshold_soft),
.status_detect_count_cand(status_detect_count_cand)