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_chirp(host_short_chirp_cycles),
|
||||||
.status_short_listen(host_short_listen_cycles),
|
.status_short_listen(host_short_listen_cycles),
|
||||||
.status_chirps_per_elev(host_chirps_per_elev),
|
.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];
|
8'h27: host_dc_notch_width <= usb_cmd_value[2:0];
|
||||||
// Board bring-up self-test opcodes
|
// Board bring-up self-test opcodes
|
||||||
8'h30: host_self_test_trigger <= 1'b1; // Trigger self-test
|
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)
|
// 0x31: readback handled via status mechanism (latched results)
|
||||||
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
|
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
|
||||||
default: ;
|
default: ;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ PROD_RTL=(
|
|||||||
rx_gain_control.v
|
rx_gain_control.v
|
||||||
cfar_ca.v
|
cfar_ca.v
|
||||||
mti_canceller.v
|
mti_canceller.v
|
||||||
|
fpga_self_test.v
|
||||||
)
|
)
|
||||||
|
|
||||||
# Source-only RTL (not instantiated at top level, but should still be lint-clean)
|
# 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.vvp \
|
||||||
tb/tb_cfar_ca.v cfar_ca.v
|
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 ""
|
echo ""
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
@@ -436,7 +441,7 @@ if [[ "$QUICK" -eq 0 ]]; then
|
|||||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.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 \
|
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)
|
# E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset)
|
||||||
run_test "System E2E (tb_system_e2e)" \
|
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 \
|
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.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 \
|
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
|
else
|
||||||
echo " (skipped receiver golden + system top + E2E — use without --quick)"
|
echo " (skipped receiver golden + system top + E2E — use without --quick)"
|
||||||
SKIP=$((SKIP + 4))
|
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 [5:0] status_chirps_per_elev;
|
||||||
reg [1:0] status_range_mode;
|
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) ────────────────────────
|
// ── Clock generators (asynchronous) ────────────────────────
|
||||||
always #(CLK_PERIOD / 2) clk = ~clk;
|
always #(CLK_PERIOD / 2) clk = ~clk;
|
||||||
always #(FT_CLK_PERIOD / 2) ft601_clk_in = ~ft601_clk_in;
|
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_chirp (status_short_chirp),
|
||||||
.status_short_listen (status_short_listen),
|
.status_short_listen (status_short_listen),
|
||||||
.status_chirps_per_elev(status_chirps_per_elev),
|
.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 ───────────────────────────────────────
|
// ── Test bookkeeping ───────────────────────────────────────
|
||||||
@@ -181,6 +191,9 @@ module tb_usb_data_interface;
|
|||||||
status_short_listen = 16'd17450;
|
status_short_listen = 16'd17450;
|
||||||
status_chirps_per_elev = 6'd32;
|
status_chirps_per_elev = 6'd32;
|
||||||
status_range_mode = 2'b00;
|
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);
|
repeat (6) @(posedge ft601_clk_in);
|
||||||
reset_n = 1;
|
reset_n = 1;
|
||||||
// Wait enough cycles for stream_control CDC to propagate
|
// 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)
|
// 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.
|
// status response via the SEND_STATUS state.
|
||||||
// ════════════════════════════════════════════════════════
|
// ════════════════════════════════════════════════════════
|
||||||
$display("\n--- Test Group 16: Status Readback (Gap 2) ---");
|
$display("\n--- Test Group 16: Status Readback (Gap 2) ---");
|
||||||
@@ -885,6 +898,10 @@ module tb_usb_data_interface;
|
|||||||
status_short_listen = 16'd17450;
|
status_short_listen = 16'd17450;
|
||||||
status_chirps_per_elev = 6'd32;
|
status_chirps_per_elev = 6'd32;
|
||||||
status_range_mode = 2'b10; // Long-range for status test
|
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)
|
// Pulse status_request (1 cycle in clk domain — toggles status_req_toggle_100m)
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
@@ -903,14 +920,14 @@ module tb_usb_data_interface;
|
|||||||
check(uut.current_state === S_SEND_STATUS,
|
check(uut.current_state === S_SEND_STATUS,
|
||||||
"Status readback: FSM entered SEND_STATUS");
|
"Status readback: FSM entered SEND_STATUS");
|
||||||
|
|
||||||
// The SEND_STATUS state sends 7 words (idx 0-6):
|
// The SEND_STATUS state sends 8 words (idx 0-7):
|
||||||
// idx 0: 0xBB header, idx 1-5: status_words[0-4], idx 6: 0x55 footer
|
// idx 0: 0xBB header, idx 1-6: status_words[0-5], idx 7: 0x55 footer
|
||||||
// After idx 6 it transitions to WAIT_ACK -> IDLE.
|
// After idx 7 it transitions to WAIT_ACK -> IDLE.
|
||||||
// Since ft601_txe=0, all 7 words should stream without stall.
|
// Since ft601_txe=0, all 8 words should stream without stall.
|
||||||
wait_for_state(S_IDLE, 100);
|
wait_for_state(S_IDLE, 100);
|
||||||
#1;
|
#1;
|
||||||
check(uut.current_state === S_IDLE,
|
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.
|
// 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]}
|
// 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}");
|
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
|
||||||
check(uut.status_words[4] === {30'd0, 2'b10},
|
check(uut.status_words[4] === {30'd0, 2'b10},
|
||||||
"Status readback: word 4 = range_mode=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)
|
// TEST GROUP 17: New Chirp Timing Opcodes (Gap 2)
|
||||||
@@ -999,6 +1020,41 @@ module tb_usb_data_interface;
|
|||||||
check(cmd_opcode === 8'hFF,
|
check(cmd_opcode === 8'hFF,
|
||||||
"Chirp opcode: 0xFF (status request)");
|
"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
|
// 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_chirp, // Current short chirp cycles
|
||||||
input wire [15:0] status_short_listen, // Current short listen 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 [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)
|
// 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.
|
// Status snapshot: captured in ft601_clk domain when status request arrives.
|
||||||
// The clk_100m-domain status inputs are stable for many cycles after the
|
// 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.
|
// 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;
|
reg [2:0] status_word_idx;
|
||||||
|
|
||||||
wire range_valid_ft;
|
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};
|
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||||
// Word 4: Fix 7 — range_mode in bits [1:0], rest reserved
|
// Word 4: Fix 7 — range_mode in bits [1:0], rest reserved
|
||||||
status_words[4] <= {30'd0, status_range_mode};
|
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
|
end
|
||||||
|
|
||||||
// Delayed version of sync[1] for edge detection
|
// 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
|
||||||
end
|
end
|
||||||
|
|
||||||
// Gap 2: Status readback — send 5 x 32-bit status words
|
// Gap 2: Status readback — send 6 x 32-bit status words
|
||||||
// Format: HEADER, status_words[0..4], FOOTER
|
// Format: HEADER, status_words[0..5], FOOTER
|
||||||
SEND_STATUS: begin
|
SEND_STATUS: begin
|
||||||
if (!ft601_txe) begin
|
if (!ft601_txe) begin
|
||||||
ft601_data_oe <= 1;
|
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'd3: ft601_data_out <= status_words[2];
|
||||||
3'd4: ft601_data_out <= status_words[3];
|
3'd4: ft601_data_out <= status_words[3];
|
||||||
3'd5: ft601_data_out <= status_words[4];
|
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
|
// Send status footer
|
||||||
ft601_data_out <= {24'b0, FOOTER};
|
ft601_data_out <= {24'b0, FOOTER};
|
||||||
ft601_be <= 4'b0001;
|
ft601_be <= 4'b0001;
|
||||||
@@ -549,7 +559,7 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
|||||||
default: ;
|
default: ;
|
||||||
endcase
|
endcase
|
||||||
ft601_wr_n <= 0;
|
ft601_wr_n <= 0;
|
||||||
if (status_word_idx == 3'd6) begin
|
if (status_word_idx == 3'd7) begin
|
||||||
status_word_idx <= 3'd0;
|
status_word_idx <= 3'd0;
|
||||||
current_state <= WAIT_ACK;
|
current_state <= WAIT_ACK;
|
||||||
end else begin
|
end else begin
|
||||||
|
|||||||
Reference in New Issue
Block a user