Real-data co-simulation: range FFT, Doppler, full-chain all 2048/2048 exact match

ADI CN0566 Phaser 10.525 GHz X-band radar data validation:
- golden_reference.py: bit-accurate Python model with range_bin_decimator
- tb_range_fft_realdata.v: 1024/1024 exact match
- tb_doppler_realdata.v: 2048/2048 exact match
- tb_fullchain_realdata.v: decimator+Doppler 2048/2048 exact match
- 19/19 FPGA regression unaffected
This commit is contained in:
Jason
2026-03-20 03:19:22 +02:00
parent 19284ac277
commit 0b0643619c
25 changed files with 128710 additions and 0 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,291 @@
# AERIS-10 Golden Reference Detections
# Threshold: 10000
# Format: range_bin doppler_bin magnitude
0 0 44371
0 1 24165
0 31 17748
1 0 34391
1 1 17923
1 31 18610
2 0 28512
2 1 13818
2 31 15787
3 0 47402
3 1 25214
3 31 23504
4 0 51870
4 1 32733
4 31 31545
5 0 31752
5 1 13486
5 31 19300
6 0 63406
6 1 33383
6 31 36672
7 0 37576
7 1 21215
7 31 27773
8 0 14823
10 0 30062
10 1 13616
10 31 17149
11 0 65534
11 1 60963
11 2 14848
11 3 12082
11 4 18060
11 29 10045
11 30 20661
11 31 65536
12 0 65536
12 1 44569
12 4 11189
12 30 13936
12 31 57036
13 0 47038
13 1 40212
13 2 14655
13 4 10242
13 30 14945
13 31 40237
14 0 65534
14 1 43568
14 3 10974
14 4 11491
14 30 15272
14 31 57983
15 0 34501
15 1 22496
15 31 25197
16 0 32784
16 1 19309
16 31 14005
17 0 23063
17 1 13730
18 0 17087
18 1 12092
19 0 65535
19 1 49084
19 2 11399
19 30 13119
19 31 48411
20 0 65509
20 1 37881
20 31 35014
21 0 39614
21 1 23389
21 31 22417
22 0 27174
22 1 12577
22 31 15278
23 0 39885
23 1 29247
23 31 33561
24 0 29644
24 28 11071
24 31 20937
25 0 65535
25 1 54580
25 2 20278
25 30 20041
25 31 59445
26 0 58162
26 1 46544
26 2 17230
26 3 10127
26 31 44711
27 0 65535
27 1 65535
27 2 44599
27 3 17124
27 28 15139
27 29 26067
27 30 54631
27 31 65535
28 0 65535
28 1 65535
28 2 43056
28 3 14647
28 4 11808
28 29 15256
28 30 50518
28 31 65535
29 0 65535
29 1 61621
29 2 28859
29 3 19523
29 4 21765
29 5 12687
29 27 13175
29 28 19619
29 29 24365
29 30 48682
29 31 65535
30 0 55399
30 1 46683
30 2 21192
30 3 15905
30 4 18003
30 29 11105
30 30 22360
30 31 40830
31 0 46504
31 1 44346
31 2 34200
31 3 20677
31 4 18570
31 5 10430
31 29 12684
31 30 31778
31 31 36195
32 0 39540
32 1 36657
32 31 35394
33 0 35482
33 1 32886
33 2 15041
33 28 10103
33 29 11617
33 30 17465
33 31 34603
34 0 47950
34 1 25855
34 31 23198
35 0 65536
35 1 63059
35 2 24416
35 30 27412
35 31 65534
36 0 65535
36 1 41914
36 2 11341
36 30 11276
36 31 41419
38 0 63253
38 1 46689
38 2 13576
38 30 14208
38 31 49979
39 0 33480
39 1 25439
39 31 23094
40 0 52003
40 1 47059
40 2 13164
40 31 37992
41 0 65536
41 1 65534
41 2 25844
41 3 14580
41 4 12743
41 30 22231
41 31 65534
42 0 52097
42 1 45022
42 2 10317
42 28 11984
42 29 10182
42 30 13078
42 31 40477
43 0 61723
43 1 48104
43 2 17623
43 3 10105
43 28 28331
43 29 24102
43 31 45085
44 0 65535
44 1 65535
44 2 60795
44 3 25438
44 27 39330
44 28 60025
44 29 52445
44 30 35091
44 31 65535
45 0 65535
45 1 65535
45 2 27652
45 3 14416
45 4 10622
45 27 16323
45 28 40935
45 29 30694
45 30 29375
45 31 65535
46 0 65536
46 1 57696
46 2 14924
46 30 14433
46 31 45164
47 0 59141
47 1 44129
47 2 15305
47 28 13092
47 30 13754
47 31 47415
48 0 27722
48 1 13381
48 31 16907
49 0 51936
49 1 43775
49 2 13004
49 31 40023
50 0 45430
50 1 39187
50 2 15881
50 30 12925
50 31 38207
51 0 34026
51 1 33081
51 31 34429
52 0 34415
52 1 15408
52 31 19344
53 0 52351
53 1 42915
53 2 14442
53 30 13099
53 31 42143
54 0 62356
54 1 49279
54 2 15596
54 30 15478
54 31 46574
55 0 33829
55 1 15941
55 31 18110
56 0 65535
56 1 46926
56 2 11443
56 28 12373
56 29 12101
56 30 14660
56 31 53058
58 0 65535
58 1 56769
58 2 14110
58 28 12576
58 29 16059
58 30 18858
58 31 63517
59 0 30703
59 1 24206
59 28 17534
59 29 12652
60 0 35136
60 1 21277
60 31 25048
61 0 28692
61 1 11267
61 28 11881
61 31 17628
62 0 35795
62 1 18879
62 31 18083
63 0 65535
63 1 40428
63 28 11884
63 29 13271
63 30 14869
63 31 52574
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,189 @@
# AERIS-10 Full-Chain Golden Reference Detections
# Threshold: 10000
# Format: range_bin doppler_bin magnitude
0 0 65534
0 1 59350
0 2 16748
0 4 18802
0 29 10539
0 30 18526
0 31 65536
1 0 65535
1 1 65535
1 2 37002
1 4 12412
1 5 14956
1 6 12586
1 7 11607
1 8 11379
1 24 11725
1 28 17218
1 29 32939
1 30 58888
1 31 65535
2 0 65535
2 1 65535
2 2 60795
2 3 25438
2 27 39330
2 28 60025
2 29 52445
2 30 35091
2 31 65535
3 0 65535
3 1 63297
3 2 32758
3 3 42197
3 4 35819
3 5 12663
3 7 19561
3 8 12012
3 12 13537
3 13 12879
3 19 10255
3 20 10129
3 24 17256
3 25 22733
3 26 10202
3 28 24061
3 29 19639
3 31 37328
4 0 46755
4 1 39569
4 2 12396
4 28 12471
4 29 12156
4 30 16659
4 31 40340
5 0 44089
5 1 23634
5 31 21331
6 0 48634
6 1 24635
6 31 25423
7 0 24477
7 1 14206
7 31 10955
8 0 41014
8 1 19527
8 31 21133
9 0 47277
9 1 28366
9 31 29936
10 0 47095
10 1 26150
10 31 24009
11 0 47384
11 1 25409
11 31 24250
12 0 24648
12 1 14298
12 31 13970
13 0 13062
15 0 10284
16 0 14267
17 0 16165
18 0 14235
18 31 12120
19 0 18006
19 1 14936
20 0 47569
20 1 33826
20 31 35752
21 0 47804
21 1 21420
21 31 30292
22 0 14968
26 0 16086
26 31 10462
30 0 16628
30 1 10044
38 0 23453
38 1 13989
38 31 10672
39 0 31656
39 1 17367
39 31 17314
40 0 19156
40 1 10817
40 31 10083
45 0 25385
45 1 11685
45 31 14673
46 0 12576
46 4 10141
46 28 12358
47 0 19657
47 31 15741
48 0 13189
48 1 10038
49 0 33747
49 1 16561
49 31 18910
50 0 20552
50 31 10843
51 0 20068
51 1 13887
51 4 10305
51 28 11339
53 28 10166
55 0 39891
55 1 17615
55 31 24898
56 0 62796
56 1 29788
56 31 38261
57 0 63585
57 1 59760
57 2 13027
57 3 43395
57 4 59148
57 5 31472
57 6 11913
57 7 13807
57 8 12132
57 16 14068
57 17 10379
57 24 15712
57 25 11076
57 26 14856
57 27 23468
57 28 38479
57 29 23078
57 30 17921
57 31 46558
58 0 54425
58 1 45222
58 2 11380
58 4 11700
58 29 12022
58 30 13911
58 31 45374
59 0 45581
59 1 31538
59 2 10481
59 31 34132
60 0 28622
60 1 12594
60 3 11799
60 4 13327
60 28 11737
60 29 11439
60 31 16902
61 0 28716
61 1 16605
61 31 15745
62 0 14151
62 1 15747
62 4 11738
62 5 12479
62 26 10789
62 27 16875
62 28 19372
62 29 16120
62 30 18215
62 31 10810
63 0 63651
63 1 35648
63 30 10692
63 31 38169
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,342 @@
`timescale 1ns / 1ps
/**
* tb_doppler_realdata.v
*
* Co-simulation testbench: feeds real ADI CN0566 radar data (post-range-FFT)
* through the Doppler processor RTL and compares output bit-for-bit against
* the Python golden reference (golden_reference.py).
*
* Stimulus: cosim/real_data/hex/doppler_input_realdata.hex
* (2048 x 32-bit packed {Q[31:16], I[15:0]}, chirp-major order)
* Expected: cosim/real_data/hex/doppler_ref_i.hex, doppler_ref_q.hex
* (2048 x 16-bit signed, range-major order: rbin0 x 32 doppler, ...)
*
* Pass criteria: ALL 2048 output bins match golden reference exactly.
*
* Compile:
* iverilog -Wall -DSIMULATION -g2012 \
* -o tb/tb_doppler_realdata.vvp \
* tb/tb_doppler_realdata.v doppler_processor.v xfft_32.v fft_engine.v
*
* Run from: 9_Firmware/9_2_FPGA/
* vvp tb/tb_doppler_realdata.vvp
*/
module tb_doppler_realdata;
// ============================================================================
// PARAMETERS
// ============================================================================
localparam CLK_PERIOD = 10.0; // 100 MHz
localparam DOPPLER_FFT = 32;
localparam RANGE_BINS = 64;
localparam CHIRPS = 32;
localparam TOTAL_INPUTS = CHIRPS * RANGE_BINS; // 2048
localparam TOTAL_OUTPUTS = RANGE_BINS * DOPPLER_FFT; // 2048
localparam MAX_CYCLES = 500_000; // Timeout: 5 ms at 100 MHz
// Error tolerance: 0 means exact match required.
localparam integer MAX_ERROR = 0;
// ============================================================================
// CLOCK AND RESET
// ============================================================================
reg clk;
reg reset_n;
initial clk = 0;
always #(CLK_PERIOD / 2) clk = ~clk;
// ============================================================================
// DUT SIGNALS
// ============================================================================
reg [31:0] range_data;
reg data_valid;
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;
// ============================================================================
// DUT INSTANTIATION
// ============================================================================
doppler_processor_optimized dut (
.clk(clk),
.reset_n(reset_n),
.range_data(range_data),
.data_valid(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)
);
// Internal DUT state (for debug)
wire [2:0] dut_state_w = dut.state;
// ============================================================================
// INPUT DATA MEMORY (loaded from hex file)
// ============================================================================
reg [31:0] input_mem [0:TOTAL_INPUTS-1];
initial begin
$readmemh("tb/cosim/real_data/hex/doppler_input_realdata.hex", input_mem);
end
// ============================================================================
// REFERENCE DATA (loaded from hex files)
// ============================================================================
reg signed [15:0] ref_i [0:TOTAL_OUTPUTS-1];
reg signed [15:0] ref_q [0:TOTAL_OUTPUTS-1];
initial begin
$readmemh("tb/cosim/real_data/hex/doppler_ref_i.hex", ref_i);
$readmemh("tb/cosim/real_data/hex/doppler_ref_q.hex", ref_q);
end
// ============================================================================
// OUTPUT CAPTURE
// ============================================================================
reg signed [15:0] cap_out_i [0:TOTAL_OUTPUTS-1];
reg signed [15:0] cap_out_q [0:TOTAL_OUTPUTS-1];
reg [5:0] cap_rbin [0:TOTAL_OUTPUTS-1];
reg [4:0] cap_dbin [0:TOTAL_OUTPUTS-1];
integer out_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, 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;
initial begin
// ---- Init ----
pass_count = 0;
fail_count = 0;
test_count = 0;
out_count = 0;
range_data = 0;
data_valid = 0;
new_chirp_frame = 0;
reset_n = 0;
// ---- Reset ----
#(CLK_PERIOD * 10);
reset_n = 1;
#(CLK_PERIOD * 5);
$display("============================================================");
$display(" Doppler Processor Real-Data Co-Simulation");
$display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW");
$display(" Input: 32 chirps x 64 range bins (post-range-FFT)");
$display(" Expected: 64 range bins x 32 Doppler bins");
$display("============================================================");
// ---- Debug: check hex file loaded ----
$display(" input_mem[0] = %08h", input_mem[0]);
$display(" input_mem[1] = %08h", input_mem[1]);
$display(" input_mem[2047] = %08h", input_mem[2047]);
$display(" ref_i[0] = %04h, ref_q[0] = %04h", ref_i[0], ref_q[0]);
// ---- Check 1: DUT starts in IDLE ----
check(dut_state_w == 3'b000,
"DUT starts in S_IDLE after reset");
// ---- Pulse new_chirp_frame to start a new frame ----
@(posedge clk);
new_chirp_frame <= 1;
@(posedge clk);
@(posedge clk);
new_chirp_frame <= 0;
@(posedge clk);
// ---- Feed input data (2048 samples, chirp-major) ----
$display("\n--- Feeding %0d input samples ---", TOTAL_INPUTS);
for (i = 0; i < TOTAL_INPUTS; i = i + 1) begin
@(posedge clk);
range_data <= input_mem[i];
data_valid <= 1;
if (i < 3 || i == TOTAL_INPUTS - 1) begin
$display(" [feed] i=%0d data=%08h state=%0d",
i, input_mem[i], dut_state_w);
end
end
@(posedge clk);
data_valid <= 0;
range_data <= 0;
$display(" After feeding: state=%0d", dut_state_w);
// ---- Check 2: DUT should be processing ----
#(CLK_PERIOD * 5);
check(dut_state_w != 3'b000 && dut_state_w != 3'b001,
"DUT entered processing state after 2048 input samples");
// ---- Collect outputs ----
$display("\n--- Waiting for %0d output samples ---", TOTAL_OUTPUTS);
cycle_count = 0;
while (out_count < TOTAL_OUTPUTS && cycle_count < MAX_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
if (doppler_valid) begin
cap_out_i[out_count] = doppler_output[15:0];
cap_out_q[out_count] = doppler_output[31:16];
cap_rbin[out_count] = range_bin;
cap_dbin[out_count] = doppler_bin;
out_count = out_count + 1;
end
end
$display(" Collected %0d output samples in %0d cycles", out_count,
cycle_count);
// ---- Check 3: Correct output count ----
check(out_count == TOTAL_OUTPUTS,
"Output sample count == 2048");
// ---- Check 4: Did not timeout ----
check(cycle_count < MAX_CYCLES,
"Processing completed within timeout");
// ---- Check 5: DUT returns to IDLE ----
#(CLK_PERIOD * 20);
check(dut_state_w == 3'b000,
"DUT returned to S_IDLE after processing");
// ---- Check 6: Output ordering ----
if (out_count > 0) begin
check(cap_rbin[0] == 0 && cap_dbin[0] == 0,
"First output: range_bin=0, doppler_bin=0");
end
if (out_count == TOTAL_OUTPUTS) begin
check(cap_rbin[TOTAL_OUTPUTS-1] == RANGE_BINS - 1,
"Last output: range_bin=63");
check(cap_dbin[TOTAL_OUTPUTS-1] == DOPPLER_FFT - 1,
"Last output: doppler_bin=31");
end
// ==================================================================
// BIT-FOR-BIT COMPARISON against golden reference
// ==================================================================
$display("");
$display("--- Comparing RTL output vs Python golden reference ---");
max_err_i = 0;
max_err_q = 0;
n_exact = 0;
n_within_tol = 0;
mismatches_printed = 0;
for (i = 0; i < out_count; i = i + 1) begin
diff_i = cap_out_i[i] - ref_i[i];
diff_q = cap_out_q[i] - ref_q[i];
// Absolute value
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 <= MAX_ERROR && abs_diff_q <= MAX_ERROR)
n_within_tol = n_within_tol + 1;
// Print first 20 mismatches for debug
if ((abs_diff_i > MAX_ERROR || abs_diff_q > MAX_ERROR) &&
mismatches_printed < 20) begin
$display(" [%4d] rbin=%2d dbin=%2d RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)",
i, cap_rbin[i], cap_dbin[i],
$signed(cap_out_i[i]), $signed(cap_out_q[i]),
$signed(ref_i[i]), $signed(ref_q[i]),
diff_i, diff_q);
mismatches_printed = mismatches_printed + 1;
end
end
// Per-sample pass/fail
for (i = 0; i < out_count; i = i + 1) begin
diff_i = cap_out_i[i] - ref_i[i];
diff_q = cap_out_q[i] - ref_q[i];
abs_diff_i = (diff_i < 0) ? -diff_i : diff_i;
abs_diff_q = (diff_q < 0) ? -diff_q : diff_q;
check(abs_diff_i <= MAX_ERROR && abs_diff_q <= MAX_ERROR,
"Doppler output bin match");
end
// ==================================================================
// SUMMARY
// ==================================================================
$display("");
$display("============================================================");
$display(" SUMMARY: Doppler Processor Real-Data Co-Simulation");
$display("============================================================");
$display(" Total output bins: %0d", out_count);
$display(" Exact match: %0d / %0d", n_exact, out_count);
$display(" Within tolerance: %0d / %0d (tol=%0d)", n_within_tol, out_count, MAX_ERROR);
$display(" Max error (I): %0d", max_err_i);
$display(" Max error (Q): %0d", max_err_q);
$display(" Structural checks: %0d", 7); // checks 1-7 above
$display(" Data match checks: %0d", out_count);
$display(" Pass: %0d Fail: %0d", pass_count, fail_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
@@ -0,0 +1,462 @@
`timescale 1ns / 1ps
/**
* tb_fullchain_realdata.v
*
* Full-chain co-simulation testbench: feeds real ADI CN0566 radar data
* (post-range-FFT, 32 chirps x 1024 bins) through:
*
* range_bin_decimator (peak detection, 102464)
* doppler_processor_optimized (Hamming + 32-pt FFT)
*
* and compares the Doppler output bit-for-bit against the Python golden
* reference that models the same chain (golden_reference.py).
*
* 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
*
* Expected:
* tb/cosim/real_data/hex/fullchain_doppler_ref_i.hex
* tb/cosim/real_data/hex/fullchain_doppler_ref_q.hex
* 2048 x 16-bit signed 64 range bins x 32 Doppler bins
*
* Pass criteria: ALL 2048 Doppler output bins match exactly.
*
* Compile:
* iverilog -Wall -DSIMULATION -g2012 \
* -o tb/tb_fullchain_realdata.vvp \
* tb/tb_fullchain_realdata.v \
* range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v
*
* Run from: 9_Firmware/9_2_FPGA/
* vvp tb/tb_fullchain_realdata.vvp
*/
module tb_fullchain_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_OUTPUT_SAMPLES = RANGE_BINS * DOPPLER_FFT; // 2048
localparam SAMPLES_PER_CHIRP = INPUT_BINS; // 1024
localparam DECIM_PER_CHIRP = RANGE_BINS; // 64
// Generous timeout: decimator + Doppler processing + margin
localparam MAX_CYCLES = 2_000_000;
// Error tolerance: 0 = exact match required
localparam integer MAX_ERROR = 0;
// ============================================================================
// 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;
// ============================================================================
// DOPPLER SIGNALS
// ============================================================================
// Wire decimator output directly into Doppler input (matching RTL)
wire [31:0] range_data_32bit;
wire range_data_valid;
assign range_data_32bit = {decim_q_out, decim_i_out};
assign range_data_valid = decim_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;
// ============================================================================
// 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)
);
// ============================================================================
// 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)
);
// 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)
// ============================================================================
// 32768 x 32-bit packed {Q[31:16], I[15:0]}
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)
// ============================================================================
reg signed [15:0] ref_i [0:TOTAL_OUTPUT_SAMPLES-1];
reg signed [15:0] ref_q [0:TOTAL_OUTPUT_SAMPLES-1];
initial begin
$readmemh("tb/cosim/real_data/hex/fullchain_doppler_ref_i.hex", ref_i);
$readmemh("tb/cosim/real_data/hex/fullchain_doppler_ref_q.hex", ref_q);
end
// ============================================================================
// DECIMATOR OUTPUT CAPTURE (for debug)
// ============================================================================
integer decim_out_count;
reg signed [15:0] decim_cap_i [0:CHIRPS*RANGE_BINS-1];
reg signed [15:0] decim_cap_q [0:CHIRPS*RANGE_BINS-1];
// ============================================================================
// DOPPLER OUTPUT CAPTURE
// ============================================================================
reg signed [15:0] cap_out_i [0:TOTAL_OUTPUT_SAMPLES-1];
reg signed [15:0] cap_out_q [0:TOTAL_OUTPUT_SAMPLES-1];
reg [5:0] cap_rbin [0:TOTAL_OUTPUT_SAMPLES-1];
reg [4:0] cap_dbin [0:TOTAL_OUTPUT_SAMPLES-1];
integer out_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
// ============================================================================
// COUNT DECIMATOR OUTPUTS (always block)
// ============================================================================
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
decim_out_count <= 0;
end else if (decim_valid_out) begin
if (decim_out_count < CHIRPS * RANGE_BINS) begin
decim_cap_i[decim_out_count] <= decim_i_out;
decim_cap_q[decim_out_count] <= decim_q_out;
end
decim_out_count <= decim_out_count + 1;
end
end
// ============================================================================
// 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;
initial begin
// ---- Init ----
pass_count = 0;
fail_count = 0;
test_count = 0;
out_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");
$display(" range_bin_decimator (peak, 1024->64)");
$display(" -> doppler_processor_optimized (Hamming + 32-pt FFT)");
$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 range bins x %0d Doppler bins = %0d outputs",
RANGE_BINS, DOPPLER_FFT, TOTAL_OUTPUT_SAMPLES);
$display("============================================================");
// ---- Debug: check hex files loaded ----
$display(" input_mem[0] = %08h", input_mem[0]);
$display(" input_mem[1023] = %08h", input_mem[1023]);
$display(" input_mem[32767] = %08h", input_mem[32767]);
$display(" ref_i[0] = %04h, ref_q[0] = %04h", ref_i[0], ref_q[0]);
// ---- Check 1: Both DUTs start in IDLE ----
check(decim_state == 3'd0,
"Decimator starts in ST_IDLE after reset");
check(doppler_state == 3'b000,
"Doppler starts in S_IDLE after reset");
// ---- 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 ----
// Each chirp is 1024 consecutive samples. Between chirps, the
// decimator completes (ST_DONE ST_IDLE) and restarts on the
// next valid input.
$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
// Feed 1024 range bins for this chirp
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
// Deassert valid after each chirp to let decimator finish
// (ST_PROCESS ST_EMIT ... ST_DONE ST_IDLE)
@(posedge clk);
decim_valid_in <= 0;
decim_i_in <= 0;
decim_q_in <= 0;
// Wait for decimator to return to IDLE
// The decimator needs a few cycles for the last EMIT + DONE
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, decim_out=%0d",
chirp, cycle_count, decim_out_count);
end
end
$display(" All input fed. Total decimator outputs: %0d (expected %0d)",
decim_out_count, CHIRPS * RANGE_BINS);
// ---- Check 3: Decimator produced correct number of outputs ----
check(decim_out_count == CHIRPS * RANGE_BINS,
"Decimator output count == 2048");
// ---- Wait for Doppler processing to complete ----
$display("\n--- Waiting for Doppler to process and emit %0d outputs ---",
TOTAL_OUTPUT_SAMPLES);
cycle_count = 0;
while (out_count < TOTAL_OUTPUT_SAMPLES && cycle_count < MAX_CYCLES) begin
@(posedge clk);
cycle_count = cycle_count + 1;
if (doppler_valid) begin
cap_out_i[out_count] = doppler_output[15:0];
cap_out_q[out_count] = doppler_output[31:16];
cap_rbin[out_count] = range_bin;
cap_dbin[out_count] = doppler_bin;
out_count = out_count + 1;
end
end
$display(" Collected %0d Doppler outputs in %0d cycles", out_count,
cycle_count);
// ---- Check 4: Correct Doppler output count ----
check(out_count == TOTAL_OUTPUT_SAMPLES,
"Doppler output count == 2048");
// ---- Check 5: Did not timeout ----
check(cycle_count < MAX_CYCLES,
"Processing completed within timeout");
// ---- Check 6: Doppler returns to IDLE ----
#(CLK_PERIOD * 20);
check(doppler_state == 3'b000,
"Doppler returned to S_IDLE after processing");
// ---- Check 7: Output ordering ----
if (out_count > 0) begin
check(cap_rbin[0] == 0 && cap_dbin[0] == 0,
"First output: range_bin=0, doppler_bin=0");
end
if (out_count == TOTAL_OUTPUT_SAMPLES) begin
check(cap_rbin[TOTAL_OUTPUT_SAMPLES-1] == RANGE_BINS - 1,
"Last output: range_bin=63");
check(cap_dbin[TOTAL_OUTPUT_SAMPLES-1] == DOPPLER_FFT - 1,
"Last output: doppler_bin=31");
end
// ==================================================================
// BIT-FOR-BIT COMPARISON against golden reference
// ==================================================================
$display("");
$display("--- Comparing Doppler RTL output vs Python golden reference ---");
$display(" (full-chain: range FFT -> decimator -> Doppler)");
max_err_i = 0;
max_err_q = 0;
n_exact = 0;
n_within_tol = 0;
mismatches_printed = 0;
for (i = 0; i < out_count; i = i + 1) begin
diff_i = cap_out_i[i] - ref_i[i];
diff_q = cap_out_q[i] - ref_q[i];
// Absolute value
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 <= MAX_ERROR && abs_diff_q <= MAX_ERROR)
n_within_tol = n_within_tol + 1;
// Print first 20 mismatches for debug
if ((abs_diff_i > MAX_ERROR || abs_diff_q > MAX_ERROR) &&
mismatches_printed < 20) begin
$display(" [%4d] rbin=%2d dbin=%2d RTL=(%6d,%6d) REF=(%6d,%6d) ERR=(%4d,%4d)",
i, cap_rbin[i], cap_dbin[i],
$signed(cap_out_i[i]), $signed(cap_out_q[i]),
$signed(ref_i[i]), $signed(ref_q[i]),
diff_i, diff_q);
mismatches_printed = mismatches_printed + 1;
end
end
// Per-sample pass/fail check
for (i = 0; i < out_count; i = i + 1) begin
diff_i = cap_out_i[i] - ref_i[i];
diff_q = cap_out_q[i] - ref_q[i];
abs_diff_i = (diff_i < 0) ? -diff_i : diff_i;
abs_diff_q = (diff_q < 0) ? -diff_q : diff_q;
check(abs_diff_i <= MAX_ERROR && abs_diff_q <= MAX_ERROR,
"Full-chain Doppler output bin match");
end
// ==================================================================
// SUMMARY
// ==================================================================
$display("");
$display("============================================================");
$display(" SUMMARY: Full-Chain Real-Data Co-Simulation");
$display("============================================================");
$display(" Chain: range_bin_decimator(peak) -> doppler_processor");
$display(" Input samples: %0d (%0d chirps x %0d bins)",
TOTAL_INPUT_SAMPLES, CHIRPS, INPUT_BINS);
$display(" Decimator outputs: %0d (expected %0d)",
decim_out_count, CHIRPS * RANGE_BINS);
$display(" Doppler outputs: %0d (expected %0d)",
out_count, TOTAL_OUTPUT_SAMPLES);
$display(" Exact match: %0d / %0d", n_exact, out_count);
$display(" Within tolerance: %0d / %0d (tol=%0d)",
n_within_tol, out_count, MAX_ERROR);
$display(" Max error (I): %0d", max_err_i);
$display(" Max error (Q): %0d", max_err_q);
$display(" Structural checks: %0d", 9);
$display(" Data match checks: %0d", out_count);
$display(" Pass: %0d Fail: %0d", pass_count, fail_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
@@ -0,0 +1,263 @@
`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