fix(fpga): TX range-mode awareness + clamp reserved host codes

C-1: plfm_chirp_controller was not range_mode-aware; it always ran
LONG_CHIRP for CHIRP_MAX/2 chirps even when the host selected 3 km
mode, where the ~4.5 km blind zone exceeds the 3 km max range and
pollutes the RX window. IDLE now branches straight to SHORT_CHIRP
when range_mode == RP_RANGE_MODE_3KM. host_range_mode is passed from
radar_system_top through radar_transmitter, CDC'd per-bit from
clk_100m to clk_120m_dac (coherency-safe: reserved codes are clamped
at the source so only bit[0] toggles).

S-3: opcode 0x20 now clamps reserved range-mode codes (2'b10, 2'b11)
to the 3 km default so a garbled host write cannot silently enable
long-range TX behaviour.

Regression: tb_chirp_controller adds a 3 km-mode group (5 checks)
verifying IDLE->SHORT_CHIRP skip path and DONE after CHIRP_MAX short
chirps; tb_system_e2e G14 labels updated for clamped reserved codes.
32/32 regression PASS (50/50 on chirp TB).
This commit is contained in:
Jason
2026-04-22 20:26:43 +05:45
parent 27c9c22ad2
commit 6af79f9c74
5 changed files with 228 additions and 105 deletions
+63 -1
View File
@@ -42,6 +42,7 @@ always #5 clk_100m = ~clk_100m;
// =========================================================================
reg new_chirp, new_elevation, new_azimuth;
reg mixers_enable;
reg [1:0] range_mode; // 2'b00 = 3 km (short-only), 2'b01 = long-range (dual)
wire [7:0] chirp_data;
wire chirp_valid;
@@ -78,6 +79,7 @@ plfm_chirp_controller_enhanced #(
.new_elevation(new_elevation),
.new_azimuth(new_azimuth),
.mixers_enable(mixers_enable),
.range_mode(range_mode),
.chirp_data(chirp_data),
.chirp_valid(chirp_valid),
.new_chirp_frame(new_chirp_frame),
@@ -184,7 +186,8 @@ initial begin
new_elevation = 0;
new_azimuth = 0;
mixers_enable = 0;
range_mode = 2'b01; // Default: long-range (dual) matches existing tests
$display("");
$display("============================================================");
$display(" CHIRP CONTROLLER TESTBENCH");
@@ -493,6 +496,65 @@ initial begin
check("ADAR load pins: adar_tx_load_1 is 0", adar_tx_load_1 == 1'b0);
check("ADAR load pins: adar_rx_load_1 is 0", adar_rx_load_1 == 1'b0);
// =====================================================================
// TEST GROUP 8: RANGE MODE 3 KM SHORT-ONLY PATH (C-1)
//
// Bug: plfm_chirp_controller_enhanced was not range_mode-aware; the
// FSM always ran LONG_CHIRP/LONG_LISTEN for CHIRP_MAX/2 chirps even
// when the host had selected 3 km mode. LONG_CHIRP's ~4.5 km blind
// zone exceeds the 3 km max, so those long chirps pollute the RX
// window. Fix: IDLE branches to SHORT_CHIRP directly when
// range_mode == RP_RANGE_MODE_3KM.
//
// These checks verify the skip path: no LONG_CHIRP ever entered, FSM
// reaches DONE after CHIRP_MAX short chirps.
// =====================================================================
$display("--- Group 8: Range Mode 3 km (C-1) ---");
// Full reset into 3 km mode.
reset_n = 0;
mixers_enable = 0;
new_chirp = 0;
range_mode = 2'b00; // 3 km short-only
#100;
reset_n = 1;
@(posedge clk_120m);
mixers_enable = 1;
@(posedge clk_120m);
new_chirp = 1;
@(posedge clk_120m);
// T8.1: Skip LONG_CHIRP, enter SHORT_CHIRP directly from IDLE.
wait_for_state(3'b100, 10);
check("3 km mode: IDLE -> SHORT_CHIRP (skips LONG_CHIRP)",
dut.current_state == 3'b100);
// T8.2: FSM must never have entered any LONG_* state in this frame.
// (dut.current_state is sampled now; a transient visit would have
// been caught by the wait_for_state above landing in 3'b001/3'b010
// instead.)
check("3 km mode: FSM not in LONG_CHIRP",
dut.current_state != 3'b001);
check("3 km mode: FSM not in LONG_LISTEN",
dut.current_state != 3'b010);
// T8.3: After CHIRP_MAX short chirps, reach DONE. No GUARD_TIME in the
// 3 km path (GUARD bridges long->short; not needed here).
wait_for_state(3'b110,
(T2_SAMPLES + T2_RADAR_LISTENING) * CHIRP_MAX + 50);
check("3 km mode: reaches DONE after CHIRP_MAX short chirps",
dut.current_state == 3'b110);
// T8.4: chirp_counter cleared at DONE (same invariant as C-3).
new_chirp = 0;
@(posedge clk_120m);
check("3 km mode: chirp_counter reset to 0 after DONE",
chirp_counter == 6'd0);
// Restore default for any future tests.
range_mode = 2'b01;
// =====================================================================
// SUMMARY
// =====================================================================
+12 -3
View File
@@ -1166,10 +1166,19 @@ initial begin
check(dut.host_range_mode == 2'b01,
"G14.1: Opcode 0x20 -> host_range_mode = 2'b01 (long-range)");
// G14.2: Set range_mode to reserved value 0x02 (permissive: stored as-is)
// G14.2: Reserved value 0x02 must be clamped to 3 km (safe default)
// so a garbled host write cannot silently enable long-range TX.
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 (reserved)");
check(dut.host_range_mode == 2'b00,
"G14.2: Opcode 0x20 reserved=0x02 clamped to 2'b00 (3 km safe default)");
// G14.2b: Reserved value 0x03 also clamps to 3 km.
bfm_send_cmd(8'h20, 8'h00, 16'h0003);
check(dut.host_range_mode == 2'b00,
"G14.2b: Opcode 0x20 reserved=0x03 clamped to 2'b00 (3 km safe default)");
// Restore to a known-valid value before G14.3 asserts reset-to-3km.
bfm_send_cmd(8'h20, 8'h00, 16'h0001);
// G14.3: Restore range_mode to 3 km (0x00)
bfm_send_cmd(8'h20, 8'h00, 16'h0000);