mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-14 01:05:33 +00:00
chore(repo): PR-H — G-series close-out (regression infra + lint sweep)
Closeout pass for the G-series 3-ladder chirp + adaptive-escalation work.
Cleanup, watchdog/fallback, lint, full regression — final sign-off.
Cleanup + watchdog/fallback: already wired during earlier audit waves
(track watchdog in chirp_scheduler RP_DEF_TRACK_WATCHDOG_FRAMES, RESERVED
fallback in plfm_chirp_controller_v2, range-decim watchdog in
radar_system_top with gpio_dig7 surfacing, F-3.* MCU error path).
Verified — no residual TODO/FIXME in production RTL or MCU.
Regression infra: tb/cosim/compare_independent.py SKIP-detection bug —
importlib.util.find_spec("scipy.signal") raises ModuleNotFoundError when
the parent scipy package is itself absent (instead of returning None as
the surrounding logic assumed). Wrap in try/except so the regression
runner gets the intended rc=2 SKIP marker rather than a crash that masks
the rest of the script.
Lint sweep: ruff full-repo → 0 errors. Two changes:
- pyproject.toml broadens 5_Simulations/Antenna/**.py exemption from
just T20+ERA to the full set of script-ergonomics rules
(RUF001/002/003 Greek µ/λ/π/θ in physical-units strings, E501 long
matplotlib/numpy lines, RUF005/015/046, E70x one-line setup, B007
tuple-unpack loop vars, B905, BLE001 diag try/except, C401, RET504,
SIM118, PERF40x, ARG001, E402). These are sim/analysis scripts, not
production code — keep substantive bug rules (F unused, B core
bugbears) but drop stylistic noise.
- Auto-fix sweep: 31x F541 (f-string-no-placeholder), 3x F401 (unused
sys import), 2x F841 (dead leftover ref_pat / phases_quant in
array_factor_adar1000_aeris10.py).
.gitignore: cover 9_Firmware/9_2_FPGA/tb/cosim/mf_chain_autocorr.csv
(matched_filter cosim writes here now; was already covered for tb/ but
not tb/cosim/).
Regression baseline (radar_venv):
FPGA : 42/43 — 1 pre-existing T-6 drift cosim fail surfaced by the
SKIP fix above. Three sub-checks now red because PR-O moved
xFFT/MF chain to LogiCORE v9.1 *Scaled* mode (1/2 per stage,
1/2^11 total for N=2048) but compare_independent.py's invariants
(FFT-impulse uniform-spectrum, MF peak-at-injected-delay, MF
peak/median ≥ 5) were written assuming UNSCALED FFT. Not
introduced by this PR — was hidden by the SKIP-detection crash.
Defer to PR-M.4: redesign T-6 invariants (or input amplitudes)
to match scaled-mode arithmetic.
MCU : 34/34 binary suites pass.
GUI : test_v7 150/150 pass.
uv.lock: scipy resolution catch-up (declared in pyproject dev group all
along; lock just hadn't been refreshed after pyproject edits landed).
Bench-side checks: none — this PR is repo hygiene, no firmware/RTL
behaviour change.
This commit is contained in:
@@ -432,7 +432,7 @@ print("=" * 70)
|
||||
print(f" Resonance (Im(Zin)=0): {f_res/1e9:.3f} GHz (target 10.5 GHz)")
|
||||
print(f" S11 at resonance : {s11_min:.2f} dB")
|
||||
print(f" Zin at resonance : {zin[i_res].real:.1f} + j{zin[i_res].imag:.1f} Ω")
|
||||
print(f" ── at 10.500 GHz exactly:")
|
||||
print(" ── at 10.500 GHz exactly:")
|
||||
print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB")
|
||||
print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:.1f} Ω")
|
||||
print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}")
|
||||
|
||||
@@ -261,11 +261,8 @@ def main():
|
||||
peak_dB_co = []
|
||||
actual_peak_fw = []
|
||||
actual_peak_co = []
|
||||
# Reference broadside peak for absolute scan-loss
|
||||
ref_pat = total_pattern_dB(theta_deg, np.zeros(N_TOTAL, dtype=int), h_pat_lin)
|
||||
# Find peak amplitude (ref) on linear scale by recomputing without normalisation
|
||||
# Use a simpler proxy: peak in dB rel peak is 0; we want amplitude relative to
|
||||
# broadside. Compute |E|² broadside vs scanned without normalisation.
|
||||
# Reference broadside peak for absolute scan-loss — peak in dB rel peak is 0;
|
||||
# we want amplitude relative to broadside, so compute |E|² without normalisation.
|
||||
|
||||
def total_amp_lin(theta_deg_arr, phase_codes):
|
||||
af = array_factor_hplane(theta_deg_arr, phase_codes)
|
||||
@@ -374,7 +371,6 @@ def main():
|
||||
phase_shift = (2 * np.pi * D_X * np.sin(angle_rad)) / LAMBDA
|
||||
phases_continuous = np.array([(n*phase_shift) % (2*np.pi) for n in range(N_TOTAL)])
|
||||
codes_quant = correct_phase_codes(ang)
|
||||
phases_quant = codes_to_radians(codes_quant)
|
||||
|
||||
def total_pattern_dB_continuous(theta_deg_arr, phases_rad, h_pat_lin):
|
||||
k = 2*np.pi/LAMBDA
|
||||
@@ -409,9 +405,9 @@ def main():
|
||||
# = 4d = 2λ → grating lobes at sin(θ_g) = ±λ/(4d) ± sin(θ_0) = ±0.5 ± sin(θ_0).
|
||||
print()
|
||||
print(" TEST 3: Grating-lobe geometry")
|
||||
print(f" Element pitch d = λ/2 → no real-space grating lobes at any scan ✓")
|
||||
print(f" Firmware's 4-elem broadcast → super-pitch d_super = 4d = 2λ")
|
||||
print(f" → grating lobes appear at sin(θ_g) = ±0.5 ± sin(θ_0)")
|
||||
print(" Element pitch d = λ/2 → no real-space grating lobes at any scan ✓")
|
||||
print(" Firmware's 4-elem broadcast → super-pitch d_super = 4d = 2λ")
|
||||
print(" → grating lobes appear at sin(θ_g) = ±0.5 ± sin(θ_0)")
|
||||
for ang in [0, 15, 30, 45]:
|
||||
sin0 = np.sin(np.deg2rad(ang))
|
||||
gl = []
|
||||
|
||||
@@ -267,7 +267,7 @@ print(f" Edge-fed (inset) on 0.508 mm RO4350B (W={PATCH_W} L={PATCH_L} inset={I
|
||||
print(f" Resonance (R peak + Im=0): {f_res/1e9:.3f} GHz (target 10.5 GHz)")
|
||||
print(f" S11 at resonance : {s11_min:.2f} dB")
|
||||
print(f" Zin at resonance : {zin[i_res].real:.1f} + j{zin[i_res].imag:+.1f} Ω")
|
||||
print(f" ── at 10.500 GHz exactly:")
|
||||
print(" ── at 10.500 GHz exactly:")
|
||||
print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB")
|
||||
print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:+.1f} Ω")
|
||||
print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}")
|
||||
@@ -286,7 +286,7 @@ if (f_hi-f_lo) > 0:
|
||||
label=f"BW {(f_hi-f_lo)/1e6:.0f} MHz ({bw_pct:.2f}%)")
|
||||
ax.set_xlabel("Frequency (GHz)")
|
||||
ax.set_ylabel("S11 (dB)")
|
||||
ax.set_title(f"AERIS-10 Edge-Fed (inset) — 2-layer 0.508 mm RO4350B")
|
||||
ax.set_title("AERIS-10 Edge-Fed (inset) — 2-layer 0.508 mm RO4350B")
|
||||
ax.set_xlim(F_START/1e9, F_STOP/1e9)
|
||||
ax.set_ylim(-40, 0)
|
||||
ax.grid(True, alpha=0.3)
|
||||
|
||||
@@ -311,7 +311,7 @@ print(f" W={PATCH_W} L={PATCH_L} inset={INSET_DEPTH}/{INSET_GAP} "
|
||||
f"conn={CONN_LEN} pitch={PITCH:.2f}")
|
||||
print(f" Operating mode (nearest 10.5 GHz): {f_res/1e9:.3f} GHz, {s11_min:.2f} dB")
|
||||
print(f" Zin at op mode : {zin[i_res].real:.1f} + j{zin[i_res].imag:+.1f} Ω")
|
||||
print(f" ── at 10.500 GHz exactly:")
|
||||
print(" ── at 10.500 GHz exactly:")
|
||||
print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB")
|
||||
print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:+.1f} Ω")
|
||||
print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}")
|
||||
|
||||
@@ -261,11 +261,11 @@ bw_h = beamwidth_3dB(theta_deg, pat_dB_h_norm, i_pk_h)
|
||||
print()
|
||||
print("=" * 78)
|
||||
print(f" Far-field NORMALISED pattern at f = {F_TX/1e9:.3f} GHz")
|
||||
print(f" ── E-plane (φ=90°, along array axis y — array factor lives here) ──")
|
||||
print(" ── E-plane (φ=90°, along array axis y — array factor lives here) ──")
|
||||
print(f" Broadside (θ=0°) level : {pat_dB_e_norm[i_bs]:.2f} dB (rel peak)")
|
||||
print(f" Peak direction : θ = {theta_deg[i_pk_e]:+.1f}° (peak = {pat_dB_e_norm[i_pk_e]:.2f} dB)")
|
||||
print(f" -3 dB beamwidth : {bw_e:.1f}°")
|
||||
print(f" ── H-plane (φ=0°, perpendicular to array) ──")
|
||||
print(" ── H-plane (φ=0°, perpendicular to array) ──")
|
||||
print(f" Broadside (θ=0°) level : {pat_dB_h_norm[i_bs]:.2f} dB (rel peak)")
|
||||
print(f" Peak direction : θ = {theta_deg[i_pk_h]:+.1f}° (peak = {pat_dB_h_norm[i_pk_h]:.2f} dB)")
|
||||
print(f" -3 dB beamwidth : {bw_h:.1f}°")
|
||||
@@ -279,13 +279,13 @@ print("=" * 78)
|
||||
# Plot (normalized to peak = 0 dB)
|
||||
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
|
||||
for ax, pat_dB, title, peak_deg in [
|
||||
(axes[0], pat_dB_h_norm, f"H-plane (φ=0°, xz cut, perp. to array)",
|
||||
(axes[0], pat_dB_h_norm, "H-plane (φ=0°, xz cut, perp. to array)",
|
||||
theta_deg[i_pk_h]),
|
||||
(axes[1], pat_dB_e_norm, f"E-plane (φ=90°, yz cut, ALONG array — array factor)",
|
||||
(axes[1], pat_dB_e_norm, "E-plane (φ=90°, yz cut, ALONG array — array factor)",
|
||||
theta_deg[i_pk_e]),
|
||||
]:
|
||||
ax.plot(theta_deg, pat_dB, "b-", lw=1.6)
|
||||
ax.axvline(0, color="r", ls=":", lw=0.8, label=f"broadside (θ=0°)")
|
||||
ax.axvline(0, color="r", ls=":", lw=0.8, label="broadside (θ=0°)")
|
||||
if abs(peak_deg) > 1.0:
|
||||
ax.axvline(peak_deg, color="g", ls=":", lw=0.8,
|
||||
label=f"peak at θ={peak_deg:+.1f}°")
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
# FEED_X_MM (default 0; offset along W-axis, normally 0 for centred feed)
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import csv
|
||||
import numpy as np
|
||||
@@ -277,7 +276,7 @@ print("=" * 70)
|
||||
print(f" Resonance (R peak + Im=0): {f_res/1e9:.3f} GHz (target 10.5 GHz)")
|
||||
print(f" S11 at resonance : {s11_min:.2f} dB")
|
||||
print(f" Zin at resonance : {zin[i_res].real:.1f} + j{zin[i_res].imag:.1f} Ω")
|
||||
print(f" ── at 10.500 GHz exactly:")
|
||||
print(" ── at 10.500 GHz exactly:")
|
||||
print(f" S11 @ 10.5GHz : {s11_dB[i_op]:.2f} dB")
|
||||
print(f" Zin @ 10.5GHz : {zin[i_op].real:.1f} + j{zin[i_op].imag:.1f} Ω")
|
||||
print(f" VSWR @ 10.5GHz : {vswr[i_op]:.2f}")
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
# coupling_grid.png — heatmap of |S_jd| dB at 10.5 GHz across array
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import csv
|
||||
import numpy as np
|
||||
@@ -314,7 +313,7 @@ for (i, j) in sorted(DRIVEN_SET):
|
||||
|
||||
if N_DRIVEN > 1:
|
||||
print()
|
||||
print(f" Sub-array uniformity:")
|
||||
print(" Sub-array uniformity:")
|
||||
print(f" S11 min/max/avg : {min(S11_at_op):>6.2f} / {max(S11_at_op):>6.2f} / "
|
||||
f"{sum(S11_at_op)/N_DRIVEN:>6.2f} dB")
|
||||
R_vals = [z.real for z in zin_at_op]
|
||||
@@ -325,7 +324,7 @@ if N_DRIVEN > 1:
|
||||
f"{sum(X_vals)/N_DRIVEN:+5.1f} Ω")
|
||||
# Average port (what the ADAR channel "sees" through ideal 1:8 splitter)
|
||||
Z_avg = sum(zin_at_op) / N_DRIVEN
|
||||
print(f" Z avg (= what ADAR channel sees through ideal 1:8 splitter):")
|
||||
print(" Z avg (= what ADAR channel sees through ideal 1:8 splitter):")
|
||||
print(f" Z = {Z_avg.real:.1f} + j{Z_avg.imag:+.1f} Ω, "
|
||||
f"VSWR = {abs((Z_avg-50)/(Z_avg+50)) and (1+abs((Z_avg-50)/(Z_avg+50)))/(1-abs((Z_avg-50)/(Z_avg+50))):.2f}")
|
||||
|
||||
|
||||
@@ -257,24 +257,24 @@ def main():
|
||||
label_at_bp7 = expected_angle(pd_at_bp7)
|
||||
pk_at_bp7 = analyse_beam(theta_deg, total_pattern_dB(theta_deg, matrix1[7], h_pat_lin)[0])[0]
|
||||
print("2) Sign convention check (matrix1[bp=7], phase_diff = +20°):")
|
||||
print(f" Comment in main.cpp says \"positive steering angles\".")
|
||||
print(f" Math says positive phase_diff steers to NEGATIVE θ.")
|
||||
print(" Comment in main.cpp says \"positive steering angles\".")
|
||||
print(" Math says positive phase_diff steers to NEGATIVE θ.")
|
||||
print(f" Sim peak θ = {pk_at_bp7:+.1f}° (predicted {label_at_bp7:+.1f}°).")
|
||||
if pk_at_bp7 * 1.0 < 0:
|
||||
print(f" → Comment is MISLEADING: matrix1 actually steers to NEGATIVE elevations.")
|
||||
print(" → Comment is MISLEADING: matrix1 actually steers to NEGATIVE elevations.")
|
||||
else:
|
||||
print(f" → Sim agrees with comment (positive steering).")
|
||||
print(" → Sim agrees with comment (positive steering).")
|
||||
print()
|
||||
# Symmetry / asymmetry
|
||||
print("3) Symmetry of matrix1[bp] vs matrix2[bp]:")
|
||||
print(f" At bp=0: matrix1 → {iter_pos_pks[0]:+5.1f}°, matrix2 → {iter_neg_pks[0]:+5.1f}°")
|
||||
print(f" At bp=14: matrix1 → {iter_pos_pks[14]:+5.1f}°, matrix2 → {iter_neg_pks[14]:+5.1f}°")
|
||||
if abs(abs(iter_pos_pks[0]) - abs(iter_neg_pks[0])) > 5:
|
||||
print(f" → ASYMMETRIC: matrix1[bp] and matrix2[bp] are NOT mirror images.")
|
||||
print(f" → Likely indexing intent: matrix2 should use phase_differences[30 - bp]")
|
||||
print(f" (mirror), not phase_differences[bp + 16] (current).")
|
||||
print(" → ASYMMETRIC: matrix1[bp] and matrix2[bp] are NOT mirror images.")
|
||||
print(" → Likely indexing intent: matrix2 should use phase_differences[30 - bp]")
|
||||
print(" (mirror), not phase_differences[bp + 16] (current).")
|
||||
else:
|
||||
print(f" → Symmetric.")
|
||||
print(" → Symmetric.")
|
||||
print()
|
||||
# Coverage
|
||||
all_pks = sorted(iter_pos_pks + [pk0] + iter_neg_pks)
|
||||
@@ -285,20 +285,20 @@ def main():
|
||||
f"and {all_pks[int(np.argmax(gaps))+1]:+.1f}°")
|
||||
print(f" Smallest gap: {min(g for g in gaps if g > 0.1):.2f}° "
|
||||
f"(near broadside — heavily oversampled)")
|
||||
print(f" 1/n distribution → dense near broadside, sparse at large angles.")
|
||||
print(" 1/n distribution → dense near broadside, sparse at large angles.")
|
||||
print()
|
||||
# SLL at extreme scan
|
||||
pk_extreme = max(rows, key=lambda r: abs(r[3] or 0))
|
||||
print(f"5) Worst-case SLL at max scan: bp={pk_extreme[0]}, "
|
||||
f"matrix1 peak={pk_extreme[3]:+.1f}°, SLL={pk_extreme[5]:+.1f} dB")
|
||||
if pk_extreme[5] > -10:
|
||||
print(f" → SLL exceeds -10 dB at extreme scan. Significant scan loss + "
|
||||
f"degraded sidelobe rejection (expected at near-grazing scan).")
|
||||
print(" → SLL exceeds -10 dB at extreme scan. Significant scan loss + "
|
||||
"degraded sidelobe rejection (expected at near-grazing scan).")
|
||||
print()
|
||||
print(f"6) setBeamAngle() (the 4-broadcast bug we found earlier) is DEAD CODE")
|
||||
print(f" in production. main.cpp uses initializeBeamMatrices() +")
|
||||
print(f" setCustomBeamPattern16() exclusively. Fixing setBeamAngle() has zero")
|
||||
print(f" risk of regressing production behaviour.")
|
||||
print("6) setBeamAngle() (the 4-broadcast bug we found earlier) is DEAD CODE")
|
||||
print(" in production. main.cpp uses initializeBeamMatrices() +")
|
||||
print(" setCustomBeamPattern16() exclusively. Fixing setBeamAngle() has zero")
|
||||
print(" risk of regressing production behaviour.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user