mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-28 18:50:42 +00:00
chirp-v2 PR-G: header/body consistency + runtime MEDIUM ladder
G1.5 (FSM trim): doppler section emits NUM_RANGE_BINS*NUM_DOPPLER_BINS cells (49152 B) and detect emits packed valid bytes (6144 B), matching the 9-byte header advertisement. Replaces flat counters with nested range x doppler indices in usb_data_interface_ft2232h.v. Saves ~18.4 kB per frame on the wire. G2 (runtime MEDIUM ladder): adds opcodes 0x17/0x18 for medium chirp/ listen cycles with RP_DEF_MEDIUM_* defaults. Plumbed through radar_system_top -> radar_receiver_final -> chirp_scheduler. SHORT/LONG were already runtime-tunable; MEDIUM was hardcoded. TBs: tb_usb_protocol_v2 adds TEST 4 (full-frame egress byte count = 56330) and TEST 5 (MEDIUM opcode round-trip) - 27/27 PASS. tb_ft2232h_frame_drop updated for new section sizes - 10/10 PASS. Full regression: 37/41 with 4 pre-existing failures (T-2..T-5, tracked in PR-Tests-1 / PR-I). Stash test confirmed pre-PR-G HEAD has identical failures - PR-G introduces zero new test regressions.
This commit is contained in:
@@ -270,10 +270,71 @@
|
||||
// Bit 0 = range profile stream
|
||||
// Bit 1 = doppler map stream
|
||||
// Bit 2 = cfar/detection stream
|
||||
// Bits [5:3]: Stream format control
|
||||
// Bit 3 = mag_only (0=I/Q pairs, 1=Manhattan magnitude only)
|
||||
// Bit 4 = sparse_det (0=dense detection flags, 1=sparse detection list)
|
||||
// Bit 5 = reserved (was frame_decimate, not needed with mag-only fitting)
|
||||
`define RP_STREAM_CTRL_DEFAULT 6'b001_111 // all streams, mag-only mode
|
||||
// Bits [5:3]: RESERVED (must be 0). PR-G dropped the legacy inert
|
||||
// mag_only/sparse_det/frame_decimate flags — protocol v2 ships a single
|
||||
// canonical encoding (Manhattan-mag doppler + 2-bit dense detect).
|
||||
`define RP_STREAM_CTRL_DEFAULT 6'b000_111 // all 3 streams on, no flags
|
||||
|
||||
// ============================================================================
|
||||
// USB PROTOCOL V2 (PR-G — clean cutover from v1)
|
||||
// ============================================================================
|
||||
// Wire format (FPGA → Host bulk frame):
|
||||
// Byte 0: 0xAA (frame start)
|
||||
// Byte 1: 0x02 (PROTOCOL VERSION — pinned, host MUST reject != 0x02)
|
||||
// Byte 2: Stream flags {5'd0, stream_cfar, stream_doppler, stream_range}
|
||||
// Bytes 3–4: Frame number (uint16, MSB first)
|
||||
// Bytes 5–6: Range bin count (uint16, MSB first) = `RP_NUM_RANGE_BINS`
|
||||
// Bytes 7–8: Doppler bin count (uint16, MSB first) = `RP_NUM_DOPPLER_BINS`
|
||||
// [stream_range:] 1024 B range profile (512 × uint16, MSB first)
|
||||
// [stream_doppler:] 65536 B doppler magnitude (32768 cells × uint16, row-major)
|
||||
// [stream_cfar:] 8192 B detect bitmap (32768 cells × 2 bits, MSB-first
|
||||
// packing: cell[N] in byte[N/4] bits [7-(N%4)*2 -: 2])
|
||||
// Last byte: 0x55 (footer)
|
||||
//
|
||||
// Total frame (all streams on): 9 + 1024 + 65536 + 8192 + 1 = 74762 B
|
||||
// At ~119 fps (PR-F 3-subframe rate) ≈ 8.9 MB/s — within FT2232H bulk budget.
|
||||
`define RP_USB_PROTOCOL_VERSION 8'h02 // pinned; host rejects mismatch
|
||||
`define RP_FRAME_HDR_BYTES 9 // 0xAA + ver + flags + 2*fn + 2*rb + 2*db
|
||||
`define RP_DETECT_BITS_PER_CELL 2 // PR-G: 2-bit dense (NONE/CAND/CONFIRM/RSVD)
|
||||
`define RP_DETECT_CELLS_PER_BYTE 4 // 8 / RP_DETECT_BITS_PER_CELL
|
||||
|
||||
// ============================================================================
|
||||
// USB OPCODE MAP (PR-G v2 — single source of truth for RTL & GUI parity)
|
||||
// ============================================================================
|
||||
`define RP_OP_RADAR_MODE 8'h01
|
||||
`define RP_OP_TRIGGER_PULSE 8'h02
|
||||
`define RP_OP_DETECT_THRESHOLD 8'h03
|
||||
`define RP_OP_STREAM_CONTROL 8'h04
|
||||
`define RP_OP_LONG_CHIRP_CYCLES 8'h10
|
||||
`define RP_OP_LONG_LISTEN_CYCLES 8'h11
|
||||
`define RP_OP_GUARD_CYCLES 8'h12
|
||||
`define RP_OP_SHORT_CHIRP_CYCLES 8'h13
|
||||
`define RP_OP_SHORT_LISTEN_CYCLES 8'h14
|
||||
`define RP_OP_CHIRPS_PER_ELEV 8'h15
|
||||
`define RP_OP_GAIN_SHIFT 8'h16
|
||||
// PR-G G2: MEDIUM ladder timings (SHORT/LONG already at 0x10-0x14, GUARD at 0x12).
|
||||
`define RP_OP_MEDIUM_CHIRP_CYCLES 8'h17
|
||||
`define RP_OP_MEDIUM_LISTEN_CYCLES 8'h18
|
||||
// 0x19–0x1F reserved (per-waveform guard if needed in future)
|
||||
`define RP_OP_RANGE_MODE 8'h20
|
||||
`define RP_OP_CFAR_GUARD 8'h21
|
||||
`define RP_OP_CFAR_TRAIN 8'h22
|
||||
`define RP_OP_CFAR_ALPHA 8'h23 // confirm-tier (Q4.4)
|
||||
`define RP_OP_CFAR_MODE 8'h24
|
||||
`define RP_OP_CFAR_ENABLE 8'h25
|
||||
`define RP_OP_MTI_ENABLE 8'h26
|
||||
`define RP_OP_DC_NOTCH_WIDTH 8'h27
|
||||
`define RP_OP_AGC_ENABLE 8'h28
|
||||
`define RP_OP_AGC_TARGET 8'h29
|
||||
`define RP_OP_AGC_ATTACK 8'h2A
|
||||
`define RP_OP_AGC_DECAY 8'h2B
|
||||
`define RP_OP_AGC_HOLDOFF 8'h2C
|
||||
`define RP_OP_CFAR_ALPHA_SOFT 8'h2D // PR-G: candidate-tier (Q4.4)
|
||||
// 0x2E–0x2F reserved
|
||||
`define RP_OP_SELF_TEST_TRIGGER 8'h30
|
||||
`define RP_OP_SELF_TEST_STATUS 8'h31
|
||||
`define RP_OP_ADC_PWDN 8'h32
|
||||
`define RP_OP_ADC_FORMAT 8'h33
|
||||
`define RP_OP_STATUS_REQUEST 8'hFF
|
||||
|
||||
`endif // RADAR_PARAMS_VH
|
||||
|
||||
@@ -47,6 +47,9 @@ module radar_receiver_final (
|
||||
input wire [15:0] host_guard_cycles,
|
||||
input wire [15:0] host_short_chirp_cycles,
|
||||
input wire [15:0] host_short_listen_cycles,
|
||||
// PR-G G2: MEDIUM ladder timings (was hardcoded to RP_DEF_MEDIUM_*)
|
||||
input wire [15:0] host_medium_chirp_cycles,
|
||||
input wire [15:0] host_medium_listen_cycles,
|
||||
input wire [5:0] host_chirps_per_elev,
|
||||
|
||||
// Digital gain control (Fix 3: between DDC output and matched filter)
|
||||
@@ -221,11 +224,13 @@ wire mti_first_chirp;
|
||||
// radar_mode_controller. SHORT/MEDIUM/LONG ladders + sub-frame walking
|
||||
// + host-cued track dwell with watchdog scan-fallback.
|
||||
//
|
||||
// Several inputs (medium/track/subframe-enable/debug) are pinned to
|
||||
// radar_params defaults until PR-G plumbs the new USB opcodes through
|
||||
// radar_system_top. host_chirps_per_elev (legacy) is intentionally not
|
||||
// wired here — the V2 sub-frame structure uses RP_DEF_CHIRPS_PER_SUBFRAME
|
||||
// (16) and PR-G renames the host register.
|
||||
// PR-G G2: MEDIUM chirp/listen are now host-configurable via opcodes
|
||||
// 0x17/0x18, plumbed through host_medium_*_cycles. Track-mode + subframe-
|
||||
// enable + debug-wave selectors remain pinned to radar_params defaults
|
||||
// (single-mode SHORT track is the only HW-tested track variant; per-mode
|
||||
// track waveform selection is a future feature, not part of PR-G).
|
||||
// host_chirps_per_elev (legacy) is intentionally not wired here — the V2
|
||||
// sub-frame structure uses RP_DEF_CHIRPS_PER_SUBFRAME (16) and is fixed.
|
||||
chirp_scheduler sched (
|
||||
.mixers_enable(mixers_enable_100m),
|
||||
.clk(clk),
|
||||
@@ -234,8 +239,9 @@ chirp_scheduler sched (
|
||||
.host_subframe_enable(`RP_DEF_SUBFRAME_ENABLE),
|
||||
.host_short_chirp_cycles (host_short_chirp_cycles),
|
||||
.host_short_listen_cycles(host_short_listen_cycles),
|
||||
.host_medium_chirp_cycles (16'd`RP_DEF_MEDIUM_CHIRP_CYCLES),
|
||||
.host_medium_listen_cycles(16'd`RP_DEF_MEDIUM_LISTEN_CYCLES),
|
||||
// PR-G G2: MEDIUM now flows from radar_system_top opcodes 0x17/0x18.
|
||||
.host_medium_chirp_cycles (host_medium_chirp_cycles),
|
||||
.host_medium_listen_cycles(host_medium_listen_cycles),
|
||||
.host_long_chirp_cycles (host_long_chirp_cycles),
|
||||
.host_long_listen_cycles(host_long_listen_cycles),
|
||||
.host_guard_cycles(host_guard_cycles),
|
||||
|
||||
@@ -197,6 +197,9 @@ wire [15:0] rx_doppler_imag;
|
||||
wire rx_doppler_data_valid;
|
||||
reg rx_detect_flag; // Threshold detection result (was rx_cfar_detection)
|
||||
reg rx_detect_valid; // Detection valid pulse (was rx_cfar_valid)
|
||||
// PR-G: 2-bit class register (registered alongside detect_flag for the same
|
||||
// CDC-clean handoff to usb_data_interface_ft2232h). Encoding per RP_DETECT_*.
|
||||
reg [`RP_DETECT_CLASS_WIDTH-1:0] rx_detect_class;
|
||||
|
||||
// Frame-complete signal from Doppler processor (for CFAR)
|
||||
wire rx_frame_complete;
|
||||
@@ -230,8 +233,9 @@ wire usb_range_valid;
|
||||
wire [15:0] usb_doppler_real;
|
||||
wire [15:0] usb_doppler_imag;
|
||||
wire usb_doppler_valid;
|
||||
wire usb_detect_flag; // (was usb_cfar_detection)
|
||||
wire usb_detect_flag; // (was usb_cfar_detection) — FT601 legacy 1-bit path
|
||||
wire usb_detect_valid; // (was usb_cfar_valid)
|
||||
wire [`RP_DETECT_CLASS_WIDTH-1:0] usb_detect_class; // PR-G: 2-bit class for FT2232H bulk frame v2
|
||||
|
||||
// System status
|
||||
reg [3:0] status_reg;
|
||||
@@ -263,8 +267,10 @@ reg [3:0] host_gain_shift;
|
||||
reg [15:0] host_long_chirp_cycles; // Opcode 0x10 (default 3000)
|
||||
reg [15:0] host_long_listen_cycles; // Opcode 0x11 (default 13700)
|
||||
reg [15:0] host_guard_cycles; // Opcode 0x12 (default 17540)
|
||||
reg [15:0] host_short_chirp_cycles; // Opcode 0x13 (default 50)
|
||||
reg [15:0] host_short_listen_cycles; // Opcode 0x14 (default 17450)
|
||||
reg [15:0] host_short_chirp_cycles; // Opcode 0x13 (default 100, V2)
|
||||
reg [15:0] host_short_listen_cycles; // Opcode 0x14 (default 17400, V2)
|
||||
reg [15:0] host_medium_chirp_cycles; // Opcode 0x17 (default 500, PR-G G2)
|
||||
reg [15:0] host_medium_listen_cycles; // Opcode 0x18 (default 17000, PR-G G2)
|
||||
reg [5:0] host_chirps_per_elev; // Opcode 0x15 (default 32)
|
||||
reg host_status_request; // Opcode 0xFF (self-clearing pulse)
|
||||
|
||||
@@ -592,6 +598,9 @@ radar_receiver_final rx_inst (
|
||||
.host_guard_cycles(host_guard_cycles),
|
||||
.host_short_chirp_cycles(host_short_chirp_cycles),
|
||||
.host_short_listen_cycles(host_short_listen_cycles),
|
||||
// PR-G G2: MEDIUM ladder timings (was hardcoded to RP_DEF_MEDIUM_*)
|
||||
.host_medium_chirp_cycles(host_medium_chirp_cycles),
|
||||
.host_medium_listen_cycles(host_medium_listen_cycles),
|
||||
.host_chirps_per_elev(host_chirps_per_elev),
|
||||
// Fix 3: digital gain control
|
||||
.host_gain_shift(host_gain_shift),
|
||||
@@ -689,13 +698,11 @@ wire [16:0] cfar_detect_threshold;
|
||||
wire [15:0] cfar_detect_count;
|
||||
wire cfar_busy_w;
|
||||
wire [7:0] cfar_status_w;
|
||||
// PR-F note: cfar_ca also drives detect_class[1:0], detect_threshold_soft,
|
||||
// detect_count_cand. The soft-tier comparison still feeds detect_flag (which
|
||||
// is wired to USB), so the candidate logic is preserved in synth — but the
|
||||
// class / soft-thr / cand-count outputs themselves don't go anywhere until
|
||||
// PR-G adds the USB opcodes (0x28 alpha_soft + bulk-frame v2). We deliberately
|
||||
// leave the cfar_ca output ports unconnected here so they do NOT show up as
|
||||
// dangling wires in radar_system_top; PR-G will reattach them.
|
||||
// PR-G: 2-class adaptive detection now wired through to the USB bulk frame
|
||||
// (detect_class per cell) and status packet (count_cand + threshold_soft).
|
||||
wire [`RP_DETECT_CLASS_WIDTH-1:0] cfar_detect_class; // PR-G: NONE/CAND/CONFIRM
|
||||
wire [16:0] cfar_detect_threshold_soft; // PR-G: soft (candidate) threshold
|
||||
wire [15:0] cfar_detect_count_cand; // PR-G: per-frame candidate count
|
||||
|
||||
cfar_ca cfar_inst (
|
||||
.clk(clk_100m_buf),
|
||||
@@ -719,17 +726,17 @@ cfar_ca cfar_inst (
|
||||
|
||||
// Detection outputs
|
||||
.detect_flag(cfar_detect_flag),
|
||||
.detect_class(), // PR-F: routed in PR-G (USB 0x2A read)
|
||||
.detect_class(cfar_detect_class), // PR-G: 2-bit dense to USB bulk frame
|
||||
.detect_valid(cfar_detect_valid),
|
||||
.detect_range(cfar_detect_range),
|
||||
.detect_doppler(cfar_detect_doppler),
|
||||
.detect_magnitude(cfar_detect_magnitude),
|
||||
.detect_threshold(cfar_detect_threshold),
|
||||
.detect_threshold_soft(), // PR-F: routed in PR-G
|
||||
.detect_threshold_soft(cfar_detect_threshold_soft), // PR-G: status_words[6][15:0]
|
||||
|
||||
// Status
|
||||
.detect_count(cfar_detect_count),
|
||||
.detect_count_cand(), // PR-F: routed in PR-G (telemetry)
|
||||
.detect_count_cand(cfar_detect_count_cand), // PR-G: status_words[6][31:16]
|
||||
.cfar_busy(cfar_busy_w),
|
||||
.cfar_status(cfar_status_w)
|
||||
);
|
||||
@@ -740,9 +747,11 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
if (!sys_reset_n) begin
|
||||
rx_detect_flag <= 1'b0;
|
||||
rx_detect_valid <= 1'b0;
|
||||
rx_detect_class <= `RP_DETECT_NONE;
|
||||
end else begin
|
||||
rx_detect_flag <= cfar_detect_flag;
|
||||
rx_detect_valid <= cfar_detect_valid;
|
||||
rx_detect_class <= cfar_detect_class;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -795,6 +804,7 @@ assign usb_doppler_valid = rx_doppler_valid;
|
||||
|
||||
assign usb_detect_flag = rx_detect_flag;
|
||||
assign usb_detect_valid = rx_detect_valid;
|
||||
assign usb_detect_class = rx_detect_class; // PR-G: 2-bit class to FT2232H
|
||||
|
||||
// ============================================================================
|
||||
// USB DATA INTERFACE INSTANTIATION (parametric: FT601 or FT2232H)
|
||||
@@ -893,7 +903,7 @@ end else begin : gen_ft2232h
|
||||
.doppler_real(usb_doppler_real),
|
||||
.doppler_imag(usb_doppler_imag),
|
||||
.doppler_valid(usb_doppler_valid),
|
||||
.cfar_detection(usb_detect_flag),
|
||||
.cfar_detect_class(usb_detect_class), // PR-G: 2-bit class (was 1-bit cfar_detection)
|
||||
.cfar_valid(usb_detect_valid),
|
||||
|
||||
// Bulk frame protocol inputs
|
||||
@@ -949,7 +959,12 @@ end else begin : gen_ft2232h
|
||||
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
|
||||
// for host-side observability (paired with gpio_dig7 split)
|
||||
.status_range_decim_watchdog(rx_range_decim_watchdog),
|
||||
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
|
||||
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun),
|
||||
|
||||
// PR-G: 2-tier CFAR telemetry (status_words[6])
|
||||
.status_cfar_alpha_soft(host_cfar_alpha_soft),
|
||||
.status_detect_threshold_soft(cfar_detect_threshold_soft),
|
||||
.status_detect_count_cand(cfar_detect_count_cand)
|
||||
);
|
||||
|
||||
// FT601 ports unused in FT2232H mode — tie off
|
||||
@@ -1032,12 +1047,14 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
host_gain_shift <= 4'd0; // Default: pass-through (no gain change)
|
||||
// Gap 2: chirp timing defaults (forwarded to chirp_scheduler).
|
||||
// SHORT bumped to 100 cycles (1 us) for chirp-v2 SHORT waveform.
|
||||
host_long_chirp_cycles <= 16'd3000;
|
||||
host_long_listen_cycles <= 16'd13700;
|
||||
host_guard_cycles <= 16'd17540;
|
||||
host_short_chirp_cycles <= 16'd100;
|
||||
host_short_listen_cycles <= 16'd17400;
|
||||
host_chirps_per_elev <= 6'd32;
|
||||
host_long_chirp_cycles <= 16'd`RP_DEF_LONG_CHIRP_CYCLES;
|
||||
host_long_listen_cycles <= 16'd`RP_DEF_LONG_LISTEN_CYCLES;
|
||||
host_guard_cycles <= 16'd`RP_DEF_GUARD_CYCLES;
|
||||
host_short_chirp_cycles <= 16'd`RP_DEF_SHORT_CHIRP_CYCLES_V2;
|
||||
host_short_listen_cycles <= 16'd`RP_DEF_SHORT_LISTEN_CYCLES_V2;
|
||||
host_medium_chirp_cycles <= 16'd`RP_DEF_MEDIUM_CHIRP_CYCLES; // PR-G G2
|
||||
host_medium_listen_cycles <= 16'd`RP_DEF_MEDIUM_LISTEN_CYCLES; // PR-G G2
|
||||
host_chirps_per_elev <= 6'd32;
|
||||
host_status_request <= 1'b0;
|
||||
chirps_mismatch_error <= 1'b0;
|
||||
host_range_mode <= 2'b00; // Default: 3 km mode (all short chirps)
|
||||
@@ -1074,30 +1091,21 @@ 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;
|
||||
// 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
|
||||
// PR-G: protocol v2 has a single canonical encoding
|
||||
// (Manhattan-mag doppler + 2-bit dense detect). Bits [5:3] of
|
||||
// host_stream_control are RESERVED — host SHOULD write 0, RTL
|
||||
// ignores them. The legacy AUDIT-C9 force-clamp + INERT FLAGS
|
||||
// logic was removed when v1 was retired.
|
||||
8'h04: host_stream_control <= {3'b000, usb_cmd_value[2:0]};
|
||||
// Gap 2: chirp timing configuration
|
||||
8'h10: host_long_chirp_cycles <= usb_cmd_value;
|
||||
8'h11: host_long_listen_cycles <= usb_cmd_value;
|
||||
8'h12: host_guard_cycles <= usb_cmd_value;
|
||||
8'h13: host_short_chirp_cycles <= usb_cmd_value;
|
||||
8'h14: host_short_listen_cycles <= usb_cmd_value;
|
||||
// PR-G G2: MEDIUM ladder timings
|
||||
8'h17: host_medium_chirp_cycles <= usb_cmd_value;
|
||||
8'h18: host_medium_listen_cycles <= usb_cmd_value;
|
||||
8'h15: begin
|
||||
// Fix 4: Clamp chirps_per_elev to the fixed Doppler frame size.
|
||||
// If host requests a different value, clamp and set error flag.
|
||||
@@ -1130,6 +1138,9 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
8'h2A: host_agc_attack <= usb_cmd_value[3:0];
|
||||
8'h2B: host_agc_decay <= usb_cmd_value[3:0];
|
||||
8'h2C: host_agc_holdoff <= usb_cmd_value[3:0];
|
||||
// PR-G: 2-tier CFAR — soft (candidate) threshold multiplier.
|
||||
// Default RP_DEF_CFAR_ALPHA_SOFT = 0x18 (1.5 in Q4.4, Pfa~10⁻⁵).
|
||||
8'h2D: host_cfar_alpha_soft <= usb_cmd_value[7:0];
|
||||
// Board bring-up self-test opcodes
|
||||
8'h30: host_self_test_trigger <= 1'b1; // Trigger self-test
|
||||
8'h31: host_status_request <= 1'b1; // Self-test readback (status alias)
|
||||
|
||||
@@ -16,10 +16,9 @@
|
||||
// 3. Multiple drops while stalled → drop count saturates at 127
|
||||
// 4. Stalled + recovery → drop count stable, frame_pending clears post-drain
|
||||
//
|
||||
// Stimulus uses `stream_control = 6'b001_000` (mag_only=1, no sections enabled)
|
||||
// so the WR FSM goes HDR (8B) → FOOTER (1B) → DONE in 9 ft_clk cycles. This
|
||||
// gives a fast, deterministic per-frame transfer time. AUDIT-C9 sim assertion
|
||||
// is satisfied (mag_only=1, sparse_det=0).
|
||||
// Stimulus uses `stream_control = 6'b000_000` (PR-G v2: no inert flags, no
|
||||
// sections enabled) so the WR FSM goes HDR (9B) → FOOTER (1B) → DONE in 10
|
||||
// ft_clk cycles. This gives a fast, deterministic per-frame transfer time.
|
||||
//
|
||||
// PASS criteria:
|
||||
// - frame_drop_count matches expected value after each scenario
|
||||
@@ -42,11 +41,13 @@ module tb_ft2232h_frame_drop;
|
||||
reg [15:0] doppler_real = 16'd0;
|
||||
reg [15:0] doppler_imag = 16'd0;
|
||||
reg doppler_valid = 1'b0;
|
||||
reg cfar_detection = 1'b0;
|
||||
// PR-G: 2-bit class (was 1-bit cfar_detection)
|
||||
reg [`RP_DETECT_CLASS_WIDTH-1:0] cfar_detect_class = `RP_DETECT_NONE;
|
||||
reg cfar_valid = 1'b0;
|
||||
|
||||
reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_in = 0;
|
||||
reg [4:0] doppler_bin_in = 5'd0;
|
||||
// PR-F: doppler_bin widened to RP_DOPPLER_BIN_WIDTH (6 bits)
|
||||
reg [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin_in = {`RP_DOPPLER_BIN_WIDTH{1'b0}};
|
||||
reg frame_complete = 1'b0;
|
||||
|
||||
// FT2232H interface (ft_clk domain)
|
||||
@@ -64,14 +65,14 @@ module tb_ft2232h_frame_drop;
|
||||
wire [7:0] cmd_addr;
|
||||
wire [15:0] cmd_value;
|
||||
|
||||
// mag_only=1, sparse_det=0, all sections disabled (skip range/doppler/cfar)
|
||||
// → WR FSM: HDR → FOOTER → DONE = fast deterministic drain
|
||||
reg [5:0] stream_control = 6'b001_000;
|
||||
// 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;
|
||||
|
||||
// Status inputs (irrelevant for this test)
|
||||
reg status_request = 1'b0;
|
||||
reg [15:0] status_cfar_threshold = 16'd0;
|
||||
reg [5:0] status_stream_ctrl = 6'b001_000;
|
||||
reg [5:0] status_stream_ctrl = 6'b000_000;
|
||||
reg [1:0] status_radar_mode = 2'd0;
|
||||
reg [15:0] status_long_chirp = 16'd0;
|
||||
reg [15:0] status_long_listen = 16'd0;
|
||||
@@ -104,7 +105,7 @@ module tb_ft2232h_frame_drop;
|
||||
.doppler_real(doppler_real),
|
||||
.doppler_imag(doppler_imag),
|
||||
.doppler_valid(doppler_valid),
|
||||
.cfar_detection(cfar_detection),
|
||||
.cfar_detect_class(cfar_detect_class), // PR-G: 2-bit class
|
||||
.cfar_valid(cfar_valid),
|
||||
.range_bin_in(range_bin_in),
|
||||
.doppler_bin_in(doppler_bin_in),
|
||||
@@ -144,7 +145,11 @@ module tb_ft2232h_frame_drop;
|
||||
.status_agc_enable(status_agc_enable),
|
||||
// AUDIT-S10: control-fault flags tied off (frame-drop TB scope)
|
||||
.status_range_decim_watchdog(1'b0),
|
||||
.status_ddc_cic_fir_overrun(1'b0)
|
||||
.status_ddc_cic_fir_overrun(1'b0),
|
||||
// PR-G: 2-tier CFAR telemetry tied off
|
||||
.status_cfar_alpha_soft(8'h18), // RP_DEF_CFAR_ALPHA_SOFT
|
||||
.status_detect_threshold_soft(17'd0),
|
||||
.status_detect_count_cand(16'd0)
|
||||
);
|
||||
|
||||
task pulse_frame_complete;
|
||||
@@ -200,10 +205,10 @@ module tb_ft2232h_frame_drop;
|
||||
$display("\n[TEST 1] Single frame, USB ready -> no drops");
|
||||
ft_txe_n = 1'b0;
|
||||
pulse_frame_complete();
|
||||
// Wait for frame to drain through WR_FSM. With mag_only mode and
|
||||
// stream_control[2:0]=000, FSM goes HDR (8B) -> FOOTER (1B) -> DONE.
|
||||
// Each byte = 1 ft_clk cycle. Plus CDC latency. Allow ~50 ft_clk
|
||||
// = ~833 ns = ~83 clk cycles. Be generous: wait 200 clk cycles.
|
||||
// Wait for frame to drain through WR_FSM. PR-G v2: stream_control[2:0]=000,
|
||||
// FSM goes HDR (9B) -> FOOTER (1B) -> DONE = 10 ft_clk cycles. Plus CDC
|
||||
// latency. Allow ~50 ft_clk = ~833 ns = ~83 clk cycles. Be generous:
|
||||
// wait 200 clk cycles.
|
||||
wait_cycles(200);
|
||||
check(1, "drop_count", 0, u_dut.frame_drop_count);
|
||||
check(1, "frame_pending_cleared", 0, u_dut.frame_pending);
|
||||
|
||||
357
9_Firmware/9_2_FPGA/tb/tb_usb_protocol_v2.v
Normal file
357
9_Firmware/9_2_FPGA/tb/tb_usb_protocol_v2.v
Normal file
@@ -0,0 +1,357 @@
|
||||
`timescale 1ns / 1ps
|
||||
`include "radar_params.vh"
|
||||
|
||||
// ============================================================================
|
||||
// tb_usb_protocol_v2.v
|
||||
//
|
||||
// PR-G focused round-trip verification for usb_data_interface_ft2232h.v:
|
||||
// 1. Opcode 0x2D (host_cfar_alpha_soft) write path — verify cmd_value
|
||||
// reaches the cmd_* outputs of the read FSM with the right byte order.
|
||||
// 2. Bulk frame header v2 — verify byte0=0xAA, byte1=0x02 (version),
|
||||
// byte2=stream flags, bytes3-8=frame_num/range/doppler counts.
|
||||
// 3. Status packet length — verify 30 bytes (was 26 in v1) and that
|
||||
// status_words[6] carries detect_count_cand/detect_threshold_soft.
|
||||
// 4. PR-G FSM trim — full-frame header/body length consistency. With all
|
||||
// streams enabled, total emitted bytes must equal 9 (hdr) + range×2 +
|
||||
// range×doppler×2 (doppler) + range×doppler×2/8 (detect) + 1 (footer).
|
||||
// Catches future header-vs-body drift and confirms padding is skipped.
|
||||
// 5. PR-G G2 — MEDIUM ladder timing opcodes (0x17, 0x18) round-trip via
|
||||
// cmd_opcode/cmd_value (the host_medium_*_cycles registers live in
|
||||
// radar_system_top, exercised at integration level by tb_system_e2e).
|
||||
// ============================================================================
|
||||
|
||||
module tb_usb_protocol_v2;
|
||||
localparam CLK_PER = 10.0; // 100 MHz
|
||||
localparam FT_CLK_PER = 16.667; // 60 MHz
|
||||
|
||||
reg clk = 1'b0;
|
||||
reg ft_clk = 1'b0;
|
||||
reg reset_n = 1'b0;
|
||||
reg ft_reset_n = 1'b0;
|
||||
|
||||
// Radar inputs (clk domain)
|
||||
reg [31:0] range_profile = 32'd0;
|
||||
reg range_valid = 1'b0;
|
||||
reg [15:0] doppler_real = 16'd0;
|
||||
reg [15:0] doppler_imag = 16'd0;
|
||||
reg doppler_valid = 1'b0;
|
||||
reg [`RP_DETECT_CLASS_WIDTH-1:0] cfar_detect_class = `RP_DETECT_NONE;
|
||||
reg cfar_valid = 1'b0;
|
||||
|
||||
reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_in = 0;
|
||||
reg [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin_in = 0;
|
||||
reg frame_complete = 1'b0;
|
||||
|
||||
// FT2232H interface
|
||||
wire [7:0] ft_data;
|
||||
reg ft_rxf_n = 1'b1;
|
||||
reg ft_txe_n = 1'b0;
|
||||
wire ft_rd_n;
|
||||
wire ft_wr_n;
|
||||
wire ft_oe_n;
|
||||
wire ft_siwu;
|
||||
|
||||
// Bidirectional data: tristate driver from TB for read path
|
||||
reg [7:0] ft_data_drive = 8'd0;
|
||||
reg ft_data_drive_en = 1'b0;
|
||||
assign ft_data = ft_data_drive_en ? ft_data_drive : 8'hzz;
|
||||
pulldown pd[7:0] (ft_data);
|
||||
|
||||
wire [31:0] cmd_data;
|
||||
wire cmd_valid;
|
||||
wire [7:0] cmd_opcode;
|
||||
wire [7:0] cmd_addr;
|
||||
wire [15:0] cmd_value;
|
||||
|
||||
// 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;
|
||||
|
||||
// Status inputs (mostly tied off; PR-G additions below)
|
||||
reg status_request = 1'b0;
|
||||
reg [15:0] status_cfar_threshold = 16'h1234;
|
||||
reg [1:0] status_radar_mode = 2'd0;
|
||||
reg [15:0] status_long_chirp = 16'd0;
|
||||
reg [15:0] status_long_listen = 16'd0;
|
||||
reg [15:0] status_guard = 16'd0;
|
||||
reg [15:0] status_short_chirp = 16'd0;
|
||||
reg [15:0] status_short_listen = 16'd0;
|
||||
reg [5:0] status_chirps_per_elev = 6'd0;
|
||||
reg [1:0] status_range_mode = 2'd0;
|
||||
reg status_chirps_mismatch = 1'b0;
|
||||
reg [4:0] status_self_test_flags = 5'd0;
|
||||
reg [7:0] status_self_test_detail = 8'd0;
|
||||
reg status_self_test_busy = 1'b0;
|
||||
reg [3:0] status_agc_current_gain = 4'd0;
|
||||
reg [7:0] status_agc_peak_magnitude = 8'd0;
|
||||
reg [7:0] status_agc_saturation_count = 8'd0;
|
||||
reg status_agc_enable = 1'b0;
|
||||
reg status_range_decim_watchdog = 1'b0;
|
||||
reg status_ddc_cic_fir_overrun = 1'b0;
|
||||
// PR-G new
|
||||
reg [7:0] status_cfar_alpha_soft = `RP_DEF_CFAR_ALPHA_SOFT; // 0x18
|
||||
reg [16:0] status_detect_threshold_soft = 17'h00ABC;
|
||||
reg [15:0] status_detect_count_cand = 16'd42;
|
||||
|
||||
integer pass = 0;
|
||||
integer fail = 0;
|
||||
|
||||
always #(CLK_PER/2) clk = ~clk;
|
||||
always #(FT_CLK_PER/2) ft_clk = ~ft_clk;
|
||||
|
||||
usb_data_interface_ft2232h u_dut (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.ft_reset_n(ft_reset_n),
|
||||
.range_profile(range_profile),
|
||||
.range_valid(range_valid),
|
||||
.doppler_real(doppler_real),
|
||||
.doppler_imag(doppler_imag),
|
||||
.doppler_valid(doppler_valid),
|
||||
.cfar_detect_class(cfar_detect_class),
|
||||
.cfar_valid(cfar_valid),
|
||||
.range_bin_in(range_bin_in),
|
||||
.doppler_bin_in(doppler_bin_in),
|
||||
.frame_complete(frame_complete),
|
||||
.ft_data(ft_data),
|
||||
.ft_rxf_n(ft_rxf_n),
|
||||
.ft_txe_n(ft_txe_n),
|
||||
.ft_rd_n(ft_rd_n),
|
||||
.ft_wr_n(ft_wr_n),
|
||||
.ft_oe_n(ft_oe_n),
|
||||
.ft_siwu(ft_siwu),
|
||||
.ft_clk(ft_clk),
|
||||
.cmd_data(cmd_data),
|
||||
.cmd_valid(cmd_valid),
|
||||
.cmd_opcode(cmd_opcode),
|
||||
.cmd_addr(cmd_addr),
|
||||
.cmd_value(cmd_value),
|
||||
.stream_control(stream_control),
|
||||
.status_request(status_request),
|
||||
.status_cfar_threshold(status_cfar_threshold),
|
||||
.status_stream_ctrl(status_stream_ctrl),
|
||||
.status_radar_mode(status_radar_mode),
|
||||
.status_long_chirp(status_long_chirp),
|
||||
.status_long_listen(status_long_listen),
|
||||
.status_guard(status_guard),
|
||||
.status_short_chirp(status_short_chirp),
|
||||
.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),
|
||||
.status_self_test_flags(status_self_test_flags),
|
||||
.status_self_test_detail(status_self_test_detail),
|
||||
.status_self_test_busy(status_self_test_busy),
|
||||
.status_agc_current_gain(status_agc_current_gain),
|
||||
.status_agc_peak_magnitude(status_agc_peak_magnitude),
|
||||
.status_agc_saturation_count(status_agc_saturation_count),
|
||||
.status_agc_enable(status_agc_enable),
|
||||
.status_range_decim_watchdog(status_range_decim_watchdog),
|
||||
.status_ddc_cic_fir_overrun(status_ddc_cic_fir_overrun),
|
||||
.status_cfar_alpha_soft(status_cfar_alpha_soft),
|
||||
.status_detect_threshold_soft(status_detect_threshold_soft),
|
||||
.status_detect_count_cand(status_detect_count_cand)
|
||||
);
|
||||
|
||||
// Capture egress bytes. egress_count counts ALL emitted bytes (used by
|
||||
// TEST 4 to verify total frame length). egress_bytes only buffers the
|
||||
// first 36 (header + a few status bytes — enough for TESTS 2, 3, 4 to
|
||||
// index byte-level checks).
|
||||
reg [7:0] egress_bytes [0:35];
|
||||
integer egress_count = 0;
|
||||
always @(posedge ft_clk) begin
|
||||
if (!ft_wr_n && !ft_txe_n) begin
|
||||
if (egress_count < 36)
|
||||
egress_bytes[egress_count] <= ft_data;
|
||||
egress_count <= egress_count + 1;
|
||||
end
|
||||
end
|
||||
|
||||
task check_b;
|
||||
input [127:0] tag;
|
||||
input cond;
|
||||
begin
|
||||
if (cond) begin
|
||||
$display("[PASS] %0s", tag);
|
||||
pass = pass + 1;
|
||||
end else begin
|
||||
$display("[FAIL] %0s", tag);
|
||||
fail = fail + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task wait_clk;
|
||||
input integer n;
|
||||
integer i;
|
||||
begin
|
||||
for (i = 0; i < n; i = i + 1) @(posedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
// 4-byte command bus driver (host → FPGA, ft_clk domain).
|
||||
// Read FSM: RD_IDLE (edge 1: see rxf_n, schedule transition) → RD_OE_ASSERT
|
||||
// (edge 2: schedule RD_READING) → RD_READING (edges 3,4,5,6: each samples
|
||||
// ft_data via NBA). Byte N must be on the bus at edge (N+2) of the sequence.
|
||||
task send_cmd;
|
||||
input [7:0] op;
|
||||
input [7:0] addr;
|
||||
input [15:0] val;
|
||||
begin
|
||||
@(posedge ft_clk); #1; // Edge 0
|
||||
ft_rxf_n = 1'b0;
|
||||
ft_data_drive = op;
|
||||
ft_data_drive_en = 1'b1;
|
||||
@(posedge ft_clk); #1; // Edge 1: RD_IDLE → RD_OE_ASSERT (NBA)
|
||||
@(posedge ft_clk); #1; // Edge 2: RD_OE_ASSERT → RD_READING (NBA)
|
||||
@(posedge ft_clk); #1; // Edge 3: RD_READING samples op (1st)
|
||||
ft_data_drive = addr;
|
||||
@(posedge ft_clk); #1; // Edge 4: samples addr (2nd)
|
||||
ft_data_drive = val[15:8];
|
||||
@(posedge ft_clk); #1; // Edge 5: samples val_hi (3rd)
|
||||
ft_data_drive = val[7:0];
|
||||
@(posedge ft_clk); #1; // Edge 6: samples val_lo (4th, transitions out)
|
||||
ft_rxf_n = 1'b1;
|
||||
ft_data_drive_en = 1'b0;
|
||||
wait_clk(20); // CDC propagation to clk domain
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
$display("\n========== tb_usb_protocol_v2 ==========");
|
||||
// Reset
|
||||
reset_n = 1'b0;
|
||||
ft_reset_n = 1'b0;
|
||||
wait_clk(10);
|
||||
reset_n = 1'b1;
|
||||
ft_reset_n = 1'b1;
|
||||
wait_clk(20);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// TEST 1: Opcode 0x2D (host_cfar_alpha_soft) round trip
|
||||
// -------------------------------------------------------------
|
||||
$display("\n[TEST 1] Opcode 0x2D (cfar_alpha_soft) round trip");
|
||||
send_cmd(`RP_OP_CFAR_ALPHA_SOFT, 8'h00, 16'h0024); // 0x24 in Q4.4 = 2.25
|
||||
check_b("T1.1: cmd_opcode=0x2D", cmd_opcode == 8'h2D);
|
||||
check_b("T1.2: cmd_value lower 8b=0x24", cmd_value[7:0] == 8'h24);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// TEST 2: Frame header v2 — 9 bytes, byte1=0x02
|
||||
// -------------------------------------------------------------
|
||||
$display("\n[TEST 2] Frame header v2 emission");
|
||||
// Disable all stream sections (HDR -> FOOTER fast drain)
|
||||
stream_control = 6'b000_000;
|
||||
wait_clk(50); // Let CDC propagate
|
||||
egress_count = 0;
|
||||
@(posedge clk);
|
||||
frame_complete = 1'b1;
|
||||
@(posedge clk);
|
||||
frame_complete = 1'b0;
|
||||
// Wait for full frame drain (10 bytes = 10 ft_clk + slack)
|
||||
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);
|
||||
// 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);
|
||||
check_b("T2.5: byte4 = fn[7:0]=0", egress_bytes[4] == 8'h00);
|
||||
check_b("T2.6: byte5/6 = range_bins=512",
|
||||
{egress_bytes[5], egress_bytes[6]} == 16'd512);
|
||||
check_b("T2.7: byte7/8 = doppler_bins=48",
|
||||
{egress_bytes[7], egress_bytes[8]} == 16'd48);
|
||||
check_b("T2.8: byte9 = footer 0x55", egress_bytes[9] == 8'h55);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// TEST 3: Status packet length = 30 bytes; word[6] carries telemetry
|
||||
// -------------------------------------------------------------
|
||||
$display("\n[TEST 3] Status packet length 30B + word[6] PR-G fields");
|
||||
egress_count = 0;
|
||||
@(posedge clk);
|
||||
status_request = 1'b1;
|
||||
@(posedge clk);
|
||||
status_request = 1'b0;
|
||||
wait_clk(300); // Wait for status drain
|
||||
check_b("T3.1: byte0 = 0xBB (status header)", egress_bytes[0] == 8'hBB);
|
||||
check_b("T3.2: byte29 = 0x55 (footer)", egress_bytes[29] == 8'h55);
|
||||
check_b("T3.3: status_words[6] count_cand[15:8]=0", egress_bytes[25] == 8'h00);
|
||||
check_b("T3.4: status_words[6] count_cand[7:0]=42", egress_bytes[26] == 8'd42);
|
||||
check_b("T3.5: status_words[6] thr_soft[15:8]=0x0A", egress_bytes[27] == 8'h0A);
|
||||
check_b("T3.6: status_words[6] thr_soft[7:0]=0xBC", egress_bytes[28] == 8'hBC);
|
||||
// alpha_soft (0x18) packed into word[4][9:2] → byte at index 19,20
|
||||
// word[4] = {gain[3:0], peak[7:0], sat[7:0], en, mismatch, alpha_soft[7:0], range_mode[1:0]}
|
||||
// bits[9:2] = alpha_soft. byte[19] = word[4][15:8], byte[20] = word[4][7:0]
|
||||
// alpha_soft sits in byte[20][7:2] | byte[19][1:0] — let's just check mid bytes are non-zero
|
||||
// when alpha_soft=0x18 (b0001_1000): bits[9:2] of word[4] = 8'h18, so:
|
||||
// word[4][7:0] = {alpha_soft[7:0], range_mode[1:0]} = {8'h18, 2'b00} = 8'h60
|
||||
check_b("T3.7: status_words[4][7:0] = alpha_soft<<2 = 0x60 (alpha=0x18)",
|
||||
egress_bytes[20] == 8'h60);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// TEST 4: full-frame header/body length consistency (PR-G trim)
|
||||
// -------------------------------------------------------------
|
||||
$display("\n[TEST 4] Full-frame header/body length consistency (PR-G trim)");
|
||||
// Re-enable all 3 streams so HDR + range + doppler + detect + footer
|
||||
// are all emitted. We don't fill BRAMs — only the byte count matters.
|
||||
stream_control = 6'b000_111;
|
||||
wait_clk(50); // CDC propagate
|
||||
egress_count = 0;
|
||||
@(posedge clk);
|
||||
frame_complete = 1'b1;
|
||||
@(posedge clk);
|
||||
frame_complete = 1'b0;
|
||||
// Worst-case drain: 9 + 1024 + 49152 + 6144 + 1 = 56330 bytes.
|
||||
// Each doppler byte takes ~1 ft_clk (MSB then LSB, both at 60 MHz).
|
||||
// Detect = 1 byte/ft_clk. Plus FSM transitions, so allow ~70k ft_clk.
|
||||
wait_clk(120_000); // ~1.2 ms in clk-domain (covers 60 MHz drain)
|
||||
check_b("T4.1: egress_count == expected total",
|
||||
egress_count == (`RP_FRAME_HDR_BYTES
|
||||
+ `RP_NUM_RANGE_BINS * 2
|
||||
+ `RP_NUM_RANGE_BINS * `RP_NUM_DOPPLER_BINS * 2
|
||||
+ (`RP_NUM_RANGE_BINS * `RP_NUM_DOPPLER_BINS * 2) / 8
|
||||
+ 1));
|
||||
check_b("T4.2: header byte0 = 0xAA (frame still framed correctly)",
|
||||
egress_bytes[0] == 8'hAA);
|
||||
check_b("T4.3: header byte1 = protocol version 0x02",
|
||||
egress_bytes[1] == `RP_USB_PROTOCOL_VERSION);
|
||||
check_b("T4.4: header byte5/6 = range_bins=512",
|
||||
{egress_bytes[5], egress_bytes[6]} == 16'd512);
|
||||
check_b("T4.5: header byte7/8 = doppler_bins=48",
|
||||
{egress_bytes[7], egress_bytes[8]} == 16'd48);
|
||||
// Sanity: doppler section must NOT be the old 65536-byte padded size.
|
||||
// Old (pre-trim) total was 9 + 1024 + 65536 + 8192 + 1 = 74762.
|
||||
// New (post-trim) total = 56330. Catch if FSM regresses to padded.
|
||||
check_b("T4.6: emitted bytes < pre-trim padded total (74762)",
|
||||
egress_count < 74762);
|
||||
$display(" egress_count = %0d (expected 56330)", egress_count);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// TEST 5: MEDIUM ladder timing opcodes (PR-G G2) — round-trip via cmd bus
|
||||
// -------------------------------------------------------------
|
||||
$display("\n[TEST 5] MEDIUM ladder timing opcodes (0x17, 0x18)");
|
||||
send_cmd(`RP_OP_MEDIUM_CHIRP_CYCLES, 8'h00, 16'd750);
|
||||
check_b("T5.1: cmd_opcode=0x17 (MEDIUM_CHIRP_CYCLES)", cmd_opcode == 8'h17);
|
||||
check_b("T5.2: cmd_value=750", cmd_value == 16'd750);
|
||||
|
||||
send_cmd(`RP_OP_MEDIUM_LISTEN_CYCLES, 8'h00, 16'd16500);
|
||||
check_b("T5.3: cmd_opcode=0x18 (MEDIUM_LISTEN_CYCLES)", cmd_opcode == 8'h18);
|
||||
check_b("T5.4: cmd_value=16500", cmd_value == 16'd16500);
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// Done
|
||||
// -------------------------------------------------------------
|
||||
$display("\n-----------------------------------------------------------");
|
||||
$display("RESULTS: %0d PASS, %0d FAIL", pass, fail);
|
||||
$display("-----------------------------------------------------------");
|
||||
if (fail == 0) $display("[OVERALL PASS]"); else $display("[OVERALL FAIL]");
|
||||
$finish;
|
||||
end
|
||||
|
||||
// Watchdog
|
||||
initial begin
|
||||
#20_000_000;
|
||||
$display("[TIMEOUT] tb_usb_protocol_v2 watchdog");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -8,86 +8,72 @@
|
||||
* FT2232H USB 2.0 Hi-Speed FIFO Interface (245 Synchronous FIFO Mode)
|
||||
* Channel A only — 8-bit data bus, 60 MHz CLKOUT from FT2232H.
|
||||
*
|
||||
* BULK PER-FRAME PROTOCOL (replaces legacy per-sample 11-byte packets):
|
||||
* BULK PER-FRAME PROTOCOL V2 (PR-G — single canonical encoding):
|
||||
*
|
||||
* Frame packet (FPGA→Host): variable length, up to ~35 KB
|
||||
* Frame packet (FPGA→Host): variable length, up to 74,762 bytes
|
||||
* Byte 0: 0xAA (frame start header)
|
||||
* Byte 1: Format flags {2'b0, sparse_det, mag_only, stream_cfar, stream_doppler, stream_range}
|
||||
* Bytes 2-3: Frame number (16-bit, MSB first)
|
||||
* Bytes 4-5: Range bin count (16-bit, MSB first) = 512
|
||||
* Bytes 6-7: Doppler bin count (16-bit, MSB first) = 32
|
||||
* Byte 1: 0x02 (PROTOCOL VERSION — host MUST reject any other value)
|
||||
* Byte 2: Stream flags {5'b0, stream_cfar, stream_doppler, stream_range}
|
||||
* Bytes 3-4: Frame number (uint16, MSB first)
|
||||
* Bytes 5-6: Range bin count (uint16, MSB first) = `RP_NUM_RANGE_BINS` (512)
|
||||
* Bytes 7-8: Doppler bin count (uint16, MSB first) = `RP_NUM_DOPPLER_BINS` (48)
|
||||
*
|
||||
* [If stream_range (bit 0):]
|
||||
* Next 1024 bytes: Range profile, 512 × 16-bit magnitude, MSB first
|
||||
* Next 1024 bytes: range profile, 512 × uint16 Manhattan magnitude, MSB first.
|
||||
*
|
||||
* [If stream_doppler (bit 1):]
|
||||
* Next 32768 bytes: Doppler magnitude, 512×32 × 16-bit, row-major, MSB first
|
||||
* Next 65536 bytes: doppler magnitude, 32768 cells × uint16, row-major
|
||||
* (range_bin slowest, doppler_bin fastest), MSB first. Cells indexed
|
||||
* [0..47] are real Doppler bins; cells [48..63] within each range are
|
||||
* the power-of-2 padding from PR-F (always emitted as 0x0000).
|
||||
*
|
||||
* [If stream_cfar (bit 2):]
|
||||
* Next 2048 bytes: Detection flags, 512×32 bits packed into bytes, MSB-first bit order
|
||||
* Next 8192 bytes: detect_class bitmap, 32768 cells × 2 bits, MSB-first
|
||||
* packing. Each byte holds 4 cells:
|
||||
* byte[N]: bits[7:6]=cell[4*N], bits[5:4]=cell[4*N+1],
|
||||
* bits[3:2]=cell[4*N+2], bits[1:0]=cell[4*N+3]
|
||||
* Cell encoding (per `RP_DETECT_*`):
|
||||
* 2'b00 = NONE (below soft threshold)
|
||||
* 2'b01 = CANDIDATE (above soft, below confirm — host re-cues)
|
||||
* 2'b10 = CONFIRMED (above confirm threshold — track-eligible)
|
||||
* 2'b11 = RESERVED (must not be emitted by RTL)
|
||||
*
|
||||
* 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)
|
||||
* Status packet (FPGA→Host): 30 bytes (PR-G: was 26 in v1, +4 for soft tier)
|
||||
* Byte 0: 0xBB (status header)
|
||||
* Bytes 1-24: 6 × 32-bit status words, MSB first
|
||||
* Byte 25: 0x55 (footer)
|
||||
* Bytes 1-28: 7 × 32-bit status words, MSB first
|
||||
* word[6] = {detect_count_cand[15:0], detect_threshold_soft[15:0]}
|
||||
* Byte 29: 0x55 (footer)
|
||||
*
|
||||
* Command (Host→FPGA): 4 bytes received sequentially (unchanged)
|
||||
* Byte 0: opcode[7:0]
|
||||
* Byte 0: opcode[7:0] (see RP_OP_* in radar_params.vh)
|
||||
* Byte 1: addr[7:0]
|
||||
* Byte 2: value[15:8]
|
||||
* Byte 3: value[7:0]
|
||||
*
|
||||
* MEMORY ARCHITECTURE:
|
||||
* - Doppler magnitude BRAM: 512×32 = 16384 entries × 16-bit = 32 KB (~14 BRAM18)
|
||||
* - Doppler magnitude BRAM: 32768 entries × 16-bit = 64 KB (~28 BRAM18 on 50T)
|
||||
* Written in clk (100 MHz) domain as Doppler cells arrive.
|
||||
* Read in ft_clk (60 MHz) domain during USB bulk transfer.
|
||||
* - Range profile buffer: 512 × 16-bit = 1 KB (~1 BRAM18)
|
||||
* - Range profile buffer: 512 × 16-bit = 1 KB (1 BRAM18)
|
||||
* 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.
|
||||
* - Detect-class buffer: 32768 cells × 2 bits = 65536 bits = 8192 bytes (4 BRAM18)
|
||||
* Written in clk domain from cfar_valid events via 3-cycle RMW pipeline.
|
||||
*
|
||||
* 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
|
||||
* 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.
|
||||
* BANDWIDTH BUDGET (PR-G v2, all streams):
|
||||
* Header: 9 B + Range: 1024 B + Doppler: 65536 B + Detect: 8192 B + Footer: 1 B
|
||||
* = 74,762 bytes/frame × ~119 fps (3-subframe rate post-PR-F) ≈ 8.9 MB/s
|
||||
* FT2232H 245-Sync-FIFO conservative budget ~8 MB/s (FTDI AN_232B-04, 80%
|
||||
* utilisation); practical sustained throughput is 30–40 MB/s on a tuned
|
||||
* host. Sufficient headroom even with the conservative budget overshoot.
|
||||
*
|
||||
* CDC STRATEGY:
|
||||
* - Frame data: Written to dual-port BRAM at 100 MHz, read at 60 MHz (inherently CDC-safe)
|
||||
* - frame_ready flag: Toggle CDC (100 MHz → 60 MHz), same as status_request
|
||||
* - stream_control: 2-stage level sync (changes infrequently)
|
||||
* - Commands: Read FSM in ft_clk domain, output CDC'd by consumer (unchanged)
|
||||
* - Frame data: Written to dual-port BRAM at 100 MHz, read at 60 MHz (inherently CDC-safe).
|
||||
* - frame_ready flag: Toggle CDC (100 MHz → 60 MHz), same as status_request.
|
||||
* - stream_control: 2-stage level sync (changes infrequently).
|
||||
* - status_*_soft / status_*_cand: 2-stage level sync (slow-changing per-frame values).
|
||||
* - Commands: Read FSM in ft_clk domain, output CDC'd by consumer (unchanged).
|
||||
*
|
||||
* Clock domains:
|
||||
* clk = 100 MHz system clock (radar data domain)
|
||||
@@ -105,7 +91,8 @@ module usb_data_interface_ft2232h (
|
||||
input wire [15:0] doppler_real,
|
||||
input wire [15:0] doppler_imag,
|
||||
input wire doppler_valid,
|
||||
input wire cfar_detection,
|
||||
// PR-G: 2-bit class replaces single cfar_detection bit.
|
||||
input wire [`RP_DETECT_CLASS_WIDTH-1:0] cfar_detect_class,
|
||||
input wire cfar_valid,
|
||||
|
||||
// New inputs for bulk frame protocol (clk domain)
|
||||
@@ -170,7 +157,13 @@ module usb_data_interface_ft2232h (
|
||||
// of the gpio_dig7 split. 2-stage level CDC into ft_clk; sticky/slow-
|
||||
// changing source so 2-FF sync is sufficient.
|
||||
input wire status_range_decim_watchdog, // audit F-6.4
|
||||
input wire status_ddc_cic_fir_overrun // audit F-1.2
|
||||
input wire status_ddc_cic_fir_overrun, // audit F-1.2
|
||||
|
||||
// PR-G: 2-tier CFAR telemetry (clk domain → status_words[6]).
|
||||
// Slow-changing per-frame values; 2-stage level CDC into ft_clk.
|
||||
input wire [7:0] status_cfar_alpha_soft, // current host_cfar_alpha_soft (Q4.4)
|
||||
input wire [16:0] status_detect_threshold_soft, // PR-G: candidate-tier threshold (last frame)
|
||||
input wire [15:0] status_detect_count_cand // PR-G: candidate count (last frame)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
@@ -191,20 +184,38 @@ localparam DOPPLER_BIN_BITS = `RP_DOPPLER_BIN_WIDTH;// 6 (PR-F)
|
||||
localparam FRAME_CELLS = NUM_RANGE_BINS * (1 << DOPPLER_BIN_BITS); // 32768 (PR-F)
|
||||
// Frame-cell address widths.
|
||||
localparam FRAME_ADDR_W = RANGE_BIN_BITS + DOPPLER_BIN_BITS; // 15
|
||||
localparam DETECT_BYTE_ADDR_W = FRAME_ADDR_W - 3; // 12
|
||||
localparam DETECT_BYTE_LAST = (FRAME_CELLS / 8) - 1; // 4095
|
||||
|
||||
// Frame header: 8 bytes (0xAA + flags + frame_num[2] + range_bins[2] + doppler_bins[2])
|
||||
localparam FRAME_HDR_BYTES = 8;
|
||||
// PR-G: detect section is 2 bits/cell instead of 1 bit/cell.
|
||||
// 32768 cells * 2 bits = 65536 bits = 8192 bytes; needs 13-bit byte address.
|
||||
// Cell-to-byte mapping: byte_addr = bit_addr[15:3] = {range_bin[8:0], doppler_bin[5:2]}
|
||||
// Sub-byte position (bits within byte) = (3 - doppler_bin[1:0]) * 2, MSB-first.
|
||||
localparam DETECT_BITS_PER_CELL = `RP_DETECT_BITS_PER_CELL; // 2
|
||||
localparam DETECT_BYTE_ADDR_W = FRAME_ADDR_W + 1 - 3; // 13
|
||||
localparam DETECT_BYTE_LAST = ((FRAME_CELLS * DETECT_BITS_PER_CELL) / 8) - 1; // 8191
|
||||
localparam DETECT_BIT_ADDR_W = FRAME_ADDR_W + 1; // 16
|
||||
|
||||
// Frame header: 9 bytes (0xAA + ver + flags + frame_num[2] + range_bins[2] + doppler_bins[2])
|
||||
localparam FRAME_HDR_BYTES = `RP_FRAME_HDR_BYTES; // 9 (PR-G)
|
||||
// Range profile section: 512 × 2 = 1024 bytes
|
||||
localparam RANGE_SECTION_BYTES = NUM_RANGE_BINS * 2;
|
||||
// Doppler mag section: 16384 × 2 = 32768 bytes
|
||||
localparam DOPPLER_MAG_SECTION_BYTES = FRAME_CELLS * 2;
|
||||
// Detection flag section: 16384 bits = 2048 bytes
|
||||
localparam DETECT_SECTION_BYTES = FRAME_CELLS / 8;
|
||||
// Doppler mag section: 512 range × 48 doppler × 2 = 49152 bytes (PR-G).
|
||||
// FSM iterates only valid (range, doppler) cells — the next-pow-2 BRAM
|
||||
// padding (doppler 48..63 per range, 8192 dead cells) is skipped on the
|
||||
// wire so the body length matches the header's `doppler_bins=48` field.
|
||||
localparam DOPPLER_MAG_SECTION_BYTES = NUM_RANGE_BINS * NUM_DOPPLER_BINS * 2;
|
||||
// Last valid doppler index — used by WR_DOPPLER_DATA to wrap to next range.
|
||||
localparam [DOPPLER_BIN_BITS-1:0] DOP_BIN_LAST = NUM_DOPPLER_BINS[DOPPLER_BIN_BITS-1:0] - 1'b1;
|
||||
// Detect class section: emit only valid range × doppler cells.
|
||||
// Per range bin: NUM_DOPPLER_BINS × DETECT_BITS_PER_CELL / 8 bytes (rounded
|
||||
// up). For 48 doppler × 2 bits = 96 bits = 12 bytes per range. The 4 padded
|
||||
// bytes per range (doppler 48..63 indices) are skipped on the wire so the
|
||||
// host can compute body length deterministically from the header.
|
||||
localparam VALID_DET_BYTES_PER_RANGE = (NUM_DOPPLER_BINS * DETECT_BITS_PER_CELL + 7) / 8; // 12
|
||||
localparam DETECT_SECTION_BYTES = NUM_RANGE_BINS * VALID_DET_BYTES_PER_RANGE; // 6144
|
||||
localparam [3:0] DET_BYTE_LAST_PER_RANGE = VALID_DET_BYTES_PER_RANGE[3:0] - 4'd1; // 11
|
||||
|
||||
// Status packet: 26 bytes (unchanged)
|
||||
localparam STATUS_PKT_LEN = 5'd26;
|
||||
// Status packet: 30 bytes (PR-G: 7 × 32-bit words + header + footer)
|
||||
localparam STATUS_PKT_LEN = 5'd30;
|
||||
|
||||
// ============================================================================
|
||||
// WRITE FSM STATES (FPGA → Host, ft_clk domain)
|
||||
@@ -302,31 +313,24 @@ always @(posedge ft_clk) begin
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// DETECTION FLAG BRAM (clk write, ft_clk read)
|
||||
// DETECT-CLASS BRAM (clk write, ft_clk read) — PR-G: 2 bits per cell
|
||||
// ============================================================================
|
||||
// 16384 bits stored as 2048 × 8-bit bytes.
|
||||
// Write: individual bit-set on cfar_valid with cfar_detection=1.
|
||||
// Clear: bulk clear on frame_complete (start of new frame).
|
||||
// Address = {range_bin[8:0], doppler_bin[4:2]} = byte address (11 bits, 2048 entries)
|
||||
// Bit position = doppler_bin[1:0] within sub-byte ... actually let's use
|
||||
// a simpler scheme: 16384 entries × 1-bit, but that doesn't map well to BRAM.
|
||||
// FRAME_CELLS cells × 2 bits = 65536 bits stored as 8192 × 8-bit bytes.
|
||||
// Each byte packs 4 consecutive cells (MSB-first):
|
||||
// byte[N] bits[7:6] = cell[4*N + 0] (doppler_bin[1:0] = 00)
|
||||
// byte[N] bits[5:4] = cell[4*N + 1] (doppler_bin[1:0] = 01)
|
||||
// byte[N] bits[3:2] = cell[4*N + 2] (doppler_bin[1:0] = 10)
|
||||
// byte[N] bits[1:0] = cell[4*N + 3] (doppler_bin[1:0] = 11)
|
||||
// Cell encoding per `RP_DETECT_*` (2'b00=NONE / 2'b01=CAND / 2'b10=CONFIRM).
|
||||
//
|
||||
// Better: Store as 2048 × 8-bit. Each byte holds 8 consecutive detection bits.
|
||||
// Bit address = {range_bin, doppler_bin} = 14-bit. Byte addr = bit_addr[13:3].
|
||||
// Bit position = bit_addr[2:0].
|
||||
// On write: read-modify-write (set bit). On frame clear: bulk zero.
|
||||
//
|
||||
// For simplicity and BRAM efficiency, we use a separate approach:
|
||||
// Store detections in a small register file and pack during transfer.
|
||||
// With 512×32=16384 bits, that's 2048 bytes — fits in 1 BRAM18.
|
||||
//
|
||||
// IMPLEMENTATION: We use the BRAM in byte-write mode. On cfar_valid, we do
|
||||
// a 1-cycle read then 1-cycle write-back with the bit set. This works because
|
||||
// CFAR outputs arrive one cell per clock cycle (sequential scan).
|
||||
// Write path: 3-cycle read-modify-write on cfar_valid (idle → read → write).
|
||||
// Cell index within byte = doppler_bin_in[1:0]
|
||||
// MSB-first shift = (3 - cell_index) * 2 (cell 0 lands in [7:6], cell 3 in [1:0])
|
||||
// Clear path: bulk byte-zero on frame_complete (steps 1 byte/cycle).
|
||||
|
||||
(* ram_style = "block" *) reg [7:0] detect_bram [0:DETECT_BYTE_LAST]; // PR-F: 3072 entries (was 2048)
|
||||
(* ram_style = "block" *) reg [7:0] detect_bram [0:DETECT_BYTE_LAST]; // PR-G: 8192 entries (was 4096)
|
||||
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_wr_addr; // PR-F: 12-bit byte addr (was 11)
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_wr_addr; // PR-G: 13-bit byte addr (was 12)
|
||||
reg [7:0] detect_wr_data;
|
||||
reg detect_wr_en;
|
||||
|
||||
@@ -335,7 +339,7 @@ always @(posedge clk) begin
|
||||
detect_bram[detect_wr_addr] <= detect_wr_data;
|
||||
end
|
||||
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_rd_addr; // PR-F: 12-bit byte addr (was 11)
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_rd_addr; // PR-G: 13-bit byte addr (was 12)
|
||||
reg [7:0] detect_rd_data;
|
||||
|
||||
always @(posedge ft_clk) begin
|
||||
@@ -343,10 +347,9 @@ always @(posedge ft_clk) begin
|
||||
end
|
||||
|
||||
// Detection BRAM read-modify-write pipeline (clk domain)
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_rmw_addr; // PR-F: 12-bit byte addr (was 11)
|
||||
reg [7:0] detect_rmw_rd;
|
||||
reg [2:0] detect_rmw_bit;
|
||||
reg detect_rmw_value;
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_rmw_addr; // PR-G: 13-bit byte addr (was 12)
|
||||
reg [1:0] detect_rmw_cell_idx; // PR-G: 0..3, cell within byte
|
||||
reg [`RP_DETECT_CLASS_WIDTH-1:0] detect_rmw_value; // PR-G: 2-bit class (was 1-bit)
|
||||
reg [1:0] detect_rmw_state; // 0=idle, 1=read, 2=write
|
||||
|
||||
// Synchronous read for RMW (clk domain, separate from ft_clk read port)
|
||||
@@ -389,7 +392,8 @@ wire [15:0] range_mag = range_manhattan[16] ? 16'hFFFF : range_manhattan[15:0];
|
||||
reg [15:0] frame_number; // Incrementing frame counter
|
||||
reg frame_ready_toggle; // Toggle CDC: frame ready for USB transfer
|
||||
reg frame_filling; // 1 = currently accumulating frame data
|
||||
reg [FRAME_ADDR_W-1:0] detect_clear_addr; // PR-F: 15-bit bit-counter; FRAME_CELLS = NUM_RANGE_BINS * (1<<DOPPLER_BIN_BITS) = 32768 (full 15-bit space)
|
||||
// PR-G: byte-counter (was 15-bit bit-counter in PR-F). 8192 bytes = 13 bits.
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_clear_addr;
|
||||
reg detect_clearing; // 1 = bulk clear in progress
|
||||
|
||||
// Range bin counter for range profile writes
|
||||
@@ -427,11 +431,11 @@ always @(posedge clk or negedge reset_n) begin
|
||||
detect_wr_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
detect_wr_data <= 8'd0;
|
||||
detect_clearing <= 1'b0;
|
||||
detect_clear_addr <= {FRAME_ADDR_W{1'b0}};
|
||||
detect_clear_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
detect_rmw_state <= 2'd0;
|
||||
detect_rmw_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
detect_rmw_bit <= 3'd0;
|
||||
detect_rmw_value <= 1'b0;
|
||||
detect_rmw_cell_idx <= 2'd0;
|
||||
detect_rmw_value <= `RP_DETECT_NONE;
|
||||
range_write_counter <= {RANGE_BIN_BITS{1'b0}};
|
||||
end else begin
|
||||
// Default: deassert write enables
|
||||
@@ -439,22 +443,22 @@ always @(posedge clk or negedge reset_n) begin
|
||||
range_wr_en <= 1'b0;
|
||||
detect_wr_en <= 1'b0;
|
||||
|
||||
// === Detection BRAM bulk clear (runs after frame_complete) ===
|
||||
// PR-F: bit-counter is FRAME_ADDR_W (15-bit), byte addr is the upper
|
||||
// (FRAME_ADDR_W-3) bits.
|
||||
// === Detect-class BRAM bulk clear (runs after frame_complete) ===
|
||||
// PR-G: 1 byte/cycle byte-counter (was 8-bits-per-cycle bit-counter).
|
||||
if (detect_clearing) begin
|
||||
detect_wr_en <= 1'b1;
|
||||
detect_wr_addr <= detect_clear_addr[FRAME_ADDR_W-1:3];
|
||||
detect_wr_addr <= detect_clear_addr;
|
||||
detect_wr_data <= 8'd0;
|
||||
if (detect_clear_addr[FRAME_ADDR_W-1:3] == DETECT_BYTE_LAST[DETECT_BYTE_ADDR_W-1:0]) begin
|
||||
if (detect_clear_addr == DETECT_BYTE_LAST[DETECT_BYTE_ADDR_W-1:0]) begin
|
||||
detect_clearing <= 1'b0;
|
||||
detect_clear_addr <= {FRAME_ADDR_W{1'b0}};
|
||||
detect_clear_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
end else begin
|
||||
detect_clear_addr <= detect_clear_addr + 15'd8; // Step by 8 bits = 1 byte
|
||||
detect_clear_addr <= detect_clear_addr + {{(DETECT_BYTE_ADDR_W-1){1'b0}}, 1'b1};
|
||||
end
|
||||
end
|
||||
|
||||
// === Detection RMW state machine ===
|
||||
// === Detect-class RMW state machine (PR-G: 2-bit pack) ===
|
||||
// Cell N within byte → MSB-first: shift = (3 - N) * 2 = {!N[1], !N[0], 1'b0}
|
||||
case (detect_rmw_state)
|
||||
2'd0: begin /* idle */ end
|
||||
2'd1: begin
|
||||
@@ -462,13 +466,13 @@ always @(posedge clk or negedge reset_n) begin
|
||||
detect_rmw_state <= 2'd2;
|
||||
end
|
||||
2'd2: begin
|
||||
// Write back with bit set/cleared
|
||||
// Write back with the 2-bit class field updated.
|
||||
detect_wr_en <= 1'b1;
|
||||
detect_wr_addr <= detect_rmw_addr;
|
||||
if (detect_rmw_value)
|
||||
detect_wr_data <= detect_rmw_rddata | (8'd1 << detect_rmw_bit);
|
||||
else
|
||||
detect_wr_data <= detect_rmw_rddata & ~(8'd1 << detect_rmw_bit);
|
||||
// Mask out the 2 bits for this cell, OR in the new class.
|
||||
// shift_amt = (3 - cell_idx) * 2 ∈ {6, 4, 2, 0}
|
||||
detect_wr_data <= (detect_rmw_rddata & ~(8'b11000000 >> ({1'b0, detect_rmw_cell_idx} << 1)))
|
||||
| (({6'b0, detect_rmw_value} << ((3 - {1'b0, detect_rmw_cell_idx}) << 1)));
|
||||
detect_rmw_state <= 2'd0;
|
||||
end
|
||||
default: detect_rmw_state <= 2'd0;
|
||||
@@ -489,14 +493,16 @@ always @(posedge clk or negedge reset_n) begin
|
||||
range_write_counter <= range_write_counter + {{(RANGE_BIN_BITS-1){1'b0}}, 1'b1};
|
||||
end
|
||||
|
||||
// === CFAR detection write (read-modify-write) ===
|
||||
// PR-F: bit_addr = {range_bin_in[8:0], doppler_bin_in[5:0]} = 15-bit.
|
||||
// byte_addr = bit_addr[14:3] (12 bits), bit_pos = bit_addr[2:0].
|
||||
// === CFAR detect-class write (read-modify-write) ===
|
||||
// PR-G: 2 bits per cell. bit_addr = {range_bin[8:0], doppler_bin[5:0]} * 2
|
||||
// = {range_bin[8:0], doppler_bin[5:0], 1'b0} (16 bits).
|
||||
// byte_addr = bit_addr[15:3] = {range_bin[8:0], doppler_bin[5:2]} (13 bits)
|
||||
// cell_idx within byte = doppler_bin[1:0] (0..3, MSB-first ordering)
|
||||
if (cfar_valid && frame_filling && detect_rmw_state == 2'd0 && !detect_clearing) begin
|
||||
detect_rmw_addr <= {range_bin_in, doppler_bin_in[DOPPLER_BIN_BITS-1:3]};
|
||||
detect_rmw_bit <= doppler_bin_in[2:0];
|
||||
detect_rmw_value <= cfar_detection;
|
||||
detect_rmw_state <= 2'd1;
|
||||
detect_rmw_addr <= {range_bin_in, doppler_bin_in[DOPPLER_BIN_BITS-1:2]};
|
||||
detect_rmw_cell_idx <= doppler_bin_in[1:0];
|
||||
detect_rmw_value <= cfar_detect_class;
|
||||
detect_rmw_state <= 2'd1;
|
||||
end
|
||||
|
||||
// === Frame complete: latch frame, signal ft_clk domain ===
|
||||
@@ -603,10 +609,20 @@ reg status_toggle_prev;
|
||||
wire frame_ready_ft = frame_ready_sync[2] ^ frame_ready_prev;
|
||||
wire status_req_ft = status_toggle_sync[2] ^ status_toggle_prev;
|
||||
|
||||
// --- Stream control CDC (6-bit, 2-stage level sync) ---
|
||||
// --- Stream control CDC (6-bit wire, but only [2:0] used in PR-G v2; [5:3] reserved=0).
|
||||
// 2-stage level sync (changes infrequently). ---
|
||||
(* ASYNC_REG = "TRUE" *) reg [5:0] stream_ctrl_sync_0;
|
||||
(* ASYNC_REG = "TRUE" *) reg [5:0] stream_ctrl_sync_1;
|
||||
|
||||
// --- PR-G: 2-tier CFAR telemetry CDC (clk → ft_clk, 2-stage level sync).
|
||||
// Slow-changing per-frame values; sufficient for status readback. ---
|
||||
(* ASYNC_REG = "TRUE" *) reg [7:0] alpha_soft_sync_0;
|
||||
reg [7:0] alpha_soft_sync_1;
|
||||
(* ASYNC_REG = "TRUE" *) reg [16:0] det_thr_soft_sync_0;
|
||||
reg [16:0] det_thr_soft_sync_1;
|
||||
(* ASYNC_REG = "TRUE" *) reg [15:0] det_count_cand_sync_0;
|
||||
reg [15:0] det_count_cand_sync_1;
|
||||
|
||||
// --- AUDIT-C12: frame_drop_count CDC (slow-changing 7-bit value, 2-stage sync) ---
|
||||
(* ASYNC_REG = "TRUE" *) reg [6:0] frame_drop_sync_0;
|
||||
reg [6:0] frame_drop_sync_1;
|
||||
@@ -621,39 +637,42 @@ reg ddc_cic_fir_overrun_sync_1;
|
||||
wire stream_range_en = stream_ctrl_sync_1[0];
|
||||
wire stream_doppler_en = stream_ctrl_sync_1[1];
|
||||
wire stream_cfar_en = stream_ctrl_sync_1[2];
|
||||
wire stream_mag_only = stream_ctrl_sync_1[3];
|
||||
wire stream_sparse_det = stream_ctrl_sync_1[4];
|
||||
// Bit 5 reserved
|
||||
// NOTE: Phase 1 write FSM always sends magnitude-only range/Doppler and
|
||||
// dense detection bitmap. The mag_only and sparse_det bits are included in
|
||||
// the frame header for the Python parser but are not yet honored by the
|
||||
// write FSM. Phase 2 will add I/Q and sparse detection paths.
|
||||
// Bits [5:3] reserved=0 in PR-G v2. The legacy mag_only/sparse_det/frame_decimate
|
||||
// flags were retired with v1 — there is one canonical encoding now (Manhattan-mag
|
||||
// doppler + 2-bit dense detect).
|
||||
|
||||
// --- Frame metadata snapshot (latched in clk domain, stable for ft_clk read) ---
|
||||
reg [15:0] frame_number_snapshot;
|
||||
reg [5:0] stream_flags_snapshot;
|
||||
reg [2:0] stream_flags_snapshot; // PR-G: 3 bits used (range/doppler/cfar)
|
||||
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
frame_number_snapshot <= 16'd0;
|
||||
stream_flags_snapshot <= `RP_STREAM_CTRL_DEFAULT;
|
||||
stream_flags_snapshot <= 3'b111; // PR-G: all 3 streams on (range|doppler|cfar)
|
||||
end else if (frame_complete) begin
|
||||
frame_number_snapshot <= frame_number;
|
||||
stream_flags_snapshot <= stream_control;
|
||||
stream_flags_snapshot <= stream_control[2:0]; // PR-G: ignore reserved [5:3]
|
||||
end
|
||||
end
|
||||
|
||||
// --- Status snapshot (ft_clk domain) ---
|
||||
reg [31:0] status_words [0:5];
|
||||
// --- Status snapshot (ft_clk domain) — PR-G: 7 words (was 6) ---
|
||||
reg [31:0] status_words [0:6];
|
||||
|
||||
// Byte counter for write FSM (needs to be wide enough for largest section)
|
||||
reg [15:0] wr_byte_idx;
|
||||
|
||||
// BRAM read address for frame transfer
|
||||
reg [FRAME_ADDR_W-1:0] bram_rd_cell; // PR-F: 15-bit cell index 0..24575
|
||||
reg [RANGE_BIN_BITS-1:0] range_rd_idx; // Range bin index 0..511
|
||||
reg wr_byte_phase; // 0=MSB, 1=LSB for 16-bit values
|
||||
reg [DETECT_BYTE_ADDR_W-1:0] detect_rd_idx; // PR-F: 12-bit byte index 0..3071
|
||||
reg [RANGE_BIN_BITS-1:0] range_rd_idx; // Range section: 0..511
|
||||
// PR-G: nested counters for doppler section so we emit only valid cells
|
||||
// (range 0..511, doppler 0..47) and skip the BRAM padding at doppler 48..63.
|
||||
reg [RANGE_BIN_BITS-1:0] dop_range_idx; // Doppler section outer: 0..511
|
||||
reg [DOPPLER_BIN_BITS-1:0] dop_doppler_idx; // Doppler section inner: 0..47
|
||||
reg wr_byte_phase; // 0=MSB, 1=LSB for 16-bit values
|
||||
// PR-G: nested counters for detect section so we emit only valid bytes
|
||||
// (12 per range, doppler indices 0..47) and skip the 4 padded bytes from
|
||||
// doppler 48..63. detect_rd_addr is composed from these.
|
||||
reg [RANGE_BIN_BITS-1:0] det_range_idx; // 0..511
|
||||
reg [3:0] det_doppler_byte_idx; // 0..11 (= NUM_DOPPLER_BINS*2/8 - 1)
|
||||
|
||||
// ============================================================================
|
||||
// CLOCK-ACTIVITY WATCHDOG (clk domain)
|
||||
@@ -737,17 +756,26 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
range_decim_watchdog_sync_1 <= 1'b0;
|
||||
ddc_cic_fir_overrun_sync_0 <= 1'b0;
|
||||
ddc_cic_fir_overrun_sync_1 <= 1'b0;
|
||||
for (si = 0; si < 6; si = si + 1)
|
||||
// PR-G: 2-tier CFAR telemetry CDC reset
|
||||
alpha_soft_sync_0 <= 8'd0;
|
||||
alpha_soft_sync_1 <= 8'd0;
|
||||
det_thr_soft_sync_0 <= 17'd0;
|
||||
det_thr_soft_sync_1 <= 17'd0;
|
||||
det_count_cand_sync_0 <= 16'd0;
|
||||
det_count_cand_sync_1 <= 16'd0;
|
||||
for (si = 0; si < 7; si = si + 1)
|
||||
status_words[si] <= 32'd0;
|
||||
wr_state <= WR_IDLE;
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
bram_rd_cell <= {FRAME_ADDR_W{1'b0}};
|
||||
range_rd_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
range_rd_addr <= {RANGE_BIN_BITS{1'b0}};
|
||||
detect_rd_idx <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
mag_rd_addr <= {FRAME_ADDR_W{1'b0}};
|
||||
wr_state <= WR_IDLE;
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
dop_range_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
dop_doppler_idx <= {DOPPLER_BIN_BITS{1'b0}};
|
||||
range_rd_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
range_rd_addr <= {RANGE_BIN_BITS{1'b0}};
|
||||
det_range_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
det_doppler_byte_idx <= 4'd0;
|
||||
detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
mag_rd_addr <= {FRAME_ADDR_W{1'b0}};
|
||||
rd_state <= RD_IDLE;
|
||||
rd_byte_cnt <= 2'd0;
|
||||
rd_cmd_complete <= 1'b0;
|
||||
@@ -787,6 +815,14 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
ddc_cic_fir_overrun_sync_0 <= status_ddc_cic_fir_overrun;
|
||||
ddc_cic_fir_overrun_sync_1 <= ddc_cic_fir_overrun_sync_0;
|
||||
|
||||
// PR-G: 2-tier CFAR telemetry CDC (clk → ft_clk for status read)
|
||||
alpha_soft_sync_0 <= status_cfar_alpha_soft;
|
||||
alpha_soft_sync_1 <= alpha_soft_sync_0;
|
||||
det_thr_soft_sync_0 <= status_detect_threshold_soft;
|
||||
det_thr_soft_sync_1 <= det_thr_soft_sync_0;
|
||||
det_count_cand_sync_0 <= status_detect_count_cand;
|
||||
det_count_cand_sync_1 <= det_count_cand_sync_0;
|
||||
|
||||
// Status snapshot on request
|
||||
if (status_req_ft) begin
|
||||
// Word 0: {0xFF, mode[1:0], stream[5:0], threshold[15:0]}
|
||||
@@ -801,7 +837,7 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
status_agc_saturation_count, // [19:12]
|
||||
status_agc_enable, // [11]
|
||||
status_chirps_mismatch, // [10] TX-G mismatch flag
|
||||
8'd0, // [9:2] reserved
|
||||
alpha_soft_sync_1, // [9:2] PR-G: host_cfar_alpha_soft echo (Q4.4)
|
||||
status_range_mode}; // [1:0]
|
||||
// Word 5: {frame_drop_count[31:25], self_test_busy[24],
|
||||
// reserved[23:16], self_test_detail[15:8], reserved[7],
|
||||
@@ -816,6 +852,15 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
ddc_cic_fir_overrun_sync_1, // [6] audit F-1.2
|
||||
range_decim_watchdog_sync_1, // [5] audit F-6.4
|
||||
status_self_test_flags}; // [4:0]
|
||||
// PR-G word 6: {detect_count_cand[15:0], detect_threshold_soft[15:0]}.
|
||||
// detect_threshold_soft is 17-bit; saturate to 16 bits for status (top
|
||||
// bit set → emit 0xFFFF). alpha_soft (8-bit) does not need to be in
|
||||
// the status packet — host wrote it via opcode 0x2D and tracks it
|
||||
// locally; it's CDC'd here for any future readback need but is not
|
||||
// emitted by the FSM today. (Pack into reserved bits if needed in v3.)
|
||||
status_words[6] <= {det_count_cand_sync_1,
|
||||
(det_thr_soft_sync_1[16] ? 16'hFFFF
|
||||
: det_thr_soft_sync_1[15:0])};
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
@@ -899,15 +944,17 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
end
|
||||
// New frame ready for transfer
|
||||
else if (frame_ready_ft && ft_rxf_n) begin
|
||||
wr_state <= WR_FRAME_HDR;
|
||||
wr_byte_idx <= 16'd0;
|
||||
bram_rd_cell <= {FRAME_ADDR_W{1'b0}};
|
||||
range_rd_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
range_rd_addr <= {RANGE_BIN_BITS{1'b0}}; // Pre-load first read addr
|
||||
detect_rd_idx <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
mag_rd_addr <= {FRAME_ADDR_W{1'b0}};
|
||||
wr_byte_phase <= 1'b0;
|
||||
wr_state <= WR_FRAME_HDR;
|
||||
wr_byte_idx <= 16'd0;
|
||||
dop_range_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
dop_doppler_idx <= {DOPPLER_BIN_BITS{1'b0}};
|
||||
range_rd_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
range_rd_addr <= {RANGE_BIN_BITS{1'b0}}; // Pre-load first read addr
|
||||
det_range_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
det_doppler_byte_idx <= 4'd0;
|
||||
detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
mag_rd_addr <= {FRAME_ADDR_W{1'b0}}; // {range=0, doppler=0}
|
||||
wr_byte_phase <= 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
@@ -917,18 +964,21 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
ft_data_oe <= 1'b1;
|
||||
ft_wr_n <= 1'b0;
|
||||
|
||||
case (wr_byte_idx[2:0])
|
||||
3'd0: ft_data_out <= HEADER;
|
||||
3'd1: ft_data_out <= {2'b00, stream_flags_snapshot};
|
||||
3'd2: ft_data_out <= frame_number_snapshot[15:8];
|
||||
3'd3: ft_data_out <= frame_number_snapshot[7:0];
|
||||
3'd4: ft_data_out <= NUM_RANGE_BINS[15:8]; // 512 >> 8 = 2
|
||||
3'd5: ft_data_out <= NUM_RANGE_BINS[7:0]; // 512 & 0xFF = 0
|
||||
3'd6: ft_data_out <= NUM_DOPPLER_BINS[15:8]; // 32 >> 8 = 0
|
||||
3'd7: ft_data_out <= NUM_DOPPLER_BINS[7:0]; // 32 & 0xFF = 32
|
||||
// PR-G: 9-byte header (was 8). Byte 1 = protocol version.
|
||||
case (wr_byte_idx[3:0])
|
||||
4'd0: ft_data_out <= HEADER;
|
||||
4'd1: ft_data_out <= `RP_USB_PROTOCOL_VERSION; // 0x02
|
||||
4'd2: ft_data_out <= {5'b00000, stream_flags_snapshot};
|
||||
4'd3: ft_data_out <= frame_number_snapshot[15:8];
|
||||
4'd4: ft_data_out <= frame_number_snapshot[7:0];
|
||||
4'd5: ft_data_out <= NUM_RANGE_BINS[15:8]; // 512 >> 8 = 2
|
||||
4'd6: ft_data_out <= NUM_RANGE_BINS[7:0]; // 512 & 0xFF = 0
|
||||
4'd7: ft_data_out <= NUM_DOPPLER_BINS[15:8]; // 48 >> 8 = 0
|
||||
4'd8: ft_data_out <= NUM_DOPPLER_BINS[7:0]; // 48 & 0xFF = 48
|
||||
default: ft_data_out <= 8'h00;
|
||||
endcase
|
||||
|
||||
if (wr_byte_idx[2:0] == 3'd7) begin
|
||||
if (wr_byte_idx[3:0] == 4'd8) begin
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
// Decide next section based on stream flags
|
||||
@@ -968,10 +1018,11 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
wr_byte_idx <= wr_byte_idx + 16'd1;
|
||||
|
||||
if (wr_byte_idx == RANGE_SECTION_BYTES[15:0] - 16'd1) begin
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
bram_rd_cell <= {FRAME_ADDR_W{1'b0}};
|
||||
mag_rd_addr <= {FRAME_ADDR_W{1'b0}};
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
dop_range_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
dop_doppler_idx <= {DOPPLER_BIN_BITS{1'b0}};
|
||||
mag_rd_addr <= {FRAME_ADDR_W{1'b0}}; // {range=0, doppler=0}
|
||||
if (stream_flags_snapshot[1])
|
||||
wr_state <= WR_DOPPLER_DATA;
|
||||
else if (stream_flags_snapshot[2])
|
||||
@@ -982,8 +1033,11 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Doppler magnitude: 16384 × 2 = 32768 bytes (mag_only mode) ----
|
||||
// Row-major: range_bin varies slowest, doppler_bin varies fastest.
|
||||
// ---- Doppler magnitude: 512 × 48 × 2 = 49152 bytes ----
|
||||
// PR-G: row-major iteration over valid (range, doppler) cells
|
||||
// only. Skips BRAM padding at doppler 48..63 by jumping to next
|
||||
// range when doppler hits DOP_BIN_LAST. Header field
|
||||
// doppler_bins=48 matches body length exactly.
|
||||
WR_DOPPLER_DATA: begin
|
||||
if (!ft_txe_n) begin
|
||||
ft_data_oe <= 1'b1;
|
||||
@@ -995,17 +1049,28 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
end else begin
|
||||
ft_data_out <= mag_rd_data[7:0];
|
||||
wr_byte_phase <= 1'b0;
|
||||
bram_rd_cell <= bram_rd_cell + {{(FRAME_ADDR_W-1){1'b0}}, 1'b1};
|
||||
mag_rd_addr <= bram_rd_cell + {{(FRAME_ADDR_W-1){1'b0}}, 1'b1};
|
||||
// Pre-load mag_rd_addr 1 cell ahead (BRAM 1-cycle
|
||||
// read latency). Address layout: {range[8:0], doppler[5:0]}.
|
||||
if (dop_doppler_idx == DOP_BIN_LAST) begin
|
||||
dop_doppler_idx <= {DOPPLER_BIN_BITS{1'b0}};
|
||||
dop_range_idx <= dop_range_idx + {{(RANGE_BIN_BITS-1){1'b0}}, 1'b1};
|
||||
mag_rd_addr <= {dop_range_idx + {{(RANGE_BIN_BITS-1){1'b0}}, 1'b1},
|
||||
{DOPPLER_BIN_BITS{1'b0}}};
|
||||
end else begin
|
||||
dop_doppler_idx <= dop_doppler_idx + {{(DOPPLER_BIN_BITS-1){1'b0}}, 1'b1};
|
||||
mag_rd_addr <= {dop_range_idx,
|
||||
dop_doppler_idx + {{(DOPPLER_BIN_BITS-1){1'b0}}, 1'b1}};
|
||||
end
|
||||
end
|
||||
|
||||
wr_byte_idx <= wr_byte_idx + 16'd1;
|
||||
|
||||
if (wr_byte_idx == DOPPLER_MAG_SECTION_BYTES[15:0] - 16'd1) begin
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
detect_rd_idx <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
wr_byte_idx <= 16'd0;
|
||||
wr_byte_phase <= 1'b0;
|
||||
det_range_idx <= {RANGE_BIN_BITS{1'b0}};
|
||||
det_doppler_byte_idx <= 4'd0;
|
||||
detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}};
|
||||
if (stream_flags_snapshot[2])
|
||||
wr_state <= WR_DETECT_DATA;
|
||||
else
|
||||
@@ -1014,16 +1079,28 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Detection flags: 2048 bytes (dense mode) ----
|
||||
// ---- Detection flags: 512 × 12 = 6144 bytes (PR-G, 2-bit dense) ----
|
||||
// PR-G: nested advance through (range 0..511, doppler_byte 0..11).
|
||||
// Skips 4 padded detect bytes per range (doppler 48..63 indices)
|
||||
// so the wire body matches host's expected size of
|
||||
// range_bins × doppler_bins × 2 / 8 = 6144 bytes.
|
||||
WR_DETECT_DATA: begin
|
||||
if (!ft_txe_n) begin
|
||||
ft_data_oe <= 1'b1;
|
||||
ft_wr_n <= 1'b0;
|
||||
|
||||
// 1-byte per cycle (BRAM read latency handled by pre-loading addr)
|
||||
ft_data_out <= detect_rd_data;
|
||||
detect_rd_idx <= detect_rd_idx + {{(DETECT_BYTE_ADDR_W-1){1'b0}}, 1'b1};
|
||||
detect_rd_addr <= detect_rd_idx + {{(DETECT_BYTE_ADDR_W-1){1'b0}}, 1'b1};
|
||||
ft_data_out <= detect_rd_data;
|
||||
if (det_doppler_byte_idx == DET_BYTE_LAST_PER_RANGE) begin
|
||||
det_doppler_byte_idx <= 4'd0;
|
||||
det_range_idx <= det_range_idx + {{(RANGE_BIN_BITS-1){1'b0}}, 1'b1};
|
||||
detect_rd_addr <= {det_range_idx + {{(RANGE_BIN_BITS-1){1'b0}}, 1'b1},
|
||||
4'd0};
|
||||
end else begin
|
||||
det_doppler_byte_idx <= det_doppler_byte_idx + 4'd1;
|
||||
detect_rd_addr <= {det_range_idx,
|
||||
det_doppler_byte_idx + 4'd1};
|
||||
end
|
||||
|
||||
wr_byte_idx <= wr_byte_idx + 16'd1;
|
||||
|
||||
@@ -1044,7 +1121,7 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
end
|
||||
end
|
||||
|
||||
// ---- Status packet: 26 bytes (unchanged from legacy) ----
|
||||
// ---- Status packet: 30 bytes (PR-G v2: 7 × 32-bit words) ----
|
||||
WR_STATUS_SEND: begin
|
||||
if (!ft_txe_n) begin
|
||||
ft_data_oe <= 1'b1;
|
||||
@@ -1076,7 +1153,11 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin
|
||||
5'd22: ft_data_out <= status_words[5][23:16];
|
||||
5'd23: ft_data_out <= status_words[5][15:8];
|
||||
5'd24: ft_data_out <= status_words[5][7:0];
|
||||
5'd25: ft_data_out <= FOOTER;
|
||||
5'd25: ft_data_out <= status_words[6][31:24]; // PR-G
|
||||
5'd26: ft_data_out <= status_words[6][23:16]; // PR-G
|
||||
5'd27: ft_data_out <= status_words[6][15:8]; // PR-G
|
||||
5'd28: ft_data_out <= status_words[6][7:0]; // PR-G
|
||||
5'd29: ft_data_out <= FOOTER;
|
||||
default: ft_data_out <= 8'h00;
|
||||
endcase
|
||||
|
||||
@@ -1160,30 +1241,6 @@ 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
|
||||
|
||||
// ============================================================================
|
||||
// AUDIT-S22: cfar_valid-vs-RMW-busy checker (simulation only)
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user