diff --git a/9_Firmware/9_2_FPGA/run_regression.sh b/9_Firmware/9_2_FPGA/run_regression.sh index ce1fd9f..d6504ff 100755 --- a/9_Firmware/9_2_FPGA/run_regression.sh +++ b/9_Firmware/9_2_FPGA/run_regression.sh @@ -621,24 +621,34 @@ if [[ "$QUICK" -eq 0 ]]; then tb/tb_system_reg.vvp \ tb/radar_system_tb.v "${SYSTEM_RTL[@]}" - # E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset) - run_test "System E2E (tb_system_e2e)" \ - tb/tb_system_e2e_reg.vvp \ - tb/tb_system_e2e.v "${SYSTEM_RTL[@]}" + # PR-I subsuites (replace tb_system_e2e). Each TB instantiates + # radar_system_top with USB_MODE=1 (production FT2232H path) and + # carves a focused slice of what the legacy tb_system_e2e tried to + # cover all at once: + # tb_system_opcodes - opcode dispatch via FT2232H send_cmd (fast) + # tb_system_mechanics - reset/RF/safety/CDC mechanics (fast) + # tb_system_dataflow - shallow TX + range-pipeline integration + # (slow; 18 ms sim, ~430-450 s wall on this host). + run_test "System Opcodes (tb_system_opcodes)" \ + tb/tb_system_opcodes_reg.vvp \ + tb/tb_system_opcodes.v "${SYSTEM_RTL[@]}" - # USB_MODE=1 (FT2232H production) variants of system tests + run_test "System Mechanics (tb_system_mechanics)" \ + tb/tb_system_mechanics_reg.vvp \ + tb/tb_system_mechanics.v "${SYSTEM_RTL[@]}" + + run_test --timeout=600 "System Dataflow (tb_system_dataflow)" \ + tb/tb_system_dataflow_reg.vvp \ + tb/tb_system_dataflow.v "${SYSTEM_RTL[@]}" + + # USB_MODE=1 system top — different TB, kept as a structural smoke test. run_test "System Top USB_MODE=1 (FT2232H)" \ tb/tb_system_ft2232h_reg.vvp \ -DUSB_MODE_1 \ tb/radar_system_tb.v "${SYSTEM_RTL[@]}" - - run_test "System E2E USB_MODE=1 (FT2232H)" \ - tb/tb_system_e2e_ft2232h_reg.vvp \ - -DUSB_MODE_1 \ - tb/tb_system_e2e.v "${SYSTEM_RTL[@]}" else - echo " (skipped receiver integration + system top + E2E + USB_MODE=1 variants — use without --quick)" - SKIP=$((SKIP + 5)) + echo " (skipped receiver integration + system top + opcodes/mechanics/dataflow + USB_MODE=1 — use without --quick)" + SKIP=$((SKIP + 6)) fi echo "" diff --git a/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v b/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v deleted file mode 100644 index ba6bd82..0000000 --- a/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v +++ /dev/null @@ -1,1294 +0,0 @@ -`timescale 1ns / 1ps - -/** - * tb_system_e2e.v - * - * Comprehensive End-to-End Integration Testbench for AERIS-10 Radar FPGA - * - * This testbench exercises the FULL system (radar_system_top) with strict - * pass/fail scoring across 12 test groups covering every subsystem, - * clock domain crossing, and data path. - * - * Test Groups: - * G1: Reset & Initialization (4 checks) - * G2: Transmitter Chain (5 checks) - * G3: Safety Architecture (4 checks) - * G4: Receiver Chain Data Flow (5 checks) - * G5: USB Write Path (5 checks) - * G6: USB Read Path / Host Commands (6 checks) - * G7: CDC Crossing Stress (4 checks) - * G8: Beam Scanning / Auto-Scan Mode (4 checks) - * G9: Mid-Operation Reset Recovery (3 checks) - * G10: Stream Control (3 checks) - * G11: Processing Latency Budgets (2 checks) - * G12: Watchdog / Liveness (2 checks) - * G13: Doppler/Chirps Mismatch Protection (8 checks) [Fix 4] - * G14: CFAR Configuration Registers (13 checks) [CFAR integration] - * - * Compile: - * iverilog -g2001 -DSIMULATION -o tb/tb_system_e2e.vvp \ - * tb/tb_system_e2e.v radar_system_top.v \ - * radar_transmitter.v dac_interface_single.v plfm_chirp_controller.v \ - * radar_receiver_final.v tb/ad9484_interface_400m_stub.v \ - * ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v \ - * cdc_modules.v fir_lowpass.v ddc_input_interface.v \ - * chirp_reference_rom.v \ - * matched_filter_multi_segment.v matched_filter_processing_chain.v \ - * range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \ - * usb_data_interface.v edge_detector.v chirp_scheduler.v - * - * Run: - * vvp tb/tb_system_e2e.vvp - */ - -`include "radar_params.vh" - -module tb_system_e2e; - -// ============================================================================ -// PARAMETERS -// ============================================================================ -parameter CLK_100M_PERIOD = 10.0; // 100 MHz = 10 ns -parameter CLK_120M_PERIOD = 8.333; // 120 MHz -parameter FT601_CLK_PERIOD = 10.0; // 100 MHz (async to clk_100m) -parameter ADC_DCO_PERIOD = 2.5; // 400 MHz - -// Simulation budget: tuned for iverilog performance with 400MHz ADC clock. -// Keep short — iverilog is ~10x slower than compiled simulators. -parameter SIM_TIMEOUT_NS = 800_000; - -// ============================================================================ -// SCORING -// ============================================================================ -integer test_num = 0; -integer pass_count = 0; -integer fail_count = 0; - -task check; - input cond; - input [80*8-1:0] msg; - begin - test_num = test_num + 1; - if (cond) begin - pass_count = pass_count + 1; - $display(" [PASS] %0d: %0s", test_num, msg); - end else begin - fail_count = fail_count + 1; - $display(" [FAIL] %0d: %0s", test_num, msg); - end - end -endtask - -// ============================================================================ -// CLOCK GENERATION -// ============================================================================ -reg clk_100m; -reg clk_120m_dac; -reg ft601_clk_in; -reg adc_dco_p, adc_dco_n; - -initial begin clk_100m = 0; forever #(CLK_100M_PERIOD/2) clk_100m = ~clk_100m; end -initial begin clk_120m_dac = 0; forever #(CLK_120M_PERIOD/2) clk_120m_dac = ~clk_120m_dac; end -// FT601 clock: offset by 1.7ns to ensure truly async w.r.t. clk_100m -initial begin ft601_clk_in = 0; #1.7; forever #(FT601_CLK_PERIOD/2) ft601_clk_in = ~ft601_clk_in; end -initial begin - adc_dco_p = 0; adc_dco_n = 1; - forever #(ADC_DCO_PERIOD/2) begin adc_dco_p = ~adc_dco_p; adc_dco_n = ~adc_dco_n; end -end - -// ============================================================================ -// DUT SIGNALS -// ============================================================================ -reg reset_n; - -// ADC -reg [7:0] adc_d_p; -reg [7:0] adc_d_n; - -// STM32 control -reg stm32_new_chirp; -reg stm32_new_elevation; -reg stm32_new_azimuth; -reg stm32_mixers_enable; - -// SPI (unused in this TB — tie off) -reg stm32_sclk_3v3; -reg stm32_mosi_3v3; -wire stm32_miso_3v3; -reg stm32_cs_adar1_3v3, stm32_cs_adar2_3v3; -reg stm32_cs_adar3_3v3, stm32_cs_adar4_3v3; -wire stm32_sclk_1v8, stm32_mosi_1v8; -reg stm32_miso_1v8; -wire stm32_cs_adar1_1v8, stm32_cs_adar2_1v8; -wire stm32_cs_adar3_1v8, stm32_cs_adar4_1v8; - -// DAC outputs -wire [7:0] dac_data; -wire dac_clk; -wire dac_sleep; - -// RF control -wire fpga_rf_switch; -wire rx_mixer_en; -wire tx_mixer_en; -wire adc_pwdn; - -// ADAR1000 -wire adar_tx_load_1, adar_rx_load_1; -wire adar_tx_load_2, adar_rx_load_2; -wire adar_tx_load_3, adar_rx_load_3; -wire adar_tx_load_4, adar_rx_load_4; -wire adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4; - -// FT601 interface -wire [31:0] ft601_data; -wire [3:0] ft601_be; -wire ft601_txe_n; -wire ft601_rxf_n; -reg ft601_txe; -reg ft601_rxf; -wire ft601_wr_n; -wire ft601_rd_n; -wire ft601_oe_n; -wire ft601_siwu_n; -reg [1:0] ft601_srb; -reg [1:0] ft601_swb; -wire ft601_clk_out; - -// Status -wire [5:0] current_elevation; -wire [5:0] current_azimuth; -wire [5:0] current_chirp; -wire new_chirp_frame; -wire [31:0] dbg_doppler_data; -wire dbg_doppler_valid; -wire [`RP_DOPPLER_BIN_WIDTH-1:0] dbg_doppler_bin; -wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] dbg_range_bin; -wire [3:0] system_status; - -// ============================================================================ -// FT601 BUS FUNCTIONAL MODEL (BFM) -// ============================================================================ -// The FT601 data bus is bidirectional. During writes (FPGA→host), the DUT -// drives it. During reads (host→FPGA), the BFM drives it. -// -// BFM provides: -// - ft601_rxf control (signal when host has data for FPGA) -// - ft601_data driving during reads -// - ft601_txe control (backpressure for writes) -// - Packet capture for write verification - -// Read injection FIFO -reg [31:0] bfm_rx_fifo [0:15]; -reg [3:0] bfm_rx_wr_ptr; -reg [3:0] bfm_rx_rd_ptr; -wire bfm_rx_empty; -reg bfm_rx_driving; -reg [31:0] bfm_rx_data_out; - -assign bfm_rx_empty = (bfm_rx_wr_ptr == bfm_rx_rd_ptr); - -// BFM drives ft601_data during read operations (active low OE from DUT) -assign ft601_data = (!ft601_oe_n && !bfm_rx_driving) ? 32'hzzzz_zzzz : - bfm_rx_driving ? bfm_rx_data_out : 32'hzzzz_zzzz; - -// BFM read-side state machine: respond to DUT OE/RD assertions -always @(posedge ft601_clk_in or negedge reset_n) begin - if (!reset_n) begin - bfm_rx_rd_ptr <= 0; - bfm_rx_driving <= 0; - bfm_rx_data_out <= 32'd0; - end else begin - if (!ft601_oe_n && !bfm_rx_empty) begin - bfm_rx_driving <= 1; - bfm_rx_data_out <= bfm_rx_fifo[bfm_rx_rd_ptr]; - end else begin - bfm_rx_driving <= 0; - end - // Advance pointer when DUT samples (RD_N goes high after reading) - if (ft601_rd_n && bfm_rx_driving) begin - bfm_rx_rd_ptr <= bfm_rx_rd_ptr + 1; - bfm_rx_driving <= 0; - end - end -end - -// RXF signal: active-low (0 = data available from host) -// Directly driven by whether there's data in the BFM FIFO -always @(*) begin - ft601_rxf = bfm_rx_empty; // 1 = no data, 0 = data available -end - -// Task: Inject a USB command word into the BFM FIFO -task bfm_send_cmd; - input [7:0] opcode; - input [7:0] addr; - input [15:0] value; - begin - bfm_rx_fifo[bfm_rx_wr_ptr] = {opcode, addr, value}; - bfm_rx_wr_ptr = bfm_rx_wr_ptr + 1; - // Wait for the read to be processed through CDC pipeline - // Read FSM: IDLE→OE(1)→READING(1)→DEASSERT(1)→PROCESS(1) = 4 ft601 cycles - // Then CDC: 3-stage toggle sync + edge detect = ~5 clk_100m cycles - // Total: ~15 clk_100m cycles to be safe - #200; // 200ns = 20 clk_100m cycles — generous margin - end -endtask - -// Write capture buffer -reg [31:0] usb_wr_capture [0:1023]; -integer usb_wr_count; -integer usb_wr_header_count; -integer usb_wr_footer_count; - -always @(posedge ft601_clk_in) begin - if (!reset_n) begin - usb_wr_count <= 0; - usb_wr_header_count <= 0; - usb_wr_footer_count <= 0; - end else if (!ft601_wr_n && !ft601_txe) begin - if (usb_wr_count < 1024) - usb_wr_capture[usb_wr_count] <= ft601_data; - usb_wr_count <= usb_wr_count + 1; - // Count headers and footers - if (ft601_data[7:0] == 8'hAA && ft601_be == 4'b0001) - usb_wr_header_count <= usb_wr_header_count + 1; - if (ft601_data[7:0] == 8'h55 && ft601_be == 4'b0001) - usb_wr_footer_count <= usb_wr_footer_count + 1; - end -end - -// ============================================================================ -// SAFETY MONITORS (continuous checks) -// ============================================================================ -// These run throughout the entire simulation and flag violations immediately. - -// After Fix #4, tx_mixer_en and rx_mixer_en are mutually exclusive -// (TX active during chirp, RX active during listen). Verify: -// 1. They are NEVER simultaneously high -// 2. They deassert when stm32_mixers_enable goes low -integer safety_simultaneous_mixer_count; -integer safety_mixer_deassert_fail_count; - -initial begin - safety_simultaneous_mixer_count = 0; - safety_mixer_deassert_fail_count = 0; -end - -always @(posedge clk_100m) begin - if (reset_n) begin - // Check mutual exclusion: tx and rx mixers must never both be high - if (tx_mixer_en && rx_mixer_en) begin - safety_simultaneous_mixer_count = safety_simultaneous_mixer_count + 1; - if (safety_simultaneous_mixer_count <= 5) - $display(" [SAFETY VIOLATION @%0t] tx_mixer_en AND rx_mixer_en both HIGH", $time); - end - end -end - -// Monitor: when stm32_mixers_enable is 0, mixers must eventually deassert -reg [3:0] mixer_disable_timer; -always @(posedge clk_100m) begin - if (!reset_n) begin - mixer_disable_timer <= 0; - end else begin - if (!stm32_mixers_enable) begin - if (mixer_disable_timer < 15) - mixer_disable_timer <= mixer_disable_timer + 1; - if (mixer_disable_timer >= 12 && (tx_mixer_en || rx_mixer_en)) begin - safety_mixer_deassert_fail_count = safety_mixer_deassert_fail_count + 1; - if (safety_mixer_deassert_fail_count <= 3) - $display(" [SAFETY @%0t] Mixers still on 12+ cycles after disable", $time); - end - end else begin - mixer_disable_timer <= 0; - end - end -end - -// ============================================================================ -// OBSERVATION COUNTERS -// ============================================================================ -integer obs_chirp_frame_count; -integer obs_dac_nonzero_count; -integer obs_range_valid_count; -integer obs_doppler_valid_count; -integer obs_usb_backpressure_writes; -reg obs_seen_tx_mixer; -reg obs_seen_rx_mixer; -reg obs_seen_rf_switch; -reg [5:0] obs_max_chirp; -reg [5:0] obs_max_elevation; -reg [5:0] obs_max_azimuth; -integer obs_range_first_time; -integer obs_doppler_first_time; - -initial begin - obs_chirp_frame_count = 0; - obs_dac_nonzero_count = 0; - obs_range_valid_count = 0; - obs_doppler_valid_count = 0; - obs_usb_backpressure_writes = 0; - obs_seen_tx_mixer = 0; - obs_seen_rx_mixer = 0; - obs_seen_rf_switch = 0; - obs_max_chirp = 0; - obs_max_elevation = 0; - obs_max_azimuth = 0; - obs_range_first_time = 0; - obs_doppler_first_time = 0; -end - -always @(posedge clk_100m) begin - if (reset_n) begin - if (new_chirp_frame) - obs_chirp_frame_count = obs_chirp_frame_count + 1; - if (tx_mixer_en) obs_seen_tx_mixer = 1; - if (rx_mixer_en) obs_seen_rx_mixer = 1; - if (fpga_rf_switch) obs_seen_rf_switch = 1; - if (current_chirp > obs_max_chirp) - obs_max_chirp = current_chirp; - if (current_elevation > obs_max_elevation) - obs_max_elevation = current_elevation; - if (current_azimuth > obs_max_azimuth) - obs_max_azimuth = current_azimuth; - end -end - -always @(posedge clk_120m_dac) begin - if (reset_n && dac_data != 8'h80 && dac_data != 8'h00) - obs_dac_nonzero_count = obs_dac_nonzero_count + 1; -end - -always @(posedge clk_100m) begin - if (reset_n) begin - if (dut.rx_range_valid) begin - obs_range_valid_count = obs_range_valid_count + 1; - if (obs_range_first_time == 0) - obs_range_first_time = $time; - end - if (dbg_doppler_valid) begin - obs_doppler_valid_count = obs_doppler_valid_count + 1; - if (obs_doppler_first_time == 0) - obs_doppler_first_time = $time; - end - end -end - -// Track USB writes during backpressure -always @(posedge ft601_clk_in) begin - if (reset_n && !ft601_wr_n && ft601_txe) - obs_usb_backpressure_writes = obs_usb_backpressure_writes + 1; -end - -// ============================================================================ -// DUT INSTANTIATION -// ============================================================================ -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), - .reset_n(reset_n), - - .dac_data(dac_data), - .dac_clk(dac_clk), - .dac_sleep(dac_sleep), - .fpga_rf_switch(fpga_rf_switch), - .rx_mixer_en(rx_mixer_en), - .tx_mixer_en(tx_mixer_en), - - .adar_tx_load_1(adar_tx_load_1), .adar_rx_load_1(adar_rx_load_1), - .adar_tx_load_2(adar_tx_load_2), .adar_rx_load_2(adar_rx_load_2), - .adar_tx_load_3(adar_tx_load_3), .adar_rx_load_3(adar_rx_load_3), - .adar_tx_load_4(adar_tx_load_4), .adar_rx_load_4(adar_rx_load_4), - .adar_tr_1(adar_tr_1), .adar_tr_2(adar_tr_2), - .adar_tr_3(adar_tr_3), .adar_tr_4(adar_tr_4), - - .stm32_sclk_3v3(stm32_sclk_3v3), - .stm32_mosi_3v3(stm32_mosi_3v3), - .stm32_miso_3v3(stm32_miso_3v3), - .stm32_cs_adar1_3v3(stm32_cs_adar1_3v3), - .stm32_cs_adar2_3v3(stm32_cs_adar2_3v3), - .stm32_cs_adar3_3v3(stm32_cs_adar3_3v3), - .stm32_cs_adar4_3v3(stm32_cs_adar4_3v3), - .stm32_sclk_1v8(stm32_sclk_1v8), - .stm32_mosi_1v8(stm32_mosi_1v8), - .stm32_miso_1v8(stm32_miso_1v8), - .stm32_cs_adar1_1v8(stm32_cs_adar1_1v8), - .stm32_cs_adar2_1v8(stm32_cs_adar2_1v8), - .stm32_cs_adar3_1v8(stm32_cs_adar3_1v8), - .stm32_cs_adar4_1v8(stm32_cs_adar4_1v8), - - .adc_d_p(adc_d_p), - .adc_d_n(adc_d_n), - .adc_dco_p(adc_dco_p), - .adc_dco_n(adc_dco_n), - .adc_or_p(1'b0), - .adc_or_n(1'b1), - .adc_pwdn(adc_pwdn), - - .stm32_new_chirp(stm32_new_chirp), - .stm32_new_elevation(stm32_new_elevation), - .stm32_new_azimuth(stm32_new_azimuth), - .stm32_mixers_enable(stm32_mixers_enable), - - .ft601_data(ft601_data), - .ft601_be(ft601_be), - .ft601_txe_n(ft601_txe_n), - .ft601_rxf_n(ft601_rxf_n), - .ft601_txe(ft601_txe), - .ft601_rxf(ft601_rxf), - .ft601_wr_n(ft601_wr_n), - .ft601_rd_n(ft601_rd_n), - .ft601_oe_n(ft601_oe_n), - .ft601_siwu_n(ft601_siwu_n), - .ft601_srb(ft601_srb), - .ft601_swb(ft601_swb), - .ft601_clk_out(ft601_clk_out), - - .current_elevation(current_elevation), - .current_azimuth(current_azimuth), - .current_chirp(current_chirp), - .new_chirp_frame(new_chirp_frame), - .dbg_doppler_data(dbg_doppler_data), - .dbg_doppler_valid(dbg_doppler_valid), - .dbg_doppler_bin(dbg_doppler_bin), - .dbg_range_bin(dbg_range_bin), - .system_status(system_status) -); - -// ============================================================================ -// HELPER TASKS -// ============================================================================ - -task do_reset; - begin - reset_n = 0; - #200; // Hold reset for 200ns (20+ cycles at 100MHz) - reset_n = 1; - #100; // Wait for synchronizers to settle - end -endtask - -// Pulse STM32 chirp toggle (simulates STM32 GPIO toggle) -task stm32_chirp_toggle; - begin - stm32_new_chirp = ~stm32_new_chirp; - #40; // Hold for 4 clk_100m cycles for edge detector - end -endtask - -task stm32_elevation_toggle; - begin - stm32_new_elevation = ~stm32_new_elevation; - #40; - end -endtask - -task stm32_azimuth_toggle; - begin - stm32_new_azimuth = ~stm32_new_azimuth; - #40; - end -endtask - -// Drive ADC with a sinusoid-like pattern (simple ramp for stimulus) -integer adc_phase; -initial begin - adc_d_p = 8'h80; - adc_d_n = 8'h7F; - adc_phase = 0; - forever begin - @(posedge adc_dco_p); - if (reset_n) begin - // Simple ramp + mid-scale offset to generate non-trivial data - adc_d_p = 8'h80 + ((adc_phase * 7) & 8'h3F) - 8'h20; - adc_d_n = ~adc_d_p; - adc_phase = adc_phase + 1; - end else begin - adc_d_p = 8'h80; - adc_d_n = 8'h7F; - end - end -end - -// ============================================================================ -// MAIN TEST SEQUENCE -// ============================================================================ -integer i; -integer t_start; -integer saved_wr_count; -integer saved_range_count; -integer saved_doppler_count; - -initial begin - // VCD dump disabled by default for performance (400MHz ADC = huge trace). - // Uncomment for debug: $dumpfile("tb/tb_system_e2e.vcd"); - // $dumpvars(0, tb_system_e2e); - - // ---- Signal initialization ---- - reset_n = 0; - stm32_new_chirp = 0; - stm32_new_elevation = 0; - stm32_new_azimuth = 0; - stm32_mixers_enable = 0; - stm32_sclk_3v3 = 0; - stm32_mosi_3v3 = 0; - stm32_cs_adar1_3v3 = 1; - stm32_cs_adar2_3v3 = 1; - stm32_cs_adar3_3v3 = 1; - stm32_cs_adar4_3v3 = 1; - stm32_miso_1v8 = 0; - ft601_txe = 0; // TX FIFO not full (ready to accept writes) - ft601_srb = 2'b00; - ft601_swb = 2'b00; - bfm_rx_wr_ptr = 0; - - $display(""); - $display("============================================================"); - $display(" AERIS-10 FPGA End-to-End Integration Testbench"); - $display(" 12 test groups, strict PASS/FAIL scoring"); - $display("============================================================"); - $display(""); - - // ================================================================ - // GROUP 1: RESET & INITIALIZATION - // ================================================================ - $display("--- Group 1: Reset & Initialization ---"); - do_reset; - - // CRITICAL: Configure stream control to range-only BEFORE any chirps - // 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 - // range_valid fires and triggers the write FSM. - #500; - - // G1.1: System status clears to 0 after reset - check(system_status == 4'b0000, - "G1.1: system_status == 0 after reset"); - - // G1.2: No USB writes during/after reset - check(usb_wr_count == 0, - "G1.2: No USB writes during reset"); - - // G1.3: ft601_wr_n is deasserted (high) after reset - check(ft601_wr_n == 1, - "G1.3: ft601_wr_n == 1 after reset"); - - // G1.4: ADC power-down is low (ADC always on) - check(adc_pwdn == 0, - "G1.4: adc_pwdn == 0 (ADC enabled)"); - - $display(""); - - // ================================================================ - // GROUP 2: TRANSMITTER CHAIN - // ================================================================ - $display("--- Group 2: Transmitter Chain ---"); - - // Enable mixers and trigger a chirp via STM32 toggles - stm32_mixers_enable = 1; - #100; - - // Fire a chirp and wait long enough for the TX chirp controller FSM to - // progress through LONG_CHIRP (30us) into LONG_LISTEN (where rx_mixer_en - // activates). Then fire more chirps for the receiver pipeline. - stm32_chirp_toggle; - #40000; // 40us — enough for LONG_CHIRP to complete and enter LONG_LISTEN - for (i = 0; i < 3; i = i + 1) begin - stm32_chirp_toggle; - #3000; - end - - // Wait for chirp processing - #5000; - - // G2.1: DAC produced non-midscale output (chirp data) - check(obs_dac_nonzero_count > 0, - "G2.1: DAC output non-trivial (chirp generated)"); - - // G2.2: At least one chirp frame was observed - check(obs_chirp_frame_count > 0, - "G2.2: new_chirp_frame pulsed at least once"); - - // G2.3: RF switch activated at some point - check(obs_seen_rf_switch == 1, - "G2.3: fpga_rf_switch activated during chirp"); - - // G2.4: TX mixer was enabled during transmit phase - check(obs_seen_tx_mixer == 1, - "G2.4: tx_mixer_en seen during chirp sequence"); - - // G2.5: RX mixer was enabled during receive phase - check(obs_seen_rx_mixer == 1, - "G2.5: rx_mixer_en seen during chirp sequence"); - - $display(""); - - // ================================================================ - // GROUP 3: SAFETY ARCHITECTURE - // ================================================================ - $display("--- Group 3: Safety Architecture ---"); - - // G3.1: TX/RX mixers are mutually exclusive (never both high) - check(safety_simultaneous_mixer_count == 0, - "G3.1: TX and RX mixers never simultaneously enabled"); - - // G3.2: ADC power-down stays low throughout - check(adc_pwdn == 0, - "G3.2: adc_pwdn remains 0 throughout operation"); - - // G3.3: ADAR TR pins are consistent (all same value) - // Check instantaneous: during active period, all TR should be the same - check(adar_tr_1 == adar_tr_2 && adar_tr_2 == adar_tr_3 && adar_tr_3 == adar_tr_4, - "G3.3: All ADAR TR pins consistent"); - - // G3.4: Disable mixers — verify they deassert - stm32_mixers_enable = 0; - #500; // Allow CDC + propagation - check(tx_mixer_en == 0 && rx_mixer_en == 0, - "G3.4: Mixers deassert when stm32_mixers_enable=0"); - - // Re-enable for subsequent tests - stm32_mixers_enable = 1; - #100; - - $display(""); - - // ================================================================ - // GROUP 4: RECEIVER CHAIN DATA FLOW - // ================================================================ - $display("--- Group 4: Receiver Chain Data Flow ---"); - - // Let the system run with chirps firing to generate receiver output. - // The receiver chain: ADC → DDC → Matched Filter → Range Decimator → Doppler - // This takes many cycles. Fire multiple chirps and wait. - obs_range_valid_count = 0; - obs_doppler_valid_count = 0; - obs_range_first_time = 0; - obs_doppler_first_time = 0; - - t_start = $time; - for (i = 0; i < 8; i = i + 1) begin - stm32_chirp_toggle; - #3000; - end - - // Wait for processing pipeline to flush - #100000; - - // G4.1: Range profile valid was observed - check(obs_range_valid_count > 0, - "G4.1: range_profile_valid_out pulsed (matched filter produced output)"); - - // G4.2: Multiple range bins were output (should be 1024 per chirp) - check(obs_range_valid_count >= 100, - "G4.2: >= 100 range profile outputs (multi-bin output)"); - - // G4.3: Range profile data is non-zero for at least some bins - // Check the DUT's internal range profile bus for non-zero - // (Observation: if range_valid_count > 0, pipeline ran — good enough) - check(obs_range_valid_count > 0, - "G4.3: Range profile processing pipeline completed"); - - // G4.4: Doppler valid was observed (requires 32 chirps per frame) - // This may not fire with only 8 chirps — check and skip gracefully - // G4.4: Doppler may not fire with only 8 chirps (needs 32/frame). - // In this short sim, we verify the pipeline ran and didn't hang. - // Full Doppler verification is in tb_doppler_cosim (unit test). - check(obs_range_valid_count > 0 || obs_doppler_valid_count > 0, - "G4.4: Processing pipeline produced output (range or doppler)"); - - // G4.5: Data flows end-to-end without hang - check($time - t_start < 300_000, - "G4.5: Processing completed within 300us budget"); - - $display(""); - - // ================================================================ - // GROUP 5: USB WRITE PATH - // ================================================================ - $display("--- Group 5: USB Write Path ---"); - - // G5.1: Verify USB writes have occurred overall (from earlier chirps too) - // The write FSM in range-only mode sends HEADER+RANGE+FOOTER packets - // whenever range_valid fires. By this point in the sim, chirps from - // Groups 2-4 have already generated range data. - check(usb_wr_count > 0, - "G5.1: USB write transactions observed"); - - // G5.2: At least one header byte was sent (0xAA) - check(usb_wr_header_count > 0, - "G5.2: USB packet header (0xAA) observed"); - - // G5.3: At least one footer byte was sent (0x55) - check(usb_wr_footer_count > 0, - "G5.3: USB packet footer (0x55) observed"); - - // G5.4: Header count matches footer count (balanced packets) - // Note: in range-only mode, packets are HEADER→RANGE_DATA→FOOTER - check(usb_wr_header_count == usb_wr_footer_count, - "G5.4: Header count == Footer count (balanced packets)"); - - // G5.5: No writes occurred during backpressure (ft601_txe deasserted) - check(obs_usb_backpressure_writes == 0, - "G5.5: No USB writes during backpressure (ft601_txe=1)"); - - $display(""); - - // ================================================================ - // GROUP 6: USB READ PATH / HOST COMMANDS (Gap 4 + Gap 2) - // ================================================================ - $display("--- Group 6: USB Read Path / Host Commands ---"); - - // G6.1: Set radar mode via USB command - bfm_send_cmd(8'h01, 8'h00, 16'h0002); // mode = 2'b10 (single chirp) - check(dut.host_radar_mode == 2'b10, - "G6.1: Opcode 0x01 -> host_radar_mode = 2'b10 (single chirp)"); - - // G6.2: Set detection threshold via USB command - bfm_send_cmd(8'h03, 8'h00, 16'h1234); - check(dut.host_detect_threshold == 16'h1234, - "G6.2: Opcode 0x03 -> host_detect_threshold = 0x1234"); - - // G6.3: Set stream control via USB command - bfm_send_cmd(8'h04, 8'h00, 16'h0005); // enable range + detect, disable doppler - check(dut.host_stream_control == 3'b101, - "G6.3: Opcode 0x04 -> host_stream_control = 3'b101"); - - // G6.4: Set chirp timing — long chirp cycles - bfm_send_cmd(8'h10, 8'h00, 16'd2000); - check(dut.host_long_chirp_cycles == 16'd2000, - "G6.4: Opcode 0x10 -> host_long_chirp_cycles = 2000"); - - // G6.5: Set chirps per elevation - bfm_send_cmd(8'h15, 8'h00, 16'd16); - check(dut.host_chirps_per_elev == 6'd16, - "G6.5: Opcode 0x15 -> host_chirps_per_elev = 16"); - - // G6.6: Trigger command (0x02) — verify pulse fires - // host_trigger_pulse is self-clearing, so we check it fired by observing - // the mode controller's trigger path. Simply verify the command was accepted. - bfm_send_cmd(8'h02, 8'h00, 16'h0000); - // Trigger pulse is self-clearing after 1 cycle — it may have already - // cleared by now. Check that the opcode was decoded (cmd_opcode register). - check(dut.usb_cmd_opcode == 8'h02, - "G6.6: Opcode 0x02 trigger command decoded correctly"); - - // 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 (TB lacks 32-chirp doppler data) - bfm_send_cmd(8'h10, 8'h00, 16'd3000); // restore long chirp cycles - - $display(""); - - // ================================================================ - // GROUP 7: CDC CROSSING STRESS - // ================================================================ - $display("--- Group 7: CDC Crossing Stress ---"); - - // G7.1: Rapid chirp toggles (stress 100MHz→120MHz toggle CDC) - // Set mode to STM32-driven (0x00) so toggles drive the RX mode controller. - // We observe mc_new_chirp toggling on the RX side as proof the CDC path - // delivered the pulses. The TX chirp controller has its own long timing - // and won't produce new_chirp_frame quickly enough for this test. - bfm_send_cmd(8'h01, 8'h00, 16'h0000); // mode = STM32-driven - saved_range_count = obs_range_valid_count; - for (i = 0; i < 10; i = i + 1) begin - stm32_chirp_toggle; - #500; // 500ns apart — rapid but allow CDC + edge detector settling - end - #20000; // Wait for chirp processing - // Verify TX-side CDC delivered at least one chirp toggle through. - // The TX chirp controller fires new_chirp_frame on IDLE→LONG_CHIRP, - // which takes the full chirp sequence. Check DAC activity instead. - check(obs_dac_nonzero_count > 0, - "G7.1: CDC delivered chirp toggles (DAC active after rapid toggles)"); - - // G7.2: Multiple USB commands in quick succession (ft601→100MHz CDC) - bfm_send_cmd(8'h03, 8'h00, 16'hAAAA); - bfm_send_cmd(8'h03, 8'h00, 16'hBBBB); - bfm_send_cmd(8'h03, 8'h00, 16'hCCCC); - check(dut.host_detect_threshold == 16'hCCCC, - "G7.2: Last of 3 rapid USB commands applied (threshold=0xCCCC)"); - - // G7.3: Verify CDC path for TX chirp counter (120MHz→100MHz) - // In the AERIS-10 architecture, STM32 toggles drive the TX chirp - // controller (120MHz domain). The chirp counter is CDC'd to 100MHz - // via Gray-code synchronizer. Verify the CDC'd counter is non-zero. - // Note: RX mode controller STM32 inputs are hardwired to 0 in - // radar_receiver_final.v, so RX-side counters don't advance in - // STM32-driven mode — this is a known architectural decision. - check(obs_max_chirp > 0 || obs_dac_nonzero_count > 100, - "G7.3: TX chirp CDC path delivered data (DAC or counter active)"); - - // G7.4: Command CDC didn't corrupt data — verify threshold is exact - check(dut.host_detect_threshold == 16'hCCCC, - "G7.4: CDC-transferred detect threshold is bit-exact (0xCCCC)"); - - // Restore detection threshold - bfm_send_cmd(8'h03, 8'h00, 16'd10000); - - $display(""); - - // ================================================================ - // GROUP 8: BEAM SCANNING / AUTO-SCAN MODE - // ================================================================ - $display("--- Group 8: Beam Scanning / Auto-Scan Mode ---"); - - // Switch to auto-scan mode - bfm_send_cmd(8'h01, 8'h00, 16'h0001); // mode = 01 = auto-scan - #100; - - // Use very short chirp timing for fast auto-scan in simulation - bfm_send_cmd(8'h10, 8'h00, 16'd100); // long chirp = 100 cycles - bfm_send_cmd(8'h11, 8'h00, 16'd200); // long listen = 200 cycles - bfm_send_cmd(8'h12, 8'h00, 16'd100); // guard = 100 cycles - bfm_send_cmd(8'h13, 8'h00, 16'd20); // short chirp = 20 cycles - bfm_send_cmd(8'h14, 8'h00, 16'd100); // short listen = 100 cycles - bfm_send_cmd(8'h15, 8'h00, 16'd4); // chirps per elev = 4 - - // Reset observation counters - obs_range_valid_count = 0; - saved_range_count = obs_range_valid_count; - - // Let auto-scan run for a while - // Total chirp cycle = 100+200+100+20+100 = 520 cycles = 5.2us per chirp - // 4 chirps/elev, so one elevation = ~21us. - #120000; // 120us — enough for multiple chirps - - // G8.1: Auto-scan generated range profile outputs (proves chirps fired) - // The receiver mode controller fires chirps autonomously in auto-scan, - // producing matched filter outputs. This verifies the auto-scan timing - // engine works end-to-end. - check(obs_range_valid_count > saved_range_count, - "G8.1: Auto-scan generated range profile output autonomously"); - - // G8.2: chirp_scheduler chirp counter advanced (chirp-v2 PR-D) - // Sub-frame id replaces "elevation" in the v2 contract. - check(dut.rx_inst.sched_chirp_counter > 0 || dut.rx_inst.sched_subframe_id > 0, - "G8.2: chirp_scheduler chirp/sub-frame counters advanced"); - - // G8.3: sub-frame index incremented (auto-scan walks SHORT->MEDIUM->LONG). - // chirps_per_subframe = 16 in PR-D, so a sub-frame transition implies the - // first 16 chirps completed. - check(dut.rx_inst.sched_subframe_id >= 1 || dut.rx_inst.sched_chirp_counter >= 6'd1, - "G8.3: sub-frame counter or chirp counter advanced in auto-scan"); - - // G8.4: Switch to single-chirp mode — auto-scan stops - bfm_send_cmd(8'h01, 8'h00, 16'h0002); // mode = 10 = single chirp - #10000; // Wait for mode to take effect through CDC - obs_chirp_frame_count = 0; - #30000; - // In single-chirp mode, no new frames should appear without trigger - // Allow at most 1 stale frame from pipeline flushing - check(obs_chirp_frame_count <= 1, - "G8.4: Single-chirp mode stops autonomous scanning (<= 1 stale)"); - - $display(""); - - // ================================================================ - // GROUP 9: MID-OPERATION RESET RECOVERY - // ================================================================ - $display("--- Group 9: Mid-Operation Reset Recovery ---"); - - // Switch back to auto-scan with short timing - bfm_send_cmd(8'h01, 8'h00, 16'h0001); // auto-scan - #20000; - - // G9.1: Assert reset during active processing - reset_n = 0; - // Reset BFM FIFO pointers so they're clean for post-reset commands - bfm_rx_wr_ptr = 0; - bfm_rx_rd_ptr = 0; - #200; - - // G9.2: After reset, system is in known state - check(system_status == 4'b0000, - "G9.2: system_status == 0 during reset"); - - // Release reset and re-initialize - reset_n = 1; - #500; - - // 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 (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 - bfm_send_cmd(8'h11, 8'h00, 16'd200); - bfm_send_cmd(8'h12, 8'h00, 16'd100); - bfm_send_cmd(8'h13, 8'h00, 16'd20); - bfm_send_cmd(8'h14, 8'h00, 16'd100); - bfm_send_cmd(8'h15, 8'h00, 16'd4); - - saved_range_count = obs_range_valid_count; - #120000; // 120us for auto-scan to produce range outputs - - // G9.3: System resumes after reset — verify range processing restarts - check(obs_range_valid_count > saved_range_count, - "G9.3: System resumed processing after reset recovery"); - - $display(""); - - // ================================================================ - // GROUP 9B: Adversarial reset sweep (audit F-2.2) - // ================================================================ - // Drive the same auto-scan pipeline, then inject reset at four distinct - // offsets relative to a known-good start of operation. For each offset - // the system must: - // (a) present system_status == 0 while held in reset - // (b) produce at least one additional new_chirp_frame within the - // observation window after reset release - // (c) advance obs_range_valid_count (confirms full DDC+MF chain resumes) - // The four offsets are chosen to hit mid-chirp, mid-listen, and around - // the short/long chirp boundary, which covers the interesting FSM and - // CDC transitions in the pipeline. - $display("--- Group 9B: Adversarial reset sweep (F-2.2) ---"); - begin : reset_sweep - integer sweep_i; - integer sweep_baseline_range; - integer sweep_baseline_chirp; - integer sweep_offsets [0:3]; - integer sweep_holds [0:3]; - reg sweep_ok; - - // Reset injection offsets (ns) after the last auto-scan reconfigure. - // 3 us / 7 us / 12 us / 18 us — sprayed across a short-chirp burst. - sweep_offsets[0] = 3000; - sweep_offsets[1] = 7000; - sweep_offsets[2] = 12000; - sweep_offsets[3] = 18000; - // Reset-assert durations mix short (~20 clk_100m) and long (~120) - sweep_holds[0] = 200; - sweep_holds[1] = 1200; - sweep_holds[2] = 400; - sweep_holds[3] = 800; - - for (sweep_i = 0; sweep_i < 4; sweep_i = sweep_i + 1) begin - // Re-seed auto-scan from a clean base each iteration - reset_n = 0; - bfm_rx_wr_ptr = 0; - bfm_rx_rd_ptr = 0; - #200; - reset_n = 1; - #500; - stm32_mixers_enable = 1; - ft601_txe = 0; - bfm_send_cmd(8'h04, 8'h00, 16'h0001); - #500; - bfm_send_cmd(8'h01, 8'h00, 16'h0001); - bfm_send_cmd(8'h10, 8'h00, 16'd100); - bfm_send_cmd(8'h11, 8'h00, 16'd200); - bfm_send_cmd(8'h12, 8'h00, 16'd100); - bfm_send_cmd(8'h13, 8'h00, 16'd20); - bfm_send_cmd(8'h14, 8'h00, 16'd100); - bfm_send_cmd(8'h15, 8'h00, 16'd4); - - // Let the pipeline reach steady-state and capture a baseline - #30000; - sweep_baseline_range = obs_range_valid_count; - sweep_baseline_chirp = obs_chirp_frame_count; - - // Wait out the configured offset, then assert reset asynchronously - #(sweep_offsets[sweep_i]); - reset_n = 0; - #(sweep_holds[sweep_i]); - sweep_ok = (system_status == 4'b0000); - check(sweep_ok, - "G9B.a: system_status drops to 0 during injected reset"); - - // Release reset, re-configure (regs are cleared), allow recovery - reset_n = 1; - #500; - stm32_mixers_enable = 1; - ft601_txe = 0; - bfm_send_cmd(8'h04, 8'h00, 16'h0001); - #500; - bfm_send_cmd(8'h01, 8'h00, 16'h0001); - bfm_send_cmd(8'h10, 8'h00, 16'd100); - bfm_send_cmd(8'h11, 8'h00, 16'd200); - bfm_send_cmd(8'h12, 8'h00, 16'd100); - bfm_send_cmd(8'h13, 8'h00, 16'd20); - bfm_send_cmd(8'h14, 8'h00, 16'd100); - bfm_send_cmd(8'h15, 8'h00, 16'd4); - - sweep_baseline_range = obs_range_valid_count; - sweep_baseline_chirp = obs_chirp_frame_count; - #60000; // 60 us — two+ short-chirp frames - - check(obs_chirp_frame_count > sweep_baseline_chirp, - "G9B.b: new_chirp_frame resumes after injected reset"); - check(obs_range_valid_count > sweep_baseline_range, - "G9B.c: range pipeline resumes after injected reset"); - - $display(" [F-2.2] iter=%0d offset=%0dns hold=%0dns chirps=+%0d ranges=+%0d", - sweep_i, sweep_offsets[sweep_i], sweep_holds[sweep_i], - obs_chirp_frame_count - sweep_baseline_chirp, - obs_range_valid_count - sweep_baseline_range); - end - end - - $display(""); - - // ================================================================ - // GROUP 10: STREAM CONTROL (Gap 2) - // ================================================================ - $display("--- Group 10: Stream Control ---"); - - // G10.1: Disable range stream - bfm_send_cmd(8'h04, 8'h00, 16'h0006); // stream_control = 3'b110 - check(dut.host_stream_control == 3'b110, - "G10.1: Range stream disabled (stream_control = 3'b110)"); - - // G10.2: Disable all streams - bfm_send_cmd(8'h04, 8'h00, 16'h0000); // stream_control = 3'b000 - check(dut.host_stream_control == 3'b000, - "G10.2: All streams disabled (stream_control = 3'b000)"); - - // 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)"); - - $display(""); - - // ================================================================ - // GROUP 11: PROCESSING LATENCY BUDGETS - // ================================================================ - $display("--- Group 11: Processing Latency Budgets ---"); - - // Trigger chirps and measure time to first range output - obs_range_valid_count = 0; - obs_range_first_time = 0; - t_start = $time; - - for (i = 0; i < 4; i = i + 1) begin - stm32_chirp_toggle; - #3000; - end - #80000; - - // G11.1: Range output appeared within 200us of chirp start - if (obs_range_first_time > 0) begin - check((obs_range_first_time - t_start) < 200_000, - "G11.1: First range output within 200us of chirp start"); - end else begin - check(0, "G11.1: First range output within 200us of chirp start (NO OUTPUT)"); - end - - // G11.2: Range output count indicates real processing (not stuck at 0) - check(obs_range_valid_count > 0, - "G11.2: Range profile outputs generated in latency test"); - - $display(""); - - // ================================================================ - // GROUP 12: WATCHDOG / LIVENESS - // ================================================================ - $display("--- Group 12: Watchdog / Liveness ---"); - - // G12.1: System hasn't hung AND prior groups actually ran assertions. - // `check(1, ...)` alone is tautological; we additionally require that a - // meaningful number of earlier checks executed (catches a TB that skips - // past test groups via an @(posedge) that never fires or similar stall). - check(test_num > 20, - "G12.1: System reached final group AND >20 prior checks executed"); - - // G12.2: Total simulation time is within budget - check($time < SIM_TIMEOUT_NS, - "G12.2: Total sim time within 2ms budget"); - - $display(""); - - // ================================================================ - // GROUP 13: DOPPLER/CHIRPS MISMATCH PROTECTION (Fix 4) - // ================================================================ - $display("--- Group 13: Doppler/Chirps Mismatch Protection ---"); - - // G13.1: Setting chirps_per_elev = 32 (matching DOPPLER_FFT_SIZE) clears error - bfm_send_cmd(8'h15, 8'h00, 16'd32); - check(dut.host_chirps_per_elev == 6'd32, - "G13.1: chirps_per_elev=32 accepted (matches FFT size)"); - - // G13.2: Error flag is clear when value matches - check(dut.chirps_mismatch_error == 1'b0, - "G13.2: Mismatch error clear when chirps==DOPPLER_FFT_SIZE"); - - // G13.3: Setting chirps_per_elev > 32 gets clamped to 32 - bfm_send_cmd(8'h15, 8'h00, 16'd48); - check(dut.host_chirps_per_elev == 6'd32, - "G13.3: chirps_per_elev=48 clamped to 32"); - - // G13.4: Mismatch error flag set after clamping - check(dut.chirps_mismatch_error == 1'b1, - "G13.4: Mismatch error set when chirps>DOPPLER_FFT_SIZE"); - - // G13.5: Setting chirps_per_elev = 0 gets clamped to 32 - bfm_send_cmd(8'h15, 8'h00, 16'd0); - check(dut.host_chirps_per_elev == 6'd32, - "G13.5: chirps_per_elev=0 clamped to 32"); - - // G13.6: Value < 32 is accepted but flagged as mismatch - bfm_send_cmd(8'h15, 8'h00, 16'd16); - check(dut.host_chirps_per_elev == 6'd16, - "G13.6: chirps_per_elev=16 accepted (not clamped)"); - check(dut.chirps_mismatch_error == 1'b1, - "G13.7: Mismatch error set when chirps host_range_mode = 2'b01 (long-range)"); - - // G14.2: Set range_mode to reserved value 0x02 (permissive: stored as-is) - bfm_send_cmd(8'h20, 8'h00, 16'h0002); - check(dut.host_range_mode == 2'b10, - "G14.2: Opcode 0x20 -> host_range_mode = 2'b10 (reserved)"); - - // G14.3: Restore range_mode to 3 km (0x00) - bfm_send_cmd(8'h20, 8'h00, 16'h0000); - check(dut.host_range_mode == 2'b00, - "G14.3: Opcode 0x20 -> host_range_mode = 2'b00 (3 km)"); - - // --- CFAR Guard Cells (0x21) --- - // G14.4: Set guard cells to 4 - bfm_send_cmd(8'h21, 8'h00, 16'h0004); - check(dut.host_cfar_guard == 4'd4, - "G14.4: Opcode 0x21 -> host_cfar_guard = 4"); - - // G14.5: Set guard cells to 0 (valid edge case) - bfm_send_cmd(8'h21, 8'h00, 16'h0000); - check(dut.host_cfar_guard == 4'd0, - "G14.5: Opcode 0x21 -> host_cfar_guard = 0 (edge case)"); - - // --- CFAR Training Cells (0x22) --- - // G14.6: Set training cells to 16 - bfm_send_cmd(8'h22, 8'h00, 16'h0010); - check(dut.host_cfar_train == 5'd16, - "G14.6: Opcode 0x22 -> host_cfar_train = 16"); - - // G14.7: Set training cells to 1 (minimum) - bfm_send_cmd(8'h22, 8'h00, 16'h0001); - check(dut.host_cfar_train == 5'd1, - "G14.7: Opcode 0x22 -> host_cfar_train = 1 (min)"); - - // --- CFAR Alpha / Threshold Multiplier (0x23) --- - // G14.8: Set alpha to 0x48 (4.5 in Q4.4) - bfm_send_cmd(8'h23, 8'h00, 16'h0048); - check(dut.host_cfar_alpha == 8'h48, - "G14.8: Opcode 0x23 -> host_cfar_alpha = 0x48 (4.5 Q4.4)"); - - // G14.9: Set alpha to 0x10 (1.0 in Q4.4) - bfm_send_cmd(8'h23, 8'h00, 16'h0010); - check(dut.host_cfar_alpha == 8'h10, - "G14.9: Opcode 0x23 -> host_cfar_alpha = 0x10 (1.0 Q4.4)"); - - // --- CFAR Mode (0x24) --- - // G14.10: Set mode to GO-CFAR (0x01) - bfm_send_cmd(8'h24, 8'h00, 16'h0001); - check(dut.host_cfar_mode == 2'b01, - "G14.10: Opcode 0x24 -> host_cfar_mode = 2'b01 (GO-CFAR)"); - - // G14.11: Set mode to SO-CFAR (0x02) - bfm_send_cmd(8'h24, 8'h00, 16'h0002); - check(dut.host_cfar_mode == 2'b10, - "G14.11: Opcode 0x24 -> host_cfar_mode = 2'b10 (SO-CFAR)"); - - // --- CFAR Enable (0x25) --- - // G14.12: Enable CFAR - bfm_send_cmd(8'h25, 8'h00, 16'h0001); - check(dut.host_cfar_enable == 1'b1, - "G14.12: Opcode 0x25 -> host_cfar_enable = 1 (CFAR active)"); - - // G14.13: Disable CFAR (restore default) - bfm_send_cmd(8'h25, 8'h00, 16'h0000); - check(dut.host_cfar_enable == 1'b0, - "G14.13: Opcode 0x25 -> host_cfar_enable = 0 (simple threshold)"); - - // Restore CFAR registers to safe defaults for remainder of sim - bfm_send_cmd(8'h21, 8'h00, 16'h0002); // guard=2 - bfm_send_cmd(8'h22, 8'h00, 16'h0008); // train=8 - bfm_send_cmd(8'h23, 8'h00, 16'h0030); // alpha=3.0 - bfm_send_cmd(8'h24, 8'h00, 16'h0000); // mode=CA - bfm_send_cmd(8'h25, 8'h00, 16'h0000); // enable=0 - - $display(""); - - // ================================================================ - // FINAL SUMMARY - // ================================================================ - $display("============================================================"); - $display(" AERIS-10 End-to-End Integration Testbench"); - $display("============================================================"); - $display(" PASSED: %0d / %0d", pass_count, test_num); - $display(" FAILED: %0d / %0d", fail_count, test_num); - $display(" Total simulation time: %0t ns", $time); - $display("------------------------------------------------------------"); - $display(" Observation Summary:"); - $display(" Chirp frames: %0d", obs_chirp_frame_count); - $display(" DAC non-zero samples: %0d", obs_dac_nonzero_count); - $display(" Range valid pulses: %0d", obs_range_valid_count); - $display(" Doppler valid pulses: %0d", obs_doppler_valid_count); - $display(" USB write packets: %0d", usb_wr_count); - $display(" USB headers: %0d", usb_wr_header_count); - $display(" USB footers: %0d", usb_wr_footer_count); - $display(" Mixer deassert fails: %0d", safety_mixer_deassert_fail_count); - $display(" Max chirp counter: %0d", obs_max_chirp); - $display(" Max elevation: %0d", obs_max_elevation); - $display(" Max azimuth: %0d", obs_max_azimuth); - $display("============================================================"); - - if (fail_count == 0) - $display(" *** ALL TESTS PASSED ***"); - else - $display(" *** %0d TEST(S) FAILED ***", fail_count); - - $display("============================================================"); - $display(""); - $finish; -end - -// ============================================================================ -// SIMULATION TIMEOUT WATCHDOG -// ============================================================================ -initial begin - #(SIM_TIMEOUT_NS + 100_000); - $display(""); - $display("[WATCHDOG] Simulation exceeded %0d ns timeout — ABORTING", SIM_TIMEOUT_NS); - $display(" Tests completed: %0d, Passed: %0d, Failed: %0d", - test_num, pass_count, fail_count); - $display(""); - $finish; -end - -endmodule diff --git a/9_Firmware/9_2_FPGA/tb/tb_usb_protocol_v2.v b/9_Firmware/9_2_FPGA/tb/tb_usb_protocol_v2.v index de10fc1..de11ed2 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_usb_protocol_v2.v +++ b/9_Firmware/9_2_FPGA/tb/tb_usb_protocol_v2.v @@ -17,7 +17,7 @@ // 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). +// radar_system_top, exercised at integration level by tb_system_opcodes). // ============================================================================ module tb_usb_protocol_v2;