feat: 2048-pt FFT upgrade with decimation=4, 512 output bins, 6m spacing
Complete cross-layer upgrade from 1024-pt/64-bin to 2048-pt/512-bin FFT: FPGA RTL (14+ modules): - radar_params.vh: FFT_SIZE=2048, RANGE_BINS=512, 9-bit range, 6-bit stream - fft_engine.v: 2048-pt FFT with XPM BRAM - chirp_memory_loader_param.v: 2 segments x 2048 (was 4 x 1024) - matched_filter_multi_segment.v: BRAM inference for overlap_cache, explicit ov_waddr - mti_canceller.v: BRAM inference for prev_i/q arrays (was fabric FFs) - doppler_processor.v: 16384-deep memory, 14-bit addressing - cfar_ca.v: 512 rows, indentation fix - radar_receiver_final.v: rising-edge detector for frame_complete, 11-bit sample_addr - range_bin_decimator.v: 512 output bins - usb_data_interface_ft2232h.v: bulk per-frame with Manhattan magnitude - radar_mode_controller.v: XOR edge detector for toggle signals - rx_gain_control.v: updated for new bin count Python GUI + Protocol (8 files): - radar_protocol.py: 512-bin bulk frame parser, LSB-first bitmap - GUI_V65_Tk.py, v7/*.py: updated for 512 bins, 6m range resolution Golden data + tests: - All .hex/.csv/.npy golden references regenerated for 2048/512 - fft_twiddle_2048.mem added - Deleted stale seg2/seg3 chirp mem files - 9 new bulk frame cross-layer tests, deleted 6 stale per-sample tests - Deleted stale tb_cross_layer_ft2232h.v and dead contract_parser functions - Updated validate_mem_files.py for 2048/2-segment config MCU: RadarSettings.cpp max_distance/map_size 1536->3072 All 4 CI jobs pass: 285 tests, 0 failures, 0 skips
This commit is contained in:
@@ -56,7 +56,8 @@ log = logging.getLogger(__name__)
|
||||
# Twiddle factor file paths (relative to FPGA root)
|
||||
# ---------------------------------------------------------------------------
|
||||
_FPGA_DIR = Path(__file__).resolve().parents[2] / "9_2_FPGA"
|
||||
TWIDDLE_1024 = str(_FPGA_DIR / "fft_twiddle_1024.mem")
|
||||
TWIDDLE_2048 = str(_FPGA_DIR / "fft_twiddle_2048.mem")
|
||||
TWIDDLE_1024 = str(_FPGA_DIR / "fft_twiddle_1024.mem") # kept for reference
|
||||
TWIDDLE_16 = str(_FPGA_DIR / "fft_twiddle_16.mem")
|
||||
|
||||
# CFAR mode int→string mapping (FPGA register 0x24: 0=CA, 1=GO, 2=SO)
|
||||
@@ -179,15 +180,19 @@ class SoftwareFPGA:
|
||||
# --- Stage 1: Range FFT (per chirp) ---
|
||||
range_i = np.zeros((n_chirps, n_samples), dtype=np.int64)
|
||||
range_q = np.zeros((n_chirps, n_samples), dtype=np.int64)
|
||||
twiddle_1024 = TWIDDLE_1024 if os.path.exists(TWIDDLE_1024) else None
|
||||
# Select twiddle file matching input FFT size
|
||||
if n_samples >= 2048:
|
||||
twiddle = TWIDDLE_2048 if os.path.exists(TWIDDLE_2048) else None
|
||||
else:
|
||||
twiddle = TWIDDLE_1024 if os.path.exists(TWIDDLE_1024) else None
|
||||
for c in range(n_chirps):
|
||||
range_i[c], range_q[c] = run_range_fft(
|
||||
iq_i[c].astype(np.int64),
|
||||
iq_q[c].astype(np.int64),
|
||||
twiddle_file=twiddle_1024,
|
||||
twiddle_file=twiddle,
|
||||
)
|
||||
|
||||
# --- Stage 2: Range bin decimation (1024 → 64) ---
|
||||
# --- Stage 2: Range bin decimation (2048 → 512) ---
|
||||
decim_i, decim_q = run_range_bin_decimator(range_i, range_q)
|
||||
|
||||
# --- Stage 3: MTI canceller (pre-Doppler, per-chirp) ---
|
||||
@@ -230,6 +235,10 @@ class SoftwareFPGA:
|
||||
frame.range_doppler_q = np.clip(notch_q, -32768, 32767).astype(np.int16)
|
||||
frame.magnitude = mag
|
||||
frame.detections = det_mask
|
||||
# Range profile: magnitude at Doppler bin 0 (zero-velocity / stationary).
|
||||
# This differs from the FPGA USB stream which sends per-chirp decimated
|
||||
# Manhattan magnitude. The zero-Doppler slice is more useful for the
|
||||
# host-side display because it represents coherently integrated range energy.
|
||||
frame.range_profile = np.sqrt(
|
||||
notch_i[:, 0].astype(np.float64) ** 2
|
||||
+ notch_q[:, 0].astype(np.float64) ** 2
|
||||
@@ -257,7 +266,7 @@ def quantize_raw_iq(
|
||||
n_chirps : int
|
||||
Number of chirps to keep (default 32, matching FPGA).
|
||||
n_samples : int
|
||||
Number of samples per chirp to keep (default 1024, matching FFT).
|
||||
Number of samples per chirp to keep (default 2048, matching FFT).
|
||||
peak_target : int
|
||||
Target peak magnitude after scaling (default 200, matching
|
||||
golden_reference INPUT_PEAK_TARGET).
|
||||
@@ -270,8 +279,11 @@ def quantize_raw_iq(
|
||||
# (frames, chirps, samples) — take first frame
|
||||
raw_complex = raw_complex[0]
|
||||
|
||||
# Truncate to FPGA dimensions
|
||||
block = raw_complex[:n_chirps, :n_samples]
|
||||
# Truncate chirps, zero-pad samples if source is shorter than n_samples
|
||||
block = np.zeros((n_chirps, n_samples), dtype=raw_complex.dtype)
|
||||
avail_chirps = min(raw_complex.shape[0], n_chirps)
|
||||
avail_samples = min(raw_complex.shape[1], n_samples)
|
||||
block[:avail_chirps, :avail_samples] = raw_complex[:avail_chirps, :avail_samples]
|
||||
|
||||
max_abs = np.max(np.abs(block))
|
||||
if max_abs == 0:
|
||||
|
||||
Reference in New Issue
Block a user