From db6b220f92b3b4e74c4f6f58e0fda02029911506 Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Fri, 1 May 2026 18:53:09 +0545 Subject: [PATCH] =?UTF-8?q?ci(fpga):=20PR-M.3=20=E2=80=94=20wire=20T-6=20d?= =?UTF-8?q?rift=20cosim=20into=20regression=20+=20CI=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the T-6 independent reference drift cosim (PR-M.1, c30be89) as a gated regression check so any future hand-edit drift in NCO_SINE_LUT, fft_twiddle_*.mem, or DOPPLER_WINDOW_COEFF surfaces on every run. run_regression.sh: new "Independent Reference Drift (T-6)" check after the RX-B autocorrelation block in Phase 3. Plain `python3` (no path sniffing). Distinguishes three states from the script's exit code + markers: rc=0, PASS markers -> PASS (counts toward `passed`) rc=2, no markers -> SKIP (counts toward `skipped`) rc!=0, FAIL markers -> FAIL (gates the regression) compare_independent.py: detects missing numpy/scipy at startup and exits with code 2 plus a [SKIP] marker pointing at `uv sync --group dev`. Without that, an environment without scipy crashed mid-script and the regression captured a partial 3-of-13 PASS count. pyproject.toml: scipy>=1.13 added to the dev dependency group (used by fpga_reference.doppler_window_ideal() for analytical Cheby ground truth). .github/workflows/ci-tests.yml: fpga-regression now installs Python 3.12, sets up uv, runs `uv sync --group dev`, and activates the resulting .venv before bash run_regression.sh. Without the activate line the runner's system python3 (no scipy) would resolve first and the drift check would [SKIP] in CI. Verified locally: with venv: Drift PASS (13 checks), Tests: 43 passed / 0 / 0 no scipy: Drift SKIP (msg points at install cmd), 42p / 0f / 1s --- .github/workflows/ci-tests.yml | 22 ++++++++- 9_Firmware/9_2_FPGA/run_regression.sh | 46 +++++++++++++++++++ .../9_2_FPGA/tb/cosim/compare_independent.py | 23 +++++++++- pyproject.toml | 1 + 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml index be7637a..44c16ca 100644 --- a/.github/workflows/ci-tests.yml +++ b/.github/workflows/ci-tests.yml @@ -69,7 +69,10 @@ jobs: working-directory: 9_Firmware/9_1_Microcontroller/tests # =========================================================================== - # FPGA RTL Regression (25 testbenches + lint) + # FPGA RTL Regression (testbenches + lint + cosim helpers) + # Python deps (numpy, scipy) come from the dev group in pyproject.toml + # and are installed via uv so the run_regression.sh cosim and T-6 drift + # check have everything they need. # =========================================================================== fpga-regression: name: FPGA Regression @@ -78,11 +81,26 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - uses: astral-sh/setup-uv@v5 + + - name: Install Python cosim deps (numpy + scipy) + run: uv sync --group dev + - name: Install Icarus Verilog run: sudo apt-get update && sudo apt-get install -y iverilog - name: Run full FPGA regression - run: bash run_regression.sh + # uv-managed venv lives at ./.venv; activate so run_regression.sh's + # plain `python3` resolves to it. Without this the cosim helpers + # would fall back to the runner's system python (no scipy) and the + # T-6 drift check would emit [SKIP] instead of running. + run: | + source ../../.venv/bin/activate + bash run_regression.sh working-directory: 9_Firmware/9_2_FPGA # =========================================================================== diff --git a/9_Firmware/9_2_FPGA/run_regression.sh b/9_Firmware/9_2_FPGA/run_regression.sh index de02950..b68af07 100755 --- a/9_Firmware/9_2_FPGA/run_regression.sh +++ b/9_Firmware/9_2_FPGA/run_regression.sh @@ -708,6 +708,52 @@ run_test --timeout=600 "RX-B Full-Chain Autocorrelation (tb_rxb_fullchain_latenc fft_engine_axi_bridge.v frequency_matched_filter.v \ chirp_reference_rom.v +# --------------------------------------------------------------------------- +# T-6 independent reference drift cosim (PR-M). +# Bytewise spot-checks of NCO_SINE_LUT, fft_twiddle_{16,2048}.mem, and +# DOPPLER_WINDOW_COEFF against analytical Q15 values, plus end-to-end peak +# and roundtrip invariants for NCO / FFT / MF / Doppler. Catches the bug +# class where a transcription error exists identically in both fpga_model.py +# (RTL-mirroring twin) and the RTL — which the bit-exact cosim cannot detect +# because both sides of that comparison are computing the same wrong values. +# Pure Python (numpy + scipy), ~1 s wall, no iverilog compile. +# +# Required deps: numpy, scipy (declared in pyproject.toml dev group). +# Install with: uv sync --group dev (from repo root) +# CI handles this in the fpga-regression job; locally activate the +# resulting .venv (or use `uv run bash run_regression.sh`). +# If a dep is missing the script emits a [SKIP] marker and exits with +# code 2; the regression treats it as SKIP rather than FAIL so the +# missing-dep state is visible without breaking the gate. +# --------------------------------------------------------------------------- +printf " %-46s" "Independent Reference Drift (T-6)" +set +e +drift_output=$(python3 tb/cosim/compare_independent.py 2>&1) +drift_rc=$? +set -e +drift_pass=$(echo "$drift_output" | grep -Ec '^[[:space:]]*\[PASS\]' || true) +drift_fail=$(echo "$drift_output" | grep -Ec '^[[:space:]]*\[FAIL\]' || true) +if [[ "$drift_rc" -eq 2 ]]; then + # Script signalled missing-dep skip. Show its message body so the + # operator knows which package to install. + echo -e "${YELLOW}SKIP${NC} (missing python dep — see below)" + echo "$drift_output" | sed 's/^/ /' + SKIP=$((SKIP + 1)) +elif [[ "$drift_fail" -gt 0 ]]; then + echo -e "${RED}FAIL${NC} (pass=$drift_pass, fail=$drift_fail)" + ERRORS="$ERRORS\n Independent Reference Drift: $drift_fail failure(s)" + echo "$drift_output" | sed 's/^/ /' + FAIL=$((FAIL + 1)) +elif [[ "$drift_pass" -gt 0 && "$drift_rc" -eq 0 ]]; then + echo -e "${GREEN}PASS${NC} ($drift_pass checks)" + PASS=$((PASS + 1)) +else + echo -e "${RED}FAIL${NC} (rc=$drift_rc, no clean PASS/FAIL markers)" + ERRORS="$ERRORS\n Independent Reference Drift: rc=$drift_rc" + echo "$drift_output" | sed 's/^/ /' + FAIL=$((FAIL + 1)) +fi + echo "" # =========================================================================== diff --git a/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py b/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py index ddef2cf..d148bcb 100644 --- a/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py +++ b/9_Firmware/9_2_FPGA/tb/cosim/compare_independent.py @@ -35,7 +35,28 @@ import math import sys from pathlib import Path -import numpy as np +# Required: numpy + scipy. If either is missing, exit code 2 with a [SKIP] +# marker so the regression can distinguish missing-deps from real failures +# (see run_regression.sh "Independent Reference Drift (T-6)" block). +_MISSING = [] +try: + import numpy as np # noqa: F401 +except ImportError: + _MISSING.append("numpy") +try: + import scipy.signal.windows # noqa: F401 +except ImportError: + _MISSING.append("scipy") +if _MISSING: + print( + " [SKIP] T-6 drift cosim requires Python packages: " + f"{', '.join(_MISSING)}.\n" + " Install with: uv sync --group dev (from repo root)\n" + " or: pip install numpy scipy" + ) + sys.exit(2) + +import numpy as np # re-import to get module binding now that we know it's there # Make local imports work when invoked from anywhere THIS_DIR = Path(__file__).resolve().parent diff --git a/pyproject.toml b/pyproject.toml index 3d03b57..7dd0037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dev = [ "ruff>=0.5", "pytest>=8", "numpy>=1.26", + "scipy>=1.13", # PR-M: tb/cosim/compare_independent.py (T-6 drift cosim) "h5py>=3.10", ]