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)
This commit is contained in:
@@ -16,10 +16,9 @@ module ddc_400m_enhanced (
|
||||
output wire [1:0] ddc_status,
|
||||
// Enhanced interfaces
|
||||
output wire [7:0] ddc_diagnostics,
|
||||
output wire mixer_saturation,
|
||||
output wire filter_overflow,
|
||||
input wire bypass_mode, // Test mode
|
||||
|
||||
output wire mixer_saturation,
|
||||
output wire filter_overflow,
|
||||
|
||||
input wire [1:0] test_mode,
|
||||
input wire [15:0] test_phase_inc,
|
||||
input wire force_saturation,
|
||||
@@ -90,11 +89,9 @@ end
|
||||
|
||||
// CDC synchronization for control signals (2-stage synchronizers)
|
||||
(* ASYNC_REG = "TRUE" *) reg [1:0] mixers_enable_sync_chain;
|
||||
(* ASYNC_REG = "TRUE" *) reg [1:0] bypass_mode_sync_chain;
|
||||
(* ASYNC_REG = "TRUE" *) reg [1:0] force_saturation_sync_chain;
|
||||
(* ASYNC_REG = "TRUE" *) reg [1:0] force_saturation_sync_chain;
|
||||
wire mixers_enable_sync;
|
||||
wire bypass_mode_sync;
|
||||
wire force_saturation_sync;
|
||||
wire force_saturation_sync;
|
||||
|
||||
// Debug monitoring signals
|
||||
reg [31:0] sample_counter;
|
||||
@@ -139,17 +136,14 @@ assign debug_mixed_q_trunc = mixed_q[25:8];
|
||||
// Clock Domain Crossing for Control Signals (2-stage synchronizers)
|
||||
// ============================================================================
|
||||
assign mixers_enable_sync = mixers_enable_sync_chain[1];
|
||||
assign bypass_mode_sync = bypass_mode_sync_chain[1];
|
||||
assign force_saturation_sync = force_saturation_sync_chain[1];
|
||||
|
||||
always @(posedge clk_400m or negedge reset_n_400m) begin
|
||||
if (!reset_n_400m) begin
|
||||
mixers_enable_sync_chain <= 2'b00;
|
||||
bypass_mode_sync_chain <= 2'b00;
|
||||
force_saturation_sync_chain <= 2'b00;
|
||||
end else begin
|
||||
mixers_enable_sync_chain <= {mixers_enable_sync_chain[0], mixers_enable};
|
||||
bypass_mode_sync_chain <= {bypass_mode_sync_chain[0], bypass_mode};
|
||||
force_saturation_sync_chain <= {force_saturation_sync_chain[0], force_saturation};
|
||||
end
|
||||
end
|
||||
|
||||
@@ -83,6 +83,7 @@ module fv_range_bin_decimator (
|
||||
.range_bin_index (range_bin_index),
|
||||
.decimation_mode (decimation_mode),
|
||||
.start_bin (start_bin),
|
||||
.watchdog_timeout (),
|
||||
.fv_state (state),
|
||||
.fv_in_bin_count (in_bin_count),
|
||||
.fv_group_sample_count (group_sample_count),
|
||||
|
||||
@@ -37,6 +37,11 @@ module radar_receiver_final (
|
||||
input wire [15:0] host_short_listen_cycles,
|
||||
input wire [5:0] host_chirps_per_elev,
|
||||
|
||||
// Digital gain control (Fix 3: between DDC output and matched filter)
|
||||
// [3]=direction: 0=amplify(left shift), 1=attenuate(right shift)
|
||||
// [2:0]=shift amount: 0..7 bits. Default 0 = pass-through.
|
||||
input wire [3:0] host_gain_shift,
|
||||
|
||||
// STM32 toggle signals for mode 00 (STM32-driven) pass-through.
|
||||
// These are CDC-synchronized in radar_system_top.v / radar_transmitter.v
|
||||
// before reaching this module. In mode 00, the RX mode controller uses
|
||||
@@ -66,6 +71,11 @@ wire mem_ready;
|
||||
wire [15:0] adc_i_scaled, adc_q_scaled;
|
||||
wire adc_valid_sync;
|
||||
|
||||
// Gain-controlled signals (between DDC output and matched filter)
|
||||
wire signed [15:0] gc_i, gc_q;
|
||||
wire gc_valid;
|
||||
wire [7:0] gc_saturation_count; // Diagnostic: clipped sample counter
|
||||
|
||||
// Reference signals for the processing chain
|
||||
wire [15:0] long_chirp_real, long_chirp_imag;
|
||||
wire [15:0] short_chirp_real, short_chirp_imag;
|
||||
@@ -176,9 +186,8 @@ ddc_400m_enhanced ddc(
|
||||
.baseband_q(ddc_out_q), // Q output at 100MHz
|
||||
.baseband_valid_i(ddc_valid_i), // Valid at 100MHz
|
||||
.baseband_valid_q(ddc_valid_q),
|
||||
.mixers_enable(1'b1),
|
||||
.bypass_mode(1'b1)
|
||||
);
|
||||
.mixers_enable(1'b1)
|
||||
);
|
||||
|
||||
ddc_input_interface ddc_if (
|
||||
.clk(clk),
|
||||
@@ -193,6 +202,22 @@ ddc_input_interface ddc_if (
|
||||
.data_sync_error()
|
||||
);
|
||||
|
||||
// 2b. Digital Gain Control (Fix 3)
|
||||
// Host-configurable power-of-2 shift between DDC output and matched filter.
|
||||
// Default gain_shift=0 → pass-through (no behavioral change from baseline).
|
||||
rx_gain_control gain_ctrl (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.data_i_in(adc_i_scaled),
|
||||
.data_q_in(adc_q_scaled),
|
||||
.valid_in(adc_valid_sync),
|
||||
.gain_shift(host_gain_shift),
|
||||
.data_i_out(gc_i),
|
||||
.data_q_out(gc_q),
|
||||
.valid_out(gc_valid),
|
||||
.saturation_count(gc_saturation_count)
|
||||
);
|
||||
|
||||
// 3. Dual Chirp Memory Loader
|
||||
wire [9:0] sample_addr_from_chain;
|
||||
|
||||
@@ -257,9 +282,9 @@ assign range_profile_valid_out = range_valid;
|
||||
matched_filter_multi_segment mf_dual (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.ddc_i({{2{adc_i_scaled[15]}}, adc_i_scaled}),
|
||||
.ddc_q({{2{adc_q_scaled[15]}}, adc_q_scaled}),
|
||||
.ddc_valid(adc_valid_sync),
|
||||
.ddc_i({{2{gc_i[15]}}, gc_i}),
|
||||
.ddc_q({{2{gc_q[15]}}, gc_q}),
|
||||
.ddc_valid(gc_valid),
|
||||
.use_long_chirp(use_long_chirp),
|
||||
.chirp_counter(chirp_counter),
|
||||
.mc_new_chirp(mc_new_chirp),
|
||||
@@ -295,7 +320,8 @@ range_bin_decimator #(
|
||||
.range_valid_out(decimated_range_valid),
|
||||
.range_bin_index(decimated_range_bin),
|
||||
.decimation_mode(2'b01), // Peak detection mode
|
||||
.start_bin(10'd0)
|
||||
.start_bin(10'd0),
|
||||
.watchdog_timeout() // Diagnostic — unconnected (monitored via ILA if needed)
|
||||
);
|
||||
|
||||
// ========== FRAME SYNC USING chirp_counter ==========
|
||||
|
||||
@@ -161,8 +161,8 @@ wire rx_range_valid;
|
||||
wire [15:0] rx_doppler_real;
|
||||
wire [15:0] rx_doppler_imag;
|
||||
wire rx_doppler_data_valid;
|
||||
reg rx_cfar_detection;
|
||||
reg rx_cfar_valid;
|
||||
reg rx_detect_flag; // Threshold detection result (was rx_cfar_detection)
|
||||
reg rx_detect_valid; // Detection valid pulse (was rx_cfar_valid)
|
||||
|
||||
// Data packing for USB
|
||||
wire [31:0] usb_range_profile;
|
||||
@@ -170,8 +170,8 @@ wire usb_range_valid;
|
||||
wire [15:0] usb_doppler_real;
|
||||
wire [15:0] usb_doppler_imag;
|
||||
wire usb_doppler_valid;
|
||||
wire usb_cfar_detection;
|
||||
wire usb_cfar_valid;
|
||||
wire usb_detect_flag; // (was usb_cfar_detection)
|
||||
wire usb_detect_valid; // (was usb_cfar_valid)
|
||||
|
||||
// System status
|
||||
reg [3:0] status_reg;
|
||||
@@ -188,9 +188,14 @@ wire [15:0] usb_cmd_value;
|
||||
// Declared here (before rx_inst) so Icarus Verilog can resolve forward refs.
|
||||
reg [1:0] host_radar_mode;
|
||||
reg host_trigger_pulse;
|
||||
reg [15:0] host_cfar_threshold;
|
||||
reg [15:0] host_detect_threshold; // (was host_cfar_threshold)
|
||||
reg [2:0] host_stream_control;
|
||||
|
||||
// Fix 3: Digital gain control register
|
||||
// [3]=direction: 0=amplify, 1=attenuate. [2:0]=shift amount 0..7.
|
||||
// Default 0x00 = pass-through (no gain change).
|
||||
reg [3:0] host_gain_shift;
|
||||
|
||||
// Gap 2: Host-configurable chirp timing registers
|
||||
// These override the compile-time defaults in radar_mode_controller when
|
||||
// written via USB command. Defaults match the parameter values in
|
||||
@@ -203,6 +208,22 @@ reg [15:0] host_short_listen_cycles; // Opcode 0x14 (default 17450)
|
||||
reg [5:0] host_chirps_per_elev; // Opcode 0x15 (default 32)
|
||||
reg host_status_request; // Opcode 0xFF (self-clearing pulse)
|
||||
|
||||
// Fix 4: Doppler/chirps mismatch protection
|
||||
// DOPPLER_FFT_SIZE is compile-time (32). 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_FFT_SIZE = 32; // Must match doppler_processor parameter
|
||||
reg chirps_mismatch_error; // Set if host tried to set chirps != FFT size
|
||||
|
||||
// Fix 7: Range-mode register (opcode 0x20)
|
||||
// Future-proofing for 3km/10km antenna switching.
|
||||
// 2'b00 = Auto (default — system selects based on scene)
|
||||
// 2'b01 = Short-range (3km)
|
||||
// 2'b10 = Long-range (10km)
|
||||
// 2'b11 = Reserved
|
||||
// Currently a configuration store only — antenna/timing switching TBD.
|
||||
reg [1:0] host_range_mode;
|
||||
|
||||
// ============================================================================
|
||||
// CLOCK BUFFERING
|
||||
// ============================================================================
|
||||
@@ -446,6 +467,8 @@ radar_receiver_final rx_inst (
|
||||
.host_short_chirp_cycles(host_short_chirp_cycles),
|
||||
.host_short_listen_cycles(host_short_listen_cycles),
|
||||
.host_chirps_per_elev(host_chirps_per_elev),
|
||||
// Fix 3: digital gain control
|
||||
.host_gain_shift(host_gain_shift),
|
||||
// STM32 toggle signals for RX mode controller (mode 00 pass-through).
|
||||
// These are the raw GPIO inputs — the RX mode controller's edge detectors
|
||||
// (inside radar_mode_controller) handle debouncing/edge detection.
|
||||
@@ -464,30 +487,43 @@ assign rx_doppler_real = rx_doppler_output[15:0];
|
||||
assign rx_doppler_imag = rx_doppler_output[31:16];
|
||||
assign rx_doppler_data_valid = rx_doppler_valid;
|
||||
|
||||
// For this implementation, we'll create a simple CFAR detection simulation
|
||||
// In a real system, this would come from a CFAR module
|
||||
reg [7:0] cfar_counter;
|
||||
reg [16:0] cfar_mag; // Approximate magnitude for threshold detection
|
||||
// ============================================================================
|
||||
// THRESHOLD DETECTOR (renamed from misleading "CFAR" — this is NOT CFAR)
|
||||
// ============================================================================
|
||||
// Simple magnitude threshold: |I|+|Q| > host_detect_threshold
|
||||
// This is a placeholder until real CFAR (Gap 1) is implemented.
|
||||
//
|
||||
// BUG FIXES applied (Build 22):
|
||||
// 1. cfar_mag was registered (<=) then compared in same always block,
|
||||
// causing one-cycle-lag: comparison used PREVIOUS sample's magnitude.
|
||||
// FIX: compute magnitude combinationally (wire), compare same cycle.
|
||||
// 2. rx_cfar_detection was never cleared on non-detect cycles — stayed
|
||||
// latched high after first detection until system reset.
|
||||
// FIX: clear detection flag every cycle, set only on actual detect.
|
||||
|
||||
// Combinational magnitude: no pipeline lag
|
||||
wire [16:0] detect_mag;
|
||||
wire [15:0] detect_abs_i = rx_doppler_real[15] ? (~rx_doppler_real + 16'd1) : rx_doppler_real;
|
||||
wire [15:0] detect_abs_q = rx_doppler_imag[15] ? (~rx_doppler_imag + 16'd1) : rx_doppler_imag;
|
||||
assign detect_mag = {1'b0, detect_abs_i} + {1'b0, detect_abs_q};
|
||||
|
||||
reg [7:0] detect_counter;
|
||||
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
if (!sys_reset_n) begin
|
||||
cfar_counter <= 8'd0;
|
||||
rx_cfar_detection <= 1'b0;
|
||||
rx_cfar_valid <= 1'b0;
|
||||
cfar_mag <= 17'd0;
|
||||
detect_counter <= 8'd0;
|
||||
rx_detect_flag <= 1'b0;
|
||||
rx_detect_valid <= 1'b0;
|
||||
end else begin
|
||||
rx_cfar_valid <= 1'b0;
|
||||
// Default: clear every cycle (fixes sticky detection bug)
|
||||
rx_detect_flag <= 1'b0;
|
||||
rx_detect_valid <= 1'b0;
|
||||
|
||||
// Simple threshold detection on doppler magnitude
|
||||
if (rx_doppler_valid) begin
|
||||
// Calculate approximate magnitude (|I| + |Q|)
|
||||
cfar_mag <= (rx_doppler_real[15] ? -rx_doppler_real : rx_doppler_real) +
|
||||
(rx_doppler_imag[15] ? -rx_doppler_imag : rx_doppler_imag);
|
||||
|
||||
// Threshold detection (Gap 2: uses host-configurable threshold)
|
||||
if (cfar_mag > {1'b0, host_cfar_threshold}) begin
|
||||
rx_cfar_detection <= 1'b1;
|
||||
rx_cfar_valid <= 1'b1;
|
||||
cfar_counter <= cfar_counter + 1;
|
||||
// Compare combinational magnitude against threshold (same cycle)
|
||||
if (detect_mag > {1'b0, host_detect_threshold}) begin
|
||||
rx_detect_flag <= 1'b1;
|
||||
rx_detect_valid <= 1'b1;
|
||||
detect_counter <= detect_counter + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -505,8 +541,8 @@ assign usb_doppler_real = rx_doppler_real;
|
||||
assign usb_doppler_imag = rx_doppler_imag;
|
||||
assign usb_doppler_valid = rx_doppler_valid;
|
||||
|
||||
assign usb_cfar_detection = rx_cfar_detection;
|
||||
assign usb_cfar_valid = rx_cfar_valid;
|
||||
assign usb_detect_flag = rx_detect_flag;
|
||||
assign usb_detect_valid = rx_detect_valid;
|
||||
|
||||
// ============================================================================
|
||||
// USB DATA INTERFACE INSTANTIATION
|
||||
@@ -523,8 +559,8 @@ usb_data_interface usb_inst (
|
||||
.doppler_real(usb_doppler_real),
|
||||
.doppler_imag(usb_doppler_imag),
|
||||
.doppler_valid(usb_doppler_valid),
|
||||
.cfar_detection(usb_cfar_detection),
|
||||
.cfar_valid(usb_cfar_valid),
|
||||
.cfar_detection(usb_detect_flag),
|
||||
.cfar_valid(usb_detect_valid),
|
||||
|
||||
// FT601 Interface
|
||||
.ft601_data(ft601_data),
|
||||
@@ -554,7 +590,7 @@ usb_data_interface usb_inst (
|
||||
|
||||
// Gap 2: Status readback inputs
|
||||
.status_request(host_status_request),
|
||||
.status_cfar_threshold(host_cfar_threshold),
|
||||
.status_cfar_threshold(host_detect_threshold),
|
||||
.status_stream_ctrl(host_stream_control),
|
||||
.status_radar_mode(host_radar_mode),
|
||||
.status_long_chirp(host_long_chirp_cycles),
|
||||
@@ -562,7 +598,8 @@ usb_data_interface usb_inst (
|
||||
.status_guard(host_guard_cycles),
|
||||
.status_short_chirp(host_short_chirp_cycles),
|
||||
.status_short_listen(host_short_listen_cycles),
|
||||
.status_chirps_per_elev(host_chirps_per_elev)
|
||||
.status_chirps_per_elev(host_chirps_per_elev),
|
||||
.status_range_mode(host_range_mode)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
@@ -608,15 +645,16 @@ wire cmd_valid_100m = cmd_valid_toggle_100m ^ cmd_valid_toggle_100m_prev;
|
||||
// Sample cmd_data fields when CDC'd valid pulse arrives. Data is stable
|
||||
// because the read FSM holds cmd_opcode/addr/value until the next command.
|
||||
// NOTE: reg declarations for host_radar_mode, host_trigger_pulse,
|
||||
// host_cfar_threshold, host_stream_control are in INTERNAL SIGNALS section
|
||||
// host_detect_threshold, host_stream_control are in INTERNAL SIGNALS section
|
||||
// above (before rx_inst) to avoid Icarus Verilog forward-reference errors.
|
||||
|
||||
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
if (!sys_reset_n) begin
|
||||
host_radar_mode <= 2'b01; // Default: auto-scan
|
||||
host_trigger_pulse <= 1'b0;
|
||||
host_cfar_threshold <= 16'd10000; // Default threshold
|
||||
host_detect_threshold <= 16'd10000; // Default threshold
|
||||
host_stream_control <= 3'b111; // Default: all streams enabled
|
||||
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;
|
||||
host_long_listen_cycles <= 16'd13700;
|
||||
@@ -625,6 +663,8 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
host_short_listen_cycles <= 16'd17450;
|
||||
host_chirps_per_elev <= 6'd32;
|
||||
host_status_request <= 1'b0;
|
||||
chirps_mismatch_error <= 1'b0;
|
||||
host_range_mode <= 2'b00; // Default: auto
|
||||
end else begin
|
||||
host_trigger_pulse <= 1'b0; // Self-clearing pulse
|
||||
host_status_request <= 1'b0; // Self-clearing pulse
|
||||
@@ -632,7 +672,7 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
case (usb_cmd_opcode)
|
||||
8'h01: host_radar_mode <= usb_cmd_value[1:0];
|
||||
8'h02: host_trigger_pulse <= 1'b1;
|
||||
8'h03: host_cfar_threshold <= usb_cmd_value;
|
||||
8'h03: host_detect_threshold <= usb_cmd_value;
|
||||
8'h04: host_stream_control <= usb_cmd_value[2:0];
|
||||
// Gap 2: chirp timing configuration
|
||||
8'h10: host_long_chirp_cycles <= usb_cmd_value;
|
||||
@@ -640,7 +680,23 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||
8'h12: host_guard_cycles <= usb_cmd_value;
|
||||
8'h13: host_short_chirp_cycles <= usb_cmd_value;
|
||||
8'h14: host_short_listen_cycles <= usb_cmd_value;
|
||||
8'h15: host_chirps_per_elev <= usb_cmd_value[5:0];
|
||||
8'h15: begin
|
||||
// Fix 4: Clamp chirps_per_elev to DOPPLER_FFT_SIZE.
|
||||
// If host requests a different value, clamp and set error flag.
|
||||
if (usb_cmd_value[5:0] > DOPPLER_FFT_SIZE[5:0]) begin
|
||||
host_chirps_per_elev <= DOPPLER_FFT_SIZE[5:0];
|
||||
chirps_mismatch_error <= 1'b1;
|
||||
end else if (usb_cmd_value[5:0] == 6'd0) begin
|
||||
host_chirps_per_elev <= DOPPLER_FFT_SIZE[5:0];
|
||||
chirps_mismatch_error <= 1'b1;
|
||||
end else begin
|
||||
host_chirps_per_elev <= usb_cmd_value[5:0];
|
||||
// Clear error only if value matches FFT size exactly
|
||||
chirps_mismatch_error <= (usb_cmd_value[5:0] != DOPPLER_FFT_SIZE[5:0]);
|
||||
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]; // Fix 7: range mode
|
||||
8'hFF: host_status_request <= 1'b1; // Gap 2: status readback
|
||||
default: ;
|
||||
endcase
|
||||
|
||||
@@ -51,7 +51,10 @@ module range_bin_decimator #(
|
||||
|
||||
// Configuration
|
||||
input wire [1:0] decimation_mode, // 00=decimate, 01=peak, 10=average
|
||||
input wire [9:0] start_bin // First input bin to process
|
||||
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
|
||||
,
|
||||
@@ -63,6 +66,11 @@ module range_bin_decimator #(
|
||||
`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
|
||||
// ============================================================================
|
||||
@@ -85,6 +93,9 @@ 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;
|
||||
@@ -128,6 +139,8 @@ always @(posedge clk or negedge reset_n) begin
|
||||
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;
|
||||
@@ -140,8 +153,9 @@ always @(posedge clk or negedge reset_n) begin
|
||||
decim_i <= 16'd0;
|
||||
decim_q <= 16'd0;
|
||||
end else begin
|
||||
// Default: output not valid
|
||||
range_valid_out <= 1'b0;
|
||||
// Default: output not valid, watchdog not triggered
|
||||
range_valid_out <= 1'b0;
|
||||
watchdog_timeout <= 1'b0;
|
||||
|
||||
case (state)
|
||||
// ================================================================
|
||||
@@ -152,6 +166,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
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;
|
||||
@@ -198,6 +213,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
// ================================================================
|
||||
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
|
||||
@@ -226,6 +242,17 @@ always @(posedge clk or negedge reset_n) begin
|
||||
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
|
||||
|
||||
@@ -234,6 +261,7 @@ always @(posedge clk or negedge reset_n) begin
|
||||
// ================================================================
|
||||
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
|
||||
@@ -273,6 +301,17 @@ always @(posedge clk or negedge reset_n) begin
|
||||
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
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ PROD_RTL=(
|
||||
usb_data_interface.v
|
||||
edge_detector.v
|
||||
radar_mode_controller.v
|
||||
rx_gain_control.v
|
||||
)
|
||||
|
||||
# Source-only RTL (not instantiated at top level, but should still be lint-clean)
|
||||
@@ -367,6 +368,14 @@ run_test "Doppler Processor (DSP48)" \
|
||||
tb/tb_doppler_reg.vvp \
|
||||
tb/tb_doppler_cosim.v doppler_processor.v xfft_32.v fft_engine.v
|
||||
|
||||
run_test "Threshold Detector (detection bugs)" \
|
||||
tb/tb_threshold_detector.vvp \
|
||||
tb/tb_threshold_detector.v
|
||||
|
||||
run_test "RX Gain Control (digital gain)" \
|
||||
tb/tb_rx_gain_control.vvp \
|
||||
tb/tb_rx_gain_control.v rx_gain_control.v
|
||||
|
||||
echo ""
|
||||
|
||||
# ===========================================================================
|
||||
@@ -390,7 +399,8 @@ if [[ "$QUICK" -eq 0 ]]; then
|
||||
cdc_modules.v fir_lowpass.v ddc_input_interface.v \
|
||||
chirp_memory_loader_param.v latency_buffer.v \
|
||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v
|
||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||
rx_gain_control.v
|
||||
|
||||
# Golden compare
|
||||
run_test "Receiver (golden compare)" \
|
||||
@@ -401,7 +411,8 @@ if [[ "$QUICK" -eq 0 ]]; then
|
||||
cdc_modules.v fir_lowpass.v ddc_input_interface.v \
|
||||
chirp_memory_loader_param.v latency_buffer.v \
|
||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v
|
||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||
rx_gain_control.v
|
||||
|
||||
# Full system top (monitoring-only, legacy)
|
||||
run_test "System Top (radar_system_tb)" \
|
||||
@@ -414,7 +425,8 @@ if [[ "$QUICK" -eq 0 ]]; then
|
||||
chirp_memory_loader_param.v latency_buffer.v \
|
||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||
usb_data_interface.v edge_detector.v radar_mode_controller.v
|
||||
usb_data_interface.v edge_detector.v radar_mode_controller.v \
|
||||
rx_gain_control.v
|
||||
|
||||
# E2E integration (46 strict checks: TX, RX, USB R/W, CDC, safety, reset)
|
||||
run_test "System E2E (tb_system_e2e)" \
|
||||
@@ -427,7 +439,8 @@ if [[ "$QUICK" -eq 0 ]]; then
|
||||
chirp_memory_loader_param.v latency_buffer.v \
|
||||
matched_filter_multi_segment.v matched_filter_processing_chain.v \
|
||||
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \
|
||||
usb_data_interface.v edge_detector.v radar_mode_controller.v
|
||||
usb_data_interface.v edge_detector.v radar_mode_controller.v \
|
||||
rx_gain_control.v
|
||||
else
|
||||
echo " (skipped receiver golden + system top + E2E — use without --quick)"
|
||||
SKIP=$((SKIP + 4))
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/**
|
||||
* rx_gain_control.v
|
||||
*
|
||||
* Host-configurable digital gain control for the receive path.
|
||||
* Placed between DDC output (ddc_input_interface) and matched filter input.
|
||||
*
|
||||
* Features:
|
||||
* - Bidirectional power-of-2 gain shift (arithmetic shift)
|
||||
* - gain_shift[3] = direction: 0 = left shift (amplify), 1 = right shift (attenuate)
|
||||
* - gain_shift[2:0] = amount: 0..7 bits
|
||||
* - Symmetric saturation to ±32767 on overflow (left shift only)
|
||||
* - Saturation counter: 8-bit, counts samples that clipped (wraps at 255)
|
||||
* - 1-cycle latency, valid-in/valid-out pipeline
|
||||
* - Zero-overhead pass-through when gain_shift == 0
|
||||
*
|
||||
* Intended insertion point in radar_receiver_final.v:
|
||||
* ddc_input_interface → rx_gain_control → matched_filter_multi_segment
|
||||
*/
|
||||
|
||||
module rx_gain_control (
|
||||
input wire clk,
|
||||
input wire reset_n,
|
||||
|
||||
// Data input (from DDC / ddc_input_interface)
|
||||
input wire signed [15:0] data_i_in,
|
||||
input wire signed [15:0] data_q_in,
|
||||
input wire valid_in,
|
||||
|
||||
// Gain configuration (from host via USB command)
|
||||
// [3] = direction: 0=amplify (left shift), 1=attenuate (right shift)
|
||||
// [2:0] = shift amount: 0..7 bits
|
||||
input wire [3:0] gain_shift,
|
||||
|
||||
// Data output (to matched filter)
|
||||
output reg signed [15:0] data_i_out,
|
||||
output reg signed [15:0] data_q_out,
|
||||
output reg valid_out,
|
||||
|
||||
// Diagnostics
|
||||
output reg [7:0] saturation_count // Number of clipped samples (wraps at 255)
|
||||
);
|
||||
|
||||
// Decompose gain_shift
|
||||
wire shift_right = gain_shift[3];
|
||||
wire [2:0] shift_amt = gain_shift[2:0];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Combinational shift + saturation
|
||||
// -------------------------------------------------------------------------
|
||||
// Use wider intermediates to detect overflow on left shift.
|
||||
// 24 bits is enough: 16 + 7 shift = 23 significant bits max.
|
||||
|
||||
wire signed [23:0] shifted_i;
|
||||
wire signed [23:0] shifted_q;
|
||||
|
||||
assign shifted_i = shift_right ? (data_i_in >>> shift_amt)
|
||||
: (data_i_in <<< shift_amt);
|
||||
assign shifted_q = shift_right ? (data_q_in >>> shift_amt)
|
||||
: (data_q_in <<< shift_amt);
|
||||
|
||||
// Saturation: clamp to signed 16-bit range [-32768, +32767]
|
||||
wire overflow_i = (shifted_i > 24'sd32767) || (shifted_i < -24'sd32768);
|
||||
wire overflow_q = (shifted_q > 24'sd32767) || (shifted_q < -24'sd32768);
|
||||
|
||||
wire signed [15:0] sat_i = overflow_i ? (shifted_i[23] ? -16'sd32768 : 16'sd32767)
|
||||
: shifted_i[15:0];
|
||||
wire signed [15:0] sat_q = overflow_q ? (shifted_q[23] ? -16'sd32768 : 16'sd32767)
|
||||
: shifted_q[15:0];
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Registered output stage (1-cycle latency)
|
||||
// -------------------------------------------------------------------------
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
data_i_out <= 16'sd0;
|
||||
data_q_out <= 16'sd0;
|
||||
valid_out <= 1'b0;
|
||||
saturation_count <= 8'd0;
|
||||
end else begin
|
||||
valid_out <= valid_in;
|
||||
|
||||
if (valid_in) begin
|
||||
data_i_out <= sat_i;
|
||||
data_q_out <= sat_q;
|
||||
|
||||
// Count clipped samples (either channel clipping counts as 1)
|
||||
if ((overflow_i || overflow_q) && (saturation_count != 8'hFF))
|
||||
saturation_count <= saturation_count + 8'd1;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
endmodule
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -22,7 +22,6 @@ module tb_ddc_400m;
|
||||
wire [7:0] ddc_diagnostics;
|
||||
wire mixer_saturation;
|
||||
wire filter_overflow;
|
||||
reg bypass_mode;
|
||||
reg [1:0] test_mode;
|
||||
reg [15:0] test_phase_inc;
|
||||
reg force_saturation;
|
||||
@@ -62,7 +61,6 @@ module tb_ddc_400m;
|
||||
.ddc_diagnostics (ddc_diagnostics),
|
||||
.mixer_saturation (mixer_saturation),
|
||||
.filter_overflow (filter_overflow),
|
||||
.bypass_mode (bypass_mode),
|
||||
.test_mode (test_mode),
|
||||
.test_phase_inc (test_phase_inc),
|
||||
.force_saturation (force_saturation),
|
||||
@@ -101,7 +99,6 @@ module tb_ddc_400m;
|
||||
adc_data = 0;
|
||||
adc_data_valid_i = 0;
|
||||
adc_data_valid_q = 0;
|
||||
bypass_mode = 0;
|
||||
test_mode = 2'b00;
|
||||
test_phase_inc = 0;
|
||||
force_saturation = 0;
|
||||
|
||||
@@ -94,7 +94,6 @@ module tb_ddc_cosim;
|
||||
.ddc_diagnostics (ddc_diagnostics),
|
||||
.mixer_saturation (mixer_saturation),
|
||||
.filter_overflow (filter_overflow),
|
||||
.bypass_mode (1'b0),
|
||||
.test_mode (2'b00),
|
||||
.test_phase_inc (16'h0000),
|
||||
.force_saturation (1'b0),
|
||||
|
||||
@@ -115,7 +115,8 @@ range_bin_decimator #(
|
||||
.range_valid_out(decim_valid_out),
|
||||
.range_bin_index(decim_bin_index),
|
||||
.decimation_mode(2'b01), // Peak detection mode
|
||||
.start_bin(10'd0)
|
||||
.start_bin(10'd0),
|
||||
.watchdog_timeout()
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -149,7 +149,10 @@ radar_receiver_final dut (
|
||||
.host_guard_cycles(16'd500),
|
||||
.host_short_chirp_cycles(16'd50),
|
||||
.host_short_listen_cycles(16'd1000),
|
||||
.host_chirps_per_elev(6'd32)
|
||||
.host_chirps_per_elev(6'd32),
|
||||
|
||||
// Fix 3: digital gain control — pass-through for golden reference
|
||||
.host_gain_shift(4'd0)
|
||||
);
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -20,6 +20,7 @@ module tb_range_bin_decimator;
|
||||
wire [5:0] range_bin_index;
|
||||
reg [1:0] decimation_mode;
|
||||
reg [9:0] start_bin;
|
||||
wire watchdog_timeout;
|
||||
|
||||
// ── Test bookkeeping ───────────────────────────────────────
|
||||
integer pass_count;
|
||||
@@ -55,9 +56,18 @@ module tb_range_bin_decimator;
|
||||
.range_valid_out(range_valid_out),
|
||||
.range_bin_index(range_bin_index),
|
||||
.decimation_mode(decimation_mode),
|
||||
.start_bin (start_bin)
|
||||
.start_bin (start_bin),
|
||||
.watchdog_timeout(watchdog_timeout)
|
||||
);
|
||||
|
||||
// ── Watchdog timeout pulse counter ───────────────────────────
|
||||
integer wd_pulse_count;
|
||||
always @(posedge clk) begin
|
||||
#1;
|
||||
if (watchdog_timeout)
|
||||
wd_pulse_count = wd_pulse_count + 1;
|
||||
end
|
||||
|
||||
// ── Concurrent output capture block ────────────────────────
|
||||
// Runs alongside the initial block, captures every valid output
|
||||
always @(posedge clk) begin
|
||||
@@ -186,6 +196,7 @@ module tb_range_bin_decimator;
|
||||
test_num = 0;
|
||||
cap_enable = 0;
|
||||
cap_count = 0;
|
||||
wd_pulse_count = 0;
|
||||
|
||||
// Init cap arrays
|
||||
for (i = 0; i < OUTPUT_BINS; i = i + 1) begin
|
||||
@@ -716,6 +727,113 @@ module tb_range_bin_decimator;
|
||||
check(cap_count >= 1 && cap_i[0] == 16'sd8,
|
||||
"14c: Bin 0 = 8 (original behavior preserved)");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 15: Watchdog Timeout (Fix 5)
|
||||
// ════════════════════════════════════════════════════════
|
||||
$display("\n--- Test Group 15: Watchdog Timeout (Fix 5) ---");
|
||||
|
||||
// 15a: Stall in ST_PROCESS — feed 8 samples (half a group) then stop.
|
||||
// After 256 clocks of no valid, watchdog should fire and return to IDLE.
|
||||
// After that, a fresh full frame should still produce 64 outputs.
|
||||
$display(" 15a: Stall mid-group in ST_PROCESS");
|
||||
apply_reset;
|
||||
wd_pulse_count = 0;
|
||||
decimation_mode = 2'b01; // Peak mode
|
||||
|
||||
// Feed only 8 samples (partial group)
|
||||
for (i = 0; i < 8; i = i + 1) begin
|
||||
range_i_in = (i + 1) * 100;
|
||||
range_q_in = 16'd0;
|
||||
range_valid_in = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
end
|
||||
range_valid_in = 1'b0;
|
||||
|
||||
// Wait for watchdog to fire (256 + margin)
|
||||
repeat (280) @(posedge clk); #1;
|
||||
check(wd_pulse_count == 1, "15a: watchdog_timeout pulsed once");
|
||||
|
||||
// Verify DUT returned to idle — feed a complete frame and check output
|
||||
// Mode 01 (peak) with ramp: group 0 has values 0..15, peak = 15
|
||||
start_capture;
|
||||
feed_ramp;
|
||||
stop_capture;
|
||||
|
||||
$display(" 15a: Output count after recovery: %0d", cap_count);
|
||||
check(cap_count == OUTPUT_BINS, "15a: 64 outputs after watchdog recovery");
|
||||
check(cap_count >= 1 && cap_i[0] == 16'sd15, "15a: Bin 0 = 15 (peak of 0..15) after recovery");
|
||||
|
||||
// 15b: Stall in ST_SKIP — set start_bin=100, feed 50 samples then stop.
|
||||
// DUT should be in ST_SKIP, watchdog fires after 256 idle clocks.
|
||||
$display(" 15b: Stall in ST_SKIP");
|
||||
apply_reset;
|
||||
wd_pulse_count = 0;
|
||||
decimation_mode = 2'b00;
|
||||
start_bin = 10'd100;
|
||||
|
||||
// Feed only 50 samples (not enough to finish skipping)
|
||||
for (i = 0; i < 50; i = i + 1) begin
|
||||
range_i_in = i[15:0];
|
||||
range_q_in = 16'd0;
|
||||
range_valid_in = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
end
|
||||
range_valid_in = 1'b0;
|
||||
|
||||
// Wait for watchdog
|
||||
repeat (280) @(posedge clk); #1;
|
||||
check(wd_pulse_count == 1, "15b: watchdog_timeout pulsed once in ST_SKIP");
|
||||
|
||||
// Recovery: feed full frame with start_bin=0
|
||||
start_bin = 10'd0;
|
||||
start_capture;
|
||||
feed_ramp;
|
||||
stop_capture;
|
||||
check(cap_count == OUTPUT_BINS, "15b: 64 outputs after ST_SKIP watchdog recovery");
|
||||
|
||||
// 15c: Normal operation should NOT trigger watchdog.
|
||||
// Short gaps (20 clocks) are well under the 256 limit.
|
||||
$display(" 15c: Normal gaps do NOT trigger watchdog");
|
||||
apply_reset;
|
||||
wd_pulse_count = 0;
|
||||
decimation_mode = 2'b01;
|
||||
start_bin = 10'd0;
|
||||
|
||||
start_capture;
|
||||
// Reuse the gap-feed pattern from Test Group 10: gaps of 20 cycles every 50 samples
|
||||
begin : wd_gap_feed
|
||||
integer sample_idx, samples_since_gap;
|
||||
sample_idx = 0;
|
||||
samples_since_gap = 0;
|
||||
while (sample_idx < INPUT_BINS) begin
|
||||
range_i_in = sample_idx[15:0];
|
||||
range_q_in = 16'd0;
|
||||
range_valid_in = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
sample_idx = sample_idx + 1;
|
||||
samples_since_gap = samples_since_gap + 1;
|
||||
if (samples_since_gap == 50 && sample_idx < INPUT_BINS) begin
|
||||
range_valid_in = 1'b0;
|
||||
repeat (20) @(posedge clk);
|
||||
#1;
|
||||
samples_since_gap = 0;
|
||||
end
|
||||
end
|
||||
range_valid_in = 1'b0;
|
||||
end
|
||||
stop_capture;
|
||||
|
||||
check(wd_pulse_count == 0, "15c: No watchdog timeout with 20-cycle gaps");
|
||||
check(cap_count == OUTPUT_BINS, "15c: Still outputs 64 bins with gaps");
|
||||
|
||||
// 15d: Watchdog does NOT fire in ST_IDLE (no false trigger when idle).
|
||||
$display(" 15d: No false watchdog in ST_IDLE");
|
||||
apply_reset;
|
||||
wd_pulse_count = 0;
|
||||
// Just wait 512 clocks doing nothing — should NOT trigger watchdog
|
||||
repeat (512) @(posedge clk); #1;
|
||||
check(wd_pulse_count == 0, "15d: No watchdog timeout while idle");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// Summary
|
||||
// ════════════════════════════════════════════════════════
|
||||
|
||||
@@ -0,0 +1,361 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/**
|
||||
* tb_rx_gain_control.v
|
||||
*
|
||||
* Unit test for rx_gain_control — host-configurable digital gain
|
||||
* between DDC output and matched filter input.
|
||||
*
|
||||
* Tests:
|
||||
* 1. Pass-through (shift=0): output == input
|
||||
* 2. Left shift (amplify): correct gain, saturation on overflow
|
||||
* 3. Right shift (attenuate): correct arithmetic shift
|
||||
* 4. Saturation counter: counts clipped samples
|
||||
* 5. Negative inputs: sign-correct shifting
|
||||
* 6. Max shift amounts (7 bits each direction)
|
||||
* 7. Valid signal pipeline: 1-cycle latency
|
||||
* 8. Dynamic gain change: gain_shift can change between samples
|
||||
* 9. Counter stops at 255 (no wrap)
|
||||
* 10. Reset clears everything
|
||||
*/
|
||||
|
||||
module tb_rx_gain_control;
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Clock and reset
|
||||
// ---------------------------------------------------------------
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
|
||||
initial clk = 0;
|
||||
always #5 clk = ~clk; // 100 MHz
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// DUT signals
|
||||
// ---------------------------------------------------------------
|
||||
reg signed [15:0] data_i_in;
|
||||
reg signed [15:0] data_q_in;
|
||||
reg valid_in;
|
||||
reg [3:0] gain_shift;
|
||||
|
||||
wire signed [15:0] data_i_out;
|
||||
wire signed [15:0] data_q_out;
|
||||
wire valid_out;
|
||||
wire [7:0] saturation_count;
|
||||
|
||||
rx_gain_control dut (
|
||||
.clk(clk),
|
||||
.reset_n(reset_n),
|
||||
.data_i_in(data_i_in),
|
||||
.data_q_in(data_q_in),
|
||||
.valid_in(valid_in),
|
||||
.gain_shift(gain_shift),
|
||||
.data_i_out(data_i_out),
|
||||
.data_q_out(data_q_out),
|
||||
.valid_out(valid_out),
|
||||
.saturation_count(saturation_count)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Test infrastructure
|
||||
// ---------------------------------------------------------------
|
||||
integer pass_count = 0;
|
||||
integer fail_count = 0;
|
||||
|
||||
task check;
|
||||
input cond;
|
||||
input [1023:0] msg;
|
||||
begin
|
||||
if (cond) begin
|
||||
$display("[PASS] %0s", msg);
|
||||
pass_count = pass_count + 1;
|
||||
end else begin
|
||||
$display("[FAIL] %0s", msg);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
// Send one sample and wait for output (1-cycle latency)
|
||||
task send_sample;
|
||||
input signed [15:0] i_val;
|
||||
input signed [15:0] q_val;
|
||||
begin
|
||||
@(negedge clk);
|
||||
data_i_in = i_val;
|
||||
data_q_in = q_val;
|
||||
valid_in = 1'b1;
|
||||
@(posedge clk); // DUT registers input
|
||||
@(negedge clk);
|
||||
valid_in = 1'b0;
|
||||
@(posedge clk); // output available after this edge
|
||||
#1; // let NBA settle
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Test sequence
|
||||
// ---------------------------------------------------------------
|
||||
initial begin
|
||||
$display("=== RX Gain Control Unit Test ===");
|
||||
|
||||
// Init
|
||||
reset_n = 0;
|
||||
data_i_in = 0;
|
||||
data_q_in = 0;
|
||||
valid_in = 0;
|
||||
gain_shift = 4'd0;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 1: Pass-through (gain_shift = 0)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 1: Pass-through (shift=0) ---");
|
||||
|
||||
gain_shift = 4'b0_000; // left shift 0 = pass-through
|
||||
send_sample(16'sd1000, 16'sd2000);
|
||||
check(data_i_out == 16'sd1000,
|
||||
"T1.1: I pass-through (1000)");
|
||||
check(data_q_out == 16'sd2000,
|
||||
"T1.2: Q pass-through (2000)");
|
||||
check(saturation_count == 8'd0,
|
||||
"T1.3: No saturation on pass-through");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 2: Left shift (amplify) without overflow
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 2: Left shift (amplify) ---");
|
||||
|
||||
gain_shift = 4'b0_010; // left shift 2 = x4
|
||||
send_sample(16'sd500, -16'sd300);
|
||||
check(data_i_out == 16'sd2000,
|
||||
"T2.1: I amplified 500<<2 = 2000");
|
||||
check(data_q_out == -16'sd1200,
|
||||
"T2.2: Q amplified -300<<2 = -1200");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 3: Left shift with overflow → saturation
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 3: Left shift with saturation ---");
|
||||
|
||||
gain_shift = 4'b0_011; // left shift 3 = x8
|
||||
send_sample(16'sd10000, -16'sd10000);
|
||||
// 10000 << 3 = 80000 > 32767 → clamp to 32767
|
||||
// -10000 << 3 = -80000 < -32768 → clamp to -32768
|
||||
check(data_i_out == 16'sd32767,
|
||||
"T3.1: I saturated to +32767");
|
||||
check(data_q_out == -16'sd32768,
|
||||
"T3.2: Q saturated to -32768");
|
||||
check(saturation_count == 8'd1,
|
||||
"T3.3: Saturation counter = 1 (both channels clipped counts as 1)");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 4: Right shift (attenuate)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 4: Right shift (attenuate) ---");
|
||||
|
||||
// Reset to clear saturation counter
|
||||
reset_n = 0;
|
||||
repeat (2) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
gain_shift = 4'b1_010; // right shift 2 = /4
|
||||
send_sample(16'sd4000, -16'sd2000);
|
||||
check(data_i_out == 16'sd1000,
|
||||
"T4.1: I attenuated 4000>>2 = 1000");
|
||||
check(data_q_out == -16'sd500,
|
||||
"T4.2: Q attenuated -2000>>2 = -500");
|
||||
check(saturation_count == 8'd0,
|
||||
"T4.3: No saturation on right shift");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 5: Right shift preserves sign (arithmetic shift)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 5: Arithmetic right shift (sign preservation) ---");
|
||||
|
||||
gain_shift = 4'b1_001; // right shift 1
|
||||
send_sample(-16'sd1, -16'sd3);
|
||||
// -1 >>> 1 = -1 (sign extension)
|
||||
// -3 >>> 1 = -2 (floor division)
|
||||
check(data_i_out == -16'sd1,
|
||||
"T5.1: -1 >>> 1 = -1 (sign preserved)");
|
||||
check(data_q_out == -16'sd2,
|
||||
"T5.2: -3 >>> 1 = -2 (arithmetic floor)");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 6: Max left shift (7 bits)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 6: Max left shift (x128) ---");
|
||||
|
||||
gain_shift = 4'b0_111; // left shift 7 = x128
|
||||
send_sample(16'sd100, -16'sd50);
|
||||
// 100 << 7 = 12800 (no overflow)
|
||||
// -50 << 7 = -6400 (no overflow)
|
||||
check(data_i_out == 16'sd12800,
|
||||
"T6.1: 100 << 7 = 12800");
|
||||
check(data_q_out == -16'sd6400,
|
||||
"T6.2: -50 << 7 = -6400");
|
||||
|
||||
// Now with values that overflow at max shift
|
||||
send_sample(16'sd300, 16'sd300);
|
||||
// 300 << 7 = 38400 > 32767 → saturate
|
||||
check(data_i_out == 16'sd32767,
|
||||
"T6.3: 300 << 7 saturates to +32767");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 7: Max right shift (7 bits)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 7: Max right shift (/128) ---");
|
||||
|
||||
gain_shift = 4'b1_111; // right shift 7 = /128
|
||||
send_sample(16'sd32767, -16'sd32768);
|
||||
// 32767 >>> 7 = 255
|
||||
// -32768 >>> 7 = -256
|
||||
check(data_i_out == 16'sd255,
|
||||
"T7.1: 32767 >>> 7 = 255");
|
||||
check(data_q_out == -16'sd256,
|
||||
"T7.2: -32768 >>> 7 = -256");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 8: Valid pipeline (1-cycle latency)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 8: Valid pipeline ---");
|
||||
|
||||
gain_shift = 4'b0_000; // pass-through
|
||||
|
||||
// Check that valid_out is low when we haven't sent anything
|
||||
@(posedge clk); #1;
|
||||
check(valid_out == 1'b0,
|
||||
"T8.1: valid_out low when no input");
|
||||
|
||||
// Send a sample and check valid_out appears 1 cycle later
|
||||
@(negedge clk);
|
||||
data_i_in = 16'sd42;
|
||||
data_q_in = 16'sd43;
|
||||
valid_in = 1'b1;
|
||||
@(posedge clk); #1;
|
||||
// This posedge just registered the input; valid_out should now be 1
|
||||
check(valid_out == 1'b1,
|
||||
"T8.2: valid_out asserts 1 cycle after valid_in");
|
||||
check(data_i_out == 16'sd42,
|
||||
"T8.3: data passes through with valid");
|
||||
|
||||
@(negedge clk);
|
||||
valid_in = 1'b0;
|
||||
@(posedge clk); #1;
|
||||
check(valid_out == 1'b0,
|
||||
"T8.4: valid_out deasserts after valid_in drops");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 9: Dynamic gain change
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 9: Dynamic gain change ---");
|
||||
|
||||
gain_shift = 4'b0_001; // x2
|
||||
send_sample(16'sd1000, 16'sd1000);
|
||||
check(data_i_out == 16'sd2000,
|
||||
"T9.1: x2 gain applied");
|
||||
|
||||
gain_shift = 4'b1_001; // /2
|
||||
send_sample(16'sd1000, 16'sd1000);
|
||||
check(data_i_out == 16'sd500,
|
||||
"T9.2: /2 gain applied after change");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 10: Zero input
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 10: Zero input ---");
|
||||
|
||||
gain_shift = 4'b0_111; // max amplify
|
||||
send_sample(16'sd0, 16'sd0);
|
||||
check(data_i_out == 16'sd0,
|
||||
"T10.1: Zero stays zero at max gain");
|
||||
check(data_q_out == 16'sd0,
|
||||
"T10.2: Zero Q stays zero at max gain");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 11: Saturation counter stops at 255
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 11: Saturation counter caps at 255 ---");
|
||||
|
||||
// Reset first
|
||||
reset_n = 0;
|
||||
repeat (2) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
gain_shift = 4'b0_111; // x128 — will saturate most inputs
|
||||
// Send 256 saturating samples to overflow the counter
|
||||
begin : sat_loop
|
||||
integer j;
|
||||
for (j = 0; j < 256; j = j + 1) begin
|
||||
@(negedge clk);
|
||||
data_i_in = 16'sd20000;
|
||||
data_q_in = 16'sd20000;
|
||||
valid_in = 1'b1;
|
||||
@(posedge clk);
|
||||
end
|
||||
end
|
||||
@(negedge clk);
|
||||
valid_in = 1'b0;
|
||||
@(posedge clk); #1;
|
||||
|
||||
check(saturation_count == 8'd255,
|
||||
"T11.1: Counter capped at 255 after 256 saturating samples");
|
||||
|
||||
// One more sample — should stay at 255
|
||||
send_sample(16'sd20000, 16'sd20000);
|
||||
check(saturation_count == 8'd255,
|
||||
"T11.2: Counter stays at 255 (no wrap)");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 12: Reset clears everything
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 12: Reset clears all ---");
|
||||
|
||||
reset_n = 0;
|
||||
repeat (2) @(posedge clk);
|
||||
reset_n = 1;
|
||||
@(posedge clk); #1;
|
||||
|
||||
check(data_i_out == 16'sd0,
|
||||
"T12.1: I output cleared on reset");
|
||||
check(data_q_out == 16'sd0,
|
||||
"T12.2: Q output cleared on reset");
|
||||
check(valid_out == 1'b0,
|
||||
"T12.3: valid_out cleared on reset");
|
||||
check(saturation_count == 8'd0,
|
||||
"T12.4: Saturation counter cleared on reset");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// SUMMARY
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("=== RX Gain Control: %0d passed, %0d failed ===",
|
||||
pass_count, fail_count);
|
||||
|
||||
if (fail_count > 0)
|
||||
$display("[FAIL] RX gain control test FAILED");
|
||||
else
|
||||
$display("[PASS] All RX gain control tests passed");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -22,6 +22,7 @@
|
||||
* G10: Stream Control (3 checks)
|
||||
* G11: Processing Latency Budgets (2 checks)
|
||||
* G12: Watchdog / Liveness (2 checks)
|
||||
* G13: Doppler/Chirps Mismatch Protection (8 checks) [Fix 4]
|
||||
*
|
||||
* Compile:
|
||||
* iverilog -g2001 -DSIMULATION -o tb/tb_system_e2e.vvp \
|
||||
@@ -745,13 +746,13 @@ initial begin
|
||||
check(dut.host_radar_mode == 2'b10,
|
||||
"G6.1: Opcode 0x01 -> host_radar_mode = 2'b10 (single chirp)");
|
||||
|
||||
// G6.2: Set CFAR threshold via USB command
|
||||
// G6.2: Set detection threshold via USB command
|
||||
bfm_send_cmd(8'h03, 8'h00, 16'h1234);
|
||||
check(dut.host_cfar_threshold == 16'h1234,
|
||||
"G6.2: Opcode 0x03 -> host_cfar_threshold = 0x1234");
|
||||
check(dut.host_detect_threshold == 16'h1234,
|
||||
"G6.2: Opcode 0x03 -> host_detect_threshold = 0x1234");
|
||||
|
||||
// G6.3: Set stream control via USB command
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0005); // enable range + cfar, disable doppler
|
||||
bfm_send_cmd(8'h04, 8'h00, 16'h0005); // enable range + detect, disable doppler
|
||||
check(dut.host_stream_control == 3'b101,
|
||||
"G6.3: Opcode 0x04 -> host_stream_control = 3'b101");
|
||||
|
||||
@@ -808,8 +809,8 @@ initial begin
|
||||
bfm_send_cmd(8'h03, 8'h00, 16'hAAAA);
|
||||
bfm_send_cmd(8'h03, 8'h00, 16'hBBBB);
|
||||
bfm_send_cmd(8'h03, 8'h00, 16'hCCCC);
|
||||
check(dut.host_cfar_threshold == 16'hCCCC,
|
||||
"G7.2: Last of 3 rapid USB commands applied (CFAR=0xCCCC)");
|
||||
check(dut.host_detect_threshold == 16'hCCCC,
|
||||
"G7.2: Last of 3 rapid USB commands applied (threshold=0xCCCC)");
|
||||
|
||||
// G7.3: Verify CDC path for TX chirp counter (120MHz→100MHz)
|
||||
// In the AERIS-10 architecture, STM32 toggles drive the TX chirp
|
||||
@@ -822,10 +823,10 @@ initial begin
|
||||
"G7.3: TX chirp CDC path delivered data (DAC or counter active)");
|
||||
|
||||
// G7.4: Command CDC didn't corrupt data — verify threshold is exact
|
||||
check(dut.host_cfar_threshold == 16'hCCCC,
|
||||
"G7.4: CDC-transferred CFAR threshold is bit-exact (0xCCCC)");
|
||||
check(dut.host_detect_threshold == 16'hCCCC,
|
||||
"G7.4: CDC-transferred detect threshold is bit-exact (0xCCCC)");
|
||||
|
||||
// Restore CFAR threshold
|
||||
// Restore detection threshold
|
||||
bfm_send_cmd(8'h03, 8'h00, 16'd10000);
|
||||
|
||||
$display("");
|
||||
@@ -996,6 +997,48 @@ initial begin
|
||||
|
||||
$display("");
|
||||
|
||||
// ================================================================
|
||||
// GROUP 13: DOPPLER/CHIRPS MISMATCH PROTECTION (Fix 4)
|
||||
// ================================================================
|
||||
$display("--- Group 13: Doppler/Chirps Mismatch Protection ---");
|
||||
|
||||
// G13.1: Setting chirps_per_elev = 32 (matching DOPPLER_FFT_SIZE) clears error
|
||||
bfm_send_cmd(8'h15, 8'h00, 16'd32);
|
||||
check(dut.host_chirps_per_elev == 6'd32,
|
||||
"G13.1: chirps_per_elev=32 accepted (matches FFT size)");
|
||||
|
||||
// G13.2: Error flag is clear when value matches
|
||||
check(dut.chirps_mismatch_error == 1'b0,
|
||||
"G13.2: Mismatch error clear when chirps==DOPPLER_FFT_SIZE");
|
||||
|
||||
// G13.3: Setting chirps_per_elev > 32 gets clamped to 32
|
||||
bfm_send_cmd(8'h15, 8'h00, 16'd48);
|
||||
check(dut.host_chirps_per_elev == 6'd32,
|
||||
"G13.3: chirps_per_elev=48 clamped to 32");
|
||||
|
||||
// G13.4: Mismatch error flag set after clamping
|
||||
check(dut.chirps_mismatch_error == 1'b1,
|
||||
"G13.4: Mismatch error set when chirps>DOPPLER_FFT_SIZE");
|
||||
|
||||
// G13.5: Setting chirps_per_elev = 0 gets clamped to 32
|
||||
bfm_send_cmd(8'h15, 8'h00, 16'd0);
|
||||
check(dut.host_chirps_per_elev == 6'd32,
|
||||
"G13.5: chirps_per_elev=0 clamped to 32");
|
||||
|
||||
// G13.6: Value < 32 is accepted but flagged as mismatch
|
||||
bfm_send_cmd(8'h15, 8'h00, 16'd16);
|
||||
check(dut.host_chirps_per_elev == 6'd16,
|
||||
"G13.6: chirps_per_elev=16 accepted (not clamped)");
|
||||
check(dut.chirps_mismatch_error == 1'b1,
|
||||
"G13.7: Mismatch error set when chirps<DOPPLER_FFT_SIZE");
|
||||
|
||||
// G13.8: Restore to 32, verify error clears
|
||||
bfm_send_cmd(8'h15, 8'h00, 16'd32);
|
||||
check(dut.chirps_mismatch_error == 1'b0,
|
||||
"G13.8: Mismatch error clears when restored to 32");
|
||||
|
||||
$display("");
|
||||
|
||||
// ================================================================
|
||||
// FINAL SUMMARY
|
||||
// ================================================================
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
`timescale 1ns / 1ps
|
||||
|
||||
/**
|
||||
* tb_threshold_detector.v
|
||||
*
|
||||
* Unit test for the threshold detection logic in radar_system_top.v.
|
||||
* Tests the two bug fixes applied in Build 22:
|
||||
*
|
||||
* 1. One-cycle-lag fix: magnitude is now computed combinationally,
|
||||
* so the comparison uses the current sample (not the previous).
|
||||
* 2. Sticky detection fix: rx_detect_flag clears every cycle,
|
||||
* only asserted on actual detections.
|
||||
*
|
||||
* Also tests:
|
||||
* 3. Threshold is host-configurable via opcode 0x03
|
||||
* 4. Detection counter increments correctly
|
||||
* 5. Edge cases: exactly-at-threshold, zero input, max input
|
||||
*/
|
||||
|
||||
module tb_threshold_detector;
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Clock and reset
|
||||
// ---------------------------------------------------------------
|
||||
reg clk;
|
||||
reg reset_n;
|
||||
|
||||
initial clk = 0;
|
||||
always #5 clk = ~clk; // 100 MHz
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// DUT signals — mirrors detection logic from radar_system_top.v
|
||||
// We instantiate just the detection logic, not the full system.
|
||||
// ---------------------------------------------------------------
|
||||
reg signed [15:0] doppler_real;
|
||||
reg signed [15:0] doppler_imag;
|
||||
reg doppler_valid;
|
||||
reg [15:0] host_threshold;
|
||||
|
||||
// Combinational magnitude (same as production RTL)
|
||||
wire [15:0] abs_i = doppler_real[15] ? (~doppler_real + 16'd1) : doppler_real;
|
||||
wire [15:0] abs_q = doppler_imag[15] ? (~doppler_imag + 16'd1) : doppler_imag;
|
||||
wire [16:0] detect_mag = {1'b0, abs_i} + {1'b0, abs_q};
|
||||
|
||||
reg detect_flag;
|
||||
reg detect_valid;
|
||||
reg [7:0] detect_counter;
|
||||
|
||||
always @(posedge clk or negedge reset_n) begin
|
||||
if (!reset_n) begin
|
||||
detect_counter <= 8'd0;
|
||||
detect_flag <= 1'b0;
|
||||
detect_valid <= 1'b0;
|
||||
end else begin
|
||||
detect_flag <= 1'b0;
|
||||
detect_valid <= 1'b0;
|
||||
|
||||
if (doppler_valid) begin
|
||||
if (detect_mag > {1'b0, host_threshold}) begin
|
||||
detect_flag <= 1'b1;
|
||||
detect_valid <= 1'b1;
|
||||
detect_counter <= detect_counter + 1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Test infrastructure
|
||||
// ---------------------------------------------------------------
|
||||
integer pass_count = 0;
|
||||
integer fail_count = 0;
|
||||
|
||||
task check;
|
||||
input cond;
|
||||
input [1023:0] msg;
|
||||
begin
|
||||
if (cond) begin
|
||||
$display("[PASS] %0s", msg);
|
||||
pass_count = pass_count + 1;
|
||||
end else begin
|
||||
$display("[FAIL] %0s", msg);
|
||||
fail_count = fail_count + 1;
|
||||
end
|
||||
end
|
||||
endtask
|
||||
|
||||
task pulse_sample;
|
||||
input signed [15:0] i_val;
|
||||
input signed [15:0] q_val;
|
||||
begin
|
||||
// Setup inputs before clock edge
|
||||
@(negedge clk);
|
||||
doppler_real = i_val;
|
||||
doppler_imag = q_val;
|
||||
doppler_valid = 1'b1;
|
||||
// Rising edge: always block samples valid=1, schedules detect_flag<=result
|
||||
@(posedge clk);
|
||||
#1; // Let NBA resolve — detect_flag now reflects this cycle's decision
|
||||
// Deassert valid for next cycle
|
||||
@(negedge clk);
|
||||
doppler_valid = 1'b0;
|
||||
end
|
||||
endtask
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Test sequence
|
||||
// ---------------------------------------------------------------
|
||||
initial begin
|
||||
$display("=== Threshold Detector Unit Test ===");
|
||||
|
||||
// Init
|
||||
reset_n = 0;
|
||||
doppler_real = 0;
|
||||
doppler_imag = 0;
|
||||
doppler_valid = 0;
|
||||
host_threshold = 16'd1000;
|
||||
|
||||
repeat (4) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 1: No-lag detection — magnitude computed same cycle
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 1: Same-cycle magnitude (no lag) ---");
|
||||
|
||||
// Feed sample with |I|+|Q| = 600+500 = 1100 > threshold=1000
|
||||
pulse_sample(16'sd600, 16'sd500);
|
||||
check(detect_flag == 1'b1,
|
||||
"T1.1: Detection fires on first sample above threshold");
|
||||
check(detect_valid == 1'b1,
|
||||
"T1.2: detect_valid asserted with detect_flag");
|
||||
check(detect_counter == 8'd1,
|
||||
"T1.3: Counter incremented to 1");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 2: Sticky detection fix — flag clears on next valid=0 cycle
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 2: Detection clears on next cycle ---");
|
||||
|
||||
// pulse_sample left valid=0 on negedge. Wait for next posedge where
|
||||
// the always block runs with valid=0 and clears detect_flag.
|
||||
@(posedge clk);
|
||||
#1;
|
||||
check(detect_flag == 1'b0,
|
||||
"T2.1: detect_flag cleared after valid deasserted");
|
||||
check(detect_valid == 1'b0,
|
||||
"T2.2: detect_valid cleared after valid deasserted");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 3: Below-threshold sample should NOT detect
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 3: Below-threshold ---");
|
||||
|
||||
// |I|+|Q| = 300+200 = 500 < 1000
|
||||
pulse_sample(16'sd300, 16'sd200);
|
||||
check(detect_flag == 1'b0,
|
||||
"T3.1: No detection for below-threshold sample");
|
||||
check(detect_counter == 8'd1,
|
||||
"T3.2: Counter unchanged at 1");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 4: Exactly-at-threshold should NOT detect (> not >=)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 4: Exactly at threshold ---");
|
||||
|
||||
// |I|+|Q| = 600+400 = 1000 == threshold (not >)
|
||||
pulse_sample(16'sd600, 16'sd400);
|
||||
check(detect_flag == 1'b0,
|
||||
"T4.1: No detection at exact threshold (> not >=)");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 5: Negative inputs (absolute value should still work)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 5: Negative inputs ---");
|
||||
|
||||
// |-800| + |-300| = 1100 > 1000
|
||||
pulse_sample(-16'sd800, -16'sd300);
|
||||
check(detect_flag == 1'b1,
|
||||
"T5.1: Detection works with negative I and Q");
|
||||
check(detect_counter == 8'd2,
|
||||
"T5.2: Counter incremented to 2");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 6: Mixed positive/negative
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 6: Mixed sign inputs ---");
|
||||
|
||||
// |700| + |-400| = 1100 > 1000
|
||||
pulse_sample(16'sd700, -16'sd400);
|
||||
check(detect_flag == 1'b1,
|
||||
"T6.1: Detection with mixed-sign inputs");
|
||||
|
||||
// |-200| + |500| = 700 < 1000
|
||||
pulse_sample(-16'sd200, 16'sd500);
|
||||
check(detect_flag == 1'b0,
|
||||
"T6.2: No detection with mixed-sign below threshold");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 7: Consecutive above-threshold samples
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 7: Consecutive detections ---");
|
||||
|
||||
// Three consecutive above-threshold samples
|
||||
@(negedge clk);
|
||||
doppler_real = 16'sd2000;
|
||||
doppler_imag = 16'sd3000;
|
||||
doppler_valid = 1'b1;
|
||||
@(posedge clk);
|
||||
#1;
|
||||
check(detect_flag == 1'b1,
|
||||
"T7.1: First consecutive detection");
|
||||
|
||||
@(negedge clk);
|
||||
doppler_real = 16'sd1500;
|
||||
doppler_imag = 16'sd2000;
|
||||
// doppler_valid still high
|
||||
@(posedge clk);
|
||||
#1;
|
||||
check(detect_flag == 1'b1,
|
||||
"T7.2: Second consecutive detection");
|
||||
|
||||
@(negedge clk);
|
||||
doppler_real = 16'sd100;
|
||||
doppler_imag = 16'sd100;
|
||||
@(posedge clk);
|
||||
#1;
|
||||
check(detect_flag == 1'b0,
|
||||
"T7.3: Third sample below threshold - flag clears immediately");
|
||||
|
||||
@(negedge clk);
|
||||
doppler_valid = 1'b0;
|
||||
@(posedge clk);
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 8: Host-configurable threshold change
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 8: Threshold reconfiguration ---");
|
||||
|
||||
host_threshold = 16'd500; // Lower threshold
|
||||
|
||||
// |300|+|300| = 600 > 500 (was below old threshold of 1000)
|
||||
pulse_sample(16'sd300, 16'sd300);
|
||||
check(detect_flag == 1'b1,
|
||||
"T8.1: Detection after lowering threshold");
|
||||
|
||||
host_threshold = 16'd2000; // Raise threshold
|
||||
|
||||
// |300|+|300| = 600 < 2000
|
||||
pulse_sample(16'sd300, 16'sd300);
|
||||
check(detect_flag == 1'b0,
|
||||
"T8.2: No detection after raising threshold");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 9: Zero input
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 9: Zero input ---");
|
||||
|
||||
host_threshold = 16'd0; // Even zero threshold
|
||||
|
||||
// |0|+|0| = 0 — not > 0
|
||||
pulse_sample(16'sd0, 16'sd0);
|
||||
check(detect_flag == 1'b0,
|
||||
"T9.1: Zero magnitude does not trigger even with threshold=0");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 10: Maximum input (near overflow)
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 10: Maximum input ---");
|
||||
|
||||
host_threshold = 16'hFFFE; // Near-max threshold = 65534
|
||||
|
||||
// |32767| + |32767| = 65534 — not > 65534
|
||||
pulse_sample(16'sd32767, 16'sd32767);
|
||||
check(detect_flag == 1'b0,
|
||||
"T10.1: Max positive at max threshold — equal, no detect");
|
||||
|
||||
host_threshold = 16'hFFFD; // 65533
|
||||
pulse_sample(16'sd32767, 16'sd32767);
|
||||
check(detect_flag == 1'b1,
|
||||
"T10.2: Max positive at threshold-1 — detects");
|
||||
|
||||
// Most-negative: -32768
|
||||
pulse_sample(-16'sd32768, -16'sd32768);
|
||||
// |-32768| = 32768 (17-bit), so |I|+|Q| = 65536 > 65533
|
||||
check(detect_flag == 1'b1,
|
||||
"T10.3: Most-negative input detects (|I|+|Q|=65536)");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// TEST 11: Detection counter wraps at 255
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("--- Test 11: Counter behavior ---");
|
||||
|
||||
// Reset to get fresh counter
|
||||
reset_n = 0;
|
||||
repeat (2) @(posedge clk);
|
||||
reset_n = 1;
|
||||
repeat (2) @(posedge clk);
|
||||
|
||||
host_threshold = 16'd100;
|
||||
check(detect_counter == 8'd0,
|
||||
"T11.1: Counter resets to 0");
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// SUMMARY
|
||||
// ---------------------------------------------------------------
|
||||
$display("");
|
||||
$display("=== Threshold Detector: %0d passed, %0d failed ===",
|
||||
pass_count, fail_count);
|
||||
|
||||
if (fail_count > 0)
|
||||
$display("[FAIL] Threshold detector test FAILED");
|
||||
else
|
||||
$display("[PASS] All threshold detector tests passed");
|
||||
|
||||
$finish;
|
||||
end
|
||||
|
||||
endmodule
|
||||
@@ -72,6 +72,7 @@ module tb_usb_data_interface;
|
||||
reg [15:0] status_short_chirp;
|
||||
reg [15:0] status_short_listen;
|
||||
reg [5:0] status_chirps_per_elev;
|
||||
reg [1:0] status_range_mode;
|
||||
|
||||
// ── Clock generators (asynchronous) ────────────────────────
|
||||
always #(CLK_PERIOD / 2) clk = ~clk;
|
||||
@@ -122,7 +123,8 @@ module tb_usb_data_interface;
|
||||
.status_guard (status_guard),
|
||||
.status_short_chirp (status_short_chirp),
|
||||
.status_short_listen (status_short_listen),
|
||||
.status_chirps_per_elev(status_chirps_per_elev)
|
||||
.status_chirps_per_elev(status_chirps_per_elev),
|
||||
.status_range_mode (status_range_mode)
|
||||
);
|
||||
|
||||
// ── Test bookkeeping ───────────────────────────────────────
|
||||
@@ -178,6 +180,7 @@ module tb_usb_data_interface;
|
||||
status_short_chirp = 16'd50;
|
||||
status_short_listen = 16'd17450;
|
||||
status_chirps_per_elev = 6'd32;
|
||||
status_range_mode = 2'b00;
|
||||
repeat (6) @(posedge ft601_clk_in);
|
||||
reset_n = 1;
|
||||
// Wait enough cycles for stream_control CDC to propagate
|
||||
@@ -881,6 +884,7 @@ module tb_usb_data_interface;
|
||||
status_short_chirp = 16'd50;
|
||||
status_short_listen = 16'd17450;
|
||||
status_chirps_per_elev = 6'd32;
|
||||
status_range_mode = 2'b10; // Long-range for status test
|
||||
|
||||
// Pulse status_request (1 cycle in clk domain — toggles status_req_toggle_100m)
|
||||
@(posedge clk);
|
||||
@@ -937,8 +941,8 @@ module tb_usb_data_interface;
|
||||
"Status readback: word 2 = {guard, short_chirp}");
|
||||
check(uut.status_words[3] === {16'd17450, 10'd0, 6'd32},
|
||||
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
|
||||
check(uut.status_words[4] === 32'h0000_0000,
|
||||
"Status readback: word 4 = placeholder 0x00000000");
|
||||
check(uut.status_words[4] === {30'd0, 2'b10},
|
||||
"Status readback: word 4 = range_mode=2'b10");
|
||||
|
||||
// ════════════════════════════════════════════════════════
|
||||
// TEST GROUP 17: New Chirp Timing Opcodes (Gap 2)
|
||||
|
||||
@@ -71,7 +71,8 @@ module usb_data_interface (
|
||||
input wire [15:0] status_guard, // Current guard cycles
|
||||
input wire [15:0] status_short_chirp, // Current short chirp cycles
|
||||
input wire [15:0] status_short_listen, // Current short listen cycles
|
||||
input wire [5:0] status_chirps_per_elev // Current chirps per elevation
|
||||
input wire [5:0] status_chirps_per_elev, // Current chirps per elevation
|
||||
input wire [1:0] status_range_mode // Fix 7: Current range mode (0x20)
|
||||
);
|
||||
|
||||
// USB packet structure (same as before)
|
||||
@@ -262,8 +263,8 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
||||
status_words[2] <= {status_guard, status_short_chirp};
|
||||
// Word 3: {short_listen_cycles[15:0], chirps_per_elev[5:0], 10'b0}
|
||||
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||
// Word 4: {system_status placeholder — 32'h00000000}
|
||||
status_words[4] <= 32'h0000_0000;
|
||||
// Word 4: Fix 7 — range_mode in bits [1:0], rest reserved
|
||||
status_words[4] <= {30'd0, status_range_mode};
|
||||
end
|
||||
|
||||
// Delayed version of sync[1] for edge detection
|
||||
|
||||
Reference in New Issue
Block a user