feat(rtl,gui): PR-U / M-8 — sub-frame enable mask routed end-to-end (C-5 hardening)

The chirp_scheduler had a 3-bit host_subframe_enable input {LONG, MEDIUM, SHORT}
that was tied to the constant RP_DEF_SUBFRAME_ENABLE at the receiver instance,
so the host could neither change it nor know what mask was active. With the
mask not at 3'b111 the scheduler skips a sub-frame at TX but doppler_processor
still writes 48 chirp slots, so the host CRT (`dbin // 16 → {SHORT, MED, LONG}`)
silently mis-attributes the SF axis and unfolds to the wrong velocity.

Plumb the mask through:

- radar_system_top.v: new reg [2:0] host_subframe_enable, cold-reset
  RP_DEF_SUBFRAME_ENABLE, opcode 0x19 setter, wired to rx_inst and usb_inst.
- radar_receiver_final.v: new host_subframe_enable[2:0] input port; the
  chirp_scheduler instance is untied from the constant.
- usb_data_interface_ft2232h.v: new subframe_enable[2:0] input + per-frame
  snapshot reg latched at frame_complete (stable for ft_clk read, same
  pattern as stream_flags_snapshot). Byte 2 emission is now
  {2'b00, subframe_enable[2:0], stream_flags[2:0]} — was {5'b00000, stream}.
- radar_protocol.py: Opcode.SUBFRAME_ENABLE = 0x19; RadarFrame.subframe_enable
  field; parse_bulk_frame surfaces bits[5:3]; reserved-mask 0xF8 → 0xC0.
  Bulk-frame mock encodes the mask in its emit so dashboard replay is correct.
- v7/processing.py: extract_targets_from_frame_crt forces every target to
  AMBIGUOUS when frame.subframe_enable != 0b111. Operator sees the red `?`
  flag in the targets table instead of a silently-wrong velocity.
- v7/software_fpga.py + v7/dashboard.py: subframe_enable mirror + setter, and
  replay dispatch routes 0x19 to set_subframe_enable.

Tests (test_v7.py): TestSubframeEnableRoundTrip (4), TestSoftwareFpgaSubframeEnable
(2), TestCrtSubframeMaskGating (3), 0x19 added to TestOpcodeEnumFillIn and
TestReplayOpcodeDispatch. Existing test_full_frame_round_trip updated to expect
byte 2 = 0x3F (mask 0b111 default + stream 0x07).

Cosim TBs (tb/tb_usb_protocol_v2.v, tb/tb_ft2232h_frame_drop.v) drive the new
input with 3'b111 and assert the new byte-2 layout (T2.3: 0x00 → 0x38).

Regression: test_v7 146/146, test_GUI_V65_Tk 117/117, ruff clean.
iverilog: tb_usb_protocol_v2 27/27 PASS, tb_ft2232h_frame_drop 10/10 PASS.
This commit is contained in:
Jason
2026-05-02 17:49:16 +05:45
parent 8ebb7016de
commit ef32345b26
10 changed files with 283 additions and 27 deletions
@@ -68,6 +68,8 @@ module tb_ft2232h_frame_drop;
// PR-G: stream bits [2:0] all off WR FSM: HDR FOOTER DONE
// = fast deterministic drain. Bits [5:3] are reserved=0 in v2.
reg [5:0] stream_control = 6'b000_000;
// PR-U / M-8: production 3-PRI ladder.
reg [2:0] subframe_enable = 3'b111;
// Status inputs (irrelevant for this test)
reg status_request = 1'b0;
@@ -124,6 +126,8 @@ module tb_ft2232h_frame_drop;
.cmd_addr(cmd_addr),
.cmd_value(cmd_value),
.stream_control(stream_control),
// PR-U / M-8: per-frame snapshot of host_subframe_enable.
.subframe_enable(subframe_enable),
.status_request(status_request),
.status_cfar_threshold(status_cfar_threshold),
.status_stream_ctrl(status_stream_ctrl),
+9 -1
View File
@@ -66,6 +66,8 @@ module tb_usb_protocol_v2;
// PR-G v2: enable all 3 streams (range|doppler|cfar). Bits [5:3] reserved=0.
reg [5:0] stream_control = 6'b000_111;
reg [5:0] status_stream_ctrl = 6'b000_111;
// PR-U / M-8: production 3-PRI ladder (SHORT|MEDIUM|LONG).
reg [2:0] subframe_enable = 3'b111;
// Status inputs (mostly tied off; PR-G additions below)
reg status_request = 1'b0;
@@ -127,6 +129,9 @@ module tb_usb_protocol_v2;
.cmd_addr(cmd_addr),
.cmd_value(cmd_value),
.stream_control(stream_control),
// PR-U / M-8: per-frame snapshot of host_subframe_enable echoed in
// v2 frame byte 2 bits[5:3].
.subframe_enable(subframe_enable),
.status_request(status_request),
.status_cfar_threshold(status_cfar_threshold),
.status_stream_ctrl(status_stream_ctrl),
@@ -251,7 +256,10 @@ module tb_usb_protocol_v2;
wait_clk(150);
check_b("T2.1: byte0 = 0xAA", egress_bytes[0] == 8'hAA);
check_b("T2.2: byte1 = 0x02 (ver)", egress_bytes[1] == `RP_USB_PROTOCOL_VERSION);
check_b("T2.3: byte2 = stream flags=0", egress_bytes[2] == 8'h00);
// PR-U / M-8: byte 2 = {2'b00, subframe_enable[2:0], stream[2:0]}.
// subframe_enable defaults to 3'b111 → byte 2 = (0b111 << 3) | 0 = 0x38.
check_b("T2.3: byte2 = {00, sf=111, stream=0} = 0x38",
egress_bytes[2] == 8'h38);
// Byte 3-4 = frame_number snapshot. snapshot latches OLD frame_number
// at frame_complete (NBA), so first frame emitted carries fn=0.
check_b("T2.4: byte3 = fn[15:8]=0", egress_bytes[3] == 8'h00);