run_regression.sh
1 #!/bin/bash 2 # =========================================================================== 3 # FPGA Regression Test Runner for AERIS-10 Radar 4 # Phase 0: Vivado-style lint (catches issues iverilog silently accepts) 5 # Phase 1+: Compile and run all verified iverilog testbenches 6 # 7 # Usage: ./run_regression.sh [--quick] [--skip-lint] 8 # --quick Skip long-running integration tests (receiver golden, system TB) 9 # --skip-lint Skip Phase 0 lint checks (not recommended) 10 # 11 # Exit code: 0 if all tests pass, 1 if any fail 12 # =========================================================================== 13 14 set -euo pipefail 15 16 SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" 17 cd "$SCRIPT_DIR" 18 19 QUICK=0 20 SKIP_LINT=0 21 for arg in "$@"; do 22 case "$arg" in 23 --quick) QUICK=1 ;; 24 --skip-lint) SKIP_LINT=1 ;; 25 esac 26 done 27 28 PASS=0 29 FAIL=0 30 SKIP=0 31 LINT_WARN=0 32 LINT_ERR=0 33 ERRORS="" 34 35 # Colors (if terminal supports it) 36 RED='\033[0;31m' 37 GREEN='\033[0;32m' 38 YELLOW='\033[0;33m' 39 CYAN='\033[0;36m' 40 NC='\033[0m' # No Color 41 42 # =========================================================================== 43 # PHASE 0: VIVADO-STYLE LINT 44 # Two layers: 45 # (A) iverilog -Wall full-design compile — parse for serious warnings 46 # (B) Custom regex checks for patterns Vivado treats as errors 47 # =========================================================================== 48 49 # Production RTL file list (same as system TB minus testbench files) 50 # Uses ADC stub for IBUFDS/BUFIO primitives that iverilog can't parse 51 PROD_RTL=( 52 radar_system_top.v 53 radar_transmitter.v 54 dac_interface_single.v 55 plfm_chirp_controller.v 56 radar_receiver_final.v 57 tb/ad9484_interface_400m_stub.v 58 ddc_400m.v 59 nco_400m_enhanced.v 60 cic_decimator_4x_enhanced.v 61 cdc_modules.v 62 fir_lowpass.v 63 ddc_input_interface.v 64 chirp_memory_loader_param.v 65 latency_buffer.v 66 matched_filter_multi_segment.v 67 matched_filter_processing_chain.v 68 range_bin_decimator.v 69 doppler_processor.v 70 xfft_16.v 71 fft_engine.v 72 usb_data_interface.v 73 edge_detector.v 74 radar_mode_controller.v 75 rx_gain_control.v 76 cfar_ca.v 77 mti_canceller.v 78 fpga_self_test.v 79 ) 80 81 # Source-only RTL (not instantiated at top level, but should still be lint-clean) 82 # Note: ad9484_interface_400m.v is excluded — it uses Xilinx primitives 83 # (IBUFDS, BUFIO, BUFG, IDDR) that iverilog cannot compile. The production 84 # design uses tb/ad9484_interface_400m_stub.v for simulation instead. 85 EXTRA_RTL=( 86 frequency_matched_filter.v 87 ) 88 89 # ---- Layer A: iverilog -Wall compilation ---- 90 run_lint_iverilog() { 91 local label="$1" 92 shift 93 local files=("$@") 94 local warn_file="/tmp/iverilog_lint_$$_${label}.log" 95 96 printf " %-45s " "iverilog -Wall ($label)" 97 98 if ! iverilog -g2001 -DSIMULATION -Wall -o /dev/null "${files[@]}" 2>"$warn_file"; then 99 # Hard compile error — always fatal 100 echo -e "${RED}COMPILE ERROR${NC}" 101 while IFS= read -r line; do 102 echo " $line" 103 done < "$warn_file" 104 LINT_ERR=$((LINT_ERR + 1)) 105 rm -f "$warn_file" 106 return 1 107 fi 108 109 # Parse warnings — classify as error-level or info-level 110 local err_count=0 111 local info_count=0 112 local err_lines="" 113 114 while IFS= read -r line; do 115 # Part-select out of range — Vivado Synth 8-524 (ERROR in Vivado) 116 if echo "$line" | grep -q 'Part select.*is selecting after the vector\|out of bound bits'; then 117 err_count=$((err_count + 1)) 118 err_lines="$err_lines\n ${RED}[VIVADO-ERR]${NC} $line" 119 # Port width mismatch / connection mismatch 120 elif echo "$line" | grep -q 'port.*does not match\|Port.*mismatch'; then 121 err_count=$((err_count + 1)) 122 err_lines="$err_lines\n ${RED}[VIVADO-ERR]${NC} $line" 123 # Informational warnings (timescale, dangling ports, array sensitivity) 124 elif echo "$line" | grep -q 'timescale\|dangling\|sensitive to all'; then 125 info_count=$((info_count + 1)) 126 # Unknown warning — report but don't fail 127 elif [[ -n "$line" ]]; then 128 info_count=$((info_count + 1)) 129 fi 130 done < "$warn_file" 131 132 if [[ "$err_count" -gt 0 ]]; then 133 echo -e "${RED}FAIL${NC} ($err_count Vivado-class errors, $info_count info)" 134 echo -e "$err_lines" 135 LINT_ERR=$((LINT_ERR + err_count)) 136 else 137 echo -e "${GREEN}PASS${NC} ($info_count info warnings)" 138 fi 139 140 rm -f "$warn_file" 141 } 142 143 # ---- Layer B: Custom regex static checks ---- 144 # Catches patterns that Vivado treats as errors/warnings but iverilog ignores 145 run_lint_static() { 146 printf " %-45s " "Static RTL checks" 147 148 local err_count=0 149 local warn_count=0 150 local err_lines="" 151 local warn_lines="" 152 153 for f in "$@"; do 154 [[ -f "$f" ]] || continue 155 # Skip testbench files (tb/ directory) — only lint production RTL 156 case "$f" in tb/*) continue ;; esac 157 158 local linenum=0 159 while IFS= read -r line; do 160 linenum=$((linenum + 1)) 161 162 # --- CHECK 1: Part-select with literal range on reg --- 163 # Pattern: identifier[N:M] where N exceeds declared width 164 # (iverilog catches this, but belt-and-suspenders) 165 166 # --- CHECK 2: case/casex/casez without default (non-full case) --- 167 # Vivado SYNTH-6 / inferred latch warning 168 # Heuristic: look for case/casex/casez, then check if 'default' appears 169 # before the matching 'endcase'. This is approximate — full parsing 170 # would need a real parser. We flag 'case' lines so the developer 171 # can manually verify. 172 # (Handled below as a multi-line check) 173 174 # --- CHECK 3: Blocking assignment (=) inside always @(posedge ...) --- 175 # Vivado SYNTH-5 warning for inferred latches / race conditions 176 # Only flag if the always block is clocked (posedge/negedge) 177 # This is a heuristic — we check for '= ' that isn't '<=', '==', '!=' 178 # inside an always block header containing 'posedge' or 'negedge'. 179 # (Too complex for line-by-line — skip for now, handled by testbenches) 180 181 # --- CHECK 4: Multi-driven register (assign + always on same signal) --- 182 # (Would need cross-file analysis — skip for v1) 183 184 done < "$f" 185 done 186 187 # --- Multi-line check: case without default --- 188 for f in "$@"; do 189 [[ -f "$f" ]] || continue 190 case "$f" in tb/*) continue ;; esac 191 192 # Find case blocks and check for default 193 # Use awk to find case..endcase blocks missing 'default' 194 local missing_defaults 195 missing_defaults=$(awk ' 196 /^[[:space:]]*(case|casex|casez)[[:space:]]*\(/ { 197 case_line = NR 198 case_file = FILENAME 199 has_default = 0 200 in_case = 1 201 next 202 } 203 in_case && /default[[:space:]]*:/ { 204 has_default = 1 205 } 206 in_case && /endcase/ { 207 if (!has_default) { 208 printf "%s:%d: case statement without default\n", FILENAME, case_line 209 } 210 in_case = 0 211 } 212 ' "$f" 2>/dev/null) 213 214 if [[ -n "$missing_defaults" ]]; then 215 while IFS= read -r hit; do 216 warn_count=$((warn_count + 1)) 217 warn_lines="$warn_lines\n ${YELLOW}[SYNTH-6]${NC} $hit" 218 done <<< "$missing_defaults" 219 fi 220 done 221 222 # --- Single-line regex checks across all production RTL --- 223 for f in "$@"; do 224 [[ -f "$f" ]] || continue 225 case "$f" in tb/*) continue ;; esac 226 227 local linenum=0 228 while IFS= read -r line; do 229 linenum=$((linenum + 1)) 230 231 # CHECK 5: $readmemh / $readmemb in synthesizable code 232 # (Only valid in simulation blocks — flag if outside `ifdef SIMULATION) 233 # This is hard to check line-by-line without tracking ifdefs. 234 # Skip for v1. 235 236 # CHECK 6: Unused `include files (informational only) 237 # Skip for v1. 238 239 : # placeholder — prevents empty loop body 240 done < "$f" 241 done 242 243 if [[ "$err_count" -gt 0 ]]; then 244 echo -e "${RED}FAIL${NC} ($err_count errors, $warn_count warnings)" 245 echo -e "$err_lines" 246 LINT_ERR=$((LINT_ERR + err_count)) 247 elif [[ "$warn_count" -gt 0 ]]; then 248 echo -e "${YELLOW}WARN${NC} ($warn_count warnings)" 249 echo -e "$warn_lines" 250 LINT_WARN=$((LINT_WARN + warn_count)) 251 else 252 echo -e "${GREEN}PASS${NC}" 253 fi 254 } 255 256 # --------------------------------------------------------------------------- 257 # Helper: compile and run a single testbench 258 # run_test <name> <vvp_path> <iverilog_args...> 259 # --------------------------------------------------------------------------- 260 run_test() { 261 local name="$1" 262 local vvp="$2" 263 shift 2 264 local args=("$@") 265 266 printf " %-45s " "$name" 267 268 # Compile 269 if ! iverilog -g2001 -DSIMULATION -o "$vvp" "${args[@]}" 2>/tmp/iverilog_err_$$; then 270 echo -e "${RED}COMPILE FAIL${NC}" 271 ERRORS="$ERRORS\n $name: compile error ($(head -1 /tmp/iverilog_err_$$))" 272 FAIL=$((FAIL + 1)) 273 return 274 fi 275 276 # Run 277 local output 278 output=$(timeout 120 vvp "$vvp" 2>&1) || true 279 280 # Count PASS/FAIL in output (testbenches use explicit [PASS]/[FAIL] markers) 281 local test_pass test_fail 282 test_pass=$(echo "$output" | grep -Ec '^\[PASS([^]]*)\]' || true) 283 test_fail=$(echo "$output" | grep -Ec '^\[FAIL([^]]*)\]' || true) 284 285 if [[ "$test_fail" -gt 0 ]]; then 286 echo -e "${RED}FAIL${NC} (pass=$test_pass, fail=$test_fail)" 287 ERRORS="$ERRORS\n $name: $test_fail failure(s)" 288 FAIL=$((FAIL + 1)) 289 elif [[ "$test_pass" -gt 0 ]]; then 290 echo -e "${GREEN}PASS${NC} ($test_pass checks)" 291 PASS=$((PASS + 1)) 292 else 293 # No PASS/FAIL markers — check for clean completion 294 if echo "$output" | grep -qi 'finish\|complete\|done'; then 295 echo -e "${GREEN}PASS${NC} (completed)" 296 PASS=$((PASS + 1)) 297 else 298 echo -e "${YELLOW}UNKNOWN${NC} (no PASS/FAIL markers)" 299 ERRORS="$ERRORS\n $name: no pass/fail markers in output" 300 FAIL=$((FAIL + 1)) 301 fi 302 fi 303 304 rm -f "$vvp" 305 } 306 307 # =========================================================================== 308 echo "============================================" 309 echo " AERIS-10 FPGA Regression Test Suite" 310 echo "============================================" 311 echo "" 312 echo "Date: $(date)" 313 echo "iverilog: $(iverilog -V 2>&1 | head -1)" 314 echo "" 315 316 # =========================================================================== 317 # PHASE 0: LINT (Vivado-class error detection) 318 # =========================================================================== 319 if [[ "$SKIP_LINT" -eq 0 ]]; then 320 echo "--- PHASE 0: LINT (Vivado-class checks) ---" 321 322 # Layer A: iverilog -Wall on full production design 323 run_lint_iverilog "production" "${PROD_RTL[@]}" 324 325 # Layer A: standalone modules not in top-level hierarchy 326 for extra in "${EXTRA_RTL[@]}"; do 327 if [[ -f "$extra" ]]; then 328 run_lint_iverilog "$(basename "$extra" .v)" "$extra" 329 fi 330 done 331 332 # Layer B: custom static regex checks 333 ALL_RTL=("${PROD_RTL[@]}" "${EXTRA_RTL[@]}") 334 run_lint_static "${ALL_RTL[@]}" 335 336 echo "" 337 if [[ "$LINT_ERR" -gt 0 ]]; then 338 echo -e "${RED} LINT FAILED: $LINT_ERR Vivado-class error(s) detected.${NC}" 339 echo " Fix lint errors before pushing to Vivado. Aborting regression." 340 echo "" 341 exit 1 342 elif [[ "$LINT_WARN" -gt 0 ]]; then 343 echo -e "${YELLOW} LINT: $LINT_WARN advisory warning(s) (non-blocking)${NC}" 344 else 345 echo -e "${GREEN} LINT: All checks passed${NC}" 346 fi 347 echo "" 348 else 349 echo "--- PHASE 0: LINT (skipped via --skip-lint) ---" 350 echo "" 351 fi 352 353 # =========================================================================== 354 # PHASE 1: UNIT TESTS — Changed Modules (HIGH PRIORITY) 355 # =========================================================================== 356 echo "--- PHASE 1: Changed Modules ---" 357 358 run_test "CIC Decimator" \ 359 tb/tb_cic_reg.vvp \ 360 tb/tb_cic_decimator.v cic_decimator_4x_enhanced.v 361 362 run_test "Chirp Controller (BRAM)" \ 363 tb/tb_chirp_reg.vvp \ 364 tb/tb_chirp_controller.v plfm_chirp_controller.v 365 366 run_test "Chirp Contract" \ 367 tb/tb_chirp_ctr_reg.vvp \ 368 tb/tb_chirp_contract.v plfm_chirp_controller.v 369 370 run_test "Doppler Processor (DSP48)" \ 371 tb/tb_doppler_reg.vvp \ 372 tb/tb_doppler_cosim.v doppler_processor.v xfft_16.v fft_engine.v 373 374 run_test "Threshold Detector (detection bugs)" \ 375 tb/tb_threshold_detector.vvp \ 376 tb/tb_threshold_detector.v 377 378 run_test "RX Gain Control (digital gain)" \ 379 tb/tb_rx_gain_control.vvp \ 380 tb/tb_rx_gain_control.v rx_gain_control.v 381 382 run_test "MTI Canceller (ground clutter)" \ 383 tb/tb_mti_canceller.vvp \ 384 tb/tb_mti_canceller.v mti_canceller.v 385 386 run_test "CFAR CA Detector" \ 387 tb/tb_cfar_ca.vvp \ 388 tb/tb_cfar_ca.v cfar_ca.v 389 390 run_test "FPGA Self-Test" \ 391 tb/tb_fpga_self_test.vvp \ 392 tb/tb_fpga_self_test.v fpga_self_test.v 393 394 echo "" 395 396 # =========================================================================== 397 # PHASE 2: INTEGRATION TESTS 398 # =========================================================================== 399 echo "--- PHASE 2: Integration Tests ---" 400 401 run_test "DDC Chain (NCO→CIC→FIR)" \ 402 tb/tb_ddc_reg.vvp \ 403 tb/tb_ddc_cosim.v ddc_400m.v nco_400m_enhanced.v \ 404 cic_decimator_4x_enhanced.v fir_lowpass.v cdc_modules.v 405 406 if [[ "$QUICK" -eq 0 ]]; then 407 # Golden generate 408 run_test "Receiver (golden generate)" \ 409 tb/tb_rx_golden_reg.vvp \ 410 -DGOLDEN_GENERATE \ 411 tb/tb_radar_receiver_final.v radar_receiver_final.v \ 412 radar_mode_controller.v tb/ad9484_interface_400m_stub.v \ 413 ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v \ 414 cdc_modules.v fir_lowpass.v ddc_input_interface.v \ 415 chirp_memory_loader_param.v latency_buffer.v \ 416 matched_filter_multi_segment.v matched_filter_processing_chain.v \ 417 range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \ 418 rx_gain_control.v mti_canceller.v 419 420 # Golden compare 421 run_test "Receiver (golden compare)" \ 422 tb/tb_rx_compare_reg.vvp \ 423 tb/tb_radar_receiver_final.v radar_receiver_final.v \ 424 radar_mode_controller.v tb/ad9484_interface_400m_stub.v \ 425 ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v \ 426 cdc_modules.v fir_lowpass.v ddc_input_interface.v \ 427 chirp_memory_loader_param.v latency_buffer.v \ 428 matched_filter_multi_segment.v matched_filter_processing_chain.v \ 429 range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \ 430 rx_gain_control.v mti_canceller.v 431 432 # Full system top (monitoring-only, legacy) 433 run_test "System Top (radar_system_tb)" \ 434 tb/tb_system_reg.vvp \ 435 tb/radar_system_tb.v radar_system_top.v \ 436 radar_transmitter.v dac_interface_single.v plfm_chirp_controller.v \ 437 radar_receiver_final.v tb/ad9484_interface_400m_stub.v \ 438 ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v \ 439 cdc_modules.v fir_lowpass.v ddc_input_interface.v \ 440 chirp_memory_loader_param.v latency_buffer.v \ 441 matched_filter_multi_segment.v matched_filter_processing_chain.v \ 442 range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \ 443 usb_data_interface.v edge_detector.v radar_mode_controller.v \ 444 rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v 445 446 # E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset) 447 run_test "System E2E (tb_system_e2e)" \ 448 tb/tb_system_e2e_reg.vvp \ 449 tb/tb_system_e2e.v radar_system_top.v \ 450 radar_transmitter.v dac_interface_single.v plfm_chirp_controller.v \ 451 radar_receiver_final.v tb/ad9484_interface_400m_stub.v \ 452 ddc_400m.v nco_400m_enhanced.v cic_decimator_4x_enhanced.v \ 453 cdc_modules.v fir_lowpass.v ddc_input_interface.v \ 454 chirp_memory_loader_param.v latency_buffer.v \ 455 matched_filter_multi_segment.v matched_filter_processing_chain.v \ 456 range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \ 457 usb_data_interface.v edge_detector.v radar_mode_controller.v \ 458 rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v 459 else 460 echo " (skipped receiver golden + system top + E2E — use without --quick)" 461 SKIP=$((SKIP + 4)) 462 fi 463 464 echo "" 465 466 # =========================================================================== 467 # PHASE 3: UNIT TESTS — Signal Processing 468 # =========================================================================== 469 echo "--- PHASE 3: Signal Processing ---" 470 471 run_test "FFT Engine" \ 472 tb/tb_fft_reg.vvp \ 473 tb/tb_fft_engine.v fft_engine.v 474 475 run_test "NCO 400MHz" \ 476 tb/tb_nco_reg.vvp \ 477 tb/tb_nco_400m.v nco_400m_enhanced.v 478 479 run_test "FIR Lowpass" \ 480 tb/tb_fir_reg.vvp \ 481 tb/tb_fir_lowpass.v fir_lowpass.v 482 483 run_test "Matched Filter Chain" \ 484 tb/tb_mf_reg.vvp \ 485 tb/tb_matched_filter_processing_chain.v matched_filter_processing_chain.v \ 486 fft_engine.v chirp_memory_loader_param.v 487 488 echo "" 489 490 # =========================================================================== 491 # PHASE 4: UNIT TESTS — Infrastructure 492 # =========================================================================== 493 echo "--- PHASE 4: Infrastructure ---" 494 495 run_test "CDC Modules (3 variants)" \ 496 tb/tb_cdc_reg.vvp \ 497 tb/tb_cdc_modules.v cdc_modules.v 498 499 run_test "Edge Detector" \ 500 tb/tb_edge_reg.vvp \ 501 tb/tb_edge_detector.v edge_detector.v 502 503 run_test "USB Data Interface" \ 504 tb/tb_usb_reg.vvp \ 505 tb/tb_usb_data_interface.v usb_data_interface.v 506 507 run_test "Range Bin Decimator" \ 508 tb/tb_rbd_reg.vvp \ 509 tb/tb_range_bin_decimator.v range_bin_decimator.v 510 511 run_test "Radar Mode Controller" \ 512 tb/tb_rmc_reg.vvp \ 513 tb/tb_radar_mode_controller.v radar_mode_controller.v 514 515 echo "" 516 517 # =========================================================================== 518 # SUMMARY 519 # =========================================================================== 520 TOTAL=$((PASS + FAIL + SKIP)) 521 echo "============================================" 522 echo " RESULTS" 523 echo "============================================" 524 if [[ "$SKIP_LINT" -eq 0 ]]; then 525 if [[ "$LINT_ERR" -gt 0 ]]; then 526 echo -e " Lint: ${RED}$LINT_ERR error(s)${NC}, $LINT_WARN warning(s)" 527 elif [[ "$LINT_WARN" -gt 0 ]]; then 528 echo -e " Lint: ${GREEN}0 errors${NC}, ${YELLOW}$LINT_WARN warning(s)${NC}" 529 else 530 echo -e " Lint: ${GREEN}clean${NC}" 531 fi 532 fi 533 echo " Tests: $PASS passed, $FAIL failed, $SKIP skipped / $TOTAL total" 534 echo "============================================" 535 536 if [[ -n "$ERRORS" ]]; then 537 echo "" 538 echo "Failures:" 539 echo -e "$ERRORS" 540 fi 541 542 echo "" 543 544 # Exit with error if any failures 545 if [[ "$FAIL" -gt 0 ]]; then 546 exit 1 547 fi 548 549 exit 0