mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-10 23:41:18 +00:00
fix(usb): C-9 — GUI bulk-frame parser for FT2232H + clamp inert flag bits
The GUI's radar_protocol.py parsed 11-byte legacy packets only. The
production board (50T, USB_MODE=1) emits ~35 KB bulk frames from
usb_data_interface_ft2232h.v, so the legacy parser saw a random walk
of false 11-byte boundaries through bulk data — no usable display on
production hardware.
Bulk parser added (radar_protocol.py):
- parse_bulk_frame validates header, reserved bits, n_range=512,
n_doppler=32, footer-at-flag-derived-offset; unpacks range_profile
/ doppler_mag / cfar_dense per the format-flags byte.
- find_bulk_frame_boundaries is the bulk counterpart of
find_packet_boundaries; status packets (0xBB) handled in the same
stream since FT2232H emits them too.
- RadarAcquisition dispatches on isinstance(conn, FT2232HConnection):
bulk path skips the per-sample state machine and fills RadarFrame
in one shot. FT601 / 200T keeps legacy 11-byte (USB 3.0 has 50x
bandwidth headroom; per-sample format is correct and already works).
- RadarFrame.mag_only flag carries the wire's mag_only bit so
downstream consumers can skip I/Q panels cleanly.
- FT2232HConnection._mock_read now emits synthetic bulk frames
(was misleading legacy 11-byte).
RTL alignment (AUDIT-C9 RTL stub option):
- usb_data_interface_ft2232h.v header no longer promises the
unimplemented mag_only=0 (full-I/Q) and sparse_det=1 paths;
explicit INERT FLAGS note distinguishes the two reasons:
* Full-I/Q is constrained by hardware — needs ~28-BRAM18 I/Q
buffer (50T currently 78% BRAM utilised after FFT IP) AND
USB 2.0 bandwidth (12.21 MB/s vs 8 MB/s conservative budget).
* Sparse-list is feasible — smaller than dense for typical
scenes (<341 detections), ~1 BRAM18 cost. Just unimplemented
RTL work (small list BRAM + new WR_DETECT_SPARSE state).
- New SIMULATION-only assertion fires if stream_mag_only ever
becomes 0 or stream_sparse_det ever becomes 1 — backstop for
any future regression that bypasses the host-register clamp.
- radar_system_top.v opcode 0x04 force-clamps mag_only=1 and
sparse_det=0 in host_stream_control when USB_MODE=1, so a
Custom-Command host write can't push the FPGA into a wire-format
vs FSM divergence.
Bandwidth math (verified for 27c9c22+):
Frame rate = 1 / (16x167 us + 175.4 us + 16x175 us) = ~178 fps
Mag-only frame = 8+1024+32768+2048+1 = 35849 B = 6.38 MB/s
FT2232H 245-Sync-FIFO sustained budget (FTDI AN_232B-04
conservative): 8 MB/s. Headroom 20%.
Tests: test_GUI_V65_Tk.py TestBulkFrameParser — 18 new cases covering
round-trip per stream-flag combo, header/footer/n_range/n_doppler/
reserved-bit/truncation rejection, multi-frame boundaries, bulk+status
mixed streams, byte-drop resync, dispatch-by-connection-type,
ingest-to-RadarFrame end-to-end. GUI 117/117 PASS, v7 83/83 PASS,
FPGA quick regression 29/29 PASS, ruff clean.
Refs: AUDIT-C9 (GUI parses legacy 11-byte vs FT2232H bulk).
Follow-ups (separate patches):
- Sparse-detection write FSM (~1 BRAM18 + ~100 RTL lines).
Bandwidth- and memory-feasible; just unimplemented work.
- Full-I/Q write FSM. Constrained: needs ~28-BRAM18 I/Q buffer
AND USB 2.0 bandwidth headroom (50T post-FFT-IP at 78% BRAM).
This commit is contained in:
@@ -1018,7 +1018,24 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
8'h01: host_radar_mode <= usb_cmd_value[1:0];
|
||||
8'h02: host_trigger_pulse <= 1'b1;
|
||||
8'h03: host_detect_threshold <= usb_cmd_value;
|
||||
8'h04: host_stream_control <= usb_cmd_value[5:0];
|
||||
// AUDIT-C9: stream_control bits [3] (mag_only) and [4]
|
||||
// (sparse_det) are documented in the FT2232H bulk-frame
|
||||
// header but the write FSM does not implement the alternate
|
||||
// encodings yet (see usb_data_interface_ft2232h.v "INERT
|
||||
// FLAGS" note). Force-clamp them to the only encodings the
|
||||
// FSM actually emits so a host write of 0x04 cannot create
|
||||
// a wire-format vs FSM divergence on the production board.
|
||||
8'h04: begin
|
||||
if (USB_MODE == 1) begin
|
||||
// FT2232H production: mag_only stuck at 1, sparse_det stuck at 0.
|
||||
host_stream_control <= {usb_cmd_value[5],
|
||||
1'b0, // sparse_det
|
||||
1'b1, // mag_only
|
||||
usb_cmd_value[2:0]}; // stream r/d/c
|
||||
end else begin
|
||||
host_stream_control <= usb_cmd_value[5:0];
|
||||
end
|
||||
end
|
||||
// Gap 2: chirp timing configuration
|
||||
8'h10: host_long_chirp_cycles <= usb_cmd_value;
|
||||
8'h11: host_long_listen_cycles <= usb_cmd_value;
|
||||
|
||||
@@ -20,21 +20,42 @@
|
||||
* [If stream_range (bit 0):]
|
||||
* Next 1024 bytes: Range profile, 512 × 16-bit magnitude, MSB first
|
||||
*
|
||||
* [If stream_doppler (bit 1) AND mag_only (bit 3):]
|
||||
* [If stream_doppler (bit 1):]
|
||||
* Next 32768 bytes: Doppler magnitude, 512×32 × 16-bit, row-major, MSB first
|
||||
*
|
||||
* [If stream_doppler (bit 1) AND NOT mag_only:]
|
||||
* Next 65536 bytes: Doppler I/Q, 512×32 × 32-bit (I16,Q16), row-major, MSB first
|
||||
*
|
||||
* [If stream_cfar (bit 2) AND NOT sparse_det (bit 4):]
|
||||
* [If stream_cfar (bit 2):]
|
||||
* Next 2048 bytes: Detection flags, 512×32 bits packed into bytes, MSB-first bit order
|
||||
*
|
||||
* [If stream_cfar (bit 2) AND sparse_det (bit 4):]
|
||||
* Next 2 bytes: Detection count N (16-bit, MSB first)
|
||||
* Next N×6 bytes: Each = {range_bin[16], doppler_bin[16], magnitude[16]}, MSB first
|
||||
*
|
||||
* Last byte: 0x55 (frame end footer)
|
||||
*
|
||||
* INERT FLAGS — mag_only (bit 3) and sparse_det (bit 4) (AUDIT-C9):
|
||||
* The wire format byte 1 reserves these two bits for future encodings:
|
||||
* - mag_only=0 was meant to switch the doppler section to 65536 B
|
||||
* full-I/Q (16-bit I + 16-bit Q per cell, row-major, MSB first).
|
||||
* - sparse_det=1 was meant to switch the CFAR section to a
|
||||
* variable-length list: 2 B count N + N×6 B (range, doppler, mag).
|
||||
* Neither encoding is implemented in the write FSM below — the FSM
|
||||
* always emits 32768 B mag and 2048 B dense bitmap regardless of the
|
||||
* flag bits. To eliminate the foot-gun, `radar_system_top.v` opcode
|
||||
* 0x04 force-clamps mag_only=1 and sparse_det=0 in `host_stream_control`
|
||||
* when USB_MODE=1. A SIMULATION-only assertion at the bottom of this
|
||||
* module fires if either bit ever leaves its clamped value, in case a
|
||||
* future patch adds a path that bypasses the host register clamp.
|
||||
*
|
||||
* Reasons differ between the two:
|
||||
* - Full-I/Q is constrained by FPGA resources: it needs a new
|
||||
* ~28-BRAM18 I/Q buffer (16384 cells × 32-bit) which may not fit
|
||||
* on the 50T (currently ~78% BRAM18 utilisation after wiring the
|
||||
* Xilinx FFT IP). USB 2.0 bandwidth is also tight: 12.21 MB/s vs
|
||||
* the conservative 8 MB/s sustained budget. Both gating items.
|
||||
* - Sparse-list is feasible — bandwidth-wise it's smaller than the
|
||||
* dense bitmap for any frame with fewer than ~341 detections
|
||||
* (typical scenes produce 10-200), and memory-wise it costs
|
||||
* ~1 BRAM18 with MAX_DETECTIONS=256. The absence is just
|
||||
* unimplemented RTL work (a small detection-list BRAM + a new
|
||||
* WR_DETECT_SPARSE FSM state), not a hardware constraint.
|
||||
* See the open-defects ledger for the follow-up work items.
|
||||
*
|
||||
* Status packet (FPGA→Host): 26 bytes (unchanged from legacy)
|
||||
* Byte 0: 0xBB (status header)
|
||||
* Bytes 1-24: 6 × 32-bit status words, MSB first
|
||||
@@ -54,13 +75,13 @@
|
||||
* Written in clk domain from range_valid events.
|
||||
* - Detection flag buffer: 512×32 = 16384 bits = 2048 bytes (~1 BRAM18)
|
||||
* Written in clk domain from cfar_valid events.
|
||||
* - Doppler I/Q BRAM: 16384 × 32-bit = 64 KB (~28 BRAM18) — only when mag_only=0
|
||||
* NOTE: For 50T (75 BRAM18 total), I/Q mode may not fit alongside processing
|
||||
* chain BRAMs. Default to mag_only=1. I/Q mode is a stretch goal.
|
||||
*
|
||||
* BANDWIDTH BUDGET (mag_only=1, all streams):
|
||||
* BANDWIDTH BUDGET (current production: mag_only=1, all streams):
|
||||
* Header: 8 B + Range: 1024 B + Doppler: 32768 B + CFAR: 2048 B + Footer: 1 B
|
||||
* = 35,849 bytes/frame × 178 fps = 6.38 MB/s (80% of USB 2.0 Hi-Speed 8 MB/s)
|
||||
* = 35,849 bytes/frame × ~178 fps = 6.38 MB/s
|
||||
* FT2232H 245-Sync-FIFO sustained budget ~8 MB/s conservative (FTDI
|
||||
* AN_232B-04). 80% utilisation; full-I/Q (12.21 MB/s) would not fit at
|
||||
* the conservative budget and is why mag_only is force-clamped to 1.
|
||||
*
|
||||
* CDC STRATEGY:
|
||||
* - Frame data: Written to dual-port BRAM at 100 MHz, read at 60 MHz (inherently CDC-safe)
|
||||
@@ -1025,4 +1046,28 @@ always @(posedge ft_clk or negedge ft_reset_n) begin
|
||||
end
|
||||
`endif
|
||||
|
||||
// ============================================================================
|
||||
// AUDIT-C9: inert-flag checker (simulation only)
|
||||
//
|
||||
// stream_mag_only and stream_sparse_det are documented in the wire format
|
||||
// but the write FSM does not act on them — see the "INERT FLAGS" note in
|
||||
// the module header. radar_system_top.v opcode 0x04 force-clamps these
|
||||
// bits when USB_MODE=1 so production firmware cannot reach an unsupported
|
||||
// state. This checker is the backstop: it fires `[ASSERT FAIL]` if either
|
||||
// bit ever escapes its clamped value, catching any future patch that
|
||||
// bypasses the host register clamp (e.g. a different opcode that writes
|
||||
// stream_control directly, or a stream_control source other than the
|
||||
// host). Synthesis-inert.
|
||||
// ============================================================================
|
||||
`ifdef SIMULATION
|
||||
always @(posedge clk) begin
|
||||
if (reset_n) begin
|
||||
if (stream_mag_only !== 1'b1)
|
||||
$display("[ASSERT FAIL] AUDIT-C9: stream_mag_only=0; full-I/Q write FSM not implemented");
|
||||
if (stream_sparse_det !== 1'b0)
|
||||
$display("[ASSERT FAIL] AUDIT-C9: stream_sparse_det=1; sparse-list write FSM not implemented");
|
||||
end
|
||||
end
|
||||
`endif
|
||||
|
||||
endmodule
|
||||
|
||||
Reference in New Issue
Block a user