/ 9_Firmware / 9_2_FPGA / run_regression.sh
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