fix: full-repo ruff lint cleanup and CI migration to uv

Resolve all 374 ruff errors across 36 Python files (E501, E702, E722,
E741, F821, F841, invalid-syntax) bringing `ruff check .` to zero
errors repo-wide with line-length=100.

Rewrite CI workflow to use uv for dependency management, whole-repo
`ruff check .`, py_compile syntax gate, and merged python-tests job.
Add pyproject.toml with ruff config and uv dependency groups.

CI structure proposed by hcm444.
This commit is contained in:
Jason
2026-04-09 02:05:34 +03:00
parent 57de32b172
commit 11aa590cf2
31 changed files with 3633 additions and 2789 deletions
+4 -1
View File
@@ -358,7 +358,10 @@ def compare_scenario(scenario_name):
# ---- First/last sample comparison ----
print("\nFirst 10 samples (after alignment):")
print(f" {'idx':>4s} {'RTL_I':>8s} {'Py_I':>8s} {'Err_I':>6s} {'RTL_Q':>8s} {'Py_Q':>8s} {'Err_Q':>6s}")
print(
f" {'idx':>4s} {'RTL_I':>8s} {'Py_I':>8s} {'Err_I':>6s} "
f"{'RTL_Q':>8s} {'Py_Q':>8s} {'Err_Q':>6s}"
)
for k in range(min(10, aligned_len)):
ei = aligned_rtl_i[k] - aligned_py_i[k]
eq = aligned_rtl_q[k] - aligned_py_q[k]
+17 -5
View File
@@ -199,14 +199,17 @@ class NCO:
# Wait - let me re-derive. The Verilog is:
# phase_accumulator <= phase_accumulator + frequency_tuning_word;
# phase_accum_reg <= phase_accumulator; // OLD value (NBA)
# phase_with_offset <= phase_accum_reg + {phase_offset, 16'b0}; // OLD phase_accum_reg
# phase_with_offset <= phase_accum_reg + {phase_offset, 16'b0};
# // OLD phase_accum_reg
# Since all are NBA (<=), they all read the values from BEFORE this edge.
# So: new_phase_accumulator = old_phase_accumulator + ftw
# new_phase_accum_reg = old_phase_accumulator
# new_phase_with_offset = old_phase_accum_reg + offset
old_phase_accumulator = (self.phase_accumulator - ftw) & 0xFFFFFFFF # reconstruct
self.phase_accum_reg = old_phase_accumulator
self.phase_with_offset = (old_phase_accum_reg + ((phase_offset << 16) & 0xFFFFFFFF)) & 0xFFFFFFFF
self.phase_with_offset = (
old_phase_accum_reg + ((phase_offset << 16) & 0xFFFFFFFF)
) & 0xFFFFFFFF
# phase_accumulator was already updated above
# ---- Stage 3a: Register LUT address + quadrant ----
@@ -607,8 +610,14 @@ class FIRFilter:
if (old_valid_pipe >> 0) & 1:
for i in range(16):
# Sign-extend products to ACCUM_WIDTH
a = sign_extend(mult_results[2*i] & ((1 << self.PRODUCT_WIDTH) - 1), self.PRODUCT_WIDTH)
b = sign_extend(mult_results[2*i+1] & ((1 << self.PRODUCT_WIDTH) - 1), self.PRODUCT_WIDTH)
a = sign_extend(
mult_results[2 * i] & ((1 << self.PRODUCT_WIDTH) - 1),
self.PRODUCT_WIDTH,
)
b = sign_extend(
mult_results[2 * i + 1] & ((1 << self.PRODUCT_WIDTH) - 1),
self.PRODUCT_WIDTH,
)
self.add_l0[i] = a + b
# ---- Stage 2 (Level 1): 8 pairwise sums ----
@@ -1365,7 +1374,10 @@ def _self_test():
mag_sq = s * s + c * c
expected = 32767 * 32767
error_pct = abs(mag_sq - expected) / expected * 100
print(f" Quadrature check: sin^2+cos^2={mag_sq}, expected~{expected}, error={error_pct:.2f}%")
print(
f" Quadrature check: sin^2+cos^2={mag_sq}, "
f"expected~{expected}, error={error_pct:.2f}%"
)
print(" NCO: OK")
# --- Mixer test ---
@@ -218,7 +218,8 @@ def generate_long_chirp_test():
if seg == 0:
buffer_write_ptr = 0
else:
# Overlap-save: copy buffer[SEGMENT_ADVANCE:SEGMENT_ADVANCE+OVERLAP] -> buffer[0:OVERLAP]
# Overlap-save: copy
# buffer[SEGMENT_ADVANCE:SEGMENT_ADVANCE+OVERLAP] -> buffer[0:OVERLAP]
for i in range(OVERLAP_SAMPLES):
input_buffer_i[i] = input_buffer_i[i + SEGMENT_ADVANCE]
input_buffer_q[i] = input_buffer_q[i + SEGMENT_ADVANCE]
@@ -335,7 +335,9 @@ def run_ddc(adc_samples):
for n in range(n_samples):
integrators[0][n + 1] = (integrators[0][n] + mixed_i[n]) & ((1 << CIC_ACC_WIDTH) - 1)
for s in range(1, CIC_STAGES):
integrators[s][n + 1] = (integrators[s][n] + integrators[s - 1][n + 1]) & ((1 << CIC_ACC_WIDTH) - 1)
integrators[s][n + 1] = (
integrators[s][n] + integrators[s - 1][n + 1]
) & ((1 << CIC_ACC_WIDTH) - 1)
# Downsample by 4
n_decimated = n_samples // CIC_DECIMATION
@@ -580,8 +582,11 @@ def run_range_bin_decimator(range_fft_i, range_fft_q,
decimated_i = np.zeros((n_chirps, output_bins), dtype=np.int64)
decimated_q = np.zeros((n_chirps, output_bins), dtype=np.int64)
print(f"[DECIM] Decimating {n_in}{output_bins} bins, mode={'peak' if mode==1 else 'avg' if mode==2 else 'simple'}, "
f"start_bin={start_bin}, {n_chirps} chirps")
mode_str = 'peak' if mode == 1 else 'avg' if mode == 2 else 'simple'
print(
f"[DECIM] Decimating {n_in}{output_bins} bins, mode={mode_str}, "
f"start_bin={start_bin}, {n_chirps} chirps"
)
for c in range(n_chirps):
# Index into input, skip start_bin
@@ -678,7 +683,9 @@ def run_doppler_fft(range_data_i, range_data_q, twiddle_file_16=None):
if twiddle_file_16 and os.path.exists(twiddle_file_16):
cos_rom_16 = load_twiddle_rom(twiddle_file_16)
else:
cos_rom_16 = np.round(32767 * np.cos(2 * np.pi * np.arange(n_fft // 4) / n_fft)).astype(np.int64)
cos_rom_16 = np.round(
32767 * np.cos(2 * np.pi * np.arange(n_fft // 4) / n_fft)
).astype(np.int64)
LOG2N_16 = 4
doppler_map_i = np.zeros((n_range, n_total), dtype=np.int64)
@@ -835,7 +842,10 @@ def run_dc_notch(doppler_i, doppler_q, width=2):
notched_i = doppler_i.copy()
notched_q = doppler_q.copy()
print(f"[DC NOTCH] width={width}, {n_range} range bins x {n_doppler} Doppler bins (dual sub-frame)")
print(
f"[DC NOTCH] width={width}, {n_range} range bins x "
f"{n_doppler} Doppler bins (dual sub-frame)"
)
if width == 0:
print(" Pass-through (width=0)")
@@ -1167,7 +1177,12 @@ def main():
parser = argparse.ArgumentParser(description="AERIS-10 FPGA golden reference model")
parser.add_argument('--frame', type=int, default=0, help='Frame index to process')
parser.add_argument('--plot', action='store_true', help='Show plots')
parser.add_argument('--threshold', type=int, default=10000, help='Detection threshold (L1 magnitude)')
parser.add_argument(
'--threshold',
type=int,
default=10000,
help='Detection threshold (L1 magnitude)'
)
args = parser.parse_args()
# Paths
@@ -1175,7 +1190,11 @@ def main():
fpga_dir = os.path.abspath(os.path.join(script_dir, '..', '..', '..'))
data_base = os.path.expanduser("~/Downloads/adi_radar_data")
amp_data = os.path.join(data_base, "amp_radar", "phaser_amp_4MSPS_500M_300u_256_m3dB.npy")
amp_config = os.path.join(data_base, "amp_radar", "phaser_amp_4MSPS_500M_300u_256_m3dB_config.npy")
amp_config = os.path.join(
data_base,
"amp_radar",
"phaser_amp_4MSPS_500M_300u_256_m3dB_config.npy"
)
twiddle_1024 = os.path.join(fpga_dir, "fft_twiddle_1024.mem")
output_dir = os.path.join(script_dir, "hex")
@@ -1290,7 +1309,10 @@ def main():
q_val = int(fc_doppler_q[rbin, dbin]) & 0xFFFF
packed = (q_val << 16) | i_val
f.write(f"{packed:08X}\n")
print(f" Wrote {fc_doppler_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} packed IQ words)")
print(
f" Wrote {fc_doppler_packed_file} ("
f"{DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} packed IQ words)"
)
# Save numpy arrays for the full-chain path
np.save(os.path.join(output_dir, "decimated_range_i.npy"), decim_i)
@@ -1336,7 +1358,10 @@ def main():
q_val = int(notched_q[rbin, dbin]) & 0xFFFF
packed = (q_val << 16) | i_val
f.write(f"{packed:08X}\n")
print(f" Wrote {fc_notched_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} packed IQ words)")
print(
f" Wrote {fc_notched_packed_file} ("
f"{DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} packed IQ words)"
)
# CFAR on DC-notched data
CFAR_GUARD = 2
@@ -1385,7 +1410,10 @@ def main():
with open(cfar_det_list_file, 'w') as f:
f.write("# AERIS-10 Full-Chain CFAR Detection List\n")
f.write(f"# Chain: decim -> MTI -> Doppler -> DC notch(w={DC_NOTCH_WIDTH}) -> CA-CFAR\n")
f.write(f"# CFAR: guard={CFAR_GUARD}, train={CFAR_TRAIN}, alpha=0x{CFAR_ALPHA:02X}, mode={CFAR_MODE}\n")
f.write(
f"# CFAR: guard={CFAR_GUARD}, train={CFAR_TRAIN}, "
f"alpha=0x{CFAR_ALPHA:02X}, mode={CFAR_MODE}\n"
)
f.write("# Format: range_bin doppler_bin magnitude threshold\n")
for det in cfar_detections:
r, d = det
@@ -1481,12 +1509,18 @@ def main():
print(f" Chirps processed: {DOPPLER_CHIRPS}")
print(f" Samples/chirp: {FFT_SIZE}")
print(f" Range FFT: {FFT_SIZE}-point → {snr_range:.1f} dB vs float")
print(f" Doppler FFT (direct): {DOPPLER_FFT_SIZE}-point Hamming → {snr_doppler:.1f} dB vs float")
print(
f" Doppler FFT (direct): {DOPPLER_FFT_SIZE}-point Hamming "
f"{snr_doppler:.1f} dB vs float"
)
print(f" Detections (direct): {len(detections)} (threshold={args.threshold})")
print(" Full-chain decimator: 1024→64 peak detection")
print(f" Full-chain detections: {len(fc_detections)} (threshold={args.threshold})")
print(f" MTI+CFAR chain: decim → MTI → Doppler → DC notch(w={DC_NOTCH_WIDTH}) → CA-CFAR")
print(f" CFAR detections: {len(cfar_detections)} (guard={CFAR_GUARD}, train={CFAR_TRAIN}, alpha=0x{CFAR_ALPHA:02X})")
print(
f" CFAR detections: {len(cfar_detections)} "
f"(guard={CFAR_GUARD}, train={CFAR_TRAIN}, alpha=0x{CFAR_ALPHA:02X})"
)
print(f" Hex stimulus files: {output_dir}/")
print(" Ready for RTL co-simulation with Icarus Verilog")
@@ -199,7 +199,10 @@ def test_long_chirp():
avg_mag = sum(magnitudes) / len(magnitudes)
print(f" Magnitude: min={min_mag:.1f}, max={max_mag:.1f}, avg={avg_mag:.1f}")
print(f" Max magnitude as fraction of Q15 range: {max_mag/32767:.4f} ({max_mag/32767*100:.2f}%)")
print(
f" Max magnitude as fraction of Q15 range: "
f"{max_mag/32767:.4f} ({max_mag/32767*100:.2f}%)"
)
# Check if this looks like it came from generate_reference_chirp_q15
# That function uses 32767 * 0.9 scaling => max magnitude ~29490
@@ -262,7 +265,10 @@ def test_long_chirp():
# Check if bandwidth roughly matches expected
bw_match = abs(f_range - CHIRP_BW) / CHIRP_BW < 0.5 # within 50%
if bw_match:
print(f" Bandwidth {f_range/1e6:.2f} MHz roughly matches expected {CHIRP_BW/1e6:.2f} MHz")
print(
f" Bandwidth {f_range/1e6:.2f} MHz roughly matches expected "
f"{CHIRP_BW/1e6:.2f} MHz"
)
else:
warn(f"Bandwidth {f_range/1e6:.2f} MHz does NOT match expected {CHIRP_BW/1e6:.2f} MHz")
@@ -415,8 +421,11 @@ def test_chirp_vs_model():
print(f" Max phase diff: {max_phase_diff:.4f} rad ({math.degrees(max_phase_diff):.2f} deg)")
phase_match = max_phase_diff < 0.5 # within 0.5 rad
check(phase_match,
f"Phase shape match: max diff = {math.degrees(max_phase_diff):.1f} deg (tolerance: 28.6 deg)")
check(
phase_match,
f"Phase shape match: max diff = {math.degrees(max_phase_diff):.1f} deg "
f"(tolerance: 28.6 deg)",
)
# ============================================================================
@@ -521,8 +530,11 @@ def test_memory_addressing():
addr_from_concat = (seg << 10) | 0 # {seg[1:0], 10'b0}
addr_end = (seg << 10) | 1023
check(addr_from_concat == base,
f"Seg {seg} base address: {{{seg}[1:0], 10'b0}} = {addr_from_concat} (expected {base})")
check(
addr_from_concat == base,
f"Seg {seg} base address: {{{seg}[1:0], 10'b0}} = {addr_from_concat} "
f"(expected {base})",
)
check(addr_end == end,
f"Seg {seg} end address: {{{seg}[1:0], 10'h3FF}} = {addr_end} (expected {end})")