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:
@@ -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: ;
|
||||
|
||||
@@ -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
@@ -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
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user