diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index 1fb802b..24371b7 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -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), diff --git a/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v b/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v index 4a0f1f8..eef162f 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v +++ b/9_Firmware/9_2_FPGA/tb/tb_usb_data_interface.v @@ -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}, diff --git a/9_Firmware/9_2_FPGA/usb_data_interface.v b/9_Firmware/9_2_FPGA/usb_data_interface.v index 232ce19..a84f088 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface.v @@ -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, diff --git a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v index dd81b86..67fd100 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v @@ -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}; diff --git a/9_Firmware/9_3_GUI/radar_protocol.py b/9_Firmware/9_3_GUI/radar_protocol.py index 652c2ac..a3f71b1 100644 --- a/9_Firmware/9_3_GUI/radar_protocol.py +++ b/9_Firmware/9_3_GUI/radar_protocol.py @@ -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 diff --git a/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py b/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py index 1cd32ad..beee48b 100644 --- a/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py +++ b/9_Firmware/9_3_GUI/test_GUI_V65_Tk.py @@ -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))