Integrate CA-CFAR detector: replace fixed-threshold comparator with adaptive sliding-window CFAR engine (22/22 regression PASS)
- Add cfar_ca.v: CA/GO/SO-CFAR with BRAM magnitude buffer, host-configurable guard cells, training cells, alpha multiplier, and mode selection - Replace old threshold detector block in radar_system_top.v with cfar_ca instantiation; backward-compatible (cfar_enable defaults to 0) - Add 5 new host registers: guard (0x21), train (0x22), alpha (0x23), mode (0x24), enable (0x25) - Expose doppler_frame_done_out from radar_receiver_final for CFAR frame sync - Add tb_cfar_ca.v standalone testbench (14 tests, 24 checks) - Add Group 14 E2E tests: 13 checks covering range-mode (0x20) and all CFAR config registers (0x21-0x25) through full USB command path - Update run_regression.sh with CFAR in lint, Phase 1, and integration compiles
This commit is contained in:
@@ -0,0 +1,704 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/**
|
||||
* tb_cfar_ca.v
|
||||
*
|
||||
* Comprehensive testbench for cfar_ca.v (Cell-Averaging CFAR Detector).
|
||||
* Uses [PASS]/[FAIL] markers compatible with run_regression.sh.
|
||||
*
|
||||
* Test plan:
|
||||
* T1: CFAR disabled → simple threshold pass-through (backward-compatible)
|
||||
* T2: CA-CFAR with uniform noise floor → no detections
|
||||
* T3: CA-CFAR with single strong target → exactly 1 detection at correct bin
|
||||
* T4: CA-CFAR with two targets separated by > window size → 2 detections
|
||||
* T5: CA-CFAR edge handling → targets at range bin 0 and 63
|
||||
* T6: GO-CFAR mode with asymmetric noise → correct target detection
|
||||
* T7: SO-CFAR mode → lower threshold, more sensitive
|
||||
* T8: Alpha scaling → higher alpha reduces detections
|
||||
* T9: Guard cell effect → target neighbor doesn't raise threshold
|
||||
* T10: Zero guard, zero train corner case
|
||||
* T11: Reset during processing → clean recovery
|
||||
* T12: Back-to-back frames → second frame processes correctly
|
||||
* T13: detect_count accumulates across frames
|
||||
* T14: cfar_busy asserts during processing, deasserts after
|
||||
*/
|
||||
|
||||
module tb_cfar_ca;
|
||||
|
||||
// ============================================================================
|
||||
// PARAMETERS
|
||||
// ============================================================================
|
||||
parameter NUM_RANGE = 64;
|
||||
parameter NUM_DOPPLER = 32;
|
||||
parameter MAG_W = 17;
|
||||
parameter ALPHA_W = 8;
|
||||
parameter CLK_PERIOD = 10; // 100 MHz
|
||||
|
||||
// ============================================================================
|
||||
// DUT SIGNALS
|
||||
// ============================================================================
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
|
||||
reg [31:0] doppler_data;
|
||||
reg doppler_valid;
|
||||
reg [4:0] doppler_bin_in;
|
||||
reg [5:0] range_bin_in;
|
||||
reg frame_complete;
|
||||
|
||||
reg [3:0] cfg_guard_cells;
|
||||
reg [4:0] cfg_train_cells;
|
||||
reg [ALPHA_W-1:0] cfg_alpha;
|
||||
reg [1:0] cfg_cfar_mode;
|
||||
reg cfg_cfar_enable;
|
||||
reg [15:0] cfg_simple_threshold;
|
||||
|
||||
wire detect_flag;
|
||||
wire detect_valid;
|
||||
wire [5:0] detect_range;
|
||||
wire [4:0] detect_doppler;
|
||||
wire [MAG_W-1:0] detect_magnitude;
|
||||
wire [MAG_W-1:0] detect_threshold;
|
||||
wire [15:0] detect_count;
|
||||
wire cfar_busy;
|
||||
wire [7:0] cfar_status;
|
||||
|
||||
// ============================================================================
|
||||
// TEST TRACKING
|
||||
// ============================================================================
|
||||
integer pass_count;
|
||||
integer fail_count;
|
||||
integer test_num;
|
||||
reg [255:0] test_name;
|
||||
|
||||
// Detection capture (flagged detections only)
|
||||
integer det_cap_count;
|
||||
reg [5:0] det_cap_range [0:255];
|
||||
reg [4:0] det_cap_doppler[0:255];
|
||||
reg [MAG_W-1:0] det_cap_mag[0:255];
|
||||
reg [MAG_W-1:0] det_cap_thr[0:255];
|
||||
reg det_cap_flag [0:255];
|
||||
integer det_total_valid; // Total valid outputs (including non-detections)
|
||||
|
||||
// ============================================================================
|
||||
// DUT INSTANTIATION
|
||||
// ============================================================================
|
||||
cfar_ca #(
|
||||
.NUM_RANGE_BINS(NUM_RANGE),
|
||||
.NUM_DOPPLER_BINS(NUM_DOPPLER),
|
||||
.MAG_WIDTH(MAG_W),
|
||||
.ALPHA_WIDTH(ALPHA_W)
|
||||
) dut (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.doppler_data(doppler_data),
|
||||
.doppler_valid(doppler_valid),
|
||||
.doppler_bin_in(doppler_bin_in),
|
||||
.range_bin_in(range_bin_in),
|
||||
.frame_complete(frame_complete),
|
||||
.cfg_guard_cells(cfg_guard_cells),
|
||||
.cfg_train_cells(cfg_train_cells),
|
||||
.cfg_alpha(cfg_alpha),
|
||||
.cfg_cfar_mode(cfg_cfar_mode),
|
||||
.cfg_cfar_enable(cfg_cfar_enable),
|
||||
.cfg_simple_threshold(cfg_simple_threshold),
|
||||
.detect_flag(detect_flag),
|
||||
.detect_valid(detect_valid),
|
||||
.detect_range(detect_range),
|
||||
.detect_doppler(detect_doppler),
|
||||
.detect_magnitude(detect_magnitude),
|
||||
.detect_threshold(detect_threshold),
|
||||
.detect_count(detect_count),
|
||||
.cfar_busy(cfar_busy),
|
||||
.cfar_status(cfar_status)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
// CLOCK GENERATION
|
||||
// ============================================================================
|
||||
initial clk = 0;
|
||||
always #(CLK_PERIOD/2) clk = ~clk;
|
||||
|
||||
// ============================================================================
|
||||
// HELPER TASKS
|
||||
// ============================================================================
|
||||
|
||||
task check;
|
||||
input integer tnum;
|
||||
input [255:0] desc;
|
||||
input condition;
|
||||
begin
|
||||
if (condition) begin
|
||||
$display("[PASS(T%0d)] %0s", tnum, desc);
|
||||
pass_count = pass_count + 1;
|
||||
end else begin
|
||||
$display("[FAIL(T%0d)] %0s", tnum, desc);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task do_reset;
|
||||
begin
|
||||
reset_n = 0;
|
||||
doppler_data = 32'd0;
|
||||
doppler_valid = 1'b0;
|
||||
doppler_bin_in = 5'd0;
|
||||
range_bin_in = 6'd0;
|
||||
frame_complete = 1'b0;
|
||||
repeat (5) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
end
|
||||
endtask
|
||||
|
||||
// Feed one Doppler sample (I/Q packed as {Q, I})
|
||||
task feed_sample;
|
||||
input [5:0] rbin;
|
||||
input [4:0] dbin;
|
||||
input signed [15:0] i_val;
|
||||
input signed [15:0] q_val;
|
||||
begin
|
||||
@(posedge clk);
|
||||
doppler_data <= {q_val, i_val};
|
||||
doppler_valid <= 1'b1;
|
||||
range_bin_in <= rbin;
|
||||
doppler_bin_in <= dbin;
|
||||
@(posedge clk);
|
||||
doppler_valid <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Feed a complete frame with uniform noise + optional targets
|
||||
// noise_level: base I value for all cells
|
||||
// num_targets: number of target cells
|
||||
// tgt_range[0..3], tgt_doppler[0..3], tgt_level[0..3]: target parameters
|
||||
reg [5:0] tgt_range [0:7];
|
||||
reg [4:0] tgt_doppler[0:7];
|
||||
reg [15:0] tgt_level [0:7];
|
||||
integer num_targets;
|
||||
|
||||
task feed_frame;
|
||||
input [15:0] noise_level;
|
||||
integer r, d, t;
|
||||
reg is_target;
|
||||
reg [15:0] i_val;
|
||||
begin
|
||||
// Feed all 64*32 = 2048 samples in Doppler processor output order:
|
||||
// For each range bin, output all 32 Doppler bins
|
||||
for (r = 0; r < NUM_RANGE; r = r + 1) begin
|
||||
for (d = 0; d < NUM_DOPPLER; d = d + 1) begin
|
||||
is_target = 0;
|
||||
i_val = noise_level;
|
||||
for (t = 0; t < num_targets; t = t + 1) begin
|
||||
if (r == tgt_range[t] && d == tgt_doppler[t]) begin
|
||||
is_target = 1;
|
||||
i_val = tgt_level[t];
|
||||
end
|
||||
end
|
||||
feed_sample(r[5:0], d[4:0], $signed(i_val), 16'sd0);
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task pulse_frame_complete;
|
||||
begin
|
||||
@(posedge clk);
|
||||
frame_complete <= 1'b1;
|
||||
@(posedge clk);
|
||||
frame_complete <= 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// Wait for CFAR processing to complete (with timeout)
|
||||
task wait_cfar_done;
|
||||
input integer timeout_cycles;
|
||||
integer countdown;
|
||||
begin
|
||||
countdown = timeout_cycles;
|
||||
while (cfar_busy && countdown > 0) begin
|
||||
@(posedge clk);
|
||||
countdown = countdown - 1;
|
||||
end
|
||||
if (countdown == 0)
|
||||
$display("[WARN] CFAR processing timeout after %0d cycles", timeout_cycles);
|
||||
end
|
||||
endtask
|
||||
|
||||
// Capture flagged detections during CFAR processing
|
||||
task capture_detections;
|
||||
input integer timeout_cycles;
|
||||
integer countdown;
|
||||
begin
|
||||
det_cap_count = 0;
|
||||
det_total_valid = 0;
|
||||
countdown = timeout_cycles;
|
||||
while ((cfar_busy || countdown == timeout_cycles) && countdown > 0) begin
|
||||
@(posedge clk);
|
||||
countdown = countdown - 1;
|
||||
if (detect_valid) begin
|
||||
det_total_valid = det_total_valid + 1;
|
||||
// Only capture flagged detections (saves buffer space)
|
||||
if (detect_flag && det_cap_count < 256) begin
|
||||
det_cap_range[det_cap_count] = detect_range;
|
||||
det_cap_doppler[det_cap_count] = detect_doppler;
|
||||
det_cap_mag[det_cap_count] = detect_magnitude;
|
||||
det_cap_thr[det_cap_count] = detect_threshold;
|
||||
det_cap_flag[det_cap_count] = 1'b1;
|
||||
det_cap_count = det_cap_count + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
// Count flagged detections (all captured entries are flagged)
|
||||
function integer count_flagged_detections;
|
||||
input integer dummy;
|
||||
begin
|
||||
count_flagged_detections = det_cap_count;
|
||||
end
|
||||
endfunction
|
||||
|
||||
// Find if a specific (range, doppler) was flagged as detection
|
||||
function integer find_detection;
|
||||
input [5:0] rbin;
|
||||
input [4:0] dbin;
|
||||
integer i;
|
||||
begin
|
||||
find_detection = 0;
|
||||
for (i = 0; i < det_cap_count; i = i + 1) begin
|
||||
if (det_cap_flag[i] && det_cap_range[i] == rbin && det_cap_doppler[i] == dbin)
|
||||
find_detection = 1;
|
||||
end
|
||||
end
|
||||
endfunction
|
||||
|
||||
// ============================================================================
|
||||
// MAIN TEST SEQUENCE
|
||||
// ============================================================================
|
||||
integer i;
|
||||
integer flagged;
|
||||
|
||||
initial begin
|
||||
$dumpfile("tb_cfar_ca.vcd");
|
||||
$dumpvars(0, tb_cfar_ca);
|
||||
|
||||
pass_count = 0;
|
||||
fail_count = 0;
|
||||
num_targets = 0;
|
||||
|
||||
// Default config: CA-CFAR, guard=2, train=8, alpha=3.0 (Q4.4 = 0x30)
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h30;
|
||||
cfg_cfar_mode = 2'b00;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_simple_threshold = 16'd5000;
|
||||
|
||||
// ================================================================
|
||||
// T1: CFAR disabled → simple threshold pass-through
|
||||
// ================================================================
|
||||
test_num = 1;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b0;
|
||||
cfg_simple_threshold = 16'd100;
|
||||
|
||||
// Feed a few samples: one below threshold, one above
|
||||
feed_sample(6'd0, 5'd0, 16'sd50, 16'sd0); // mag=50 < 100 → no detect
|
||||
@(posedge clk); // let detect_valid propagate
|
||||
check(1, "T1.1: CFAR disabled, below threshold -> no flag", detect_flag == 0);
|
||||
|
||||
feed_sample(6'd1, 5'd0, 16'sd200, 16'sd0); // mag=200 > 100 → detect
|
||||
@(posedge clk);
|
||||
check(1, "T1.2: CFAR disabled, above threshold -> flag=1", detect_flag == 1);
|
||||
|
||||
// ================================================================
|
||||
// T2: CA-CFAR uniform noise → no detections
|
||||
// ================================================================
|
||||
test_num = 2;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10; // alpha=1.0 in Q4.4 (low threshold → still no detect if uniform)
|
||||
cfg_cfar_mode = 2'b00;
|
||||
num_targets = 0;
|
||||
|
||||
feed_frame(16'd1000); // Uniform noise: all cells = 1000
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
flagged = count_flagged_detections(0);
|
||||
// With uniform noise and alpha >= 1.0, threshold ≈ noise level
|
||||
// Some edge cells might detect due to fewer training cells → lower threshold
|
||||
// Check that interior cells (away from edges) have no detections
|
||||
check(2, "T2: Uniform noise, CA-CFAR: few or no interior detections", flagged < 20);
|
||||
$display(" [INFO] T2: %0d detections out of %0d valid outputs (uniform noise)", flagged, det_total_valid);
|
||||
|
||||
// ================================================================
|
||||
// T3: CA-CFAR single strong target → detection at correct bin
|
||||
// ================================================================
|
||||
test_num = 3;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10; // alpha=1.0
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd32;
|
||||
tgt_doppler[0] = 5'd16;
|
||||
tgt_level[0] = 16'd20000; // 20x noise level
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
flagged = count_flagged_detections(0);
|
||||
check(3, "T3.1: Single strong target detected", flagged > 0);
|
||||
check(3, "T3.2: Target at (32,16) flagged", find_detection(6'd32, 5'd16) == 1);
|
||||
$display(" [INFO] T3: %0d total detections", flagged);
|
||||
|
||||
// ================================================================
|
||||
// T4: Two targets well-separated → both detected
|
||||
// ================================================================
|
||||
test_num = 4;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10;
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
num_targets = 2;
|
||||
tgt_range[0] = 6'd10; tgt_doppler[0] = 5'd5; tgt_level[0] = 16'd25000;
|
||||
tgt_range[1] = 6'd50; tgt_doppler[1] = 5'd20; tgt_level[1] = 16'd25000;
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
flagged = count_flagged_detections(0);
|
||||
check(4, "T4.1: Two targets: at least 2 detections", flagged >= 2);
|
||||
check(4, "T4.2: Target at (10,5) detected", find_detection(6'd10, 5'd5) == 1);
|
||||
check(4, "T4.3: Target at (50,20) detected", find_detection(6'd50, 5'd20) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T5: Edge targets → range bin 0 and 63
|
||||
// ================================================================
|
||||
test_num = 5;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd4;
|
||||
cfg_alpha = 8'h08; // alpha=0.5 (low → more sensitive at edges)
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
num_targets = 2;
|
||||
tgt_range[0] = 6'd0; tgt_doppler[0] = 5'd0; tgt_level[0] = 16'd20000;
|
||||
tgt_range[1] = 6'd63; tgt_doppler[1] = 5'd0; tgt_level[1] = 16'd20000;
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
// Edge targets have fewer training cells → lower threshold → should still detect
|
||||
check(5, "T5.1: Edge target at (0,0) detected", find_detection(6'd0, 5'd0) == 1);
|
||||
check(5, "T5.2: Edge target at (63,0) detected", find_detection(6'd63, 5'd0) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T6: GO-CFAR with asymmetric noise
|
||||
// ================================================================
|
||||
test_num = 6;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd1;
|
||||
cfg_train_cells = 5'd4;
|
||||
cfg_alpha = 8'h10; // alpha=1.0
|
||||
cfg_cfar_mode = 2'b01; // GO-CFAR
|
||||
|
||||
// Target at range=32, noise higher on one side (simulate clutter edge)
|
||||
// Leading (range<32): noise=1000, Lagging (range>32): noise=5000
|
||||
// GO-CFAR should use max(leading_avg, lagging_avg) = lagging
|
||||
// Target must exceed the higher threshold
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd32; tgt_doppler[0] = 5'd0; tgt_level[0] = 16'd25000;
|
||||
|
||||
// Custom frame: asymmetric noise
|
||||
begin : t6_feed
|
||||
integer r, d;
|
||||
reg [15:0] noise;
|
||||
for (r = 0; r < NUM_RANGE; r = r + 1) begin
|
||||
for (d = 0; d < NUM_DOPPLER; d = d + 1) begin
|
||||
if (r == 32 && d == 0)
|
||||
noise = 16'd25000;
|
||||
else if (r < 32)
|
||||
noise = 16'd1000;
|
||||
else
|
||||
noise = 16'd5000;
|
||||
feed_sample(r[5:0], d[4:0], $signed(noise), 16'sd0);
|
||||
end
|
||||
end
|
||||
end
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
check(6, "T6: GO-CFAR with asymmetric noise: target at (32,0) detected", find_detection(6'd32, 5'd0) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T7: SO-CFAR → more sensitive (lower threshold)
|
||||
// ================================================================
|
||||
test_num = 7;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd1;
|
||||
cfg_train_cells = 5'd4;
|
||||
cfg_alpha = 8'h10;
|
||||
cfg_cfar_mode = 2'b10; // SO-CFAR
|
||||
|
||||
// Same asymmetric scene — SO-CFAR uses min(leading_avg, lagging_avg)
|
||||
// Threshold lower → should detect more easily
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd32; tgt_doppler[0] = 5'd0; tgt_level[0] = 16'd8000;
|
||||
|
||||
begin : t7_feed
|
||||
integer r, d;
|
||||
reg [15:0] noise;
|
||||
for (r = 0; r < NUM_RANGE; r = r + 1) begin
|
||||
for (d = 0; d < NUM_DOPPLER; d = d + 1) begin
|
||||
if (r == 32 && d == 0)
|
||||
noise = 16'd8000;
|
||||
else if (r < 32)
|
||||
noise = 16'd1000;
|
||||
else
|
||||
noise = 16'd5000;
|
||||
feed_sample(r[5:0], d[4:0], $signed(noise), 16'sd0);
|
||||
end
|
||||
end
|
||||
end
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
check(7, "T7: SO-CFAR: target at (32,0) with modest level detected", find_detection(6'd32, 5'd0) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T8: High alpha → fewer detections
|
||||
// ================================================================
|
||||
test_num = 8;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'hF0; // alpha=15.0 (very high → very few detections)
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd32; tgt_doppler[0] = 5'd16; tgt_level[0] = 16'd5000;
|
||||
// Target only 5x noise → shouldn't exceed alpha=15 threshold
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
flagged = count_flagged_detections(0);
|
||||
check(8, "T8: High alpha=15.0: weak target NOT detected", find_detection(6'd32, 5'd16) == 0);
|
||||
$display(" [INFO] T8: %0d detections with alpha=15.0", flagged);
|
||||
|
||||
// ================================================================
|
||||
// T9: Guard cells prevent target leakage
|
||||
// ================================================================
|
||||
test_num = 9;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd3; // 3 guard cells each side
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10; // alpha=1.0
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
// Strong target at range=32. Neighbors (range 29-31, 33-35) are guard cells
|
||||
// and should NOT inflate the noise estimate.
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd32; tgt_doppler[0] = 5'd0; tgt_level[0] = 16'd30000;
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
check(9, "T9: Guard cells: target at (32,0) detected with guard=3", find_detection(6'd32, 5'd0) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T10: Corner case — zero guard, minimal train
|
||||
// ================================================================
|
||||
test_num = 10;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd0; // No guard cells
|
||||
cfg_train_cells = 5'd1; // Minimum training
|
||||
cfg_alpha = 8'h10; // alpha=1.0
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd32; tgt_doppler[0] = 5'd0; tgt_level[0] = 16'd10000;
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
check(10, "T10: guard=0, train=1: target detected", find_detection(6'd32, 5'd0) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T11: Reset during processing → clean recovery
|
||||
// ================================================================
|
||||
test_num = 11;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10;
|
||||
cfg_cfar_mode = 2'b00;
|
||||
num_targets = 0;
|
||||
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
|
||||
// Wait partway into CFAR processing
|
||||
repeat (200) @(posedge clk);
|
||||
check(11, "T11.1: CFAR busy during processing", cfar_busy == 1);
|
||||
|
||||
// Reset mid-processing
|
||||
reset_n = 0;
|
||||
repeat (5) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (5) @(posedge clk);
|
||||
|
||||
check(11, "T11.2: After reset, CFAR not busy", cfar_busy == 0);
|
||||
check(11, "T11.3: After reset, state is IDLE", dut.state == 4'd0);
|
||||
|
||||
// ================================================================
|
||||
// T12: Back-to-back frames
|
||||
// ================================================================
|
||||
test_num = 12;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10;
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
// Frame 1: no targets
|
||||
num_targets = 0;
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
wait_cfar_done(20000);
|
||||
|
||||
// Frame 2: one target
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd20; tgt_doppler[0] = 5'd10; tgt_level[0] = 16'd25000;
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
capture_detections(20000);
|
||||
|
||||
check(12, "T12: Back-to-back frame 2: target at (20,10) detected", find_detection(6'd20, 5'd10) == 1);
|
||||
|
||||
// ================================================================
|
||||
// T13: detect_count accumulates
|
||||
// ================================================================
|
||||
test_num = 13;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10;
|
||||
cfg_cfar_mode = 2'b00;
|
||||
|
||||
num_targets = 1;
|
||||
tgt_range[0] = 6'd30; tgt_doppler[0] = 5'd0; tgt_level[0] = 16'd20000;
|
||||
|
||||
// Frame 1
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
wait_cfar_done(20000);
|
||||
begin : t13_save
|
||||
reg [15:0] count_after_frame1;
|
||||
count_after_frame1 = detect_count;
|
||||
$display(" [INFO] T13: detect_count after frame 1 = %0d", count_after_frame1);
|
||||
|
||||
// Frame 2 (same target)
|
||||
feed_frame(16'd1000);
|
||||
pulse_frame_complete;
|
||||
wait_cfar_done(20000);
|
||||
$display(" [INFO] T13: detect_count after frame 2 = %0d", detect_count);
|
||||
|
||||
check(13, "T13: detect_count increases after second frame", detect_count > count_after_frame1);
|
||||
end
|
||||
|
||||
// ================================================================
|
||||
// T14: cfar_busy signal
|
||||
// ================================================================
|
||||
test_num = 14;
|
||||
do_reset;
|
||||
cfg_cfar_enable = 1'b1;
|
||||
cfg_guard_cells = 4'd2;
|
||||
cfg_train_cells = 5'd8;
|
||||
cfg_alpha = 8'h10;
|
||||
cfg_cfar_mode = 2'b00;
|
||||
num_targets = 0;
|
||||
|
||||
check(14, "T14.1: Initially not busy", cfar_busy == 0);
|
||||
|
||||
// Start feeding data
|
||||
feed_sample(6'd0, 5'd0, 16'sd1000, 16'sd0);
|
||||
@(posedge clk);
|
||||
check(14, "T14.2: Busy after first sample", cfar_busy == 1);
|
||||
|
||||
// Feed rest of frame
|
||||
begin : t14_feed
|
||||
integer r, d;
|
||||
for (r = 0; r < NUM_RANGE; r = r + 1) begin
|
||||
for (d = 0; d < NUM_DOPPLER; d = d + 1) begin
|
||||
if (r == 0 && d == 0) begin
|
||||
// Already fed
|
||||
end else begin
|
||||
feed_sample(r[5:0], d[4:0], 16'sd1000, 16'sd0);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
pulse_frame_complete;
|
||||
wait_cfar_done(20000);
|
||||
repeat (5) @(posedge clk);
|
||||
|
||||
check(14, "T14.3: Not busy after processing complete", cfar_busy == 0);
|
||||
|
||||
// ================================================================
|
||||
// SUMMARY
|
||||
// ================================================================
|
||||
$display("");
|
||||
$display("============================================");
|
||||
$display(" CFAR CA Testbench Results");
|
||||
$display("============================================");
|
||||
$display(" PASS: %0d", pass_count);
|
||||
$display(" FAIL: %0d", fail_count);
|
||||
$display("============================================");
|
||||
|
||||
if (fail_count > 0)
|
||||
$display("[FAIL] %0d test(s) failed", fail_count);
|
||||
else
|
||||
$display("[PASS] All %0d tests passed", pass_count);
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
// ============================================================================
|
||||
// WATCHDOG TIMEOUT
|
||||
// ============================================================================
|
||||
initial begin
|
||||
#50_000_000; // 50 ms
|
||||
$display("[FAIL] Global watchdog timeout");
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -152,7 +152,9 @@ radar_receiver_final dut (
|
||||
.host_chirps_per_elev(6'd32),
|
||||
|
||||
// Fix 3: digital gain control — pass-through for golden reference
|
||||
.host_gain_shift(4'd0)
|
||||
.host_gain_shift(4'd0),
|
||||
// CFAR: frame-complete output (not used in this TB)
|
||||
.doppler_frame_done_out()
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
* G11: Processing Latency Budgets (2 checks)
|
||||
* G12: Watchdog / Liveness (2 checks)
|
||||
* G13: Doppler/Chirps Mismatch Protection (8 checks) [Fix 4]
|
||||
* G14: CFAR Configuration Registers (13 checks) [CFAR integration]
|
||||
*
|
||||
* Compile:
|
||||
* iverilog -g2001 -DSIMULATION -o tb/tb_system_e2e.vvp \
|
||||
@@ -1039,6 +1040,94 @@ initial begin
|
||||
|
||||
$display("");
|
||||
|
||||
// ================================================================
|
||||
// GROUP 14: CFAR CONFIGURATION REGISTERS (CFAR integration)
|
||||
// ================================================================
|
||||
$display("--- Group 14: CFAR Configuration Registers ---");
|
||||
|
||||
// G14.1: Verify CFAR defaults after reset (we did a mid-test reset in G9)
|
||||
// The registers were re-loaded in G9. Send fresh values to verify write path.
|
||||
|
||||
// --- Range Mode Register (0x20, Fix 7) ---
|
||||
// G14.1: Set range_mode to short-range (0x01)
|
||||
bfm_send_cmd(8'h20, 8'h00, 16'h0001);
|
||||
check(dut.host_range_mode == 2'b01,
|
||||
"G14.1: Opcode 0x20 -> host_range_mode = 2'b01 (short)");
|
||||
|
||||
// G14.2: Set range_mode to long-range (0x02)
|
||||
bfm_send_cmd(8'h20, 8'h00, 16'h0002);
|
||||
check(dut.host_range_mode == 2'b10,
|
||||
"G14.2: Opcode 0x20 -> host_range_mode = 2'b10 (long)");
|
||||
|
||||
// G14.3: Restore range_mode to auto (0x00)
|
||||
bfm_send_cmd(8'h20, 8'h00, 16'h0000);
|
||||
check(dut.host_range_mode == 2'b00,
|
||||
"G14.3: Opcode 0x20 -> host_range_mode = 2'b00 (auto)");
|
||||
|
||||
// --- CFAR Guard Cells (0x21) ---
|
||||
// G14.4: Set guard cells to 4
|
||||
bfm_send_cmd(8'h21, 8'h00, 16'h0004);
|
||||
check(dut.host_cfar_guard == 4'd4,
|
||||
"G14.4: Opcode 0x21 -> host_cfar_guard = 4");
|
||||
|
||||
// G14.5: Set guard cells to 0 (valid edge case)
|
||||
bfm_send_cmd(8'h21, 8'h00, 16'h0000);
|
||||
check(dut.host_cfar_guard == 4'd0,
|
||||
"G14.5: Opcode 0x21 -> host_cfar_guard = 0 (edge case)");
|
||||
|
||||
// --- CFAR Training Cells (0x22) ---
|
||||
// G14.6: Set training cells to 16
|
||||
bfm_send_cmd(8'h22, 8'h00, 16'h0010);
|
||||
check(dut.host_cfar_train == 5'd16,
|
||||
"G14.6: Opcode 0x22 -> host_cfar_train = 16");
|
||||
|
||||
// G14.7: Set training cells to 1 (minimum)
|
||||
bfm_send_cmd(8'h22, 8'h00, 16'h0001);
|
||||
check(dut.host_cfar_train == 5'd1,
|
||||
"G14.7: Opcode 0x22 -> host_cfar_train = 1 (min)");
|
||||
|
||||
// --- CFAR Alpha / Threshold Multiplier (0x23) ---
|
||||
// G14.8: Set alpha to 0x48 (4.5 in Q4.4)
|
||||
bfm_send_cmd(8'h23, 8'h00, 16'h0048);
|
||||
check(dut.host_cfar_alpha == 8'h48,
|
||||
"G14.8: Opcode 0x23 -> host_cfar_alpha = 0x48 (4.5 Q4.4)");
|
||||
|
||||
// G14.9: Set alpha to 0x10 (1.0 in Q4.4)
|
||||
bfm_send_cmd(8'h23, 8'h00, 16'h0010);
|
||||
check(dut.host_cfar_alpha == 8'h10,
|
||||
"G14.9: Opcode 0x23 -> host_cfar_alpha = 0x10 (1.0 Q4.4)");
|
||||
|
||||
// --- CFAR Mode (0x24) ---
|
||||
// G14.10: Set mode to GO-CFAR (0x01)
|
||||
bfm_send_cmd(8'h24, 8'h00, 16'h0001);
|
||||
check(dut.host_cfar_mode == 2'b01,
|
||||
"G14.10: Opcode 0x24 -> host_cfar_mode = 2'b01 (GO-CFAR)");
|
||||
|
||||
// G14.11: Set mode to SO-CFAR (0x02)
|
||||
bfm_send_cmd(8'h24, 8'h00, 16'h0002);
|
||||
check(dut.host_cfar_mode == 2'b10,
|
||||
"G14.11: Opcode 0x24 -> host_cfar_mode = 2'b10 (SO-CFAR)");
|
||||
|
||||
// --- CFAR Enable (0x25) ---
|
||||
// G14.12: Enable CFAR
|
||||
bfm_send_cmd(8'h25, 8'h00, 16'h0001);
|
||||
check(dut.host_cfar_enable == 1'b1,
|
||||
"G14.12: Opcode 0x25 -> host_cfar_enable = 1 (CFAR active)");
|
||||
|
||||
// G14.13: Disable CFAR (restore default)
|
||||
bfm_send_cmd(8'h25, 8'h00, 16'h0000);
|
||||
check(dut.host_cfar_enable == 1'b0,
|
||||
"G14.13: Opcode 0x25 -> host_cfar_enable = 0 (simple threshold)");
|
||||
|
||||
// Restore CFAR registers to safe defaults for remainder of sim
|
||||
bfm_send_cmd(8'h21, 8'h00, 16'h0002); // guard=2
|
||||
bfm_send_cmd(8'h22, 8'h00, 16'h0008); // train=8
|
||||
bfm_send_cmd(8'h23, 8'h00, 16'h0030); // alpha=3.0
|
||||
bfm_send_cmd(8'h24, 8'h00, 16'h0000); // mode=CA
|
||||
bfm_send_cmd(8'h25, 8'h00, 16'h0000); // enable=0
|
||||
|
||||
$display("");
|
||||
|
||||
// ================================================================
|
||||
// FINAL SUMMARY
|
||||
// ================================================================
|
||||
|
||||
Reference in New Issue
Block a user