From 6af79f9c74852b12d215e492415ad27bae4c66ce Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Wed, 22 Apr 2026 20:26:43 +0545 Subject: [PATCH] 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). --- 9_Firmware/9_2_FPGA/plfm_chirp_controller.v | 26 ++- 9_Firmware/9_2_FPGA/radar_system_top.v | 46 +++-- 9_Firmware/9_2_FPGA/radar_transmitter.v | 182 +++++++++++-------- 9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v | 64 ++++++- 9_Firmware/9_2_FPGA/tb/tb_system_e2e.v | 15 +- 5 files changed, 228 insertions(+), 105 deletions(-) diff --git a/9_Firmware/9_2_FPGA/plfm_chirp_controller.v b/9_Firmware/9_2_FPGA/plfm_chirp_controller.v index 4b70ebb..5da7cee 100644 --- a/9_Firmware/9_2_FPGA/plfm_chirp_controller.v +++ b/9_Firmware/9_2_FPGA/plfm_chirp_controller.v @@ -10,6 +10,10 @@ module plfm_chirp_controller_enhanced ( input wire new_elevation, input wire new_azimuth, 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 chirp_valid, output wire new_chirp_frame, @@ -80,7 +84,11 @@ reg [7:0] long_chirp_rd_data; assign chirp__toggling = new_chirp; assign elevation__toggling = new_elevation; 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. // TX mixer active during chirp transmission, RX mixer during listen. @@ -179,10 +187,20 @@ end always @(*) begin case (current_state) IDLE: begin - if (chirp__toggling && mixers_enable) - next_state = LONG_CHIRP; - else + // 3 km mode skips the long-chirp half entirely — LONG_CHIRP's + // 4500 m blind zone exceeds the 3 km max range, so long chirps + // 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; + end end LONG_CHIRP: begin diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index 41e5dde..f5daa51 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -182,12 +182,12 @@ wire [31:0] rx_doppler_output; wire rx_doppler_valid; wire [4:0] rx_doppler_bin; wire [`RP_RANGE_BIN_BITS-1:0] rx_range_bin; -wire [31:0] rx_range_profile; -wire rx_range_valid; -wire [15:0] rx_range_profile_decimated; -wire rx_range_profile_decimated_valid; -wire [15:0] rx_doppler_real; -wire [15:0] rx_doppler_imag; +wire [31:0] rx_range_profile; +wire rx_range_valid; +wire [15:0] rx_range_profile_decimated; +wire rx_range_profile_decimated_valid; +wire [15:0] rx_doppler_real; +wire [15:0] rx_doppler_imag; wire rx_doppler_data_valid; reg rx_detect_flag; // Threshold detection result (was rx_cfar_detection) reg rx_detect_valid; // Detection valid pulse (was rx_cfar_valid) @@ -506,7 +506,10 @@ radar_transmitter tx_inst ( .stm32_cs_adar2_1v8(stm32_cs_adar2_1v8), .stm32_cs_adar3_1v8(stm32_cs_adar3_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 .current_elevation(tx_current_elevation), .current_azimuth(tx_current_azimuth), @@ -542,12 +545,12 @@ radar_receiver_final rx_inst ( .doppler_bin(rx_doppler_bin), .range_bin(rx_range_bin), - // Range-profile outputs - .range_profile_i_out(rx_range_profile[15:0]), - .range_profile_q_out(rx_range_profile[31:16]), - .range_profile_valid_out(rx_range_valid), - .decimated_range_mag_out(rx_range_profile_decimated), - .decimated_range_valid_out(rx_range_profile_decimated_valid), + // Range-profile outputs + .range_profile_i_out(rx_range_profile[15:0]), + .range_profile_q_out(rx_range_profile[31:16]), + .range_profile_valid_out(rx_range_valid), + .decimated_range_mag_out(rx_range_profile_decimated), + .decimated_range_valid_out(rx_range_profile_decimated_valid), .host_mode(host_radar_mode), .host_trigger(host_trigger_pulse), @@ -731,10 +734,10 @@ end // DATA PACKING FOR USB // ============================================================================ -// USB range profile must match the advertised 512-bin frame payload, so source it -// from the decimated range stream that feeds Doppler rather than raw MF samples. -assign usb_range_profile = {16'd0, rx_range_profile_decimated}; -assign usb_range_valid = rx_range_profile_decimated_valid; +// USB range profile must match the advertised 512-bin frame payload, so source it +// from the decimated range stream that feeds Doppler rather than raw MF samples. +assign usb_range_profile = {16'd0, rx_range_profile_decimated}; +assign usb_range_valid = rx_range_profile_decimated_valid; assign usb_doppler_real = rx_doppler_real; assign usb_doppler_imag = rx_doppler_imag; @@ -963,7 +966,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin host_radar_mode <= 2'b01; // Default: auto-scan host_trigger_pulse <= 1'b0; host_detect_threshold <= 16'd10000; // Default threshold - host_stream_control <= `RP_STREAM_CTRL_DEFAULT; // Default: all streams, mag-only mode + host_stream_control <= `RP_STREAM_CTRL_DEFAULT; // Default: all streams, mag-only mode host_gain_shift <= 4'd0; // Default: pass-through (no gain change) // Gap 2: chirp timing defaults (match radar_mode_controller parameters) host_long_chirp_cycles <= 16'd3000; @@ -1024,7 +1027,12 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin end end 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 8'h21: host_cfar_guard <= usb_cmd_value[3:0]; 8'h22: host_cfar_train <= usb_cmd_value[4:0]; diff --git a/9_Firmware/9_2_FPGA/radar_transmitter.v b/9_Firmware/9_2_FPGA/radar_transmitter.v index 682e8c6..ebdbdbd 100644 --- a/9_Firmware/9_2_FPGA/radar_transmitter.v +++ b/9_Firmware/9_2_FPGA/radar_transmitter.v @@ -23,60 +23,65 @@ module radar_transmitter( input wire clk_100m, // System clock input wire clk_120m_dac, // 120MHz DAC clock input wire reset_n, // Reset synchronized to clk_120m_dac - input wire reset_100m_n, // Reset synchronized to clk_100m (for edge detectors/CDC) - - // DAC Interface - output wire [7:0] dac_data, - output wire dac_clk, - output wire dac_sleep, - output wire rx_mixer_en, + input wire reset_100m_n, // Reset synchronized to clk_100m (for edge detectors/CDC) + + // DAC Interface + output wire [7:0] dac_data, + output wire dac_clk, + output wire dac_sleep, + output wire rx_mixer_en, output wire tx_mixer_en, - // STM32 Control Interface - input wire stm32_new_chirp, - input wire stm32_new_elevation, - input wire stm32_new_azimuth, + // STM32 Control Interface + input wire stm32_new_chirp, + input wire stm32_new_elevation, + input wire stm32_new_azimuth, 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, - // ADAR1000 Control Interface - output wire adar_tx_load_1, - output wire adar_rx_load_1, - output wire adar_tx_load_2, - output wire adar_rx_load_2, - output wire adar_tx_load_3, - output wire adar_rx_load_3, - output wire adar_tx_load_4, - output wire adar_rx_load_4, - output wire adar_tr_1, - output wire adar_tr_2, - output wire adar_tr_3, - output wire adar_tr_4, - - // Level Shifter SPI Interface (STM32F7 to ADAR1000) - input wire stm32_sclk_3v3, - input wire stm32_mosi_3v3, - output wire stm32_miso_3v3, - input wire stm32_cs_adar1_3v3, - input wire stm32_cs_adar2_3v3, - input wire stm32_cs_adar3_3v3, - input wire stm32_cs_adar4_3v3, - - output wire stm32_sclk_1v8, - output wire stm32_mosi_1v8, - input wire stm32_miso_1v8, - output wire stm32_cs_adar1_1v8, - output wire stm32_cs_adar2_1v8, - output wire stm32_cs_adar3_1v8, + // ADAR1000 Control Interface + output wire adar_tx_load_1, + output wire adar_rx_load_1, + output wire adar_tx_load_2, + output wire adar_rx_load_2, + output wire adar_tx_load_3, + output wire adar_rx_load_3, + output wire adar_tx_load_4, + output wire adar_rx_load_4, + output wire adar_tr_1, + output wire adar_tr_2, + output wire adar_tr_3, + output wire adar_tr_4, + + // Level Shifter SPI Interface (STM32F7 to ADAR1000) + input wire stm32_sclk_3v3, + input wire stm32_mosi_3v3, + output wire stm32_miso_3v3, + input wire stm32_cs_adar1_3v3, + input wire stm32_cs_adar2_3v3, + input wire stm32_cs_adar3_3v3, + input wire stm32_cs_adar4_3v3, + + output wire stm32_sclk_1v8, + output wire stm32_mosi_1v8, + input wire stm32_miso_1v8, + output wire stm32_cs_adar1_1v8, + output wire stm32_cs_adar2_1v8, + output wire stm32_cs_adar3_1v8, output wire stm32_cs_adar4_1v8, - // Beam Position Tracking - output wire [5:0] current_elevation, - output wire [5:0] current_azimuth, + // Beam Position Tracking + output wire [5:0] current_elevation, + output wire [5:0] current_azimuth, output wire [5:0] current_chirp, - output wire new_chirp_frame - + output wire new_chirp_frame + ); @@ -143,6 +148,26 @@ always @(posedge clk_120m_dac or negedge reset_n) begin end 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 cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m ( .src_clk(clk_100m), // Treat as pseudo-source (GPIO is async) @@ -150,7 +175,7 @@ cdc_single_bit #(.STAGES(3)) cdc_mixers_en_120m ( .reset_n(reset_n), .src_signal(stm32_mixers_enable), .dst_signal(mixers_enable_120m) -); +); // CDC synchronizers: async STM32 GPIO inputs -> clk_100m domain // These prevent metastability in the edge detectors. Without these, @@ -201,7 +226,7 @@ edge_detector_enhanced azimuth_edge ( .reset_n(reset_100m_n), .signal_in(stm32_new_azimuth_sync), .rising_falling_edge(new_azimuth_pulse) -); +); // Enhanced PLFM Chirp Generation plfm_chirp_controller_enhanced plfm_chirp_inst ( @@ -212,38 +237,39 @@ plfm_chirp_controller_enhanced plfm_chirp_inst ( .new_elevation(new_elevation_pulse), .new_azimuth(new_azimuth_pulse), .new_chirp_frame(new_chirp_frame), - .mixers_enable(mixers_enable_120m), // CDC-synchronized level in clk_120m domain - .chirp_data(chirp_data), - .chirp_valid(chirp_valid), - .chirp_done(chirp_sequence_done), - .rf_switch_ctrl(fpga_rf_switch), - .rx_mixer_en(rx_mixer_en), - .tx_mixer_en(tx_mixer_en), - .adar_tx_load_1(adar_tx_load_1), - .adar_rx_load_1(adar_rx_load_1), - .adar_tx_load_2(adar_tx_load_2), - .adar_rx_load_2(adar_rx_load_2), - .adar_tx_load_3(adar_tx_load_3), - .adar_rx_load_3(adar_rx_load_3), - .adar_tx_load_4(adar_tx_load_4), - .adar_rx_load_4(adar_rx_load_4), - .adar_tr_1(adar_tr_1), - .adar_tr_2(adar_tr_2), - .adar_tr_3(adar_tr_3), - .adar_tr_4(adar_tr_4), - .elevation_counter(current_elevation), - .azimuth_counter(current_azimuth), - .chirp_counter(current_chirp) + .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_valid(chirp_valid), + .chirp_done(chirp_sequence_done), + .rf_switch_ctrl(fpga_rf_switch), + .rx_mixer_en(rx_mixer_en), + .tx_mixer_en(tx_mixer_en), + .adar_tx_load_1(adar_tx_load_1), + .adar_rx_load_1(adar_rx_load_1), + .adar_tx_load_2(adar_tx_load_2), + .adar_rx_load_2(adar_rx_load_2), + .adar_tx_load_3(adar_tx_load_3), + .adar_rx_load_3(adar_rx_load_3), + .adar_tx_load_4(adar_tx_load_4), + .adar_rx_load_4(adar_rx_load_4), + .adar_tr_1(adar_tr_1), + .adar_tr_2(adar_tr_2), + .adar_tr_3(adar_tr_3), + .adar_tr_4(adar_tr_4), + .elevation_counter(current_elevation), + .azimuth_counter(current_azimuth), + .chirp_counter(current_chirp) ); -// Enhanced DAC Interface -dac_interface_enhanced dac_interface_inst ( - .clk_120m(clk_120m_dac), - .reset_n(reset_n), - .chirp_data(chirp_data), - .chirp_valid(chirp_valid), - .dac_data(dac_data), - .dac_clk(dac_clk), - .dac_sleep(dac_sleep) +// Enhanced DAC Interface +dac_interface_enhanced dac_interface_inst ( + .clk_120m(clk_120m_dac), + .reset_n(reset_n), + .chirp_data(chirp_data), + .chirp_valid(chirp_valid), + .dac_data(dac_data), + .dac_clk(dac_clk), + .dac_sleep(dac_sleep) ); endmodule diff --git a/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v b/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v index 87615fd..02494de 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v +++ b/9_Firmware/9_2_FPGA/tb/tb_chirp_controller.v @@ -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 // ===================================================================== diff --git a/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v b/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v index 87de989..636b81e 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v +++ b/9_Firmware/9_2_FPGA/tb/tb_system_e2e.v @@ -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);