diff --git a/9_Firmware/9_2_FPGA/tb/tb_fullchain_mti_cfar_realdata.v b/9_Firmware/9_2_FPGA/tb/tb_fullchain_mti_cfar_realdata.v deleted file mode 100644 index 3fc9f1b..0000000 --- a/9_Firmware/9_2_FPGA/tb/tb_fullchain_mti_cfar_realdata.v +++ /dev/null @@ -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 diff --git a/9_Firmware/9_2_FPGA/tb/tb_mf_chain_synth.v b/9_Firmware/9_2_FPGA/tb/tb_mf_chain_synth.v deleted file mode 100644 index b2e952a..0000000 --- a/9_Firmware/9_2_FPGA/tb/tb_mf_chain_synth.v +++ /dev/null @@ -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 diff --git a/9_Firmware/9_2_FPGA/tb/tb_range_fft_realdata.v b/9_Firmware/9_2_FPGA/tb/tb_range_fft_realdata.v deleted file mode 100644 index 2953eea..0000000 --- a/9_Firmware/9_2_FPGA/tb/tb_range_fft_realdata.v +++ /dev/null @@ -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 diff --git a/9_Firmware/9_2_FPGA/usb_data_interface.v b/9_Firmware/9_2_FPGA/usb_data_interface.v index ae0e29e..232ce19 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface.v @@ -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