feat(fpga): make FT2232H default USB interface, rewrite FT601 write FSM, add clock-loss watchdog
- Set USB_MODE default to 1 (FT2232H) in radar_system_top.v; 200T build overrides to USB_MODE=0 via build_200t.tcl generic property - Rewrite FT601 write FSM: 4-state architecture with 3-word packed data, pending-flag gating, and frame sync counter - Add FT2232H read FSM rd_cmd_complete flag, stream field zeroing, and range_data_ready 1-cycle pipeline delay in both USB modules - Implement clock-loss watchdog: ft_heartbeat toggle + 16-bit timeout counter drives ft_clk_lost, feeding ft_effective_reset_n via 2-stage ASYNC_REG synchronizer chain - Fix sample_counter reset literal width (11'd0 -> 12'd0) - Add FT2232H I/O timing constraints to 50T XDC; fix dac_clk comments - Document vestigial ft601_txe_n/rxf_n ports (needed for 200T XDC) - Tie off AGC ports on TE0713 dev wrapper - Rewrite tb_usb_data_interface.v for new 4-state FSM (89 checks) - Add USB_MODE=1 regression runs; remove dead CHECK 5/6 loop - Update diag_log.h USB interface comment
This commit is contained in:
@@ -430,7 +430,13 @@ end
|
||||
// DUT INSTANTIATION
|
||||
// ============================================================================
|
||||
|
||||
radar_system_top dut (
|
||||
radar_system_top #(
|
||||
`ifdef USB_MODE_1
|
||||
.USB_MODE(1) // FT2232H interface (production 50T board)
|
||||
`else
|
||||
.USB_MODE(0) // FT601 interface (200T dev board)
|
||||
`endif
|
||||
) dut (
|
||||
// System Clocks
|
||||
.clk_100m(clk_100m),
|
||||
.clk_120m_dac(clk_120m_dac),
|
||||
@@ -619,7 +625,11 @@ initial begin
|
||||
// Optional: dump specific signals for debugging
|
||||
$dumpvars(1, dut.tx_inst);
|
||||
$dumpvars(1, dut.rx_inst);
|
||||
`ifdef USB_MODE_1
|
||||
$dumpvars(1, dut.gen_ft2232h.usb_inst);
|
||||
`else
|
||||
$dumpvars(1, dut.gen_ft601.usb_inst);
|
||||
`endif
|
||||
end
|
||||
|
||||
endmodule
|
||||
|
||||
@@ -382,7 +382,13 @@ end
|
||||
// ============================================================================
|
||||
// DUT INSTANTIATION
|
||||
// ============================================================================
|
||||
radar_system_top dut (
|
||||
radar_system_top #(
|
||||
`ifdef USB_MODE_1
|
||||
.USB_MODE(1) // FT2232H interface (production 50T board)
|
||||
`else
|
||||
.USB_MODE(0) // FT601 interface (200T dev board)
|
||||
`endif
|
||||
) dut (
|
||||
.clk_100m(clk_100m),
|
||||
.clk_120m_dac(clk_120m_dac),
|
||||
.ft601_clk_in(ft601_clk_in),
|
||||
@@ -554,10 +560,10 @@ initial begin
|
||||
do_reset;
|
||||
|
||||
// CRITICAL: Configure stream control to range-only BEFORE any chirps
|
||||
// fire. The USB write FSM blocks on doppler_valid_ft if doppler stream
|
||||
// is enabled but no Doppler data arrives (needs 32 chirps/frame).
|
||||
// Without this, the write FSM deadlocks and the read FSM can never
|
||||
// activate (it requires write FSM == IDLE).
|
||||
// fire. The USB write FSM gates on pending flags: if doppler stream is
|
||||
// enabled but no Doppler data arrives (needs 32 chirps/frame), the FSM
|
||||
// stays in IDLE waiting for doppler_data_pending. With the write FSM
|
||||
// not in IDLE, the read FSM cannot activate (bus arbitration rule).
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001); // stream_control = range only
|
||||
// Wait for stream_control CDC to propagate (2-stage sync in ft601_clk)
|
||||
// Must be long enough that stream_ctrl_sync_1 is updated before any
|
||||
@@ -778,7 +784,7 @@ initial begin
|
||||
|
||||
// Restore defaults for subsequent tests
|
||||
bfm_send_cmd(8'h01, 8'h00, 16'h0001); // mode = auto-scan
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001); // keep range-only (prevents write FSM deadlock)
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001); // keep range-only (TB lacks 32-chirp doppler data)
|
||||
bfm_send_cmd(8'h10, 8'h00, 16'd3000); // restore long chirp cycles
|
||||
|
||||
$display("");
|
||||
@@ -913,7 +919,7 @@ initial begin
|
||||
// Need to re-send configuration since reset clears all registers
|
||||
stm32_mixers_enable = 1;
|
||||
ft601_txe = 0;
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001); // stream_control = range only (prevent deadlock)
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001); // stream_control = range only (TB lacks doppler data)
|
||||
#500; // Wait for stream_control CDC
|
||||
bfm_send_cmd(8'h01, 8'h00, 16'h0001); // auto-scan
|
||||
bfm_send_cmd(8'h10, 8'h00, 16'd100); // short timing
|
||||
@@ -947,7 +953,7 @@ initial begin
|
||||
check(dut.host_stream_control == 3'b000,
|
||||
"G10.2: All streams disabled (stream_control = 3'b000)");
|
||||
|
||||
// G10.3: Re-enable range only (keep range-only to prevent write FSM deadlock)
|
||||
// G10.3: Re-enable range only (TB uses range-only — no doppler processing)
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001); // stream_control = 3'b001
|
||||
check(dut.host_stream_control == 3'b001,
|
||||
"G10.3: Range stream re-enabled (stream_control = 3'b001)");
|
||||
|
||||
@@ -6,15 +6,11 @@ module tb_usb_data_interface;
|
||||
localparam CLK_PERIOD = 10.0; // 100 MHz main clock
|
||||
localparam FT_CLK_PERIOD = 10.0; // 100 MHz FT601 clock (asynchronous)
|
||||
|
||||
// State definitions (mirror the DUT)
|
||||
localparam [2:0] S_IDLE = 3'd0,
|
||||
S_SEND_HEADER = 3'd1,
|
||||
S_SEND_RANGE = 3'd2,
|
||||
S_SEND_DOPPLER = 3'd3,
|
||||
S_SEND_DETECT = 3'd4,
|
||||
S_SEND_FOOTER = 3'd5,
|
||||
S_WAIT_ACK = 3'd6,
|
||||
S_SEND_STATUS = 3'd7; // Gap 2: status readback
|
||||
// State definitions (mirror the DUT — 4-state packed-word FSM)
|
||||
localparam [3:0] S_IDLE = 4'd0,
|
||||
S_SEND_DATA_WORD = 4'd1,
|
||||
S_SEND_STATUS = 4'd2,
|
||||
S_WAIT_ACK = 4'd3;
|
||||
|
||||
// ── Signals ────────────────────────────────────────────────
|
||||
reg clk;
|
||||
@@ -219,9 +215,9 @@ module tb_usb_data_interface;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ── Helper: wait for DUT to reach a specific state ─────────
|
||||
// ── Helper: wait for DUT to reach a specific write FSM state ──
|
||||
task wait_for_state;
|
||||
input [2:0] target;
|
||||
input [3:0] target;
|
||||
input integer max_cyc;
|
||||
integer cnt;
|
||||
begin
|
||||
@@ -280,7 +276,7 @@ module tb_usb_data_interface;
|
||||
// Set data_pending flags directly via hierarchical access.
|
||||
// This is the standard TB technique for internal state setup —
|
||||
// bypasses the CDC path for immediate, reliable flag setting.
|
||||
// Call BEFORE assert_range_valid in tests that need SEND_DOPPLER/DETECT.
|
||||
// Call BEFORE assert_range_valid in tests that need doppler/cfar data.
|
||||
task preload_pending_data;
|
||||
begin
|
||||
@(posedge ft601_clk_in);
|
||||
@@ -354,24 +350,26 @@ module tb_usb_data_interface;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Drive a complete packet through the FSM by sequentially providing
|
||||
// range, doppler (4x), and cfar valid pulses.
|
||||
// Drive a complete data packet through the new 3-word packed FSM.
|
||||
// Pre-loads pending flags, triggers range_valid, and waits for IDLE.
|
||||
// With the new FSM, all data is pre-packed in IDLE then sent as 3 words.
|
||||
task drive_full_packet;
|
||||
input [31:0] rng;
|
||||
input [15:0] dr;
|
||||
input [15:0] di;
|
||||
input det;
|
||||
begin
|
||||
// Pre-load pending flags so FSM enters doppler/cfar states
|
||||
// Set doppler/cfar captured values via CDC inputs
|
||||
@(posedge clk);
|
||||
doppler_real = dr;
|
||||
doppler_imag = di;
|
||||
cfar_detection = det;
|
||||
@(posedge clk);
|
||||
// Pre-load pending flags so FSM includes doppler/cfar in packet
|
||||
preload_pending_data;
|
||||
// Trigger the packet
|
||||
assert_range_valid(rng);
|
||||
wait_for_state(S_SEND_DOPPLER, 100);
|
||||
pulse_doppler_once(dr, di);
|
||||
pulse_doppler_once(dr, di);
|
||||
pulse_doppler_once(dr, di);
|
||||
pulse_doppler_once(dr, di);
|
||||
wait_for_state(S_SEND_DETECT, 100);
|
||||
pulse_cfar_once(det);
|
||||
// Wait for complete packet cycle: IDLE → SEND_DATA_WORD(×3) → WAIT_ACK → IDLE
|
||||
wait_for_state(S_IDLE, 100);
|
||||
end
|
||||
endtask
|
||||
@@ -414,101 +412,138 @@ module tb_usb_data_interface;
|
||||
"ft601_siwu_n=1 after reset");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 2: Range data packet
|
||||
// TEST GROUP 2: Data packet word packing
|
||||
//
|
||||
// Use backpressure to freeze the FSM at specific states
|
||||
// so we can reliably sample outputs.
|
||||
// New FSM packs 11-byte data into 3 × 32-bit words:
|
||||
// Word 0: {HEADER, range[31:24], range[23:16], range[15:8]}
|
||||
// Word 1: {range[7:0], dop_re_hi, dop_re_lo, dop_im_hi}
|
||||
// Word 2: {dop_im_lo, detection, FOOTER, 0x00} BE=1110
|
||||
//
|
||||
// The DUT uses range_data_ready (1-cycle delayed range_valid_ft)
|
||||
// to trigger packing. Doppler/CFAR _cap registers must be
|
||||
// pre-loaded via hierarchical access because no valid pulse is
|
||||
// given in this test (we only want to verify packing, not CDC).
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 2: Range Data Packet ---");
|
||||
$display("\n--- Test Group 2: Data Packet Word Packing ---");
|
||||
apply_reset;
|
||||
ft601_txe = 1; // Stall so we can inspect packed words
|
||||
|
||||
// Stall at SEND_HEADER so we can verify first range word later
|
||||
ft601_txe = 1;
|
||||
// Set known doppler/cfar values on clk-domain inputs
|
||||
@(posedge clk);
|
||||
doppler_real = 16'hABCD;
|
||||
doppler_imag = 16'hEF01;
|
||||
cfar_detection = 1'b1;
|
||||
@(posedge clk);
|
||||
|
||||
// Pre-load pending flags AND captured-data registers directly.
|
||||
// No doppler/cfar valid pulses are given, so the CDC capture path
|
||||
// never fires — we must set the _cap registers via hierarchical
|
||||
// access for the word-packing checks to be meaningful.
|
||||
preload_pending_data;
|
||||
@(posedge ft601_clk_in);
|
||||
uut.doppler_real_cap = 16'hABCD;
|
||||
uut.doppler_imag_cap = 16'hEF01;
|
||||
uut.cfar_detection_cap = 1'b1;
|
||||
@(posedge ft601_clk_in);
|
||||
|
||||
assert_range_valid(32'hDEAD_BEEF);
|
||||
wait_for_state(S_SEND_HEADER, 50);
|
||||
repeat (2) @(posedge ft601_clk_in); #1;
|
||||
check(uut.current_state === S_SEND_HEADER,
|
||||
"Stalled in SEND_HEADER (backpressure)");
|
||||
|
||||
// Release: FSM drives header then moves to SEND_RANGE_DATA
|
||||
// FSM should be in SEND_DATA_WORD, stalled on ft601_txe=1
|
||||
wait_for_state(S_SEND_DATA_WORD, 50);
|
||||
repeat (2) @(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.current_state === S_SEND_DATA_WORD,
|
||||
"Stalled in SEND_DATA_WORD (backpressure)");
|
||||
|
||||
// Verify pre-packed words
|
||||
// range_profile = 0xDEAD_BEEF → range[31:24]=0xDE, [23:16]=0xAD, [15:8]=0xBE, [7:0]=0xEF
|
||||
// Word 0: {0xAA, 0xDE, 0xAD, 0xBE}
|
||||
check(uut.data_pkt_word0 === {8'hAA, 8'hDE, 8'hAD, 8'hBE},
|
||||
"Word 0: {HEADER=AA, range[31:8]}");
|
||||
// Word 1: {0xEF, 0xAB, 0xCD, 0xEF}
|
||||
check(uut.data_pkt_word1 === {8'hEF, 8'hAB, 8'hCD, 8'hEF},
|
||||
"Word 1: {range[7:0], dop_re, dop_im_hi}");
|
||||
// Word 2: {0x01, detection_byte, 0x55, 0x00}
|
||||
// detection_byte bit 7 = frame_start (sample_counter==0 → 1), bit 0 = cfar=1
|
||||
// so detection_byte = 8'b1000_0001 = 8'h81
|
||||
check(uut.data_pkt_word2 === {8'h01, 8'h81, 8'h55, 8'h00},
|
||||
"Word 2: {dop_im_lo, det=81, FOOTER=55, pad=00}");
|
||||
check(uut.data_pkt_be2 === 4'b1110,
|
||||
"Word 2 BE=1110 (3 valid bytes + 1 pad)");
|
||||
|
||||
// Release backpressure and verify word 0 appears on bus.
|
||||
// On the first posedge with !ft601_txe the FSM drives word 0 and
|
||||
// advances data_word_idx 0→1 via NBA. After #1 the NBA has
|
||||
// resolved, so we see idx=1 and ft601_data_out=word0.
|
||||
ft601_txe = 0;
|
||||
@(posedge ft601_clk_in); #1;
|
||||
// Now the FSM registered the header output and will transition
|
||||
// At the NEXT posedge the state becomes SEND_RANGE_DATA
|
||||
@(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.current_state === S_SEND_RANGE,
|
||||
"Entered SEND_RANGE_DATA after header");
|
||||
|
||||
// The first range word should be on the data bus (byte_counter=0 just
|
||||
// drove range_profile_cap, byte_counter incremented to 1)
|
||||
check(uut.ft601_data_out === 32'hDEAD_BEEF || uut.byte_counter <= 8'd1,
|
||||
"Range data word 0 driven (range_profile_cap)");
|
||||
|
||||
check(uut.ft601_data_out === {8'hAA, 8'hDE, 8'hAD, 8'hBE},
|
||||
"Word 0 driven on data bus after backpressure release");
|
||||
check(ft601_wr_n === 1'b0,
|
||||
"Write strobe active during range data");
|
||||
|
||||
"Write strobe active during SEND_DATA_WORD");
|
||||
check(ft601_be === 4'b1111,
|
||||
"Byte enable=1111 for range data");
|
||||
"Byte enable=1111 for word 0");
|
||||
check(uut.ft601_data_oe === 1'b1,
|
||||
"Data bus output enabled during SEND_DATA_WORD");
|
||||
|
||||
// Wait for all 4 range words to complete
|
||||
wait_for_state(S_SEND_DOPPLER, 50);
|
||||
#1;
|
||||
check(uut.current_state === S_SEND_DOPPLER,
|
||||
"Advanced to SEND_DOPPLER_DATA after 4 range words");
|
||||
// Next posedge: FSM drives word 1, advances idx 1→2.
|
||||
// After NBA: idx=2, ft601_data_out=word1.
|
||||
@(posedge ft601_clk_in); #1;
|
||||
check(uut.data_word_idx === 2'd2,
|
||||
"data_word_idx advanced past word 1 (now 2)");
|
||||
check(uut.ft601_data_out === {8'hEF, 8'hAB, 8'hCD, 8'hEF},
|
||||
"Word 1 driven on data bus");
|
||||
check(ft601_be === 4'b1111,
|
||||
"Byte enable=1111 for word 1");
|
||||
|
||||
// Next posedge: FSM drives word 2, idx resets 2→0,
|
||||
// and current_state transitions to WAIT_ACK.
|
||||
@(posedge ft601_clk_in); #1;
|
||||
check(uut.current_state === S_WAIT_ACK,
|
||||
"Transitioned to WAIT_ACK after 3 data words");
|
||||
check(uut.ft601_data_out === {8'h01, 8'h81, 8'h55, 8'h00},
|
||||
"Word 2 driven on data bus");
|
||||
check(ft601_be === 4'b1110,
|
||||
"Byte enable=1110 for word 2 (last byte is pad)");
|
||||
|
||||
// Then back to IDLE
|
||||
@(posedge ft601_clk_in); #1;
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Returned to IDLE after WAIT_ACK");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 3: Header verification (stall to observe)
|
||||
// TEST GROUP 3: Header and footer verification
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 3: Header Verification ---");
|
||||
$display("\n--- Test Group 3: Header and Footer Verification ---");
|
||||
apply_reset;
|
||||
ft601_txe = 1; // Stall at SEND_HEADER
|
||||
ft601_txe = 1; // Stall to inspect
|
||||
|
||||
@(posedge clk);
|
||||
range_profile = 32'hCAFE_BABE;
|
||||
range_valid = 1;
|
||||
repeat (4) @(posedge ft601_clk_in);
|
||||
doppler_real = 16'h0000;
|
||||
doppler_imag = 16'h0000;
|
||||
cfar_detection = 1'b0;
|
||||
@(posedge clk);
|
||||
range_valid = 0;
|
||||
repeat (3) @(posedge ft601_clk_in);
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'hCAFE_BABE);
|
||||
|
||||
wait_for_state(S_SEND_HEADER, 50);
|
||||
wait_for_state(S_SEND_DATA_WORD, 50);
|
||||
repeat (2) @(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.current_state === S_SEND_HEADER,
|
||||
"Stalled in SEND_HEADER with backpressure");
|
||||
|
||||
// Release backpressure - header will be latched at next posedge
|
||||
ft601_txe = 0;
|
||||
@(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.ft601_data_out[7:0] === 8'hAA,
|
||||
"Header byte 0xAA on data bus");
|
||||
check(ft601_be === 4'b0001,
|
||||
"Byte enable=0001 for header (lower byte only)");
|
||||
check(ft601_wr_n === 1'b0,
|
||||
"Write strobe active during header");
|
||||
check(uut.ft601_data_oe === 1'b1,
|
||||
"Data bus output enabled during header");
|
||||
// Header is in byte 3 (MSB) of word 0
|
||||
check(uut.data_pkt_word0[31:24] === 8'hAA,
|
||||
"Header byte 0xAA in word 0 MSB");
|
||||
// Footer is in byte 1 (bits [15:8]) of word 2
|
||||
check(uut.data_pkt_word2[15:8] === 8'h55,
|
||||
"Footer byte 0x55 in word 2");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 4: Doppler data verification
|
||||
// TEST GROUP 4: Doppler data capture verification
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 4: Doppler Data Verification ---");
|
||||
$display("\n--- Test Group 4: Doppler Data Capture ---");
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
|
||||
// Preload only doppler pending (not cfar) so the FSM sends
|
||||
// doppler data. After doppler, SEND_DETECT sees cfar_data_pending=0
|
||||
// and skips to SEND_FOOTER, then WAIT_ACK, then IDLE.
|
||||
preload_doppler_pending;
|
||||
assert_range_valid(32'h0000_0001);
|
||||
wait_for_state(S_SEND_DOPPLER, 100);
|
||||
#1;
|
||||
check(uut.current_state === S_SEND_DOPPLER,
|
||||
"Reached SEND_DOPPLER_DATA");
|
||||
|
||||
// Provide doppler data via valid pulse (updates captured values)
|
||||
@(posedge clk);
|
||||
doppler_real = 16'hAAAA;
|
||||
@@ -524,110 +559,70 @@ module tb_usb_data_interface;
|
||||
check(uut.doppler_imag_cap === 16'h5555,
|
||||
"doppler_imag captured correctly");
|
||||
|
||||
// The FSM has doppler_data_pending set and sends 4 bytes, then
|
||||
// transitions past SEND_DETECT (cfar_data_pending=0) to IDLE.
|
||||
// Drive a packet with pending doppler + cfar (both needed for gating
|
||||
// since all streams are enabled after reset/apply_reset).
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'h0000_0001);
|
||||
wait_for_state(S_IDLE, 100);
|
||||
#1;
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Doppler done, packet completed");
|
||||
"Packet completed with doppler data");
|
||||
check(uut.doppler_data_pending === 1'b0,
|
||||
"doppler_data_pending cleared after packet");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 5: CFAR detection data
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 5: CFAR Detection Data ---");
|
||||
// Start a new packet with both doppler and cfar pending to verify
|
||||
// cfar data is properly sent in SEND_DETECTION_DATA.
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'h0000_0002);
|
||||
// FSM races through: HEADER -> RANGE -> DOPPLER -> DETECT -> FOOTER -> IDLE
|
||||
// All pending flags consumed proves SEND_DETECT was entered.
|
||||
wait_for_state(S_IDLE, 200);
|
||||
#1;
|
||||
check(uut.cfar_data_pending === 1'b0,
|
||||
"Starting in SEND_DETECTION_DATA");
|
||||
|
||||
// Verify the full packet completed with cfar data consumed
|
||||
"cfar_data_pending cleared after packet");
|
||||
check(uut.current_state === S_IDLE &&
|
||||
uut.doppler_data_pending === 1'b0 &&
|
||||
uut.cfar_data_pending === 1'b0,
|
||||
"CFAR detection sent, FSM advanced past SEND_DETECTION_DATA");
|
||||
"CFAR detection sent, all pending flags cleared");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 6: Footer check
|
||||
//
|
||||
// Strategy: drive packet with ft601_txe=0 all the way through.
|
||||
// The SEND_FOOTER state is only active for 1 cycle, but we can
|
||||
// poll the state machine at each ft601_clk_in edge to observe
|
||||
// it. We use a monitor-style approach: run the packet and
|
||||
// capture what ft601_data_out contains when we see SEND_FOOTER.
|
||||
// TEST GROUP 6: Footer retained after packet
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 6: Footer Check ---");
|
||||
$display("\n--- Test Group 6: Footer Retention ---");
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
|
||||
// Drive packet through range data
|
||||
@(posedge clk);
|
||||
cfar_detection = 1'b1;
|
||||
@(posedge clk);
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'hFACE_FEED);
|
||||
wait_for_state(S_SEND_DOPPLER, 100);
|
||||
// Feed doppler data (need 4 pulses)
|
||||
pulse_doppler_once(16'h1111, 16'h2222);
|
||||
pulse_doppler_once(16'h1111, 16'h2222);
|
||||
pulse_doppler_once(16'h1111, 16'h2222);
|
||||
pulse_doppler_once(16'h1111, 16'h2222);
|
||||
wait_for_state(S_SEND_DETECT, 100);
|
||||
// Feed cfar data, but keep ft601_txe=0 so it flows through
|
||||
pulse_cfar_once(1'b1);
|
||||
|
||||
// Now the FSM should pass through SEND_FOOTER quickly.
|
||||
// Use wait_for_state to reach SEND_FOOTER, or it may already
|
||||
// be at WAIT_ACK/IDLE. Let's catch WAIT_ACK or IDLE.
|
||||
// The footer values are latched into registers, so we can
|
||||
// verify them even after the state transitions.
|
||||
// Key verification: the FOOTER constant (0x55) must have been
|
||||
// driven. We check this by looking at the constant definition.
|
||||
// Since we can't easily freeze the FSM at SEND_FOOTER without
|
||||
// also stalling SEND_DETECTION_DATA (both check ft601_txe),
|
||||
// we verify the footer indirectly:
|
||||
// 1. The packet completed (reached IDLE/WAIT_ACK)
|
||||
// 2. ft601_data_out last held 0x55 during SEND_FOOTER
|
||||
|
||||
wait_for_state(S_IDLE, 100);
|
||||
#1;
|
||||
// If we reached IDLE, the full sequence ran including footer
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Full packet incl. footer completed, back in IDLE");
|
||||
|
||||
// The registered ft601_data_out should still hold 0x55 from
|
||||
// SEND_FOOTER (WAIT_ACK and IDLE don't overwrite ft601_data_out).
|
||||
// Actually, looking at the DUT: WAIT_ACK only sets wr_n=1 and
|
||||
// data_oe=0, it doesn't change ft601_data_out. So it retains 0x55.
|
||||
check(uut.ft601_data_out[7:0] === 8'h55,
|
||||
"ft601_data_out retains footer 0x55 after packet");
|
||||
// The last word driven was word 2 which contains footer 0x55.
|
||||
// WAIT_ACK and IDLE don't overwrite ft601_data_out, so it retains
|
||||
// the last driven value.
|
||||
check(uut.ft601_data_out[15:8] === 8'h55,
|
||||
"ft601_data_out retains footer 0x55 in word 2 position");
|
||||
|
||||
// Verify WAIT_ACK behavior by doing another packet and catching it
|
||||
// Verify WAIT_ACK → IDLE transition
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'h1234_5678);
|
||||
wait_for_state(S_SEND_DOPPLER, 100);
|
||||
pulse_doppler_once(16'hABCD, 16'hEF01);
|
||||
pulse_doppler_once(16'hABCD, 16'hEF01);
|
||||
pulse_doppler_once(16'hABCD, 16'hEF01);
|
||||
pulse_doppler_once(16'hABCD, 16'hEF01);
|
||||
wait_for_state(S_SEND_DETECT, 100);
|
||||
pulse_cfar_once(1'b0);
|
||||
// WAIT_ACK lasts exactly 1 ft601_clk_in cycle then goes IDLE.
|
||||
// Poll for IDLE (which means WAIT_ACK already happened).
|
||||
wait_for_state(S_IDLE, 100);
|
||||
#1;
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Returned to IDLE after WAIT_ACK");
|
||||
check(ft601_wr_n === 1'b1,
|
||||
"ft601_wr_n deasserted in IDLE (was deasserted in WAIT_ACK)");
|
||||
"ft601_wr_n deasserted in IDLE");
|
||||
check(uut.ft601_data_oe === 1'b0,
|
||||
"Data bus released in IDLE (was released in WAIT_ACK)");
|
||||
"Data bus released in IDLE");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 7: Full packet sequence (end-to-end)
|
||||
@@ -646,23 +641,24 @@ module tb_usb_data_interface;
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 8: FIFO Backpressure ---");
|
||||
apply_reset;
|
||||
ft601_txe = 1;
|
||||
ft601_txe = 1; // FIFO full — stall
|
||||
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'hBBBB_CCCC);
|
||||
|
||||
wait_for_state(S_SEND_HEADER, 50);
|
||||
wait_for_state(S_SEND_DATA_WORD, 50);
|
||||
repeat (10) @(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.current_state === S_SEND_HEADER,
|
||||
"Stalled in SEND_HEADER when ft601_txe=1 (FIFO full)");
|
||||
check(uut.current_state === S_SEND_DATA_WORD,
|
||||
"Stalled in SEND_DATA_WORD when ft601_txe=1 (FIFO full)");
|
||||
check(ft601_wr_n === 1'b1,
|
||||
"ft601_wr_n not asserted during backpressure stall");
|
||||
|
||||
ft601_txe = 0;
|
||||
repeat (2) @(posedge ft601_clk_in); #1;
|
||||
repeat (6) @(posedge ft601_clk_in); #1;
|
||||
|
||||
check(uut.current_state !== S_SEND_HEADER,
|
||||
"Resumed from SEND_HEADER after backpressure released");
|
||||
check(uut.current_state === S_IDLE || uut.current_state === S_WAIT_ACK,
|
||||
"Resumed and completed after backpressure released");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 9: Clock divider
|
||||
@@ -705,13 +701,6 @@ module tb_usb_data_interface;
|
||||
ft601_txe = 0;
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'h1111_2222);
|
||||
wait_for_state(S_SEND_DOPPLER, 100);
|
||||
pulse_doppler_once(16'h3333, 16'h4444);
|
||||
pulse_doppler_once(16'h3333, 16'h4444);
|
||||
pulse_doppler_once(16'h3333, 16'h4444);
|
||||
pulse_doppler_once(16'h3333, 16'h4444);
|
||||
wait_for_state(S_SEND_DETECT, 100);
|
||||
pulse_cfar_once(1'b0);
|
||||
wait_for_state(S_WAIT_ACK, 50);
|
||||
#1;
|
||||
|
||||
@@ -805,7 +794,7 @@ module tb_usb_data_interface;
|
||||
// Start a write packet
|
||||
preload_pending_data;
|
||||
assert_range_valid(32'hFACE_FEED);
|
||||
wait_for_state(S_SEND_HEADER, 50);
|
||||
wait_for_state(S_SEND_DATA_WORD, 50);
|
||||
@(posedge ft601_clk_in); #1;
|
||||
|
||||
// While write FSM is active, assert RXF=0 (host has data)
|
||||
@@ -818,13 +807,6 @@ module tb_usb_data_interface;
|
||||
|
||||
// Deassert RXF, complete the write packet
|
||||
ft601_rxf = 1;
|
||||
wait_for_state(S_SEND_DOPPLER, 100);
|
||||
pulse_doppler_once(16'hAAAA, 16'hBBBB);
|
||||
pulse_doppler_once(16'hAAAA, 16'hBBBB);
|
||||
pulse_doppler_once(16'hAAAA, 16'hBBBB);
|
||||
pulse_doppler_once(16'hAAAA, 16'hBBBB);
|
||||
wait_for_state(S_SEND_DETECT, 100);
|
||||
pulse_cfar_once(1'b1);
|
||||
wait_for_state(S_IDLE, 100);
|
||||
@(posedge ft601_clk_in); #1;
|
||||
|
||||
@@ -841,32 +823,42 @@ module tb_usb_data_interface;
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 15: Stream Control Gating (Gap 2)
|
||||
// Verify that disabling individual streams causes the write
|
||||
// FSM to skip those data phases.
|
||||
// FSM to zero those fields in the packed words.
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 15: Stream Control Gating (Gap 2) ---");
|
||||
|
||||
// 15a: Disable doppler stream (stream_control = 3'b101 = range + cfar only)
|
||||
apply_reset;
|
||||
ft601_txe = 0;
|
||||
ft601_txe = 1; // Stall to inspect packed words
|
||||
stream_control = 3'b101; // range + cfar, no doppler
|
||||
// Wait for CDC propagation (2-stage sync)
|
||||
repeat (6) @(posedge ft601_clk_in);
|
||||
|
||||
// Preload cfar pending so the FSM enters the SEND_DETECT data path
|
||||
// (without it, SEND_DETECT skips immediately on !cfar_data_pending).
|
||||
preload_cfar_pending;
|
||||
// Drive range valid — triggers write FSM
|
||||
assert_range_valid(32'hAA11_BB22);
|
||||
// FSM: IDLE -> SEND_HEADER -> SEND_RANGE (doppler disabled) -> SEND_DETECT -> FOOTER
|
||||
// The FSM races through SEND_DETECT in 1 cycle (cfar_data_pending is consumed).
|
||||
// Verify the packet completed correctly (doppler was skipped).
|
||||
wait_for_state(S_IDLE, 200);
|
||||
#1;
|
||||
// Reaching IDLE proves: HEADER -> RANGE -> (skip DOPPLER) -> DETECT -> FOOTER -> ACK -> IDLE.
|
||||
// cfar_data_pending consumed confirms SEND_DETECT was entered.
|
||||
check(uut.current_state === S_IDLE && uut.cfar_data_pending === 1'b0,
|
||||
"Stream gate: reached SEND_DETECT (range sent, doppler skipped)");
|
||||
@(posedge clk);
|
||||
doppler_real = 16'hAAAA;
|
||||
doppler_imag = 16'hBBBB;
|
||||
cfar_detection = 1'b1;
|
||||
@(posedge clk);
|
||||
|
||||
preload_cfar_pending;
|
||||
assert_range_valid(32'hAA11_BB22);
|
||||
|
||||
wait_for_state(S_SEND_DATA_WORD, 200);
|
||||
repeat (2) @(posedge ft601_clk_in); #1;
|
||||
|
||||
// With doppler disabled, doppler fields in words 1 and 2 should be zero
|
||||
// Word 1: {range[7:0], 0x00, 0x00, 0x00} (doppler zeroed)
|
||||
check(uut.data_pkt_word1[23:0] === 24'h000000,
|
||||
"Stream gate: doppler bytes zeroed in word 1 when disabled");
|
||||
|
||||
// Word 2 byte 3 (dop_im_lo) should also be zero
|
||||
check(uut.data_pkt_word2[31:24] === 8'h00,
|
||||
"Stream gate: dop_im_lo zeroed in word 2 when disabled");
|
||||
|
||||
// Let it complete
|
||||
ft601_txe = 0;
|
||||
wait_for_state(S_IDLE, 100);
|
||||
#1;
|
||||
check(uut.current_state === S_IDLE,
|
||||
"Stream gate: packet completed without doppler");
|
||||
|
||||
@@ -951,28 +943,6 @@ module tb_usb_data_interface;
|
||||
"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]}
|
||||
// = {8'hFF, 3'b000, 2'b01, 5'b00000, 3'b101, 16'hABCD}
|
||||
// = 0xFF_09_05_ABCD... let's compute:
|
||||
// Byte 3: 0xFF = 8'hFF
|
||||
// Byte 2: {3'b000, 2'b01} = 5'b00001 + 3 high bits of next field...
|
||||
// Actually the packing is: {8'hFF, 3'b000, status_radar_mode[1:0], 5'b00000, status_stream_ctrl[2:0], status_cfar_threshold[15:0]}
|
||||
// = {8'hFF, 3'b000, 2'b01, 5'b00000, 3'b101, 16'hABCD}
|
||||
// = 8'hFF, 5'b00001, 8'b00000101, 16'hABCD
|
||||
// = FF_09_05_ABCD? Let me compute carefully:
|
||||
// Bits [31:24] = 8'hFF = 0xFF
|
||||
// Bits [23:21] = 3'b000
|
||||
// Bits [20:19] = 2'b01 (mode)
|
||||
// Bits [18:14] = 5'b00000
|
||||
// Bits [13:11] = 3'b101 (stream_ctrl)
|
||||
// Bits [10:0] = ... wait, cfar_threshold is 16 bits → [15:0]
|
||||
// Total bits = 8+3+2+5+3+16 = 37 bits — won't fit in 32!
|
||||
// Re-reading the RTL: the packing at line 241 is:
|
||||
// {8'hFF, 3'b000, status_radar_mode, 5'b00000, status_stream_ctrl, status_cfar_threshold}
|
||||
// = 8 + 3 + 2 + 5 + 3 + 16 = 37 bits
|
||||
// This would be truncated to 32 bits. Let me re-read the actual RTL to check.
|
||||
// For now, just verify status_words[1] (word index 1 in the packet = idx 2 in FSM)
|
||||
// status_words[1] = {status_long_chirp, status_long_listen} = {16'd3000, 16'd13700}
|
||||
check(uut.status_words[1] === {16'd3000, 16'd13700},
|
||||
"Status readback: word 1 = {long_chirp, long_listen}");
|
||||
check(uut.status_words[2] === {16'd17540, 16'd50},
|
||||
|
||||
Reference in New Issue
Block a user