fix(fpga): TX-G — surface chirps_mismatch_error to host status

`chirps_mismatch_error` was set in radar_system_top when the host
requested chirps_per_elev != Doppler FFT size, but never wired into the
USB status response — a latent silent failure.

Wired the flag through both USB interfaces (FT601 + FT2232H) into bit
[10] of status word 4 (was reserved). GUI parser exposes it as
StatusResponse.chirps_mismatch.

- usb_data_interface*.v: new status_chirps_mismatch input, packed at [10]
- radar_system_top.v: connect chirps_mismatch_error to both USB instances
- radar_protocol.py + test_GUI_V65_Tk.py: parse new bit, +1 round-trip test
- tb_usb_data_interface.v: drive the new port, update word-4 expectation

Tests: GUI 92/92 (was 91), MCU 75/75, USB TB 91/91, ruff clean repo-wide.
The 2 remaining FPGA regression failures (Receiver Integration, MF Chain)
are the pre-existing iverilog-can't-link-Xilinx-IP issue tracked
separately as the open RX-NEW-3 follow-up.
This commit is contained in:
Jason
2026-04-24 11:06:26 +05:45
parent 89dc9156c7
commit ca2b6e527d
6 changed files with 36 additions and 13 deletions
+3 -2
View File
@@ -147,6 +147,7 @@ class StatusResponse:
agc_peak_magnitude: int = 0 # 8-bit peak magnitude [7:0]
agc_saturation_count: int = 0 # 8-bit saturation count [7:0]
agc_enable: int = 0 # 1-bit AGC enable readback
chirps_mismatch: int = 0 # TX-G: 1 if FPGA clamped/rejected host chirps_per_elev
# ============================================================================
@@ -247,9 +248,9 @@ class RadarProtocol:
# Word 3: {short_listen[31:16], 10'd0, chirps_per_elev[5:0]}
sr.chirps_per_elev = words[3] & 0x3F
sr.short_listen = (words[3] >> 16) & 0xFFFF
# Word 4: {agc_current_gain[31:28], agc_peak_magnitude[27:20],
# agc_saturation_count[19:12], agc_enable[11], 9'd0, range_mode[1:0]}
# Word 4 layout: gain[31:28] peak[27:20] sat[19:12] agc_en[11] mismatch[10] mode[1:0]
sr.range_mode = words[4] & 0x03
sr.chirps_mismatch = (words[4] >> 10) & 0x01
sr.agc_enable = (words[4] >> 11) & 0x01
sr.agc_saturation_count = (words[4] >> 12) & 0xFF
sr.agc_peak_magnitude = (words[4] >> 20) & 0xFF
+14 -2
View File
@@ -126,7 +126,8 @@ class TestRadarProtocol(unittest.TestCase):
guard=17540, short_chirp=50,
short_listen=17450, chirps=32, range_mode=0,
st_flags=0, st_detail=0, st_busy=0,
agc_gain=0, agc_peak=0, agc_sat=0, agc_enable=0):
agc_gain=0, agc_peak=0, agc_sat=0, agc_enable=0,
chirps_mismatch=0):
"""Build a 26-byte status response matching FPGA format (Build 26)."""
pkt = bytearray()
pkt.append(STATUS_HEADER_BYTE)
@@ -148,9 +149,11 @@ class TestRadarProtocol(unittest.TestCase):
pkt += struct.pack(">I", w3)
# Word 4: {agc_current_gain[3:0], agc_peak_magnitude[7:0],
# agc_saturation_count[7:0], agc_enable, 9'd0, range_mode[1:0]}
# agc_saturation_count[7:0], agc_enable,
# chirps_mismatch[10], 8'd0, range_mode[1:0]}
w4 = (((agc_gain & 0x0F) << 28) | ((agc_peak & 0xFF) << 20) |
((agc_sat & 0xFF) << 12) | ((agc_enable & 0x01) << 11) |
((chirps_mismatch & 0x01) << 10) |
(range_mode & 0x03))
pkt += struct.pack(">I", w4)
@@ -176,12 +179,21 @@ class TestRadarProtocol(unittest.TestCase):
self.assertEqual(sr.short_listen, 17450)
self.assertEqual(sr.chirps_per_elev, 32)
self.assertEqual(sr.range_mode, 0)
self.assertEqual(sr.chirps_mismatch, 0)
def test_parse_status_range_mode(self):
raw = self._make_status_packet(range_mode=2)
sr = RadarProtocol.parse_status_packet(raw)
self.assertEqual(sr.range_mode, 2)
def test_parse_status_chirps_mismatch(self):
# TX-G: bit 10 of word 4 must round-trip without disturbing neighbours.
raw = self._make_status_packet(chirps_mismatch=1, agc_enable=1, range_mode=2)
sr = RadarProtocol.parse_status_packet(raw)
self.assertEqual(sr.chirps_mismatch, 1)
self.assertEqual(sr.agc_enable, 1)
self.assertEqual(sr.range_mode, 2)
def test_parse_status_too_short(self):
self.assertIsNone(RadarProtocol.parse_status_packet(b"\xBB" + b"\x00" * 20))