mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-05-23 16:22:03 +00:00
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:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user