mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-08 22:47:16 +00:00
8865e9a0ef
RTL (P0 pre-bringup findings R-1/R-2/R-3/R-5/R-6): - mti_canceller: add use_long_chirp input and waveform-boundary mute so the long->short transition in mode 01 no longer subtracts across heterogeneous waveforms (R-1). Prev buffer is overwritten in-flight at the boundary so the next same-waveform chirp subtracts cleanly. - ad9484_interface_400m: 2FF sync of mmcm_locked into the 400 MHz domain before gating reset_n_gated (R-6). - cic_decimator_4x_enhanced: correct max_fanout narrative (R-3). - ad9484_interface_400m: strip stale pblock comment, note 3.0 ns max_delay instead (R-2). - mti_canceller / doppler_processor: 200T-20km WARNING banners flagging the broken 4096-bin path (R-5). 9-bit BRAM address aliases silently until rewritten. - adc_clk_mmcm.xdc: relax set_max_delay from 2.700 -> 3.000 ns, closes WNS with headroom on 50T build. - radar_receiver_final: wire use_long_chirp into mti_inst. Architecture-bump finalization (2048-pt range FFT, 512 range bins, 32 Doppler bins -> 16384 output cells per frame): - tb/cosim/radar_scene.py: FFT_SIZE 1024 -> 2048, RANGE_BINS 64 -> 512. - tb/gen_mf_golden_ref.py: N 1024 -> 2048. - Regenerate all affected hex goldens (MF cases 1-4, Doppler inputs + py goldens, receiver integration golden_doppler.mem 2048 -> 16384). - tb_radar_receiver_final: widen range_bin_out 6 -> 9 bits, bump GOLDEN_ENTRIES 2048 -> 16384, expand bitmaps/arrays to 512 bins, update all check messages and thresholds. - tb_mti_canceller, tb_fullchain_mti_cfar_realdata: tie/pass use_long_chirp so compile still works after RTL port add. Test-suite hardening (coverage audit findings): - tb_mti_canceller T12: 10 new assertions exercising R-1 waveform- boundary mute across a long/long/short/short/long sequence. Catches a regression that re-enables subtraction across the boundary. - tb_fir_lowpass: replace tautological check(1'b1, ...) on coefficient symmetry with a real hierarchical check coeff[k]===coeff[31-k]; replace always-pass overflow check with a well-driven (not X/Z) assertion on filter_overflow. - tb_matched_filter_processing_chain: replace three always-pass peak- bin placeholders with peak-to-mean-|out| > 2x ratio checks (catches flat/zero output that the old tautologies silently accepted). - tb_cdc_modules M2: replace always-pass narrow-pulse check with a well-defined-output assertion on the synchronizer. - tb_nco_400m: replace always-pass freq-switch check with a swing + no-X assertion across 200 post-switch samples. - tb_system_e2e G12.1: replace check(1, ...) with test_num > 20 so it catches a stalled TB that skipped prior groups. - tb_multiseg_cosim TEST 4: replace always-pass placeholder with a bitmap that asserts segment_request visited all 4 values. - tb_mf_chain_synth and tb_fullchain_mti_cfar_realdata: add DEPRECATED headers plus \$fatal guards (ifndef ALLOW_STALE_*) so they cannot be silently re-enabled in CI with stale 1024-bin goldens against current 2048-pt RTL. Regression: 32 passed, 0 failed. MTI TB grew 30 -> 39 checks; receiver integration grew 17 -> 18 checks with 16384/16384 golden match at tolerance +/- 2 LSB.
294 lines
13 KiB
Verilog
294 lines
13 KiB
Verilog
`timescale 1ns / 1ps
|
||
|
||
/**
|
||
* mti_canceller.v
|
||
*
|
||
* Moving Target Indication (MTI) — 2-pulse canceller for ground clutter removal.
|
||
*
|
||
* Sits between the range bin decimator and the Doppler processor in the
|
||
* AERIS-10 receiver chain. Subtracts the previous chirp's range profile
|
||
* from the current chirp's profile, implementing H(z) = 1 - z^{-1} in
|
||
* slow-time. This places a null at zero Doppler (DC), removing stationary
|
||
* ground clutter while passing moving targets through.
|
||
*
|
||
* Signal chain position:
|
||
* Range Bin Decimator → [MTI Canceller] → Doppler Processor
|
||
*
|
||
* Algorithm:
|
||
* For each range bin r (0..NUM_RANGE_BINS-1):
|
||
* mti_out_i[r] = current_i[r] - previous_i[r]
|
||
* mti_out_q[r] = current_q[r] - previous_q[r]
|
||
*
|
||
* 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.
|
||
*
|
||
* 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"
|
||
|
||
// ----------------------------------------------------------------------------
|
||
// !!! 200T 20 km MODE BROKEN — FIX BEFORE 200T BRING-UP !!!
|
||
// The prev-chirp BRAM buffer is sized to NUM_RANGE_BINS (512) and the
|
||
// range_bin_in port is 9 bits (`RP_RANGE_BIN_BITS). In 20 km mode the
|
||
// upstream range_bin_decimator emits `RP_OUTPUT_RANGE_BINS_20KM = 4096
|
||
// bins per chirp (8 segments × 512 decimated bins), which aliases into
|
||
// the 9-bit address space and collapses bins 512..4095 onto bins 0..511.
|
||
// On XC7A50T this is latent (SUPPORT_LONG_RANGE undefined → 3 km only),
|
||
// but on XC7A200T with SUPPORT_LONG_RANGE the 20 km data path will
|
||
// silently corrupt every range cell above 3 km.
|
||
// Fix before 200T bring-up: scale NUM_RANGE_BINS/range_bin width with
|
||
// `RP_MAX_OUTPUT_BINS, or gate MTI off entirely in 20 km mode.
|
||
// ----------------------------------------------------------------------------
|
||
module mti_canceller #(
|
||
parameter NUM_RANGE_BINS = `RP_NUM_RANGE_BINS, // 512
|
||
parameter DATA_WIDTH = `RP_DATA_WIDTH // 16
|
||
) (
|
||
input wire clk,
|
||
input wire reset_n,
|
||
|
||
// ========== INPUT (from range bin decimator) ==========
|
||
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 [`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 [`RP_RANGE_BIN_BITS-1:0] range_bin_out, // 9-bit
|
||
|
||
// ========== CONFIGURATION ==========
|
||
input wire mti_enable, // 1=MTI active, 0=pass-through
|
||
|
||
// Current chirp's waveform selector (from radar_mode_controller). Used
|
||
// to mute MTI output across the long↔short chirp boundary in range
|
||
// mode 01 (long-range interleave) — without this, the first chirp of
|
||
// a new waveform subtracts the previous waveform's range profile,
|
||
// injecting a per-range-bin impulse into slow-time sample 0 of the
|
||
// new Doppler sub-frame that spreads across all Doppler bins.
|
||
input wire use_long_chirp,
|
||
|
||
// ========== STATUS ==========
|
||
output reg mti_first_chirp, // 1 during first chirp (output muted)
|
||
|
||
// Audit F-6.3: count of saturated samples since last reset. Saturation
|
||
// here produces spurious Doppler harmonics (phantom targets at ±fs/2)
|
||
// and was previously invisible to the MCU. Saturates at 0xFF.
|
||
output reg [7:0] mti_saturation_count
|
||
);
|
||
|
||
// ============================================================================
|
||
// PREVIOUS CHIRP BUFFER (512 x 16-bit I, 512 x 16-bit Q)
|
||
// ============================================================================
|
||
// BRAM-inferred on XC7A50T/200T (512 entries, sync-only read/write).
|
||
// Using separate I/Q arrays for clean dual-port inference.
|
||
|
||
(* 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;
|
||
reg use_long_chirp_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;
|
||
use_long_chirp_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;
|
||
use_long_chirp_d1 <= use_long_chirp;
|
||
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;
|
||
|
||
// Waveform of the chirp whose profile currently lives in prev_i/prev_q.
|
||
// Latched at end-of-chirp when we mark has_previous=1. Compared against
|
||
// the incoming chirp's waveform at its first bin (range_bin_d1 == 0) to
|
||
// detect a long↔short transition and re-mute.
|
||
reg prev_chirp_was_long;
|
||
wire waveform_changed = has_previous
|
||
&& (use_long_chirp_d1 != prev_chirp_was_long);
|
||
|
||
// ============================================================================
|
||
// MTI PROCESSING (operates on d1 pipeline stage + BRAM read data)
|
||
// ============================================================================
|
||
|
||
// 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_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_d1[DATA_WIDTH-1], range_q_d1}
|
||
- {prev_q_rd[DATA_WIDTH-1], prev_q_rd};
|
||
|
||
// Saturate to DATA_WIDTH bits
|
||
wire signed [DATA_WIDTH-1:0] diff_i_sat;
|
||
wire signed [DATA_WIDTH-1:0] diff_q_sat;
|
||
|
||
assign diff_i_sat = (diff_i_full > $signed({{2{1'b0}}, {(DATA_WIDTH-1){1'b1}}}))
|
||
? $signed({1'b0, {(DATA_WIDTH-1){1'b1}}}) // +max
|
||
: (diff_i_full < $signed({{2{1'b1}}, {(DATA_WIDTH-1){1'b0}}}))
|
||
? $signed({1'b1, {(DATA_WIDTH-1){1'b0}}}) // -max
|
||
: diff_i_full[DATA_WIDTH-1:0];
|
||
|
||
assign diff_q_sat = (diff_q_full > $signed({{2{1'b0}}, {(DATA_WIDTH-1){1'b1}}}))
|
||
? $signed({1'b0, {(DATA_WIDTH-1){1'b1}}})
|
||
: (diff_q_full < $signed({{2{1'b1}}, {(DATA_WIDTH-1){1'b0}}}))
|
||
? $signed({1'b1, {(DATA_WIDTH-1){1'b0}}})
|
||
: diff_q_full[DATA_WIDTH-1:0];
|
||
|
||
// Saturation detection (F-6.3): the top two bits of the DATA_WIDTH+1 signed
|
||
// difference disagree iff the value exceeds the DATA_WIDTH signed range.
|
||
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 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 <= {`RP_RANGE_BIN_BITS{1'b0}};
|
||
has_previous <= 1'b0;
|
||
mti_first_chirp <= 1'b1;
|
||
prev_chirp_was_long <= 1'b0;
|
||
mti_saturation_count <= 8'd0;
|
||
end else begin
|
||
// Count saturated MTI-active samples (F-6.3). Clamp at 0xFF.
|
||
// 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;
|
||
end
|
||
// Default: no valid output
|
||
range_valid_out <= 1'b0;
|
||
|
||
if (range_valid_d1) begin
|
||
// Output path — range_bin is from the delayed pipeline
|
||
range_bin_out <= range_bin_d1;
|
||
|
||
if (!mti_enable_d1) begin
|
||
// Pass-through mode: no MTI processing
|
||
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;
|
||
mti_first_chirp <= 1'b1;
|
||
end else if (!has_previous || waveform_changed) begin
|
||
// No valid previous chirp to subtract from — either the very
|
||
// first chirp after reset/enable, or the long↔short boundary
|
||
// in range_mode=01 where the prev buffer holds a different
|
||
// waveform's profile. Mute output (emit zeros with valid=1
|
||
// so Doppler still sees the expected chirp count), overwrite
|
||
// prev_i/prev_q as this chirp streams through the write port,
|
||
// then re-arm at end-of-chirp with the CURRENT waveform tag.
|
||
range_i_out <= {DATA_WIDTH{1'b0}};
|
||
range_q_out <= {DATA_WIDTH{1'b0}};
|
||
range_valid_out <= 1'b1;
|
||
mti_first_chirp <= 1'b1;
|
||
|
||
// After last range bin of this chirp, the prev buffer now
|
||
// holds a full copy of THIS chirp's profile — arm for the
|
||
// next chirp and remember which waveform was written.
|
||
if (range_bin_d1 == NUM_RANGE_BINS - 1) begin
|
||
has_previous <= 1'b1;
|
||
mti_first_chirp <= 1'b0;
|
||
prev_chirp_was_long <= use_long_chirp_d1;
|
||
end
|
||
end else begin
|
||
// Normal MTI: subtract previous from current
|
||
range_i_out <= diff_i_sat;
|
||
range_q_out <= diff_q_sat;
|
||
range_valid_out <= 1'b1;
|
||
|
||
// Refresh the waveform tag at end-of-chirp so the compare
|
||
// on the next chirp stays correct (same-waveform runs are
|
||
// the common case and the tag must track them).
|
||
if (range_bin_d1 == NUM_RANGE_BINS - 1) begin
|
||
prev_chirp_was_long <= use_long_chirp_d1;
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
// ============================================================================
|
||
// MEMORY INITIALIZATION (simulation only)
|
||
// ============================================================================
|
||
`ifdef SIMULATION
|
||
integer init_k;
|
||
initial begin
|
||
for (init_k = 0; init_k < NUM_RANGE_BINS; init_k = init_k + 1) begin
|
||
prev_i[init_k] = 0;
|
||
prev_q[init_k] = 0;
|
||
end
|
||
end
|
||
`endif
|
||
|
||
endmodule
|