mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-08 14:44:56 +00:00
PR-AB.b expanded commit 4: GUI cleanup (modes / range_mode strip)
Strip the host-side parser/dashboard/test references for the FPGA registers retired in commit 1: host_radar_mode (opcode 0x01), host_trigger_pulse (opcode 0x02), and host_range_mode (opcode 0x20). The v7 backend (models.py / software_fpga.py / processing.py) had no references — only the parser, dashboard, and Tk test file did. - radar_protocol.py: drop Opcode.RADAR_MODE / TRIGGER_PULSE / RANGE_MODE enum members; rebuild the doc table around the surviving opcodes; drop StatusResponse.radar_mode and StatusResponse.range_mode fields; drop the two parse-status assignments (sr.radar_mode = words[0]>>22 and sr.range_mode = words[4] & 0x03); update the layout comments on words 0 + 4 to mark the freed bits as reserved-0. Acquisition loop log line switches from "mode=X stream=Y" to "stream=Y chirps/elev=Z" — radar mode is no longer a runtime concept. - v7/dashboard.py: delete the "Radar Mode Off" (0x01) and "Trigger Chirp" (0x02) QPushButtons from the operations group; remove "Mode:" and "Range Mode:" fields from _update_status_display. - test_GUI_V65_Tk.py: drop mode + range_mode kwargs from the _make_status_packet helper; update the word-4 layout co-spec test (range_mode entry deleted, reserved span widened from [9:2] to [9:0], sanity-check sum bumped from used+8 to used+10); delete test_parse_status_range_mode + test_radar_mode_names; rename test_agc_and_range_mode_coexist → test_agc_fields_coexist_with_mismatch with chirps_mismatch replacing range_mode as the coexisting field; drop 0x01 / 0x02 / 0x20 from test_all_rtl_opcodes_present's expected set. GUI tests: 118/0 (test_GUI_V65_Tk) + 152/0 (test_v7). FPGA regression unchanged at 42/0/0.
This commit is contained in:
@@ -124,24 +124,23 @@ class Opcode(IntEnum):
|
||||
"""Host register opcodes — must match radar_system_top.v case(usb_cmd_opcode).
|
||||
|
||||
FPGA truth table (from radar_system_top.v opcode dispatch case-block):
|
||||
0x01 host_radar_mode 0x20 host_range_mode
|
||||
0x02 host_trigger_pulse 0x21-0x27 CFAR / MTI / DC-notch
|
||||
0x03 host_detect_threshold 0x28-0x2C AGC control
|
||||
0x04 host_stream_control 0x2D host_cfar_alpha_soft
|
||||
0x10 host_long_chirp_cycles 0x30 host_self_test_trigger
|
||||
0x11 host_long_listen_cycles 0x31/0xFF host_status_request
|
||||
0x12 host_guard_cycles 0x32 host_adc_pwdn
|
||||
0x13 host_short_chirp_cycles 0x33 host_adc_format
|
||||
0x14 host_short_listen_cycles
|
||||
0x03 host_detect_threshold 0x21-0x27 CFAR / MTI / DC-notch
|
||||
0x04 host_stream_control 0x28-0x2C AGC control
|
||||
0x10 host_long_chirp_cycles 0x2D host_cfar_alpha_soft
|
||||
0x11 host_long_listen_cycles 0x30 host_self_test_trigger
|
||||
0x12 host_guard_cycles 0x31/0xFF host_status_request
|
||||
0x13 host_short_chirp_cycles 0x32 host_adc_pwdn
|
||||
0x14 host_short_listen_cycles 0x33 host_adc_format
|
||||
0x15 host_chirps_per_elev
|
||||
0x16 host_gain_shift
|
||||
0x17 host_medium_chirp_cycles (PR-G G2)
|
||||
0x18 host_medium_listen_cycles (PR-G G2)
|
||||
0x19 host_subframe_enable (PR-U / M-8 — 3-bit {LONG, MED, SHORT} mask)
|
||||
|
||||
PR-AB.b expanded retired opcodes 0x01 (host_radar_mode),
|
||||
0x02 (host_trigger_pulse), 0x20 (host_range_mode).
|
||||
"""
|
||||
# --- Basic control (0x01-0x04) ---
|
||||
RADAR_MODE = 0x01 # 2-bit mode select
|
||||
TRIGGER_PULSE = 0x02 # self-clearing one-shot trigger
|
||||
# --- Basic control (0x03-0x04) ---
|
||||
DETECT_THRESHOLD = 0x03 # 16-bit detection threshold value
|
||||
STREAM_CONTROL = 0x04 # 6-bit stream enable mask (FPGA: usb_cmd_value[5:0])
|
||||
|
||||
@@ -167,8 +166,8 @@ class Opcode(IntEnum):
|
||||
# otherwise be wrong when the scheduler skips a sub-frame).
|
||||
SUBFRAME_ENABLE = 0x19
|
||||
|
||||
# --- Signal processing (0x20-0x27) ---
|
||||
RANGE_MODE = 0x20
|
||||
# --- Signal processing (0x21-0x27;
|
||||
# 0x20 host_range_mode retired in PR-AB.b expanded) ---
|
||||
CFAR_GUARD = 0x21
|
||||
CFAR_TRAIN = 0x22
|
||||
CFAR_ALPHA = 0x23
|
||||
@@ -243,7 +242,6 @@ class RadarFrame:
|
||||
@dataclass
|
||||
class StatusResponse:
|
||||
"""Parsed status response from FPGA (M-5: 8-word / 34-byte packet)."""
|
||||
radar_mode: int = 0
|
||||
stream_ctrl: int = 0
|
||||
cfar_threshold: int = 0
|
||||
long_chirp: int = 0
|
||||
@@ -252,7 +250,6 @@ class StatusResponse:
|
||||
short_chirp: int = 0
|
||||
short_listen: int = 0
|
||||
chirps_per_elev: int = 0
|
||||
range_mode: int = 0
|
||||
# Self-test results (word 5, added in Build 26)
|
||||
self_test_flags: int = 0 # 5-bit result flags [4:0]
|
||||
self_test_detail: int = 0 # 8-bit detail code [7:0]
|
||||
@@ -363,10 +360,10 @@ class RadarProtocol:
|
||||
return None
|
||||
|
||||
sr = StatusResponse()
|
||||
# Word 0: {0xFF[31:24], mode[23:22], stream[21:19], 3'b000[18:16], threshold[15:0]}
|
||||
# Word 0: {0xFF[31:24], reserved[23:22], stream[21:19], 3'b000[18:16], threshold[15:0]}
|
||||
# PR-AB.b expanded: bits [23:22] formerly radar_mode, now reserved 0.
|
||||
sr.cfar_threshold = words[0] & 0xFFFF
|
||||
sr.stream_ctrl = (words[0] >> 19) & 0x07
|
||||
sr.radar_mode = (words[0] >> 22) & 0x03
|
||||
# Word 1: {long_chirp[31:16], long_listen[15:0]}
|
||||
sr.long_listen = words[1] & 0xFFFF
|
||||
sr.long_chirp = (words[1] >> 16) & 0xFFFF
|
||||
@@ -376,8 +373,8 @@ 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 layout: gain[31:28] peak[27:20] sat[19:12] agc_en[11] mismatch[10] mode[1:0]
|
||||
sr.range_mode = words[4] & 0x03
|
||||
# Word 4 layout: gain[31:28] peak[27:20] sat[19:12] agc_en[11] mismatch[10] reserved[1:0]
|
||||
# PR-AB.b expanded: bits [1:0] formerly range_mode, now reserved 0.
|
||||
sr.chirps_mismatch = (words[4] >> 10) & 0x01
|
||||
sr.agc_enable = (words[4] >> 11) & 0x01
|
||||
sr.agc_saturation_count = (words[4] >> 12) & 0xFF
|
||||
@@ -1144,8 +1141,8 @@ class RadarAcquisition(threading.Thread):
|
||||
elif ptype == "status":
|
||||
status = RadarProtocol.parse_status_packet(raw[start:end])
|
||||
if status is not None:
|
||||
log.info(f"Status: mode={status.radar_mode} "
|
||||
f"stream={status.stream_ctrl}")
|
||||
log.info(f"Status: stream={status.stream_ctrl} "
|
||||
f"chirps/elev={status.chirps_per_elev}")
|
||||
if status.self_test_busy or status.self_test_flags:
|
||||
log.info(f"Self-test: busy={status.self_test_busy} "
|
||||
f"flags=0b{status.self_test_flags:05b} "
|
||||
|
||||
@@ -126,10 +126,10 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
# ----------------------------------------------------------------
|
||||
# Status packet parsing
|
||||
# ----------------------------------------------------------------
|
||||
def _make_status_packet(self, mode=1, stream=7, threshold=10000,
|
||||
def _make_status_packet(self, stream=7, threshold=10000,
|
||||
long_chirp=3000, long_listen=13700,
|
||||
guard=17540, short_chirp=50,
|
||||
short_listen=17450, chirps=32, range_mode=0,
|
||||
short_listen=17450, chirps=32,
|
||||
st_flags=0, st_detail=0, st_busy=0,
|
||||
agc_gain=0, agc_peak=0, agc_sat=0, agc_enable=0,
|
||||
chirps_mismatch=0,
|
||||
@@ -139,8 +139,9 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
pkt = bytearray()
|
||||
pkt.append(STATUS_HEADER_BYTE)
|
||||
|
||||
# Word 0: {0xFF[31:24], mode[23:22], stream[21:19], 3'b000[18:16], threshold[15:0]}
|
||||
w0 = (0xFF << 24) | ((mode & 0x03) << 22) | ((stream & 0x07) << 19) | (threshold & 0xFFFF)
|
||||
# Word 0: {0xFF[31:24], reserved[23:22], stream[21:19], 3'b000[18:16], threshold[15:0]}
|
||||
# PR-AB.b expanded: bits [23:22] formerly radar_mode, now reserved 0.
|
||||
w0 = (0xFF << 24) | ((stream & 0x07) << 19) | (threshold & 0xFFFF)
|
||||
pkt += struct.pack(">I", w0)
|
||||
|
||||
# Word 1: {long_chirp, long_listen}
|
||||
@@ -157,11 +158,11 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
|
||||
# Word 4: {agc_current_gain[3:0], agc_peak_magnitude[7:0],
|
||||
# agc_saturation_count[7:0], agc_enable,
|
||||
# chirps_mismatch[10], 8'd0, range_mode[1:0]}
|
||||
# chirps_mismatch[10], 10'd0 reserved [9:0]}
|
||||
# PR-AB.b expanded: bits [1:0] formerly range_mode, now reserved 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))
|
||||
((chirps_mismatch & 0x01) << 10))
|
||||
pkt += struct.pack(">I", w4)
|
||||
|
||||
# Word 5: {frame_drop[31:25], self_test_busy[24], 8'd0,
|
||||
@@ -186,7 +187,6 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
raw = self._make_status_packet()
|
||||
sr = RadarProtocol.parse_status_packet(raw)
|
||||
self.assertIsNotNone(sr)
|
||||
self.assertEqual(sr.radar_mode, 1)
|
||||
self.assertEqual(sr.stream_ctrl, 7)
|
||||
self.assertEqual(sr.cfar_threshold, 10000)
|
||||
self.assertEqual(sr.long_chirp, 3000)
|
||||
@@ -195,21 +195,14 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
self.assertEqual(sr.short_chirp, 50)
|
||||
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)
|
||||
raw = self._make_status_packet(chirps_mismatch=1, agc_enable=1)
|
||||
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):
|
||||
# Anything under STATUS_PACKET_SIZE (34 post-M-5) must be rejected.
|
||||
@@ -274,8 +267,9 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
[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)
|
||||
[9:0] reserved (10 bits, must be zero from builder)
|
||||
(was [9:2] + range_mode[1:0]; range_mode retired in
|
||||
PR-AB.b expanded)
|
||||
|
||||
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
|
||||
@@ -289,12 +283,11 @@ class TestRadarProtocol(unittest.TestCase):
|
||||
("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.
|
||||
# Sanity: layout fields + reserved [9:0] 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")
|
||||
self.assertEqual(used + 10, 32,
|
||||
"word 4 layout (incl. reserved [9:0]) must total 32 bits")
|
||||
|
||||
# No two fields may overlap.
|
||||
occupied = set()
|
||||
@@ -1000,18 +993,24 @@ class TestOpcodeEnum(unittest.TestCase):
|
||||
self.assertFalse(hasattr(Opcode, name),
|
||||
f"Legacy alias Opcode.{name} should not exist")
|
||||
|
||||
def test_radar_mode_names(self):
|
||||
"""New canonical names must exist and match FPGA opcodes."""
|
||||
self.assertEqual(Opcode.RADAR_MODE, 0x01)
|
||||
self.assertEqual(Opcode.TRIGGER_PULSE, 0x02)
|
||||
def test_basic_control_opcodes(self):
|
||||
"""Canonical basic-control opcodes (0x03/0x04) match FPGA RTL.
|
||||
|
||||
PR-AB.b expanded retired RADAR_MODE (0x01) and TRIGGER_PULSE (0x02);
|
||||
the legacy-alias test below ensures they stay absent.
|
||||
"""
|
||||
self.assertEqual(Opcode.DETECT_THRESHOLD, 0x03)
|
||||
self.assertEqual(Opcode.STREAM_CONTROL, 0x04)
|
||||
|
||||
def test_all_rtl_opcodes_present(self):
|
||||
"""Every RTL opcode (from radar_system_top.v) has a matching Opcode enum member."""
|
||||
expected = {0x01, 0x02, 0x03, 0x04,
|
||||
"""Every RTL opcode (from radar_system_top.v) has a matching Opcode enum member.
|
||||
|
||||
PR-AB.b expanded: 0x01 / 0x02 / 0x20 retired from the FPGA dispatch
|
||||
case-block; they must NOT reappear in the Opcode enum.
|
||||
"""
|
||||
expected = {0x03, 0x04,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2A, 0x2B, 0x2C,
|
||||
0x30, 0x31, 0xFF}
|
||||
enum_values = {int(m) for m in Opcode}
|
||||
@@ -1093,17 +1092,17 @@ class TestAGCStatusParsing(unittest.TestCase):
|
||||
self.assertEqual(sr.agc_saturation_count, 255)
|
||||
self.assertEqual(sr.agc_enable, 1)
|
||||
|
||||
def test_agc_and_range_mode_coexist(self):
|
||||
"""AGC fields and range_mode occupy the same word without conflict."""
|
||||
def test_agc_fields_coexist_with_mismatch(self):
|
||||
"""AGC fields and chirps_mismatch occupy the same word without conflict."""
|
||||
raw = self._make_status_packet(agc_gain=5, agc_peak=128,
|
||||
agc_sat=42, agc_enable=1,
|
||||
range_mode=2)
|
||||
chirps_mismatch=1)
|
||||
sr = RadarProtocol.parse_status_packet(raw)
|
||||
self.assertEqual(sr.agc_current_gain, 5)
|
||||
self.assertEqual(sr.agc_peak_magnitude, 128)
|
||||
self.assertEqual(sr.agc_saturation_count, 42)
|
||||
self.assertEqual(sr.agc_enable, 1)
|
||||
self.assertEqual(sr.range_mode, 2)
|
||||
self.assertEqual(sr.chirps_mismatch, 1)
|
||||
|
||||
|
||||
class TestAGCStatusResponseDefaults(unittest.TestCase):
|
||||
|
||||
@@ -697,13 +697,9 @@ class RadarDashboard(QMainWindow):
|
||||
btn_mode_on.clicked.connect(lambda: self._send_fpga_cmd(0x01, 1))
|
||||
op_layout.addWidget(btn_mode_on)
|
||||
|
||||
btn_mode_off = QPushButton("Radar Mode Off")
|
||||
btn_mode_off.clicked.connect(lambda: self._send_fpga_cmd(0x01, 0))
|
||||
op_layout.addWidget(btn_mode_off)
|
||||
|
||||
btn_trigger = QPushButton("Trigger Chirp")
|
||||
btn_trigger.clicked.connect(lambda: self._send_fpga_cmd(0x02, 1))
|
||||
op_layout.addWidget(btn_trigger)
|
||||
# PR-AB.b expanded: "Radar Mode Off" (opcode 0x01) and "Trigger Chirp"
|
||||
# (opcode 0x02) buttons retired — the FPGA-side host_radar_mode and
|
||||
# host_trigger_pulse registers are gone. Auto-scan is the only mode.
|
||||
|
||||
# Stream Control (3-bit mask)
|
||||
self._add_fpga_param_row(op_layout, "Stream Control", 0x04, 7, 3,
|
||||
@@ -1951,12 +1947,11 @@ class RadarDashboard(QMainWindow):
|
||||
"""Update FPGA status readback labels."""
|
||||
# Diagnostics tab
|
||||
lines = [
|
||||
f"Mode: {st.radar_mode} Stream: {st.stream_ctrl:03b} "
|
||||
f"Thresh: {st.cfar_threshold}",
|
||||
f"Stream: {st.stream_ctrl:03b} Thresh: {st.cfar_threshold}",
|
||||
f"Long Chirp: {st.long_chirp} Listen: {st.long_listen}",
|
||||
f"Guard: {st.guard} Short Chirp: {st.short_chirp} "
|
||||
f"Listen: {st.short_listen}",
|
||||
f"Chirps/Elev: {st.chirps_per_elev} Range Mode: {st.range_mode}",
|
||||
f"Chirps/Elev: {st.chirps_per_elev}",
|
||||
]
|
||||
self._fpga_status_label.setText("\n".join(lines))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user