mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-14 09:08:17 +00:00
fix(radar): RX chain corrections, GUI bin alignment, MCU boot ordering
FPGA — RX chain
matched_filter_multi_segment.v: drop the gratuitous /4 scaling on
DDC sign-extended input (was ddc_i[17:2] + ddc_i[1]); use
ddc_i[15:0] directly. fft_engine has INTERNAL_W=32 with
saturating 16-bit output, so full 16-bit input is safe. Restores
~12 dB of MF input dynamic range.
radar_receiver_final.v: remove latency_buffer (count-N-pulses-then-
prime FIFO that left frame 1 with all-zero ref). Replaced with
a single-FF alignment register on ref_i/ref_q that matches the
1-FF stage multi_segment ST_PROCESSING uses on adc_data.
Verified by tb/tb_rxb_fullchain_latency.v — autocorrelation peak
at bin 0 with peak/mean ~88x.
doppler_processor.v / mti_canceller.v / cfar_ca.v /
range_bin_decimator.v / radar_receiver_final.v / radar_system_top.v
/ usb_data_interface_ft2232h.v: switch port and parameter widths
from RP_NUM_RANGE_BINS / RP_RANGE_BIN_BITS (always 512 / 9-bit)
to RP_MAX_OUTPUT_BINS / RP_RANGE_BIN_WIDTH_MAX (auto-scales:
50T 512 / 9-bit, 200T 4096 / 12-bit). Unblocks 200T 20 km mode
at the RX module boundary; USB wire-protocol extension still
pending.
radar_receiver_final.v: doppler_frame_done_prev reset value 0 -> 1
to prevent false done pulse on cycle 1 when level signal is
HIGH at reset.
matched_filter_processing_chain.v: delete the broken `ifdef
SIMULATION inline behavioural FFT (482 lines removed). It
produced wrong-bin peaks and 100-1000x weak magnitudes. Chain
now uses production fft_engine.v + frequency_matched_filter.v
in both iverilog and Vivado. Iverilog tests are ~38x slower per
chain pass but produce correct results. Misleading "OK with
Xilinx IP" comments at three test sites updated since the FFT
is in-house, not an IP placeholder.
FPGA — testbenches
tb/tb_rxb_latency_measure.v (new): measures chain internal pipeline
depth (~2057 cycles, chirp-agnostic).
tb/tb_rxb_fullchain_latency.v (new): full-chain autocorrelation
verification — drives ddc with the same chirp samples the loader
serves as ref, finds peak position and peak/mean.
tb/tb_matched_filter_processing_chain.v: wait timeouts bumped
50000 -> 500000 cycles to accommodate production FFT pipeline.
MCU
main.cpp checkSystemHealthStatus: latch system_emergency_state on
the error_count > 10 path so the SAFE-MODE blink loop in main()
actually engages (was bypassed because predicate was false).
main.cpp: move FPGA reset BEFORE the if(PowerAmplifier) block so
adar_tr_x is driven LOW (RX commanded externally) before PA Vdd
reaches 22 V. Old reset block at the original location removed.
main.cpp MX_GPIO_Init: add GPIO_PIN_12 (FPGA reset) to the
explicit WritePin(LOW) list so the safe initial state is no
longer implicit.
main.cpp checkSystemHealth: rate-limit ADAR1000
verifyDeviceCommunication (HAL_Delay 1ms x 4 devices = 4 ms
blocking SPI burst per main-loop iteration) from every-loop to
every 2 s. readTemperature stays per-loop so over-temp
detection latency is unchanged.
USBHandler.cpp processSettingsData: dispatch threshold bumped
74 -> 82 (matches parser minimum); buffer drained after parse
attempt (slide remaining bytes left) so a false END find no
longer sticks the buffer until 256-byte overflow.
GUI
radar_protocol.py: NUM_RANGE_BINS 64 -> 512 (matches FPGA
RP_NUM_RANGE_BINS); NUM_CELLS 2048 -> 16384.
radar_protocol.py _ingest_sample: honor FPGA frame_start bit for
resync after a USB drop; capture range_profile[rbin] once per
range bin at dbin == 0 (FPGA emits the same range_i/range_q for
all 32 Doppler cells of a given range bin; previous accumulator
inflated the profile 32x).
v7/models.py RadarSettings: range_resolution 24 -> 6 m (matches
c/(2*100MHz)*4); max_distance and coverage_radius 1536 -> 3072 m;
map_size 2000 -> 4000.
v7/models.py WaveformConfig: n_range_bins 64 -> 512, fft_size
1024 -> 2048, decimation_factor 16 -> 4.
GUI_V65_Tk.py: _RANGE_PER_BIN math and stale "~24 m / ~1536 m"
comments updated.
test_v7.py: assertion values updated to match new defaults.
Tests
test_ddc_cosim_fuzz.py: remove unused os/tempfile imports, wrap
three long lines for ruff E501 compliance.
This commit is contained in:
@@ -195,7 +195,7 @@ module tb_matched_filter_processing_chain;
|
||||
integer wait_count;
|
||||
begin
|
||||
wait_count = 0;
|
||||
while (chain_state != target_state && wait_count < 50000) begin
|
||||
while (chain_state != target_state && wait_count < 500000) begin
|
||||
@(posedge clk);
|
||||
wait_count = wait_count + 1;
|
||||
end
|
||||
@@ -208,7 +208,7 @@ module tb_matched_filter_processing_chain;
|
||||
integer wait_count;
|
||||
begin
|
||||
wait_count = 0;
|
||||
while (chain_state != ST_IDLE && wait_count < 50000) begin
|
||||
while (chain_state != ST_IDLE && wait_count < 500000) begin
|
||||
@(posedge clk);
|
||||
wait_count = wait_count + 1;
|
||||
end
|
||||
@@ -332,7 +332,11 @@ module tb_matched_filter_processing_chain;
|
||||
// noise that scatters energy far from bin 0. Xilinx IP uses full internal
|
||||
// precision and passes this correctly in hardware.
|
||||
if (!(cap_peak_bin <= 128 || cap_peak_bin >= FFT_SIZE - 128)) begin
|
||||
$display("[WARN] Autocorrelation peak at bin %0d (expected near 0) - behavioral FFT noise, OK with Xilinx IP", cap_peak_bin);
|
||||
// [RX-NEW-1] fft_engine.v is in-house — it IS the production FFT, not
|
||||
// a behavioural model that gets swapped for Xilinx IP. Wrong-bin peak
|
||||
// is therefore a real bug in fft_engine / frequency_matched_filter,
|
||||
// not "behavioral noise". See project memory ledger entry RX-NEW-1.
|
||||
$display("[FAIL-INFO] Autocorrelation peak at bin %0d (expected 0) — fft_engine bug, see RX-NEW-1", cap_peak_bin);
|
||||
end
|
||||
// Behavioral Q15 FFT scatters the peak, so we cannot assert bin
|
||||
// location — but the peak MUST dominate the mean magnitude. This
|
||||
@@ -496,7 +500,7 @@ module tb_matched_filter_processing_chain;
|
||||
|
||||
check(cap_count == FFT_SIZE, "Case 1: Got 2048 output samples");
|
||||
if (!(cap_peak_bin <= 128 || cap_peak_bin >= FFT_SIZE - 128)) begin
|
||||
$display("[WARN] Case 1: peak at bin %0d (expected near 0) - behavioral FFT noise", cap_peak_bin);
|
||||
$display("[FAIL-INFO] Case 1: peak at bin %0d (expected 0) — fft_engine bug, see RX-NEW-1", cap_peak_bin);
|
||||
end
|
||||
begin : p2m_case1
|
||||
integer k, sum_abs, mean_abs;
|
||||
@@ -538,7 +542,7 @@ module tb_matched_filter_processing_chain;
|
||||
|
||||
check(cap_count == FFT_SIZE, "Case 2: Got 2048 output samples");
|
||||
if (!(cap_peak_bin <= 128 || cap_peak_bin >= FFT_SIZE - 128)) begin
|
||||
$display("[WARN] Case 2: peak at bin %0d (expected near 0) - behavioral FFT noise", cap_peak_bin);
|
||||
$display("[FAIL-INFO] Case 2: peak at bin %0d (expected near 0) — fft_engine bug, see RX-NEW-1", cap_peak_bin);
|
||||
end
|
||||
begin : p2m_case2
|
||||
integer k, sum_abs, mean_abs;
|
||||
|
||||
@@ -0,0 +1,309 @@
|
||||
`timescale 1ns/1ps
|
||||
`include "radar_params.vh"
|
||||
|
||||
// ============================================================================
|
||||
// tb_rxb_fullchain_latency.v
|
||||
//
|
||||
// RX-B verification — Option A (latency_buffer removed, ref direct-wired).
|
||||
//
|
||||
// Production wiring this TB mirrors:
|
||||
// ddc_i/q (test stimulus) -> matched_filter_multi_segment -> chain
|
||||
// chirp_memory_loader -----direct wire--------------------> chain ref
|
||||
//
|
||||
// Tests:
|
||||
// 1) Pipeline timing: report cycle counts (first ddc_valid -> first
|
||||
// pc_valid). Confirms FSM advances and produces output.
|
||||
// 2) Autocorrelation peak position: drive ddc with the SAME short-chirp
|
||||
// samples that the loader serves up as ref. Output is the chirp
|
||||
// autocorrelation. Peak should be at bin 0 if ref/signal are aligned
|
||||
// at the chain. Any shift indicates an alignment error of N cycles.
|
||||
// ============================================================================
|
||||
|
||||
module tb_rxb_fullchain_latency;
|
||||
|
||||
localparam CLK_PERIOD = 10.0; // 100 MHz
|
||||
localparam FFT_SIZE = `RP_FFT_SIZE; // 2048
|
||||
localparam SHORT_LEN = 50; // matches RP_SHORT_CHIRP_SAMPLES
|
||||
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
|
||||
// multi_segment inputs
|
||||
reg signed [17:0] ddc_i;
|
||||
reg signed [17:0] ddc_q;
|
||||
reg ddc_valid;
|
||||
reg use_long_chirp;
|
||||
reg [5:0] chirp_counter;
|
||||
reg mc_new_chirp;
|
||||
reg mc_new_elevation;
|
||||
reg mc_new_azimuth;
|
||||
|
||||
// multi_segment <-> memory loader interconnect
|
||||
wire [1:0] segment_request;
|
||||
wire [10:0] sample_addr_out;
|
||||
wire mem_request;
|
||||
wire mem_ready_loader; // direct from loader
|
||||
|
||||
// Loader outputs (direct-wired to chain via multi_segment ports)
|
||||
wire [15:0] ref_i_raw;
|
||||
wire [15:0] ref_q_raw;
|
||||
|
||||
// multi_segment outputs
|
||||
wire signed [15:0] pc_i;
|
||||
wire signed [15:0] pc_q;
|
||||
wire pc_valid;
|
||||
wire [3:0] ms_status;
|
||||
|
||||
// ----- Memory loader -----
|
||||
chirp_memory_loader_param #(
|
||||
.DEBUG(0)
|
||||
) chirp_mem (
|
||||
.clk (clk),
|
||||
.reset_n (reset_n),
|
||||
.segment_select (segment_request),
|
||||
.mem_request (mem_request),
|
||||
.use_long_chirp (use_long_chirp),
|
||||
.sample_addr (sample_addr_out),
|
||||
.ref_i (ref_i_raw),
|
||||
.ref_q (ref_q_raw),
|
||||
.mem_ready (mem_ready_loader)
|
||||
);
|
||||
|
||||
// ----- 1-FF alignment register (mirrors radar_receiver_final.v) -----
|
||||
// multi_segment ST_PROCESSING latches adc_data through one register
|
||||
// stage; ref path needs the same to align at chain inputs.
|
||||
reg [15:0] ref_i_d, ref_q_d;
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
ref_i_d <= 16'd0;
|
||||
ref_q_d <= 16'd0;
|
||||
end else begin
|
||||
ref_i_d <= ref_i_raw;
|
||||
ref_q_d <= ref_q_raw;
|
||||
end
|
||||
end
|
||||
|
||||
// ----- multi_segment (drives chain internally) -----
|
||||
matched_filter_multi_segment ms_dut (
|
||||
.clk (clk),
|
||||
.reset_n (reset_n),
|
||||
.ddc_i (ddc_i),
|
||||
.ddc_q (ddc_q),
|
||||
.ddc_valid (ddc_valid),
|
||||
.use_long_chirp (use_long_chirp),
|
||||
.chirp_counter (chirp_counter),
|
||||
.mc_new_chirp (mc_new_chirp),
|
||||
.mc_new_elevation (mc_new_elevation),
|
||||
.mc_new_azimuth (mc_new_azimuth),
|
||||
.ref_chirp_real (ref_i_d),
|
||||
.ref_chirp_imag (ref_q_d),
|
||||
.segment_request (segment_request),
|
||||
.sample_addr_out (sample_addr_out),
|
||||
.mem_request (mem_request),
|
||||
.mem_ready (mem_ready_loader),
|
||||
.pc_i_w (pc_i),
|
||||
.pc_q_w (pc_q),
|
||||
.pc_valid_w (pc_valid),
|
||||
.status (ms_status)
|
||||
);
|
||||
|
||||
always #(CLK_PERIOD/2.0) clk = ~clk;
|
||||
|
||||
// -------- Cycle counter + first-event capture --------
|
||||
integer cycle_count;
|
||||
integer first_ddc_cycle;
|
||||
integer first_mem_request_cycle;
|
||||
integer first_pc_valid_cycle;
|
||||
integer pc_out_count;
|
||||
reg saw_ddc, saw_mem_req, saw_pc;
|
||||
|
||||
// -------- Output capture for peak detection --------
|
||||
reg signed [15:0] cap_i [0:FFT_SIZE-1];
|
||||
reg signed [15:0] cap_q [0:FFT_SIZE-1];
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!reset_n) begin
|
||||
cycle_count <= 0;
|
||||
saw_ddc <= 0;
|
||||
saw_mem_req <= 0;
|
||||
saw_pc <= 0;
|
||||
pc_out_count <= 0;
|
||||
first_ddc_cycle <= 0;
|
||||
first_mem_request_cycle <= 0;
|
||||
first_pc_valid_cycle <= 0;
|
||||
end else begin
|
||||
cycle_count <= cycle_count + 1;
|
||||
|
||||
if (ddc_valid && !saw_ddc) begin
|
||||
first_ddc_cycle <= cycle_count;
|
||||
saw_ddc <= 1;
|
||||
$display("[T=%0t] FIRST ddc_valid at cycle %0d", $time, cycle_count);
|
||||
end
|
||||
if (mem_request && !saw_mem_req) begin
|
||||
first_mem_request_cycle <= cycle_count;
|
||||
saw_mem_req <= 1;
|
||||
$display("[T=%0t] FIRST mem_request at cycle %0d", $time, cycle_count);
|
||||
end
|
||||
if (pc_valid) begin
|
||||
if (!saw_pc) begin
|
||||
first_pc_valid_cycle <= cycle_count;
|
||||
saw_pc <= 1;
|
||||
$display("[T=%0t] FIRST pc_valid at cycle %0d", $time, cycle_count);
|
||||
end
|
||||
if (pc_out_count < FFT_SIZE) begin
|
||||
cap_i[pc_out_count] <= pc_i;
|
||||
cap_q[pc_out_count] <= pc_q;
|
||||
pc_out_count <= pc_out_count + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// -------- Stimulus arrays — load same short-chirp values that loader will serve --------
|
||||
reg [15:0] stim_chirp_i [0:SHORT_LEN-1];
|
||||
reg [15:0] stim_chirp_q [0:SHORT_LEN-1];
|
||||
|
||||
integer k;
|
||||
|
||||
task feed_short_chirp_signal;
|
||||
// Drive ddc with the chirp samples (autocorrelation: signal == ref).
|
||||
// Multi_segment will buffer them and zero-pad to FFT_SIZE.
|
||||
integer j;
|
||||
begin
|
||||
for (j = 0; j < SHORT_LEN; j = j + 1) begin
|
||||
ddc_i <= {{2{stim_chirp_i[j][15]}}, stim_chirp_i[j]}; // sign-ext to 18b
|
||||
ddc_q <= {{2{stim_chirp_q[j][15]}}, stim_chirp_q[j]};
|
||||
ddc_valid <= 1'b1;
|
||||
@(posedge clk);
|
||||
end
|
||||
ddc_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// -------- Peak finding --------
|
||||
integer peak_bin;
|
||||
integer peak_abs;
|
||||
integer mean_abs;
|
||||
integer abs_val;
|
||||
integer total_abs;
|
||||
|
||||
task find_peak;
|
||||
integer kk;
|
||||
integer val_i, val_q;
|
||||
begin
|
||||
peak_bin = 0;
|
||||
peak_abs = 0;
|
||||
total_abs = 0;
|
||||
for (kk = 0; kk < FFT_SIZE; kk = kk + 1) begin
|
||||
val_i = $signed(cap_i[kk]);
|
||||
val_q = $signed(cap_q[kk]);
|
||||
abs_val = (val_i < 0 ? -val_i : val_i)
|
||||
+ (val_q < 0 ? -val_q : val_q);
|
||||
total_abs = total_abs + abs_val;
|
||||
if (abs_val > peak_abs) begin
|
||||
peak_abs = abs_val;
|
||||
peak_bin = kk;
|
||||
end
|
||||
end
|
||||
mean_abs = total_abs / FFT_SIZE;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
$dumpfile("tb_rxb_fullchain_latency.vcd");
|
||||
$dumpvars(0, tb_rxb_fullchain_latency);
|
||||
|
||||
clk = 0;
|
||||
reset_n = 0;
|
||||
ddc_i = 0;
|
||||
ddc_q = 0;
|
||||
ddc_valid = 0;
|
||||
use_long_chirp = 1'b0; // use SHORT chirp path so loader uses short_chirp_*.mem
|
||||
chirp_counter = 6'd0;
|
||||
mc_new_chirp = 1'b0;
|
||||
mc_new_elevation = 1'b0;
|
||||
mc_new_azimuth = 1'b0;
|
||||
|
||||
// Load the same short-chirp samples the loader will serve as ref,
|
||||
// so signal == ref → autocorrelation. Peak should be at bin 0 if
|
||||
// ref/signal alignment is correct.
|
||||
$readmemh("short_chirp_i.mem", stim_chirp_i, 0, SHORT_LEN-1);
|
||||
$readmemh("short_chirp_q.mem", stim_chirp_q, 0, SHORT_LEN-1);
|
||||
$display("[TB] Loaded %0d short-chirp samples for stimulus", SHORT_LEN);
|
||||
|
||||
repeat (8) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (8) @(posedge clk);
|
||||
|
||||
$display("\n=== RX-B Option A verification ===");
|
||||
$display("Configuration: latency_buffer REMOVED, ref direct-wired");
|
||||
$display("Path: chirp_memory_loader.ref_i ----> multi_segment.ref_chirp_real");
|
||||
$display("FFT_SIZE: %0d, SHORT_LEN: %0d", FFT_SIZE, SHORT_LEN);
|
||||
$display("");
|
||||
|
||||
// Pulse mc_new_chirp
|
||||
$display("[T=%0t] Pulsing mc_new_chirp HIGH...", $time);
|
||||
@(posedge clk);
|
||||
#1 mc_new_chirp = 1'b1;
|
||||
repeat (4) @(posedge clk);
|
||||
#1 mc_new_chirp = 1'b0;
|
||||
|
||||
// Feed signal samples (same as ref → autocorrelation)
|
||||
feed_short_chirp_signal;
|
||||
|
||||
// Wait for FFT_SIZE outputs (or timeout)
|
||||
for (k = 0; k < 200000; k = k + 1) begin
|
||||
@(posedge clk);
|
||||
if (pc_out_count >= FFT_SIZE) k = 200001;
|
||||
end
|
||||
|
||||
$display("\n=== TIMING ===");
|
||||
if (saw_ddc) $display("First ddc_valid : cycle %0d", first_ddc_cycle);
|
||||
if (saw_mem_req) $display("First mem_request : cycle %0d", first_mem_request_cycle);
|
||||
if (saw_pc) $display("First pc_valid : cycle %0d", first_pc_valid_cycle);
|
||||
$display("pc outputs captured: %0d / %0d", pc_out_count, FFT_SIZE);
|
||||
|
||||
if (pc_out_count >= FFT_SIZE) begin
|
||||
find_peak;
|
||||
$display("\n=== AUTOCORRELATION RESULT ===");
|
||||
$display("Peak bin : %0d", peak_bin);
|
||||
$display("Peak |I|+|Q| : %0d", peak_abs);
|
||||
$display("Mean |I|+|Q| : %0d", mean_abs);
|
||||
$display("Peak / mean ratio : ~%0dx",
|
||||
(mean_abs > 0) ? (peak_abs / mean_abs) : 0);
|
||||
$display("");
|
||||
// Run with the SYNTHESIS path (no +define+SIMULATION) to use
|
||||
// the production fft_engine.v — peak should be exactly at bin 0
|
||||
// with peak/mean > 50x for the autocorrelation case. The
|
||||
// SIMULATION path uses an inline behavioural FFT in
|
||||
// matched_filter_processing_chain.v with documented numerical
|
||||
// issues (peaks at non-zero bins, weak magnitudes); the
|
||||
// synthesis path is the production code.
|
||||
if (pc_out_count >= FFT_SIZE && peak_abs > 2 * mean_abs && peak_bin == 0) begin
|
||||
$display("[PASS] Frame 1 produces output, peak at bin 0, peak/mean ~%0dx",
|
||||
(mean_abs > 0) ? (peak_abs / mean_abs) : 0);
|
||||
$display(" RX-B fully fixed — latency_buffer removed + 1-FF align register.");
|
||||
end else if (pc_out_count >= FFT_SIZE && peak_abs > 2 * mean_abs) begin
|
||||
$display("[NEAR] Output present, peak/mean OK, but peak at bin %0d (not 0).",
|
||||
peak_bin);
|
||||
$display(" If running with +define+SIMULATION, this is the inline");
|
||||
$display(" behavioural FFT and is expected to fail. Run without it.");
|
||||
end else if (pc_out_count >= FFT_SIZE) begin
|
||||
$display("[FAIL] Output present but peak/mean too low — no real correlation.");
|
||||
end
|
||||
end else begin
|
||||
$display("\n=== TIMEOUT — chain did not produce all outputs ===");
|
||||
$display("ms_status=%b", ms_status);
|
||||
end
|
||||
|
||||
repeat (1000) @(posedge clk);
|
||||
$finish;
|
||||
end
|
||||
|
||||
initial begin
|
||||
#100000000; // 100 ms hard timeout
|
||||
$display("[ERROR] Hard simulation timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -0,0 +1,181 @@
|
||||
`timescale 1ns/1ps
|
||||
`include "radar_params.vh"
|
||||
|
||||
// ============================================================================
|
||||
// tb_rxb_latency_measure.v
|
||||
//
|
||||
// Purpose: empirically measure the pipeline latency of
|
||||
// matched_filter_processing_chain — cycles between the first ADC sample in
|
||||
// and the first range_profile_valid out — for both the long-chirp path
|
||||
// (3000 samples padded to FFT_SIZE) and the short-chirp path (50 samples
|
||||
// padded to FFT_SIZE).
|
||||
//
|
||||
// The measured latency is the value LATENCY in latency_buffer should
|
||||
// compensate for so that ref_chirp_real/imag arrive at the chain in the
|
||||
// SAME cycle as the corresponding adc_data_i/q.
|
||||
//
|
||||
// Note: matched_filter_multi_segment buffers BUFFER_SIZE=2048 samples
|
||||
// before emitting to the chain regardless of how many active samples are in
|
||||
// the chirp (zero-pads short chirps). So both paths feed the chain
|
||||
// FFT_SIZE samples — the chain itself sees no chirp-type difference. This
|
||||
// test confirms whether a single LATENCY value works for both.
|
||||
// ============================================================================
|
||||
|
||||
module tb_rxb_latency_measure;
|
||||
|
||||
localparam CLK_PERIOD = 10.0; // 100 MHz
|
||||
localparam FFT_SIZE = `RP_FFT_SIZE; // 2048
|
||||
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
reg signed [15:0] adc_data_i;
|
||||
reg signed [15:0] adc_data_q;
|
||||
reg adc_valid;
|
||||
reg [5:0] chirp_counter;
|
||||
reg signed [15:0] ref_chirp_real;
|
||||
reg signed [15:0] ref_chirp_imag;
|
||||
wire signed [15:0] range_profile_i;
|
||||
wire signed [15:0] range_profile_q;
|
||||
wire range_profile_valid;
|
||||
wire [3:0] chain_state;
|
||||
|
||||
matched_filter_processing_chain dut (
|
||||
.clk (clk),
|
||||
.reset_n (reset_n),
|
||||
.adc_data_i (adc_data_i),
|
||||
.adc_data_q (adc_data_q),
|
||||
.adc_valid (adc_valid),
|
||||
.chirp_counter (chirp_counter),
|
||||
.ref_chirp_real (ref_chirp_real),
|
||||
.ref_chirp_imag (ref_chirp_imag),
|
||||
.range_profile_i (range_profile_i),
|
||||
.range_profile_q (range_profile_q),
|
||||
.range_profile_valid (range_profile_valid),
|
||||
.chain_state (chain_state)
|
||||
);
|
||||
|
||||
always #(CLK_PERIOD/2.0) clk = ~clk;
|
||||
|
||||
// Measurement state
|
||||
integer cycle_in_first; // cycle when first adc_valid pulse went HIGH
|
||||
integer cycle_out_first; // cycle when first range_profile_valid went HIGH
|
||||
integer cycle_count;
|
||||
reg saw_first_in;
|
||||
reg saw_first_out;
|
||||
|
||||
always @(posedge clk) begin
|
||||
if (!reset_n) begin
|
||||
cycle_count <= 0;
|
||||
saw_first_in <= 0;
|
||||
saw_first_out <= 0;
|
||||
end else begin
|
||||
cycle_count <= cycle_count + 1;
|
||||
if (adc_valid && !saw_first_in) begin
|
||||
cycle_in_first <= cycle_count;
|
||||
saw_first_in <= 1;
|
||||
$display("[T=%0t] FIRST adc_valid=1 at cycle %0d", $time, cycle_count);
|
||||
end
|
||||
if (range_profile_valid && !saw_first_out) begin
|
||||
cycle_out_first <= cycle_count;
|
||||
saw_first_out <= 1;
|
||||
$display("[T=%0t] FIRST range_profile_valid=1 at cycle %0d", $time, cycle_count);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// Stimulus
|
||||
integer k;
|
||||
integer pipeline_latency;
|
||||
|
||||
task feed_unit_chirp(input integer n_active_samples);
|
||||
// Feed FFT_SIZE samples: first n_active_samples are unit-impulse chirp
|
||||
// (1 at sample 0, 0 elsewhere) — represents a maximally simple input.
|
||||
// Both adc and ref get the same impulse for autocorrelation.
|
||||
integer j;
|
||||
begin
|
||||
for (j = 0; j < FFT_SIZE; j = j + 1) begin
|
||||
if (j == 0) begin
|
||||
adc_data_i <= 16'sd16384; // ~half full-scale
|
||||
adc_data_q <= 16'sd0;
|
||||
ref_chirp_real <= 16'sd16384;
|
||||
ref_chirp_imag <= 16'sd0;
|
||||
end else begin
|
||||
adc_data_i <= 16'sd0;
|
||||
adc_data_q <= 16'sd0;
|
||||
ref_chirp_real <= 16'sd0;
|
||||
ref_chirp_imag <= 16'sd0;
|
||||
end
|
||||
adc_valid <= 1'b1;
|
||||
@(posedge clk);
|
||||
end
|
||||
adc_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
initial begin
|
||||
$dumpfile("tb_rxb_latency_measure.vcd");
|
||||
$dumpvars(0, tb_rxb_latency_measure);
|
||||
|
||||
clk = 0;
|
||||
reset_n = 0;
|
||||
adc_data_i = 0;
|
||||
adc_data_q = 0;
|
||||
adc_valid = 0;
|
||||
chirp_counter = 6'd0;
|
||||
ref_chirp_real = 0;
|
||||
ref_chirp_imag = 0;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (4) @(posedge clk);
|
||||
|
||||
$display("\n=== RX-B latency measurement: chain pipeline depth ===");
|
||||
$display("FFT_SIZE = %0d", FFT_SIZE);
|
||||
$display("Feeding 2048-sample unit-impulse autocorrelation frame...");
|
||||
|
||||
// Two runs: short chirp (50 active) and long chirp (3000 active).
|
||||
// The chain itself is chirp-agnostic (always processes FFT_SIZE=2048
|
||||
// samples) — multi_segment upstream zero-pads — so both should give
|
||||
// identical chain latency. Confirms whether prior review's claim of
|
||||
// "different LATENCY for short chirp" is real or a misconception.
|
||||
feed_unit_chirp(50); // active samples; multi_segment zero-pads upstream
|
||||
|
||||
// Wait for output to start (poll every cycle, abort if too long)
|
||||
for (k = 0; k < 60000; k = k + 1) begin
|
||||
@(posedge clk);
|
||||
if (saw_first_out) k = 60001; // exit
|
||||
end
|
||||
|
||||
if (saw_first_out) begin
|
||||
pipeline_latency = cycle_out_first - cycle_in_first;
|
||||
$display("\n=== RESULT ===");
|
||||
$display("First adc_valid : cycle %0d", cycle_in_first);
|
||||
$display("First valid output : cycle %0d", cycle_out_first);
|
||||
$display("Pipeline latency : %0d cycles", pipeline_latency);
|
||||
$display("Current LATENCY in latency_buffer: 3187 cycles");
|
||||
$display("Delta (measured - configured): %0d cycles", pipeline_latency - 3187);
|
||||
$display("");
|
||||
$display("Interpretation:");
|
||||
$display(" - If delta is near 0, LATENCY=3187 is correct.");
|
||||
$display(" - Note: this measures only the chain's internal pipeline.");
|
||||
$display(" Full LATENCY also accounts for upstream multi_segment buffer fill.");
|
||||
end else begin
|
||||
$display("\n=== TIMEOUT ===");
|
||||
$display("range_profile_valid never asserted within 60000 cycles");
|
||||
$display("(behavioural FFT model in fft_engine.v may be much slower than");
|
||||
$display(" Xilinx FFT IP — try Vivado simulation for accurate timing)");
|
||||
end
|
||||
|
||||
// Wait a bit more to see if we get full 2048 outputs
|
||||
repeat (5000) @(posedge clk);
|
||||
$finish;
|
||||
end
|
||||
|
||||
// Safety timeout
|
||||
initial begin
|
||||
#10000000; // 10 ms simulated time
|
||||
$display("[ERROR] Simulation timeout at 10 ms");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
Reference in New Issue
Block a user