mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-11 07:51:17 +00:00
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:
@@ -10,6 +10,10 @@ module plfm_chirp_controller_enhanced (
|
|||||||
input wire new_elevation,
|
input wire new_elevation,
|
||||||
input wire new_azimuth,
|
input wire new_azimuth,
|
||||||
input wire mixers_enable,
|
input wire mixers_enable,
|
||||||
|
// Range mode (CDC-synchronized into clk_120m by the caller).
|
||||||
|
// 2'b00 = 3 km — short chirps only (skip LONG_CHIRP/LONG_LISTEN)
|
||||||
|
// 2'b01 = long-range — dual chirp (first half long, second half short)
|
||||||
|
input wire [1:0] range_mode,
|
||||||
output reg [7:0] chirp_data,
|
output reg [7:0] chirp_data,
|
||||||
output reg chirp_valid,
|
output reg chirp_valid,
|
||||||
output wire new_chirp_frame,
|
output wire new_chirp_frame,
|
||||||
@@ -80,7 +84,11 @@ reg [7:0] long_chirp_rd_data;
|
|||||||
assign chirp__toggling = new_chirp;
|
assign chirp__toggling = new_chirp;
|
||||||
assign elevation__toggling = new_elevation;
|
assign elevation__toggling = new_elevation;
|
||||||
assign azimuth__toggling = new_azimuth;
|
assign azimuth__toggling = new_azimuth;
|
||||||
assign new_chirp_frame = (current_state == IDLE && next_state == LONG_CHIRP);
|
// new_chirp_frame fires on IDLE -> first active state (long or short
|
||||||
|
// depending on range_mode).
|
||||||
|
assign new_chirp_frame = (current_state == IDLE &&
|
||||||
|
(next_state == LONG_CHIRP ||
|
||||||
|
next_state == SHORT_CHIRP));
|
||||||
|
|
||||||
// Mixer TX/RX sequencing — mutually exclusive based on chirp FSM state.
|
// Mixer TX/RX sequencing — mutually exclusive based on chirp FSM state.
|
||||||
// TX mixer active during chirp transmission, RX mixer during listen.
|
// TX mixer active during chirp transmission, RX mixer during listen.
|
||||||
@@ -179,10 +187,20 @@ end
|
|||||||
always @(*) begin
|
always @(*) begin
|
||||||
case (current_state)
|
case (current_state)
|
||||||
IDLE: begin
|
IDLE: begin
|
||||||
if (chirp__toggling && mixers_enable)
|
// 3 km mode skips the long-chirp half entirely — LONG_CHIRP's
|
||||||
next_state = LONG_CHIRP;
|
// 4500 m blind zone exceeds the 3 km max range, so long chirps
|
||||||
else
|
// would just pollute the receive window. Go straight to
|
||||||
|
// SHORT_CHIRP and let the SHORT_LISTEN -> DONE guard
|
||||||
|
// (chirp_counter == CHIRP_MAX-1) terminate after CHIRP_MAX
|
||||||
|
// short chirps.
|
||||||
|
if (chirp__toggling && mixers_enable) begin
|
||||||
|
if (range_mode == `RP_RANGE_MODE_3KM)
|
||||||
|
next_state = SHORT_CHIRP;
|
||||||
|
else
|
||||||
|
next_state = LONG_CHIRP;
|
||||||
|
end else begin
|
||||||
next_state = IDLE;
|
next_state = IDLE;
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
LONG_CHIRP: begin
|
LONG_CHIRP: begin
|
||||||
|
|||||||
@@ -507,6 +507,9 @@ radar_transmitter tx_inst (
|
|||||||
.stm32_cs_adar3_1v8(stm32_cs_adar3_1v8),
|
.stm32_cs_adar3_1v8(stm32_cs_adar3_1v8),
|
||||||
.stm32_cs_adar4_1v8(stm32_cs_adar4_1v8),
|
.stm32_cs_adar4_1v8(stm32_cs_adar4_1v8),
|
||||||
|
|
||||||
|
// Host range mode (clk_100m domain; CDC'd inside radar_transmitter)
|
||||||
|
.host_range_mode(host_range_mode),
|
||||||
|
|
||||||
// Beam Position Tracking
|
// Beam Position Tracking
|
||||||
.current_elevation(tx_current_elevation),
|
.current_elevation(tx_current_elevation),
|
||||||
.current_azimuth(tx_current_azimuth),
|
.current_azimuth(tx_current_azimuth),
|
||||||
@@ -1024,7 +1027,12 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
8'h16: host_gain_shift <= usb_cmd_value[3:0]; // Fix 3: digital gain
|
8'h16: host_gain_shift <= usb_cmd_value[3:0]; // Fix 3: digital gain
|
||||||
8'h20: host_range_mode <= usb_cmd_value[1:0]; // Range mode
|
// Range mode: clamp reserved codes (2'b10, 2'b11) to the safe
|
||||||
|
// 3 km default so a garbled host write cannot silently enable
|
||||||
|
// long-range TX behaviour.
|
||||||
|
8'h20: host_range_mode <= (usb_cmd_value[1:0] > 2'b01)
|
||||||
|
? `RP_RANGE_MODE_3KM
|
||||||
|
: usb_cmd_value[1:0];
|
||||||
// CFAR configuration opcodes
|
// CFAR configuration opcodes
|
||||||
8'h21: host_cfar_guard <= usb_cmd_value[3:0];
|
8'h21: host_cfar_guard <= usb_cmd_value[3:0];
|
||||||
8'h22: host_cfar_train <= usb_cmd_value[4:0];
|
8'h22: host_cfar_train <= usb_cmd_value[4:0];
|
||||||
|
|||||||
@@ -38,6 +38,11 @@ module radar_transmitter(
|
|||||||
input wire stm32_new_azimuth,
|
input wire stm32_new_azimuth,
|
||||||
input wire stm32_mixers_enable,
|
input wire stm32_mixers_enable,
|
||||||
|
|
||||||
|
// Range mode from host (clk_100m domain, opcode 0x20). CDC'd to clk_120m_dac
|
||||||
|
// internally and fed to plfm_chirp_controller_enhanced so 3 km mode skips
|
||||||
|
// the long-chirp half of the waveform entirely.
|
||||||
|
input wire [1:0] host_range_mode,
|
||||||
|
|
||||||
output wire fpga_rf_switch,
|
output wire fpga_rf_switch,
|
||||||
|
|
||||||
// ADAR1000 Control Interface
|
// ADAR1000 Control Interface
|
||||||
@@ -143,6 +148,26 @@ always @(posedge clk_120m_dac or negedge reset_n) begin
|
|||||||
end
|
end
|
||||||
assign new_chirp_pulse_120m = chirp_toggle_120m ^ chirp_toggle_120m_prev;
|
assign new_chirp_pulse_120m = chirp_toggle_120m ^ chirp_toggle_120m_prev;
|
||||||
|
|
||||||
|
// Sync host_range_mode (clk_100m level) to clk_120m_dac domain.
|
||||||
|
// Only bit[0] toggles between the two valid codes (2'b00 / 2'b01) since
|
||||||
|
// reserved codes are clamped at the source, so per-bit 2FF synchronization
|
||||||
|
// has no coherency hazard.
|
||||||
|
wire [1:0] range_mode_120m;
|
||||||
|
cdc_single_bit #(.STAGES(2)) cdc_range_mode_bit0 (
|
||||||
|
.src_clk(clk_100m),
|
||||||
|
.dst_clk(clk_120m_dac),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.src_signal(host_range_mode[0]),
|
||||||
|
.dst_signal(range_mode_120m[0])
|
||||||
|
);
|
||||||
|
cdc_single_bit #(.STAGES(2)) cdc_range_mode_bit1 (
|
||||||
|
.src_clk(clk_100m),
|
||||||
|
.dst_clk(clk_120m_dac),
|
||||||
|
.reset_n(reset_n),
|
||||||
|
.src_signal(host_range_mode[1]),
|
||||||
|
.dst_signal(range_mode_120m[1])
|
||||||
|
);
|
||||||
|
|
||||||
// Sync stm32_mixers_enable (async GPIO level) to clk_120m_dac domain
|
// Sync stm32_mixers_enable (async GPIO level) to clk_120m_dac domain
|
||||||
cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m (
|
cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m (
|
||||||
.src_clk(clk_100m), // Treat as pseudo-source (GPIO is async)
|
.src_clk(clk_100m), // Treat as pseudo-source (GPIO is async)
|
||||||
@@ -213,6 +238,7 @@ plfm_chirp_controller_enhanced plfm_chirp_inst (
|
|||||||
.new_azimuth(new_azimuth_pulse),
|
.new_azimuth(new_azimuth_pulse),
|
||||||
.new_chirp_frame(new_chirp_frame),
|
.new_chirp_frame(new_chirp_frame),
|
||||||
.mixers_enable(mixers_enable_120m), // CDC-synchronized level in clk_120m domain
|
.mixers_enable(mixers_enable_120m), // CDC-synchronized level in clk_120m domain
|
||||||
|
.range_mode(range_mode_120m), // CDC-synchronized range mode in clk_120m domain
|
||||||
.chirp_data(chirp_data),
|
.chirp_data(chirp_data),
|
||||||
.chirp_valid(chirp_valid),
|
.chirp_valid(chirp_valid),
|
||||||
.chirp_done(chirp_sequence_done),
|
.chirp_done(chirp_sequence_done),
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ always #5 clk_100m = ~clk_100m;
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
reg new_chirp, new_elevation, new_azimuth;
|
reg new_chirp, new_elevation, new_azimuth;
|
||||||
reg mixers_enable;
|
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 [7:0] chirp_data;
|
||||||
wire chirp_valid;
|
wire chirp_valid;
|
||||||
@@ -78,6 +79,7 @@ plfm_chirp_controller_enhanced #(
|
|||||||
.new_elevation(new_elevation),
|
.new_elevation(new_elevation),
|
||||||
.new_azimuth(new_azimuth),
|
.new_azimuth(new_azimuth),
|
||||||
.mixers_enable(mixers_enable),
|
.mixers_enable(mixers_enable),
|
||||||
|
.range_mode(range_mode),
|
||||||
.chirp_data(chirp_data),
|
.chirp_data(chirp_data),
|
||||||
.chirp_valid(chirp_valid),
|
.chirp_valid(chirp_valid),
|
||||||
.new_chirp_frame(new_chirp_frame),
|
.new_chirp_frame(new_chirp_frame),
|
||||||
@@ -184,6 +186,7 @@ initial begin
|
|||||||
new_elevation = 0;
|
new_elevation = 0;
|
||||||
new_azimuth = 0;
|
new_azimuth = 0;
|
||||||
mixers_enable = 0;
|
mixers_enable = 0;
|
||||||
|
range_mode = 2'b01; // Default: long-range (dual) — matches existing tests
|
||||||
|
|
||||||
$display("");
|
$display("");
|
||||||
$display("============================================================");
|
$display("============================================================");
|
||||||
@@ -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_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);
|
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
|
// SUMMARY
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|||||||
@@ -1166,10 +1166,19 @@ initial begin
|
|||||||
check(dut.host_range_mode == 2'b01,
|
check(dut.host_range_mode == 2'b01,
|
||||||
"G14.1: Opcode 0x20 -> host_range_mode = 2'b01 (long-range)");
|
"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);
|
bfm_send_cmd(8'h20, 8'h00, 16'h0002);
|
||||||
check(dut.host_range_mode == 2'b10,
|
check(dut.host_range_mode == 2'b00,
|
||||||
"G14.2: Opcode 0x20 -> host_range_mode = 2'b10 (reserved)");
|
"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)
|
// G14.3: Restore range_mode to 3 km (0x00)
|
||||||
bfm_send_cmd(8'h20, 8'h00, 16'h0000);
|
bfm_send_cmd(8'h20, 8'h00, 16'h0000);
|
||||||
|
|||||||
Reference in New Issue
Block a user