mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-24 00:32:02 +00:00
test(gui): GUI-S3 — pin status word 4 bit layout in a co-spec test
Cross-verified status word 4 bit positions against the FPGA word builder (usb_data_interface.v:376-380, usb_data_interface_ft2232h.v:675-679) and the GUI parser (radar_protocol.py:252-257) — all positions match. No production change needed; the gap was that nothing in the test suite caught a future drift between the two sides. Added test_parse_status_word4_layout_co_spec: a single canonical layout table is the source of truth; for each field the test sets only that field to its max value, builds the status packet via the existing FPGA-builder-mirror _make_status_packet, parses, and asserts the field round-trips exactly AND every other field reads back zero. Catches both LSB drift and width drift on either side of the wire. Pre-checks that widths plus the reserved [9:2] gap sum to 32 and that no two fields overlap. Also fixed test_default_shapes — stale (64,32)/(64,) literals predated the GUI-C1 / Q3 alignment that bumped NUM_RANGE_BINS 64 -> 512. test_v7 was updated at the time, this one was missed. Replaced with references to NUM_RANGE_BINS/NUM_DOPPLER_BINS so any future bin-count change auto-updates the assertion. Suite now 180/180 PASS.
This commit is contained in:
@@ -207,6 +207,63 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
raw[25] = 0x00 # corrupt footer (was at index 21 in old 5-word format)
|
||||
self.assertIsNone(RadarProtocol.parse_status_packet(bytes(raw)))
|
||||
|
||||
def test_parse_status_word4_layout_co_spec(self):
|
||||
"""GUI-S3: pin status word 4 bit positions to the FPGA word builder.
|
||||
|
||||
Canonical layout per usb_data_interface.v:376-380 and
|
||||
usb_data_interface_ft2232h.v:675-679 — exactly one source of truth
|
||||
in this test, so any future drift between FPGA and GUI trips here:
|
||||
|
||||
[31:28] agc_current_gain (4-bit)
|
||||
[27:20] agc_peak_magnitude (8-bit)
|
||||
[19:12] agc_saturation_count (8-bit)
|
||||
[11] agc_enable (1-bit)
|
||||
[10] chirps_mismatch (1-bit, TX-G)
|
||||
[9:2] reserved (8 bits, must be zero from builder)
|
||||
[1:0] range_mode (2-bit)
|
||||
|
||||
For each field we set ONLY that field to its max, build the packet,
|
||||
parse, and assert (a) the field reads back correctly and (b) every
|
||||
other field reads back zero. Catches both LSB drift and width drift
|
||||
on either side of the wire.
|
||||
"""
|
||||
layout = [
|
||||
# (field_name, builder_kwarg, lsb, width, parsed_attr)
|
||||
("agc_current_gain", "agc_gain", 28, 4, "agc_current_gain"),
|
||||
("agc_peak_magnitude", "agc_peak", 20, 8, "agc_peak_magnitude"),
|
||||
("agc_saturation_count", "agc_sat", 12, 8, "agc_saturation_count"),
|
||||
("agc_enable", "agc_enable", 11, 1, "agc_enable"),
|
||||
("chirps_mismatch", "chirps_mismatch", 10, 1, "chirps_mismatch"),
|
||||
("range_mode", "range_mode", 0, 2, "range_mode"),
|
||||
]
|
||||
# Sanity: layout fields + reserved [9:2] must cover exactly 32 bits.
|
||||
used = sum(width for _, _, _, width, _ in layout)
|
||||
self.assertEqual(used + 8, 32,
|
||||
"word 4 layout (incl. reserved [9:2]) must total 32 bits")
|
||||
|
||||
# No two fields may overlap.
|
||||
occupied = set()
|
||||
for name, _, lsb, width, _ in layout:
|
||||
bits = set(range(lsb, lsb + width))
|
||||
self.assertFalse(occupied & bits,
|
||||
f"{name} bits {sorted(bits)} overlap previously-allocated bits")
|
||||
occupied |= bits
|
||||
|
||||
other_attrs = [attr for _, _, _, _, attr in layout]
|
||||
|
||||
for name, kwarg, _lsb, width, attr in layout:
|
||||
max_val = (1 << width) - 1
|
||||
raw = self._make_status_packet(**{kwarg: max_val})
|
||||
sr = RadarProtocol.parse_status_packet(raw)
|
||||
self.assertIsNotNone(sr, f"{name}: parse failed")
|
||||
self.assertEqual(getattr(sr, attr), max_val,
|
||||
f"{name}: round-trip mismatch (set={max_val}, got={getattr(sr, attr)})")
|
||||
for other in other_attrs:
|
||||
if other == attr:
|
||||
continue
|
||||
self.assertEqual(getattr(sr, other), 0,
|
||||
f"{name} max value bled into {other} -- bit-position drift?")
|
||||
|
||||
def test_parse_status_self_test_all_pass(self):
|
||||
"""Status with all self-test flags set (all tests pass)."""
|
||||
raw = self._make_status_packet(st_flags=0x1F, st_detail=0xA5, st_busy=0)
|
||||
@@ -558,12 +615,15 @@ class TestRadarFrameDefaults(unittest.TestCase):
|
||||
"""Test RadarFrame default initialization."""
|
||||
|
||||
def test_default_shapes(self):
|
||||
# Stale literals (64,32)/(64,) predated the GUI-C1 / Q3 alignment that
|
||||
# bumped NUM_RANGE_BINS 64 -> 512 to match FPGA truth. Reference the
|
||||
# constants so any future bin-count change updates the assertion too.
|
||||
f = RadarFrame()
|
||||
self.assertEqual(f.range_doppler_i.shape, (64, 32))
|
||||
self.assertEqual(f.range_doppler_q.shape, (64, 32))
|
||||
self.assertEqual(f.magnitude.shape, (64, 32))
|
||||
self.assertEqual(f.detections.shape, (64, 32))
|
||||
self.assertEqual(f.range_profile.shape, (64,))
|
||||
self.assertEqual(f.range_doppler_i.shape, (NUM_RANGE_BINS, NUM_DOPPLER_BINS))
|
||||
self.assertEqual(f.range_doppler_q.shape, (NUM_RANGE_BINS, NUM_DOPPLER_BINS))
|
||||
self.assertEqual(f.magnitude.shape, (NUM_RANGE_BINS, NUM_DOPPLER_BINS))
|
||||
self.assertEqual(f.detections.shape, (NUM_RANGE_BINS, NUM_DOPPLER_BINS))
|
||||
self.assertEqual(f.range_profile.shape, (NUM_RANGE_BINS,))
|
||||
self.assertEqual(f.detection_count, 0)
|
||||
|
||||
def test_default_zeros(self):
|
||||
|
||||
Reference in New Issue
Block a user