Wire self-test results (0x31) to USB status readback path, add fpga_self_test to regression

- usb_data_interface.v: Add 3 self-test status inputs, expand status packet
  from 7 words (header + 5 data + footer) to 8 words (header + 6 data + footer).
  New status_words[5] carries {busy, detail[7:0], flags[4:0]}.
- radar_system_top.v: Wire self_test_flags_latched, self_test_detail_latched,
  self_test_busy to usb_data_interface ports. Add opcode 0x31 as status
  readback alias so host can read self-test results.
- tb_usb_data_interface.v: Add self-test port connections, verify word 5 in
  Group 16, add Group 18 (busy flag + partial failure variant). 81 checks pass.
- run_regression.sh: Add fpga_self_test.v to PROD_RTL lint list and system-
  level compile lists. Add tb_fpga_self_test as Phase 1 unit test.
- 24/24 regression tests pass, lint clean (0 errors, 4 advisory warnings).
This commit is contained in:
Jason
2026-03-20 20:03:11 +02:00
parent eb907de3d1
commit 4985eccbae
6 changed files with 4271 additions and 4194 deletions
+7 -1
View File
@@ -715,7 +715,12 @@ usb_data_interface usb_inst (
.status_short_chirp(host_short_chirp_cycles),
.status_short_listen(host_short_listen_cycles),
.status_chirps_per_elev(host_chirps_per_elev),
.status_range_mode(host_range_mode)
.status_range_mode(host_range_mode),
// Self-test status readback
.status_self_test_flags(self_test_flags_latched),
.status_self_test_detail(self_test_detail_latched),
.status_self_test_busy(self_test_busy)
);
// ============================================================================
@@ -836,6 +841,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
8'h27: host_dc_notch_width <= usb_cmd_value[2: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)
// 0x31: readback handled via status mechanism (latched results)
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
default: ;
+7 -2
View File
@@ -75,6 +75,7 @@ PROD_RTL=(
rx_gain_control.v
cfar_ca.v
mti_canceller.v
fpga_self_test.v
)
# Source-only RTL (not instantiated at top level, but should still be lint-clean)
@@ -386,6 +387,10 @@ run_test "CFAR CA Detector" \
tb/tb_cfar_ca.vvp \
tb/tb_cfar_ca.v cfar_ca.v
run_test "FPGA Self-Test" \
tb/tb_fpga_self_test.vvp \
tb/tb_fpga_self_test.v fpga_self_test.v
echo ""
# ===========================================================================
@@ -436,7 +441,7 @@ if [[ "$QUICK" -eq 0 ]]; then
matched_filter_multi_segment.v matched_filter_processing_chain.v \
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
usb_data_interface.v edge_detector.v radar_mode_controller.v \
rx_gain_control.v cfar_ca.v mti_canceller.v
rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v
# E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset)
run_test "System E2E (tb_system_e2e)" \
@@ -450,7 +455,7 @@ if [[ "$QUICK" -eq 0 ]]; then
matched_filter_multi_segment.v matched_filter_processing_chain.v \
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
usb_data_interface.v edge_detector.v radar_mode_controller.v \
rx_gain_control.v cfar_ca.v mti_canceller.v
rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v
else
echo " (skipped receiver golden + system top + E2E — use without --quick)"
SKIP=$((SKIP + 4))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+63 -7
View File
@@ -74,6 +74,11 @@ module tb_usb_data_interface;
reg [5:0] status_chirps_per_elev;
reg [1:0] status_range_mode;
// Self-test status readback inputs
reg [4:0] status_self_test_flags;
reg [7:0] status_self_test_detail;
reg status_self_test_busy;
// Clock generators (asynchronous)
always #(CLK_PERIOD / 2) clk = ~clk;
always #(FT_CLK_PERIOD / 2) ft601_clk_in = ~ft601_clk_in;
@@ -124,7 +129,12 @@ module tb_usb_data_interface;
.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_range_mode (status_range_mode),
// Self-test status readback
.status_self_test_flags (status_self_test_flags),
.status_self_test_detail(status_self_test_detail),
.status_self_test_busy (status_self_test_busy)
);
// Test bookkeeping
@@ -181,6 +191,9 @@ module tb_usb_data_interface;
status_short_listen = 16'd17450;
status_chirps_per_elev = 6'd32;
status_range_mode = 2'b00;
status_self_test_flags = 5'b00000;
status_self_test_detail = 8'd0;
status_self_test_busy = 1'b0;
repeat (6) @(posedge ft601_clk_in);
reset_n = 1;
// Wait enough cycles for stream_control CDC to propagate
@@ -867,7 +880,7 @@ module tb_usb_data_interface;
//
// TEST GROUP 16: Status Readback (Gap 2)
// Verify that pulsing status_request triggers a 7-word
// Verify that pulsing status_request triggers an 8-word
// status response via the SEND_STATUS state.
//
$display("\n--- Test Group 16: Status Readback (Gap 2) ---");
@@ -885,6 +898,10 @@ module tb_usb_data_interface;
status_short_listen = 16'd17450;
status_chirps_per_elev = 6'd32;
status_range_mode = 2'b10; // Long-range for status test
// Self-test status: all 5 tests passed, detail=0xA5, not busy
status_self_test_flags = 5'b11111;
status_self_test_detail = 8'hA5;
status_self_test_busy = 1'b0;
// Pulse status_request (1 cycle in clk domain toggles status_req_toggle_100m)
@(posedge clk);
@@ -903,14 +920,14 @@ module tb_usb_data_interface;
check(uut.current_state === S_SEND_STATUS,
"Status readback: FSM entered SEND_STATUS");
// The SEND_STATUS state sends 7 words (idx 0-6):
// idx 0: 0xBB header, idx 1-5: status_words[0-4], idx 6: 0x55 footer
// After idx 6 it transitions to WAIT_ACK -> IDLE.
// Since ft601_txe=0, all 7 words should stream without stall.
// The SEND_STATUS state sends 8 words (idx 0-7):
// idx 0: 0xBB header, idx 1-6: status_words[0-5], idx 7: 0x55 footer
// After idx 7 it transitions to WAIT_ACK -> IDLE.
// Since ft601_txe=0, all 8 words should stream without stall.
wait_for_state(S_IDLE, 100);
#1;
check(uut.current_state === S_IDLE,
"Status readback: returned to IDLE after 7-word response");
"Status readback: returned to IDLE after 8-word response");
// Verify the status snapshot was captured correctly.
// status_words[0] = {0xFF, 3'b000, mode[1:0], 5'b0, stream_ctrl[2:0], cfar_threshold[15:0]}
@@ -943,6 +960,10 @@ module tb_usb_data_interface;
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
check(uut.status_words[4] === {30'd0, 2'b10},
"Status readback: word 4 = range_mode=2'b10");
// status_words[5] = {7'd0, busy, 8'd0, detail[7:0], 3'd0, flags[4:0]}
// = {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111}
check(uut.status_words[5] === {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111},
"Status readback: word 5 = self-test {busy=0, detail=A5, flags=1F}");
//
// TEST GROUP 17: New Chirp Timing Opcodes (Gap 2)
@@ -999,6 +1020,41 @@ module tb_usb_data_interface;
check(cmd_opcode === 8'hFF,
"Chirp opcode: 0xFF (status request)");
//
// TEST GROUP 18: Self-Test Readback Variants
// Verify self-test busy flag, partial failures, and
// alternate status word 5 values.
//
$display("\n--- Test Group 18: Self-Test Readback Variants ---");
apply_reset;
ft601_txe = 0;
// Scenario A: Self-test busy, partial failure, different detail
status_self_test_flags = 5'b10110; // T0 fail, T3 fail
status_self_test_detail = 8'h42;
status_self_test_busy = 1'b1;
// Trigger status readback
@(posedge clk);
status_request = 1;
@(posedge clk);
status_request = 0;
repeat (8) @(posedge ft601_clk_in); #1;
wait_for_state(S_SEND_STATUS, 20);
#1;
check(uut.current_state === S_SEND_STATUS,
"Self-test readback A: FSM entered SEND_STATUS");
wait_for_state(S_IDLE, 100);
#1;
check(uut.current_state === S_IDLE,
"Self-test readback A: returned to IDLE");
// Verify word 5: {7'd0, busy=1, 8'd0, detail=0x42, 3'd0, flags=5'b10110}
check(uut.status_words[5] === {7'd0, 1'b1, 8'd0, 8'h42, 3'd0, 5'b10110},
"Self-test readback A: word 5 = {busy=1, detail=42, flags=16}");
//
// Summary
//
+16 -6
View File
@@ -72,7 +72,12 @@ module usb_data_interface (
input wire [15:0] status_short_chirp, // Current short chirp cycles
input wire [15:0] status_short_listen, // Current short listen cycles
input wire [5:0] status_chirps_per_elev, // Current chirps per elevation
input wire [1:0] status_range_mode // Fix 7: Current range mode (0x20)
input wire [1:0] status_range_mode, // Fix 7: Current range mode (0x20)
// Self-test status readback (opcode 0x31 / included in 0xFF status packet)
input wire [4:0] status_self_test_flags, // Per-test PASS(1)/FAIL(0) latched
input wire [7:0] status_self_test_detail, // Diagnostic detail byte latched
input wire status_self_test_busy // Self-test FSM still running
);
// USB packet structure (same as before)
@@ -210,7 +215,7 @@ wire status_req_ft601 = status_req_sync[1] ^ status_req_sync_prev;
// Status snapshot: captured in ft601_clk domain when status request arrives.
// The clk_100m-domain status inputs are stable for many cycles after the
// command decode, so sampling them a few ft601_clk cycles later is safe.
reg [31:0] status_words [0:4]; // 5 status words
reg [31:0] status_words [0:5]; // 6 status words (word 5 = self-test)
reg [2:0] status_word_idx;
wire range_valid_ft;
@@ -265,6 +270,10 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
// Word 4: Fix 7 range_mode in bits [1:0], rest reserved
status_words[4] <= {30'd0, status_range_mode};
// Word 5: Self-test results {reserved[6:0], busy, reserved[7:0], detail[7:0], reserved[2:0], flags[4:0]}
status_words[5] <= {7'd0, status_self_test_busy,
8'd0, status_self_test_detail,
3'd0, status_self_test_flags};
end
// Delayed version of sync[1] for edge detection
@@ -524,8 +533,8 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
end
end
// Gap 2: Status readback send 5 x 32-bit status words
// Format: HEADER, status_words[0..4], FOOTER
// Gap 2: Status readback send 6 x 32-bit status words
// Format: HEADER, status_words[0..5], FOOTER
SEND_STATUS: begin
if (!ft601_txe) begin
ft601_data_oe <= 1;
@@ -541,7 +550,8 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
3'd3: ft601_data_out <= status_words[2];
3'd4: ft601_data_out <= status_words[3];
3'd5: ft601_data_out <= status_words[4];
3'd6: begin
3'd6: ft601_data_out <= status_words[5];
3'd7: begin
// Send status footer
ft601_data_out <= {24'b0, FOOTER};
ft601_be <= 4'b0001;
@@ -549,7 +559,7 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
default: ;
endcase
ft601_wr_n <= 0;
if (status_word_idx == 3'd6) begin
if (status_word_idx == 3'd7) begin
status_word_idx <= 3'd0;
current_state <= WAIT_ACK;
end else begin