merge(wave2): manual resolution of 6 shared files — fft-2048 × p0 audit

Hand-merged files modified on both fix/pre-bringup-audit-p0 and
feat/fft-2048-upgrade. Wave 1 (commit 60e49c7) took 20 files from fft
verbatim; this wave resolves the overlap.

- run_regression.sh: 3-way merge. Adopts fft's ${RECEIVER_RTL[@]} array
  refactor and drops the self-blessing golden pair from p0. Skip count
  bumped to 5.

- usb_data_interface.v (FT601/200T): p0 FSM + clock-loss watchdog kept
  wholesale; widened stream_control 3 -> 6 bits to carry fft's extended
  mode bits through the CDC sync chain and the 0xFF status word.

- mti_canceller.v: fft's BRAM-inferred 512-range-bin implementation as
  the base, with p0's F-6.3 saturation counter grafted onto the d1
  pipeline stage. Overflow detection uses the top-two-bits disagreement
  on diff_{i,q}_full (DATA_WIDTH+1 signed).

- radar_receiver_final.v: fft's 2048-pt / 512-bin structure + p0
  diagnostic plumbing (ADC overrange sticky+CDC, DDC diagnostics,
  tx_frame_start edge detector replacing chirp_counter frame sync,
  mti_saturation_count, range_decim_watchdog).

- radar_system_top.v: clean 3-way merge, orthogonal regions
  (+38 / -27).

- usb_data_interface_ft2232h.v (FT2232H/50T): fft's per-frame bulk BRAM
  rewrite kept wholesale. Ported two p0 items that are orthogonal to
  the write FSM:
    * ft_clk-loss watchdog (heartbeat + 2FF ASYNC_REG sync + 16-bit
      timeout) ORed into a 2FF sync'd ft_effective_reset_n for the FSM.
    * rd_cmd_complete flag so RD_DEASSERT can distinguish a legitimate
      3-byte completion from an ft_rxf_n abort that also zeros
      rd_byte_cnt.

Deliberately NOT taken from 2401f5f: cic_decimator_4x_enhanced.v and
ddc_400m.v reset-strategy changes. Those conflict with p0's shipped
registered-sync-reset + max_fanout=25 distribution, which is already
timing-clean on the production build.
This commit is contained in:
Jason
2026-04-21 02:12:04 +05:45
parent 60e49c7da6
commit 5f3002a4d1
6 changed files with 1031 additions and 502 deletions
+91 -37
View File
@@ -19,25 +19,33 @@
* mti_out_i[r] = current_i[r] - previous_i[r]
* mti_out_q[r] = current_q[r] - previous_q[r]
*
* The previous chirp's 64 range bins are stored in a small BRAM.
* The previous chirp's 512 range bins are stored in BRAM (inferred via
* sync-only read/write always blocks NO async reset on memory arrays).
* On the very first chirp after reset (or enable), there is no previous
* data output is zero (muted) for that first chirp.
*
* When mti_enable=0, the module is a transparent pass-through with zero
* latency penalty (data goes straight through combinationally registered).
* When mti_enable=0, the module is a transparent pass-through.
*
* Resources:
* - 2 BRAM18 (64 x 16-bit I + 64 x 16-bit Q) or distributed RAM
* - ~30 LUTs (subtract + mux)
* - ~40 FFs (pipeline + control)
* BRAM inference note:
* prev_i/prev_q arrays use dedicated sync-only always blocks for read
* and write. This ensures Vivado infers BRAM (RAMB18) instead of fabric
* FFs + mux trees. The registered read adds 1 cycle of latency, which
* is compensated by a pipeline stage on the input data path.
*
* Resources (target):
* - 2 BRAM18 (512 x 16-bit I + 512 x 16-bit Q)
* - ~30 LUTs (subtract + mux + saturation)
* - ~80 FFs (pipeline + control)
* - 0 DSP48
*
* Clock domain: clk (100 MHz)
*/
`include "radar_params.vh"
module mti_canceller #(
parameter NUM_RANGE_BINS = 64,
parameter DATA_WIDTH = 16
parameter NUM_RANGE_BINS = `RP_NUM_RANGE_BINS, // 512
parameter DATA_WIDTH = `RP_DATA_WIDTH // 16
) (
input wire clk,
input wire reset_n,
@@ -46,13 +54,13 @@ module mti_canceller #(
input wire signed [DATA_WIDTH-1:0] range_i_in,
input wire signed [DATA_WIDTH-1:0] range_q_in,
input wire range_valid_in,
input wire [5:0] range_bin_in,
input wire [`RP_RANGE_BIN_BITS-1:0] range_bin_in, // 9-bit
// ========== OUTPUT (to Doppler processor) ==========
output reg signed [DATA_WIDTH-1:0] range_i_out,
output reg signed [DATA_WIDTH-1:0] range_q_out,
output reg range_valid_out,
output reg [5:0] range_bin_out,
output reg [`RP_RANGE_BIN_BITS-1:0] range_bin_out, // 9-bit
// ========== CONFIGURATION ==========
input wire mti_enable, // 1=MTI active, 0=pass-through
@@ -67,30 +75,79 @@ module mti_canceller #(
);
// ============================================================================
// PREVIOUS CHIRP BUFFER (64 x 16-bit I, 64 x 16-bit Q)
// PREVIOUS CHIRP BUFFER (512 x 16-bit I, 512 x 16-bit Q)
// ============================================================================
// Small enough for distributed RAM on XC7A200T (64 entries).
// Using separate I/Q arrays for clean read/write.
// BRAM-inferred on XC7A50T/200T (512 entries, sync-only read/write).
// Using separate I/Q arrays for clean dual-port inference.
reg signed [DATA_WIDTH-1:0] prev_i [0:NUM_RANGE_BINS-1];
reg signed [DATA_WIDTH-1:0] prev_q [0:NUM_RANGE_BINS-1];
(* ram_style = "block" *) reg signed [DATA_WIDTH-1:0] prev_i [0:NUM_RANGE_BINS-1];
(* ram_style = "block" *) reg signed [DATA_WIDTH-1:0] prev_q [0:NUM_RANGE_BINS-1];
// ============================================================================
// INPUT PIPELINE STAGE (1 cycle delay to match BRAM read latency)
// ============================================================================
// Declarations must precede the BRAM write block that references them.
reg signed [DATA_WIDTH-1:0] range_i_d1, range_q_d1;
reg range_valid_d1;
reg [`RP_RANGE_BIN_BITS-1:0] range_bin_d1;
reg mti_enable_d1;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
range_i_d1 <= {DATA_WIDTH{1'b0}};
range_q_d1 <= {DATA_WIDTH{1'b0}};
range_valid_d1 <= 1'b0;
range_bin_d1 <= {`RP_RANGE_BIN_BITS{1'b0}};
mti_enable_d1 <= 1'b0;
end else begin
range_i_d1 <= range_i_in;
range_q_d1 <= range_q_in;
range_valid_d1 <= range_valid_in;
range_bin_d1 <= range_bin_in;
mti_enable_d1 <= mti_enable;
end
end
// ============================================================================
// BRAM WRITE PORT (sync only — NO async reset for BRAM inference)
// ============================================================================
// Writes the current chirp sample into prev_i/prev_q for next chirp's
// subtraction. Uses the delayed (d1) signals so the write happens 1 cycle
// after the read address is presented, avoiding RAW hazards.
always @(posedge clk) begin
if (range_valid_d1) begin
prev_i[range_bin_d1] <= range_i_d1;
prev_q[range_bin_d1] <= range_q_d1;
end
end
// ============================================================================
// BRAM READ PORT (sync only — 1 cycle read latency)
// ============================================================================
// Address is always driven by range_bin_in (cycle 0). Read data appears
// on prev_i_rd / prev_q_rd at cycle 1, aligned with the d1 pipeline stage.
reg signed [DATA_WIDTH-1:0] prev_i_rd, prev_q_rd;
always @(posedge clk) begin
prev_i_rd <= prev_i[range_bin_in];
prev_q_rd <= prev_q[range_bin_in];
end
// Track whether we have valid previous data
reg has_previous;
// ============================================================================
// MTI PROCESSING
// MTI PROCESSING (operates on d1 pipeline stage + BRAM read data)
// ============================================================================
// Read previous chirp data (combinational)
wire signed [DATA_WIDTH-1:0] prev_i_rd = prev_i[range_bin_in];
wire signed [DATA_WIDTH-1:0] prev_q_rd = prev_q[range_bin_in];
// Compute difference with saturation
// Subtraction can produce DATA_WIDTH+1 bits; saturate back to DATA_WIDTH.
wire signed [DATA_WIDTH:0] diff_i_full = {range_i_in[DATA_WIDTH-1], range_i_in}
wire signed [DATA_WIDTH:0] diff_i_full = {range_i_d1[DATA_WIDTH-1], range_i_d1}
- {prev_i_rd[DATA_WIDTH-1], prev_i_rd};
wire signed [DATA_WIDTH:0] diff_q_full = {range_q_in[DATA_WIDTH-1], range_q_in}
wire signed [DATA_WIDTH:0] diff_q_full = {range_q_d1[DATA_WIDTH-1], range_q_d1}
- {prev_q_rd[DATA_WIDTH-1], prev_q_rd};
// Saturate to DATA_WIDTH bits
@@ -115,20 +172,21 @@ wire diff_i_overflow = (diff_i_full[DATA_WIDTH] != diff_i_full[DATA_WIDTH-1]);
wire diff_q_overflow = (diff_q_full[DATA_WIDTH] != diff_q_full[DATA_WIDTH-1]);
// ============================================================================
// MAIN LOGIC
// MAIN OUTPUT LOGIC (operates on d1 pipeline stage)
// ============================================================================
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
range_i_out <= {DATA_WIDTH{1'b0}};
range_q_out <= {DATA_WIDTH{1'b0}};
range_valid_out <= 1'b0;
range_bin_out <= 6'd0;
range_bin_out <= {`RP_RANGE_BIN_BITS{1'b0}};
has_previous <= 1'b0;
mti_first_chirp <= 1'b1;
mti_saturation_count <= 8'd0;
end else begin
// Count saturated MTI-active samples (F-6.3). Clamp at 0xFF.
if (range_valid_in && mti_enable && has_previous
// Uses d1 pipeline stage to align with diff_i_full/diff_q_full.
if (range_valid_d1 && mti_enable_d1 && has_previous
&& (diff_i_overflow || diff_q_overflow)
&& (mti_saturation_count != 8'hFF)) begin
mti_saturation_count <= mti_saturation_count + 8'd1;
@@ -136,18 +194,14 @@ always @(posedge clk or negedge reset_n) begin
// Default: no valid output
range_valid_out <= 1'b0;
if (range_valid_in) begin
// Always store current sample as "previous" for next chirp
prev_i[range_bin_in] <= range_i_in;
prev_q[range_bin_in] <= range_q_in;
if (range_valid_d1) begin
// Output path — range_bin is from the delayed pipeline
range_bin_out <= range_bin_d1;
// Output path
range_bin_out <= range_bin_in;
if (!mti_enable) begin
if (!mti_enable_d1) begin
// Pass-through mode: no MTI processing
range_i_out <= range_i_in;
range_q_out <= range_q_in;
range_i_out <= range_i_d1;
range_q_out <= range_q_d1;
range_valid_out <= 1'b1;
// Reset first-chirp state when MTI is disabled
has_previous <= 1'b0;
@@ -161,7 +215,7 @@ always @(posedge clk or negedge reset_n) begin
range_valid_out <= 1'b1;
// After last range bin of first chirp, mark previous as valid
if (range_bin_in == NUM_RANGE_BINS - 1) begin
if (range_bin_d1 == NUM_RANGE_BINS - 1) begin
has_previous <= 1'b1;
mti_first_chirp <= 1'b0;
end
+65 -49
View File
@@ -1,5 +1,7 @@
`timescale 1ns / 1ps
`include "radar_params.vh"
module radar_receiver_final (
input wire clk, // 100MHz
input wire reset_n,
@@ -13,26 +15,31 @@ module radar_receiver_final (
input wire adc_or_p,
input wire adc_or_n,
output wire adc_pwdn,
// Chirp counter from transmitter (for matched filter indexing)
input wire [5:0] chirp_counter,
// Frame-start pulse from transmitter (CDC-synchronized, 1 clk_100m cycle)
input wire tx_frame_start,
output wire [31:0] doppler_output,
output wire doppler_valid,
output wire [4:0] doppler_bin,
output wire [5:0] range_bin,
output wire [`RP_RANGE_BIN_BITS-1:0] range_bin, // 9-bit
// Matched filter range profile output (for USB)
// Raw matched-filter output (debug/bring-up)
output wire signed [15:0] range_profile_i_out,
output wire signed [15:0] range_profile_q_out,
output wire range_profile_valid_out,
// Decimated 512-bin range profile (for USB bulk frames / downstream consumers)
output wire [15:0] decimated_range_mag_out,
output wire decimated_range_valid_out,
// Host command inputs (Gap 4: USB Read Path, CDC-synchronized)
// CDC-synchronized in radar_system_top.v before reaching here
input wire [1:0] host_mode, // Radar mode: 00=STM32, 01=auto-scan, 10=single-chirp
input wire host_trigger, // Single-chirp trigger pulse (1 clk cycle)
input wire [1:0] host_range_mode, // Range mode: 00=3km (short only), 01=long-range (dual chirp)
// Gap 2: Host-configurable chirp timing (CDC-synchronized in radar_system_top.v)
input wire [15:0] host_long_chirp_cycles,
@@ -128,9 +135,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 [3:0] gc_current_gain; // Diagnostic: effective gain_shift
// Reference signals for the processing chain
wire [15:0] long_chirp_real, long_chirp_imag;
wire [15:0] short_chirp_real, short_chirp_imag;
// Reference signal for the processing chain (carries long OR short ref
// depending on use_long_chirp selected by chirp_memory_loader_param)
wire [15:0] ref_chirp_real, ref_chirp_imag;
// ========== DOPPLER PROCESSING SIGNALS ==========
wire [31:0] range_data_32bit;
@@ -142,20 +149,36 @@ wire [31:0] doppler_spectrum;
wire doppler_spectrum_valid;
wire [4:0] doppler_bin_out;
wire doppler_processing;
wire doppler_frame_done;
// frame_complete from doppler_processor is a LEVEL signal (high whenever
// state == S_IDLE && !frame_buffer_full). Downstream consumers (USB FT2232H,
// AGC, CFAR) expect a single-cycle PULSE. Convert here at the source so all
// consumers are safe.
wire doppler_frame_done_level; // raw level from doppler_processor
reg doppler_frame_done_prev;
wire doppler_frame_done; // rising-edge pulse (1 clk cycle)
always @(posedge clk or negedge reset_n) begin
if (!reset_n)
doppler_frame_done_prev <= 1'b0;
else
doppler_frame_done_prev <= doppler_frame_done_level;
end
assign doppler_frame_done = doppler_frame_done_level & ~doppler_frame_done_prev;
assign doppler_frame_done_out = doppler_frame_done;
// ========== RANGE BIN DECIMATOR SIGNALS ==========
wire signed [15:0] decimated_range_i;
wire signed [15:0] decimated_range_q;
wire decimated_range_valid;
wire [5:0] decimated_range_bin;
wire [`RP_RANGE_BIN_BITS-1:0] decimated_range_bin; // 9-bit
// ========== MTI CANCELLER SIGNALS ==========
wire signed [15:0] mti_range_i;
wire signed [15:0] mti_range_q;
wire mti_range_valid;
wire [5:0] mti_range_bin;
wire [`RP_RANGE_BIN_BITS-1:0] mti_range_bin; // 9-bit
wire mti_first_chirp;
// ========== RADAR MODE CONTROLLER SIGNALS ==========
@@ -173,6 +196,7 @@ radar_mode_controller rmc (
.clk(clk),
.reset_n(reset_n),
.mode(host_mode), // Controlled by host via USB (default: 2'b01 auto-scan)
.range_mode(host_range_mode), // Range mode: 00=3km, 01=long-range (drives chirp type)
.stm32_new_chirp(stm32_new_chirp_rx),
.stm32_new_elevation(stm32_new_elevation_rx),
.stm32_new_azimuth(stm32_new_azimuth_rx),
@@ -345,7 +369,7 @@ rx_gain_control gain_ctrl (
);
// 3. Dual Chirp Memory Loader
wire [9:0] sample_addr_from_chain;
wire [10:0] sample_addr_from_chain;
chirp_memory_loader_param chirp_mem (
.clk(clk),
@@ -359,20 +383,9 @@ chirp_memory_loader_param chirp_mem (
.mem_ready(mem_ready)
);
// Sample address generator
reg [9:0] sample_addr_reg;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
sample_addr_reg <= 0;
end else if (mem_request) begin
sample_addr_reg <= sample_addr_reg + 1;
if (sample_addr_reg == 1023) sample_addr_reg <= 0;
end
end
// sample_addr_wire removed was unused implicit wire (synthesis warning)
// 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 mem_ready_delayed;
@@ -388,11 +401,10 @@ latency_buffer #(
.valid_out(mem_ready_delayed)
);
// Assign delayed reference signals
assign long_chirp_real = delayed_ref_i;
assign long_chirp_imag = delayed_ref_q;
assign short_chirp_real = delayed_ref_i;
assign short_chirp_imag = delayed_ref_q;
// Assign delayed reference signals (single pair chirp_memory_loader_param
// selects long/short reference upstream via use_long_chirp)
assign ref_chirp_real = delayed_ref_i;
assign ref_chirp_imag = delayed_ref_q;
// 5. Dual Chirp Matched Filter
@@ -404,6 +416,12 @@ wire range_valid;
assign range_profile_i_out = range_profile_i;
assign range_profile_q_out = range_profile_q;
assign range_profile_valid_out = range_valid;
// Manhattan magnitude: |I| + |Q|, saturated to 16 bits
wire [15:0] abs_mti_i = mti_range_i[15] ? (~mti_range_i + 16'd1) : mti_range_i;
wire [15:0] abs_mti_q = mti_range_q[15] ? (~mti_range_q + 16'd1) : mti_range_q;
wire [16:0] manhattan_sum = {1'b0, abs_mti_i} + {1'b0, abs_mti_q};
assign decimated_range_mag_out = manhattan_sum[16] ? 16'hFFFF : manhattan_sum[15:0];
assign decimated_range_valid_out = mti_range_valid;
matched_filter_multi_segment mf_dual (
.clk(clk),
@@ -416,10 +434,8 @@ matched_filter_multi_segment mf_dual (
.mc_new_chirp(mc_new_chirp),
.mc_new_elevation(mc_new_elevation),
.mc_new_azimuth(mc_new_azimuth),
.long_chirp_real(delayed_ref_i), // From latency buffer
.long_chirp_imag(delayed_ref_q),
.short_chirp_real(delayed_ref_i), // Same for short chirp
.short_chirp_imag(delayed_ref_q),
.ref_chirp_real(delayed_ref_i), // From latency buffer (long or short ref)
.ref_chirp_imag(delayed_ref_q),
.segment_request(segment_request),
.mem_request(mem_request),
.sample_addr_out(sample_addr_from_chain),
@@ -430,11 +446,11 @@ matched_filter_multi_segment mf_dual (
);
// ========== CRITICAL: RANGE BIN DECIMATOR ==========
// Convert 1024 range bins to 64 bins for Doppler
// Convert 2048 range bins to 512 bins for Doppler
range_bin_decimator #(
.INPUT_BINS(1024),
.OUTPUT_BINS(64),
.DECIMATION_FACTOR(16)
.INPUT_BINS(`RP_FFT_SIZE), // 2048
.OUTPUT_BINS(`RP_NUM_RANGE_BINS), // 512
.DECIMATION_FACTOR(`RP_DECIMATION_FACTOR) // 4
) range_decim (
.clk(clk),
.reset_n(reset_n),
@@ -446,7 +462,7 @@ range_bin_decimator #(
.range_valid_out(decimated_range_valid),
.range_bin_index(decimated_range_bin),
.decimation_mode(2'b01), // Peak detection mode
.start_bin(10'd0),
.start_bin(11'd0),
.watchdog_timeout(range_decim_watchdog) // Audit F-6.4 — plumbed out
);
@@ -455,8 +471,8 @@ range_bin_decimator #(
// H(z) = 1 - z^{-1} → null at DC Doppler, removes stationary clutter.
// When host_mti_enable=0: transparent pass-through.
mti_canceller #(
.NUM_RANGE_BINS(64),
.DATA_WIDTH(16)
.NUM_RANGE_BINS(`RP_NUM_RANGE_BINS), // 512
.DATA_WIDTH(`RP_DATA_WIDTH) // 16
) mti_inst (
.clk(clk),
.reset_n(reset_n),
@@ -476,11 +492,11 @@ mti_canceller #(
// ========== FRAME SYNC FROM TRANSMITTER ==========
// [FPGA-001 FIXED] Use the authoritative new_chirp_frame signal from the
// transmitter (via plfm_chirp_controller_enhanced), CDC-synchronized to
// clk_100m in radar_system_top. Previous code tried to derive frame
// clk_100m in radar_system_top. Previous code tried to derive frame
// boundaries from chirp_counter == 0, but that counter comes from the
// transmitter path (plfm_chirp_controller_enhanced) which does NOT wrap
// at chirps_per_elev — it overflows to N and only wraps at 6-bit rollover
// (64). This caused frame pulses at half the expected rate for N=32.
// (64). This caused frame pulses at half the expected rate for N=32.
reg tx_frame_start_prev;
reg new_frame_pulse;
@@ -490,13 +506,13 @@ always @(posedge clk or negedge reset_n) begin
new_frame_pulse <= 1'b0;
end else begin
new_frame_pulse <= 1'b0;
// Edge detect: tx_frame_start is a toggle-CDC derived pulse that
// may be 1 clock wide. Capture rising edge for clean 1-cycle pulse.
if (tx_frame_start && !tx_frame_start_prev) begin
new_frame_pulse <= 1'b1;
end
tx_frame_start_prev <= tx_frame_start;
end
end
@@ -510,10 +526,10 @@ assign range_data_valid = mti_range_valid;
// ========== DOPPLER PROCESSOR ==========
doppler_processor_optimized #(
.DOPPLER_FFT_SIZE(16),
.RANGE_BINS(64),
.CHIRPS_PER_FRAME(32),
.CHIRPS_PER_SUBFRAME(16)
.DOPPLER_FFT_SIZE(`RP_DOPPLER_FFT_SIZE), // 16
.RANGE_BINS(`RP_NUM_RANGE_BINS), // 512
.CHIRPS_PER_FRAME(`RP_CHIRPS_PER_FRAME), // 32
.CHIRPS_PER_SUBFRAME(`RP_CHIRPS_PER_SUBFRAME) // 16
) doppler_proc (
.clk(clk),
.reset_n(reset_n),
@@ -529,7 +545,7 @@ doppler_processor_optimized #(
// Status
.processing_active(doppler_processing),
.frame_complete(doppler_frame_done),
.frame_complete(doppler_frame_done_level),
.status()
);
+38 -27
View File
@@ -1,5 +1,7 @@
`timescale 1ns / 1ps
`include "radar_params.vh"
/**
* radar_system_top.v
*
@@ -125,7 +127,7 @@ module radar_system_top (
output wire [31:0] dbg_doppler_data,
output wire dbg_doppler_valid,
output wire [4:0] dbg_doppler_bin,
output wire [5:0] dbg_range_bin,
output wire [`RP_RANGE_BIN_BITS-1:0] dbg_range_bin,
// System status
output wire [3:0] system_status,
@@ -179,11 +181,13 @@ wire tx_current_chirp_sync_valid;
wire [31:0] rx_doppler_output;
wire rx_doppler_valid;
wire [4:0] rx_doppler_bin;
wire [5:0] rx_range_bin;
wire [31:0] rx_range_profile;
wire rx_range_valid;
wire [15:0] rx_doppler_real;
wire [15:0] rx_doppler_imag;
wire [`RP_RANGE_BIN_BITS-1:0] rx_range_bin;
wire [31:0] rx_range_profile;
wire rx_range_valid;
wire [15:0] rx_range_profile_decimated;
wire rx_range_profile_decimated_valid;
wire [15:0] rx_doppler_real;
wire [15:0] rx_doppler_imag;
wire rx_doppler_data_valid;
reg rx_detect_flag; // Threshold detection result (was rx_cfar_detection)
reg rx_detect_valid; // Detection valid pulse (was rx_cfar_valid)
@@ -239,7 +243,7 @@ wire [15:0] usb_cmd_value;
reg [1:0] host_radar_mode;
reg host_trigger_pulse;
reg [15:0] host_detect_threshold; // (was host_cfar_threshold)
reg [2:0] host_stream_control;
reg [5:0] host_stream_control;
// Fix 3: Digital gain control register
// [3]=direction: 0=amplify, 1=attenuate. [2:0]=shift amount 0..7.
@@ -266,13 +270,12 @@ reg host_status_request; // Opcode 0xFF (self-clearing pulse)
localparam DOPPLER_FRAME_CHIRPS = 32; // Total chirps per Doppler frame
reg chirps_mismatch_error; // Set if host tried to set chirps != FFT size
// Fix 7: Range-mode register (opcode 0x20)
// Future-proofing for 3km/10km antenna switching.
// 2'b00 = Auto (default — system selects based on scene)
// 2'b01 = Short-range (3km)
// 2'b10 = Long-range (10km)
// Range-mode register (opcode 0x20)
// Controls chirp type selection in the mode controller:
// 2'b00 = 3 km mode (all short chirps — long blind zone > max range)
// 2'b01 = Long-range (dual chirp: first half long, second half short)
// 2'b10 = Reserved
// 2'b11 = Reserved
// Currently a configuration store only — antenna/timing switching TBD.
reg [1:0] host_range_mode;
// CFAR configuration registers (host-configurable via USB)
@@ -539,14 +542,16 @@ radar_receiver_final rx_inst (
.doppler_bin(rx_doppler_bin),
.range_bin(rx_range_bin),
// Matched filter range profile (for USB)
.range_profile_i_out(rx_range_profile[15:0]),
.range_profile_q_out(rx_range_profile[31:16]),
.range_profile_valid_out(rx_range_valid),
// Range-profile outputs
.range_profile_i_out(rx_range_profile[15:0]),
.range_profile_q_out(rx_range_profile[31:16]),
.range_profile_valid_out(rx_range_valid),
.decimated_range_mag_out(rx_range_profile_decimated),
.decimated_range_valid_out(rx_range_profile_decimated_valid),
// Host command inputs (Gap 4: USB Read Path)
.host_mode(host_radar_mode),
.host_trigger(host_trigger_pulse),
.host_range_mode(host_range_mode),
// Gap 2: Host-configurable chirp timing
.host_long_chirp_cycles(host_long_chirp_cycles),
.host_long_listen_cycles(host_long_listen_cycles),
@@ -624,7 +629,7 @@ assign dc_notch_active = (host_dc_notch_width != 3'd0) &&
wire [31:0] notched_doppler_data = dc_notch_active ? 32'd0 : rx_doppler_output;
wire notched_doppler_valid = rx_doppler_valid;
wire [4:0] notched_doppler_bin = rx_doppler_bin;
wire [5:0] notched_range_bin = rx_range_bin;
wire [`RP_RANGE_BIN_BITS-1:0] notched_range_bin = rx_range_bin;
// ============================================================================
// CFAR DETECTOR (replaces simple threshold detector)
@@ -635,7 +640,7 @@ wire [5:0] notched_range_bin = rx_range_bin;
wire cfar_detect_flag;
wire cfar_detect_valid;
wire [5:0] cfar_detect_range;
wire [`RP_RANGE_BIN_BITS-1:0] cfar_detect_range;
wire [4:0] cfar_detect_doppler;
wire [16:0] cfar_detect_magnitude;
wire [16:0] cfar_detect_threshold;
@@ -726,9 +731,10 @@ end
// DATA PACKING FOR USB
// ============================================================================
// Range profile from matched filter output (wired through radar_receiver_final)
assign usb_range_profile = rx_range_profile;
assign usb_range_valid = rx_range_valid;
// USB range profile must match the advertised 512-bin frame payload, so source it
// from the decimated range stream that feeds Doppler rather than raw MF samples.
assign usb_range_profile = {16'd0, rx_range_profile_decimated};
assign usb_range_valid = rx_range_profile_decimated_valid;
assign usb_doppler_real = rx_doppler_real;
assign usb_doppler_imag = rx_doppler_imag;
@@ -831,6 +837,11 @@ end else begin : gen_ft2232h
.cfar_detection(usb_detect_flag),
.cfar_valid(usb_detect_valid),
// Bulk frame protocol inputs
.range_bin_in(notched_range_bin),
.doppler_bin_in(notched_doppler_bin),
.frame_complete(rx_frame_complete),
// FT2232H Interface
.ft_data(ft_data),
.ft_rxf_n(ft_rxf_n),
@@ -952,7 +963,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
host_radar_mode <= 2'b01; // Default: auto-scan
host_trigger_pulse <= 1'b0;
host_detect_threshold <= 16'd10000; // Default threshold
host_stream_control <= 3'b111; // Default: all streams enabled
host_stream_control <= `RP_STREAM_CTRL_DEFAULT; // Default: all streams, mag-only mode
host_gain_shift <= 4'd0; // Default: pass-through (no gain change)
// Gap 2: chirp timing defaults (match radar_mode_controller parameters)
host_long_chirp_cycles <= 16'd3000;
@@ -963,7 +974,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
host_chirps_per_elev <= 6'd32;
host_status_request <= 1'b0;
chirps_mismatch_error <= 1'b0;
host_range_mode <= 2'b00; // Default: auto
host_range_mode <= 2'b00; // Default: 3 km mode (all short chirps)
// CFAR defaults (disabled by default — backward-compatible)
host_cfar_guard <= 4'd2; // 2 guard cells each side
host_cfar_train <= 5'd8; // 8 training cells each side
@@ -990,7 +1001,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
8'h01: host_radar_mode <= usb_cmd_value[1:0];
8'h02: host_trigger_pulse <= 1'b1;
8'h03: host_detect_threshold <= usb_cmd_value;
8'h04: host_stream_control <= usb_cmd_value[2:0];
8'h04: host_stream_control <= usb_cmd_value[5:0];
// Gap 2: chirp timing configuration
8'h10: host_long_chirp_cycles <= usb_cmd_value;
8'h11: host_long_listen_cycles <= usb_cmd_value;
@@ -1013,7 +1024,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
end
end
8'h16: host_gain_shift <= usb_cmd_value[3:0]; // Fix 3: digital gain
8'h20: host_range_mode <= usb_cmd_value[1:0]; // Fix 7: range mode
8'h20: host_range_mode <= usb_cmd_value[1:0]; // Range mode
// CFAR configuration opcodes
8'h21: host_cfar_guard <= usb_cmd_value[3:0];
8'h22: host_cfar_train <= usb_cmd_value[4:0];
+162 -15
View File
@@ -264,11 +264,141 @@ run_lint_static() {
fi
}
# ---------------------------------------------------------------------------
# Helper: compile, run, and compare a matched-filter co-sim scenario
# run_mf_cosim <scenario_name> <define_flag>
# ---------------------------------------------------------------------------
run_mf_cosim() {
local name="$1"
local define="$2"
local vvp="tb/tb_mf_cosim_${name}.vvp"
local scenario_lower="$name"
printf " %-45s " "MF Co-Sim ($name)"
# Compile — build command as string to handle optional define
local cmd="iverilog -g2001 -DSIMULATION"
if [[ -n "$define" ]]; then
cmd="$cmd $define"
fi
cmd="$cmd -o $vvp tb/tb_mf_cosim.v matched_filter_processing_chain.v fft_engine.v chirp_memory_loader_param.v"
if ! eval "$cmd" 2>/tmp/iverilog_err_$$; then
echo -e "${RED}COMPILE FAIL${NC}"
ERRORS="$ERRORS\n MF Co-Sim ($name): compile error ($(head -1 /tmp/iverilog_err_$$))"
FAIL=$((FAIL + 1))
return
fi
# Run TB
local output
output=$(timeout 120 vvp "$vvp" 2>&1) || true
rm -f "$vvp"
# Check TB internal pass/fail
local tb_fail
tb_fail=$(echo "$output" | grep -Ec '^\[FAIL' || true)
if [[ "$tb_fail" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} (TB internal failure)"
ERRORS="$ERRORS\n MF Co-Sim ($name): TB internal failure"
FAIL=$((FAIL + 1))
return
fi
# Run Python compare
if command -v python3 >/dev/null 2>&1; then
local compare_out
local compare_rc=0
compare_out=$(python3 tb/cosim/compare_mf.py "$scenario_lower" 2>&1) || compare_rc=$?
if [[ "$compare_rc" -ne 0 ]]; then
echo -e "${RED}FAIL${NC} (compare_mf.py mismatch)"
ERRORS="$ERRORS\n MF Co-Sim ($name): Python compare failed"
FAIL=$((FAIL + 1))
return
fi
else
echo -e "${YELLOW}SKIP${NC} (RTL passed, python3 not found — compare skipped)"
SKIP=$((SKIP + 1))
return
fi
echo -e "${GREEN}PASS${NC} (RTL + Python compare)"
PASS=$((PASS + 1))
}
# ---------------------------------------------------------------------------
# Helper: compile, run, and compare a Doppler co-sim scenario
# run_doppler_cosim <scenario_name> <define_flag>
# ---------------------------------------------------------------------------
run_doppler_cosim() {
local name="$1"
local define="$2"
local vvp="tb/tb_doppler_cosim_${name}.vvp"
printf " %-45s " "Doppler Co-Sim ($name)"
# Compile — build command as string to handle optional define
local cmd="iverilog -g2001 -DSIMULATION"
if [[ -n "$define" ]]; then
cmd="$cmd $define"
fi
cmd="$cmd -o $vvp tb/tb_doppler_cosim.v doppler_processor.v xfft_16.v fft_engine.v"
if ! eval "$cmd" 2>/tmp/iverilog_err_$$; then
echo -e "${RED}COMPILE FAIL${NC}"
ERRORS="$ERRORS\n Doppler Co-Sim ($name): compile error ($(head -1 /tmp/iverilog_err_$$))"
FAIL=$((FAIL + 1))
return
fi
# Run TB
local output
output=$(timeout 120 vvp "$vvp" 2>&1) || true
rm -f "$vvp"
# Check TB internal pass/fail
local tb_fail
tb_fail=$(echo "$output" | grep -Ec '^\[FAIL' || true)
if [[ "$tb_fail" -gt 0 ]]; then
echo -e "${RED}FAIL${NC} (TB internal failure)"
ERRORS="$ERRORS\n Doppler Co-Sim ($name): TB internal failure"
FAIL=$((FAIL + 1))
return
fi
# Run Python compare
if command -v python3 >/dev/null 2>&1; then
local compare_out
local compare_rc=0
compare_out=$(python3 tb/cosim/compare_doppler.py "$name" 2>&1) || compare_rc=$?
if [[ "$compare_rc" -ne 0 ]]; then
echo -e "${RED}FAIL${NC} (compare_doppler.py mismatch)"
ERRORS="$ERRORS\n Doppler Co-Sim ($name): Python compare failed"
FAIL=$((FAIL + 1))
return
fi
else
echo -e "${YELLOW}SKIP${NC} (RTL passed, python3 not found — compare skipped)"
SKIP=$((SKIP + 1))
return
fi
echo -e "${GREEN}PASS${NC} (RTL + Python compare)"
PASS=$((PASS + 1))
}
# ---------------------------------------------------------------------------
# Helper: compile and run a single testbench
# run_test <name> <vvp_path> <iverilog_args...>
# ---------------------------------------------------------------------------
run_test() {
# Optional: --timeout=N as first arg overrides default 120s
local timeout_secs=120
if [[ "$1" == --timeout=* ]]; then
timeout_secs="${1#--timeout=}"
shift
fi
local name="$1"
local vvp="$2"
shift 2
@@ -286,7 +416,7 @@ run_test() {
# Run
local output
output=$(timeout 120 vvp "$vvp" 2>&1) || true
output=$(timeout "$timeout_secs" vvp "$vvp" 2>&1) || true
# Count PASS/FAIL in output (testbenches use explicit [PASS]/[FAIL] markers)
local test_pass test_fail
@@ -378,9 +508,9 @@ run_test "Chirp Contract" \
tb/tb_chirp_ctr_reg.vvp \
tb/tb_chirp_contract.v plfm_chirp_controller.v
run_test "Doppler Processor (DSP48)" \
tb/tb_doppler_reg.vvp \
tb/tb_doppler_cosim.v doppler_processor.v xfft_16.v fft_engine.v
run_doppler_cosim "stationary" ""
run_doppler_cosim "moving" "-DSCENARIO_MOVING"
run_doppler_cosim "two_targets" "-DSCENARIO_TWO"
run_test "Threshold Detector (detection bugs)" \
tb/tb_threshold_detector.vvp \
@@ -427,15 +557,16 @@ run_test "Full-Chain Real-Data (decim→Doppler, exact match)" \
doppler_processor.v xfft_16.v fft_engine.v
if [[ "$QUICK" -eq 0 ]]; then
# Golden generate
run_test "Receiver (golden generate)" \
tb/tb_rx_golden_reg.vvp \
-DGOLDEN_GENERATE \
tb/tb_radar_receiver_final.v "${RECEIVER_RTL[@]}"
# Golden compare
run_test "Receiver (golden compare)" \
tb/tb_rx_compare_reg.vvp \
# Receiver integration (structural + bounds + pulse assertions).
# Replaces the earlier "Receiver golden generate/compare" pair, which was
# self-blessing (both passes ran identical RTL on identical stimulus, so
# it passed regardless of bugs). Real co-sim coverage is now provided by
# tb_doppler_realdata.v and tb_fullchain_realdata.v (Python goldens,
# exact match); this integration test exercises the full RX pipeline
# (ADC stub → DDC → MF → Decim → Doppler) and verifies that
# doppler_frame_done is a single-cycle pulse at module boundaries.
run_test --timeout=600 "Receiver Integration (tb_radar_receiver_final)" \
tb/tb_rx_final_reg.vvp \
tb/tb_radar_receiver_final.v "${RECEIVER_RTL[@]}"
# Full system top (monitoring-only, legacy)
@@ -459,12 +590,28 @@ if [[ "$QUICK" -eq 0 ]]; then
-DUSB_MODE_1 \
tb/tb_system_e2e.v "${SYSTEM_RTL[@]}"
else
echo " (skipped receiver golden + system top + E2E — use without --quick)"
SKIP=$((SKIP + 6))
echo " (skipped receiver integration + system top + E2E + USB_MODE=1 variants — use without --quick)"
SKIP=$((SKIP + 5))
fi
echo ""
# ===========================================================================
# PHASE 2b: MATCHED FILTER CO-SIMULATION (RTL vs Python golden reference)
# Runs tb_mf_cosim.v for 4 scenarios, then compare_mf.py validates output
# against committed Python golden CSV files. In SIMULATION mode, thresholds
# are generous (behavioral vs fixed-point twiddles differ) — validates
# state machine mechanics, output count, and energy sanity.
# ===========================================================================
echo "--- PHASE 2b: Matched Filter Co-Sim ---"
run_mf_cosim "chirp" ""
run_mf_cosim "dc" "-DSCENARIO_DC"
run_mf_cosim "impulse" "-DSCENARIO_IMPULSE"
run_mf_cosim "tone5" "-DSCENARIO_TONE5"
echo ""
# ===========================================================================
# PHASE 3: UNIT TESTS — Signal Processing
# ===========================================================================
+10 -10
View File
@@ -76,14 +76,14 @@ module usb_data_interface (
// Bit 0 = range stream enable
// Bit 1 = doppler stream enable
// Bit 2 = cfar/detection stream enable
input wire [2:0] stream_control,
input wire [5:0] stream_control,
// Gap 2: Status readback inputs (clk_100m domain, CDC'd internally)
// When status_request pulses, the write FSM sends a status response
// packet containing the current register values.
input wire status_request, // 1-cycle pulse in clk_100m when 0xFF received
input wire [15:0] status_cfar_threshold, // Current CFAR threshold
input wire [2:0] status_stream_ctrl, // Current stream control
input wire [5:0] status_stream_ctrl, // Current stream control (6-bit: [2:0]=stream en, [5:3]=format)
input wire [1:0] status_radar_mode, // Current radar mode
input wire [15:0] status_long_chirp, // Current long chirp cycles
input wire [15:0] status_long_listen, // Current long listen cycles
@@ -302,8 +302,8 @@ reg [11:0] sample_counter;
// deadlock before host configures streams. With all streams enabled on
// reset, the first range_valid triggers the write FSM which then blocks
// forever on SEND_DOPPLER_DATA (Doppler hasn't produced data yet).
(* ASYNC_REG = "TRUE" *) reg [2:0] stream_ctrl_sync_0;
(* ASYNC_REG = "TRUE" *) reg [2:0] stream_ctrl_sync_1;
(* ASYNC_REG = "TRUE" *) reg [5:0] stream_ctrl_sync_0;
(* ASYNC_REG = "TRUE" *) reg [5:0] stream_ctrl_sync_1;
wire stream_range_en = stream_ctrl_sync_1[0];
wire stream_doppler_en = stream_ctrl_sync_1[1];
wire stream_cfar_en = stream_ctrl_sync_1[2];
@@ -339,8 +339,8 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
cfar_detection_cap <= 1'b0;
range_data_ready <= 1'b0;
// Fix #5: Default to range-only on reset (prevents write FSM deadlock)
stream_ctrl_sync_0 <= 3'b001;
stream_ctrl_sync_1 <= 3'b001;
stream_ctrl_sync_0 <= 6'b000_001;
stream_ctrl_sync_1 <= 6'b000_001;
// Gap 2: status request CDC reset
status_req_sync <= 2'b00;
status_req_sync_prev <= 1'b0;
@@ -362,9 +362,9 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
// Gap 2: Capture status snapshot when request arrives in ft601 domain
if (status_req_ft601) begin
// Pack register values into 5x 32-bit status words
// Word 0: {0xFF[31:24], mode[23:22], stream[21:19], 3'b000[18:16], threshold[15:0]}
// Word 0: {0xFF[31:24], mode[23:22], stream[21:16], threshold[15:0]}
status_words[0] <= {8'hFF, status_radar_mode, status_stream_ctrl,
3'b000, status_cfar_threshold};
status_cfar_threshold};
// Word 1: {long_chirp_cycles[15:0], long_listen_cycles[15:0]}
status_words[1] <= {status_long_chirp, status_long_listen};
// Word 2: {guard_cycles[15:0], short_chirp_cycles[15:0]}
@@ -554,7 +554,7 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
data_pkt_word2 <= {stream_doppler_en ? doppler_imag_cap[7:0] : 8'd0,
stream_cfar_en
? {(sample_counter == 12'd0), 6'b0, cfar_detection_cap}
: {(sample_counter == 12'd0), 7'd0},
: {(sample_counter == 12'd0), 7'd0},
FOOTER,
8'h00}; // pad byte
data_pkt_be2 <= 4'b1110; // 3 valid bytes + 1 pad
@@ -637,7 +637,7 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
if (sample_counter == NUM_CELLS - 12'd1)
sample_counter <= 12'd0;
else
sample_counter <= sample_counter + 12'd1;
sample_counter <= sample_counter + 12'd1;
current_state <= IDLE;
end
endcase
File diff suppressed because it is too large Load Diff