Files
NawfalMotii79-PLFM_RADAR/9_Firmware/9_2_FPGA/run_regression.sh
T
Jason 0c82de54a2 fft_engine_axi_bridge: respect axi_din_tready with 1-deep skid buffer
Bug: bridge advanced in_count and asserted tlast on din_valid alone,
ignoring the IP's tready handshake. With LogiCORE FFT v9.1 in
nonrealtime throttle mode (per .xci), tready can deassert briefly
during BFP normalization or pipeline events, silently dropping input
samples and shifting tlast off-by-N.

Fix: add 1-deep skid buffer + AXI-correct handshake. Phase 1 drains
the active beat when the IP accepts it (and shifts skid up); Phase 2
loads new upstream samples respecting post-handshake slot availability.
Track accept_count separately from in_count to drive the S_FEED->S_DRAIN
transition on the Nth accepted beat. Sustained 2+ cycle backpressure
exhausts the skid and sets overflow_sticky for debug visibility.

Audit cross-refs (AUDIT-C10):
- "tready ignored" - CONFIRMED, fixed here
- "SCALE_SCH unset" - REFUTED (BFP mode uses tuser, not cfg_tdata)
- "output ordering not configured" - REFUTED (.xci natural_order)

Verification: new tb_fft_engine_axi_bridge.v with stub xfft_2048
exercises 4 backpressure patterns (none / dip-at-3 / dip-at-100 /
3-cycle sustained). Quick regression 30/30 PASS.
2026-04-29 17:24:21 +05:45

734 lines
25 KiB
Bash
Executable File

#!/bin/bash
# ===========================================================================
# FPGA Regression Test Runner for AERIS-10 Radar
# Phase 0: Vivado-style lint (catches issues iverilog silently accepts)
# Phase 1+: Compile and run all verified iverilog testbenches
#
# Usage: ./run_regression.sh [--quick] [--skip-lint]
# --quick Skip long-running integration tests (receiver golden, system TB)
# --skip-lint Skip Phase 0 lint checks (not recommended)
#
# Exit code: 0 if all tests pass, 1 if any fail
# ===========================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
QUICK=0
SKIP_LINT=0
for arg in "$@"; do
case "$arg" in
--quick) QUICK=1 ;;
--skip-lint) SKIP_LINT=1 ;;
esac
done
PASS=0
FAIL=0
SKIP=0
LINT_WARN=0
LINT_ERR=0
ERRORS=""
# Colors (if terminal supports it)
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# ===========================================================================
# PHASE 0: VIVADO-STYLE LINT
# Two layers:
# (A) iverilog -Wall full-design compile — parse for serious warnings
# (B) Custom regex checks for patterns Vivado treats as errors
# ===========================================================================
# Production RTL file list (same as system TB minus testbench files)
# Uses ADC stub for IBUFDS/BUFIO primitives that iverilog can't parse
PROD_RTL=(
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_memory_loader_param.v
matched_filter_multi_segment.v
matched_filter_processing_chain.v
range_bin_decimator.v
doppler_processor.v
xfft_16.v
fft_engine.v
xfft_2048.v
fft_engine_axi_bridge.v
frequency_matched_filter.v
usb_data_interface.v
usb_data_interface_ft2232h.v
edge_detector.v
radar_mode_controller.v
rx_gain_control.v
cfar_ca.v
mti_canceller.v
fpga_self_test.v
)
# Source-only RTL (not instantiated at top level, but should still be lint-clean)
# Note: ad9484_interface_400m.v is excluded — it uses Xilinx primitives
# (IBUFDS, BUFIO, BUFG, IDDR) that iverilog cannot compile. The production
# design uses tb/ad9484_interface_400m_stub.v for simulation instead.
EXTRA_RTL=(
)
# ---------------------------------------------------------------------------
# Shared RTL file lists for integration / system tests
# Centralised here so a new module only needs adding once.
# ---------------------------------------------------------------------------
# Receiver chain (used by golden generate/compare tests)
RECEIVER_RTL=(
radar_receiver_final.v
radar_mode_controller.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_memory_loader_param.v
matched_filter_multi_segment.v matched_filter_processing_chain.v
range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v
xfft_2048.v fft_engine_axi_bridge.v
frequency_matched_filter.v
rx_gain_control.v mti_canceller.v
)
# Full system top (receiver chain + TX + USB + detection + self-test)
SYSTEM_RTL=(
radar_system_top.v
radar_transmitter.v dac_interface_single.v plfm_chirp_controller.v
"${RECEIVER_RTL[@]}"
usb_data_interface.v usb_data_interface_ft2232h.v edge_detector.v
cfar_ca.v fpga_self_test.v
)
# ---- Layer A: iverilog -Wall compilation ----
run_lint_iverilog() {
local label="$1"
shift
local files=("$@")
local warn_file="/tmp/iverilog_lint_$$_${label}.log"
printf " %-45s " "iverilog -Wall ($label)"
if ! iverilog -g2001 -DSIMULATION -Wall -o /dev/null "${files[@]}" 2>"$warn_file"; then
# Hard compile error — always fatal
echo -e "${RED}COMPILE ERROR${NC}"
while IFS= read -r line; do
echo " $line"
done < "$warn_file"
LINT_ERR=$((LINT_ERR + 1))
rm -f "$warn_file"
return 1
fi
# Parse warnings — classify as error-level or info-level
local err_count=0
local info_count=0
local err_lines=""
while IFS= read -r line; do
# Part-select out of range — Vivado Synth 8-524 (ERROR in Vivado)
if echo "$line" | grep -q 'Part select.*is selecting after the vector\|out of bound bits'; then
err_count=$((err_count + 1))
err_lines="$err_lines\n ${RED}[VIVADO-ERR]${NC} $line"
# Port width mismatch / connection mismatch
elif echo "$line" | grep -q 'port.*does not match\|Port.*mismatch'; then
err_count=$((err_count + 1))
err_lines="$err_lines\n ${RED}[VIVADO-ERR]${NC} $line"
# Informational warnings (timescale, dangling ports, array sensitivity)
elif echo "$line" | grep -q 'timescale\|dangling\|sensitive to all'; then
info_count=$((info_count + 1))
# Unknown warning — report but don't fail
elif [[ -n "$line" ]]; then
info_count=$((info_count + 1))
fi
done < "$warn_file"
if [[ "$err_count" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} ($err_count Vivado-class errors, $info_count info)"
echo -e "$err_lines"
LINT_ERR=$((LINT_ERR + err_count))
else
echo -e "${GREEN}PASS${NC} ($info_count info warnings)"
fi
rm -f "$warn_file"
}
# ---- Layer B: Custom regex static checks ----
# Catches patterns that Vivado treats as errors/warnings but iverilog ignores
run_lint_static() {
printf " %-45s " "Static RTL checks"
local err_count=0
local warn_count=0
local err_lines=""
local warn_lines=""
for f in "$@"; do
[[ -f "$f" ]] || continue
# Skip testbench files (tb/ directory) — only lint production RTL
case "$f" in tb/*) continue ;; esac
local linenum=0
while IFS= read -r line; do
linenum=$((linenum + 1))
# --- CHECK 1: Part-select with literal range on reg ---
# Pattern: identifier[N:M] where N exceeds declared width
# (iverilog catches this, but belt-and-suspenders)
# --- CHECK 2: case/casex/casez without default (non-full case) ---
# Vivado SYNTH-6 / inferred latch warning
# Heuristic: look for case/casex/casez, then check if 'default' appears
# before the matching 'endcase'. This is approximate — full parsing
# would need a real parser. We flag 'case' lines so the developer
# can manually verify.
# (Handled below as a multi-line check)
# --- CHECK 3: Blocking assignment (=) inside always @(posedge ...) ---
# Vivado SYNTH-5 warning for inferred latches / race conditions
# Only flag if the always block is clocked (posedge/negedge)
# This is a heuristic — we check for '= ' that isn't '<=', '==', '!='
# inside an always block header containing 'posedge' or 'negedge'.
# (Too complex for line-by-line — skip for now, handled by testbenches)
# --- CHECK 4: Multi-driven register (assign + always on same signal) ---
# (Would need cross-file analysis — skip for v1)
done < "$f"
done
# --- Multi-line check: case without default ---
for f in "$@"; do
[[ -f "$f" ]] || continue
case "$f" in tb/*) continue ;; esac
# Find case blocks and check for default
# Use awk to find case..endcase blocks missing 'default'
local missing_defaults
missing_defaults=$(awk '
/^[[:space:]]*(case|casex|casez)[[:space:]]*\(/ {
case_line = NR
case_file = FILENAME
has_default = 0
in_case = 1
next
}
in_case && /default[[:space:]]*:/ {
has_default = 1
}
in_case && /endcase/ {
if (!has_default) {
printf "%s:%d: case statement without default\n", FILENAME, case_line
}
in_case = 0
}
' "$f" 2>/dev/null)
if [[ -n "$missing_defaults" ]]; then
while IFS= read -r hit; do
warn_count=$((warn_count + 1))
warn_lines="$warn_lines\n ${YELLOW}[SYNTH-6]${NC} $hit"
done <<< "$missing_defaults"
fi
done
# CHECK 5 ($readmemh in synth code) and CHECK 6 (unused includes)
# require multi-line ifdef tracking / cross-file analysis. Not feasible
# with line-by-line regex. Omitted — use Vivado lint instead.
if [[ "$err_count" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} ($err_count errors, $warn_count warnings)"
echo -e "$err_lines"
LINT_ERR=$((LINT_ERR + err_count))
elif [[ "$warn_count" -gt 0 ]]; then
echo -e "${YELLOW}WARN${NC} ($warn_count warnings)"
echo -e "$warn_lines"
LINT_WARN=$((LINT_WARN + warn_count))
else
echo -e "${GREEN}PASS${NC}"
fi
}
# ---------------------------------------------------------------------------
# Helper: compile, run, and compare a matched-filter co-sim scenario
# run_mf_cosim <scenario_name> <define_flag>
# ---------------------------------------------------------------------------
run_mf_cosim() {
local name="$1"
local define="$2"
local vvp="tb/tb_mf_cosim_${name}.vvp"
local scenario_lower="$name"
printf " %-45s " "MF Co-Sim ($name)"
# Compile — build command as string to handle optional define
local cmd="iverilog -g2001 -DSIMULATION"
if [[ -n "$define" ]]; then
cmd="$cmd $define"
fi
cmd="$cmd -o $vvp tb/tb_mf_cosim.v matched_filter_processing_chain.v fft_engine.v xfft_2048.v fft_engine_axi_bridge.v frequency_matched_filter.v chirp_memory_loader_param.v"
if ! eval "$cmd" 2>/tmp/iverilog_err_$$; then
echo -e "${RED}COMPILE FAIL${NC}"
ERRORS="$ERRORS\n MF Co-Sim ($name): compile error ($(head -1 /tmp/iverilog_err_$$))"
FAIL=$((FAIL + 1))
return
fi
# Run TB
local output
output=$(timeout 120 vvp "$vvp" 2>&1) || true
rm -f "$vvp"
# Check TB internal pass/fail
local tb_fail
tb_fail=$(echo "$output" | grep -Ec '^\[FAIL' || true)
if [[ "$tb_fail" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} (TB internal failure)"
ERRORS="$ERRORS\n MF Co-Sim ($name): TB internal failure"
FAIL=$((FAIL + 1))
return
fi
# Run Python compare
if command -v python3 >/dev/null 2>&1; then
local compare_out
local compare_rc=0
compare_out=$(python3 tb/cosim/compare_mf.py "$scenario_lower" 2>&1) || compare_rc=$?
if [[ "$compare_rc" -ne 0 ]]; then
echo -e "${RED}FAIL${NC} (compare_mf.py mismatch)"
ERRORS="$ERRORS\n MF Co-Sim ($name): Python compare failed"
FAIL=$((FAIL + 1))
return
fi
else
echo -e "${YELLOW}SKIP${NC} (RTL passed, python3 not found — compare skipped)"
SKIP=$((SKIP + 1))
return
fi
echo -e "${GREEN}PASS${NC} (RTL + Python compare)"
PASS=$((PASS + 1))
}
# ---------------------------------------------------------------------------
# Helper: compile, run, and compare a Doppler co-sim scenario
# run_doppler_cosim <scenario_name> <define_flag>
# ---------------------------------------------------------------------------
run_doppler_cosim() {
local name="$1"
local define="$2"
local vvp="tb/tb_doppler_cosim_${name}.vvp"
printf " %-45s " "Doppler Co-Sim ($name)"
# Compile — build command as string to handle optional define
local cmd="iverilog -g2001 -DSIMULATION"
if [[ -n "$define" ]]; then
cmd="$cmd $define"
fi
cmd="$cmd -o $vvp tb/tb_doppler_cosim.v doppler_processor.v xfft_16.v fft_engine.v"
if ! eval "$cmd" 2>/tmp/iverilog_err_$$; then
echo -e "${RED}COMPILE FAIL${NC}"
ERRORS="$ERRORS\n Doppler Co-Sim ($name): compile error ($(head -1 /tmp/iverilog_err_$$))"
FAIL=$((FAIL + 1))
return
fi
# Run TB
local output
output=$(timeout 120 vvp "$vvp" 2>&1) || true
rm -f "$vvp"
# Check TB internal pass/fail
local tb_fail
tb_fail=$(echo "$output" | grep -Ec '^\[FAIL' || true)
if [[ "$tb_fail" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} (TB internal failure)"
ERRORS="$ERRORS\n Doppler Co-Sim ($name): TB internal failure"
FAIL=$((FAIL + 1))
return
fi
# Run Python compare
if command -v python3 >/dev/null 2>&1; then
local compare_out
local compare_rc=0
compare_out=$(python3 tb/cosim/compare_doppler.py "$name" 2>&1) || compare_rc=$?
if [[ "$compare_rc" -ne 0 ]]; then
echo -e "${RED}FAIL${NC} (compare_doppler.py mismatch)"
ERRORS="$ERRORS\n Doppler Co-Sim ($name): Python compare failed"
FAIL=$((FAIL + 1))
return
fi
else
echo -e "${YELLOW}SKIP${NC} (RTL passed, python3 not found — compare skipped)"
SKIP=$((SKIP + 1))
return
fi
echo -e "${GREEN}PASS${NC} (RTL + Python compare)"
PASS=$((PASS + 1))
}
# ---------------------------------------------------------------------------
# Helper: compile and run a single testbench
# run_test <name> <vvp_path> <iverilog_args...>
# ---------------------------------------------------------------------------
run_test() {
# Optional: --timeout=N as first arg overrides default 120s
local timeout_secs=120
if [[ "$1" == --timeout=* ]]; then
timeout_secs="${1#--timeout=}"
shift
fi
local name="$1"
local vvp="$2"
shift 2
local args=("$@")
printf " %-45s " "$name"
# Compile
if ! iverilog -g2001 -DSIMULATION -o "$vvp" "${args[@]}" 2>/tmp/iverilog_err_$$; then
echo -e "${RED}COMPILE FAIL${NC}"
ERRORS="$ERRORS\n $name: compile error ($(head -1 /tmp/iverilog_err_$$))"
FAIL=$((FAIL + 1))
return
fi
# Run
local output
output=$(timeout "$timeout_secs" vvp "$vvp" 2>&1) || true
# Count PASS/FAIL in output (testbenches use explicit [PASS]/[FAIL] markers)
# Match `[PASS]` and `[PASS <digits>]` (and same for FAIL). Excludes
# informational tags like `[FAIL-INFO]` (used for known unrelated bugs,
# e.g. RX-NEW-1 fft_engine bin-shift in tb_matched_filter_processing_chain.v)
# which would otherwise false-fire as real failures.
local test_pass test_fail
test_pass=$(echo "$output" | grep -Ec '^\[PASS( [0-9]+)?\]' || true)
test_fail=$(echo "$output" | grep -Ec '^\[FAIL( [0-9]+)?\]' || true)
if [[ "$test_fail" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} (pass=$test_pass, fail=$test_fail)"
ERRORS="$ERRORS\n $name: $test_fail failure(s)"
FAIL=$((FAIL + 1))
elif [[ "$test_pass" -gt 0 ]]; then
echo -e "${GREEN}PASS${NC} ($test_pass checks)"
PASS=$((PASS + 1))
else
# No PASS/FAIL markers — check for clean completion
if echo "$output" | grep -qi 'finish\|complete\|done'; then
echo -e "${GREEN}PASS${NC} (completed)"
PASS=$((PASS + 1))
else
echo -e "${YELLOW}UNKNOWN${NC} (no PASS/FAIL markers)"
ERRORS="$ERRORS\n $name: no pass/fail markers in output"
FAIL=$((FAIL + 1))
fi
fi
rm -f "$vvp"
}
# ===========================================================================
echo "============================================"
echo " AERIS-10 FPGA Regression Test Suite"
echo "============================================"
echo ""
echo "Date: $(date)"
echo "iverilog: $(iverilog -V 2>&1 | head -1)"
echo ""
# ===========================================================================
# PHASE 0: LINT (Vivado-class error detection)
# ===========================================================================
if [[ "$SKIP_LINT" -eq 0 ]]; then
echo "--- PHASE 0: LINT (Vivado-class checks) ---"
# Layer A: iverilog -Wall on full production design
run_lint_iverilog "production" "${PROD_RTL[@]}"
# Layer A: standalone modules not in top-level hierarchy
# (use ${EXTRA_RTL[@]+...} guard so empty array doesn't trip set -u)
if [[ ${#EXTRA_RTL[@]} -gt 0 ]]; then
for extra in "${EXTRA_RTL[@]}"; do
if [[ -f "$extra" ]]; then
run_lint_iverilog "$(basename "$extra" .v)" "$extra"
fi
done
fi
# Layer B: custom static regex checks
if [[ ${#EXTRA_RTL[@]} -gt 0 ]]; then
ALL_RTL=("${PROD_RTL[@]}" "${EXTRA_RTL[@]}")
else
ALL_RTL=("${PROD_RTL[@]}")
fi
run_lint_static "${ALL_RTL[@]}"
echo ""
if [[ "$LINT_ERR" -gt 0 ]]; then
echo -e "${RED} LINT FAILED: $LINT_ERR Vivado-class error(s) detected.${NC}"
echo " Fix lint errors before pushing to Vivado. Aborting regression."
echo ""
exit 1
elif [[ "$LINT_WARN" -gt 0 ]]; then
echo -e "${YELLOW} LINT: $LINT_WARN advisory warning(s) (non-blocking)${NC}"
else
echo -e "${GREEN} LINT: All checks passed${NC}"
fi
echo ""
else
echo "--- PHASE 0: LINT (skipped via --skip-lint) ---"
echo ""
fi
# ===========================================================================
# PHASE 1: UNIT TESTS — Changed Modules (HIGH PRIORITY)
# ===========================================================================
echo "--- PHASE 1: Changed Modules ---"
run_test "CIC Decimator" \
tb/tb_cic_reg.vvp \
tb/tb_cic_decimator.v cic_decimator_4x_enhanced.v
run_test "Chirp Controller (BRAM)" \
tb/tb_chirp_reg.vvp \
tb/tb_chirp_controller.v plfm_chirp_controller.v
run_test "Chirp Contract" \
tb/tb_chirp_ctr_reg.vvp \
tb/tb_chirp_contract.v plfm_chirp_controller.v
run_doppler_cosim "stationary" ""
run_doppler_cosim "moving" "-DSCENARIO_MOVING"
run_doppler_cosim "two_targets" "-DSCENARIO_TWO"
run_test "Threshold Detector (detection bugs)" \
tb/tb_threshold_detector.vvp \
tb/tb_threshold_detector.v
run_test "RX Gain Control (digital gain)" \
tb/tb_rx_gain_control.vvp \
tb/tb_rx_gain_control.v rx_gain_control.v
run_test "MTI Canceller (ground clutter)" \
tb/tb_mti_canceller.vvp \
tb/tb_mti_canceller.v mti_canceller.v
run_test "CFAR CA Detector" \
tb/tb_cfar_ca.vvp \
tb/tb_cfar_ca.v cfar_ca.v
run_test "FPGA Self-Test" \
tb/tb_fpga_self_test.vvp \
tb/tb_fpga_self_test.v fpga_self_test.v
run_test "FFT AXI Bridge tready handshake (AUDIT-C10)" \
tb/tb_fft_engine_axi_bridge.vvp \
tb/tb_fft_engine_axi_bridge.v fft_engine_axi_bridge.v
echo ""
# ===========================================================================
# PHASE 2: INTEGRATION TESTS
# ===========================================================================
echo "--- PHASE 2: Integration Tests ---"
run_test "DDC Chain (NCO→CIC→FIR)" \
tb/tb_ddc_reg.vvp \
tb/tb_ddc_cosim.v ddc_400m.v nco_400m_enhanced.v \
cic_decimator_4x_enhanced.v fir_lowpass.v cdc_modules.v
# Real-data co-simulation: committed golden hex vs RTL (exact match required).
# These catch architecture mismatches (e.g. 32-pt → dual 16-pt Doppler FFT)
# that self-blessing golden-generate/compare tests cannot detect.
run_test "Doppler Real-Data (ADI CN0566, exact match)" \
tb/tb_doppler_realdata.vvp \
tb/tb_doppler_realdata.v doppler_processor.v xfft_16.v fft_engine.v
run_test "Full-Chain Real-Data (decim→Doppler, exact match)" \
tb/tb_fullchain_realdata.vvp \
tb/tb_fullchain_realdata.v range_bin_decimator.v \
doppler_processor.v xfft_16.v fft_engine.v
if [[ "$QUICK" -eq 0 ]]; then
# Receiver integration (structural + bounds + pulse assertions).
# Replaces the earlier "Receiver golden generate/compare" pair, which was
# self-blessing (both passes ran identical RTL on identical stimulus, so
# it passed regardless of bugs). Real co-sim coverage is now provided by
# tb_doppler_realdata.v and tb_fullchain_realdata.v (Python goldens,
# exact match); this integration test exercises the full RX pipeline
# (ADC stub → DDC → MF → Decim → Doppler) and verifies that
# doppler_frame_done is a single-cycle pulse at module boundaries.
run_test --timeout=600 "Receiver Integration (tb_radar_receiver_final)" \
tb/tb_rx_final_reg.vvp \
tb/tb_radar_receiver_final.v "${RECEIVER_RTL[@]}"
# Full system top (monitoring-only, legacy)
run_test "System Top (radar_system_tb)" \
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[@]}"
# USB_MODE=1 (FT2232H production) variants of system tests
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))
fi
echo ""
# ===========================================================================
# PHASE 2b: MATCHED FILTER CO-SIMULATION (RTL vs Python golden reference)
# Runs tb_mf_cosim.v for 4 scenarios, then compare_mf.py validates output
# against committed Python golden CSV files. In SIMULATION mode, thresholds
# are generous (behavioral vs fixed-point twiddles differ) — validates
# state machine mechanics, output count, and energy sanity.
# ===========================================================================
echo "--- PHASE 2b: Matched Filter Co-Sim ---"
run_mf_cosim "chirp" ""
run_mf_cosim "dc" "-DSCENARIO_DC"
run_mf_cosim "impulse" "-DSCENARIO_IMPULSE"
run_mf_cosim "tone5" "-DSCENARIO_TONE5"
echo ""
# ===========================================================================
# PHASE 3: UNIT TESTS — Signal Processing
# ===========================================================================
echo "--- PHASE 3: Signal Processing ---"
run_test "FFT Engine" \
tb/tb_fft_reg.vvp \
tb/tb_fft_engine.v fft_engine.v
run_test "NCO 400MHz" \
tb/tb_nco_reg.vvp \
tb/tb_nco_400m.v nco_400m_enhanced.v
run_test "FIR Lowpass" \
tb/tb_fir_reg.vvp \
tb/tb_fir_lowpass.v fir_lowpass.v
run_test --timeout=600 "Matched Filter Chain" \
tb/tb_mf_reg.vvp \
tb/tb_matched_filter_processing_chain.v matched_filter_processing_chain.v \
fft_engine.v xfft_2048.v fft_engine_axi_bridge.v \
chirp_memory_loader_param.v frequency_matched_filter.v
# RX-B regression coverage: chain pipeline depth + full-chain
# autocorrelation peak position. Both run the production fft_engine
# (no SIMULATION-only behavioural FFT exists). Long-running because
# the production FFT is BRAM-pipelined (~153k cycles per chain pass).
run_test --timeout=120 "RX-B Chain Pipeline Latency (tb_rxb_latency_measure)" \
tb/tb_rxb_lat_reg.vvp \
tb/tb_rxb_latency_measure.v matched_filter_processing_chain.v \
fft_engine.v xfft_2048.v fft_engine_axi_bridge.v frequency_matched_filter.v
run_test --timeout=600 "RX-B Full-Chain Autocorrelation (tb_rxb_fullchain_latency)" \
tb/tb_rxb_fc_reg.vvp \
tb/tb_rxb_fullchain_latency.v matched_filter_multi_segment.v \
matched_filter_processing_chain.v fft_engine.v xfft_2048.v \
fft_engine_axi_bridge.v frequency_matched_filter.v \
chirp_memory_loader_param.v
echo ""
# ===========================================================================
# PHASE 4: UNIT TESTS — Infrastructure
# ===========================================================================
echo "--- PHASE 4: Infrastructure ---"
run_test "CDC Modules (3 variants)" \
tb/tb_cdc_reg.vvp \
tb/tb_cdc_modules.v cdc_modules.v
run_test "Edge Detector" \
tb/tb_edge_reg.vvp \
tb/tb_edge_detector.v edge_detector.v
run_test "USB Data Interface" \
tb/tb_usb_reg.vvp \
tb/tb_usb_data_interface.v usb_data_interface.v
run_test "Range Bin Decimator" \
tb/tb_rbd_reg.vvp \
tb/tb_range_bin_decimator.v range_bin_decimator.v
run_test "Radar Mode Controller" \
tb/tb_rmc_reg.vvp \
tb/tb_radar_mode_controller.v radar_mode_controller.v
echo ""
# ===========================================================================
# SUMMARY
# ===========================================================================
TOTAL=$((PASS + FAIL + SKIP))
echo "============================================"
echo " RESULTS"
echo "============================================"
if [[ "$SKIP_LINT" -eq 0 ]]; then
if [[ "$LINT_ERR" -gt 0 ]]; then
echo -e " Lint: ${RED}$LINT_ERR error(s)${NC}, $LINT_WARN warning(s)"
elif [[ "$LINT_WARN" -gt 0 ]]; then
echo -e " Lint: ${GREEN}0 errors${NC}, ${YELLOW}$LINT_WARN warning(s)${NC}"
else
echo -e " Lint: ${GREEN}clean${NC}"
fi
fi
echo " Tests: $PASS passed, $FAIL failed, $SKIP skipped / $TOTAL total"
echo "============================================"
if [[ -n "$ERRORS" ]]; then
echo ""
echo "Failures:"
echo -e "$ERRORS"
fi
echo ""
# Exit with error if any failures
if [[ "$FAIL" -gt 0 ]]; then
exit 1
fi
exit 0