fix(fpga): correct USB frame-sync counter for 512x32 cell grid

usb_data_interface.v NUM_CELLS was still 12'd2048 (64 range x 32 doppler)
from the pre-2048-FFT architecture. With 512 range bins x 32 Doppler, the
12-bit counter wrapped every 2048 packets and the host received 8 false
frame-start markers per real frame via the sample_counter==0 bit packed
into the detection byte. Widen counter to 15 bits and set NUM_CELLS to
16384. Sister file usb_data_interface_ft2232h.v was already correct.

Remove three stale testbenches hardcoded to the old 1024-pt / 64-bin
architecture (tb_mf_chain_synth, tb_fullchain_mti_cfar_realdata,
tb_range_fft_realdata). Equivalent current-architecture coverage already
exists in tb_matched_filter_processing_chain, tb_fullchain_realdata,
tb_fft_engine, tb_multiseg_cosim, and tb_mf_cosim.
This commit is contained in:
Jason
2026-04-22 15:44:48 +05:45
parent f39a78cb1e
commit 21aaa5ac33
4 changed files with 8 additions and 1481 deletions

View File

@@ -1,671 +0,0 @@
`timescale 1ns / 1ps
/**
* tb_fullchain_mti_cfar_realdata.v
*
* ============================================================================
* DEPRECATED STALE FOR 2048-PT ARCHITECTURE (integration/fft-2048-on-p0)
* ----------------------------------------------------------------------------
* Hard-coded for INPUT_BINS=1024 / RANGE_BINS=64 / DECIM_FACTOR=16. Production
* pipeline is now 2048-pt range FFT -> 512 range bins (DECIM=4). Re-enabling
* this TB without regenerating Python goldens (golden_reference.py and the
* fullchain_*.hex files) would feed mis-sized data into current RTL and pass
* on nonsense. Do NOT wire into run_regression.sh until rewritten.
*
* Runtime guard ($fatal at startup) below prevents silent CI resurrection.
* ============================================================================
*
* Full-chain co-simulation testbench: feeds real ADI CN0566 radar data
* (post-range-FFT, 32 chirps x 1024 bins) through the complete signal
* processing pipeline:
*
* range_bin_decimator (peak detection, 1024->64)
* -> mti_canceller (2-pulse, mti_enable=1)
* -> doppler_processor_optimized (Hamming + dual 16-pt FFT)
* -> DC notch filter (width=2, inline logic)
* -> cfar_ca (CA mode, guard=2, train=8, alpha=0x30)
*
* and compares outputs bit-for-bit against the Python golden reference
* (golden_reference.py) at multiple checkpoints:
*
* Checkpoint 1: Decimator output matches
* Checkpoint 2: MTI canceller output matches
* Checkpoint 3: Doppler output (post-DC-notch) matches
* Checkpoint 4: CFAR magnitudes match
* Checkpoint 5: CFAR thresholds match
* Checkpoint 6: CFAR detection flags match
*
* Stimulus:
* tb/cosim/real_data/hex/fullchain_range_input.hex
* 32768 x 32-bit packed {Q[31:16], I[15:0]} -- 32 chirps x 1024 bins
*
* Golden reference files:
* fullchain_mti_ref_i.hex, fullchain_mti_ref_q.hex -- MTI output (2048)
* fullchain_notched_ref_i.hex, fullchain_notched_ref_q.hex -- DC-notched Doppler (2048)
* fullchain_cfar_mag.hex -- CFAR magnitudes (2048)
* fullchain_cfar_thr.hex -- CFAR thresholds (2048)
* fullchain_cfar_det.hex -- CFAR detection flags (2048)
*
* Pass criteria: ALL outputs match exactly.
*
* Compile:
* iverilog -Wall -DSIMULATION -g2012 \
* -o tb/tb_fullchain_mti_cfar_realdata.vvp \
* tb/tb_fullchain_mti_cfar_realdata.v \
* range_bin_decimator.v mti_canceller.v doppler_processor.v \
* xfft_16.v fft_engine.v cfar_ca.v
*
* Run from: 9_Firmware/9_2_FPGA/
* vvp tb/tb_fullchain_mti_cfar_realdata.vvp
*/
module tb_fullchain_mti_cfar_realdata;
// ============================================================================
// PARAMETERS
// ============================================================================
localparam CLK_PERIOD = 10.0; // 100 MHz
localparam DOPPLER_FFT = 32;
localparam RANGE_BINS = 64;
localparam CHIRPS = 32;
localparam INPUT_BINS = 1024;
localparam DECIM_FACTOR = 16;
localparam TOTAL_INPUT_SAMPLES = CHIRPS * INPUT_BINS; // 32768
localparam TOTAL_MTI_SAMPLES = CHIRPS * RANGE_BINS; // 2048
localparam TOTAL_DOPPLER_SAMPLES = RANGE_BINS * DOPPLER_FFT; // 2048
localparam TOTAL_CFAR_CELLS = RANGE_BINS * DOPPLER_FFT; // 2048
// Generous timeout: decimator + MTI + Doppler + CFAR processing
localparam MAX_CYCLES = 3_000_000;
// DC notch width for this test
localparam DC_NOTCH_WIDTH = 3'd2;
// ============================================================================
// CLOCK AND RESET
// ============================================================================
reg clk;
reg reset_n;
initial clk = 0;
always #(CLK_PERIOD / 2) clk = ~clk;
// ============================================================================
// DECIMATOR SIGNALS
// ============================================================================
reg signed [15:0] decim_i_in;
reg signed [15:0] decim_q_in;
reg decim_valid_in;
wire signed [15:0] decim_i_out;
wire signed [15:0] decim_q_out;
wire decim_valid_out;
wire [5:0] decim_bin_index;
// ============================================================================
// MTI CANCELLER SIGNALS
// ============================================================================
wire signed [15:0] mti_i_out;
wire signed [15:0] mti_q_out;
wire mti_valid_out;
wire [5:0] mti_bin_out;
wire mti_first_chirp;
// ============================================================================
// DOPPLER SIGNALS
// ============================================================================
// Wire MTI output into Doppler input (matching RTL system_top)
wire [31:0] range_data_32bit;
wire range_data_valid;
assign range_data_32bit = {mti_q_out, mti_i_out};
assign range_data_valid = mti_valid_out;
reg new_chirp_frame;
wire [31:0] doppler_output;
wire doppler_valid;
wire [4:0] doppler_bin;
wire [5:0] range_bin;
wire processing_active;
wire frame_complete;
wire [3:0] dut_status;
// ============================================================================
// DC NOTCH FILTER SIGNALS (inline, replicating radar_system_top.v logic)
// ============================================================================
wire [4:0] dop_bin_unsigned;
wire dc_notch_active;
wire [31:0] notched_doppler_data;
wire notched_doppler_valid;
wire [4:0] notched_doppler_bin;
wire [5:0] notched_range_bin;
assign dop_bin_unsigned = doppler_bin;
assign dc_notch_active = (DC_NOTCH_WIDTH != 3'd0) &&
(dop_bin_unsigned < {2'b0, DC_NOTCH_WIDTH} ||
dop_bin_unsigned > (5'd31 - {2'b0, DC_NOTCH_WIDTH} + 5'd1));
assign notched_doppler_data = dc_notch_active ? 32'd0 : doppler_output;
assign notched_doppler_valid = doppler_valid;
assign notched_doppler_bin = doppler_bin;
assign notched_range_bin = range_bin;
// ============================================================================
// CFAR SIGNALS
// ============================================================================
wire cfar_detect_flag;
wire cfar_detect_valid;
wire [5:0] cfar_detect_range;
wire [4:0] cfar_detect_doppler;
wire [16:0] cfar_detect_magnitude;
wire [16:0] cfar_detect_threshold;
wire [15:0] cfar_detect_count;
wire cfar_busy;
wire [7:0] cfar_status_w;
// ============================================================================
// DUT INSTANTIATION: Range Bin Decimator
// ============================================================================
range_bin_decimator #(
.INPUT_BINS(INPUT_BINS),
.OUTPUT_BINS(RANGE_BINS),
.DECIMATION_FACTOR(DECIM_FACTOR)
) range_decim (
.clk(clk),
.reset_n(reset_n),
.range_i_in(decim_i_in),
.range_q_in(decim_q_in),
.range_valid_in(decim_valid_in),
.range_i_out(decim_i_out),
.range_q_out(decim_q_out),
.range_valid_out(decim_valid_out),
.range_bin_index(decim_bin_index),
.decimation_mode(2'b01), // Peak detection mode
.start_bin(10'd0),
.watchdog_timeout()
);
// ============================================================================
// DUT INSTANTIATION: MTI Canceller
// ============================================================================
mti_canceller #(
.NUM_RANGE_BINS(RANGE_BINS),
.DATA_WIDTH(16)
) mti_inst (
.clk(clk),
.reset_n(reset_n),
.range_i_in(decim_i_out),
.range_q_in(decim_q_out),
.range_valid_in(decim_valid_out),
.range_bin_in(decim_bin_index),
.range_i_out(mti_i_out),
.range_q_out(mti_q_out),
.range_valid_out(mti_valid_out),
.range_bin_out(mti_bin_out),
.mti_enable(1'b1), // MTI always enabled for this test
.use_long_chirp(1'b0), // homogeneous-waveform stimulus in this TB
.mti_first_chirp(mti_first_chirp)
);
// ============================================================================
// DUT INSTANTIATION: Doppler Processor
// ============================================================================
doppler_processor_optimized doppler_proc (
.clk(clk),
.reset_n(reset_n),
.range_data(range_data_32bit),
.data_valid(range_data_valid),
.new_chirp_frame(new_chirp_frame),
.doppler_output(doppler_output),
.doppler_valid(doppler_valid),
.doppler_bin(doppler_bin),
.range_bin(range_bin),
.processing_active(processing_active),
.frame_complete(frame_complete),
.status(dut_status)
);
// ============================================================================
// DUT INSTANTIATION: CFAR Detector
// ============================================================================
cfar_ca cfar_inst (
.clk(clk),
.reset_n(reset_n),
.doppler_data(notched_doppler_data),
.doppler_valid(notched_doppler_valid),
.doppler_bin_in(notched_doppler_bin),
.range_bin_in(notched_range_bin),
.frame_complete(frame_complete),
.cfg_guard_cells(4'd2),
.cfg_train_cells(5'd8),
.cfg_alpha(8'h30), // Q4.4 = 3.0
.cfg_cfar_mode(2'b00), // CA-CFAR
.cfg_cfar_enable(1'b1), // CFAR enabled
.cfg_simple_threshold(16'd500),
.detect_flag(cfar_detect_flag),
.detect_valid(cfar_detect_valid),
.detect_range(cfar_detect_range),
.detect_doppler(cfar_detect_doppler),
.detect_magnitude(cfar_detect_magnitude),
.detect_threshold(cfar_detect_threshold),
.detect_count(cfar_detect_count),
.cfar_busy(cfar_busy),
.cfar_status(cfar_status_w)
);
// Internal DUT state (for debug)
wire [2:0] decim_state = range_decim.state;
wire [2:0] doppler_state = doppler_proc.state;
// ============================================================================
// INPUT DATA MEMORY (loaded from hex file)
// ============================================================================
reg [31:0] input_mem [0:TOTAL_INPUT_SAMPLES-1];
initial begin
$readmemh("tb/cosim/real_data/hex/fullchain_range_input.hex", input_mem);
end
// ============================================================================
// REFERENCE DATA (loaded from hex files)
// ============================================================================
// MTI reference: 2048 x 16-bit signed (32 chirps x 64 bins, row-major)
reg signed [15:0] ref_mti_i [0:TOTAL_MTI_SAMPLES-1];
reg signed [15:0] ref_mti_q [0:TOTAL_MTI_SAMPLES-1];
// DC-notched Doppler reference: 2048 x 16-bit signed (64 range x 32 Doppler)
reg signed [15:0] ref_notched_i [0:TOTAL_DOPPLER_SAMPLES-1];
reg signed [15:0] ref_notched_q [0:TOTAL_DOPPLER_SAMPLES-1];
// CFAR reference: magnitude, threshold, detection flags
reg [16:0] ref_cfar_mag [0:TOTAL_CFAR_CELLS-1];
reg [16:0] ref_cfar_thr [0:TOTAL_CFAR_CELLS-1];
reg [0:0] ref_cfar_det [0:TOTAL_CFAR_CELLS-1];
initial begin
$readmemh("tb/cosim/real_data/hex/fullchain_mti_ref_i.hex", ref_mti_i);
$readmemh("tb/cosim/real_data/hex/fullchain_mti_ref_q.hex", ref_mti_q);
$readmemh("tb/cosim/real_data/hex/fullchain_notched_ref_i.hex", ref_notched_i);
$readmemh("tb/cosim/real_data/hex/fullchain_notched_ref_q.hex", ref_notched_q);
$readmemh("tb/cosim/real_data/hex/fullchain_cfar_mag.hex", ref_cfar_mag);
$readmemh("tb/cosim/real_data/hex/fullchain_cfar_thr.hex", ref_cfar_thr);
$readmemh("tb/cosim/real_data/hex/fullchain_cfar_det.hex", ref_cfar_det);
end
// ============================================================================
// MTI OUTPUT CAPTURE
// ============================================================================
integer mti_out_count;
reg signed [15:0] mti_cap_i [0:TOTAL_MTI_SAMPLES-1];
reg signed [15:0] mti_cap_q [0:TOTAL_MTI_SAMPLES-1];
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
mti_out_count <= 0;
end else if (mti_valid_out) begin
if (mti_out_count < TOTAL_MTI_SAMPLES) begin
mti_cap_i[mti_out_count] <= mti_i_out;
mti_cap_q[mti_out_count] <= mti_q_out;
end
mti_out_count <= mti_out_count + 1;
end
end
// ============================================================================
// DOPPLER OUTPUT CAPTURE (post DC-notch)
// ============================================================================
reg signed [15:0] dop_cap_i [0:TOTAL_DOPPLER_SAMPLES-1];
reg signed [15:0] dop_cap_q [0:TOTAL_DOPPLER_SAMPLES-1];
reg [5:0] dop_cap_rbin [0:TOTAL_DOPPLER_SAMPLES-1];
reg [4:0] dop_cap_dbin [0:TOTAL_DOPPLER_SAMPLES-1];
integer dop_out_count;
// ============================================================================
// CFAR OUTPUT CAPTURE
// ============================================================================
reg [16:0] cfar_cap_mag [0:TOTAL_CFAR_CELLS-1];
reg [16:0] cfar_cap_thr [0:TOTAL_CFAR_CELLS-1];
reg [0:0] cfar_cap_det [0:TOTAL_CFAR_CELLS-1];
reg [5:0] cfar_cap_rbin [0:TOTAL_CFAR_CELLS-1];
reg [4:0] cfar_cap_dbin [0:TOTAL_CFAR_CELLS-1];
integer cfar_out_count;
integer cfar_det_count;
// ============================================================================
// PASS / FAIL TRACKING
// ============================================================================
integer pass_count, fail_count, test_count;
task check;
input cond;
input [511:0] label;
begin
test_count = test_count + 1;
if (cond) begin
pass_count = pass_count + 1;
end else begin
$display(" [FAIL] %0s", label);
fail_count = fail_count + 1;
end
end
endtask
// ============================================================================
// MAIN TEST SEQUENCE
// ============================================================================
integer i, chirp, sample_idx, cycle_count;
integer n_exact, n_within_tol;
integer max_err_i, max_err_q;
integer abs_diff_i, abs_diff_q;
reg signed [31:0] diff_i, diff_q;
integer mismatches_printed;
reg [31:0] packed_iq;
integer cfar_ref_idx;
integer cfar_mag_mismatches, cfar_thr_mismatches, cfar_det_mismatches;
initial begin
`ifndef ALLOW_STALE_TB_FULLCHAIN_MTI_CFAR
// DEPRECATED guard see file header. Stale 1024-bin constants against
// 2048-pt production RTL. Define ALLOW_STALE_TB_FULLCHAIN_MTI_CFAR only
// for archaeology; this TB does not validate current pipeline.
$display("ERROR: tb_fullchain_mti_cfar_realdata.v DEPRECATED (1024-bin). See header.");
$fatal;
`endif
// ---- Init ----
pass_count = 0;
fail_count = 0;
test_count = 0;
dop_out_count = 0;
cfar_out_count = 0;
cfar_det_count = 0;
decim_i_in = 0;
decim_q_in = 0;
decim_valid_in = 0;
new_chirp_frame = 0;
reset_n = 0;
// ---- Reset ----
#(CLK_PERIOD * 10);
reset_n = 1;
#(CLK_PERIOD * 5);
$display("============================================================");
$display(" Full-Chain Real-Data Co-Simulation (MTI + CFAR)");
$display(" range_bin_decimator (peak, 1024->64)");
$display(" -> mti_canceller (2-pulse, enable=1)");
$display(" -> doppler_processor_optimized (Hamming + dual 16-pt FFT)");
$display(" -> DC notch filter (width=%0d)", DC_NOTCH_WIDTH);
$display(" -> cfar_ca (CA, guard=2, train=8, alpha=0x30)");
$display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW");
$display(" Input: %0d chirps x %0d range FFT bins = %0d samples", CHIRPS, INPUT_BINS, TOTAL_INPUT_SAMPLES);
$display(" Expected: %0d MTI outputs, %0d Doppler outputs, %0d CFAR cells", TOTAL_MTI_SAMPLES, TOTAL_DOPPLER_SAMPLES, TOTAL_CFAR_CELLS);
$display("============================================================");
// ---- Debug: check hex files loaded ----
$display(" input_mem[0] = %08h", input_mem[0]);
$display(" input_mem[32767] = %08h", input_mem[32767]);
$display(" ref_mti_i[0]=%04h, ref_mti_q[0]=%04h", ref_mti_i[0], ref_mti_q[0]);
$display(" ref_notched_i[0]=%04h, ref_notched_q[0]=%04h", ref_notched_i[0], ref_notched_q[0]);
$display(" ref_cfar_mag[0]=%05h, ref_cfar_thr[0]=%05h, ref_cfar_det[0]=%01h", ref_cfar_mag[0], ref_cfar_thr[0], ref_cfar_det[0]);
// ---- Check 1: DUTs start in expected states ----
check(decim_state == 3'd0, "Decimator starts in ST_IDLE");
check(doppler_state == 3'b000, "Doppler starts in S_IDLE");
// ---- Pulse new_chirp_frame to start Doppler accumulation ----
@(posedge clk);
new_chirp_frame <= 1;
@(posedge clk);
@(posedge clk);
new_chirp_frame <= 0;
@(posedge clk);
// ---- Feed input data: 32 chirps x 1024 range bins ----
$display("\n--- Feeding %0d chirps x %0d bins = %0d samples ---", CHIRPS, INPUT_BINS, TOTAL_INPUT_SAMPLES);
for (chirp = 0; chirp < CHIRPS; chirp = chirp + 1) begin
for (i = 0; i < INPUT_BINS; i = i + 1) begin
@(posedge clk);
sample_idx = chirp * INPUT_BINS + i;
packed_iq = input_mem[sample_idx];
decim_i_in <= packed_iq[15:0];
decim_q_in <= packed_iq[31:16];
decim_valid_in <= 1;
end
@(posedge clk);
decim_valid_in <= 0;
decim_i_in <= 0;
decim_q_in <= 0;
// Wait for decimator to return to IDLE
cycle_count = 0;
while (decim_state != 3'd0 && cycle_count < 200) begin
@(posedge clk);
cycle_count = cycle_count + 1;
end
if (chirp < 3 || chirp == CHIRPS - 1) begin
$display(" Chirp %0d: IDLE after %0d extra cycles, mti_out=%0d", chirp, cycle_count, mti_out_count);
end
end
// Allow a few extra cycles for the last MTI output to propagate
repeat (10) @(posedge clk);
$display(" All input fed. MTI outputs: %0d (expected %0d)", mti_out_count, TOTAL_MTI_SAMPLES);
// ---- Check: MTI produced correct number of outputs ----
check(mti_out_count == TOTAL_MTI_SAMPLES, "MTI output count == 2048");
// ---- Wait for Doppler processing to complete ----
$display("\n--- Waiting for Doppler to process and emit %0d outputs ---", TOTAL_DOPPLER_SAMPLES);
cycle_count = 0;
while (dop_out_count < TOTAL_DOPPLER_SAMPLES && cycle_count < MAX_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
if (doppler_valid) begin
// Capture DC-notched Doppler output
dop_cap_i[dop_out_count] = notched_doppler_data[15:0];
dop_cap_q[dop_out_count] = notched_doppler_data[31:16];
dop_cap_rbin[dop_out_count] = notched_range_bin;
dop_cap_dbin[dop_out_count] = notched_doppler_bin;
dop_out_count = dop_out_count + 1;
end
end
$display(" Collected %0d Doppler outputs in %0d cycles", dop_out_count, cycle_count);
check(dop_out_count == TOTAL_DOPPLER_SAMPLES, "Doppler output count == 2048");
check(cycle_count < MAX_CYCLES, "Doppler processing within timeout");
// ---- Wait for CFAR to complete ----
$display("\n--- Waiting for CFAR to process %0d cells ---", TOTAL_CFAR_CELLS);
cycle_count = 0;
while (cfar_out_count < TOTAL_CFAR_CELLS && cycle_count < MAX_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
if (cfar_detect_valid) begin
cfar_cap_mag[cfar_out_count] = cfar_detect_magnitude;
cfar_cap_thr[cfar_out_count] = cfar_detect_threshold;
cfar_cap_det[cfar_out_count] = cfar_detect_flag;
cfar_cap_rbin[cfar_out_count] = cfar_detect_range;
cfar_cap_dbin[cfar_out_count] = cfar_detect_doppler;
if (cfar_detect_flag) cfar_det_count = cfar_det_count + 1;
cfar_out_count = cfar_out_count + 1;
end
end
$display(" Collected %0d CFAR outputs in %0d cycles (%0d detections)", cfar_out_count, cycle_count, cfar_det_count);
check(cfar_out_count == TOTAL_CFAR_CELLS, "CFAR output count == 2048");
check(cycle_count < MAX_CYCLES, "CFAR processing within timeout");
// ==================================================================
// CHECKPOINT 1: MTI OUTPUT COMPARISON
// ==================================================================
$display("");
$display("--- Checkpoint 1: MTI canceller output vs golden reference ---");
max_err_i = 0;
max_err_q = 0;
n_exact = 0;
mismatches_printed = 0;
for (i = 0; i < TOTAL_MTI_SAMPLES; i = i + 1) begin
diff_i = mti_cap_i[i] - ref_mti_i[i];
diff_q = mti_cap_q[i] - ref_mti_q[i];
abs_diff_i = (diff_i < 0) ? -diff_i : diff_i;
abs_diff_q = (diff_q < 0) ? -diff_q : diff_q;
if (abs_diff_i > max_err_i) max_err_i = abs_diff_i;
if (abs_diff_q > max_err_q) max_err_q = abs_diff_q;
if (diff_i == 0 && diff_q == 0)
n_exact = n_exact + 1;
if ((abs_diff_i > 0 || abs_diff_q > 0) && mismatches_printed < 10) begin
$display(" [%4d] chirp=%0d bin=%0d RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)", i, i / RANGE_BINS, i % RANGE_BINS, $signed(mti_cap_i[i]), $signed(mti_cap_q[i]), $signed(ref_mti_i[i]), $signed(ref_mti_q[i]), diff_i, diff_q);
mismatches_printed = mismatches_printed + 1;
end
check(abs_diff_i == 0 && abs_diff_q == 0, "MTI output bin match");
end
$display(" MTI: exact=%0d/%0d, max_err I=%0d Q=%0d", n_exact, TOTAL_MTI_SAMPLES, max_err_i, max_err_q);
// ==================================================================
// CHECKPOINT 2: DC-NOTCHED DOPPLER OUTPUT COMPARISON
// ==================================================================
$display("");
$display("--- Checkpoint 2: DC-notched Doppler output vs golden reference ---");
max_err_i = 0;
max_err_q = 0;
n_exact = 0;
mismatches_printed = 0;
for (i = 0; i < TOTAL_DOPPLER_SAMPLES; i = i + 1) begin
diff_i = dop_cap_i[i] - ref_notched_i[i];
diff_q = dop_cap_q[i] - ref_notched_q[i];
abs_diff_i = (diff_i < 0) ? -diff_i : diff_i;
abs_diff_q = (diff_q < 0) ? -diff_q : diff_q;
if (abs_diff_i > max_err_i) max_err_i = abs_diff_i;
if (abs_diff_q > max_err_q) max_err_q = abs_diff_q;
if (diff_i == 0 && diff_q == 0)
n_exact = n_exact + 1;
if ((abs_diff_i > 0 || abs_diff_q > 0) && mismatches_printed < 10) begin
$display(" [%4d] rbin=%2d dbin=%2d RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)", i, dop_cap_rbin[i], dop_cap_dbin[i], $signed(dop_cap_i[i]), $signed(dop_cap_q[i]), $signed(ref_notched_i[i]), $signed(ref_notched_q[i]), diff_i, diff_q);
mismatches_printed = mismatches_printed + 1;
end
check(abs_diff_i == 0 && abs_diff_q == 0, "Notched Doppler output bin match");
end
$display(" Notched Doppler: exact=%0d/%0d, max_err I=%0d Q=%0d", n_exact, TOTAL_DOPPLER_SAMPLES, max_err_i, max_err_q);
// ==================================================================
// CHECKPOINT 3: CFAR MAGNITUDE, THRESHOLD, AND DETECTION COMPARISON
// ==================================================================
$display("");
$display("--- Checkpoint 3: CFAR output vs golden reference ---");
cfar_mag_mismatches = 0;
cfar_thr_mismatches = 0;
cfar_det_mismatches = 0;
mismatches_printed = 0;
// The CFAR outputs cells in Doppler-column order:
// column 0 (dbin=0): range bins 0..63
// column 1 (dbin=1): range bins 0..63
// ...
// But golden reference is in row-major order (rbin, dbin).
// We need to map CFAR output index to golden reference index.
for (i = 0; i < cfar_out_count; i = i + 1) begin
// CFAR output: range=cfar_cap_rbin[i], doppler=cfar_cap_dbin[i]
// Golden ref index: rbin * 32 + dbin (row-major)
cfar_ref_idx = cfar_cap_rbin[i] * DOPPLER_FFT + cfar_cap_dbin[i];
if (cfar_cap_mag[i] != ref_cfar_mag[cfar_ref_idx]) begin
cfar_mag_mismatches = cfar_mag_mismatches + 1;
if (mismatches_printed < 10) begin
$display(" MAG[%4d] rbin=%2d dbin=%2d RTL=%0d REF=%0d", i, cfar_cap_rbin[i], cfar_cap_dbin[i], cfar_cap_mag[i], ref_cfar_mag[cfar_ref_idx]);
mismatches_printed = mismatches_printed + 1;
end
end
if (cfar_cap_thr[i] != ref_cfar_thr[cfar_ref_idx]) begin
cfar_thr_mismatches = cfar_thr_mismatches + 1;
if (mismatches_printed < 10) begin
$display(" THR[%4d] rbin=%2d dbin=%2d RTL=%0d REF=%0d", i, cfar_cap_rbin[i], cfar_cap_dbin[i], cfar_cap_thr[i], ref_cfar_thr[cfar_ref_idx]);
mismatches_printed = mismatches_printed + 1;
end
end
if (cfar_cap_det[i] != ref_cfar_det[cfar_ref_idx]) begin
cfar_det_mismatches = cfar_det_mismatches + 1;
if (mismatches_printed < 10) begin
$display(" DET[%4d] rbin=%2d dbin=%2d RTL=%0d REF=%0d (mag=%0d thr=%0d)", i, cfar_cap_rbin[i], cfar_cap_dbin[i], cfar_cap_det[i], ref_cfar_det[cfar_ref_idx], cfar_cap_mag[i], cfar_cap_thr[i]);
mismatches_printed = mismatches_printed + 1;
end
end
end
// Per-cell pass/fail for CFAR
for (i = 0; i < cfar_out_count; i = i + 1) begin
cfar_ref_idx = cfar_cap_rbin[i] * DOPPLER_FFT + cfar_cap_dbin[i];
check(cfar_cap_mag[i] == ref_cfar_mag[cfar_ref_idx], "CFAR magnitude match");
check(cfar_cap_thr[i] == ref_cfar_thr[cfar_ref_idx], "CFAR threshold match");
check(cfar_cap_det[i] == ref_cfar_det[cfar_ref_idx], "CFAR detection flag match");
end
$display(" CFAR mag mismatches: %0d / %0d", cfar_mag_mismatches, cfar_out_count);
$display(" CFAR thr mismatches: %0d / %0d", cfar_thr_mismatches, cfar_out_count);
$display(" CFAR det mismatches: %0d / %0d", cfar_det_mismatches, cfar_out_count);
$display(" CFAR total detections: RTL=%0d", cfar_det_count);
// ==================================================================
// SUMMARY
// ==================================================================
$display("");
$display("============================================================");
$display(" SUMMARY: Full-Chain Real-Data Co-Simulation (MTI + CFAR)");
$display("============================================================");
$display(" Chain: decim(peak) -> MTI -> Doppler -> DC notch(w=%0d) -> CFAR(CA)", DC_NOTCH_WIDTH);
$display(" Input samples: %0d (%0d chirps x %0d bins)", TOTAL_INPUT_SAMPLES, CHIRPS, INPUT_BINS);
$display(" MTI outputs: %0d (expected %0d)", mti_out_count, TOTAL_MTI_SAMPLES);
$display(" Doppler outputs: %0d (expected %0d)", dop_out_count, TOTAL_DOPPLER_SAMPLES);
$display(" CFAR outputs: %0d (expected %0d)", cfar_out_count, TOTAL_CFAR_CELLS);
$display(" CFAR detections: %0d", cfar_det_count);
$display(" Pass: %0d Fail: %0d Total: %0d", pass_count, fail_count, test_count);
$display("============================================================");
if (fail_count == 0)
$display("RESULT: ALL TESTS PASSED (%0d/%0d)", pass_count, test_count);
else
$display("RESULT: %0d TESTS FAILED", fail_count);
$display("============================================================");
#(CLK_PERIOD * 10);
$finish;
end
// ============================================================================
// WATCHDOG
// ============================================================================
initial begin
#(CLK_PERIOD * MAX_CYCLES * 2);
$display("[TIMEOUT] Simulation exceeded %0d cycles -- aborting", MAX_CYCLES * 2);
$display("SOME TESTS FAILED");
$finish;
end
endmodule

View File

@@ -1,539 +0,0 @@
`timescale 1ns / 1ps
/**
* tb_mf_chain_synth.v
*
* ============================================================================
* DEPRECATED STALE FOR 2048-PT ARCHITECTURE (integration/fft-2048-on-p0)
* ----------------------------------------------------------------------------
* This testbench hard-codes FFT_SIZE=1024 and targets the old synthesis branch
* sized for a 1024-point FFT. Production RTL now uses 2048-pt range FFT
* (RP_FFT_SIZE=2048). Do NOT add this TB to run_regression.sh until it has
* been rewritten for 2048 samples running it as-is validates nothing against
* current RTL and will give false confidence.
*
* Build fails fast on compile to prevent accidental resurrection.
* ============================================================================
*
* Original description:
* Testbench for the SYNTHESIS branch of matched_filter_processing_chain.v.
* Compiled WITHOUT -DSIMULATION so the `else` (fft_engine) branch activates.
* Synthesis branch uses iterative fft_engine; ~40K+ cycles per frame.
*/
module tb_mf_chain_synth;
// Parameters
localparam CLK_PERIOD = 10.0; // 100 MHz
localparam FFT_SIZE = 1024;
// Timeout for full frame processing:
// 3 FFTs × ~12K cycles each + multiply ~1K + overhead 40K
// Use 200K for safety margin
localparam FRAME_TIMEOUT = 200000;
// Signals
reg clk;
reg reset_n;
reg [15:0] adc_data_i;
reg [15:0] adc_data_q;
reg adc_valid;
reg [5:0] chirp_counter;
reg [15:0] ref_chirp_real;
reg [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;
// Test bookkeeping
integer pass_count;
integer fail_count;
integer test_num;
integer i;
// Synthesis-branch states (mirror DUT)
localparam [3:0] ST_IDLE = 4'd0,
ST_COLLECT = 4'd1,
ST_SIG_FFT = 4'd2,
ST_SIG_CAP = 4'd3,
ST_REF_FFT = 4'd4,
ST_REF_CAP = 4'd5,
ST_MULTIPLY = 4'd6,
ST_INV_FFT = 4'd7,
ST_INV_CAP = 4'd8,
ST_OUTPUT = 4'd9,
ST_DONE = 4'd10;
// Concurrent output capture
integer cap_count;
reg cap_enable;
integer cap_max_abs;
integer cap_peak_bin;
integer cap_cur_abs;
// Output capture arrays
reg signed [15:0] cap_out_i [0:1023];
reg signed [15:0] cap_out_q [0:1023];
// Clock
always #(CLK_PERIOD/2) clk = ~clk;
// DUT
matched_filter_processing_chain uut (
.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)
);
// Concurrent output capture block
always @(posedge clk) begin
#1;
if (cap_enable && range_profile_valid) begin
if (cap_count < FFT_SIZE) begin
cap_out_i[cap_count] = range_profile_i;
cap_out_q[cap_count] = range_profile_q;
end
cap_cur_abs = (range_profile_i[15] ? -range_profile_i : range_profile_i)
+ (range_profile_q[15] ? -range_profile_q : range_profile_q);
if (cap_cur_abs > cap_max_abs) begin
cap_max_abs = cap_cur_abs;
cap_peak_bin = cap_count;
end
cap_count = cap_count + 1;
end
end
// Check task
task check;
input cond;
input [511:0] label;
begin
test_num = test_num + 1;
if (cond) begin
$display("[PASS] Test %0d: %0s", test_num, label);
pass_count = pass_count + 1;
end else begin
$display("[FAIL] Test %0d: %0s", test_num, label);
fail_count = fail_count + 1;
end
end
endtask
// Helper: apply reset
task apply_reset;
begin
reset_n = 0;
adc_valid = 0;
adc_data_i = 16'd0;
adc_data_q = 16'd0;
chirp_counter = 6'd0;
ref_chirp_real = 16'd0;
ref_chirp_imag = 16'd0;
cap_enable = 0;
cap_count = 0;
cap_max_abs = 0;
cap_peak_bin = -1;
repeat (4) @(posedge clk);
reset_n = 1;
@(posedge clk);
#1;
end
endtask
// Helper: start capture
task start_capture;
begin
cap_count = 0;
cap_max_abs = 0;
cap_peak_bin = -1;
cap_enable = 1;
end
endtask
// Helper: wait for IDLE with long timeout
task wait_for_idle;
integer wait_count;
begin
wait_count = 0;
while (chain_state != ST_IDLE && wait_count < FRAME_TIMEOUT) begin
@(posedge clk);
wait_count = wait_count + 1;
end
#1;
if (wait_count >= FRAME_TIMEOUT)
$display(" WARNING: wait_for_idle timed out at %0d cycles", wait_count);
end
endtask
// Helper: feed DC frame
task feed_dc_frame;
integer k;
begin
for (k = 0; k < FFT_SIZE; k = k + 1) begin
adc_data_i = 16'sh1000; // +4096
adc_data_q = 16'sh0000;
ref_chirp_real = 16'sh1000;
ref_chirp_imag = 16'sh0000;
adc_valid = 1'b1;
@(posedge clk);
#1;
end
adc_valid = 1'b0;
end
endtask
// Helper: feed tone frame (signal=reference=tone at bin)
task feed_tone_frame;
input integer tone_bin;
integer k;
real angle;
begin
for (k = 0; k < FFT_SIZE; k = k + 1) begin
angle = 6.28318530718 * tone_bin * k / (1.0 * FFT_SIZE);
adc_data_i = $rtoi(8000.0 * $cos(angle));
adc_data_q = $rtoi(8000.0 * $sin(angle));
ref_chirp_real = $rtoi(8000.0 * $cos(angle));
ref_chirp_imag = $rtoi(8000.0 * $sin(angle));
adc_valid = 1'b1;
@(posedge clk);
#1;
end
adc_valid = 1'b0;
end
endtask
// Helper: feed impulse frame (delta at sample 0)
task feed_impulse_frame;
integer k;
begin
for (k = 0; k < FFT_SIZE; k = k + 1) begin
if (k == 0) begin
adc_data_i = 16'sh4000; // 0.5 in Q15
adc_data_q = 16'sh0000;
ref_chirp_real = 16'sh4000;
ref_chirp_imag = 16'sh0000;
end else begin
adc_data_i = 16'sh0000;
adc_data_q = 16'sh0000;
ref_chirp_real = 16'sh0000;
ref_chirp_imag = 16'sh0000;
end
adc_valid = 1'b1;
@(posedge clk);
#1;
end
adc_valid = 1'b0;
end
endtask
// Stimulus
initial begin
$dumpfile("tb_mf_chain_synth.vcd");
$dumpvars(0, tb_mf_chain_synth);
`ifndef ALLOW_STALE_TB_MF_CHAIN_SYNTH
// DEPRECATED guard see file header. Refuse to run until rewritten
// for 2048-pt architecture. Define ALLOW_STALE_TB_MF_CHAIN_SYNTH to
// override (for archaeology only; does not validate current RTL).
$display("ERROR: tb_mf_chain_synth.v is DEPRECATED (1024-pt). See header.");
$fatal;
`endif
// Init
clk = 0;
pass_count = 0;
fail_count = 0;
test_num = 0;
cap_enable = 0;
cap_count = 0;
cap_max_abs = 0;
cap_peak_bin = -1;
//
// TEST GROUP 1: Reset behaviour
//
$display("\n--- Test Group 1: Reset Behaviour ---");
apply_reset;
reset_n = 0;
repeat (4) @(posedge clk); #1;
check(range_profile_valid === 1'b0, "range_profile_valid=0 during reset");
check(chain_state === ST_IDLE, "chain_state=IDLE during reset");
reset_n = 1;
@(posedge clk); #1;
//
// TEST GROUP 2: No valid input stays IDLE
//
$display("\n--- Test Group 2: No Valid Input → Stays IDLE ---");
apply_reset;
repeat (100) @(posedge clk);
#1;
check(chain_state === ST_IDLE, "Stays in IDLE with no valid input");
check(range_profile_valid === 1'b0, "No output when no input");
//
// TEST GROUP 3: DC frame state transitions and output count
//
$display("\n--- Test Group 3: DC Frame — Full Processing ---");
apply_reset;
start_capture;
feed_dc_frame;
$display(" Waiting for processing (3 FFTs + multiply)...");
wait_for_idle;
cap_enable = 0;
$display(" Output count: %0d (expected %0d)", cap_count, FFT_SIZE);
$display(" Peak bin: %0d, magnitude: %0d", cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "DC: Outputs exactly 1024 range profile samples");
check(chain_state === ST_IDLE, "DC: Returns to IDLE after frame");
// DC autocorrelation: FFT of DC = energy at bin 0 only
// conj multiply = |bin0|^2 at bin 0, zeros elsewhere
// IFFT of single bin = constant => peak at bin 0 (or any bin since all equal)
// With Q15 truncation, expect non-zero output
check(cap_max_abs > 0, "DC: Non-zero output");
//
// TEST GROUP 4: Zero input zero output
//
$display("\n--- Test Group 4: Zero Input → Zero Output ---");
apply_reset;
start_capture;
for (i = 0; i < FFT_SIZE; i = i + 1) begin
adc_data_i = 16'd0;
adc_data_q = 16'd0;
ref_chirp_real = 16'd0;
ref_chirp_imag = 16'd0;
adc_valid = 1'b1;
@(posedge clk); #1;
end
adc_valid = 1'b0;
wait_for_idle;
cap_enable = 0;
$display(" Output count: %0d", cap_count);
$display(" Max magnitude: %0d", cap_max_abs);
check(cap_count == FFT_SIZE, "Zero: Got 1024 output samples");
// Allow small rounding noise (fft_engine Q15 rounding can produce ±1)
check(cap_max_abs <= 2, "Zero: Output magnitude <= 2 (near zero)");
//
// TEST GROUP 5: Tone autocorrelation (bin 5)
// signal = reference = tone at bin 5
// Autocorrelation peak at bin 0 (time lag 0)
//
$display("\n--- Test Group 5: Tone Autocorrelation (bin 5) ---");
apply_reset;
start_capture;
feed_tone_frame(5);
$display(" Waiting for processing...");
wait_for_idle;
cap_enable = 0;
$display(" Output count: %0d", cap_count);
$display(" Peak bin: %0d, magnitude: %0d", cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "Tone: Got 1024 output samples");
// Autocorrelation of a pure tone: peak at bin 0
check(cap_peak_bin <= 5 || cap_peak_bin >= FFT_SIZE - 5,
"Tone: Autocorrelation peak near bin 0");
check(cap_max_abs > 0, "Tone: Peak magnitude > 0");
//
// TEST GROUP 6: Impulse autocorrelation
//
$display("\n--- Test Group 6: Impulse Autocorrelation ---");
apply_reset;
start_capture;
feed_impulse_frame;
$display(" Waiting for processing...");
wait_for_idle;
cap_enable = 0;
$display(" Output count: %0d", cap_count);
$display(" Peak bin: %0d, magnitude: %0d", cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "Impulse: Got 1024 output samples");
check(cap_max_abs > 0, "Impulse: Non-zero output");
check(chain_state === ST_IDLE, "Impulse: Returns to IDLE");
//
// TEST GROUP 7: Reset mid-operation
//
$display("\n--- Test Group 7: Reset Mid-Operation ---");
apply_reset;
// Feed ~512 samples (halfway through collection)
for (i = 0; i < 512; i = i + 1) begin
adc_data_i = 16'sh1000;
adc_data_q = 16'sh0000;
ref_chirp_real = 16'sh1000;
ref_chirp_imag = 16'sh0000;
adc_valid = 1'b1;
@(posedge clk); #1;
end
adc_valid = 1'b0;
// Assert reset
reset_n = 0;
repeat (4) @(posedge clk); #1;
reset_n = 1;
@(posedge clk); #1;
check(chain_state === ST_IDLE, "Mid-op reset: Returns to IDLE");
check(range_profile_valid === 1'b0, "Mid-op reset: No output");
// Feed a complete frame after reset
start_capture;
feed_dc_frame;
wait_for_idle;
cap_enable = 0;
$display(" Post-reset frame: %0d outputs", cap_count);
check(cap_count == FFT_SIZE, "Mid-op reset: Post-reset frame gives 1024 outputs");
//
// TEST GROUP 8: Back-to-back frames
//
$display("\n--- Test Group 8: Back-to-Back Frames ---");
apply_reset;
// Frame 1
start_capture;
feed_dc_frame;
wait_for_idle;
cap_enable = 0;
$display(" Frame 1: %0d outputs, peak=%0d, mag=%0d", cap_count, cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "B2B Frame 1: 1024 outputs");
// Frame 2
start_capture;
feed_tone_frame(3);
wait_for_idle;
cap_enable = 0;
$display(" Frame 2: %0d outputs, peak=%0d, mag=%0d", cap_count, cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "B2B Frame 2: 1024 outputs");
//
// TEST GROUP 9: Mismatched signal vs reference
// Signal at bin 5, reference at bin 10
//
$display("\n--- Test Group 9: Mismatched Signal vs Reference ---");
apply_reset;
start_capture;
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_q = $rtoi(8000.0 * $sin(6.28318530718 * 5 * i / 1024.0));
ref_chirp_real = $rtoi(8000.0 * $cos(6.28318530718 * 10 * i / 1024.0));
ref_chirp_imag = $rtoi(8000.0 * $sin(6.28318530718 * 10 * i / 1024.0));
adc_valid = 1'b1;
@(posedge clk); #1;
end
adc_valid = 1'b0;
wait_for_idle;
cap_enable = 0;
$display(" Mismatched: peak bin=%0d, magnitude=%0d", cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "Mismatch: Got 1024 output samples");
// Signal=bin5, ref=bin10: product has energy at bin(5-10)=bin(-5)=bin(1019)
// IFFT of that gives a tone at sample spacing of 5
// The key check is that it completes and produces output
check(cap_max_abs > 0, "Mismatch: Non-zero output");
check(chain_state === ST_IDLE, "Mismatch: Returns to IDLE");
//
// TEST GROUP 10: Saturation max positive values
//
$display("\n--- Test Group 10: Saturation — Max Positive ---");
apply_reset;
start_capture;
for (i = 0; i < FFT_SIZE; i = i + 1) begin
adc_data_i = 16'sh7FFF;
adc_data_q = 16'sh7FFF;
ref_chirp_real = 16'sh7FFF;
ref_chirp_imag = 16'sh7FFF;
adc_valid = 1'b1;
@(posedge clk); #1;
end
adc_valid = 1'b0;
wait_for_idle;
cap_enable = 0;
$display(" Saturation: count=%0d, peak=%0d, mag=%0d", cap_count, cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "Saturation: Completes with 1024 outputs");
check(chain_state === ST_IDLE, "Saturation: Returns to IDLE");
//
// TEST GROUP 11: Valid-gap / stall test
//
$display("\n--- Test Group 11: Valid-Gap Stall Test ---");
apply_reset;
start_capture;
for (i = 0; i < FFT_SIZE; i = i + 1) begin
adc_data_i = 16'sh1000;
adc_data_q = 16'sh0000;
ref_chirp_real = 16'sh1000;
ref_chirp_imag = 16'sh0000;
adc_valid = 1'b1;
@(posedge clk); #1;
// Every 100 samples, insert a 10-cycle gap
if ((i % 100) == 99 && i < FFT_SIZE - 1) begin : stall_block
integer gap_j;
adc_valid = 1'b0;
for (gap_j = 0; gap_j < 10; gap_j = gap_j + 1) begin
@(posedge clk); #1;
end
end
end
adc_valid = 1'b0;
wait_for_idle;
cap_enable = 0;
$display(" Stall: count=%0d, peak=%0d, mag=%0d", cap_count, cap_peak_bin, cap_max_abs);
check(cap_count == FFT_SIZE, "Stall: 1024 outputs emitted");
check(chain_state === ST_IDLE, "Stall: Returns to IDLE");
//
// Summary
//
$display("");
$display("========================================");
$display(" MATCHED FILTER PROCESSING CHAIN");
$display(" (SYNTHESIS BRANCH — fft_engine)");
$display(" PASSED: %0d / %0d", pass_count, test_num);
$display(" FAILED: %0d / %0d", fail_count, test_num);
if (fail_count == 0)
$display(" ** ALL TESTS PASSED **");
else
$display(" ** SOME TESTS FAILED **");
$display("========================================");
$display("");
#100;
$finish;
end
endmodule

View File

@@ -1,263 +0,0 @@
`timescale 1ns / 1ps
/**
* tb_range_fft_realdata.v
*
* Co-simulation testbench: feeds real ADI CN0566 radar IQ data through
* the 1024-point fft_engine and compares output bit-for-bit against
* the Python golden reference (golden_reference.py).
*
* Stimulus: cosim/real_data/hex/chirp0_i.hex, chirp0_q.hex
* Expected: cosim/real_data/hex/range_fft_chirp0_i.hex, range_fft_chirp0_q.hex
*
* The golden reference uses identical fixed-point arithmetic (32-bit internal,
* 16-bit twiddle, same quarter-wave ROM, same bit-reversal and butterfly
* schedule), so outputs should match exactly (0 error tolerance).
*
* Pass criteria: ALL 1024 output bins match golden reference exactly.
*/
module tb_range_fft_realdata;
// ============================================================================
// PARAMETERS
// ============================================================================
localparam N = 1024;
localparam LOG2N = 10;
localparam DATA_W = 16;
localparam INT_W = 32;
localparam TW_W = 16;
localparam CLK_PERIOD = 10; // 100 MHz for simulation
// Error tolerance: 0 means exact match required.
// If the Python golden reference is truly bit-accurate, this should be 0.
// Set to 1 if there are minor rounding differences to debug later.
localparam integer MAX_ERROR = 0;
// ============================================================================
// SIGNALS
// ============================================================================
reg clk, reset_n;
reg start, inverse;
reg signed [DATA_W-1:0] din_re, din_im;
reg din_valid;
wire signed [DATA_W-1:0] dout_re, dout_im;
wire dout_valid, busy, done_sig;
// ============================================================================
// STIMULUS AND REFERENCE MEMORIES
// ============================================================================
reg signed [DATA_W-1:0] stim_re [0:N-1];
reg signed [DATA_W-1:0] stim_im [0:N-1];
reg signed [DATA_W-1:0] ref_re [0:N-1];
reg signed [DATA_W-1:0] ref_im [0:N-1];
reg signed [DATA_W-1:0] cap_re [0:N-1];
reg signed [DATA_W-1:0] cap_im [0:N-1];
// ============================================================================
// DUT 1024-point FFT engine
// ============================================================================
fft_engine #(
.N(N),
.LOG2N(LOG2N),
.DATA_W(DATA_W),
.INTERNAL_W(INT_W),
.TWIDDLE_W(TW_W),
.TWIDDLE_FILE("fft_twiddle_1024.mem")
) dut (
.clk(clk),
.reset_n(reset_n),
.start(start),
.inverse(inverse),
.din_re(din_re),
.din_im(din_im),
.din_valid(din_valid),
.dout_re(dout_re),
.dout_im(dout_im),
.dout_valid(dout_valid),
.busy(busy),
.done(done_sig)
);
// ============================================================================
// CLOCK
// ============================================================================
initial clk = 0;
always #(CLK_PERIOD/2) clk = ~clk;
// ============================================================================
// PASS / FAIL TRACKING
// ============================================================================
integer pass_count, fail_count;
task check;
input cond;
input [512*8-1:0] label;
begin
if (cond) begin
pass_count = pass_count + 1;
end else begin
$display(" [FAIL] %0s", label);
fail_count = fail_count + 1;
end
end
endtask
// ============================================================================
// VCD (optional uncomment for waveform debug)
// ============================================================================
// initial begin
// $dumpfile("tb_range_fft_realdata.vcd");
// $dumpvars(0, tb_range_fft_realdata);
// end
// ============================================================================
// MAIN TEST
// ============================================================================
integer i, out_idx;
integer err_re, err_im, max_err_re, max_err_im;
integer n_exact, n_within_tol;
reg signed [31:0] diff_re, diff_im;
integer abs_diff_re, abs_diff_im;
initial begin
pass_count = 0;
fail_count = 0;
$display("============================================================");
$display(" Range FFT Real-Data Co-Simulation (1024-pt)");
$display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW");
$display("============================================================");
// ------------------------------------------------------------------
// Load hex files
// ------------------------------------------------------------------
$readmemh("tb/cosim/real_data/hex/chirp0_i.hex", stim_re);
$readmemh("tb/cosim/real_data/hex/chirp0_q.hex", stim_im);
$readmemh("tb/cosim/real_data/hex/range_fft_chirp0_i.hex", ref_re);
$readmemh("tb/cosim/real_data/hex/range_fft_chirp0_q.hex", ref_im);
$display(" Loaded stimulus: chirp0_i/q.hex (1024 samples)");
$display(" Loaded reference: range_fft_chirp0_i/q.hex (1024 bins)");
$display(" First stim sample: re=%0d, im=%0d", stim_re[0], stim_im[0]);
$display(" Last stim sample: re=%0d, im=%0d", stim_re[N-1], stim_im[N-1]);
$display("");
// ------------------------------------------------------------------
// Reset
// ------------------------------------------------------------------
reset_n = 0;
start = 0;
inverse = 0;
din_re = 0;
din_im = 0;
din_valid = 0;
repeat (5) @(posedge clk); #1;
reset_n = 1;
repeat (2) @(posedge clk); #1;
// ------------------------------------------------------------------
// Start forward FFT
// ------------------------------------------------------------------
$display("--- Running 1024-point forward FFT ---");
inverse = 0;
@(posedge clk); #1;
start = 1;
@(posedge clk); #1;
start = 0;
// Feed N samples
for (i = 0; i < N; i = i + 1) begin
din_re = stim_re[i];
din_im = stim_im[i];
din_valid = 1;
@(posedge clk); #1;
end
din_valid = 0;
din_re = 0;
din_im = 0;
// Capture N output samples
out_idx = 0;
while (out_idx < N) begin
@(posedge clk); #1;
if (dout_valid) begin
cap_re[out_idx] = dout_re;
cap_im[out_idx] = dout_im;
out_idx = out_idx + 1;
end
end
$display(" FFT output captured: %0d bins", out_idx);
// ------------------------------------------------------------------
// Compare output vs golden reference
// ------------------------------------------------------------------
$display("");
$display("--- Comparing RTL output vs Python golden reference ---");
max_err_re = 0;
max_err_im = 0;
n_exact = 0;
n_within_tol = 0;
for (i = 0; i < N; i = i + 1) begin
diff_re = cap_re[i] - ref_re[i];
diff_im = cap_im[i] - ref_im[i];
// Absolute value
abs_diff_re = (diff_re < 0) ? -diff_re : diff_re;
abs_diff_im = (diff_im < 0) ? -diff_im : diff_im;
if (abs_diff_re > max_err_re) max_err_re = abs_diff_re;
if (abs_diff_im > max_err_im) max_err_im = abs_diff_im;
if (diff_re == 0 && diff_im == 0)
n_exact = n_exact + 1;
if (abs_diff_re <= MAX_ERROR && abs_diff_im <= MAX_ERROR)
n_within_tol = n_within_tol + 1;
// Print first 10 mismatches and some spot checks
if ((abs_diff_re > MAX_ERROR || abs_diff_im > MAX_ERROR) &&
(fail_count < 10)) begin
$display(" Bin %4d: RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)",
i, cap_re[i], cap_im[i], ref_re[i], ref_im[i],
diff_re, diff_im);
end
check(abs_diff_re <= MAX_ERROR && abs_diff_im <= MAX_ERROR,
"range FFT bin match");
end
// ------------------------------------------------------------------
// Summary
// ------------------------------------------------------------------
$display("");
$display("============================================================");
$display(" SUMMARY: Range FFT Real-Data Co-Simulation");
$display("============================================================");
$display(" Total bins: %0d", N);
$display(" Exact match: %0d / %0d", n_exact, N);
$display(" Within tolerance: %0d / %0d (tol=%0d)", n_within_tol, N, MAX_ERROR);
$display(" Max error (re): %0d", max_err_re);
$display(" Max error (im): %0d", max_err_im);
$display(" Pass: %0d Fail: %0d", pass_count, fail_count);
$display("============================================================");
if (fail_count == 0)
$display("RESULT: ALL TESTS PASSED");
else
$display("RESULT: %0d TESTS FAILED", fail_count);
$finish;
end
// Timeout watchdog (1024-point FFT should finish well within 1M cycles)
initial begin
#(CLK_PERIOD * 2000000);
$display("[TIMEOUT] Simulation exceeded 2M cycles — aborting");
$finish;
end
endmodule

View File

@@ -291,8 +291,8 @@ reg range_data_ready;
// Frame sync: sample counter (ft601_clk domain, wraps at NUM_CELLS)
// Bit 7 of detection byte is set when sample_counter == 0 (frame start).
localparam [11:0] NUM_CELLS = 12'd2048; // 64 range x 32 doppler
reg [11:0] sample_counter;
localparam [14:0] NUM_CELLS = 15'd16384; // 512 range x 32 doppler
reg [14:0] sample_counter;
// Gap 2: CDC for stream_control (clk_100m -> ft601_clk_in)
// stream_control changes infrequently (only on host USB command), so
@@ -442,7 +442,7 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
data_pkt_word1 <= 32'd0;
data_pkt_word2 <= 32'd0;
data_pkt_be2 <= 4'b1110;
sample_counter <= 12'd0;
sample_counter <= 15'd0;
// NOTE: ft601_clk_out is driven by the clk-domain always block below.
// Do NOT assign it here (ft601_clk_in domain) causes multi-driven net.
end else begin
@@ -553,8 +553,8 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
stream_doppler_en ? doppler_imag_cap[15:8] : 8'd0};
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 == 15'd0), 6'b0, cfar_detection_cap}
: {(sample_counter == 15'd0), 7'd0},
FOOTER,
8'h00}; // pad byte
data_pkt_be2 <= 4'b1110; // 3 valid bytes + 1 pad
@@ -634,10 +634,10 @@ always @(posedge ft601_clk_in or negedge ft601_effective_reset_n) begin
doppler_data_pending <= 1'b0;
cfar_data_pending <= 1'b0;
// Advance frame sync counter
if (sample_counter == NUM_CELLS - 12'd1)
sample_counter <= 12'd0;
if (sample_counter == NUM_CELLS - 15'd1)
sample_counter <= 15'd0;
else
sample_counter <= sample_counter + 12'd1;
sample_counter <= sample_counter + 15'd1;
current_state <= IDLE;
end
endcase