mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-21 07:11:55 +00:00
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:
@@ -802,6 +802,7 @@ if (USB_MODE == 0) begin : gen_ft601
|
||||
.status_short_listen(host_short_listen_cycles),
|
||||
.status_chirps_per_elev(host_chirps_per_elev),
|
||||
.status_range_mode(host_range_mode),
|
||||
.status_chirps_mismatch(chirps_mismatch_error),
|
||||
|
||||
// Self-test status readback
|
||||
.status_self_test_flags(self_test_flags_latched),
|
||||
@@ -874,6 +875,7 @@ end else begin : gen_ft2232h
|
||||
.status_short_listen(host_short_listen_cycles),
|
||||
.status_chirps_per_elev(host_chirps_per_elev),
|
||||
.status_range_mode(host_range_mode),
|
||||
.status_chirps_mismatch(chirps_mismatch_error),
|
||||
|
||||
// Self-test status readback
|
||||
.status_self_test_flags(self_test_flags_latched),
|
||||
|
||||
@@ -69,6 +69,7 @@ module tb_usb_data_interface;
|
||||
reg [15:0] status_short_listen;
|
||||
reg [5:0] status_chirps_per_elev;
|
||||
reg [1:0] status_range_mode;
|
||||
reg status_chirps_mismatch;
|
||||
|
||||
// Self-test status readback inputs
|
||||
reg [4:0] status_self_test_flags;
|
||||
@@ -132,6 +133,7 @@ module tb_usb_data_interface;
|
||||
.status_short_listen (status_short_listen),
|
||||
.status_chirps_per_elev(status_chirps_per_elev),
|
||||
.status_range_mode (status_range_mode),
|
||||
.status_chirps_mismatch(status_chirps_mismatch),
|
||||
|
||||
// Self-test status readback
|
||||
.status_self_test_flags (status_self_test_flags),
|
||||
@@ -199,6 +201,7 @@ module tb_usb_data_interface;
|
||||
status_short_listen = 16'd17450;
|
||||
status_chirps_per_elev = 6'd32;
|
||||
status_range_mode = 2'b00;
|
||||
status_chirps_mismatch = 1'b0;
|
||||
status_self_test_flags = 5'b00000;
|
||||
status_self_test_detail = 8'd0;
|
||||
status_self_test_busy = 1'b0;
|
||||
@@ -921,6 +924,7 @@ module tb_usb_data_interface;
|
||||
status_short_listen = 16'd17450;
|
||||
status_chirps_per_elev = 6'd32;
|
||||
status_range_mode = 2'b10; // Long-range for status test
|
||||
status_chirps_mismatch = 1'b1; // TX-G: exercise the new bit too
|
||||
// Self-test status: all 5 tests passed, detail=0xA5, not busy
|
||||
status_self_test_flags = 5'b11111;
|
||||
status_self_test_detail = 8'hA5;
|
||||
@@ -964,8 +968,8 @@ module tb_usb_data_interface;
|
||||
"Status readback: word 2 = {guard, short_chirp}");
|
||||
check(uut.status_words[3] === {16'd17450, 10'd0, 6'd32},
|
||||
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
|
||||
check(uut.status_words[4] === {4'd5, 8'd180, 8'd12, 1'b1, 9'd0, 2'b10},
|
||||
"Status readback: word 4 = {agc_gain=5, peak=180, sat=12, en=1, range_mode=2}");
|
||||
check(uut.status_words[4] === {4'd5, 8'd180, 8'd12, 1'b1, 1'b1, 8'd0, 2'b10},
|
||||
"Status readback: word 4 = {agc_gain=5, peak=180, sat=12, en=1, mismatch=1, range_mode=2}");
|
||||
// status_words[5] = {7'd0, busy, 8'd0, detail[7:0], 3'd0, flags[4:0]}
|
||||
// = {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111}
|
||||
check(uut.status_words[5] === {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111},
|
||||
|
||||
@@ -92,6 +92,7 @@ module usb_data_interface (
|
||||
input wire [15:0] status_short_listen, // Current short listen cycles
|
||||
input wire [5:0] status_chirps_per_elev, // Current chirps per elevation
|
||||
input wire [1:0] status_range_mode, // Fix 7: Current range mode (0x20)
|
||||
input wire status_chirps_mismatch, // TX-G: host requested chirps != Doppler FFT size
|
||||
|
||||
// Self-test status readback (opcode 0x31 / included in 0xFF status packet)
|
||||
input wire [4:0] status_self_test_flags, // Per-test PASS(1)/FAIL(0) latched
|
||||
@@ -376,7 +377,8 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
|
||||
status_agc_peak_magnitude, // [27:20]
|
||||
status_agc_saturation_count, // [19:12] 8-bit saturation count
|
||||
status_agc_enable, // [11]
|
||||
9'd0, // [10:2] reserved
|
||||
status_chirps_mismatch, // [10] TX-G mismatch flag
|
||||
8'd0, // [9:2] reserved
|
||||
status_range_mode}; // [1:0]
|
||||
// Word 5: Self-test results {reserved[6:0], busy, reserved[7:0], detail[7:0], reserved[2:0], flags[4:0]}
|
||||
status_words[5] <= {7'd0, status_self_test_busy,
|
||||
|
||||
@@ -131,6 +131,7 @@ module usb_data_interface_ft2232h (
|
||||
input wire [15:0] status_short_listen,
|
||||
input wire [5:0] status_chirps_per_elev,
|
||||
input wire [1:0] status_range_mode,
|
||||
input wire status_chirps_mismatch, // TX-G: host requested chirps != Doppler FFT size
|
||||
|
||||
// Self-test status readback
|
||||
input wire [4:0] status_self_test_flags,
|
||||
@@ -671,12 +672,13 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
status_words[1] <= {status_long_chirp, status_long_listen};
|
||||
status_words[2] <= {status_guard, status_short_chirp};
|
||||
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||
status_words[4] <= {status_agc_current_gain,
|
||||
status_agc_peak_magnitude,
|
||||
status_agc_saturation_count,
|
||||
status_agc_enable,
|
||||
9'd0,
|
||||
status_range_mode};
|
||||
status_words[4] <= {status_agc_current_gain, // [31:28]
|
||||
status_agc_peak_magnitude, // [27:20]
|
||||
status_agc_saturation_count, // [19:12]
|
||||
status_agc_enable, // [11]
|
||||
status_chirps_mismatch, // [10] TX-G mismatch flag
|
||||
8'd0, // [9:2] reserved
|
||||
status_range_mode}; // [1:0]
|
||||
status_words[5] <= {7'd0, status_self_test_busy,
|
||||
8'd0, status_self_test_detail,
|
||||
3'd0, status_self_test_flags};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user