Files
overwatch/9_Firmware/9_2_FPGA/range_bin_decimator.v
Jason e93bc33c6c Production fixes 1-7: detection bugs, cfar→threshold rename, digital gain control, Doppler mismatch protection, decimator watchdog, bypass_mode dead code removal, range-mode register (21/21 regression PASS)
Fix 1: Combinational magnitude + non-sticky detection flag (tb: 23/23)
Fix 2: Rename all cfar_* signals to detect_*/threshold_* (honest naming)
Fix 3: New rx_gain_control.v between DDC and FFT, opcode 0x16 (tb: 33/33)
Fix 4: Clamp host_chirps_per_elev to DOPPLER_FFT_SIZE, error flag (E2E: 54/54)
Fix 5: Decimator watchdog timeout, 256-cycle limit (tb: 63/63)
Fix 6: Remove bypass_mode dead code from ddc_400m.v (DDC tb: 21/21)
Fix 7: Range-mode register 0x20 with status readback (USB tb: 77/77)
2026-03-20 04:38:35 +02:00

406 lines
15 KiB
Verilog

`timescale 1ns / 1ps
/**
* range_bin_decimator.v
*
* Reduces 1024 range bins from the matched filter output down to 64 bins
* for the Doppler processor. Supports multiple decimation modes:
*
* Mode 2'b00: Simple decimation (take every Nth sample)
* Mode 2'b01: Peak detection (select max-magnitude sample from each group)
* Mode 2'b10: Averaging (sum group and divide by N)
* Mode 2'b11: Reserved
*
* Interface contract (from radar_receiver_final.v line 229):
* .clk, .reset_n
* .range_i_in, .range_q_in, .range_valid_in ← from matched_filter output
* .range_i_out, .range_q_out, .range_valid_out → to Doppler processor
* .range_bin_index → 6-bit output bin index
* .decimation_mode ← 2-bit mode select
* .start_bin ← 10-bit start offset
*
* start_bin usage:
* When start_bin > 0, the decimator skips the first 'start_bin' valid
* input samples before beginning decimation. This allows selecting a
* region of interest within the 1024 range bins (e.g., to focus on
* near-range or far-range targets). When start_bin = 0 (default),
* all 1024 bins are processed starting from bin 0.
*
* Clock domain: clk (100 MHz)
* Decimation: 1024 → 64 (factor of 16)
*/
module range_bin_decimator #(
parameter INPUT_BINS = 1024,
parameter OUTPUT_BINS = 64,
parameter DECIMATION_FACTOR = 16
) (
input wire clk,
input wire reset_n,
// Input from matched filter
input wire signed [15:0] range_i_in,
input wire signed [15:0] range_q_in,
input wire range_valid_in,
// Output to Doppler processor
output reg signed [15:0] range_i_out,
output reg signed [15:0] range_q_out,
output reg range_valid_out,
output reg [5:0] range_bin_index,
// Configuration
input wire [1:0] decimation_mode, // 00=decimate, 01=peak, 10=average
input wire [9:0] start_bin, // First input bin to process
// Diagnostics
output reg watchdog_timeout // Pulses high for 1 cycle on watchdog reset
`ifdef FORMAL
,
output wire [2:0] fv_state,
output wire [9:0] fv_in_bin_count,
output wire [3:0] fv_group_sample_count,
output wire [5:0] fv_output_bin_count,
output wire [9:0] fv_skip_count
`endif
);
// Fix 5: Watchdog timeout — if no valid input arrives for WATCHDOG_LIMIT
// clocks while in ST_PROCESS or ST_SKIP, return to ST_IDLE to prevent hang.
// 256 clocks at 100MHz = 2.56us, well beyond normal inter-sample gap.
localparam WATCHDOG_LIMIT = 10'd256;
// ============================================================================
// INTERNAL SIGNALS
// ============================================================================
// Input bin counter (0..1023)
reg [9:0] in_bin_count;
// Group tracking
reg [3:0] group_sample_count; // 0..15 within current group of 16
reg [5:0] output_bin_count; // 0..63 output bin index
// State machine
reg [2:0] state;
localparam ST_IDLE = 3'd0;
localparam ST_SKIP = 3'd1; // Skip first start_bin samples
localparam ST_PROCESS = 3'd2;
localparam ST_EMIT = 3'd3;
localparam ST_DONE = 3'd4;
// Skip counter for start_bin
reg [9:0] skip_count;
// Watchdog counter — counts consecutive clocks with no range_valid_in
reg [9:0] watchdog_count;
`ifdef FORMAL
assign fv_state = state;
assign fv_in_bin_count = in_bin_count;
assign fv_group_sample_count = group_sample_count;
assign fv_output_bin_count = output_bin_count;
assign fv_skip_count = skip_count;
`endif
// ============================================================================
// PEAK DETECTION (Mode 01)
// ============================================================================
// Track the sample with the largest magnitude in the current group of 16
reg signed [15:0] peak_i, peak_q;
reg [16:0] peak_mag; // |I| + |Q| approximation
wire [16:0] cur_mag;
// Magnitude approximation: |I| + |Q| (avoids multiplier for sqrt(I²+Q²))
wire [15:0] abs_i = range_i_in[15] ? (~range_i_in + 1) : range_i_in;
wire [15:0] abs_q = range_q_in[15] ? (~range_q_in + 1) : range_q_in;
assign cur_mag = {1'b0, abs_i} + {1'b0, abs_q};
// ============================================================================
// AVERAGING (Mode 10)
// ============================================================================
// Accumulate I and Q separately, then divide by DECIMATION_FACTOR (>>4)
reg signed [19:0] sum_i, sum_q; // 16 + 4 guard bits for sum of 16 values
// ============================================================================
// SIMPLE DECIMATION (Mode 00)
// ============================================================================
// Just take sample at offset (group_start + DECIMATION_FACTOR/2) for center
reg signed [15:0] decim_i, decim_q;
// ============================================================================
// MAIN STATE MACHINE
// ============================================================================
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
state <= ST_IDLE;
in_bin_count <= 10'd0;
group_sample_count <= 4'd0;
output_bin_count <= 6'd0;
skip_count <= 10'd0;
watchdog_count <= 10'd0;
watchdog_timeout <= 1'b0;
range_valid_out <= 1'b0;
range_i_out <= 16'd0;
range_q_out <= 16'd0;
range_bin_index <= 6'd0;
peak_i <= 16'd0;
peak_q <= 16'd0;
peak_mag <= 17'd0;
sum_i <= 20'd0;
sum_q <= 20'd0;
decim_i <= 16'd0;
decim_q <= 16'd0;
end else begin
// Default: output not valid, watchdog not triggered
range_valid_out <= 1'b0;
watchdog_timeout <= 1'b0;
case (state)
// ================================================================
// IDLE: Wait for first valid input
// ================================================================
ST_IDLE: begin
in_bin_count <= 10'd0;
group_sample_count <= 4'd0;
output_bin_count <= 6'd0;
skip_count <= 10'd0;
watchdog_count <= 10'd0;
peak_i <= 16'd0;
peak_q <= 16'd0;
peak_mag <= 17'd0;
sum_i <= 20'd0;
sum_q <= 20'd0;
if (range_valid_in) begin
in_bin_count <= 10'd1;
if (start_bin > 10'd0) begin
// Need to skip 'start_bin' samples first
skip_count <= 10'd1;
state <= ST_SKIP;
end else begin
// No skip — process first sample immediately
state <= ST_PROCESS;
group_sample_count <= 4'd1;
// Mode-specific first sample handling
case (decimation_mode)
2'b00: begin // Simple decimation — check if center sample
if (4'd0 == (DECIMATION_FACTOR / 2)) begin
decim_i <= range_i_in;
decim_q <= range_q_in;
end
end
2'b01: begin // Peak detection
peak_i <= range_i_in;
peak_q <= range_q_in;
peak_mag <= cur_mag;
end
2'b10: begin // Averaging
sum_i <= {{4{range_i_in[15]}}, range_i_in};
sum_q <= {{4{range_q_in[15]}}, range_q_in};
end
default: ;
endcase
end
end
end
// ================================================================
// SKIP: Discard input samples until start_bin reached
// ================================================================
ST_SKIP: begin
if (range_valid_in) begin
watchdog_count <= 10'd0;
in_bin_count <= in_bin_count + 1;
if (skip_count >= start_bin) begin
// Done skipping — this sample is the first to process
state <= ST_PROCESS;
group_sample_count <= 4'd1;
case (decimation_mode)
2'b00: begin
if (4'd0 == (DECIMATION_FACTOR / 2)) begin
decim_i <= range_i_in;
decim_q <= range_q_in;
end
end
2'b01: begin
peak_i <= range_i_in;
peak_q <= range_q_in;
peak_mag <= cur_mag;
end
2'b10: begin
sum_i <= {{4{range_i_in[15]}}, range_i_in};
sum_q <= {{4{range_q_in[15]}}, range_q_in};
end
default: ;
endcase
end else begin
skip_count <= skip_count + 1;
end
end else begin
// No valid input — increment watchdog
if (watchdog_count >= WATCHDOG_LIMIT - 1) begin
watchdog_timeout <= 1'b1;
state <= ST_IDLE;
`ifdef SIMULATION
$display("[RNG_DECIM] WATCHDOG: timeout in ST_SKIP after %0d idle clocks", WATCHDOG_LIMIT);
`endif
end else begin
watchdog_count <= watchdog_count + 1;
end
end
end
// ================================================================
// PROCESS: Accumulate samples within each group of DECIMATION_FACTOR
// ================================================================
ST_PROCESS: begin
if (range_valid_in) begin
watchdog_count <= 10'd0;
in_bin_count <= in_bin_count + 1;
// Mode-specific sample processing — always process
// the current sample before checking overflow
case (decimation_mode)
2'b00: begin // Simple decimation
if (group_sample_count == (DECIMATION_FACTOR / 2)) begin
decim_i <= range_i_in;
decim_q <= range_q_in;
end
end
2'b01: begin // Peak detection
if (cur_mag > peak_mag) begin
peak_i <= range_i_in;
peak_q <= range_q_in;
peak_mag <= cur_mag;
end
end
2'b10: begin // Averaging
sum_i <= sum_i + {{4{range_i_in[15]}}, range_i_in};
sum_q <= sum_q + {{4{range_q_in[15]}}, range_q_in};
end
default: ;
endcase
// Check if group is complete
if (group_sample_count == DECIMATION_FACTOR - 1) begin
// Group complete — emit output
state <= ST_EMIT;
group_sample_count <= 4'd0;
end else if (in_bin_count >= INPUT_BINS - 1) begin
// Overflow guard: consumed all input bins but group
// is not yet complete. Stop to prevent corruption of
// downstream Doppler BRAM if matched filter emits
// more than INPUT_BINS valid samples.
state <= ST_DONE;
end else begin
group_sample_count <= group_sample_count + 1;
end
end else begin
// No valid input — increment watchdog
if (watchdog_count >= WATCHDOG_LIMIT - 1) begin
watchdog_timeout <= 1'b1;
state <= ST_IDLE;
`ifdef SIMULATION
$display("[RNG_DECIM] WATCHDOG: timeout in ST_PROCESS after %0d idle clocks", WATCHDOG_LIMIT);
`endif
end else begin
watchdog_count <= watchdog_count + 1;
end
end
end
// ================================================================
// EMIT: Output one decimated range bin
// ================================================================
ST_EMIT: begin
range_valid_out <= 1'b1;
range_bin_index <= output_bin_count;
case (decimation_mode)
2'b00: begin // Simple decimation
range_i_out <= decim_i;
range_q_out <= decim_q;
end
2'b01: begin // Peak detection
range_i_out <= peak_i;
range_q_out <= peak_q;
end
2'b10: begin // Averaging (sum >> 4 = divide by 16)
range_i_out <= sum_i[19:4];
range_q_out <= sum_q[19:4];
end
default: begin
range_i_out <= 16'd0;
range_q_out <= 16'd0;
end
endcase
// Reset group accumulators
peak_i <= 16'd0;
peak_q <= 16'd0;
peak_mag <= 17'd0;
sum_i <= 20'd0;
sum_q <= 20'd0;
// Advance output bin
output_bin_count <= output_bin_count + 1;
// Check if all output bins emitted
if (output_bin_count == OUTPUT_BINS - 1) begin
state <= ST_DONE;
end else begin
// If we already have valid input waiting, process it immediately
if (range_valid_in) begin
state <= ST_PROCESS;
group_sample_count <= 4'd1;
in_bin_count <= in_bin_count + 1;
case (decimation_mode)
2'b00: begin
if (4'd0 == (DECIMATION_FACTOR / 2)) begin
decim_i <= range_i_in;
decim_q <= range_q_in;
end
end
2'b01: begin
peak_i <= range_i_in;
peak_q <= range_q_in;
peak_mag <= cur_mag;
end
2'b10: begin
sum_i <= {{4{range_i_in[15]}}, range_i_in};
sum_q <= {{4{range_q_in[15]}}, range_q_in};
end
default: ;
endcase
end else begin
state <= ST_PROCESS;
group_sample_count <= 4'd0;
end
end
end
// ================================================================
// DONE: All 64 output bins emitted, return to idle
// ================================================================
ST_DONE: begin
state <= ST_IDLE;
`ifdef SIMULATION
$display("[RNG_DECIM] Frame complete: %0d output bins emitted", OUTPUT_BINS);
`endif
end
default: state <= ST_IDLE;
endcase
end
end
endmodule