diff --git a/9_Firmware/9_2_FPGA/cfar_ca.v b/9_Firmware/9_2_FPGA/cfar_ca.v index 24d6189..02e22cb 100644 --- a/9_Firmware/9_2_FPGA/cfar_ca.v +++ b/9_Firmware/9_2_FPGA/cfar_ca.v @@ -102,11 +102,12 @@ // CFAR magnitude BRAM depth uses `RP_CFAR_MAG_DEPTH which already scales. module cfar_ca #( parameter NUM_RANGE_BINS = `RP_MAX_OUTPUT_BINS, // 512 (50T) / 4096 (200T) - parameter NUM_DOPPLER_BINS = `RP_NUM_DOPPLER_BINS, // 32 + parameter NUM_DOPPLER_BINS = `RP_NUM_DOPPLER_BINS, // 48 (PR-F) parameter MAG_WIDTH = 17, parameter ALPHA_WIDTH = 8, parameter MAX_GUARD = 8, - parameter MAX_TRAIN = 16 + parameter MAX_TRAIN = 16, + parameter DBIN_WIDTH = `RP_DOPPLER_BIN_WIDTH // 6 (PR-F) ) ( input wire clk, input wire reset_n, @@ -114,7 +115,7 @@ module cfar_ca #( // ========== DOPPLER PROCESSOR INPUTS ========== input wire [31:0] doppler_data, input wire doppler_valid, - input wire [4:0] doppler_bin_in, + input wire [DBIN_WIDTH-1:0] doppler_bin_in, input wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_in, // 9-bit (50T) / 12-bit (200T) input wire frame_complete, @@ -122,20 +123,24 @@ module cfar_ca #( input wire [3:0] cfg_guard_cells, input wire [4:0] cfg_train_cells, input wire [ALPHA_WIDTH-1:0] cfg_alpha, + input wire [ALPHA_WIDTH-1:0] cfg_alpha_soft, // PR-F: candidate-tier threshold input wire [1:0] cfg_cfar_mode, input wire cfg_cfar_enable, input wire [15:0] cfg_simple_threshold, // ========== DETECTION OUTPUTS ========== - output reg detect_flag, + output reg detect_flag, // = (detect_class != RP_DETECT_NONE) + output reg [`RP_DETECT_CLASS_WIDTH-1:0] detect_class, // PR-F: NONE/CANDIDATE/CONFIRMED output reg detect_valid, - output reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] detect_range, // 9-bit (50T) / 12-bit (200T) - output reg [4:0] detect_doppler, + output reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] detect_range, + output reg [DBIN_WIDTH-1:0] detect_doppler, output reg [MAG_WIDTH-1:0] detect_magnitude, - output reg [MAG_WIDTH-1:0] detect_threshold, + output reg [MAG_WIDTH-1:0] detect_threshold, // confirmed threshold (legacy) + output reg [MAG_WIDTH-1:0] detect_threshold_soft, // PR-F: soft (candidate) threshold // ========== STATUS ========== - output reg [15:0] detect_count, + output reg [15:0] detect_count, // total detections (CONFIRMED only) + output reg [15:0] detect_count_cand, // PR-F: candidate-only counter output wire cfar_busy, output reg [7:0] cfar_status ); @@ -143,10 +148,24 @@ module cfar_ca #( // ============================================================================ // INTERNAL PARAMETERS // ============================================================================ -localparam TOTAL_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS; -localparam ADDR_WIDTH = `RP_CFAR_MAG_ADDR_W; // 14 (50T) / 17 (200T) -localparam COL_BITS = 5; -localparam ROW_BITS = `RP_RANGE_BIN_WIDTH_MAX; // 9 (50T) / 12 (200T) +// Doppler-axis index width: enough bits to count 0..NUM_DOPPLER_BINS-1. +// Packed BRAM addressing pads to the next power of two so the {range,doppler} +// concatenation lands in a contiguous block per range bin (works for both +// NUM_DOPPLER_BINS=32, legacy power-of-two, and NUM_DOPPLER_BINS=48, PR-F). +function integer clog2; + input integer v; + integer i; + begin + clog2 = 0; + for (i = v - 1; i > 0; i = i >> 1) clog2 = clog2 + 1; + end +endfunction +localparam DBIN_INDEX_BITS = clog2(NUM_DOPPLER_BINS); // 5 (NUM=32) / 6 (NUM=48) +localparam DOPPLER_PAD = (1 << DBIN_INDEX_BITS); // 32 / 64 +localparam TOTAL_CELLS = NUM_RANGE_BINS * DOPPLER_PAD; // 16K (50T legacy) / 32K (50T PR-F) +localparam ADDR_WIDTH = `RP_RANGE_BIN_WIDTH_MAX + DBIN_INDEX_BITS; +localparam COL_BITS = DBIN_INDEX_BITS; // address-axis col counter +localparam ROW_BITS = `RP_RANGE_BIN_WIDTH_MAX; // 9 (50T) / 12 (200T) localparam SUM_WIDTH = MAG_WIDTH + ROW_BITS; // 26 (50T) / 29 (200T) localparam PROD_WIDTH = SUM_WIDTH + ALPHA_WIDTH; // 34 bits localparam ALPHA_FRAC_BITS = 4; // Q4.4 @@ -213,13 +232,15 @@ reg [COL_BITS-1:0] col_idx; reg [3:0] r_guard; reg [4:0] r_train; reg [ALPHA_WIDTH-1:0] r_alpha; +reg [ALPHA_WIDTH-1:0] r_alpha_soft; // PR-F: candidate threshold multiplier reg [1:0] r_mode; reg r_enable; reg [15:0] r_simple_thr; // Threshold pipeline registers reg [SUM_WIDTH-1:0] noise_sum_reg; // Stage 1: registered noise_sum_comb output -reg [PROD_WIDTH-1:0] noise_product; // Stage 2: alpha * noise_sum_reg +reg [PROD_WIDTH-1:0] noise_product; // Stage 2: alpha * noise_sum_reg +reg [PROD_WIDTH-1:0] noise_product_soft; // PR-F: alpha_soft * noise_sum_reg reg [MAG_WIDTH-1:0] adaptive_thr; // Init counter for computing initial lagging sum @@ -324,12 +345,15 @@ always @(posedge clk or negedge reset_n) begin if (!reset_n) begin state <= ST_IDLE; detect_flag <= 1'b0; + detect_class <= `RP_DETECT_NONE; detect_valid <= 1'b0; detect_range <= {ROW_BITS{1'b0}}; - detect_doppler <= 5'd0; + detect_doppler <= {DBIN_WIDTH{1'b0}}; detect_magnitude <= {MAG_WIDTH{1'b0}}; detect_threshold <= {MAG_WIDTH{1'b0}}; + detect_threshold_soft <= {MAG_WIDTH{1'b0}}; detect_count <= 16'd0; + detect_count_cand <= 16'd0; cfar_status <= 8'd0; mag_we <= 1'b0; mag_waddr <= {ADDR_WIDTH{1'b0}}; @@ -345,6 +369,7 @@ always @(posedge clk or negedge reset_n) begin init_idx <= 0; noise_sum_reg <= 0; noise_product <= 0; + noise_product_soft <= 0; adaptive_thr <= 0; lead_add_val_r <= 0; lead_rem_val_r <= 0; @@ -356,7 +381,8 @@ always @(posedge clk or negedge reset_n) begin lag_add_valid_r <= 0; r_guard <= 4'd2; r_train <= 5'd8; - r_alpha <= 8'h30; + r_alpha <= `RP_DEF_CFAR_ALPHA; + r_alpha_soft <= `RP_DEF_CFAR_ALPHA_SOFT; r_mode <= 2'b00; r_enable <= 1'b0; r_simple_thr <= 16'd10000; @@ -364,6 +390,7 @@ always @(posedge clk or negedge reset_n) begin // Defaults: clear one-shot outputs detect_valid <= 1'b0; detect_flag <= 1'b0; + detect_class <= `RP_DETECT_NONE; mag_we <= 1'b0; case (state) @@ -374,27 +401,35 @@ always @(posedge clk or negedge reset_n) begin cfar_status <= 8'd0; if (doppler_valid) begin - // Capture configuration at frame start + // Capture configuration at frame start. PR-F: per-frame counters + // reset to 0 here (matches the AUDIT-C6 fix in ST_DONE for the + // legacy detect_count). r_guard <= cfg_guard_cells; r_train <= (cfg_train_cells == 0) ? 5'd1 : cfg_train_cells; r_alpha <= cfg_alpha; + r_alpha_soft <= cfg_alpha_soft; r_mode <= cfg_cfar_mode; r_enable <= cfg_cfar_enable; r_simple_thr <= cfg_simple_threshold; // Buffer first sample mag_we <= 1'b1; - mag_waddr <= {range_bin_in, doppler_bin_in}; + mag_waddr <= {range_bin_in, doppler_bin_in[DBIN_INDEX_BITS-1:0]}; mag_wdata <= cur_mag; - // Simple threshold pass-through when CFAR disabled + // Simple threshold pass-through when CFAR disabled. + // Without an adaptive estimate we can't form a soft tier, so + // detect_class collapses to NONE/CONFIRMED on the simple thr. if (!cfg_cfar_enable) begin detect_flag <= (cur_mag > {1'b0, cfg_simple_threshold}); + detect_class <= (cur_mag > {1'b0, cfg_simple_threshold}) + ? `RP_DETECT_CONFIRMED : `RP_DETECT_NONE; detect_valid <= 1'b1; detect_range <= range_bin_in; detect_doppler <= doppler_bin_in; detect_magnitude <= cur_mag; detect_threshold <= {1'b0, cfg_simple_threshold}; + detect_threshold_soft <= {1'b0, cfg_simple_threshold}; if (cur_mag > {1'b0, cfg_simple_threshold}) detect_count <= detect_count + 1; end @@ -411,16 +446,19 @@ always @(posedge clk or negedge reset_n) begin if (doppler_valid) begin mag_we <= 1'b1; - mag_waddr <= {range_bin_in, doppler_bin_in}; + mag_waddr <= {range_bin_in, doppler_bin_in[DBIN_INDEX_BITS-1:0]}; mag_wdata <= cur_mag; if (!r_enable) begin detect_flag <= (cur_mag > {1'b0, r_simple_thr}); + detect_class <= (cur_mag > {1'b0, r_simple_thr}) + ? `RP_DETECT_CONFIRMED : `RP_DETECT_NONE; detect_valid <= 1'b1; detect_range <= range_bin_in; detect_doppler <= doppler_bin_in; detect_magnitude <= cur_mag; detect_threshold <= {1'b0, r_simple_thr}; + detect_threshold_soft <= {1'b0, r_simple_thr}; if (cur_mag > {1'b0, r_simple_thr}) detect_count <= detect_count + 1; end @@ -430,7 +468,7 @@ always @(posedge clk or negedge reset_n) begin if (r_enable) begin col_idx <= 0; col_load_idx <= 0; - mag_raddr <= {{ROW_BITS{1'b0}}, 5'd0}; + mag_raddr <= {{ROW_BITS{1'b0}}, {COL_BITS{1'b0}}}; state <= ST_COL_LOAD; end else begin state <= ST_DONE; @@ -531,7 +569,9 @@ always @(posedge clk or negedge reset_n) begin ST_CFAR_MUL: begin cfar_status <= {4'd4, 1'b1, col_idx[2:0]}; - noise_product <= r_alpha * noise_sum_reg; + // Two parallel multiplies — each maps to a single DSP48 slice. + noise_product <= r_alpha * noise_sum_reg; // confirmed tier + noise_product_soft <= r_alpha_soft * noise_sum_reg; // candidate tier (PR-F) state <= ST_CFAR_CMP; end @@ -554,19 +594,39 @@ always @(posedge clk or negedge reset_n) begin detect_doppler <= col_idx; detect_valid <= 1'b1; - // Compare: threshold computed this cycle from noise_product + // Compare: confirm + soft thresholds computed this cycle from + // noise_product / noise_product_soft. detect_class encodes the + // tier (NONE / CANDIDATE / CONFIRMED) so downstream can re-cue + // CANDIDATEs and track CONFIRMEDs. begin : threshold_compare reg [MAG_WIDTH-1:0] thr_val; + reg [MAG_WIDTH-1:0] thr_val_soft; + reg [MAG_WIDTH-1:0] cur_val; + if (noise_product[PROD_WIDTH-1:ALPHA_FRAC_BITS+MAG_WIDTH] != 0) thr_val = {MAG_WIDTH{1'b1}}; else thr_val = noise_product[ALPHA_FRAC_BITS +: MAG_WIDTH]; - detect_threshold <= thr_val; + if (noise_product_soft[PROD_WIDTH-1:ALPHA_FRAC_BITS+MAG_WIDTH] != 0) + thr_val_soft = {MAG_WIDTH{1'b1}}; + else + thr_val_soft = noise_product_soft[ALPHA_FRAC_BITS +: MAG_WIDTH]; - if (col_buf[cut_idx[ROW_BITS-1:0]] > thr_val) begin + detect_threshold <= thr_val; + detect_threshold_soft <= thr_val_soft; + + cur_val = col_buf[cut_idx[ROW_BITS-1:0]]; + + if (cur_val > thr_val) begin detect_flag <= 1'b1; + detect_class <= `RP_DETECT_CONFIRMED; detect_count <= detect_count + 1; + end else if (cur_val > thr_val_soft) begin + // Above soft, below confirm — host re-cues this cell. + detect_flag <= 1'b1; + detect_class <= `RP_DETECT_CANDIDATE; + detect_count_cand <= detect_count_cand + 1; end end @@ -592,7 +652,7 @@ always @(posedge clk or negedge reset_n) begin if (col_idx < NUM_DOPPLER_BINS - 1) begin col_idx <= col_idx + 1; col_load_idx <= 0; - mag_raddr <= {{ROW_BITS{1'b0}}, col_idx + 5'd1}; + mag_raddr <= {{ROW_BITS{1'b0}}, col_idx + {{(COL_BITS-1){1'b0}}, 1'b1}}; state <= ST_COL_LOAD; end else begin state <= ST_DONE; @@ -613,10 +673,12 @@ always @(posedge clk or negedge reset_n) begin state <= ST_IDLE; `ifdef SIMULATION - $display("[CFAR] Frame complete: %0d frame detections", detect_count); + $display("[CFAR] Frame complete: %0d confirmed, %0d candidates", + detect_count, detect_count_cand); `endif - detect_count <= 16'd0; + detect_count <= 16'd0; + detect_count_cand <= 16'd0; end default: state <= ST_IDLE; diff --git a/9_Firmware/9_2_FPGA/doppler_processor.v b/9_Firmware/9_2_FPGA/doppler_processor.v index 47d53bc..4aa6bff 100644 --- a/9_Firmware/9_2_FPGA/doppler_processor.v +++ b/9_Firmware/9_2_FPGA/doppler_processor.v @@ -1,31 +1,29 @@ `timescale 1ns / 1ps // ============================================================================ -// doppler_processor.v — Staggered-PRF Doppler Processor (CORRECTED) +// doppler_processor.v — Multi-subframe Doppler Processor (chirp-v2 PR-F) // ============================================================================ // // ARCHITECTURE: -// This module implements dual 16-point FFTs for the AERIS-10 staggered-PRF -// waveform. The radar transmits 16 long-PRI chirps followed by 16 short-PRI -// chirps per frame (32 total). Rather than a single 32-point FFT over the -// non-uniformly sampled frame (which is signal-processing invalid), this -// module processes each sub-frame independently: +// Processes NUM_SUBFRAMES = CHIRPS_PER_FRAME / CHIRPS_PER_SUBFRAME independent +// 16-point FFTs per range bin. The chirp-v2 production build runs three +// sub-frames (SHORT, MEDIUM, LONG) at 16 chirps each = 48 chirps per frame: // -// Sub-frame 0 (long PRI): chirps 0..15 → 16-pt windowed FFT -// Sub-frame 1 (short PRI): chirps 16..31 → 16-pt windowed FFT +// Sub-frame 0: chirps 0..15 → 16-pt windowed FFT (SHORT in chirp-v2) +// Sub-frame 1: chirps 16..31 → 16-pt windowed FFT (MEDIUM in chirp-v2) +// Sub-frame 2: chirps 32..47 → 16-pt windowed FFT (LONG in chirp-v2) // -// Each sub-frame produces 16 Doppler bins per range bin. The outputs are -// tagged with a sub_frame bit and the 4-bit bin index is packed into the -// existing 5-bit doppler_bin port as {sub_frame, bin[3:0]}. +// Each sub-frame produces 16 Doppler bins per range bin. Outputs are tagged +// with the 2-bit sub_frame index and the 4-bit bin index is packed into the +// 6-bit doppler_bin port as {sub_frame[1:0], bin[3:0]}. // -// This architecture enables downstream staggered-PRF ambiguity resolution: -// the same target velocity maps to DIFFERENT Doppler bins at different PRIs, -// and comparing the two sub-frame results resolves velocity ambiguity. +// Legacy 2-subframe golden-vector tests (tb_doppler_realdata, +// tb_fullchain_realdata) override CHIRPS_PER_FRAME=32 + CHIRPS_PER_SUBFRAME=16 +// to make NUM_SUBFRAMES=2; the FSM generalises cleanly. doppler_bin still +// reports 6 bits there with the high bit always zero. // -// INTERFACE COMPATIBILITY: -// The port list is a superset of the original module. Existing instantiations -// that don't connect `sub_frame` will still work. The FORMAL ports are -// retained. CHIRPS_PER_FRAME must be 32 (16 per sub-frame). +// Staggered-PRF ambiguity resolution downstream picks the matching Doppler +// bin from the SHORT vs MEDIUM vs LONG sub-frame to resolve velocity. // // WINDOW: // 16-point Hamming window (Q15), symmetric. Computed as: @@ -46,7 +44,7 @@ module doppler_processor_optimized #( parameter DOPPLER_FFT_SIZE = `RP_DOPPLER_FFT_SIZE, // 16 parameter RANGE_BINS = `RP_MAX_OUTPUT_BINS, // 512 (50T) / 4096 (200T) - parameter CHIRPS_PER_FRAME = `RP_CHIRPS_PER_FRAME, // 32 + parameter CHIRPS_PER_FRAME = `RP_CHIRPS_PER_FRAME, // 48 (PR-F); legacy TBs override to 32 parameter CHIRPS_PER_SUBFRAME = `RP_CHIRPS_PER_SUBFRAME, // 16 parameter WINDOW_TYPE = 0, // 0=Hamming, 1=Rectangular parameter DATA_WIDTH = `RP_DATA_WIDTH // 16 @@ -58,9 +56,9 @@ module doppler_processor_optimized #( input wire new_chirp_frame, output reg [31:0] doppler_output, output reg doppler_valid, - output reg [4:0] doppler_bin, // {sub_frame, bin[3:0]} - output reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin, // 9-bit (50T) / 12-bit (200T) - output reg sub_frame, // 0=long PRI, 1=short PRI + output reg [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin, // 6-bit {sub_frame[1:0], bin[3:0]} + output reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin, // 9-bit (50T) / 12-bit (200T) + output reg [`RP_SUBFRAME_ID_WIDTH-1:0] sub_frame, // 2-bit subframe index output wire processing_active, output wire frame_complete, output reg [3:0] status @@ -71,9 +69,9 @@ module doppler_processor_optimized #( output wire [`RP_DOPPLER_MEM_ADDR_W-1:0] fv_mem_write_addr, output wire [`RP_DOPPLER_MEM_ADDR_W-1:0] fv_mem_read_addr, output wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] fv_write_range_bin, - output wire [4:0] fv_write_chirp_index, + output wire [5:0] fv_write_chirp_index, output wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] fv_read_range_bin, - output wire [4:0] fv_read_doppler_index, + output wire [5:0] fv_read_doppler_index, output wire [9:0] fv_processing_timeout, output wire fv_frame_buffer_full, output wire fv_mem_we, @@ -81,6 +79,11 @@ module doppler_processor_optimized #( `endif ); +// Derived: number of sub-frames in the current configuration. Production +// build = 3 (SHORT/MEDIUM/LONG @ 16 chirps each = 48 frame). Legacy TBs +// override CHIRPS_PER_FRAME=32 to get NUM_SUBFRAMES=2 for golden compat. +localparam NUM_SUBFRAMES = CHIRPS_PER_FRAME / CHIRPS_PER_SUBFRAME; + // ============================================== // Window Coefficients — 16-point Hamming (Q15) // ============================================== @@ -127,9 +130,9 @@ localparam MEM_DEPTH = RANGE_BINS * CHIRPS_PER_FRAME; // Control Registers // ============================================== reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] write_range_bin; -reg [4:0] write_chirp_index; +reg [5:0] write_chirp_index; // 6-bit: 0..47 (PR-F) reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] read_range_bin; -reg [4:0] read_doppler_index; +reg [5:0] read_doppler_index; // 6-bit (PR-F) reg frame_buffer_full; reg [9:0] chirps_received; reg [1:0] chirp_state; @@ -146,7 +149,7 @@ reg [1:0] chirp_state; reg frame_armed; // Sub-frame tracking -reg current_sub_frame; // 0=processing long, 1=processing short +reg [`RP_SUBFRAME_ID_WIDTH-1:0] current_sub_frame; // 2-bit (PR-F): 0..NUM_SUBFRAMES-1 // ============================================== // FFT Interface @@ -365,20 +368,17 @@ always @(posedge clk or negedge reset_n) begin end S_OUTPUT: begin - if (current_sub_frame == 0) begin - // Just finished long PRI sub-frame — now do short PRI - current_sub_frame <= 1; + if (current_sub_frame < NUM_SUBFRAMES - 1) begin + // Advance to next sub-frame; same range bin, next FFT + current_sub_frame <= current_sub_frame + 1; fft_sample_counter <= 0; state <= S_PRE_READ; - // read_range_bin stays the same, read_doppler_index - // will be set to CHIRPS_PER_SUBFRAME in Block 2 end else begin - // Finished both sub-frames for this range bin + // Finished all NUM_SUBFRAMES for this range bin current_sub_frame <= 0; if (read_range_bin < RANGE_BINS - 1) begin fft_sample_counter <= 0; state <= S_PRE_READ; - // read_range_bin incremented in Block 2 end else begin state <= S_IDLE; frame_buffer_full <= 0; @@ -445,12 +445,9 @@ always @(posedge clk) begin end S_PRE_READ: begin - // Set read_doppler_index to first chirp of current sub-frame + 1 - // (because address is presented this cycle, data arrives next) - if (current_sub_frame == 0) - read_doppler_index <= 1; // Long PRI: chirps 0..15 - else - read_doppler_index <= CHIRPS_PER_SUBFRAME + 1; // Short PRI: chirps 16..31 + // First chirp of current sub-frame + 1 (address-then-data pipe). + // Generalised: chirp_base = current_sub_frame * CHIRPS_PER_SUBFRAME. + read_doppler_index <= current_sub_frame * CHIRPS_PER_SUBFRAME + 6'd1; // BREG priming: window coeff for sample 0 window_val_reg <= $signed(window_coeff[0]); @@ -463,13 +460,7 @@ always @(posedge clk) begin mult_q_raw <= $signed(mem_rdata_q) * window_val_reg; window_val_reg <= $signed(window_coeff[1]); // Advance to chirp base+2 - if (current_sub_frame == 0) - read_doppler_index <= (2 < CHIRPS_PER_SUBFRAME) ? 2 - : CHIRPS_PER_SUBFRAME - 1; - else - read_doppler_index <= (CHIRPS_PER_SUBFRAME + 2 < CHIRPS_PER_FRAME) - ? CHIRPS_PER_SUBFRAME + 2 - : CHIRPS_PER_FRAME - 1; + read_doppler_index <= current_sub_frame * CHIRPS_PER_SUBFRAME + 6'd2; end else if (fft_sample_counter == 1) begin mult_i <= mult_i_raw; mult_q <= mult_q_raw; @@ -478,14 +469,7 @@ always @(posedge clk) begin if (2 < CHIRPS_PER_SUBFRAME) window_val_reg <= $signed(window_coeff[2]); // Advance to chirp base+3 - begin : advance_chirp3 - reg [4:0] next_chirp; - next_chirp = (current_sub_frame == 0) ? 3 : CHIRPS_PER_SUBFRAME + 3; - if (next_chirp < CHIRPS_PER_FRAME) - read_doppler_index <= next_chirp; - else - read_doppler_index <= CHIRPS_PER_FRAME - 1; - end + read_doppler_index <= current_sub_frame * CHIRPS_PER_SUBFRAME + 6'd3; end else if (fft_sample_counter <= CHIRPS_PER_SUBFRAME + 1) begin // Steady state fft_input_i <= (mult_i + (1 << 14)) >>> 15; @@ -503,39 +487,34 @@ always @(posedge clk) begin if (win_idx < CHIRPS_PER_SUBFRAME) window_val_reg <= $signed(window_coeff[win_idx]); end - // Advance BRAM read - begin : advance_bram - reg [4:0] chirp_offset; - reg [4:0] chirp_base; - chirp_offset = fft_sample_counter[3:0] + 2; - chirp_base = (current_sub_frame == 0) ? 0 : CHIRPS_PER_SUBFRAME; - if (chirp_base + chirp_offset < CHIRPS_PER_FRAME) - read_doppler_index <= chirp_base + chirp_offset; - else - read_doppler_index <= CHIRPS_PER_FRAME - 1; - end + // Advance BRAM read: chirp_base + (counter + 2). + // For NUM_SUBFRAMES * CHIRPS_PER_SUBFRAME = CHIRPS_PER_FRAME + // and counter <= CHIRPS_PER_SUBFRAME-1, chirp_base+offset + // is bounded by CHIRPS_PER_FRAME so no clamp is needed. + read_doppler_index <= current_sub_frame * CHIRPS_PER_SUBFRAME + + {2'd0, fft_sample_counter[3:0]} + 6'd2; end if (fft_sample_counter == CHIRPS_PER_SUBFRAME + 1) begin - // Reset read index for potential next operation - if (current_sub_frame == 0) - read_doppler_index <= CHIRPS_PER_SUBFRAME; // Ready for short sub-frame + // Reset read index for the next sub-frame (or wrap to 0 + // when we've finished all NUM_SUBFRAMES). + if (current_sub_frame < NUM_SUBFRAMES - 1) + read_doppler_index <= (current_sub_frame + 6'd1) * CHIRPS_PER_SUBFRAME; else - read_doppler_index <= 0; + read_doppler_index <= 6'd0; end end end S_OUTPUT: begin - if (current_sub_frame == 0) begin - // Transitioning to short PRI sub-frame - // Set read_doppler_index to start of short sub-frame - read_doppler_index <= CHIRPS_PER_SUBFRAME; + if (current_sub_frame < NUM_SUBFRAMES - 1) begin + // Transitioning to next sub-frame for the same range bin. + read_doppler_index <= (current_sub_frame + 6'd1) * CHIRPS_PER_SUBFRAME; end else begin - // Both sub-frames done + // All sub-frames done for this range bin if (read_range_bin < RANGE_BINS - 1) begin read_range_bin <= read_range_bin + 1; - read_doppler_index <= 0; // Next range bin starts with long sub-frame + read_doppler_index <= 6'd0; // Next range bin starts with sub-frame 0 end end end diff --git a/9_Firmware/9_2_FPGA/radar_params.vh b/9_Firmware/9_2_FPGA/radar_params.vh index b72bfee..4b33259 100644 --- a/9_Firmware/9_2_FPGA/radar_params.vh +++ b/9_Firmware/9_2_FPGA/radar_params.vh @@ -77,9 +77,9 @@ `define RP_RANGE_BIN_BITS 9 // ceil(log2(512)) `define RP_DOPPLER_FFT_SIZE 16 // Per sub-frame Doppler FFT (scan mode) `define RP_DOPPLER_FFT_SIZE_TRACK 64 // Track-mode dwell N (xfft_64, single waveform) -`define RP_CHIRPS_PER_FRAME 32 // (LEGACY: scan-only 2-subframe; bumped to 48 in PR-F) +`define RP_CHIRPS_PER_FRAME 48 // 3 sub-frames * 16 chirps = 48 (PR-F) `define RP_CHIRPS_PER_SUBFRAME 16 // Chirps per Doppler sub-frame -`define RP_NUM_DOPPLER_BINS 32 // (LEGACY: 2 sub-frames * 16 = 32; bumped to 48 in PR-F) +`define RP_NUM_DOPPLER_BINS 48 // 3 sub-frames * 16 bins = 48 (PR-F) `define RP_DATA_WIDTH 16 // ADC/processing data width // 3-ladder waveform identity (replaces 1-bit use_long_chirp rail in PR-C onward) @@ -153,13 +153,13 @@ `ifdef SUPPORT_LONG_RANGE `define RP_SEGMENT_IDX_WIDTH 3 `define RP_RANGE_BIN_WIDTH_MAX 12 // ceil(log2(4096)) - `define RP_DOPPLER_MEM_ADDR_W 17 // ceil(log2(4096*32)) = 17 - `define RP_CFAR_MAG_ADDR_W 17 // ceil(log2(4096*32)) = 17 + `define RP_DOPPLER_MEM_ADDR_W 18 // ceil(log2(4096*48)) = 18 (PR-F) + `define RP_CFAR_MAG_ADDR_W 18 // ceil(log2(4096*48)) = 18 (PR-F) `else `define RP_SEGMENT_IDX_WIDTH 2 `define RP_RANGE_BIN_WIDTH_MAX 9 // ceil(log2(512)) - `define RP_DOPPLER_MEM_ADDR_W 14 // ceil(log2(512*32)) = 14 - `define RP_CFAR_MAG_ADDR_W 14 // ceil(log2(512*32)) = 14 + `define RP_DOPPLER_MEM_ADDR_W 15 // ceil(log2(512*48)) = 15 (PR-F) + `define RP_CFAR_MAG_ADDR_W 15 // ceil(log2(512*48)) = 15 (PR-F) `endif // Derived depths (for memory declarations) diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index a1de31a..36f68ed 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -23,7 +23,7 @@ module radar_receiver_final ( output wire [31:0] doppler_output, output wire doppler_valid, - output wire [4:0] doppler_bin, + output wire [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin, output wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin, // 9-bit // Raw matched-filter output (debug/bring-up) diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index 1ae253f..f4302ea 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -126,7 +126,7 @@ module radar_system_top ( // Doppler processing outputs (for debugging) output wire [31:0] dbg_doppler_data, output wire dbg_doppler_valid, - output wire [4:0] dbg_doppler_bin, + output wire [`RP_DOPPLER_BIN_WIDTH-1:0] dbg_doppler_bin, output wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] dbg_range_bin, // System status @@ -186,7 +186,7 @@ wire sched_frame_pulse; // Receiver internal signals wire [31:0] rx_doppler_output; wire rx_doppler_valid; -wire [4:0] rx_doppler_bin; +wire [`RP_DOPPLER_BIN_WIDTH-1:0] rx_doppler_bin; wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] rx_range_bin; wire [31:0] rx_range_profile; wire rx_range_valid; @@ -273,7 +273,7 @@ reg host_status_request; // Opcode 0xFF (self-clearing pulse) // Doppler path (16 long + 16 short). If host sets chirps_per_elev to a // different value, Doppler accumulation is corrupted. Clamp at command decode // and flag the mismatch so the host knows. -localparam DOPPLER_FRAME_CHIRPS = 32; // Total chirps per Doppler frame +localparam DOPPLER_FRAME_CHIRPS = `RP_CHIRPS_PER_FRAME; // 48 (PR-F); was 32 reg chirps_mismatch_error; // Set if host tried to set chirps != FFT size // Range-mode register (opcode 0x20) @@ -288,6 +288,8 @@ reg [1:0] host_range_mode; reg [3:0] host_cfar_guard; // Opcode 0x21: guard cells per side (0..8) reg [4:0] host_cfar_train; // Opcode 0x22: training cells per side (1..16) reg [7:0] host_cfar_alpha; // Opcode 0x23: threshold multiplier (Q4.4) +reg [7:0] host_cfar_alpha_soft; // PR-F: soft / candidate-tier multiplier (Q4.4). + // USB opcode mapping deferred to PR-G (planned 0x28). reg [1:0] host_cfar_mode; // Opcode 0x24: 00=CA, 01=GO, 10=SO reg host_cfar_enable; // Opcode 0x25: 1=CFAR, 0=simple threshold @@ -650,17 +652,16 @@ assign rx_doppler_data_valid = rx_doppler_valid; // ============================================================================ // DC NOTCH FILTER (post-Doppler-FFT, pre-CFAR) // ============================================================================ -// Zeros out Doppler bins within ±host_dc_notch_width of DC for BOTH -// sub-frames in the dual 16-pt FFT architecture. -// doppler_bin[4:0] = {sub_frame, bin[3:0]}: -// Sub-frame 0: bins 0-15, DC = bin 0, wrap = bin 15 +// Zeros out Doppler bins within ±host_dc_notch_width of DC for ALL +// 16-pt sub-frames in the chirp-v2 architecture (3 sub-frames in production). +// doppler_bin[5:0] = {sub_frame[1:0], bin[3:0]}: +// Sub-frame 0: bins 0-15, DC = bin 0, wrap = bin 15 // Sub-frame 1: bins 16-31, DC = bin 16, wrap = bin 31 -// notch_width=1 → zero bins {0,16}. notch_width=2 → zero bins -// {0,1,15,16,17,31}. etc. -// When host_dc_notch_width=0: pass-through (no zeroing). +// Sub-frame 2: bins 32-47, DC = bin 32, wrap = bin 47 +// The DC test ignores the sub-frame field and gates on the 4-bit per-FFT bin. wire dc_notch_active; -wire [4:0] dop_bin_unsigned = rx_doppler_bin; +wire [`RP_DOPPLER_BIN_WIDTH-1:0] dop_bin_unsigned = rx_doppler_bin; wire [3:0] bin_within_sf = dop_bin_unsigned[3:0]; assign dc_notch_active = (host_dc_notch_width != 3'd0) && (bin_within_sf < {1'b0, host_dc_notch_width} || @@ -669,8 +670,8 @@ assign dc_notch_active = (host_dc_notch_width != 3'd0) && // Notched Doppler data: zero I/Q when in notch zone, pass through otherwise wire [31:0] notched_doppler_data = dc_notch_active ? 32'd0 : rx_doppler_output; wire notched_doppler_valid = rx_doppler_valid; -wire [4:0] notched_doppler_bin = rx_doppler_bin; -wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] notched_range_bin = rx_range_bin; +wire [`RP_DOPPLER_BIN_WIDTH-1:0] notched_doppler_bin = rx_doppler_bin; +wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] notched_range_bin = rx_range_bin; // ============================================================================ // CFAR DETECTOR (replaces simple threshold detector) @@ -680,12 +681,15 @@ wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] notched_range_bin = rx_range_bin; // See cfar_ca.v for architecture details. wire cfar_detect_flag; +wire [`RP_DETECT_CLASS_WIDTH-1:0] cfar_detect_class; wire cfar_detect_valid; wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] cfar_detect_range; -wire [4:0] cfar_detect_doppler; +wire [`RP_DOPPLER_BIN_WIDTH-1:0] cfar_detect_doppler; wire [16:0] cfar_detect_magnitude; wire [16:0] cfar_detect_threshold; +wire [16:0] cfar_detect_threshold_soft; wire [15:0] cfar_detect_count; +wire [15:0] cfar_detect_count_cand; wire cfar_busy_w; wire [7:0] cfar_status_w; @@ -704,20 +708,24 @@ cfar_ca cfar_inst ( .cfg_guard_cells(host_cfar_guard), .cfg_train_cells(host_cfar_train), .cfg_alpha(host_cfar_alpha), + .cfg_alpha_soft(host_cfar_alpha_soft), .cfg_cfar_mode(host_cfar_mode), .cfg_cfar_enable(host_cfar_enable), .cfg_simple_threshold(host_detect_threshold), // Detection outputs .detect_flag(cfar_detect_flag), + .detect_class(cfar_detect_class), .detect_valid(cfar_detect_valid), .detect_range(cfar_detect_range), .detect_doppler(cfar_detect_doppler), .detect_magnitude(cfar_detect_magnitude), .detect_threshold(cfar_detect_threshold), + .detect_threshold_soft(cfar_detect_threshold_soft), // Status .detect_count(cfar_detect_count), + .detect_count_cand(cfar_detect_count_cand), .cfar_busy(cfar_busy_w), .cfar_status(cfar_status_w) ); @@ -1032,7 +1040,8 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin // CFAR defaults (disabled by default — backward-compatible) host_cfar_guard <= 4'd2; // 2 guard cells each side host_cfar_train <= 5'd8; // 8 training cells each side - host_cfar_alpha <= 8'h30; // alpha=3.0 (Q4.4) + host_cfar_alpha <= `RP_DEF_CFAR_ALPHA; // 3.0 (Q4.4) + host_cfar_alpha_soft <= `RP_DEF_CFAR_ALPHA_SOFT; // 1.5 (Q4.4) — PR-F host_cfar_mode <= 2'b00; // CA-CFAR host_cfar_enable <= 1'b0; // Disabled (simple threshold) // Ground clutter removal defaults (disabled — backward-compatible) diff --git a/9_Firmware/9_2_FPGA/radar_system_top_50t.v b/9_Firmware/9_2_FPGA/radar_system_top_50t.v index 1842f96..1baa7b4 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top_50t.v +++ b/9_Firmware/9_2_FPGA/radar_system_top_50t.v @@ -112,7 +112,7 @@ module radar_system_top_50t ( wire new_chirp_frame_nc; wire [31:0] dbg_doppler_data_nc; wire dbg_doppler_valid_nc; - wire [4:0] dbg_doppler_bin_nc; + wire [`RP_DOPPLER_BIN_WIDTH-1:0] dbg_doppler_bin_nc; wire [5:0] dbg_range_bin_nc; wire [3:0] system_status_nc; diff --git a/9_Firmware/9_2_FPGA/tb/tb_cfar_ca.v b/9_Firmware/9_2_FPGA/tb/tb_cfar_ca.v index 80212c8..a4377b0 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_cfar_ca.v +++ b/9_Firmware/9_2_FPGA/tb/tb_cfar_ca.v @@ -24,6 +24,8 @@ * T14: cfar_busy asserts during processing, deasserts after */ +`include "radar_params.vh" + module tb_cfar_ca; // ============================================================================ @@ -43,24 +45,28 @@ reg reset_n; reg [31:0] doppler_data; reg doppler_valid; -reg [4:0] doppler_bin_in; -reg [5:0] range_bin_in; +reg [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin_in; +reg [`RP_RANGE_BIN_WIDTH_MAX-1: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 [ALPHA_W-1:0] cfg_alpha_soft; // PR-F reg [1:0] cfg_cfar_mode; reg cfg_cfar_enable; reg [15:0] cfg_simple_threshold; wire detect_flag; +wire [`RP_DETECT_CLASS_WIDTH-1:0] detect_class; // PR-F wire detect_valid; -wire [5:0] detect_range; -wire [4:0] detect_doppler; +wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] detect_range; +wire [`RP_DOPPLER_BIN_WIDTH-1:0] detect_doppler; wire [MAG_W-1:0] detect_magnitude; wire [MAG_W-1:0] detect_threshold; +wire [MAG_W-1:0] detect_threshold_soft; // PR-F wire [15:0] detect_count; +wire [15:0] detect_count_cand; // PR-F wire cfar_busy; wire [7:0] cfar_status; @@ -84,8 +90,8 @@ 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 [`RP_RANGE_BIN_WIDTH_MAX-1:0] det_cap_range [0:255]; +reg [`RP_DOPPLER_BIN_WIDTH-1: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]; @@ -110,16 +116,20 @@ cfar_ca #( .cfg_guard_cells(cfg_guard_cells), .cfg_train_cells(cfg_train_cells), .cfg_alpha(cfg_alpha), + .cfg_alpha_soft(cfg_alpha_soft), // PR-F .cfg_cfar_mode(cfg_cfar_mode), .cfg_cfar_enable(cfg_cfar_enable), .cfg_simple_threshold(cfg_simple_threshold), .detect_flag(detect_flag), + .detect_class(detect_class), // PR-F .detect_valid(detect_valid), .detect_range(detect_range), .detect_doppler(detect_doppler), .detect_magnitude(detect_magnitude), .detect_threshold(detect_threshold), + .detect_threshold_soft(detect_threshold_soft), // PR-F .detect_count(detect_count), + .detect_count_cand(detect_count_cand), // PR-F .cfar_busy(cfar_busy), .cfar_status(cfar_status) ); @@ -165,8 +175,8 @@ endtask // Feed one Doppler sample (I/Q packed as {Q, I}) task feed_sample; - input [5:0] rbin; - input [4:0] dbin; + input [`RP_RANGE_BIN_WIDTH_MAX-1:0] rbin; + input [`RP_DOPPLER_BIN_WIDTH-1:0] dbin; input signed [15:0] i_val; input signed [15:0] q_val; begin @@ -184,8 +194,8 @@ endtask // 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 [`RP_RANGE_BIN_WIDTH_MAX-1:0] tgt_range [0:7]; +reg [`RP_DOPPLER_BIN_WIDTH-1:0] tgt_doppler[0:7]; reg [15:0] tgt_level [0:7]; integer num_targets; @@ -207,7 +217,9 @@ task feed_frame; i_val = tgt_level[t]; end end - feed_sample(r[5:0], d[4:0], $signed(i_val), 16'sd0); + feed_sample(r[`RP_RANGE_BIN_WIDTH_MAX-1:0], + d[`RP_DOPPLER_BIN_WIDTH-1:0], + $signed(i_val), 16'sd0); end end end @@ -304,6 +316,11 @@ initial begin cfg_guard_cells = 4'd2; cfg_train_cells = 5'd8; cfg_alpha = 8'h30; + // PR-F: pin alpha_soft to max so the candidate tier never triggers in + // these tests — preserves the original "detect_flag means CONFIRMED" + // semantics. The 2-class detection path is exercised by tb_system_e2e + // (live data) rather than this unit test. + cfg_alpha_soft = 8'hFF; cfg_cfar_mode = 2'b00; cfg_cfar_enable = 1'b1; cfg_simple_threshold = 16'd5000; diff --git a/9_Firmware/9_2_FPGA/tb/tb_doppler_realdata.v b/9_Firmware/9_2_FPGA/tb/tb_doppler_realdata.v index e19e904..a5945ce 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_doppler_realdata.v +++ b/9_Firmware/9_2_FPGA/tb/tb_doppler_realdata.v @@ -23,6 +23,8 @@ * vvp tb/tb_doppler_realdata.vvp */ +`include "radar_params.vh" + module tb_doppler_realdata; // ============================================================================ @@ -56,16 +58,23 @@ 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 [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin; +wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin; wire processing_active; wire frame_complete; wire [3:0] dut_status; // ============================================================================ -// DUT INSTANTIATION +// DUT INSTANTIATION — override CHIRPS_PER_FRAME=32 to keep this TB compatible +// with the legacy 2-subframe golden vectors (chirp-v2 production runs 3 +// sub-frames at 48 chirps/frame; the Doppler FSM is parameterised over +// NUM_SUBFRAMES = CHIRPS_PER_FRAME / CHIRPS_PER_SUBFRAME). // ============================================================================ -doppler_processor_optimized dut ( +doppler_processor_optimized #( + .CHIRPS_PER_FRAME(32), + .CHIRPS_PER_SUBFRAME(16), + .RANGE_BINS(64) +) dut ( .clk(clk), .reset_n(reset_n), .range_data(range_data), @@ -109,8 +118,8 @@ end // ============================================================================ 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]; +reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] cap_rbin [0:TOTAL_OUTPUTS-1]; +reg [`RP_DOPPLER_BIN_WIDTH-1:0] cap_dbin [0:TOTAL_OUTPUTS-1]; integer out_count; // ============================================================================ diff --git a/9_Firmware/9_2_FPGA/tb/tb_fullchain_realdata.v b/9_Firmware/9_2_FPGA/tb/tb_fullchain_realdata.v index af6003f..270f859 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_fullchain_realdata.v +++ b/9_Firmware/9_2_FPGA/tb/tb_fullchain_realdata.v @@ -33,6 +33,8 @@ * vvp tb/tb_fullchain_realdata.vvp */ +`include "radar_params.vh" + module tb_fullchain_realdata; // ============================================================================ @@ -91,8 +93,8 @@ reg new_chirp_frame; wire [31:0] doppler_output; wire doppler_valid; -wire [4:0] doppler_bin; -wire [5:0] range_bin; +wire [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin; +wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin; wire processing_active; wire frame_complete; wire [3:0] dut_status; @@ -122,7 +124,14 @@ range_bin_decimator #( // ============================================================================ // DUT INSTANTIATION: Doppler Processor // ============================================================================ -doppler_processor_optimized doppler_proc ( +doppler_processor_optimized #( + // Override CHIRPS_PER_FRAME / RANGE_BINS to keep this TB compatible with + // the legacy 2-subframe golden vectors (chirp-v2 production uses 48 + // chirps × 512 ranges; this co-sim runs 32 × 64). + .CHIRPS_PER_FRAME(32), + .CHIRPS_PER_SUBFRAME(16), + .RANGE_BINS(64) +) doppler_proc ( .clk(clk), .reset_n(reset_n), .range_data(range_data_32bit), @@ -174,8 +183,8 @@ reg signed [15:0] decim_cap_q [0:CHIRPS*RANGE_BINS-1]; // ============================================================================ 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]; +reg [`RP_RANGE_BIN_WIDTH_MAX-1:0] cap_rbin [0:TOTAL_OUTPUT_SAMPLES-1]; +reg [`RP_DOPPLER_BIN_WIDTH-1:0] cap_dbin [0:TOTAL_OUTPUT_SAMPLES-1]; integer out_count; // ============================================================================ diff --git a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v index 83bbf7f..c0ca83f 100644 --- a/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v +++ b/9_Firmware/9_2_FPGA/usb_data_interface_ft2232h.v @@ -115,7 +115,7 @@ module usb_data_interface_ft2232h ( // (4096 bins, 131072 cells) requires a wire-protocol extension before // bins 512..4095 can be transported to the host. input wire [`RP_RANGE_BIN_WIDTH_MAX-1:0] range_bin_in, - input wire [4:0] doppler_bin_in, // 5-bit doppler bin index + input wire [`RP_DOPPLER_BIN_WIDTH-1:0] doppler_bin_in, // 6-bit (PR-F): {sub_frame[1:0], bin[3:0]} input wire frame_complete, // 1-cycle pulse from radar_receiver_final edge detector // FT2232H Physical Interface (245 Synchronous FIFO mode) @@ -181,9 +181,18 @@ localparam FOOTER = 8'h55; localparam STATUS_HEADER = 8'hBB; localparam NUM_RANGE_BINS = `RP_NUM_RANGE_BINS; // 512 -localparam NUM_DOPPLER_BINS = `RP_NUM_DOPPLER_BINS; // 32 +localparam NUM_DOPPLER_BINS = `RP_NUM_DOPPLER_BINS; // 48 (PR-F) localparam RANGE_BIN_BITS = `RP_RANGE_BIN_BITS; // 9 -localparam FRAME_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS; // 16384 +localparam DOPPLER_BIN_BITS = `RP_DOPPLER_BIN_WIDTH;// 6 (PR-F) +// PR-F: pad FRAME_CELLS to next-power-of-2 along the doppler axis so the +// {range, doppler[N-1:0]} concatenation lands in a contiguous BRAM block per +// range bin. Costs ~10 extra RAMB18 vs the previous 16K-cell packing but +// avoids a per-write multiply on the 100 MHz path. +localparam FRAME_CELLS = NUM_RANGE_BINS * (1 << DOPPLER_BIN_BITS); // 32768 (PR-F) +// Frame-cell address widths. +localparam FRAME_ADDR_W = RANGE_BIN_BITS + DOPPLER_BIN_BITS; // 15 +localparam DETECT_BYTE_ADDR_W = FRAME_ADDR_W - 3; // 12 +localparam DETECT_BYTE_LAST = (FRAME_CELLS / 8) - 1; // 4095 // Frame header: 8 bytes (0xAA + flags + frame_num[2] + range_bins[2] + doppler_bins[2]) localparam FRAME_HDR_BYTES = 8; @@ -250,7 +259,7 @@ assign ft_data = ft_data_oe ? ft_data_out : 8'hZZ; (* ram_style = "block" *) reg [15:0] doppler_mag_bram [0:FRAME_CELLS-1]; // Write port (clk domain) -reg [13:0] mag_wr_addr; +reg [FRAME_ADDR_W-1:0] mag_wr_addr; // PR-F: 15-bit reg [15:0] mag_wr_data; reg mag_wr_en; @@ -260,7 +269,7 @@ always @(posedge clk) begin end // Read port (ft_clk domain) -reg [13:0] mag_rd_addr; +reg [FRAME_ADDR_W-1:0] mag_rd_addr; // PR-F: 15-bit reg [15:0] mag_rd_data; always @(posedge ft_clk) begin @@ -315,9 +324,9 @@ end // a 1-cycle read then 1-cycle write-back with the bit set. This works because // CFAR outputs arrive one cell per clock cycle (sequential scan). -(* ram_style = "block" *) reg [7:0] detect_bram [0:2047]; +(* ram_style = "block" *) reg [7:0] detect_bram [0:DETECT_BYTE_LAST]; // PR-F: 3072 entries (was 2048) -reg [10:0] detect_wr_addr; +reg [DETECT_BYTE_ADDR_W-1:0] detect_wr_addr; // PR-F: 12-bit byte addr (was 11) reg [7:0] detect_wr_data; reg detect_wr_en; @@ -326,15 +335,15 @@ always @(posedge clk) begin detect_bram[detect_wr_addr] <= detect_wr_data; end -reg [10:0] detect_rd_addr; -reg [7:0] detect_rd_data; +reg [DETECT_BYTE_ADDR_W-1:0] detect_rd_addr; // PR-F: 12-bit byte addr (was 11) +reg [7:0] detect_rd_data; always @(posedge ft_clk) begin detect_rd_data <= detect_bram[detect_rd_addr]; end // Detection BRAM read-modify-write pipeline (clk domain) -reg [10:0] detect_rmw_addr; +reg [DETECT_BYTE_ADDR_W-1:0] detect_rmw_addr; // PR-F: 12-bit byte addr (was 11) reg [7:0] detect_rmw_rd; reg [2:0] detect_rmw_bit; reg detect_rmw_value; @@ -380,7 +389,7 @@ wire [15:0] range_mag = range_manhattan[16] ? 16'hFFFF : range_manhattan[15:0]; reg [15:0] frame_number; // Incrementing frame counter reg frame_ready_toggle; // Toggle CDC: frame ready for USB transfer reg frame_filling; // 1 = currently accumulating frame data -reg [13:0] detect_clear_addr; // Counter for bulk-clearing detection BRAM +reg [FRAME_ADDR_W-1:0] detect_clear_addr; // PR-F: 15-bit bit-counter (FRAME_CELLS = 24576 < 32768) reg detect_clearing; // 1 = bulk clear in progress // Range bin counter for range profile writes @@ -409,18 +418,18 @@ always @(posedge clk or negedge reset_n) begin frame_ready_toggle <= 1'b0; frame_filling <= 1'b1; mag_wr_en <= 1'b0; - mag_wr_addr <= 14'd0; + mag_wr_addr <= {FRAME_ADDR_W{1'b0}}; mag_wr_data <= 16'd0; range_wr_en <= 1'b0; range_wr_addr <= {RANGE_BIN_BITS{1'b0}}; range_wr_data <= 16'd0; detect_wr_en <= 1'b0; - detect_wr_addr <= 11'd0; + detect_wr_addr <= {DETECT_BYTE_ADDR_W{1'b0}}; detect_wr_data <= 8'd0; detect_clearing <= 1'b0; - detect_clear_addr <= 14'd0; + detect_clear_addr <= {FRAME_ADDR_W{1'b0}}; detect_rmw_state <= 2'd0; - detect_rmw_addr <= 11'd0; + detect_rmw_addr <= {DETECT_BYTE_ADDR_W{1'b0}}; detect_rmw_bit <= 3'd0; detect_rmw_value <= 1'b0; range_write_counter <= {RANGE_BIN_BITS{1'b0}}; @@ -431,15 +440,17 @@ always @(posedge clk or negedge reset_n) begin detect_wr_en <= 1'b0; // === Detection BRAM bulk clear (runs after frame_complete) === + // PR-F: bit-counter is FRAME_ADDR_W (15-bit), byte addr is the upper + // (FRAME_ADDR_W-3) bits. if (detect_clearing) begin detect_wr_en <= 1'b1; - detect_wr_addr <= detect_clear_addr[13:3]; + detect_wr_addr <= detect_clear_addr[FRAME_ADDR_W-1:3]; detect_wr_data <= 8'd0; - if (detect_clear_addr[13:3] == 11'd2047) begin + if (detect_clear_addr[FRAME_ADDR_W-1:3] == DETECT_BYTE_LAST[DETECT_BYTE_ADDR_W-1:0]) begin detect_clearing <= 1'b0; - detect_clear_addr <= 14'd0; + detect_clear_addr <= {FRAME_ADDR_W{1'b0}}; end else begin - detect_clear_addr <= detect_clear_addr + 14'd8; // Step by 8 bits = 1 byte + detect_clear_addr <= detect_clear_addr + 15'd8; // Step by 8 bits = 1 byte end end @@ -479,11 +490,10 @@ always @(posedge clk or negedge reset_n) begin end // === CFAR detection write (read-modify-write) === + // PR-F: bit_addr = {range_bin_in[8:0], doppler_bin_in[5:0]} = 15-bit. + // byte_addr = bit_addr[14:3] (12 bits), bit_pos = bit_addr[2:0]. if (cfar_valid && frame_filling && detect_rmw_state == 2'd0 && !detect_clearing) begin - // Start RMW: compute byte address and bit position - // bit_addr = {range_bin_in, doppler_bin_in} = 14-bit - // byte_addr = bit_addr[13:3], bit_pos = bit_addr[2:0] - detect_rmw_addr <= {range_bin_in, doppler_bin_in[4:3]}; + detect_rmw_addr <= {range_bin_in, doppler_bin_in[DOPPLER_BIN_BITS-1:3]}; detect_rmw_bit <= doppler_bin_in[2:0]; detect_rmw_value <= cfar_detection; detect_rmw_state <= 2'd1; @@ -525,7 +535,7 @@ always @(posedge clk or negedge reset_n) begin if (!frame_filling && !frame_complete) begin frame_filling <= 1'b1; detect_clearing <= 1'b1; // Clear detection BRAM for next frame - detect_clear_addr <= 14'd0; + detect_clear_addr <= {FRAME_ADDR_W{1'b0}}; end end end @@ -640,10 +650,10 @@ reg [31:0] status_words [0:5]; reg [15:0] wr_byte_idx; // BRAM read address for frame transfer -reg [13:0] bram_rd_cell; // Cell index 0..16383 for doppler/detect -reg [RANGE_BIN_BITS-1:0] range_rd_idx; // Range bin index 0..511 -reg wr_byte_phase; // 0=MSB, 1=LSB for 16-bit values -reg [10:0] detect_rd_idx; // Byte index 0..2047 for detection flags +reg [FRAME_ADDR_W-1:0] bram_rd_cell; // PR-F: 15-bit cell index 0..24575 +reg [RANGE_BIN_BITS-1:0] range_rd_idx; // Range bin index 0..511 +reg wr_byte_phase; // 0=MSB, 1=LSB for 16-bit values +reg [DETECT_BYTE_ADDR_W-1:0] detect_rd_idx; // PR-F: 12-bit byte index 0..3071 // ============================================================================ // CLOCK-ACTIVITY WATCHDOG (clk domain) @@ -732,12 +742,12 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin wr_state <= WR_IDLE; wr_byte_idx <= 16'd0; wr_byte_phase <= 1'b0; - bram_rd_cell <= 14'd0; + bram_rd_cell <= {FRAME_ADDR_W{1'b0}}; range_rd_idx <= {RANGE_BIN_BITS{1'b0}}; range_rd_addr <= {RANGE_BIN_BITS{1'b0}}; - detect_rd_idx <= 11'd0; - detect_rd_addr <= 11'd0; - mag_rd_addr <= 14'd0; + detect_rd_idx <= {DETECT_BYTE_ADDR_W{1'b0}}; + detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}}; + mag_rd_addr <= {FRAME_ADDR_W{1'b0}}; rd_state <= RD_IDLE; rd_byte_cnt <= 2'd0; rd_cmd_complete <= 1'b0; @@ -891,12 +901,12 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin else if (frame_ready_ft && ft_rxf_n) begin wr_state <= WR_FRAME_HDR; wr_byte_idx <= 16'd0; - bram_rd_cell <= 14'd0; + bram_rd_cell <= {FRAME_ADDR_W{1'b0}}; range_rd_idx <= {RANGE_BIN_BITS{1'b0}}; range_rd_addr <= {RANGE_BIN_BITS{1'b0}}; // Pre-load first read addr - detect_rd_idx <= 11'd0; - detect_rd_addr <= 11'd0; - mag_rd_addr <= 14'd0; + detect_rd_idx <= {DETECT_BYTE_ADDR_W{1'b0}}; + detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}}; + mag_rd_addr <= {FRAME_ADDR_W{1'b0}}; wr_byte_phase <= 1'b0; end end @@ -960,8 +970,8 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin if (wr_byte_idx == RANGE_SECTION_BYTES[15:0] - 16'd1) begin wr_byte_idx <= 16'd0; wr_byte_phase <= 1'b0; - bram_rd_cell <= 14'd0; - mag_rd_addr <= 14'd0; + bram_rd_cell <= {FRAME_ADDR_W{1'b0}}; + mag_rd_addr <= {FRAME_ADDR_W{1'b0}}; if (stream_flags_snapshot[1]) wr_state <= WR_DOPPLER_DATA; else if (stream_flags_snapshot[2]) @@ -985,8 +995,8 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin end else begin ft_data_out <= mag_rd_data[7:0]; wr_byte_phase <= 1'b0; - bram_rd_cell <= bram_rd_cell + 14'd1; - mag_rd_addr <= bram_rd_cell + 14'd1; + bram_rd_cell <= bram_rd_cell + {{(FRAME_ADDR_W-1){1'b0}}, 1'b1}; + mag_rd_addr <= bram_rd_cell + {{(FRAME_ADDR_W-1){1'b0}}, 1'b1}; end wr_byte_idx <= wr_byte_idx + 16'd1; @@ -994,8 +1004,8 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin if (wr_byte_idx == DOPPLER_MAG_SECTION_BYTES[15:0] - 16'd1) begin wr_byte_idx <= 16'd0; wr_byte_phase <= 1'b0; - detect_rd_idx <= 11'd0; - detect_rd_addr <= 11'd0; + detect_rd_idx <= {DETECT_BYTE_ADDR_W{1'b0}}; + detect_rd_addr <= {DETECT_BYTE_ADDR_W{1'b0}}; if (stream_flags_snapshot[2]) wr_state <= WR_DETECT_DATA; else @@ -1012,8 +1022,8 @@ always @(posedge ft_clk or negedge ft_effective_reset_n) begin // 1-byte per cycle (BRAM read latency handled by pre-loading addr) ft_data_out <= detect_rd_data; - detect_rd_idx <= detect_rd_idx + 11'd1; - detect_rd_addr <= detect_rd_idx + 11'd1; + detect_rd_idx <= detect_rd_idx + {{(DETECT_BYTE_ADDR_W-1){1'b0}}, 1'b1}; + detect_rd_addr <= detect_rd_idx + {{(DETECT_BYTE_ADDR_W-1){1'b0}}, 1'b1}; wr_byte_idx <= wr_byte_idx + 16'd1;