fix: align all range/carrier/velocity values to PLFM hardware + FPGA bug fixes
- Correct carrier from 10.525/10 GHz to 10.5 GHz (verified ADF4382 config) - Correct range-per-bin from 4.8/5.6/781.25 m to 24.0 m (matched-filter) - Correct velocity resolution from 1.484 to 2.67 m/s/bin (PRI-based) - Correct processing rate from 4 MSPS to 100 MSPS (post-DDC) - Correct max range from 307/5000/50000 m to 1536 m (64 bins x 24 m) - Add WaveformConfig.pri_s field (167 us PRI for velocity calculation) - Fix short chirp chirp_complete deadlock (Bug A) - Remove dead short_chirp ports, rename long_chirp to ref_chirp (Bug B) - Fix stale latency comment 2159 -> 3187 cycles (Bug C) - Create radar_params.vh as single source of truth for FPGA parameters - Lower RadarSettings.cpp map_size validation bound from 1000 to 100 - Add PLFM hardware constants to golden_reference.py - Update all GUI versions, tests, and cross-layer contracts All 244 tests passing (167 Python + 21 MCU + 29 cross-layer + 27 FPGA)
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
# Define parameters
|
# Define parameters
|
||||||
fs = 120e6 # Sampling frequency
|
# NOTE: This is a standalone LUT generation utility. The production chirp LUT
|
||||||
|
# is generated by 9_Firmware/9_2_FPGA/tb/cosim/gen_chirp_mem.py with
|
||||||
|
# CHIRP_BW=20e6 (target: 30e6 Phase 1) and DAC_CLK=120e6.
|
||||||
|
fs = 120e6 # Sampling frequency (DAC clock from AD9523 OUT10)
|
||||||
Ts = 1 / fs # Sampling time
|
Ts = 1 / fs # Sampling time
|
||||||
Tb = 1e-6 # Burst time
|
Tb = 1e-6 # Burst time
|
||||||
Tau = 30e-6 # Pulse repetition time
|
Tau = 30e-6 # Pulse repetition time
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ RadarSettings::RadarSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RadarSettings::resetToDefaults() {
|
void RadarSettings::resetToDefaults() {
|
||||||
system_frequency = 10.0e9; // 10 GHz
|
system_frequency = 10.5e9; // 10.5 GHz (PLFM TX LO, ADF4382 config)
|
||||||
chirp_duration_1 = 30.0e-6; // 30 �s
|
chirp_duration_1 = 30.0e-6; // 30 µs
|
||||||
chirp_duration_2 = 0.5e-6; // 0.5 �s
|
chirp_duration_2 = 0.5e-6; // 0.5 µs
|
||||||
chirps_per_position = 32;
|
chirps_per_position = 32;
|
||||||
freq_min = 10.0e6; // 10 MHz
|
freq_min = 10.0e6; // 10 MHz
|
||||||
freq_max = 30.0e6; // 30 MHz
|
freq_max = 30.0e6; // 30 MHz
|
||||||
prf1 = 1000.0; // 1 kHz
|
prf1 = 1000.0; // 1 kHz
|
||||||
prf2 = 2000.0; // 2 kHz
|
prf2 = 2000.0; // 2 kHz
|
||||||
max_distance = 50000.0; // 50 km
|
max_distance = 1536.0; // 1536 m (64 bins × 24 m, 3 km mode)
|
||||||
map_size = 50000.0; // 50 km
|
map_size = 1536.0; // 1536 m
|
||||||
|
|
||||||
settings_valid = true;
|
settings_valid = true;
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ bool RadarSettings::validateSettings() {
|
|||||||
if (prf1 < 100 || prf1 > 10000) return false;
|
if (prf1 < 100 || prf1 > 10000) return false;
|
||||||
if (prf2 < 100 || prf2 > 10000) return false;
|
if (prf2 < 100 || prf2 > 10000) return false;
|
||||||
if (max_distance < 100 || max_distance > 100000) return false;
|
if (max_distance < 100 || max_distance > 100000) return false;
|
||||||
if (map_size < 1000 || map_size > 200000) return false;
|
if (map_size < 100 || map_size > 200000) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,9 @@ module matched_filter_multi_segment (
|
|||||||
input wire mc_new_elevation, // Toggle for new elevation (32)
|
input wire mc_new_elevation, // Toggle for new elevation (32)
|
||||||
input wire mc_new_azimuth, // Toggle for new azimuth (50)
|
input wire mc_new_azimuth, // Toggle for new azimuth (50)
|
||||||
|
|
||||||
input wire [15:0] long_chirp_real,
|
// Reference chirp (upstream memory loader selects long/short via use_long_chirp)
|
||||||
input wire [15:0] long_chirp_imag,
|
input wire [15:0] ref_chirp_real,
|
||||||
input wire [15:0] short_chirp_real,
|
input wire [15:0] ref_chirp_imag,
|
||||||
input wire [15:0] short_chirp_imag,
|
|
||||||
|
|
||||||
// Memory system interface
|
// Memory system interface
|
||||||
output reg [1:0] segment_request,
|
output reg [1:0] segment_request,
|
||||||
@@ -244,6 +243,7 @@ always @(posedge clk or negedge reset_n) begin
|
|||||||
if (!use_long_chirp) begin
|
if (!use_long_chirp) begin
|
||||||
if (chirp_samples_collected >= SHORT_CHIRP_SAMPLES - 1) begin
|
if (chirp_samples_collected >= SHORT_CHIRP_SAMPLES - 1) begin
|
||||||
state <= ST_ZERO_PAD;
|
state <= ST_ZERO_PAD;
|
||||||
|
chirp_complete <= 1; // Bug A fix: mark chirp done so ST_OUTPUT exits to IDLE
|
||||||
`ifdef SIMULATION
|
`ifdef SIMULATION
|
||||||
$display("[MULTI_SEG_FIXED] Short chirp: collected %d samples, starting zero-pad",
|
$display("[MULTI_SEG_FIXED] Short chirp: collected %d samples, starting zero-pad",
|
||||||
chirp_samples_collected + 1);
|
chirp_samples_collected + 1);
|
||||||
@@ -500,11 +500,9 @@ matched_filter_processing_chain m_f_p_c(
|
|||||||
// Chirp Selection
|
// Chirp Selection
|
||||||
.chirp_counter(chirp_counter),
|
.chirp_counter(chirp_counter),
|
||||||
|
|
||||||
// Reference Chirp Memory Interfaces
|
// Reference Chirp Memory Interface (single pair — upstream selects long/short)
|
||||||
.long_chirp_real(long_chirp_real),
|
.ref_chirp_real(ref_chirp_real),
|
||||||
.long_chirp_imag(long_chirp_imag),
|
.ref_chirp_imag(ref_chirp_imag),
|
||||||
.short_chirp_real(short_chirp_real),
|
|
||||||
.short_chirp_imag(short_chirp_imag),
|
|
||||||
|
|
||||||
// Output
|
// Output
|
||||||
.range_profile_i(fft_pc_i),
|
.range_profile_i(fft_pc_i),
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
* .clk, .reset_n
|
* .clk, .reset_n
|
||||||
* .adc_data_i, .adc_data_q, .adc_valid <- from input buffer
|
* .adc_data_i, .adc_data_q, .adc_valid <- from input buffer
|
||||||
* .chirp_counter <- 6-bit frame counter
|
* .chirp_counter <- 6-bit frame counter
|
||||||
* .long_chirp_real/imag, .short_chirp_real/imag <- reference (time-domain)
|
* .ref_chirp_real/imag <- reference (time-domain)
|
||||||
* .range_profile_i, .range_profile_q, .range_profile_valid -> output
|
* .range_profile_i, .range_profile_q, .range_profile_valid -> output
|
||||||
* .chain_state -> 4-bit status
|
* .chain_state -> 4-bit status
|
||||||
*
|
*
|
||||||
@@ -48,10 +48,10 @@ module matched_filter_processing_chain (
|
|||||||
input wire [5:0] chirp_counter,
|
input wire [5:0] chirp_counter,
|
||||||
|
|
||||||
// Reference chirp (time-domain, latency-aligned by upstream buffer)
|
// Reference chirp (time-domain, latency-aligned by upstream buffer)
|
||||||
input wire [15:0] long_chirp_real,
|
// Upstream chirp_memory_loader_param selects long/short reference
|
||||||
input wire [15:0] long_chirp_imag,
|
// via use_long_chirp — this single pair carries whichever is active.
|
||||||
input wire [15:0] short_chirp_real,
|
input wire [15:0] ref_chirp_real,
|
||||||
input wire [15:0] short_chirp_imag,
|
input wire [15:0] ref_chirp_imag,
|
||||||
|
|
||||||
// Output: range profile (pulse-compressed)
|
// Output: range profile (pulse-compressed)
|
||||||
output wire signed [15:0] range_profile_i,
|
output wire signed [15:0] range_profile_i,
|
||||||
@@ -189,8 +189,8 @@ always @(posedge clk or negedge reset_n) begin
|
|||||||
// Store first sample (signal + reference)
|
// Store first sample (signal + reference)
|
||||||
fwd_buf_i[0] <= $signed(adc_data_i);
|
fwd_buf_i[0] <= $signed(adc_data_i);
|
||||||
fwd_buf_q[0] <= $signed(adc_data_q);
|
fwd_buf_q[0] <= $signed(adc_data_q);
|
||||||
ref_buf_i[0] <= $signed(long_chirp_real);
|
ref_buf_i[0] <= $signed(ref_chirp_real);
|
||||||
ref_buf_q[0] <= $signed(long_chirp_imag);
|
ref_buf_q[0] <= $signed(ref_chirp_imag);
|
||||||
fwd_in_count <= 1;
|
fwd_in_count <= 1;
|
||||||
state <= ST_FWD_FFT;
|
state <= ST_FWD_FFT;
|
||||||
end
|
end
|
||||||
@@ -205,8 +205,8 @@ always @(posedge clk or negedge reset_n) begin
|
|||||||
if (adc_valid && fwd_in_count < FFT_SIZE) begin
|
if (adc_valid && fwd_in_count < FFT_SIZE) begin
|
||||||
fwd_buf_i[fwd_in_count] <= $signed(adc_data_i);
|
fwd_buf_i[fwd_in_count] <= $signed(adc_data_i);
|
||||||
fwd_buf_q[fwd_in_count] <= $signed(adc_data_q);
|
fwd_buf_q[fwd_in_count] <= $signed(adc_data_q);
|
||||||
ref_buf_i[fwd_in_count] <= $signed(long_chirp_real);
|
ref_buf_i[fwd_in_count] <= $signed(ref_chirp_real);
|
||||||
ref_buf_q[fwd_in_count] <= $signed(long_chirp_imag);
|
ref_buf_q[fwd_in_count] <= $signed(ref_chirp_imag);
|
||||||
fwd_in_count <= fwd_in_count + 1;
|
fwd_in_count <= fwd_in_count + 1;
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -775,16 +775,16 @@ always @(posedge clk) begin : ref_bram_port
|
|||||||
if (adc_valid) begin
|
if (adc_valid) begin
|
||||||
we = 1'b1;
|
we = 1'b1;
|
||||||
addr = 0;
|
addr = 0;
|
||||||
wdata_i = $signed(long_chirp_real);
|
wdata_i = $signed(ref_chirp_real);
|
||||||
wdata_q = $signed(long_chirp_imag);
|
wdata_q = $signed(ref_chirp_imag);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ST_COLLECT: begin
|
ST_COLLECT: begin
|
||||||
if (adc_valid && collect_count < FFT_SIZE) begin
|
if (adc_valid && collect_count < FFT_SIZE) begin
|
||||||
we = 1'b1;
|
we = 1'b1;
|
||||||
addr = collect_count[ADDR_BITS-1:0];
|
addr = collect_count[ADDR_BITS-1:0];
|
||||||
wdata_i = $signed(long_chirp_real);
|
wdata_i = $signed(ref_chirp_real);
|
||||||
wdata_q = $signed(long_chirp_imag);
|
wdata_q = $signed(ref_chirp_imag);
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
ST_REF_FFT: begin
|
ST_REF_FFT: begin
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// radar_params.vh — Single Source of Truth for AERIS-10 FPGA Parameters
|
||||||
|
// ============================================================================
|
||||||
|
//
|
||||||
|
// ALL modules in the FPGA processing chain MUST `include this file instead of
|
||||||
|
// hardcoding range bins, segment counts, chirp samples, or timing values.
|
||||||
|
//
|
||||||
|
// This file uses `define macros (not localparam) so it can be included at any
|
||||||
|
// scope. Each consuming module should include this file inside its body and
|
||||||
|
// optionally alias macros to localparams for readability.
|
||||||
|
//
|
||||||
|
// BOARD VARIANTS:
|
||||||
|
// SUPPORT_LONG_RANGE = 0 (50T, USB_MODE=1) — 3 km mode only, 64 range bins
|
||||||
|
// SUPPORT_LONG_RANGE = 1 (200T, USB_MODE=0) — 3 km + 20 km modes, up to 1024 bins
|
||||||
|
//
|
||||||
|
// RANGE MODES (runtime, via host_range_mode register, opcode 0x20):
|
||||||
|
// 2'b00 = 3 km (default on both boards)
|
||||||
|
// 2'b01 = 20 km (200T only; clamped to 3 km on 50T)
|
||||||
|
// 2'b10 = Reserved
|
||||||
|
// 2'b11 = Reserved
|
||||||
|
//
|
||||||
|
// USAGE:
|
||||||
|
// `include "radar_params.vh"
|
||||||
|
// Then reference `RP_FFT_SIZE, `RP_MAX_OUTPUT_BINS, etc.
|
||||||
|
//
|
||||||
|
// PHYSICAL CONSTANTS (derived from hardware):
|
||||||
|
// ADC clock: 400 MSPS
|
||||||
|
// CIC decimation: 4x
|
||||||
|
// Processing rate: 100 MSPS (post-DDC)
|
||||||
|
// Range per sample: c / (2 * 100e6) = 1.5 m
|
||||||
|
// Decimation factor: 16 (1024 FFT bins -> 64 output bins per segment)
|
||||||
|
// Range per dec. bin: 1.5 m * 16 = 24.0 m
|
||||||
|
// Carrier frequency: 10.5 GHz
|
||||||
|
//
|
||||||
|
// CHIRP BANDWIDTH (Phase 1 target — currently 20 MHz, planned 30 MHz):
|
||||||
|
// Range resolution: c / (2 * BW)
|
||||||
|
// 20 MHz -> 7.5 m
|
||||||
|
// 30 MHz -> 5.0 m
|
||||||
|
// NOTE: Range resolution is independent of range-per-bin. Resolution
|
||||||
|
// determines the minimum separation between two targets; range-per-bin
|
||||||
|
// determines the spatial sampling grid.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
`ifndef RADAR_PARAMS_VH
|
||||||
|
`define RADAR_PARAMS_VH
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BOARD VARIANT — set at synthesis time, NOT runtime
|
||||||
|
// ============================================================================
|
||||||
|
// Default to 50T (conservative). Override in top-level or synthesis script:
|
||||||
|
// +define+SUPPORT_LONG_RANGE
|
||||||
|
// or via Vivado: set_property verilog_define {SUPPORT_LONG_RANGE} [current_fileset]
|
||||||
|
|
||||||
|
// Note: SUPPORT_LONG_RANGE is a flag define (ifdef/ifndef), not a value.
|
||||||
|
// `ifndef SUPPORT_LONG_RANGE means 50T (no long range).
|
||||||
|
// `ifdef SUPPORT_LONG_RANGE means 200T (long range supported).
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FFT AND PROCESSING CONSTANTS (fixed, both modes)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
`define RP_FFT_SIZE 1024 // Range FFT points per segment
|
||||||
|
`define RP_OVERLAP_SAMPLES 128 // Overlap between adjacent segments
|
||||||
|
`define RP_SEGMENT_ADVANCE 896 // FFT_SIZE - OVERLAP = 1024 - 128
|
||||||
|
`define RP_DECIMATION_FACTOR 16 // Range bin decimation (1024 -> 64)
|
||||||
|
`define RP_BINS_PER_SEGMENT 64 // FFT_SIZE / DECIMATION_FACTOR
|
||||||
|
`define RP_DOPPLER_FFT_SIZE 16 // Per sub-frame Doppler FFT
|
||||||
|
`define RP_CHIRPS_PER_FRAME 32 // Total chirps (16 long + 16 short)
|
||||||
|
`define RP_CHIRPS_PER_SUBFRAME 16 // Chirps per Doppler sub-frame
|
||||||
|
`define RP_NUM_DOPPLER_BINS 32 // 2 sub-frames * 16 = 32
|
||||||
|
`define RP_DATA_WIDTH 16 // ADC/processing data width
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 3 KM MODE PARAMETERS (both 50T and 200T)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
`define RP_LONG_CHIRP_SAMPLES_3KM 3000 // 30 us at 100 MSPS
|
||||||
|
`define RP_LONG_SEGMENTS_3KM 4 // ceil((3000-1024)/896) + 1 = 4
|
||||||
|
`define RP_OUTPUT_RANGE_BINS_3KM 64 // Downstream pipeline expects 64 range bins (NOTE: will become 128 after 2048-pt FFT upgrade)
|
||||||
|
`define RP_SHORT_CHIRP_SAMPLES 50 // 0.5 us at 100 MSPS (same both modes)
|
||||||
|
`define RP_SHORT_SEGMENTS 1 // Single segment for short chirp
|
||||||
|
|
||||||
|
// Derived 3 km limits
|
||||||
|
`define RP_MAX_RANGE_3KM 1536 // 64 bins * 24 m = 1536 m
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 20 KM MODE PARAMETERS (200T only)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
`define RP_LONG_CHIRP_SAMPLES_20KM 13700 // 137 us at 100 MSPS (= listen window)
|
||||||
|
`define RP_LONG_SEGMENTS_20KM 16 // ceil((13700-1024)/896) + 1 = 16
|
||||||
|
`define RP_OUTPUT_RANGE_BINS_20KM 1024 // 16 segments * 64 dec. bins each
|
||||||
|
|
||||||
|
// Derived 20 km limits
|
||||||
|
`define RP_MAX_RANGE_20KM 24576 // 1024 bins * 24 m = 24576 m
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// MAX VALUES (for sizing buffers — compile-time, based on board variant)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
`ifdef SUPPORT_LONG_RANGE
|
||||||
|
`define RP_MAX_SEGMENTS 16
|
||||||
|
`define RP_MAX_OUTPUT_BINS 1024
|
||||||
|
`define RP_MAX_CHIRP_SAMPLES 13700
|
||||||
|
`else
|
||||||
|
`define RP_MAX_SEGMENTS 4
|
||||||
|
`define RP_MAX_OUTPUT_BINS 64
|
||||||
|
`define RP_MAX_CHIRP_SAMPLES 3000
|
||||||
|
`endif
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BIT WIDTHS (derived from MAX values)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Segment index: ceil(log2(MAX_SEGMENTS))
|
||||||
|
// 50T: log2(4) = 2 bits
|
||||||
|
// 200T: log2(16) = 4 bits
|
||||||
|
`ifdef SUPPORT_LONG_RANGE
|
||||||
|
`define RP_SEGMENT_IDX_WIDTH 4
|
||||||
|
`define RP_RANGE_BIN_WIDTH 10
|
||||||
|
`define RP_CHIRP_MEM_ADDR_W 14 // log2(16*1024) = 14
|
||||||
|
`define RP_DOPPLER_MEM_ADDR_W 15 // log2(1024*32) = 15
|
||||||
|
`define RP_CFAR_MAG_ADDR_W 15 // log2(1024*32) = 15
|
||||||
|
`else
|
||||||
|
`define RP_SEGMENT_IDX_WIDTH 2
|
||||||
|
`define RP_RANGE_BIN_WIDTH 6
|
||||||
|
`define RP_CHIRP_MEM_ADDR_W 12 // log2(4*1024) = 12
|
||||||
|
`define RP_DOPPLER_MEM_ADDR_W 11 // log2(64*32) = 11
|
||||||
|
`define RP_CFAR_MAG_ADDR_W 11 // log2(64*32) = 11
|
||||||
|
`endif
|
||||||
|
|
||||||
|
// Derived depths (for memory declarations)
|
||||||
|
// Usage: reg [15:0] mem [0:`RP_CHIRP_MEM_DEPTH-1];
|
||||||
|
`define RP_CHIRP_MEM_DEPTH (`RP_MAX_SEGMENTS * `RP_FFT_SIZE)
|
||||||
|
`define RP_DOPPLER_MEM_DEPTH (`RP_MAX_OUTPUT_BINS * `RP_CHIRPS_PER_FRAME)
|
||||||
|
`define RP_CFAR_MAG_DEPTH (`RP_MAX_OUTPUT_BINS * `RP_NUM_DOPPLER_BINS)
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CHIRP TIMING DEFAULTS (100 MHz clock cycles)
|
||||||
|
// ============================================================================
|
||||||
|
// Reset defaults for host-configurable timing registers.
|
||||||
|
// Match radar_mode_controller.v parameters and main.cpp STM32 defaults.
|
||||||
|
|
||||||
|
`define RP_DEF_LONG_CHIRP_CYCLES 3000 // 30 us
|
||||||
|
`define RP_DEF_LONG_LISTEN_CYCLES 13700 // 137 us
|
||||||
|
`define RP_DEF_GUARD_CYCLES 17540 // 175.4 us
|
||||||
|
`define RP_DEF_SHORT_CHIRP_CYCLES 50 // 0.5 us
|
||||||
|
`define RP_DEF_SHORT_LISTEN_CYCLES 17450 // 174.5 us
|
||||||
|
`define RP_DEF_CHIRPS_PER_ELEV 32
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// BLIND ZONE CONSTANTS (informational, for comments and GUI)
|
||||||
|
// ============================================================================
|
||||||
|
// Long chirp blind zone: c * 30 us / 2 = 4500 m
|
||||||
|
// Short chirp blind zone: c * 0.5 us / 2 = 75 m
|
||||||
|
|
||||||
|
`define RP_LONG_BLIND_ZONE_M 4500
|
||||||
|
`define RP_SHORT_BLIND_ZONE_M 75
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// PHYSICAL CONSTANTS (integer-scaled for Verilog — use in comments/assertions)
|
||||||
|
// ============================================================================
|
||||||
|
// Range per ADC sample: 1.5 m (stored as 15 in units of 0.1 m)
|
||||||
|
// Range per decimated bin: 24.0 m (stored as 240 in units of 0.1 m)
|
||||||
|
// Processing rate: 100 MSPS
|
||||||
|
|
||||||
|
`define RP_RANGE_PER_SAMPLE_DM 15 // 1.5 m in decimeters
|
||||||
|
`define RP_RANGE_PER_BIN_DM 240 // 24.0 m in decimeters
|
||||||
|
`define RP_PROCESSING_RATE_MHZ 100
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// AGC DEFAULTS
|
||||||
|
// ============================================================================
|
||||||
|
`define RP_DEF_AGC_TARGET 200
|
||||||
|
`define RP_DEF_AGC_ATTACK 1
|
||||||
|
`define RP_DEF_AGC_DECAY 1
|
||||||
|
`define RP_DEF_AGC_HOLDOFF 4
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// CFAR DEFAULTS
|
||||||
|
// ============================================================================
|
||||||
|
`define RP_DEF_CFAR_GUARD 2
|
||||||
|
`define RP_DEF_CFAR_TRAIN 8
|
||||||
|
`define RP_DEF_CFAR_ALPHA 8'h30 // 3.0 in Q4.4
|
||||||
|
`define RP_DEF_CFAR_MODE 2'b00 // CA-CFAR
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// DETECTION DEFAULTS
|
||||||
|
// ============================================================================
|
||||||
|
`define RP_DEF_DETECT_THRESHOLD 10000
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// RANGE MODE ENCODING
|
||||||
|
// ============================================================================
|
||||||
|
`define RP_RANGE_MODE_3KM 2'b00
|
||||||
|
`define RP_RANGE_MODE_20KM 2'b01
|
||||||
|
`define RP_RANGE_MODE_RSVD2 2'b10
|
||||||
|
`define RP_RANGE_MODE_RSVD3 2'b11
|
||||||
|
|
||||||
|
`endif // RADAR_PARAMS_VH
|
||||||
@@ -102,9 +102,9 @@ wire [7:0] gc_saturation_count; // Diagnostic: per-frame clipped sample counter
|
|||||||
wire [7:0] gc_peak_magnitude; // Diagnostic: per-frame peak magnitude
|
wire [7:0] gc_peak_magnitude; // Diagnostic: per-frame peak magnitude
|
||||||
wire [3:0] gc_current_gain; // Diagnostic: effective gain_shift
|
wire [3:0] gc_current_gain; // Diagnostic: effective gain_shift
|
||||||
|
|
||||||
// Reference signals for the processing chain
|
// Reference signal for the processing chain (carries long OR short ref
|
||||||
wire [15:0] long_chirp_real, long_chirp_imag;
|
// depending on use_long_chirp — selected by chirp_memory_loader_param)
|
||||||
wire [15:0] short_chirp_real, short_chirp_imag;
|
wire [15:0] ref_chirp_real, ref_chirp_imag;
|
||||||
|
|
||||||
// ========== DOPPLER PROCESSING SIGNALS ==========
|
// ========== DOPPLER PROCESSING SIGNALS ==========
|
||||||
wire [31:0] range_data_32bit;
|
wire [31:0] range_data_32bit;
|
||||||
@@ -292,7 +292,8 @@ end
|
|||||||
// sample_addr_wire removed — was unused implicit wire (synthesis warning)
|
// sample_addr_wire removed — was unused implicit wire (synthesis warning)
|
||||||
|
|
||||||
// 4. CRITICAL: Reference Chirp Latency Buffer
|
// 4. CRITICAL: Reference Chirp Latency Buffer
|
||||||
// This aligns reference data with FFT output (2159 cycle delay)
|
// This aligns reference data with FFT output (3187 cycle delay)
|
||||||
|
// TODO: verify empirically during hardware bring-up with correlation test
|
||||||
wire [15:0] delayed_ref_i, delayed_ref_q;
|
wire [15:0] delayed_ref_i, delayed_ref_q;
|
||||||
wire mem_ready_delayed;
|
wire mem_ready_delayed;
|
||||||
|
|
||||||
@@ -308,11 +309,10 @@ latency_buffer #(
|
|||||||
.valid_out(mem_ready_delayed)
|
.valid_out(mem_ready_delayed)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assign delayed reference signals
|
// Assign delayed reference signals (single pair — chirp_memory_loader_param
|
||||||
assign long_chirp_real = delayed_ref_i;
|
// selects long/short reference upstream via use_long_chirp)
|
||||||
assign long_chirp_imag = delayed_ref_q;
|
assign ref_chirp_real = delayed_ref_i;
|
||||||
assign short_chirp_real = delayed_ref_i;
|
assign ref_chirp_imag = delayed_ref_q;
|
||||||
assign short_chirp_imag = delayed_ref_q;
|
|
||||||
|
|
||||||
// 5. Dual Chirp Matched Filter
|
// 5. Dual Chirp Matched Filter
|
||||||
|
|
||||||
@@ -336,10 +336,8 @@ matched_filter_multi_segment mf_dual (
|
|||||||
.mc_new_chirp(mc_new_chirp),
|
.mc_new_chirp(mc_new_chirp),
|
||||||
.mc_new_elevation(mc_new_elevation),
|
.mc_new_elevation(mc_new_elevation),
|
||||||
.mc_new_azimuth(mc_new_azimuth),
|
.mc_new_azimuth(mc_new_azimuth),
|
||||||
.long_chirp_real(delayed_ref_i), // From latency buffer
|
.ref_chirp_real(delayed_ref_i), // From latency buffer (long or short ref)
|
||||||
.long_chirp_imag(delayed_ref_q),
|
.ref_chirp_imag(delayed_ref_q),
|
||||||
.short_chirp_real(delayed_ref_i), // Same for short chirp
|
|
||||||
.short_chirp_imag(delayed_ref_q),
|
|
||||||
.segment_request(segment_request),
|
.segment_request(segment_request),
|
||||||
.mem_request(mem_request),
|
.mem_request(mem_request),
|
||||||
.sample_addr_out(sample_addr_from_chain),
|
.sample_addr_out(sample_addr_from_chain),
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"""
|
"""
|
||||||
golden_reference.py — AERIS-10 FPGA bit-accurate golden reference model
|
golden_reference.py — AERIS-10 FPGA bit-accurate golden reference model
|
||||||
|
|
||||||
Uses ADI CN0566 Phaser radar data (10.525 GHz X-band FMCW) to validate
|
Uses ADI CN0566 Phaser radar data (10.525 GHz, used as test stimulus only) to
|
||||||
the FPGA signal processing pipeline stage by stage:
|
validate the FPGA signal processing pipeline stage by stage:
|
||||||
|
|
||||||
ADC → DDC (NCO+mixer+CIC+FIR) → Range FFT → Doppler FFT → Detection
|
ADC → DDC (NCO+mixer+CIC+FIR) → Range FFT → Doppler FFT → Detection
|
||||||
|
|
||||||
@@ -90,7 +90,8 @@ HAMMING_Q15 = [
|
|||||||
0x3088, 0x1B6D, 0x0E5C, 0x0A3D,
|
0x3088, 0x1B6D, 0x0E5C, 0x0A3D,
|
||||||
]
|
]
|
||||||
|
|
||||||
# ADI dataset parameters
|
# ADI dataset parameters — used ONLY for loading/requantizing ADI Phaser test data.
|
||||||
|
# These are NOT PLFM hardware parameters. See AERIS-10 constants below.
|
||||||
ADI_SAMPLE_RATE = 4e6 # 4 MSPS
|
ADI_SAMPLE_RATE = 4e6 # 4 MSPS
|
||||||
ADI_IF_FREQ = 100e3 # 100 kHz IF
|
ADI_IF_FREQ = 100e3 # 100 kHz IF
|
||||||
ADI_RF_FREQ = 9.9e9 # 9.9 GHz
|
ADI_RF_FREQ = 9.9e9 # 9.9 GHz
|
||||||
@@ -99,9 +100,17 @@ ADI_RAMP_TIME = 300e-6 # 300 us
|
|||||||
ADI_NUM_CHIRPS = 256
|
ADI_NUM_CHIRPS = 256
|
||||||
ADI_SAMPLES_PER_CHIRP = 1079
|
ADI_SAMPLES_PER_CHIRP = 1079
|
||||||
|
|
||||||
# AERIS-10 parameters
|
# AERIS-10 hardware parameters (from ADF4382/AD9523/main.cpp configuration)
|
||||||
AERIS_FS = 400e6 # 400 MHz ADC clock
|
AERIS_FS = 400e6 # 400 MHz ADC clock (AD9523 OUT4)
|
||||||
AERIS_IF = 120e6 # 120 MHz IF
|
AERIS_IF = 120e6 # 120 MHz IF (TX 10.5 GHz - RX 10.38 GHz)
|
||||||
|
AERIS_FS_PROCESSING = 100e6 # Post-DDC rate (400 MSPS / 4x CIC)
|
||||||
|
AERIS_CARRIER_HZ = 10.5e9 # TX LO (ADF4382, verified)
|
||||||
|
AERIS_RX_LO_HZ = 10.38e9 # RX LO (ADF4382)
|
||||||
|
AERIS_CHIRP_BW = 20e6 # Chirp bandwidth (target: 30 MHz Phase 1)
|
||||||
|
AERIS_LONG_CHIRP_S = 30e-6 # Long chirp duration
|
||||||
|
AERIS_PRI_S = 167e-6 # Pulse repetition interval
|
||||||
|
AERIS_DECIMATION = 16 # Range bin decimation (1024 → 64)
|
||||||
|
AERIS_RANGE_PER_BIN = 24.0 # Meters per decimated bin
|
||||||
|
|
||||||
|
|
||||||
# ===========================================================================
|
# ===========================================================================
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -421,13 +421,13 @@ def test_latency_buffer():
|
|||||||
#
|
#
|
||||||
# For synthesis: the latency_buffer feeds ref data to the chain via
|
# For synthesis: the latency_buffer feeds ref data to the chain via
|
||||||
# chirp_memory_loader_param → latency_buffer → chain.
|
# chirp_memory_loader_param → latency_buffer → chain.
|
||||||
# But wait — looking at radar_receiver_final.v:
|
# Looking at radar_receiver_final.v:
|
||||||
# - mem_request drives valid_in on the latency buffer
|
# - mem_request drives valid_in on the latency buffer
|
||||||
# - The buffer delays {ref_i, ref_q} by LATENCY valid_in cycles
|
# - The buffer delays {ref_i, ref_q} by LATENCY valid_in cycles
|
||||||
# - The delayed output feeds long_chirp_real/imag → chain
|
# - The delayed output feeds ref_chirp_real/imag → chain
|
||||||
#
|
#
|
||||||
# The purpose: the chain in the SYNTHESIS branch reads reference data
|
# The purpose: the chain in the SYNTHESIS branch reads reference data
|
||||||
# via the long_chirp_real/imag ports DURING ST_FWD_FFT (while collecting
|
# via the ref_chirp_real/imag ports DURING ST_FWD_FFT (while collecting
|
||||||
# input samples). The reference data needs to arrive LATENCY cycles
|
# input samples). The reference data needs to arrive LATENCY cycles
|
||||||
# after the first mem_request, where LATENCY accounts for:
|
# after the first mem_request, where LATENCY accounts for:
|
||||||
# - The fft_engine pipeline latency from input to output
|
# - The fft_engine pipeline latency from input to output
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -18,10 +18,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
reg [15:0] adc_data_q;
|
reg [15:0] adc_data_q;
|
||||||
reg adc_valid;
|
reg adc_valid;
|
||||||
reg [5:0] chirp_counter;
|
reg [5:0] chirp_counter;
|
||||||
reg [15:0] long_chirp_real;
|
reg [15:0] ref_chirp_real;
|
||||||
reg [15:0] long_chirp_imag;
|
reg [15:0] ref_chirp_imag;
|
||||||
reg [15:0] short_chirp_real;
|
|
||||||
reg [15:0] short_chirp_imag;
|
|
||||||
wire signed [15:0] range_profile_i;
|
wire signed [15:0] range_profile_i;
|
||||||
wire signed [15:0] range_profile_q;
|
wire signed [15:0] range_profile_q;
|
||||||
wire range_profile_valid;
|
wire range_profile_valid;
|
||||||
@@ -83,10 +81,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
.adc_data_q (adc_data_q),
|
.adc_data_q (adc_data_q),
|
||||||
.adc_valid (adc_valid),
|
.adc_valid (adc_valid),
|
||||||
.chirp_counter (chirp_counter),
|
.chirp_counter (chirp_counter),
|
||||||
.long_chirp_real (long_chirp_real),
|
.ref_chirp_real (ref_chirp_real),
|
||||||
.long_chirp_imag (long_chirp_imag),
|
.ref_chirp_imag (ref_chirp_imag),
|
||||||
.short_chirp_real (short_chirp_real),
|
|
||||||
.short_chirp_imag (short_chirp_imag),
|
|
||||||
.range_profile_i (range_profile_i),
|
.range_profile_i (range_profile_i),
|
||||||
.range_profile_q (range_profile_q),
|
.range_profile_q (range_profile_q),
|
||||||
.range_profile_valid (range_profile_valid),
|
.range_profile_valid (range_profile_valid),
|
||||||
@@ -133,10 +129,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
adc_data_i = 16'd0;
|
adc_data_i = 16'd0;
|
||||||
adc_data_q = 16'd0;
|
adc_data_q = 16'd0;
|
||||||
chirp_counter = 6'd0;
|
chirp_counter = 6'd0;
|
||||||
long_chirp_real = 16'd0;
|
ref_chirp_real = 16'd0;
|
||||||
long_chirp_imag = 16'd0;
|
ref_chirp_imag = 16'd0;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
cap_enable = 0;
|
cap_enable = 0;
|
||||||
cap_count = 0;
|
cap_count = 0;
|
||||||
cap_max_abs = 0;
|
cap_max_abs = 0;
|
||||||
@@ -168,10 +162,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
angle = 6.28318530718 * tone_bin * k / (1.0 * FFT_SIZE);
|
angle = 6.28318530718 * tone_bin * k / (1.0 * FFT_SIZE);
|
||||||
adc_data_i = $rtoi(8000.0 * $cos(angle));
|
adc_data_i = $rtoi(8000.0 * $cos(angle));
|
||||||
adc_data_q = $rtoi(8000.0 * $sin(angle));
|
adc_data_q = $rtoi(8000.0 * $sin(angle));
|
||||||
long_chirp_real = $rtoi(8000.0 * $cos(angle));
|
ref_chirp_real = $rtoi(8000.0 * $cos(angle));
|
||||||
long_chirp_imag = $rtoi(8000.0 * $sin(angle));
|
ref_chirp_imag = $rtoi(8000.0 * $sin(angle));
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
#1;
|
#1;
|
||||||
@@ -187,10 +179,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (k = 0; k < FFT_SIZE; k = k + 1) begin
|
for (k = 0; k < FFT_SIZE; k = k + 1) begin
|
||||||
adc_data_i = 16'sh1000;
|
adc_data_i = 16'sh1000;
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh1000;
|
ref_chirp_real = 16'sh1000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
#1;
|
#1;
|
||||||
@@ -233,10 +223,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (k = 0; k < FFT_SIZE; k = k + 1) begin
|
for (k = 0; k < FFT_SIZE; k = k + 1) begin
|
||||||
adc_data_i = gold_sig_i[k];
|
adc_data_i = gold_sig_i[k];
|
||||||
adc_data_q = gold_sig_q[k];
|
adc_data_q = gold_sig_q[k];
|
||||||
long_chirp_real = gold_ref_i[k];
|
ref_chirp_real = gold_ref_i[k];
|
||||||
long_chirp_imag = gold_ref_q[k];
|
ref_chirp_imag = gold_ref_q[k];
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
#1;
|
#1;
|
||||||
@@ -374,10 +362,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'd0;
|
adc_data_i = 16'd0;
|
||||||
adc_data_q = 16'd0;
|
adc_data_q = 16'd0;
|
||||||
long_chirp_real = 16'd0;
|
ref_chirp_real = 16'd0;
|
||||||
long_chirp_imag = 16'd0;
|
ref_chirp_imag = 16'd0;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -449,10 +435,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = $rtoi(8000.0 * $cos(6.28318530718 * 5 * i / 1024.0));
|
adc_data_i = $rtoi(8000.0 * $cos(6.28318530718 * 5 * i / 1024.0));
|
||||||
adc_data_q = $rtoi(8000.0 * $sin(6.28318530718 * 5 * i / 1024.0));
|
adc_data_q = $rtoi(8000.0 * $sin(6.28318530718 * 5 * i / 1024.0));
|
||||||
long_chirp_real = $rtoi(8000.0 * $cos(6.28318530718 * 10 * i / 1024.0));
|
ref_chirp_real = $rtoi(8000.0 * $cos(6.28318530718 * 10 * i / 1024.0));
|
||||||
long_chirp_imag = $rtoi(8000.0 * $sin(6.28318530718 * 10 * i / 1024.0));
|
ref_chirp_imag = $rtoi(8000.0 * $sin(6.28318530718 * 10 * i / 1024.0));
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -568,10 +552,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'sh7FFF;
|
adc_data_i = 16'sh7FFF;
|
||||||
adc_data_q = 16'sh7FFF;
|
adc_data_q = 16'sh7FFF;
|
||||||
long_chirp_real = 16'sh7FFF;
|
ref_chirp_real = 16'sh7FFF;
|
||||||
long_chirp_imag = 16'sh7FFF;
|
ref_chirp_imag = 16'sh7FFF;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -589,10 +571,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'sh8000;
|
adc_data_i = 16'sh8000;
|
||||||
adc_data_q = 16'sh8000;
|
adc_data_q = 16'sh8000;
|
||||||
long_chirp_real = 16'sh8000;
|
ref_chirp_real = 16'sh8000;
|
||||||
long_chirp_imag = 16'sh8000;
|
ref_chirp_imag = 16'sh8000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -611,16 +591,14 @@ module tb_matched_filter_processing_chain;
|
|||||||
if (i % 2 == 0) begin
|
if (i % 2 == 0) begin
|
||||||
adc_data_i = 16'sh7FFF;
|
adc_data_i = 16'sh7FFF;
|
||||||
adc_data_q = 16'sh7FFF;
|
adc_data_q = 16'sh7FFF;
|
||||||
long_chirp_real = 16'sh7FFF;
|
ref_chirp_real = 16'sh7FFF;
|
||||||
long_chirp_imag = 16'sh7FFF;
|
ref_chirp_imag = 16'sh7FFF;
|
||||||
end else begin
|
end else begin
|
||||||
adc_data_i = 16'sh8000;
|
adc_data_i = 16'sh8000;
|
||||||
adc_data_q = 16'sh8000;
|
adc_data_q = 16'sh8000;
|
||||||
long_chirp_real = 16'sh8000;
|
ref_chirp_real = 16'sh8000;
|
||||||
long_chirp_imag = 16'sh8000;
|
ref_chirp_imag = 16'sh8000;
|
||||||
end
|
end
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -641,10 +619,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (i = 0; i < 512; i = i + 1) begin
|
for (i = 0; i < 512; i = i + 1) begin
|
||||||
adc_data_i = 16'sh1000;
|
adc_data_i = 16'sh1000;
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh1000;
|
ref_chirp_real = 16'sh1000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -683,10 +659,8 @@ module tb_matched_filter_processing_chain;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'sh1000;
|
adc_data_i = 16'sh1000;
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh1000;
|
ref_chirp_real = 16'sh1000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,8 @@ module tb_mf_chain_synth;
|
|||||||
reg [15:0] adc_data_q;
|
reg [15:0] adc_data_q;
|
||||||
reg adc_valid;
|
reg adc_valid;
|
||||||
reg [5:0] chirp_counter;
|
reg [5:0] chirp_counter;
|
||||||
reg [15:0] long_chirp_real;
|
reg [15:0] ref_chirp_real;
|
||||||
reg [15:0] long_chirp_imag;
|
reg [15:0] ref_chirp_imag;
|
||||||
reg [15:0] short_chirp_real;
|
|
||||||
reg [15:0] short_chirp_imag;
|
|
||||||
wire signed [15:0] range_profile_i;
|
wire signed [15:0] range_profile_i;
|
||||||
wire signed [15:0] range_profile_q;
|
wire signed [15:0] range_profile_q;
|
||||||
wire range_profile_valid;
|
wire range_profile_valid;
|
||||||
@@ -78,10 +76,8 @@ module tb_mf_chain_synth;
|
|||||||
.adc_data_q (adc_data_q),
|
.adc_data_q (adc_data_q),
|
||||||
.adc_valid (adc_valid),
|
.adc_valid (adc_valid),
|
||||||
.chirp_counter (chirp_counter),
|
.chirp_counter (chirp_counter),
|
||||||
.long_chirp_real (long_chirp_real),
|
.ref_chirp_real (ref_chirp_real),
|
||||||
.long_chirp_imag (long_chirp_imag),
|
.ref_chirp_imag (ref_chirp_imag),
|
||||||
.short_chirp_real (short_chirp_real),
|
|
||||||
.short_chirp_imag (short_chirp_imag),
|
|
||||||
.range_profile_i (range_profile_i),
|
.range_profile_i (range_profile_i),
|
||||||
.range_profile_q (range_profile_q),
|
.range_profile_q (range_profile_q),
|
||||||
.range_profile_valid (range_profile_valid),
|
.range_profile_valid (range_profile_valid),
|
||||||
@@ -130,10 +126,8 @@ module tb_mf_chain_synth;
|
|||||||
adc_data_i = 16'd0;
|
adc_data_i = 16'd0;
|
||||||
adc_data_q = 16'd0;
|
adc_data_q = 16'd0;
|
||||||
chirp_counter = 6'd0;
|
chirp_counter = 6'd0;
|
||||||
long_chirp_real = 16'd0;
|
ref_chirp_real = 16'd0;
|
||||||
long_chirp_imag = 16'd0;
|
ref_chirp_imag = 16'd0;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
cap_enable = 0;
|
cap_enable = 0;
|
||||||
cap_count = 0;
|
cap_count = 0;
|
||||||
cap_max_abs = 0;
|
cap_max_abs = 0;
|
||||||
@@ -177,10 +171,8 @@ module tb_mf_chain_synth;
|
|||||||
for (k = 0; k < FFT_SIZE; k = k + 1) begin
|
for (k = 0; k < FFT_SIZE; k = k + 1) begin
|
||||||
adc_data_i = 16'sh1000; // +4096
|
adc_data_i = 16'sh1000; // +4096
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh1000;
|
ref_chirp_real = 16'sh1000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
#1;
|
#1;
|
||||||
@@ -199,10 +191,8 @@ module tb_mf_chain_synth;
|
|||||||
angle = 6.28318530718 * tone_bin * k / (1.0 * FFT_SIZE);
|
angle = 6.28318530718 * tone_bin * k / (1.0 * FFT_SIZE);
|
||||||
adc_data_i = $rtoi(8000.0 * $cos(angle));
|
adc_data_i = $rtoi(8000.0 * $cos(angle));
|
||||||
adc_data_q = $rtoi(8000.0 * $sin(angle));
|
adc_data_q = $rtoi(8000.0 * $sin(angle));
|
||||||
long_chirp_real = $rtoi(8000.0 * $cos(angle));
|
ref_chirp_real = $rtoi(8000.0 * $cos(angle));
|
||||||
long_chirp_imag = $rtoi(8000.0 * $sin(angle));
|
ref_chirp_imag = $rtoi(8000.0 * $sin(angle));
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
#1;
|
#1;
|
||||||
@@ -219,16 +209,14 @@ module tb_mf_chain_synth;
|
|||||||
if (k == 0) begin
|
if (k == 0) begin
|
||||||
adc_data_i = 16'sh4000; // 0.5 in Q15
|
adc_data_i = 16'sh4000; // 0.5 in Q15
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh4000;
|
ref_chirp_real = 16'sh4000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
end else begin
|
end else begin
|
||||||
adc_data_i = 16'sh0000;
|
adc_data_i = 16'sh0000;
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh0000;
|
ref_chirp_real = 16'sh0000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
end
|
end
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
#1;
|
#1;
|
||||||
@@ -309,10 +297,8 @@ module tb_mf_chain_synth;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'd0;
|
adc_data_i = 16'd0;
|
||||||
adc_data_q = 16'd0;
|
adc_data_q = 16'd0;
|
||||||
long_chirp_real = 16'd0;
|
ref_chirp_real = 16'd0;
|
||||||
long_chirp_imag = 16'd0;
|
ref_chirp_imag = 16'd0;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -379,10 +365,8 @@ module tb_mf_chain_synth;
|
|||||||
for (i = 0; i < 512; i = i + 1) begin
|
for (i = 0; i < 512; i = i + 1) begin
|
||||||
adc_data_i = 16'sh1000;
|
adc_data_i = 16'sh1000;
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh1000;
|
ref_chirp_real = 16'sh1000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -439,10 +423,8 @@ module tb_mf_chain_synth;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = $rtoi(8000.0 * $cos(6.28318530718 * 5 * i / 1024.0));
|
adc_data_i = $rtoi(8000.0 * $cos(6.28318530718 * 5 * i / 1024.0));
|
||||||
adc_data_q = $rtoi(8000.0 * $sin(6.28318530718 * 5 * i / 1024.0));
|
adc_data_q = $rtoi(8000.0 * $sin(6.28318530718 * 5 * i / 1024.0));
|
||||||
long_chirp_real = $rtoi(8000.0 * $cos(6.28318530718 * 10 * i / 1024.0));
|
ref_chirp_real = $rtoi(8000.0 * $cos(6.28318530718 * 10 * i / 1024.0));
|
||||||
long_chirp_imag = $rtoi(8000.0 * $sin(6.28318530718 * 10 * i / 1024.0));
|
ref_chirp_imag = $rtoi(8000.0 * $sin(6.28318530718 * 10 * i / 1024.0));
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -469,10 +451,8 @@ module tb_mf_chain_synth;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'sh7FFF;
|
adc_data_i = 16'sh7FFF;
|
||||||
adc_data_q = 16'sh7FFF;
|
adc_data_q = 16'sh7FFF;
|
||||||
long_chirp_real = 16'sh7FFF;
|
ref_chirp_real = 16'sh7FFF;
|
||||||
long_chirp_imag = 16'sh7FFF;
|
ref_chirp_imag = 16'sh7FFF;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
end
|
end
|
||||||
@@ -495,10 +475,8 @@ module tb_mf_chain_synth;
|
|||||||
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
for (i = 0; i < FFT_SIZE; i = i + 1) begin
|
||||||
adc_data_i = 16'sh1000;
|
adc_data_i = 16'sh1000;
|
||||||
adc_data_q = 16'sh0000;
|
adc_data_q = 16'sh0000;
|
||||||
long_chirp_real = 16'sh1000;
|
ref_chirp_real = 16'sh1000;
|
||||||
long_chirp_imag = 16'sh0000;
|
ref_chirp_imag = 16'sh0000;
|
||||||
short_chirp_real = 16'd0;
|
|
||||||
short_chirp_imag = 16'd0;
|
|
||||||
adc_valid = 1'b1;
|
adc_valid = 1'b1;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
|
|
||||||
|
|||||||
@@ -88,10 +88,8 @@ reg [15:0] adc_data_i;
|
|||||||
reg [15:0] adc_data_q;
|
reg [15:0] adc_data_q;
|
||||||
reg adc_valid;
|
reg adc_valid;
|
||||||
reg [5:0] chirp_counter;
|
reg [5:0] chirp_counter;
|
||||||
reg [15:0] long_chirp_real;
|
reg [15:0] ref_chirp_real;
|
||||||
reg [15:0] long_chirp_imag;
|
reg [15:0] ref_chirp_imag;
|
||||||
reg [15:0] short_chirp_real;
|
|
||||||
reg [15:0] short_chirp_imag;
|
|
||||||
|
|
||||||
wire signed [15:0] range_profile_i;
|
wire signed [15:0] range_profile_i;
|
||||||
wire signed [15:0] range_profile_q;
|
wire signed [15:0] range_profile_q;
|
||||||
@@ -108,10 +106,8 @@ matched_filter_processing_chain dut (
|
|||||||
.adc_data_q(adc_data_q),
|
.adc_data_q(adc_data_q),
|
||||||
.adc_valid(adc_valid),
|
.adc_valid(adc_valid),
|
||||||
.chirp_counter(chirp_counter),
|
.chirp_counter(chirp_counter),
|
||||||
.long_chirp_real(long_chirp_real),
|
.ref_chirp_real(ref_chirp_real),
|
||||||
.long_chirp_imag(long_chirp_imag),
|
.ref_chirp_imag(ref_chirp_imag),
|
||||||
.short_chirp_real(short_chirp_real),
|
|
||||||
.short_chirp_imag(short_chirp_imag),
|
|
||||||
.range_profile_i(range_profile_i),
|
.range_profile_i(range_profile_i),
|
||||||
.range_profile_q(range_profile_q),
|
.range_profile_q(range_profile_q),
|
||||||
.range_profile_valid(range_profile_valid),
|
.range_profile_valid(range_profile_valid),
|
||||||
@@ -157,10 +153,8 @@ task apply_reset;
|
|||||||
adc_data_q <= 16'd0;
|
adc_data_q <= 16'd0;
|
||||||
adc_valid <= 1'b0;
|
adc_valid <= 1'b0;
|
||||||
chirp_counter <= 6'd0;
|
chirp_counter <= 6'd0;
|
||||||
long_chirp_real <= 16'd0;
|
ref_chirp_real <= 16'd0;
|
||||||
long_chirp_imag <= 16'd0;
|
ref_chirp_imag <= 16'd0;
|
||||||
short_chirp_real <= 16'd0;
|
|
||||||
short_chirp_imag <= 16'd0;
|
|
||||||
repeat(4) @(posedge clk);
|
repeat(4) @(posedge clk);
|
||||||
reset_n <= 1'b1;
|
reset_n <= 1'b1;
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
@@ -201,18 +195,16 @@ initial begin
|
|||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
adc_data_i <= sig_mem_i[i];
|
adc_data_i <= sig_mem_i[i];
|
||||||
adc_data_q <= sig_mem_q[i];
|
adc_data_q <= sig_mem_q[i];
|
||||||
long_chirp_real <= ref_mem_i[i];
|
ref_chirp_real <= ref_mem_i[i];
|
||||||
long_chirp_imag <= ref_mem_q[i];
|
ref_chirp_imag <= ref_mem_q[i];
|
||||||
short_chirp_real <= 16'd0;
|
|
||||||
short_chirp_imag <= 16'd0;
|
|
||||||
adc_valid <= 1'b1;
|
adc_valid <= 1'b1;
|
||||||
end
|
end
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
adc_valid <= 1'b0;
|
adc_valid <= 1'b0;
|
||||||
adc_data_i <= 16'd0;
|
adc_data_i <= 16'd0;
|
||||||
adc_data_q <= 16'd0;
|
adc_data_q <= 16'd0;
|
||||||
long_chirp_real <= 16'd0;
|
ref_chirp_real <= 16'd0;
|
||||||
long_chirp_imag <= 16'd0;
|
ref_chirp_imag <= 16'd0;
|
||||||
|
|
||||||
$display("All samples fed. Waiting for processing...");
|
$display("All samples fed. Waiting for processing...");
|
||||||
|
|
||||||
|
|||||||
@@ -56,10 +56,8 @@ reg [5:0] chirp_counter;
|
|||||||
reg mc_new_chirp;
|
reg mc_new_chirp;
|
||||||
reg mc_new_elevation;
|
reg mc_new_elevation;
|
||||||
reg mc_new_azimuth;
|
reg mc_new_azimuth;
|
||||||
reg [15:0] long_chirp_real;
|
reg [15:0] ref_chirp_real;
|
||||||
reg [15:0] long_chirp_imag;
|
reg [15:0] ref_chirp_imag;
|
||||||
reg [15:0] short_chirp_real;
|
|
||||||
reg [15:0] short_chirp_imag;
|
|
||||||
reg mem_ready;
|
reg mem_ready;
|
||||||
|
|
||||||
wire signed [15:0] pc_i_w;
|
wire signed [15:0] pc_i_w;
|
||||||
@@ -84,10 +82,8 @@ matched_filter_multi_segment dut (
|
|||||||
.mc_new_chirp(mc_new_chirp),
|
.mc_new_chirp(mc_new_chirp),
|
||||||
.mc_new_elevation(mc_new_elevation),
|
.mc_new_elevation(mc_new_elevation),
|
||||||
.mc_new_azimuth(mc_new_azimuth),
|
.mc_new_azimuth(mc_new_azimuth),
|
||||||
.long_chirp_real(long_chirp_real),
|
.ref_chirp_real(ref_chirp_real),
|
||||||
.long_chirp_imag(long_chirp_imag),
|
.ref_chirp_imag(ref_chirp_imag),
|
||||||
.short_chirp_real(short_chirp_real),
|
|
||||||
.short_chirp_imag(short_chirp_imag),
|
|
||||||
.segment_request(segment_request),
|
.segment_request(segment_request),
|
||||||
.sample_addr_out(sample_addr_out),
|
.sample_addr_out(sample_addr_out),
|
||||||
.mem_request(mem_request),
|
.mem_request(mem_request),
|
||||||
@@ -123,11 +119,11 @@ end
|
|||||||
always @(posedge clk) begin
|
always @(posedge clk) begin
|
||||||
if (mem_request) begin
|
if (mem_request) begin
|
||||||
if (use_long_chirp) begin
|
if (use_long_chirp) begin
|
||||||
long_chirp_real <= ref_mem_i[{segment_request, sample_addr_out}];
|
ref_chirp_real <= ref_mem_i[{segment_request, sample_addr_out}];
|
||||||
long_chirp_imag <= ref_mem_q[{segment_request, sample_addr_out}];
|
ref_chirp_imag <= ref_mem_q[{segment_request, sample_addr_out}];
|
||||||
end else begin
|
end else begin
|
||||||
short_chirp_real <= ref_mem_i[sample_addr_out];
|
ref_chirp_real <= ref_mem_i[sample_addr_out];
|
||||||
short_chirp_imag <= ref_mem_q[sample_addr_out];
|
ref_chirp_imag <= ref_mem_q[sample_addr_out];
|
||||||
end
|
end
|
||||||
mem_ready <= 1'b1;
|
mem_ready <= 1'b1;
|
||||||
end else begin
|
end else begin
|
||||||
@@ -176,10 +172,8 @@ task apply_reset;
|
|||||||
mc_new_chirp <= 1'b0;
|
mc_new_chirp <= 1'b0;
|
||||||
mc_new_elevation <= 1'b0;
|
mc_new_elevation <= 1'b0;
|
||||||
mc_new_azimuth <= 1'b0;
|
mc_new_azimuth <= 1'b0;
|
||||||
long_chirp_real <= 16'd0;
|
ref_chirp_real <= 16'd0;
|
||||||
long_chirp_imag <= 16'd0;
|
ref_chirp_imag <= 16'd0;
|
||||||
short_chirp_real <= 16'd0;
|
|
||||||
short_chirp_imag <= 16'd0;
|
|
||||||
mem_ready <= 1'b0;
|
mem_ready <= 1'b0;
|
||||||
repeat(10) @(posedge clk);
|
repeat(10) @(posedge clk);
|
||||||
reset_n <= 1'b1;
|
reset_n <= 1'b1;
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ class GPSData:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class RadarSettings:
|
class RadarSettings:
|
||||||
"""Radar system configuration"""
|
"""Radar system configuration"""
|
||||||
system_frequency: float = 10e9 # Hz
|
system_frequency: float = 10.5e9 # Hz (PLFM TX LO)
|
||||||
chirp_duration_1: float = 30e-6 # Long chirp duration (s)
|
chirp_duration_1: float = 30e-6 # Long chirp duration (s)
|
||||||
chirp_duration_2: float = 0.5e-6 # Short chirp duration (s)
|
chirp_duration_2: float = 0.5e-6 # Short chirp duration (s)
|
||||||
chirps_per_position: int = 32
|
chirps_per_position: int = 32
|
||||||
@@ -116,8 +116,8 @@ class RadarSettings:
|
|||||||
freq_max: float = 30e6 # Hz
|
freq_max: float = 30e6 # Hz
|
||||||
prf1: float = 1000 # PRF 1 (Hz)
|
prf1: float = 1000 # PRF 1 (Hz)
|
||||||
prf2: float = 2000 # PRF 2 (Hz)
|
prf2: float = 2000 # PRF 2 (Hz)
|
||||||
max_distance: float = 50000 # Max detection range (m)
|
max_distance: float = 1536 # Max detection range (m) -- 64 bins x 24 m
|
||||||
coverage_radius: float = 50000 # Map coverage radius (m)
|
coverage_radius: float = 1536 # Map coverage radius (m)
|
||||||
|
|
||||||
|
|
||||||
class TileServer(Enum):
|
class TileServer(Enum):
|
||||||
@@ -198,7 +198,7 @@ class RadarMapWidget(QWidget):
|
|||||||
pitch=0.0
|
pitch=0.0
|
||||||
)
|
)
|
||||||
self._targets: list[RadarTarget] = []
|
self._targets: list[RadarTarget] = []
|
||||||
self._coverage_radius = 50000 # meters
|
self._coverage_radius = 1536 # meters (64 bins x 24 m, 3 km mode)
|
||||||
self._tile_server = TileServer.OPENSTREETMAP
|
self._tile_server = TileServer.OPENSTREETMAP
|
||||||
self._show_coverage = True
|
self._show_coverage = True
|
||||||
self._show_trails = False
|
self._show_trails = False
|
||||||
@@ -1088,7 +1088,7 @@ class TargetSimulator(QObject):
|
|||||||
new_range = target.range - target.velocity * 0.5 # 0.5 second update
|
new_range = target.range - target.velocity * 0.5 # 0.5 second update
|
||||||
|
|
||||||
# Check if target is still in range
|
# Check if target is still in range
|
||||||
if new_range < 500 or new_range > 50000:
|
if new_range < 50 or new_range > 1536:
|
||||||
# Remove this target and add a new one
|
# Remove this target and add a new one
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class RadarTarget:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RadarSettings:
|
class RadarSettings:
|
||||||
system_frequency: float = 10e9
|
system_frequency: float = 10.5e9
|
||||||
chirp_duration_1: float = 30e-6 # Long chirp duration
|
chirp_duration_1: float = 30e-6 # Long chirp duration
|
||||||
chirp_duration_2: float = 0.5e-6 # Short chirp duration
|
chirp_duration_2: float = 0.5e-6 # Short chirp duration
|
||||||
chirps_per_position: int = 32
|
chirps_per_position: int = 32
|
||||||
@@ -89,8 +89,8 @@ class RadarSettings:
|
|||||||
freq_max: float = 30e6
|
freq_max: float = 30e6
|
||||||
prf1: float = 1000
|
prf1: float = 1000
|
||||||
prf2: float = 2000
|
prf2: float = 2000
|
||||||
max_distance: float = 50000
|
max_distance: float = 1536
|
||||||
map_size: float = 50000 # Map size in meters
|
map_size: float = 1536 # Map size in meters (64 bins x 24 m)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1196,8 +1196,8 @@ class RadarGUI:
|
|||||||
("Frequency Max (Hz):", "freq_max", 30e6),
|
("Frequency Max (Hz):", "freq_max", 30e6),
|
||||||
("PRF1 (Hz):", "prf1", 1000),
|
("PRF1 (Hz):", "prf1", 1000),
|
||||||
("PRF2 (Hz):", "prf2", 2000),
|
("PRF2 (Hz):", "prf2", 2000),
|
||||||
("Max Distance (m):", "max_distance", 50000),
|
("Max Distance (m):", "max_distance", 1536),
|
||||||
("Map Size (m):", "map_size", 50000),
|
("Map Size (m):", "map_size", 1536),
|
||||||
("Google Maps API Key:", "google_maps_api_key", "YOUR_GOOGLE_MAPS_API_KEY"),
|
("Google Maps API Key:", "google_maps_api_key", "YOUR_GOOGLE_MAPS_API_KEY"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class RadarTarget:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RadarSettings:
|
class RadarSettings:
|
||||||
system_frequency: float = 10e9
|
system_frequency: float = 10.5e9
|
||||||
chirp_duration_1: float = 30e-6 # Long chirp duration
|
chirp_duration_1: float = 30e-6 # Long chirp duration
|
||||||
chirp_duration_2: float = 0.5e-6 # Short chirp duration
|
chirp_duration_2: float = 0.5e-6 # Short chirp duration
|
||||||
chirps_per_position: int = 32
|
chirps_per_position: int = 32
|
||||||
@@ -85,8 +85,8 @@ class RadarSettings:
|
|||||||
freq_max: float = 30e6
|
freq_max: float = 30e6
|
||||||
prf1: float = 1000
|
prf1: float = 1000
|
||||||
prf2: float = 2000
|
prf2: float = 2000
|
||||||
max_distance: float = 50000
|
max_distance: float = 1536
|
||||||
map_size: float = 50000 # Map size in meters
|
map_size: float = 1536 # Map size in meters (64 bins x 24 m)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -1254,8 +1254,8 @@ class RadarGUI:
|
|||||||
("Frequency Max (Hz):", "freq_max", 30e6),
|
("Frequency Max (Hz):", "freq_max", 30e6),
|
||||||
("PRF1 (Hz):", "prf1", 1000),
|
("PRF1 (Hz):", "prf1", 1000),
|
||||||
("PRF2 (Hz):", "prf2", 2000),
|
("PRF2 (Hz):", "prf2", 2000),
|
||||||
("Max Distance (m):", "max_distance", 50000),
|
("Max Distance (m):", "max_distance", 1536),
|
||||||
("Map Size (m):", "map_size", 50000),
|
("Map Size (m):", "map_size", 1536),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.settings_vars = {}
|
self.settings_vars = {}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class RadarTarget:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class RadarSettings:
|
class RadarSettings:
|
||||||
system_frequency: float = 10e9
|
system_frequency: float = 10.5e9
|
||||||
chirp_duration_1: float = 30e-6 # Long chirp duration
|
chirp_duration_1: float = 30e-6 # Long chirp duration
|
||||||
chirp_duration_2: float = 0.5e-6 # Short chirp duration
|
chirp_duration_2: float = 0.5e-6 # Short chirp duration
|
||||||
chirps_per_position: int = 32
|
chirps_per_position: int = 32
|
||||||
@@ -72,8 +72,8 @@ class RadarSettings:
|
|||||||
freq_max: float = 30e6
|
freq_max: float = 30e6
|
||||||
prf1: float = 1000
|
prf1: float = 1000
|
||||||
prf2: float = 2000
|
prf2: float = 2000
|
||||||
max_distance: float = 50000
|
max_distance: float = 1536
|
||||||
map_size: float = 50000 # Map size in meters
|
map_size: float = 1536 # Map size in meters (64 bins x 24 m)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class GPSData:
|
class GPSData:
|
||||||
@@ -1653,8 +1653,8 @@ class RadarGUI:
|
|||||||
('Frequency Max (Hz):', 'freq_max', 30e6),
|
('Frequency Max (Hz):', 'freq_max', 30e6),
|
||||||
('PRF1 (Hz):', 'prf1', 1000),
|
('PRF1 (Hz):', 'prf1', 1000),
|
||||||
('PRF2 (Hz):', 'prf2', 2000),
|
('PRF2 (Hz):', 'prf2', 2000),
|
||||||
('Max Distance (m):', 'max_distance', 50000),
|
('Max Distance (m):', 'max_distance', 1536),
|
||||||
('Map Size (m):', 'map_size', 50000),
|
('Map Size (m):', 'map_size', 1536),
|
||||||
('Google Maps API Key:', 'google_maps_api_key', 'YOUR_GOOGLE_MAPS_API_KEY')
|
('Google Maps API Key:', 'google_maps_api_key', 'YOUR_GOOGLE_MAPS_API_KEY')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -98,9 +98,10 @@ class DemoTarget:
|
|||||||
|
|
||||||
__slots__ = ("azimuth", "classification", "id", "range_m", "snr", "velocity")
|
__slots__ = ("azimuth", "classification", "id", "range_m", "snr", "velocity")
|
||||||
|
|
||||||
# Physical range grid: 64 bins x ~4.8 m/bin = ~307 m max
|
# Physical range grid: matched-filter receiver, 100 MSPS post-DDC, 16:1 decimation
|
||||||
_RANGE_PER_BIN: float = (3e8 / (2 * 500e6)) * 16 # ~4.8 m
|
# range_per_bin = c / (2 * 100e6) * 16 = 24.0 m
|
||||||
_MAX_RANGE: float = _RANGE_PER_BIN * NUM_RANGE_BINS # ~307 m
|
_RANGE_PER_BIN: float = (3e8 / (2 * 100e6)) * 16 # 24.0 m
|
||||||
|
_MAX_RANGE: float = _RANGE_PER_BIN * NUM_RANGE_BINS # 1536 m
|
||||||
|
|
||||||
def __init__(self, tid: int):
|
def __init__(self, tid: int):
|
||||||
self.id = tid
|
self.id = tid
|
||||||
@@ -187,10 +188,10 @@ class DemoSimulator:
|
|||||||
mag = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=np.float64)
|
mag = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=np.float64)
|
||||||
det = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=np.uint8)
|
det = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=np.uint8)
|
||||||
|
|
||||||
# Range/Doppler scaling (approximate)
|
# Range/Doppler scaling -- matched-filter receiver, 100 MSPS, 16:1 decimation
|
||||||
range_per_bin = (3e8 / (2 * 500e6)) * 16 # ~4.8 m/bin
|
range_per_bin = (3e8 / (2 * 100e6)) * 16 # 24.0 m/bin
|
||||||
max_range = range_per_bin * NUM_RANGE_BINS
|
max_range = range_per_bin * NUM_RANGE_BINS
|
||||||
vel_per_bin = 1.484 # m/s per Doppler bin (from WaveformConfig)
|
vel_per_bin = 2.67 # m/s per Doppler bin (lam/(2*32*167us))
|
||||||
|
|
||||||
for t in targets:
|
for t in targets:
|
||||||
if t.range_m > max_range or t.range_m < 0:
|
if t.range_m > max_range or t.range_m < 0:
|
||||||
@@ -385,7 +386,9 @@ class RadarDashboard:
|
|||||||
UPDATE_INTERVAL_MS = 100 # 10 Hz display refresh
|
UPDATE_INTERVAL_MS = 100 # 10 Hz display refresh
|
||||||
|
|
||||||
# Radar parameters used for range-axis scaling.
|
# Radar parameters used for range-axis scaling.
|
||||||
BANDWIDTH = 500e6 # Hz — chirp bandwidth
|
# Matched-filter receiver: range_per_bin = c / (2 * fs_processing) * decimation
|
||||||
|
# = 3e8 / (2 * 100e6) * 16 = 24.0 m/bin
|
||||||
|
BANDWIDTH = 20e6 # Hz — chirp bandwidth (for display/info only)
|
||||||
C = 3e8 # m/s — speed of light
|
C = 3e8 # m/s — speed of light
|
||||||
|
|
||||||
def __init__(self, root: tk.Tk, connection: FT2232HConnection,
|
def __init__(self, root: tk.Tk, connection: FT2232HConnection,
|
||||||
@@ -514,10 +517,9 @@ class RadarDashboard:
|
|||||||
self._build_log_tab(tab_log)
|
self._build_log_tab(tab_log)
|
||||||
|
|
||||||
def _build_display_tab(self, parent):
|
def _build_display_tab(self, parent):
|
||||||
# Compute physical axis limits
|
# Compute physical axis limits -- matched-filter receiver
|
||||||
range_res = self.C / (2.0 * self.BANDWIDTH) # ~0.3 m per FFT bin
|
# Range per bin: c / (2 * fs_processing) * decimation_factor = 24.0 m
|
||||||
# After decimation 1024→64, each range bin = 16 FFT bins
|
range_per_bin = self.C / (2.0 * 100e6) * 16 # 24.0 m
|
||||||
range_per_bin = range_res * 16
|
|
||||||
max_range = range_per_bin * NUM_RANGE_BINS
|
max_range = range_per_bin * NUM_RANGE_BINS
|
||||||
|
|
||||||
doppler_bin_lo = 0
|
doppler_bin_lo = 0
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class RadarSettings:
|
|||||||
range_bins: int = 1024
|
range_bins: int = 1024
|
||||||
doppler_bins: int = 32
|
doppler_bins: int = 32
|
||||||
prf: float = 1000
|
prf: float = 1000
|
||||||
max_range: float = 5000
|
max_range: float = 1536
|
||||||
max_velocity: float = 100
|
max_velocity: float = 100
|
||||||
cfar_threshold: float = 13.0
|
cfar_threshold: float = 13.0
|
||||||
|
|
||||||
@@ -577,7 +577,7 @@ class RadarDemoGUI:
|
|||||||
('Range Bins:', 'range_bins', 1024, 256, 2048),
|
('Range Bins:', 'range_bins', 1024, 256, 2048),
|
||||||
('Doppler Bins:', 'doppler_bins', 32, 8, 128),
|
('Doppler Bins:', 'doppler_bins', 32, 8, 128),
|
||||||
('PRF (Hz):', 'prf', 1000, 100, 10000),
|
('PRF (Hz):', 'prf', 1000, 100, 10000),
|
||||||
('Max Range (m):', 'max_range', 5000, 100, 50000),
|
('Max Range (m):', 'max_range', 1536, 100, 25000),
|
||||||
('Max Velocity (m/s):', 'max_vel', 100, 10, 500),
|
('Max Velocity (m/s):', 'max_vel', 100, 10, 500),
|
||||||
('CFAR Threshold (dB):', 'cfar', 13.0, 5.0, 30.0)
|
('CFAR Threshold (dB):', 'cfar', 13.0, 5.0, 30.0)
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,338 +0,0 @@
|
|||||||
# ruff: noqa: T201
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
One-off AGC saturation analysis for ADI CN0566 raw IQ captures.
|
|
||||||
|
|
||||||
Bit-accurate simulation of rx_gain_control.v AGC inner loop applied
|
|
||||||
to real captured IQ data. Three scenarios per dataset:
|
|
||||||
|
|
||||||
Row 1 — AGC OFF: Fixed gain_shift=0 (pass-through). Shows raw clipping.
|
|
||||||
Row 2 — AGC ON: Auto-adjusts from gain_shift=0. Clipping clears.
|
|
||||||
Row 3 — AGC delayed: OFF for first half, ON at midpoint.
|
|
||||||
Shows the transition: clipping → AGC activates → clears.
|
|
||||||
|
|
||||||
Key RTL details modelled exactly:
|
|
||||||
- gain_shift[3]=direction (0=amplify/left, 1=attenuate/right), [2:0]=amount
|
|
||||||
- Internal agc_gain is signed -7..+7
|
|
||||||
- Peak is measured PRE-gain (raw input |sample|, upper 8 of 15 bits)
|
|
||||||
- Saturation is measured POST-gain (overflow from shift)
|
|
||||||
- Attack: gain -= agc_attack when any sample clips (immediate)
|
|
||||||
- Decay: gain += agc_decay when peak < target AND holdoff expired
|
|
||||||
- Hold: when peak >= target AND no saturation, hold gain, reset holdoff
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python adi_agc_analysis.py
|
|
||||||
python adi_agc_analysis.py --data /path/to/file.npy --label "my capture"
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from v7.agc_sim import (
|
|
||||||
encoding_to_signed,
|
|
||||||
apply_gain_shift,
|
|
||||||
quantize_iq,
|
|
||||||
AGCConfig,
|
|
||||||
AGCState,
|
|
||||||
process_agc_frame,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# FPGA AGC parameters (rx_gain_control.v reset defaults)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
AGC_TARGET = 200 # host_agc_target (8-bit, default 200)
|
|
||||||
ADC_RAIL = 4095 # 12-bit ADC max absolute value
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Per-frame AGC simulation using v7.agc_sim (bit-accurate to RTL)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def simulate_agc(frames: np.ndarray, agc_enabled: bool = True,
|
|
||||||
enable_at_frame: int = 0,
|
|
||||||
initial_gain_enc: int = 0x00) -> dict:
|
|
||||||
"""Simulate FPGA inner-loop AGC across all frames.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
frames : (N, chirps, samples) complex — raw ADC captures (12-bit range)
|
|
||||||
agc_enabled : if False, gain stays fixed
|
|
||||||
enable_at_frame : frame index where AGC activates
|
|
||||||
initial_gain_enc : gain_shift[3:0] encoding when AGC enables (default 0x00 = pass-through)
|
|
||||||
"""
|
|
||||||
n_frames = frames.shape[0]
|
|
||||||
|
|
||||||
# Output arrays
|
|
||||||
out_gain_enc = np.zeros(n_frames, dtype=int)
|
|
||||||
out_gain_signed = np.zeros(n_frames, dtype=int)
|
|
||||||
out_peak_mag = np.zeros(n_frames, dtype=int)
|
|
||||||
out_sat_count = np.zeros(n_frames, dtype=int)
|
|
||||||
out_sat_rate = np.zeros(n_frames, dtype=float)
|
|
||||||
out_rms_post = np.zeros(n_frames, dtype=float)
|
|
||||||
|
|
||||||
# AGC state — managed by process_agc_frame()
|
|
||||||
state = AGCState(
|
|
||||||
gain=encoding_to_signed(initial_gain_enc),
|
|
||||||
holdoff_counter=0,
|
|
||||||
was_enabled=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
for i in range(n_frames):
|
|
||||||
frame_i, frame_q = quantize_iq(frames[i])
|
|
||||||
|
|
||||||
agc_active = agc_enabled and (i >= enable_at_frame)
|
|
||||||
|
|
||||||
# Build per-frame config (enable toggles at enable_at_frame)
|
|
||||||
config = AGCConfig(enabled=agc_active)
|
|
||||||
|
|
||||||
result = process_agc_frame(frame_i, frame_q, config, state)
|
|
||||||
|
|
||||||
# RMS of shifted signal
|
|
||||||
rms = float(np.sqrt(np.mean(
|
|
||||||
result.shifted_i.astype(np.float64)**2
|
|
||||||
+ result.shifted_q.astype(np.float64)**2)))
|
|
||||||
|
|
||||||
total_samples = frame_i.size + frame_q.size
|
|
||||||
sat_rate = result.overflow_raw / total_samples if total_samples > 0 else 0.0
|
|
||||||
|
|
||||||
# Record outputs
|
|
||||||
out_gain_enc[i] = result.gain_enc
|
|
||||||
out_gain_signed[i] = result.gain_signed
|
|
||||||
out_peak_mag[i] = result.peak_mag_8bit
|
|
||||||
out_sat_count[i] = result.saturation_count
|
|
||||||
out_sat_rate[i] = sat_rate
|
|
||||||
out_rms_post[i] = rms
|
|
||||||
|
|
||||||
return {
|
|
||||||
"gain_enc": out_gain_enc,
|
|
||||||
"gain_signed": out_gain_signed,
|
|
||||||
"peak_mag": out_peak_mag,
|
|
||||||
"sat_count": out_sat_count,
|
|
||||||
"sat_rate": out_sat_rate,
|
|
||||||
"rms_post": out_rms_post,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Range-Doppler processing for heatmap display
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def process_frame_rd(frame: np.ndarray, gain_enc: int,
|
|
||||||
n_range: int = 64,
|
|
||||||
n_doppler: int = 32) -> np.ndarray:
|
|
||||||
"""Range-Doppler magnitude for one frame with gain applied."""
|
|
||||||
frame_i, frame_q = quantize_iq(frame)
|
|
||||||
si, sq, _ = apply_gain_shift(frame_i, frame_q, gain_enc)
|
|
||||||
|
|
||||||
iq = si.astype(np.float64) + 1j * sq.astype(np.float64)
|
|
||||||
n_chirps, _ = iq.shape
|
|
||||||
|
|
||||||
range_fft = np.fft.fft(iq, axis=1)[:, :n_range]
|
|
||||||
doppler_fft = np.fft.fftshift(np.fft.fft(range_fft, axis=0), axes=0)
|
|
||||||
center = n_chirps // 2
|
|
||||||
half_d = n_doppler // 2
|
|
||||||
doppler_fft = doppler_fft[center - half_d:center + half_d, :]
|
|
||||||
|
|
||||||
rd_mag = np.abs(doppler_fft.real) + np.abs(doppler_fft.imag)
|
|
||||||
return rd_mag.T # (n_range, n_doppler)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Plotting
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def plot_scenario(axes, data: np.ndarray, agc: dict, title: str,
|
|
||||||
enable_frame: int = 0):
|
|
||||||
"""Plot one AGC scenario across 5 axes."""
|
|
||||||
n = data.shape[0]
|
|
||||||
xs = np.arange(n)
|
|
||||||
|
|
||||||
# Range-Doppler heatmap
|
|
||||||
if enable_frame > 0 and enable_frame < n:
|
|
||||||
f_before = max(0, enable_frame - 1)
|
|
||||||
f_after = min(n - 1, n - 2)
|
|
||||||
rd_before = process_frame_rd(data[f_before], int(agc["gain_enc"][f_before]))
|
|
||||||
rd_after = process_frame_rd(data[f_after], int(agc["gain_enc"][f_after]))
|
|
||||||
combined = np.hstack([rd_before, rd_after])
|
|
||||||
im = axes[0].imshow(
|
|
||||||
20 * np.log10(combined + 1), aspect="auto", origin="lower",
|
|
||||||
cmap="inferno", interpolation="nearest")
|
|
||||||
axes[0].axvline(x=rd_before.shape[1] - 0.5, color="cyan",
|
|
||||||
linewidth=2, linestyle="--")
|
|
||||||
axes[0].set_title(f"{title}\nL: f{f_before} (pre) | R: f{f_after} (post)")
|
|
||||||
else:
|
|
||||||
worst = int(np.argmax(agc["sat_count"]))
|
|
||||||
best = int(np.argmin(agc["sat_count"]))
|
|
||||||
f_show = worst if agc["sat_count"][worst] > 0 else best
|
|
||||||
rd = process_frame_rd(data[f_show], int(agc["gain_enc"][f_show]))
|
|
||||||
im = axes[0].imshow(
|
|
||||||
20 * np.log10(rd + 1), aspect="auto", origin="lower",
|
|
||||||
cmap="inferno", interpolation="nearest")
|
|
||||||
axes[0].set_title(f"{title}\nFrame {f_show}")
|
|
||||||
|
|
||||||
axes[0].set_xlabel("Doppler bin")
|
|
||||||
axes[0].set_ylabel("Range bin")
|
|
||||||
plt.colorbar(im, ax=axes[0], label="dB", shrink=0.8)
|
|
||||||
|
|
||||||
# Signed gain history (the real AGC state)
|
|
||||||
axes[1].plot(xs, agc["gain_signed"], color="#00ff88", linewidth=1.5)
|
|
||||||
axes[1].axhline(y=0, color="gray", linestyle=":", alpha=0.5,
|
|
||||||
label="Pass-through")
|
|
||||||
if enable_frame > 0:
|
|
||||||
axes[1].axvline(x=enable_frame, color="yellow", linewidth=2,
|
|
||||||
linestyle="--", label="AGC ON")
|
|
||||||
axes[1].set_ylim(-8, 8)
|
|
||||||
axes[1].set_ylabel("Gain (signed)")
|
|
||||||
axes[1].set_title("AGC Internal Gain (-7=max atten, +7=max amp)")
|
|
||||||
axes[1].legend(fontsize=7, loc="upper right")
|
|
||||||
axes[1].grid(True, alpha=0.3)
|
|
||||||
|
|
||||||
# Peak magnitude (PRE-gain, 8-bit)
|
|
||||||
axes[2].plot(xs, agc["peak_mag"], color="#ffaa00", linewidth=1.0)
|
|
||||||
axes[2].axhline(y=AGC_TARGET, color="cyan", linestyle="--",
|
|
||||||
alpha=0.7, label=f"Target ({AGC_TARGET})")
|
|
||||||
axes[2].axhspan(240, 255, color="red", alpha=0.15, label="Clip zone")
|
|
||||||
if enable_frame > 0:
|
|
||||||
axes[2].axvline(x=enable_frame, color="yellow", linewidth=2,
|
|
||||||
linestyle="--", alpha=0.8)
|
|
||||||
axes[2].set_ylim(0, 260)
|
|
||||||
axes[2].set_ylabel("Peak (8-bit)")
|
|
||||||
axes[2].set_title("Peak Magnitude (pre-gain, raw input)")
|
|
||||||
axes[2].legend(fontsize=7, loc="upper right")
|
|
||||||
axes[2].grid(True, alpha=0.3)
|
|
||||||
|
|
||||||
# Saturation count (POST-gain overflow)
|
|
||||||
axes[3].fill_between(xs, agc["sat_count"], color="red", alpha=0.4)
|
|
||||||
axes[3].plot(xs, agc["sat_count"], color="red", linewidth=0.8)
|
|
||||||
if enable_frame > 0:
|
|
||||||
axes[3].axvline(x=enable_frame, color="yellow", linewidth=2,
|
|
||||||
linestyle="--", alpha=0.8)
|
|
||||||
axes[3].set_ylabel("Overflow Count")
|
|
||||||
total = int(agc["sat_count"].sum())
|
|
||||||
axes[3].set_title(f"Post-Gain Overflow (total={total})")
|
|
||||||
axes[3].grid(True, alpha=0.3)
|
|
||||||
|
|
||||||
# RMS signal level (post-gain)
|
|
||||||
axes[4].plot(xs, agc["rms_post"], color="#44aaff", linewidth=1.0)
|
|
||||||
if enable_frame > 0:
|
|
||||||
axes[4].axvline(x=enable_frame, color="yellow", linewidth=2,
|
|
||||||
linestyle="--", alpha=0.8)
|
|
||||||
axes[4].set_ylabel("RMS")
|
|
||||||
axes[4].set_xlabel("Frame")
|
|
||||||
axes[4].set_title("Post-Gain RMS Level")
|
|
||||||
axes[4].grid(True, alpha=0.3)
|
|
||||||
|
|
||||||
|
|
||||||
def analyze_dataset(data: np.ndarray, label: str):
|
|
||||||
"""Run 3-scenario analysis for one dataset."""
|
|
||||||
n_frames = data.shape[0]
|
|
||||||
mid = n_frames // 2
|
|
||||||
|
|
||||||
print(f"\n{'='*60}")
|
|
||||||
print(f" {label} — shape {data.shape}")
|
|
||||||
print(f"{'='*60}")
|
|
||||||
|
|
||||||
# Raw ADC stats
|
|
||||||
raw_sat = np.sum((np.abs(data.real) >= ADC_RAIL) |
|
|
||||||
(np.abs(data.imag) >= ADC_RAIL))
|
|
||||||
print(f" Raw ADC saturation: {raw_sat} samples "
|
|
||||||
f"({100*raw_sat/(2*data.size):.2f}%)")
|
|
||||||
|
|
||||||
# Scenario 1: AGC OFF — pass-through (gain_shift=0x00)
|
|
||||||
print(" [1/3] AGC OFF (gain=0, pass-through) ...")
|
|
||||||
agc_off = simulate_agc(data, agc_enabled=False, initial_gain_enc=0x00)
|
|
||||||
print(f" Post-gain overflow: {agc_off['sat_count'].sum()} "
|
|
||||||
f"(should be 0 — no amplification)")
|
|
||||||
|
|
||||||
# Scenario 2: AGC ON from frame 0
|
|
||||||
print(" [2/3] AGC ON (from start) ...")
|
|
||||||
agc_on = simulate_agc(data, agc_enabled=True, enable_at_frame=0,
|
|
||||||
initial_gain_enc=0x00)
|
|
||||||
print(f" Final gain: {agc_on['gain_signed'][-1]} "
|
|
||||||
f"(enc=0x{agc_on['gain_enc'][-1]:X})")
|
|
||||||
print(f" Post-gain overflow: {agc_on['sat_count'].sum()}")
|
|
||||||
|
|
||||||
# Scenario 3: AGC delayed
|
|
||||||
print(f" [3/3] AGC delayed (ON at frame {mid}) ...")
|
|
||||||
agc_delayed = simulate_agc(data, agc_enabled=True,
|
|
||||||
enable_at_frame=mid,
|
|
||||||
initial_gain_enc=0x00)
|
|
||||||
pre_sat = int(agc_delayed["sat_count"][:mid].sum())
|
|
||||||
post_sat = int(agc_delayed["sat_count"][mid:].sum())
|
|
||||||
print(f" Pre-AGC overflow: {pre_sat} "
|
|
||||||
f"Post-AGC overflow: {post_sat}")
|
|
||||||
|
|
||||||
# Plot
|
|
||||||
fig, axes = plt.subplots(3, 5, figsize=(28, 14))
|
|
||||||
fig.suptitle(f"AERIS-10 AGC Analysis — {label}\n"
|
|
||||||
f"({n_frames} frames, {data.shape[1]} chirps, "
|
|
||||||
f"{data.shape[2]} samples/chirp, "
|
|
||||||
f"raw ADC sat={100*raw_sat/(2*data.size):.2f}%)",
|
|
||||||
fontsize=13, fontweight="bold", y=0.99)
|
|
||||||
|
|
||||||
plot_scenario(axes[0], data, agc_off, "AGC OFF (pass-through)")
|
|
||||||
plot_scenario(axes[1], data, agc_on, "AGC ON (from start)")
|
|
||||||
plot_scenario(axes[2], data, agc_delayed,
|
|
||||||
f"AGC delayed (ON at frame {mid})", enable_frame=mid)
|
|
||||||
|
|
||||||
for ax, lbl in zip(axes[:, 0],
|
|
||||||
["AGC OFF", "AGC ON", "AGC DELAYED"],
|
|
||||||
strict=True):
|
|
||||||
ax.annotate(lbl, xy=(-0.35, 0.5), xycoords="axes fraction",
|
|
||||||
fontsize=13, fontweight="bold", color="white",
|
|
||||||
ha="center", va="center", rotation=90)
|
|
||||||
|
|
||||||
plt.tight_layout(rect=[0.03, 0, 1, 0.95])
|
|
||||||
return fig
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="AGC analysis for ADI raw IQ captures "
|
|
||||||
"(bit-accurate rx_gain_control.v simulation)")
|
|
||||||
parser.add_argument("--amp", type=str,
|
|
||||||
default=str(Path.home() / "Downloads/adi_radar_data"
|
|
||||||
"/amp_radar"
|
|
||||||
"/phaser_amp_4MSPS_500M_300u_256_m3dB.npy"),
|
|
||||||
help="Path to amplified radar .npy")
|
|
||||||
parser.add_argument("--noamp", type=str,
|
|
||||||
default=str(Path.home() / "Downloads/adi_radar_data"
|
|
||||||
"/no_amp_radar"
|
|
||||||
"/phaser_NOamp_4MSPS_500M_300u_256.npy"),
|
|
||||||
help="Path to non-amplified radar .npy")
|
|
||||||
parser.add_argument("--data", type=str, default=None,
|
|
||||||
help="Single dataset mode")
|
|
||||||
parser.add_argument("--label", type=str, default="Custom Data")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
plt.style.use("dark_background")
|
|
||||||
|
|
||||||
if args.data:
|
|
||||||
data = np.load(args.data)
|
|
||||||
analyze_dataset(data, args.label)
|
|
||||||
plt.show()
|
|
||||||
return
|
|
||||||
|
|
||||||
figs = []
|
|
||||||
for path, label in [(args.amp, "With Amplifier (-3 dB)"),
|
|
||||||
(args.noamp, "No Amplifier")]:
|
|
||||||
if not Path(path).exists():
|
|
||||||
print(f"WARNING: {path} not found, skipping")
|
|
||||||
continue
|
|
||||||
data = np.load(path)
|
|
||||||
fig = analyze_dataset(data, label)
|
|
||||||
figs.append(fig)
|
|
||||||
|
|
||||||
if not figs:
|
|
||||||
print("No data found. Use --amp/--noamp or --data.")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
plt.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -65,9 +65,9 @@ class TestRadarSettings(unittest.TestCase):
|
|||||||
|
|
||||||
def test_defaults(self):
|
def test_defaults(self):
|
||||||
s = _models().RadarSettings()
|
s = _models().RadarSettings()
|
||||||
self.assertEqual(s.system_frequency, 10e9)
|
self.assertEqual(s.system_frequency, 10.5e9)
|
||||||
self.assertEqual(s.coverage_radius, 50000)
|
self.assertEqual(s.coverage_radius, 1536)
|
||||||
self.assertEqual(s.max_distance, 50000)
|
self.assertEqual(s.max_distance, 1536)
|
||||||
|
|
||||||
|
|
||||||
class TestGPSData(unittest.TestCase):
|
class TestGPSData(unittest.TestCase):
|
||||||
@@ -425,26 +425,27 @@ class TestWaveformConfig(unittest.TestCase):
|
|||||||
def test_defaults(self):
|
def test_defaults(self):
|
||||||
from v7.models import WaveformConfig
|
from v7.models import WaveformConfig
|
||||||
wc = WaveformConfig()
|
wc = WaveformConfig()
|
||||||
self.assertEqual(wc.sample_rate_hz, 4e6)
|
self.assertEqual(wc.sample_rate_hz, 100e6)
|
||||||
self.assertEqual(wc.bandwidth_hz, 500e6)
|
self.assertEqual(wc.bandwidth_hz, 20e6)
|
||||||
self.assertEqual(wc.chirp_duration_s, 300e-6)
|
self.assertEqual(wc.chirp_duration_s, 30e-6)
|
||||||
self.assertEqual(wc.center_freq_hz, 10.525e9)
|
self.assertEqual(wc.pri_s, 167e-6)
|
||||||
|
self.assertEqual(wc.center_freq_hz, 10.5e9)
|
||||||
self.assertEqual(wc.n_range_bins, 64)
|
self.assertEqual(wc.n_range_bins, 64)
|
||||||
self.assertEqual(wc.n_doppler_bins, 32)
|
self.assertEqual(wc.n_doppler_bins, 32)
|
||||||
self.assertEqual(wc.fft_size, 1024)
|
self.assertEqual(wc.fft_size, 1024)
|
||||||
self.assertEqual(wc.decimation_factor, 16)
|
self.assertEqual(wc.decimation_factor, 16)
|
||||||
|
|
||||||
def test_range_resolution(self):
|
def test_range_resolution(self):
|
||||||
"""range_resolution_m should be ~5.62 m/bin with ADI defaults."""
|
"""range_resolution_m should be ~24.0 m/bin with PLFM defaults."""
|
||||||
from v7.models import WaveformConfig
|
from v7.models import WaveformConfig
|
||||||
wc = WaveformConfig()
|
wc = WaveformConfig()
|
||||||
self.assertAlmostEqual(wc.range_resolution_m, 5.621, places=1)
|
self.assertAlmostEqual(wc.range_resolution_m, 23.98, places=1)
|
||||||
|
|
||||||
def test_velocity_resolution(self):
|
def test_velocity_resolution(self):
|
||||||
"""velocity_resolution_mps should be ~1.484 m/s/bin."""
|
"""velocity_resolution_mps should be ~2.67 m/s/bin."""
|
||||||
from v7.models import WaveformConfig
|
from v7.models import WaveformConfig
|
||||||
wc = WaveformConfig()
|
wc = WaveformConfig()
|
||||||
self.assertAlmostEqual(wc.velocity_resolution_mps, 1.484, places=2)
|
self.assertAlmostEqual(wc.velocity_resolution_mps, 2.67, places=1)
|
||||||
|
|
||||||
def test_max_range(self):
|
def test_max_range(self):
|
||||||
"""max_range_m = range_resolution * n_range_bins."""
|
"""max_range_m = range_resolution * n_range_bins."""
|
||||||
@@ -466,7 +467,8 @@ class TestWaveformConfig(unittest.TestCase):
|
|||||||
"""Non-default parameters correctly change derived values."""
|
"""Non-default parameters correctly change derived values."""
|
||||||
from v7.models import WaveformConfig
|
from v7.models import WaveformConfig
|
||||||
wc1 = WaveformConfig()
|
wc1 = WaveformConfig()
|
||||||
wc2 = WaveformConfig(bandwidth_hz=1e9) # double BW → halve range res
|
# Matched-filter: range_per_bin = c/(2*fs)*dec — proportional to 1/fs
|
||||||
|
wc2 = WaveformConfig(sample_rate_hz=200e6) # double fs → halve range res
|
||||||
self.assertAlmostEqual(wc2.range_resolution_m, wc1.range_resolution_m / 2, places=2)
|
self.assertAlmostEqual(wc2.range_resolution_m, wc1.range_resolution_m / 2, places=2)
|
||||||
|
|
||||||
def test_zero_center_freq_velocity(self):
|
def test_zero_center_freq_velocity(self):
|
||||||
@@ -925,18 +927,18 @@ class TestExtractTargetsFromFrame(unittest.TestCase):
|
|||||||
"""Detection at range bin 10 → range = 10 * range_resolution."""
|
"""Detection at range bin 10 → range = 10 * range_resolution."""
|
||||||
from v7.processing import extract_targets_from_frame
|
from v7.processing import extract_targets_from_frame
|
||||||
frame = self._make_frame(det_cells=[(10, 16)]) # dbin=16 = center → vel=0
|
frame = self._make_frame(det_cells=[(10, 16)]) # dbin=16 = center → vel=0
|
||||||
targets = extract_targets_from_frame(frame, range_resolution=5.621)
|
targets = extract_targets_from_frame(frame, range_resolution=23.98)
|
||||||
self.assertEqual(len(targets), 1)
|
self.assertEqual(len(targets), 1)
|
||||||
self.assertAlmostEqual(targets[0].range, 10 * 5.621, places=2)
|
self.assertAlmostEqual(targets[0].range, 10 * 23.98, places=1)
|
||||||
self.assertAlmostEqual(targets[0].velocity, 0.0, places=2)
|
self.assertAlmostEqual(targets[0].velocity, 0.0, places=2)
|
||||||
|
|
||||||
def test_velocity_sign(self):
|
def test_velocity_sign(self):
|
||||||
"""Doppler bin < center → negative velocity, > center → positive."""
|
"""Doppler bin < center → negative velocity, > center → positive."""
|
||||||
from v7.processing import extract_targets_from_frame
|
from v7.processing import extract_targets_from_frame
|
||||||
frame = self._make_frame(det_cells=[(5, 10), (5, 20)])
|
frame = self._make_frame(det_cells=[(5, 10), (5, 20)])
|
||||||
targets = extract_targets_from_frame(frame, velocity_resolution=1.484)
|
targets = extract_targets_from_frame(frame, velocity_resolution=2.67)
|
||||||
# dbin=10: vel = (10-16)*1.484 = -8.904 (approaching)
|
# dbin=10: vel = (10-16)*2.67 = -16.02 (approaching)
|
||||||
# dbin=20: vel = (20-16)*1.484 = +5.936 (receding)
|
# dbin=20: vel = (20-16)*2.67 = +10.68 (receding)
|
||||||
self.assertLess(targets[0].velocity, 0)
|
self.assertLess(targets[0].velocity, 0)
|
||||||
self.assertGreater(targets[1].velocity, 0)
|
self.assertGreater(targets[1].velocity, 0)
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class RadarMapWidget(QWidget):
|
|||||||
)
|
)
|
||||||
self._targets: list[RadarTarget] = []
|
self._targets: list[RadarTarget] = []
|
||||||
self._pending_targets: list[RadarTarget] | None = None
|
self._pending_targets: list[RadarTarget] | None = None
|
||||||
self._coverage_radius = 50_000 # metres
|
self._coverage_radius = 1_536 # metres (64 bins x 24 m, 3 km mode)
|
||||||
self._tile_server = TileServer.OPENSTREETMAP
|
self._tile_server = TileServer.OPENSTREETMAP
|
||||||
self._show_coverage = True
|
self._show_coverage = True
|
||||||
self._show_trails = False
|
self._show_trails = False
|
||||||
|
|||||||
@@ -108,12 +108,12 @@ class RadarSettings:
|
|||||||
range_resolution and velocity_resolution should be calibrated to
|
range_resolution and velocity_resolution should be calibrated to
|
||||||
the actual waveform parameters.
|
the actual waveform parameters.
|
||||||
"""
|
"""
|
||||||
system_frequency: float = 10e9 # Hz (carrier, used for velocity calc)
|
system_frequency: float = 10.5e9 # Hz (PLFM TX LO, verified from ADF4382 config)
|
||||||
range_resolution: float = 781.25 # Meters per range bin (default: 50km/64)
|
range_resolution: float = 24.0 # Meters per decimated range bin (c/(2*100MSPS)*16)
|
||||||
velocity_resolution: float = 1.0 # m/s per Doppler bin (calibrate to waveform)
|
velocity_resolution: float = 2.67 # m/s per Doppler bin (lam/(2*32*167us))
|
||||||
max_distance: float = 50000 # Max detection range (m)
|
max_distance: float = 1536 # Max detection range (m) -- 64 bins x 24 m (3 km mode)
|
||||||
map_size: float = 50000 # Map display size (m)
|
map_size: float = 1536 # Map display size (m)
|
||||||
coverage_radius: float = 50000 # Map coverage radius (m)
|
coverage_radius: float = 1536 # Map coverage radius (m)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -196,42 +196,44 @@ class TileServer(Enum):
|
|||||||
class WaveformConfig:
|
class WaveformConfig:
|
||||||
"""Physical waveform parameters for converting bins to SI units.
|
"""Physical waveform parameters for converting bins to SI units.
|
||||||
|
|
||||||
Encapsulates the radar waveform so that range/velocity resolution
|
Encapsulates the PLFM radar waveform so that range/velocity resolution
|
||||||
can be derived automatically instead of hardcoded in RadarSettings.
|
can be derived automatically instead of hardcoded in RadarSettings.
|
||||||
|
|
||||||
Defaults match the ADI CN0566 Phaser capture parameters used in
|
Defaults match the PLFM hardware: 100 MSPS post-DDC processing rate,
|
||||||
the golden_reference cosim (4 MSPS, 500 MHz BW, 300 us chirp).
|
20 MHz chirp bandwidth, 30 us long chirp, 167 us PRI, 10.5 GHz carrier.
|
||||||
|
The receiver uses matched-filter pulse compression (NOT deramped FMCW),
|
||||||
|
so range-per-bin = c / (2 * fs_processing) * decimation_factor.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sample_rate_hz: float = 4e6 # ADC sample rate
|
sample_rate_hz: float = 100e6 # Post-DDC processing rate (400 MSPS / 4)
|
||||||
bandwidth_hz: float = 500e6 # Chirp bandwidth
|
bandwidth_hz: float = 20e6 # Chirp bandwidth (Phase 1 target: 30 MHz)
|
||||||
chirp_duration_s: float = 300e-6 # Chirp ramp time
|
chirp_duration_s: float = 30e-6 # Long chirp ramp (informational only)
|
||||||
center_freq_hz: float = 10.525e9 # Carrier frequency
|
pri_s: float = 167e-6 # Pulse repetition interval (chirp + listen)
|
||||||
n_range_bins: int = 64 # After decimation
|
center_freq_hz: float = 10.5e9 # TX LO carrier (verified: ADF4382 config)
|
||||||
|
n_range_bins: int = 64 # After decimation (3 km mode)
|
||||||
n_doppler_bins: int = 32 # After Doppler FFT
|
n_doppler_bins: int = 32 # After Doppler FFT
|
||||||
fft_size: int = 1024 # Pre-decimation FFT length
|
fft_size: int = 1024 # Pre-decimation FFT length
|
||||||
decimation_factor: int = 16 # 1024 → 64
|
decimation_factor: int = 16 # 1024 → 64
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def range_resolution_m(self) -> float:
|
def range_resolution_m(self) -> float:
|
||||||
"""Meters per decimated range bin (FMCW deramped baseband).
|
"""Meters per decimated range bin (matched-filter receiver).
|
||||||
|
|
||||||
For deramped FMCW: bin spacing = c * Fs * T / (2 * N_FFT * BW).
|
For matched-filter pulse compression: bin spacing = c / (2 * fs).
|
||||||
After decimation the bin spacing grows by *decimation_factor*.
|
After decimation the bin spacing grows by *decimation_factor*.
|
||||||
|
This is independent of chirp bandwidth (BW affects resolution, not
|
||||||
|
bin spacing).
|
||||||
"""
|
"""
|
||||||
c = 299_792_458.0
|
c = 299_792_458.0
|
||||||
raw_bin = (
|
raw_bin = c / (2.0 * self.sample_rate_hz)
|
||||||
c * self.sample_rate_hz * self.chirp_duration_s
|
|
||||||
/ (2.0 * self.fft_size * self.bandwidth_hz)
|
|
||||||
)
|
|
||||||
return raw_bin * self.decimation_factor
|
return raw_bin * self.decimation_factor
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def velocity_resolution_mps(self) -> float:
|
def velocity_resolution_mps(self) -> float:
|
||||||
"""m/s per Doppler bin. lambda / (2 * n_doppler * chirp_duration)."""
|
"""m/s per Doppler bin. lambda / (2 * n_doppler * PRI)."""
|
||||||
c = 299_792_458.0
|
c = 299_792_458.0
|
||||||
wavelength = c / self.center_freq_hz
|
wavelength = c / self.center_freq_hz
|
||||||
return wavelength / (2.0 * self.n_doppler_bins * self.chirp_duration_s)
|
return wavelength / (2.0 * self.n_doppler_bins * self.pri_s)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max_range_m(self) -> float:
|
def max_range_m(self) -> float:
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ class TargetSimulator(QObject):
|
|||||||
|
|
||||||
for t in self._targets:
|
for t in self._targets:
|
||||||
new_range = t.range - t.velocity * 0.5
|
new_range = t.range - t.velocity * 0.5
|
||||||
if new_range < 500 or new_range > 50000:
|
if new_range < 50 or new_range > 1536:
|
||||||
continue # target exits coverage — drop it
|
continue # target exits coverage — drop it
|
||||||
|
|
||||||
new_vel = max(-150, min(150, t.velocity + random.uniform(-2, 2)))
|
new_vel = max(-150, min(150, t.velocity + random.uniform(-2, 2)))
|
||||||
|
|||||||
@@ -724,8 +724,8 @@ class TestTier3CStub:
|
|||||||
"freq_max": 30.0e6,
|
"freq_max": 30.0e6,
|
||||||
"prf1": 1000.0,
|
"prf1": 1000.0,
|
||||||
"prf2": 2000.0,
|
"prf2": 2000.0,
|
||||||
"max_distance": 50000.0,
|
"max_distance": 1536.0,
|
||||||
"map_size": 50000.0,
|
"map_size": 1536.0,
|
||||||
}
|
}
|
||||||
pkt = self._build_settings_packet(values)
|
pkt = self._build_settings_packet(values)
|
||||||
result = self._run_stub(stub_binary, pkt)
|
result = self._run_stub(stub_binary, pkt)
|
||||||
@@ -784,11 +784,11 @@ class TestTier3CStub:
|
|||||||
def test_bad_markers_rejected(self, stub_binary):
|
def test_bad_markers_rejected(self, stub_binary):
|
||||||
"""Packet with wrong start/end markers must be rejected."""
|
"""Packet with wrong start/end markers must be rejected."""
|
||||||
values = {
|
values = {
|
||||||
"system_frequency": 10.0e9, "chirp_duration_1": 30.0e-6,
|
"system_frequency": 10.5e9, "chirp_duration_1": 30.0e-6,
|
||||||
"chirp_duration_2": 0.5e-6, "chirps_per_position": 32,
|
"chirp_duration_2": 0.5e-6, "chirps_per_position": 32,
|
||||||
"freq_min": 10.0e6, "freq_max": 30.0e6,
|
"freq_min": 10.0e6, "freq_max": 30.0e6,
|
||||||
"prf1": 1000.0, "prf2": 2000.0,
|
"prf1": 1000.0, "prf2": 2000.0,
|
||||||
"max_distance": 50000.0, "map_size": 50000.0,
|
"max_distance": 1536.0, "map_size": 1536.0,
|
||||||
}
|
}
|
||||||
pkt = self._build_settings_packet(values)
|
pkt = self._build_settings_packet(values)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user