diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index 4034977..6934eb5 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -367,6 +367,10 @@ ddc_400m_enhanced ddc( .baseband_q(ddc_out_q), // Q output at 100MHz .baseband_valid_i(ddc_valid_i), // Valid at 100MHz .baseband_valid_q(ddc_valid_q), + // RX DDC mixer is always enabled — asymmetric vs the TX path which CDCs + // stm32_mixers_enable into clk_120m_dac (radar_transmitter.v:175-183). + // Counter-UAS RX has no operational scenario where the digital DDC NCO + // should be quiesced while the system is running; tie-1 is intentional. .mixers_enable(1'b1), // Diagnostics (audit F-6.1) — previously all unconnected .ddc_status(ddc_status_w), @@ -593,7 +597,7 @@ assign range_data_valid = mti_range_valid; doppler_processor_optimized #( .DOPPLER_FFT_SIZE(`RP_DOPPLER_FFT_SIZE), // 16 .RANGE_BINS(`RP_MAX_OUTPUT_BINS), // 512 (50T) / 4096 (200T) [RX-D] - .CHIRPS_PER_FRAME(`RP_CHIRPS_PER_FRAME), // 32 + .CHIRPS_PER_FRAME(`RP_CHIRPS_PER_FRAME), // 48 (PR-F: 3 sub-frames * 16) .CHIRPS_PER_SUBFRAME(`RP_CHIRPS_PER_SUBFRAME) // 16 ) doppler_proc ( .clk(clk), diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index a850fea..5259562 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -271,7 +271,7 @@ reg [15:0] host_short_chirp_cycles; // Opcode 0x13 (default 100, V2) reg [15:0] host_short_listen_cycles; // Opcode 0x14 (default 17400, V2) reg [15:0] host_medium_chirp_cycles; // Opcode 0x17 (default 500, PR-G G2) reg [15:0] host_medium_listen_cycles; // Opcode 0x18 (default 15600, PR-Q staggered PRI) -reg [5:0] host_chirps_per_elev; // Opcode 0x15 (default 32) +reg [5:0] host_chirps_per_elev; // Opcode 0x15 (default 48 = RP_CHIRPS_PER_FRAME, PR-F) reg host_status_request; // Opcode 0xFF (self-clearing pulse) // Fix 4: Doppler/chirps mismatch protection @@ -1056,7 +1056,10 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin host_short_listen_cycles <= 16'd`RP_DEF_SHORT_LISTEN_CYCLES_V2; host_medium_chirp_cycles <= 16'd`RP_DEF_MEDIUM_CHIRP_CYCLES; // PR-G G2 host_medium_listen_cycles <= 16'd`RP_DEF_MEDIUM_LISTEN_CYCLES; // PR-G G2 - host_chirps_per_elev <= 6'd32; + // PR-F bumped RP_CHIRPS_PER_FRAME 32 -> 48 (3 sub-frames * 16); the + // chirps_per_elev register is echoed in status word 3 and used by host + // sanity-checking. Keep cold-reset value in lockstep with the truth. + host_chirps_per_elev <= 6'd48; host_status_request <= 1'b0; chirps_mismatch_error <= 1'b0; host_range_mode <= 2'b00; // Default: 3 km mode (all short chirps) diff --git a/9_Firmware/9_2_FPGA/tb/cosim/real_data/gen_realdata_hex.py b/9_Firmware/9_2_FPGA/tb/cosim/real_data/gen_realdata_hex.py index 82e1d34..9719299 100644 --- a/9_Firmware/9_2_FPGA/tb/cosim/real_data/gen_realdata_hex.py +++ b/9_Firmware/9_2_FPGA/tb/cosim/real_data/gen_realdata_hex.py @@ -18,8 +18,10 @@ Outputs (all under tb/cosim/real_data/hex/): fullchain_doppler_ref_q.hex same shape as doppler_ref_* GUI replay intermediates (.npy, COSIM_DIR ReplayFormat in v7.replay): - decimated_range_i.npy / _q.npy (48, 512) — post range_bin_decimator - doppler_map_i.npy / _q.npy (512, 48) — post doppler_processor + decimated_range_i.npy / _q.npy (48, 512) — post range_bin_decimator + doppler_map_i.npy / _q.npy (512, 48) — post doppler_processor + fullchain_cfar_flags.npy (512, 48) uint8 — CA-CFAR detection mask + fullchain_cfar_mag.npy (512, 48) — |I|+|Q| magnitudes (CFAR input) Dimensions match production (radar_params.vh: RP_FFT_SIZE=2048, RP_DECIMATION_FACTOR=4, RP_NUM_RANGE_BINS=512, RP_NUM_DOPPLER_BINS=48). @@ -39,7 +41,7 @@ import numpy as np sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -from fpga_model import DopplerProcessor, RangeBinDecimator +from fpga_model import DopplerProcessor, RangeBinDecimator, run_cfar_ca from radar_scene import Target, generate_doppler_frame @@ -164,19 +166,33 @@ def gen_fullchain_realdata(): write_hex_16(os.path.join(OUT_DIR, "fullchain_doppler_ref_q.hex"), flat_q) # Same arrays serialized for v7.replay COSIM_DIR format (GUI replay). - np.save(os.path.join(OUT_DIR, "decimated_range_i.npy"), - np.asarray(decim_i_2d, dtype=np.int32)) - np.save(os.path.join(OUT_DIR, "decimated_range_q.npy"), - np.asarray(decim_q_2d, dtype=np.int32)) - np.save(os.path.join(OUT_DIR, "doppler_map_i.npy"), - np.asarray(doppler_i, dtype=np.int32)) - np.save(os.path.join(OUT_DIR, "doppler_map_q.npy"), - np.asarray(doppler_q, dtype=np.int32)) + decim_i_arr = np.asarray(decim_i_2d, dtype=np.int32) + decim_q_arr = np.asarray(decim_q_2d, dtype=np.int32) + doppler_i_arr = np.asarray(doppler_i, dtype=np.int32) + doppler_q_arr = np.asarray(doppler_q, dtype=np.int32) + np.save(os.path.join(OUT_DIR, "decimated_range_i.npy"), decim_i_arr) + np.save(os.path.join(OUT_DIR, "decimated_range_q.npy"), decim_q_arr) + np.save(os.path.join(OUT_DIR, "doppler_map_i.npy"), doppler_i_arr) + np.save(os.path.join(OUT_DIR, "doppler_map_q.npy"), doppler_q_arr) + + # m-9: Run the CA-CFAR model on the doppler map so v7.replay shows + # detections instead of falling through to zeros. Defaults match the FPGA + # cold-reset (RP_DEF_CFAR_*: guard=2, train=8, alpha=0x30=3.0 Q4.4, mode=CA). + cfar_flags, cfar_mag, _thr = run_cfar_ca( + doppler_i_arr, doppler_q_arr, + guard=2, train=8, alpha_q44=0x30, mode='CA', + ) + np.save(os.path.join(OUT_DIR, "fullchain_cfar_flags.npy"), + cfar_flags.astype(np.uint8)) + np.save(os.path.join(OUT_DIR, "fullchain_cfar_mag.npy"), + cfar_mag.astype(np.int64)) print(f" stimulus: {len(stim)} packed lines " f"(expected {CHIRPS_PER_FRAME * FULLCHAIN_INPUT_BINS})") print(f" golden: {len(flat_i)} lines i / {len(flat_q)} lines q " f"(expected {DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS})") + print(f" cfar: {int(cfar_flags.sum())} detections " + f"(across {DOPPLER_RANGE_BINS}x{DOPPLER_TOTAL_BINS} cells)") def main(): @@ -204,6 +220,8 @@ def main(): "decimated_range_q.npy", "doppler_map_i.npy", "doppler_map_q.npy", + "fullchain_cfar_flags.npy", + "fullchain_cfar_mag.npy", ) for f in npy_files: path = os.path.join(OUT_DIR, f) diff --git a/9_Firmware/9_2_FPGA/tb/cosim/real_data/hex/fullchain_cfar_flags.npy b/9_Firmware/9_2_FPGA/tb/cosim/real_data/hex/fullchain_cfar_flags.npy new file mode 100644 index 0000000..4238f17 Binary files /dev/null and b/9_Firmware/9_2_FPGA/tb/cosim/real_data/hex/fullchain_cfar_flags.npy differ diff --git a/9_Firmware/9_2_FPGA/tb/cosim/real_data/hex/fullchain_cfar_mag.npy b/9_Firmware/9_2_FPGA/tb/cosim/real_data/hex/fullchain_cfar_mag.npy new file mode 100644 index 0000000..6c596d1 Binary files /dev/null and b/9_Firmware/9_2_FPGA/tb/cosim/real_data/hex/fullchain_cfar_mag.npy differ diff --git a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v index be44e4f..764a673 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v @@ -958,7 +958,7 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin end end - // ---- Frame header: 8 bytes ---- + // ---- Frame header: 9 bytes (PR-G: added version byte at offset 1) ---- WR_FRAME_HDR: begin if (!ft_txe_n) begin ft_data_oe <= 1'b1; diff --git a/9_Firmware/9_3_GUI/v7/models.py b/9_Firmware/9_3_GUI/v7/models.py index 9392a72..9d1e8ae 100644 --- a/9_Firmware/9_3_GUI/v7/models.py +++ b/9_Firmware/9_3_GUI/v7/models.py @@ -121,8 +121,11 @@ class RadarSettings: not be relied on for physics-accurate display. """ system_frequency: float = 10.5e9 # Hz (carrier, used for velocity calc) - range_resolution: float = 6.0 # Meters per range bin (c/(2*Fs)*decim = 1.5*4) - velocity_resolution: float = 1.0 # m/s per Doppler bin (calibrate to waveform) + # PLACEHOLDER — see class docstring. Live workers derive the real values + # from WaveformConfig (per-subframe LONG/MEDIUM/SHORT v_res). Do not use + # these literals for physics-accurate display. + range_resolution: float = 6.0 # placeholder; legacy 1.5 m * decim=4 + velocity_resolution: float = 1.0 # placeholder; legacy 167 us / 16-chirp / 10.5 GHz max_distance: float = 3072 # Max detection range (m), 3 km mode map_size: float = 4000 # Map display size (m) coverage_radius: float = 3072 # Map coverage radius (m), 3 km mode diff --git a/9_Firmware/9_3_GUI/v7/processing.py b/9_Firmware/9_3_GUI/v7/processing.py index f519f7b..d693e08 100644 --- a/9_Firmware/9_3_GUI/v7/processing.py +++ b/9_Firmware/9_3_GUI/v7/processing.py @@ -56,7 +56,11 @@ class RadarProcessor: """Full radar processing pipeline: fusion, clustering, association, tracking.""" def __init__(self): - self.range_doppler_map = np.zeros((1024, 32)) + # Placeholder shape matches production (NUM_RANGE_BINS x NUM_DOPPLER_BINS + # = 512 x 48 from radar_protocol). Overwritten on every frame; pre-frame + # readers see the correct shape rather than a legacy (1024, 32). + from radar_protocol import NUM_RANGE_BINS, NUM_DOPPLER_BINS + self.range_doppler_map = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS)) self.detected_targets: list[RadarTarget] = [] self.track_id_counter: int = 0 self.tracks: dict[int, dict] = {}