feat: hybrid AGC (FPGA phases 1-3 + GUI phase 6) with timing fix
FPGA: - rx_gain_control.v rewritten: per-frame peak/saturation tracking, auto-shift AGC with attack/decay/holdoff, signed gain -7 to +7 - New registers 0x28-0x2C (agc_enable/target/attack/decay/holdoff) - status_words[4] carries AGC metrics (gain, peak, sat_count, enable) - DIG_5 GPIO outputs saturation flag for STM32 outer loop - Both USB interfaces (FT601 + FT2232H) updated with AGC status ports Timing fix (WNS +0.001ns -> +0.045ns, 45x improvement): - CIC max_fanout 4->16 on valid pipeline registers - +200ps setup uncertainty on 400MHz domain - ExtraNetDelay_high placement + AggressiveExplore routing GUI: - AGC opcodes + status parsing in radar_protocol.py - AGC control groups in both tkinter and V7 PyQt dashboards - 11 new AGC tests (103/103 GUI tests pass) Cross-layer: - AGC opcodes/defaults/status assertions added (29/29 pass) - contract_parser.py: fixed comment stripping in concat parser All tests green: 25 FPGA + 103 GUI + 29 cross-layer = 157 pass
This commit is contained in:
@@ -66,13 +66,13 @@ reg signed [COMB_WIDTH-1:0] comb_delay [0:STAGES-1][0:COMB_DELAY-1];
|
|||||||
// Pipeline valid for comb stages 1-4: delayed by 1 cycle vs comb_pipe to
|
// Pipeline valid for comb stages 1-4: delayed by 1 cycle vs comb_pipe to
|
||||||
// account for CREG+AREG+BREG pipeline inside comb_0_dsp (explicit DSP48E1).
|
// account for CREG+AREG+BREG pipeline inside comb_0_dsp (explicit DSP48E1).
|
||||||
// Comb[0] result appears 1 cycle after data_valid_comb_pipe.
|
// Comb[0] result appears 1 cycle after data_valid_comb_pipe.
|
||||||
(* keep = "true", max_fanout = 4 *) reg data_valid_comb_0_out;
|
(* keep = "true", max_fanout = 16 *) reg data_valid_comb_0_out;
|
||||||
|
|
||||||
// Enhanced control and monitoring
|
// Enhanced control and monitoring
|
||||||
reg [1:0] decimation_counter;
|
reg [1:0] decimation_counter;
|
||||||
(* keep = "true", max_fanout = 4 *) reg data_valid_delayed;
|
(* keep = "true", max_fanout = 16 *) reg data_valid_delayed;
|
||||||
(* keep = "true", max_fanout = 4 *) reg data_valid_comb;
|
(* keep = "true", max_fanout = 16 *) reg data_valid_comb;
|
||||||
(* keep = "true", max_fanout = 4 *) reg data_valid_comb_pipe;
|
(* keep = "true", max_fanout = 16 *) reg data_valid_comb_pipe;
|
||||||
reg [7:0] output_counter;
|
reg [7:0] output_counter;
|
||||||
reg [ACC_WIDTH-1:0] max_integrator_value;
|
reg [ACC_WIDTH-1:0] max_integrator_value;
|
||||||
reg overflow_detected;
|
reg overflow_detected;
|
||||||
|
|||||||
@@ -83,3 +83,12 @@ set_false_path -through [get_pins rx_inst/adc/mmcm_inst/mmcm_adc_400m/LOCKED]
|
|||||||
# Waiving hold on these 8 paths (adc_d_p[0..7] → IDDR) is standard practice
|
# Waiving hold on these 8 paths (adc_d_p[0..7] → IDDR) is standard practice
|
||||||
# for source-synchronous LVDS ADC interfaces using BUFIO capture.
|
# for source-synchronous LVDS ADC interfaces using BUFIO capture.
|
||||||
set_false_path -hold -from [get_ports {adc_d_p[*]}] -to [get_clocks adc_dco_p]
|
set_false_path -hold -from [get_ports {adc_d_p[*]}] -to [get_clocks adc_dco_p]
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Timing margin for 400 MHz CIC critical path
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# The CIC decimator at 400 MHz has near-zero margin (WNS = +0.001 ns in
|
||||||
|
# Build 26). Adding 200 ps of extra setup uncertainty forces Vivado to
|
||||||
|
# leave comfortable margin for temperature/voltage/aging variation.
|
||||||
|
# This is additive to the existing jitter-based uncertainty (~53 ps).
|
||||||
|
set_clock_uncertainty -setup -add 0.200 [get_clocks clk_mmcm_out0]
|
||||||
|
|||||||
@@ -222,8 +222,16 @@ set_property IOSTANDARD LVCMOS33 [get_ports {stm32_new_*}]
|
|||||||
set_property IOSTANDARD LVCMOS33 [get_ports {stm32_mixers_enable}]
|
set_property IOSTANDARD LVCMOS33 [get_ports {stm32_mixers_enable}]
|
||||||
# reset_n is DIG_4 (PD12) — constrained above in the RESET section
|
# reset_n is DIG_4 (PD12) — constrained above in the RESET section
|
||||||
|
|
||||||
# DIG_5 = H11, DIG_6 = G12, DIG_7 = H12 — available for FPGA→STM32 status
|
# DIG_5 = H11, DIG_6 = G12, DIG_7 = H12 — FPGA→STM32 status outputs
|
||||||
# Currently unused in RTL. Could be connected to status outputs if needed.
|
# DIG_5: AGC saturation flag (PD13 on STM32)
|
||||||
|
# DIG_6: reserved (PD14)
|
||||||
|
# DIG_7: reserved (PD15)
|
||||||
|
set_property PACKAGE_PIN H11 [get_ports {gpio_dig5}]
|
||||||
|
set_property PACKAGE_PIN G12 [get_ports {gpio_dig6}]
|
||||||
|
set_property PACKAGE_PIN H12 [get_ports {gpio_dig7}]
|
||||||
|
set_property IOSTANDARD LVCMOS33 [get_ports {gpio_dig*}]
|
||||||
|
set_property DRIVE 8 [get_ports {gpio_dig*}]
|
||||||
|
set_property SLEW SLOW [get_ports {gpio_dig*}]
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# ADC INTERFACE (LVDS — Bank 14, VCCO=3.3V)
|
# ADC INTERFACE (LVDS — Bank 14, VCCO=3.3V)
|
||||||
|
|||||||
@@ -42,6 +42,13 @@ module radar_receiver_final (
|
|||||||
// [2:0]=shift amount: 0..7 bits. Default 0 = pass-through.
|
// [2:0]=shift amount: 0..7 bits. Default 0 = pass-through.
|
||||||
input wire [3:0] host_gain_shift,
|
input wire [3:0] host_gain_shift,
|
||||||
|
|
||||||
|
// AGC configuration (opcodes 0x28-0x2C, active only when agc_enable=1)
|
||||||
|
input wire host_agc_enable, // 0x28: 0=manual, 1=auto AGC
|
||||||
|
input wire [7:0] host_agc_target, // 0x29: target peak magnitude
|
||||||
|
input wire [3:0] host_agc_attack, // 0x2A: gain-down step on clipping
|
||||||
|
input wire [3:0] host_agc_decay, // 0x2B: gain-up step when weak
|
||||||
|
input wire [3:0] host_agc_holdoff, // 0x2C: frames before gain-up
|
||||||
|
|
||||||
// STM32 toggle signals for mode 00 (STM32-driven) pass-through.
|
// STM32 toggle signals for mode 00 (STM32-driven) pass-through.
|
||||||
// These are CDC-synchronized in radar_system_top.v / radar_transmitter.v
|
// These are CDC-synchronized in radar_system_top.v / radar_transmitter.v
|
||||||
// before reaching this module. In mode 00, the RX mode controller uses
|
// before reaching this module. In mode 00, the RX mode controller uses
|
||||||
@@ -60,7 +67,12 @@ module radar_receiver_final (
|
|||||||
// ADC raw data tap (clk_100m domain, post-DDC, for self-test / debug)
|
// ADC raw data tap (clk_100m domain, post-DDC, for self-test / debug)
|
||||||
output wire [15:0] dbg_adc_i, // DDC output I (16-bit signed, 100 MHz)
|
output wire [15:0] dbg_adc_i, // DDC output I (16-bit signed, 100 MHz)
|
||||||
output wire [15:0] dbg_adc_q, // DDC output Q (16-bit signed, 100 MHz)
|
output wire [15:0] dbg_adc_q, // DDC output Q (16-bit signed, 100 MHz)
|
||||||
output wire dbg_adc_valid // DDC output valid (100 MHz)
|
output wire dbg_adc_valid, // DDC output valid (100 MHz)
|
||||||
|
|
||||||
|
// AGC status outputs (for status readback / STM32 outer loop)
|
||||||
|
output wire [7:0] agc_saturation_count, // Per-frame clipped sample count
|
||||||
|
output wire [7:0] agc_peak_magnitude, // Per-frame peak (upper 8 bits)
|
||||||
|
output wire [3:0] agc_current_gain // Effective gain_shift encoding
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== INTERNAL SIGNALS ==========
|
// ========== INTERNAL SIGNALS ==========
|
||||||
@@ -86,7 +98,9 @@ wire adc_valid_sync;
|
|||||||
// Gain-controlled signals (between DDC output and matched filter)
|
// Gain-controlled signals (between DDC output and matched filter)
|
||||||
wire signed [15:0] gc_i, gc_q;
|
wire signed [15:0] gc_i, gc_q;
|
||||||
wire gc_valid;
|
wire gc_valid;
|
||||||
wire [7:0] gc_saturation_count; // Diagnostic: clipped sample counter
|
wire [7:0] gc_saturation_count; // Diagnostic: per-frame clipped sample counter
|
||||||
|
wire [7:0] gc_peak_magnitude; // Diagnostic: per-frame peak magnitude
|
||||||
|
wire [3:0] gc_current_gain; // Diagnostic: effective gain_shift
|
||||||
|
|
||||||
// Reference signals for the processing chain
|
// Reference signals for the processing chain
|
||||||
wire [15:0] long_chirp_real, long_chirp_imag;
|
wire [15:0] long_chirp_real, long_chirp_imag;
|
||||||
@@ -160,7 +174,7 @@ wire clk_400m;
|
|||||||
// the buffered 400MHz DCO clock via adc_dco_bufg, avoiding duplicate
|
// the buffered 400MHz DCO clock via adc_dco_bufg, avoiding duplicate
|
||||||
// IBUFDS instantiations on the same LVDS clock pair.
|
// IBUFDS instantiations on the same LVDS clock pair.
|
||||||
|
|
||||||
// 1. ADC + CDC + AGC
|
// 1. ADC + CDC + Digital Gain
|
||||||
|
|
||||||
// CMOS Output Interface (400MHz Domain)
|
// CMOS Output Interface (400MHz Domain)
|
||||||
wire [7:0] adc_data_cmos; // 8-bit ADC data (CMOS, from ad9484_interface_400m)
|
wire [7:0] adc_data_cmos; // 8-bit ADC data (CMOS, from ad9484_interface_400m)
|
||||||
@@ -222,9 +236,10 @@ ddc_input_interface ddc_if (
|
|||||||
.data_sync_error()
|
.data_sync_error()
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2b. Digital Gain Control (Fix 3)
|
// 2b. Digital Gain Control with AGC
|
||||||
// Host-configurable power-of-2 shift between DDC output and matched filter.
|
// Host-configurable power-of-2 shift between DDC output and matched filter.
|
||||||
// Default gain_shift=0 → pass-through (no behavioral change from baseline).
|
// Default gain_shift=0, agc_enable=0 → pass-through (no behavioral change).
|
||||||
|
// When agc_enable=1: auto-adjusts gain per frame based on peak/saturation.
|
||||||
rx_gain_control gain_ctrl (
|
rx_gain_control gain_ctrl (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
.reset_n(reset_n),
|
.reset_n(reset_n),
|
||||||
@@ -232,10 +247,21 @@ rx_gain_control gain_ctrl (
|
|||||||
.data_q_in(adc_q_scaled),
|
.data_q_in(adc_q_scaled),
|
||||||
.valid_in(adc_valid_sync),
|
.valid_in(adc_valid_sync),
|
||||||
.gain_shift(host_gain_shift),
|
.gain_shift(host_gain_shift),
|
||||||
|
// AGC configuration
|
||||||
|
.agc_enable(host_agc_enable),
|
||||||
|
.agc_target(host_agc_target),
|
||||||
|
.agc_attack(host_agc_attack),
|
||||||
|
.agc_decay(host_agc_decay),
|
||||||
|
.agc_holdoff(host_agc_holdoff),
|
||||||
|
// Frame boundary from Doppler processor
|
||||||
|
.frame_boundary(doppler_frame_done),
|
||||||
|
// Outputs
|
||||||
.data_i_out(gc_i),
|
.data_i_out(gc_i),
|
||||||
.data_q_out(gc_q),
|
.data_q_out(gc_q),
|
||||||
.valid_out(gc_valid),
|
.valid_out(gc_valid),
|
||||||
.saturation_count(gc_saturation_count)
|
.saturation_count(gc_saturation_count),
|
||||||
|
.peak_magnitude(gc_peak_magnitude),
|
||||||
|
.current_gain(gc_current_gain)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Dual Chirp Memory Loader
|
// 3. Dual Chirp Memory Loader
|
||||||
@@ -474,4 +500,9 @@ assign dbg_adc_i = adc_i_scaled;
|
|||||||
assign dbg_adc_q = adc_q_scaled;
|
assign dbg_adc_q = adc_q_scaled;
|
||||||
assign dbg_adc_valid = adc_valid_sync;
|
assign dbg_adc_valid = adc_valid_sync;
|
||||||
|
|
||||||
|
// ========== AGC STATUS OUTPUTS ==========
|
||||||
|
assign agc_saturation_count = gc_saturation_count;
|
||||||
|
assign agc_peak_magnitude = gc_peak_magnitude;
|
||||||
|
assign agc_current_gain = gc_current_gain;
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|||||||
@@ -125,7 +125,13 @@ module radar_system_top (
|
|||||||
output wire [5:0] dbg_range_bin,
|
output wire [5:0] dbg_range_bin,
|
||||||
|
|
||||||
// System status
|
// System status
|
||||||
output wire [3:0] system_status
|
output wire [3:0] system_status,
|
||||||
|
|
||||||
|
// FPGA→STM32 GPIO outputs (DIG_5..DIG_7 on 50T board)
|
||||||
|
// Used by STM32 outer AGC loop to read saturation state without USB polling.
|
||||||
|
output wire gpio_dig5, // DIG_5 (H11→PD13): AGC saturation flag (1=clipping detected)
|
||||||
|
output wire gpio_dig6, // DIG_6 (G12→PD14): reserved (tied low)
|
||||||
|
output wire gpio_dig7 // DIG_7 (H12→PD15): reserved (tied low)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -187,6 +193,11 @@ wire [15:0] rx_dbg_adc_i;
|
|||||||
wire [15:0] rx_dbg_adc_q;
|
wire [15:0] rx_dbg_adc_q;
|
||||||
wire rx_dbg_adc_valid;
|
wire rx_dbg_adc_valid;
|
||||||
|
|
||||||
|
// AGC status from receiver (for status readback and GPIO)
|
||||||
|
wire [7:0] rx_agc_saturation_count;
|
||||||
|
wire [7:0] rx_agc_peak_magnitude;
|
||||||
|
wire [3:0] rx_agc_current_gain;
|
||||||
|
|
||||||
// Data packing for USB
|
// Data packing for USB
|
||||||
wire [31:0] usb_range_profile;
|
wire [31:0] usb_range_profile;
|
||||||
wire usb_range_valid;
|
wire usb_range_valid;
|
||||||
@@ -259,6 +270,13 @@ reg host_cfar_enable; // Opcode 0x25: 1=CFAR, 0=simple threshold
|
|||||||
reg host_mti_enable; // Opcode 0x26: 1=MTI active, 0=pass-through
|
reg host_mti_enable; // Opcode 0x26: 1=MTI active, 0=pass-through
|
||||||
reg [2:0] host_dc_notch_width; // Opcode 0x27: DC notch ±width bins (0=off, 1..7)
|
reg [2:0] host_dc_notch_width; // Opcode 0x27: DC notch ±width bins (0=off, 1..7)
|
||||||
|
|
||||||
|
// AGC configuration registers (host-configurable via USB, opcodes 0x28-0x2C)
|
||||||
|
reg host_agc_enable; // Opcode 0x28: 0=manual gain, 1=auto AGC
|
||||||
|
reg [7:0] host_agc_target; // Opcode 0x29: target peak magnitude (default 200)
|
||||||
|
reg [3:0] host_agc_attack; // Opcode 0x2A: gain-down step on clipping (default 1)
|
||||||
|
reg [3:0] host_agc_decay; // Opcode 0x2B: gain-up step when weak (default 1)
|
||||||
|
reg [3:0] host_agc_holdoff; // Opcode 0x2C: frames to wait before gain-up (default 4)
|
||||||
|
|
||||||
// Board bring-up self-test registers (opcode 0x30 trigger, 0x31 readback)
|
// Board bring-up self-test registers (opcode 0x30 trigger, 0x31 readback)
|
||||||
reg host_self_test_trigger; // Opcode 0x30: self-clearing pulse
|
reg host_self_test_trigger; // Opcode 0x30: self-clearing pulse
|
||||||
wire self_test_busy;
|
wire self_test_busy;
|
||||||
@@ -518,6 +536,12 @@ radar_receiver_final rx_inst (
|
|||||||
.host_chirps_per_elev(host_chirps_per_elev),
|
.host_chirps_per_elev(host_chirps_per_elev),
|
||||||
// Fix 3: digital gain control
|
// Fix 3: digital gain control
|
||||||
.host_gain_shift(host_gain_shift),
|
.host_gain_shift(host_gain_shift),
|
||||||
|
// AGC configuration (opcodes 0x28-0x2C)
|
||||||
|
.host_agc_enable(host_agc_enable),
|
||||||
|
.host_agc_target(host_agc_target),
|
||||||
|
.host_agc_attack(host_agc_attack),
|
||||||
|
.host_agc_decay(host_agc_decay),
|
||||||
|
.host_agc_holdoff(host_agc_holdoff),
|
||||||
// STM32 toggle signals for RX mode controller (mode 00 pass-through).
|
// STM32 toggle signals for RX mode controller (mode 00 pass-through).
|
||||||
// These are the raw GPIO inputs — the RX mode controller's edge detectors
|
// These are the raw GPIO inputs — the RX mode controller's edge detectors
|
||||||
// (inside radar_mode_controller) handle debouncing/edge detection.
|
// (inside radar_mode_controller) handle debouncing/edge detection.
|
||||||
@@ -532,7 +556,11 @@ radar_receiver_final rx_inst (
|
|||||||
// ADC debug tap (for self-test / bring-up)
|
// ADC debug tap (for self-test / bring-up)
|
||||||
.dbg_adc_i(rx_dbg_adc_i),
|
.dbg_adc_i(rx_dbg_adc_i),
|
||||||
.dbg_adc_q(rx_dbg_adc_q),
|
.dbg_adc_q(rx_dbg_adc_q),
|
||||||
.dbg_adc_valid(rx_dbg_adc_valid)
|
.dbg_adc_valid(rx_dbg_adc_valid),
|
||||||
|
// AGC status outputs
|
||||||
|
.agc_saturation_count(rx_agc_saturation_count),
|
||||||
|
.agc_peak_magnitude(rx_agc_peak_magnitude),
|
||||||
|
.agc_current_gain(rx_agc_current_gain)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -744,7 +772,13 @@ if (USB_MODE == 0) begin : gen_ft601
|
|||||||
// Self-test status readback
|
// Self-test status readback
|
||||||
.status_self_test_flags(self_test_flags_latched),
|
.status_self_test_flags(self_test_flags_latched),
|
||||||
.status_self_test_detail(self_test_detail_latched),
|
.status_self_test_detail(self_test_detail_latched),
|
||||||
.status_self_test_busy(self_test_busy)
|
.status_self_test_busy(self_test_busy),
|
||||||
|
|
||||||
|
// AGC status readback
|
||||||
|
.status_agc_current_gain(rx_agc_current_gain),
|
||||||
|
.status_agc_peak_magnitude(rx_agc_peak_magnitude),
|
||||||
|
.status_agc_saturation_count(rx_agc_saturation_count),
|
||||||
|
.status_agc_enable(host_agc_enable)
|
||||||
);
|
);
|
||||||
|
|
||||||
// FT2232H ports unused in FT601 mode — tie off
|
// FT2232H ports unused in FT601 mode — tie off
|
||||||
@@ -805,7 +839,13 @@ end else begin : gen_ft2232h
|
|||||||
// Self-test status readback
|
// Self-test status readback
|
||||||
.status_self_test_flags(self_test_flags_latched),
|
.status_self_test_flags(self_test_flags_latched),
|
||||||
.status_self_test_detail(self_test_detail_latched),
|
.status_self_test_detail(self_test_detail_latched),
|
||||||
.status_self_test_busy(self_test_busy)
|
.status_self_test_busy(self_test_busy),
|
||||||
|
|
||||||
|
// AGC status readback
|
||||||
|
.status_agc_current_gain(rx_agc_current_gain),
|
||||||
|
.status_agc_peak_magnitude(rx_agc_peak_magnitude),
|
||||||
|
.status_agc_saturation_count(rx_agc_saturation_count),
|
||||||
|
.status_agc_enable(host_agc_enable)
|
||||||
);
|
);
|
||||||
|
|
||||||
// FT601 ports unused in FT2232H mode — tie off
|
// FT601 ports unused in FT2232H mode — tie off
|
||||||
@@ -892,6 +932,12 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
|||||||
// Ground clutter removal defaults (disabled — backward-compatible)
|
// Ground clutter removal defaults (disabled — backward-compatible)
|
||||||
host_mti_enable <= 1'b0; // MTI off
|
host_mti_enable <= 1'b0; // MTI off
|
||||||
host_dc_notch_width <= 3'd0; // DC notch off
|
host_dc_notch_width <= 3'd0; // DC notch off
|
||||||
|
// AGC defaults (disabled — backward-compatible with manual gain)
|
||||||
|
host_agc_enable <= 1'b0; // AGC off (manual gain)
|
||||||
|
host_agc_target <= 8'd200; // Target peak magnitude
|
||||||
|
host_agc_attack <= 4'd1; // 1-step gain-down on clipping
|
||||||
|
host_agc_decay <= 4'd1; // 1-step gain-up when weak
|
||||||
|
host_agc_holdoff <= 4'd4; // 4 frames before gain-up
|
||||||
// Self-test defaults
|
// Self-test defaults
|
||||||
host_self_test_trigger <= 1'b0; // Self-test idle
|
host_self_test_trigger <= 1'b0; // Self-test idle
|
||||||
end else begin
|
end else begin
|
||||||
@@ -936,6 +982,12 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
|||||||
// Ground clutter removal opcodes
|
// Ground clutter removal opcodes
|
||||||
8'h26: host_mti_enable <= usb_cmd_value[0];
|
8'h26: host_mti_enable <= usb_cmd_value[0];
|
||||||
8'h27: host_dc_notch_width <= usb_cmd_value[2:0];
|
8'h27: host_dc_notch_width <= usb_cmd_value[2:0];
|
||||||
|
// AGC configuration opcodes
|
||||||
|
8'h28: host_agc_enable <= usb_cmd_value[0];
|
||||||
|
8'h29: host_agc_target <= usb_cmd_value[7:0];
|
||||||
|
8'h2A: host_agc_attack <= usb_cmd_value[3:0];
|
||||||
|
8'h2B: host_agc_decay <= usb_cmd_value[3:0];
|
||||||
|
8'h2C: host_agc_holdoff <= usb_cmd_value[3:0];
|
||||||
// Board bring-up self-test opcodes
|
// Board bring-up self-test opcodes
|
||||||
8'h30: host_self_test_trigger <= 1'b1; // Trigger self-test
|
8'h30: host_self_test_trigger <= 1'b1; // Trigger self-test
|
||||||
8'h31: host_status_request <= 1'b1; // Self-test readback (status alias)
|
8'h31: host_status_request <= 1'b1; // Self-test readback (status alias)
|
||||||
@@ -978,6 +1030,16 @@ end
|
|||||||
|
|
||||||
assign system_status = status_reg;
|
assign system_status = status_reg;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FPGA→STM32 GPIO OUTPUTS (DIG_5, DIG_6, DIG_7)
|
||||||
|
// ============================================================================
|
||||||
|
// DIG_5: AGC saturation flag — high when per-frame saturation_count > 0.
|
||||||
|
// STM32 reads PD13 to detect clipping and adjust ADAR1000 VGA gain.
|
||||||
|
// DIG_6, DIG_7: Reserved (tied low for future use).
|
||||||
|
assign gpio_dig5 = (rx_agc_saturation_count != 8'd0);
|
||||||
|
assign gpio_dig6 = 1'b0;
|
||||||
|
assign gpio_dig7 = 1'b0;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// DEBUG AND VERIFICATION
|
// DEBUG AND VERIFICATION
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -76,7 +76,12 @@ module radar_system_top_50t (
|
|||||||
output wire ft_rd_n, // Read strobe (active low)
|
output wire ft_rd_n, // Read strobe (active low)
|
||||||
output wire ft_wr_n, // Write strobe (active low)
|
output wire ft_wr_n, // Write strobe (active low)
|
||||||
output wire ft_oe_n, // Output enable / bus direction
|
output wire ft_oe_n, // Output enable / bus direction
|
||||||
output wire ft_siwu // Send Immediate / WakeUp
|
output wire ft_siwu, // Send Immediate / WakeUp
|
||||||
|
|
||||||
|
// ===== FPGA→STM32 GPIO (Bank 15: 3.3V) =====
|
||||||
|
output wire gpio_dig5, // DIG_5 (H11→PD13): AGC saturation flag
|
||||||
|
output wire gpio_dig6, // DIG_6 (G12→PD14): reserved
|
||||||
|
output wire gpio_dig7 // DIG_7 (H12→PD15): reserved
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
|
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
|
||||||
@@ -207,7 +212,12 @@ module radar_system_top_50t (
|
|||||||
.dbg_doppler_valid (dbg_doppler_valid_nc),
|
.dbg_doppler_valid (dbg_doppler_valid_nc),
|
||||||
.dbg_doppler_bin (dbg_doppler_bin_nc),
|
.dbg_doppler_bin (dbg_doppler_bin_nc),
|
||||||
.dbg_range_bin (dbg_range_bin_nc),
|
.dbg_range_bin (dbg_range_bin_nc),
|
||||||
.system_status (system_status_nc)
|
.system_status (system_status_nc),
|
||||||
|
|
||||||
|
// ----- FPGA→STM32 GPIO (DIG_5..DIG_7) -----
|
||||||
|
.gpio_dig5 (gpio_dig5),
|
||||||
|
.gpio_dig6 (gpio_dig6),
|
||||||
|
.gpio_dig7 (gpio_dig7)
|
||||||
);
|
);
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|||||||
@@ -3,19 +3,32 @@
|
|||||||
/**
|
/**
|
||||||
* rx_gain_control.v
|
* rx_gain_control.v
|
||||||
*
|
*
|
||||||
* Host-configurable digital gain control for the receive path.
|
* Digital gain control with optional per-frame automatic gain control (AGC)
|
||||||
* Placed between DDC output (ddc_input_interface) and matched filter input.
|
* for the receive path. Placed between DDC output and matched filter input.
|
||||||
*
|
*
|
||||||
* Features:
|
* Manual mode (agc_enable=0):
|
||||||
* - Bidirectional power-of-2 gain shift (arithmetic shift)
|
* - Uses host_gain_shift directly (backward-compatible, no behavioral change)
|
||||||
* - gain_shift[3] = direction: 0 = left shift (amplify), 1 = right shift (attenuate)
|
* - gain_shift[3] = direction: 0 = left shift (amplify), 1 = right shift (attenuate)
|
||||||
* - gain_shift[2:0] = amount: 0..7 bits
|
* - gain_shift[2:0] = amount: 0..7 bits
|
||||||
* - Symmetric saturation to ±32767 on overflow (left shift only)
|
* - Symmetric saturation to ±32767 on overflow
|
||||||
* - 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:
|
* AGC mode (agc_enable=1):
|
||||||
|
* - Per-frame automatic gain adjustment based on peak/saturation metrics
|
||||||
|
* - Internal signed gain: -7 (max attenuation) to +7 (max amplification)
|
||||||
|
* - On frame_boundary:
|
||||||
|
* * If saturation detected: gain -= agc_attack (fast, immediate)
|
||||||
|
* * Else if peak < target after holdoff frames: gain += agc_decay (slow)
|
||||||
|
* * Else: hold current gain
|
||||||
|
* - host_gain_shift serves as initial gain when AGC first enabled
|
||||||
|
*
|
||||||
|
* Status outputs (for readback via status_words):
|
||||||
|
* - current_gain[3:0]: effective gain_shift encoding (manual or AGC)
|
||||||
|
* - peak_magnitude[7:0]: per-frame peak |sample| (upper 8 bits of 15-bit value)
|
||||||
|
* - saturation_count[7:0]: per-frame clipped sample count (capped at 255)
|
||||||
|
*
|
||||||
|
* Timing: 1-cycle data latency, valid-in/valid-out pipeline.
|
||||||
|
*
|
||||||
|
* Insertion point in radar_receiver_final.v:
|
||||||
* ddc_input_interface → rx_gain_control → matched_filter_multi_segment
|
* ddc_input_interface → rx_gain_control → matched_filter_multi_segment
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -28,27 +41,70 @@ module rx_gain_control (
|
|||||||
input wire signed [15:0] data_q_in,
|
input wire signed [15:0] data_q_in,
|
||||||
input wire valid_in,
|
input wire valid_in,
|
||||||
|
|
||||||
// Gain configuration (from host via USB command)
|
// Host gain configuration (from USB command opcode 0x16)
|
||||||
// [3] = direction: 0=amplify (left shift), 1=attenuate (right shift)
|
// [3]=direction: 0=amplify (left shift), 1=attenuate (right shift)
|
||||||
// [2:0] = shift amount: 0..7 bits
|
// [2:0]=shift amount: 0..7 bits. Default 0x00 = pass-through.
|
||||||
|
// In AGC mode: serves as initial gain on AGC enable transition.
|
||||||
input wire [3:0] gain_shift,
|
input wire [3:0] gain_shift,
|
||||||
|
|
||||||
|
// AGC configuration inputs (from host via USB, opcodes 0x28-0x2C)
|
||||||
|
input wire agc_enable, // 0x28: 0=manual gain, 1=auto AGC
|
||||||
|
input wire [7:0] agc_target, // 0x29: target peak magnitude (unsigned, default 200)
|
||||||
|
input wire [3:0] agc_attack, // 0x2A: attenuation step on clipping (default 1)
|
||||||
|
input wire [3:0] agc_decay, // 0x2B: amplification step when weak (default 1)
|
||||||
|
input wire [3:0] agc_holdoff, // 0x2C: frames to wait before gain-up (default 4)
|
||||||
|
|
||||||
|
// Frame boundary pulse (1 clk cycle, from Doppler frame_complete)
|
||||||
|
input wire frame_boundary,
|
||||||
|
|
||||||
// Data output (to matched filter)
|
// Data output (to matched filter)
|
||||||
output reg signed [15:0] data_i_out,
|
output reg signed [15:0] data_i_out,
|
||||||
output reg signed [15:0] data_q_out,
|
output reg signed [15:0] data_q_out,
|
||||||
output reg valid_out,
|
output reg valid_out,
|
||||||
|
|
||||||
// Diagnostics
|
// Diagnostics / status readback
|
||||||
output reg [7:0] saturation_count // Number of clipped samples (wraps at 255)
|
output reg [7:0] saturation_count, // Per-frame clipped sample count (capped at 255)
|
||||||
|
output reg [7:0] peak_magnitude, // Per-frame peak |sample| (upper 8 bits of 15-bit)
|
||||||
|
output reg [3:0] current_gain // Current effective gain_shift (for status readback)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Decompose gain_shift
|
// =========================================================================
|
||||||
wire shift_right = gain_shift[3];
|
// INTERNAL AGC STATE
|
||||||
wire [2:0] shift_amt = gain_shift[2:0];
|
// =========================================================================
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// Signed internal gain: -7 (max attenuation) to +7 (max amplification)
|
||||||
// Combinational shift + saturation
|
// Stored as 4-bit signed (range -8..+7, clamped to -7..+7)
|
||||||
// -------------------------------------------------------------------------
|
reg signed [3:0] agc_gain;
|
||||||
|
|
||||||
|
// Holdoff counter: counts frames without saturation before allowing gain-up
|
||||||
|
reg [3:0] holdoff_counter;
|
||||||
|
|
||||||
|
// Per-frame accumulators (running, reset on frame_boundary)
|
||||||
|
reg [7:0] frame_sat_count; // Clipped samples this frame
|
||||||
|
reg [14:0] frame_peak; // Peak |sample| this frame (15-bit unsigned)
|
||||||
|
|
||||||
|
// Previous AGC enable state (for detecting 0→1 transition)
|
||||||
|
reg agc_enable_prev;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// EFFECTIVE GAIN SELECTION
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
// Convert between signed internal gain and the gain_shift[3:0] encoding.
|
||||||
|
// gain_shift[3]=0, [2:0]=N → amplify by N bits (internal gain = +N)
|
||||||
|
// gain_shift[3]=1, [2:0]=N → attenuate by N bits (internal gain = -N)
|
||||||
|
|
||||||
|
// Effective gain_shift used for the actual shift operation
|
||||||
|
wire [3:0] effective_gain;
|
||||||
|
assign effective_gain = agc_enable ? current_gain : gain_shift;
|
||||||
|
|
||||||
|
// Decompose effective gain for shift logic
|
||||||
|
wire shift_right = effective_gain[3];
|
||||||
|
wire [2:0] shift_amt = effective_gain[2:0];
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// COMBINATIONAL SHIFT + SATURATION
|
||||||
|
// =========================================================================
|
||||||
// Use wider intermediates to detect overflow on left shift.
|
// Use wider intermediates to detect overflow on left shift.
|
||||||
// 24 bits is enough: 16 + 7 shift = 23 significant bits max.
|
// 24 bits is enough: 16 + 7 shift = 23 significant bits max.
|
||||||
|
|
||||||
@@ -69,26 +125,138 @@ wire signed [15:0] sat_i = overflow_i ? (shifted_i[23] ? -16'sd32768 : 16'sd3276
|
|||||||
wire signed [15:0] sat_q = overflow_q ? (shifted_q[23] ? -16'sd32768 : 16'sd32767)
|
wire signed [15:0] sat_q = overflow_q ? (shifted_q[23] ? -16'sd32768 : 16'sd32767)
|
||||||
: shifted_q[15:0];
|
: shifted_q[15:0];
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// =========================================================================
|
||||||
// Registered output stage (1-cycle latency)
|
// PEAK MAGNITUDE TRACKING (combinational)
|
||||||
// -------------------------------------------------------------------------
|
// =========================================================================
|
||||||
|
// Absolute value of signed 16-bit: flip sign bit if negative.
|
||||||
|
// Result is 15-bit unsigned [0, 32767]. (We ignore -32768 → 32767 edge case.)
|
||||||
|
wire [14:0] abs_i = data_i_in[15] ? (~data_i_in[14:0] + 15'd1) : data_i_in[14:0];
|
||||||
|
wire [14:0] abs_q = data_q_in[15] ? (~data_q_in[14:0] + 15'd1) : data_q_in[14:0];
|
||||||
|
wire [14:0] max_iq = (abs_i > abs_q) ? abs_i : abs_q;
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// SIGNED GAIN ↔ GAIN_SHIFT ENCODING CONVERSION
|
||||||
|
// =========================================================================
|
||||||
|
// Convert signed agc_gain to gain_shift[3:0] encoding
|
||||||
|
function [3:0] signed_to_encoding;
|
||||||
|
input signed [3:0] g;
|
||||||
|
begin
|
||||||
|
if (g >= 0)
|
||||||
|
signed_to_encoding = {1'b0, g[2:0]}; // amplify
|
||||||
|
else
|
||||||
|
signed_to_encoding = {1'b1, (~g[2:0]) + 3'd1}; // attenuate: -g
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
// Convert gain_shift[3:0] encoding to signed gain
|
||||||
|
function signed [3:0] encoding_to_signed;
|
||||||
|
input [3:0] enc;
|
||||||
|
begin
|
||||||
|
if (enc[3] == 1'b0)
|
||||||
|
encoding_to_signed = {1'b0, enc[2:0]}; // +0..+7
|
||||||
|
else
|
||||||
|
encoding_to_signed = -$signed({1'b0, enc[2:0]}); // -1..-7
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// CLAMPING HELPER
|
||||||
|
// =========================================================================
|
||||||
|
// Clamp a wider signed value to [-7, +7]
|
||||||
|
function signed [3:0] clamp_gain;
|
||||||
|
input signed [4:0] val; // 5-bit to handle overflow from add
|
||||||
|
begin
|
||||||
|
if (val > 5'sd7)
|
||||||
|
clamp_gain = 4'sd7;
|
||||||
|
else if (val < -5'sd7)
|
||||||
|
clamp_gain = -4'sd7;
|
||||||
|
else
|
||||||
|
clamp_gain = val[3:0];
|
||||||
|
end
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// REGISTERED OUTPUT + AGC STATE MACHINE
|
||||||
|
// =========================================================================
|
||||||
always @(posedge clk or negedge reset_n) begin
|
always @(posedge clk or negedge reset_n) begin
|
||||||
if (!reset_n) begin
|
if (!reset_n) begin
|
||||||
|
// Data path
|
||||||
data_i_out <= 16'sd0;
|
data_i_out <= 16'sd0;
|
||||||
data_q_out <= 16'sd0;
|
data_q_out <= 16'sd0;
|
||||||
valid_out <= 1'b0;
|
valid_out <= 1'b0;
|
||||||
|
// Status outputs
|
||||||
saturation_count <= 8'd0;
|
saturation_count <= 8'd0;
|
||||||
|
peak_magnitude <= 8'd0;
|
||||||
|
current_gain <= 4'd0;
|
||||||
|
// AGC internal state
|
||||||
|
agc_gain <= 4'sd0;
|
||||||
|
holdoff_counter <= 4'd0;
|
||||||
|
frame_sat_count <= 8'd0;
|
||||||
|
frame_peak <= 15'd0;
|
||||||
|
agc_enable_prev <= 1'b0;
|
||||||
end else begin
|
end else begin
|
||||||
valid_out <= valid_in;
|
// Track AGC enable transitions
|
||||||
|
agc_enable_prev <= agc_enable;
|
||||||
|
|
||||||
|
// ---- Data pipeline (1-cycle latency) ----
|
||||||
|
valid_out <= valid_in;
|
||||||
if (valid_in) begin
|
if (valid_in) begin
|
||||||
data_i_out <= sat_i;
|
data_i_out <= sat_i;
|
||||||
data_q_out <= sat_q;
|
data_q_out <= sat_q;
|
||||||
|
|
||||||
// Count clipped samples (either channel clipping counts as 1)
|
// Per-frame saturation counting
|
||||||
if ((overflow_i || overflow_q) && (saturation_count != 8'hFF))
|
if ((overflow_i || overflow_q) && (frame_sat_count != 8'hFF))
|
||||||
saturation_count <= saturation_count + 8'd1;
|
frame_sat_count <= frame_sat_count + 8'd1;
|
||||||
|
|
||||||
|
// Per-frame peak tracking (pre-gain, measures input signal level)
|
||||||
|
if (max_iq > frame_peak)
|
||||||
|
frame_peak <= max_iq;
|
||||||
end
|
end
|
||||||
|
|
||||||
|
// ---- Frame boundary: AGC update + metric snapshot ----
|
||||||
|
if (frame_boundary) begin
|
||||||
|
// Snapshot per-frame metrics to output registers
|
||||||
|
saturation_count <= frame_sat_count;
|
||||||
|
peak_magnitude <= frame_peak[14:7]; // Upper 8 bits of 15-bit peak
|
||||||
|
|
||||||
|
// Reset per-frame accumulators for next frame
|
||||||
|
frame_sat_count <= 8'd0;
|
||||||
|
frame_peak <= 15'd0;
|
||||||
|
|
||||||
|
if (agc_enable) begin
|
||||||
|
// AGC auto-adjustment at frame boundary
|
||||||
|
if (frame_sat_count > 8'd0) begin
|
||||||
|
// Clipping detected: reduce gain immediately (attack)
|
||||||
|
agc_gain <= clamp_gain($signed({1'b0, agc_gain}) -
|
||||||
|
$signed({1'b0, agc_attack}));
|
||||||
|
holdoff_counter <= agc_holdoff; // Reset holdoff
|
||||||
|
end else if (frame_peak[14:7] < agc_target) begin
|
||||||
|
// Signal too weak: increase gain after holdoff expires
|
||||||
|
if (holdoff_counter == 4'd0) begin
|
||||||
|
agc_gain <= clamp_gain($signed({1'b0, agc_gain}) +
|
||||||
|
$signed({1'b0, agc_decay}));
|
||||||
|
end else begin
|
||||||
|
holdoff_counter <= holdoff_counter - 4'd1;
|
||||||
|
end
|
||||||
|
end else begin
|
||||||
|
// Signal in good range, no saturation: hold gain
|
||||||
|
// Reset holdoff so next weak frame has to wait again
|
||||||
|
holdoff_counter <= agc_holdoff;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- AGC enable transition: initialize from host gain ----
|
||||||
|
if (agc_enable && !agc_enable_prev) begin
|
||||||
|
agc_gain <= encoding_to_signed(gain_shift);
|
||||||
|
holdoff_counter <= agc_holdoff;
|
||||||
|
end
|
||||||
|
|
||||||
|
// ---- Update current_gain output ----
|
||||||
|
if (agc_enable)
|
||||||
|
current_gain <= signed_to_encoding(agc_gain);
|
||||||
|
else
|
||||||
|
current_gain <= gain_shift;
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -120,9 +120,10 @@ set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {ft_clkout_IBUF}]
|
|||||||
|
|
||||||
# ---- Run implementation steps ----
|
# ---- Run implementation steps ----
|
||||||
opt_design -directive Explore
|
opt_design -directive Explore
|
||||||
place_design -directive Explore
|
place_design -directive ExtraNetDelay_high
|
||||||
|
phys_opt_design -directive AggressiveExplore
|
||||||
|
route_design -directive AggressiveExplore
|
||||||
phys_opt_design -directive AggressiveExplore
|
phys_opt_design -directive AggressiveExplore
|
||||||
route_design -directive Explore
|
|
||||||
phys_opt_design -directive AggressiveExplore
|
phys_opt_design -directive AggressiveExplore
|
||||||
|
|
||||||
set impl_elapsed [expr {[clock seconds] - $impl_start}]
|
set impl_elapsed [expr {[clock seconds] - $impl_start}]
|
||||||
|
|||||||
@@ -38,10 +38,20 @@ reg signed [15:0] data_q_in;
|
|||||||
reg valid_in;
|
reg valid_in;
|
||||||
reg [3:0] gain_shift;
|
reg [3:0] gain_shift;
|
||||||
|
|
||||||
|
// AGC configuration (default: AGC disabled — manual mode)
|
||||||
|
reg agc_enable;
|
||||||
|
reg [7:0] agc_target;
|
||||||
|
reg [3:0] agc_attack;
|
||||||
|
reg [3:0] agc_decay;
|
||||||
|
reg [3:0] agc_holdoff;
|
||||||
|
reg frame_boundary;
|
||||||
|
|
||||||
wire signed [15:0] data_i_out;
|
wire signed [15:0] data_i_out;
|
||||||
wire signed [15:0] data_q_out;
|
wire signed [15:0] data_q_out;
|
||||||
wire valid_out;
|
wire valid_out;
|
||||||
wire [7:0] saturation_count;
|
wire [7:0] saturation_count;
|
||||||
|
wire [7:0] peak_magnitude;
|
||||||
|
wire [3:0] current_gain;
|
||||||
|
|
||||||
rx_gain_control dut (
|
rx_gain_control dut (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
@@ -50,10 +60,18 @@ rx_gain_control dut (
|
|||||||
.data_q_in(data_q_in),
|
.data_q_in(data_q_in),
|
||||||
.valid_in(valid_in),
|
.valid_in(valid_in),
|
||||||
.gain_shift(gain_shift),
|
.gain_shift(gain_shift),
|
||||||
|
.agc_enable(agc_enable),
|
||||||
|
.agc_target(agc_target),
|
||||||
|
.agc_attack(agc_attack),
|
||||||
|
.agc_decay(agc_decay),
|
||||||
|
.agc_holdoff(agc_holdoff),
|
||||||
|
.frame_boundary(frame_boundary),
|
||||||
.data_i_out(data_i_out),
|
.data_i_out(data_i_out),
|
||||||
.data_q_out(data_q_out),
|
.data_q_out(data_q_out),
|
||||||
.valid_out(valid_out),
|
.valid_out(valid_out),
|
||||||
.saturation_count(saturation_count)
|
.saturation_count(saturation_count),
|
||||||
|
.peak_magnitude(peak_magnitude),
|
||||||
|
.current_gain(current_gain)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
@@ -105,6 +123,13 @@ initial begin
|
|||||||
data_q_in = 0;
|
data_q_in = 0;
|
||||||
valid_in = 0;
|
valid_in = 0;
|
||||||
gain_shift = 4'd0;
|
gain_shift = 4'd0;
|
||||||
|
// AGC disabled for backward-compatible tests (Tests 1-12)
|
||||||
|
agc_enable = 0;
|
||||||
|
agc_target = 8'd200;
|
||||||
|
agc_attack = 4'd1;
|
||||||
|
agc_decay = 4'd1;
|
||||||
|
agc_holdoff = 4'd4;
|
||||||
|
frame_boundary = 0;
|
||||||
|
|
||||||
repeat (4) @(posedge clk);
|
repeat (4) @(posedge clk);
|
||||||
reset_n = 1;
|
reset_n = 1;
|
||||||
@@ -152,6 +177,9 @@ initial begin
|
|||||||
"T3.1: I saturated to +32767");
|
"T3.1: I saturated to +32767");
|
||||||
check(data_q_out == -16'sd32768,
|
check(data_q_out == -16'sd32768,
|
||||||
"T3.2: Q saturated to -32768");
|
"T3.2: Q saturated to -32768");
|
||||||
|
// Pulse frame_boundary to snapshot the per-frame saturation count
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
check(saturation_count == 8'd1,
|
check(saturation_count == 8'd1,
|
||||||
"T3.3: Saturation counter = 1 (both channels clipped counts as 1)");
|
"T3.3: Saturation counter = 1 (both channels clipped counts as 1)");
|
||||||
|
|
||||||
@@ -173,6 +201,9 @@ initial begin
|
|||||||
"T4.1: I attenuated 4000>>2 = 1000");
|
"T4.1: I attenuated 4000>>2 = 1000");
|
||||||
check(data_q_out == -16'sd500,
|
check(data_q_out == -16'sd500,
|
||||||
"T4.2: Q attenuated -2000>>2 = -500");
|
"T4.2: Q attenuated -2000>>2 = -500");
|
||||||
|
// Pulse frame_boundary to snapshot (should be 0 — no clipping)
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
check(saturation_count == 8'd0,
|
check(saturation_count == 8'd0,
|
||||||
"T4.3: No saturation on right shift");
|
"T4.3: No saturation on right shift");
|
||||||
|
|
||||||
@@ -315,13 +346,18 @@ initial begin
|
|||||||
valid_in = 1'b0;
|
valid_in = 1'b0;
|
||||||
@(posedge clk); #1;
|
@(posedge clk); #1;
|
||||||
|
|
||||||
|
// Pulse frame_boundary to snapshot per-frame saturation count
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
check(saturation_count == 8'd255,
|
check(saturation_count == 8'd255,
|
||||||
"T11.1: Counter capped at 255 after 256 saturating samples");
|
"T11.1: Counter capped at 255 after 256 saturating samples");
|
||||||
|
|
||||||
// One more sample — should stay at 255
|
// One more sample + frame boundary — should still be capped at 1 (new frame)
|
||||||
send_sample(16'sd20000, 16'sd20000);
|
send_sample(16'sd20000, 16'sd20000);
|
||||||
check(saturation_count == 8'd255,
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
"T11.2: Counter stays at 255 (no wrap)");
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
check(saturation_count == 8'd1,
|
||||||
|
"T11.2: New frame counter = 1 (single sample)");
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// TEST 12: Reset clears everything
|
// TEST 12: Reset clears everything
|
||||||
@@ -329,6 +365,8 @@ initial begin
|
|||||||
$display("");
|
$display("");
|
||||||
$display("--- Test 12: Reset clears all ---");
|
$display("--- Test 12: Reset clears all ---");
|
||||||
|
|
||||||
|
gain_shift = 4'd0; // Reset gain_shift to 0 so current_gain reads 0
|
||||||
|
agc_enable = 0;
|
||||||
reset_n = 0;
|
reset_n = 0;
|
||||||
repeat (2) @(posedge clk);
|
repeat (2) @(posedge clk);
|
||||||
reset_n = 1;
|
reset_n = 1;
|
||||||
@@ -342,6 +380,170 @@ initial begin
|
|||||||
"T12.3: valid_out cleared on reset");
|
"T12.3: valid_out cleared on reset");
|
||||||
check(saturation_count == 8'd0,
|
check(saturation_count == 8'd0,
|
||||||
"T12.4: Saturation counter cleared on reset");
|
"T12.4: Saturation counter cleared on reset");
|
||||||
|
check(current_gain == 4'd0,
|
||||||
|
"T12.5: current_gain cleared on reset");
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// TEST 13: current_gain reflects gain_shift in manual mode
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
$display("");
|
||||||
|
$display("--- Test 13: current_gain tracks gain_shift (manual) ---");
|
||||||
|
|
||||||
|
gain_shift = 4'b0_011; // amplify x8
|
||||||
|
@(posedge clk); @(posedge clk); #1;
|
||||||
|
check(current_gain == 4'b0011,
|
||||||
|
"T13.1: current_gain = 0x3 (amplify x8)");
|
||||||
|
|
||||||
|
gain_shift = 4'b1_010; // attenuate /4
|
||||||
|
@(posedge clk); @(posedge clk); #1;
|
||||||
|
check(current_gain == 4'b1010,
|
||||||
|
"T13.2: current_gain = 0xA (attenuate /4)");
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// TEST 14: Peak magnitude tracking
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
$display("");
|
||||||
|
$display("--- Test 14: Peak magnitude tracking ---");
|
||||||
|
|
||||||
|
reset_n = 0;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
reset_n = 1;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
|
||||||
|
gain_shift = 4'b0_000; // pass-through
|
||||||
|
// Send samples with increasing magnitude
|
||||||
|
send_sample(16'sd100, 16'sd50);
|
||||||
|
send_sample(16'sd1000, 16'sd500);
|
||||||
|
send_sample(16'sd8000, 16'sd2000); // peak = 8000
|
||||||
|
send_sample(16'sd200, 16'sd100);
|
||||||
|
// Pulse frame_boundary to snapshot
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
// peak_magnitude = upper 8 bits of 15-bit peak (8000)
|
||||||
|
// 8000 = 0x1F40, 15-bit = 0x1F40, [14:7] = 0x3E = 62
|
||||||
|
check(peak_magnitude == 8'd62,
|
||||||
|
"T14.1: Peak magnitude = 62 (8000 >> 7)");
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// TEST 15: AGC auto gain-down on saturation
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
$display("");
|
||||||
|
$display("--- Test 15: AGC gain-down on saturation ---");
|
||||||
|
|
||||||
|
reset_n = 0;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
reset_n = 1;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
|
||||||
|
// Start with amplify x4 (gain_shift = 0x02), then enable AGC
|
||||||
|
gain_shift = 4'b0_010; // amplify x4, internal gain = +2
|
||||||
|
agc_enable = 0;
|
||||||
|
agc_attack = 4'd1;
|
||||||
|
agc_decay = 4'd1;
|
||||||
|
agc_holdoff = 4'd2;
|
||||||
|
agc_target = 8'd100;
|
||||||
|
@(posedge clk); @(posedge clk);
|
||||||
|
|
||||||
|
// Enable AGC — should initialize from gain_shift
|
||||||
|
agc_enable = 1;
|
||||||
|
@(posedge clk); @(posedge clk); @(posedge clk); #1;
|
||||||
|
check(current_gain == 4'b0010,
|
||||||
|
"T15.1: AGC initialized from gain_shift (amplify x4)");
|
||||||
|
|
||||||
|
// Send saturating samples (will clip at x4 gain)
|
||||||
|
send_sample(16'sd20000, 16'sd20000);
|
||||||
|
send_sample(16'sd20000, 16'sd20000);
|
||||||
|
|
||||||
|
// Pulse frame_boundary — AGC should reduce gain by attack=1
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
// current_gain lags agc_gain by 1 cycle (NBA), wait one extra cycle
|
||||||
|
@(posedge clk); #1;
|
||||||
|
// Internal gain was +2, attack=1 → new gain = +1 (0x01)
|
||||||
|
check(current_gain == 4'b0001,
|
||||||
|
"T15.2: AGC reduced gain to x2 after saturation");
|
||||||
|
|
||||||
|
// Another frame with saturation (20000*2 = 40000 > 32767)
|
||||||
|
send_sample(16'sd20000, 16'sd20000);
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
@(posedge clk); #1;
|
||||||
|
// gain was +1, attack=1 → new gain = 0 (0x00)
|
||||||
|
check(current_gain == 4'b0000,
|
||||||
|
"T15.3: AGC reduced gain to x1 (pass-through)");
|
||||||
|
|
||||||
|
// At gain 0 (pass-through), 20000 does NOT overflow 16-bit range,
|
||||||
|
// so no saturation occurs. Signal peak = 20000 >> 7 = 156 > target(100),
|
||||||
|
// so AGC correctly holds gain at 0. This is expected behavior.
|
||||||
|
// To test crossing into attenuation: increase attack to 3.
|
||||||
|
agc_attack = 4'd3;
|
||||||
|
// Reset and start fresh with gain +2, attack=3
|
||||||
|
reset_n = 0;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
reset_n = 1;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
|
||||||
|
gain_shift = 4'b0_010; // amplify x4, internal gain = +2
|
||||||
|
agc_enable = 0;
|
||||||
|
@(posedge clk);
|
||||||
|
agc_enable = 1;
|
||||||
|
@(posedge clk); @(posedge clk); @(posedge clk); #1;
|
||||||
|
|
||||||
|
// Send saturating samples
|
||||||
|
send_sample(16'sd20000, 16'sd20000);
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
@(posedge clk); #1;
|
||||||
|
// gain was +2, attack=3 → new gain = -1 → encoding 0x09
|
||||||
|
check(current_gain == 4'b1001,
|
||||||
|
"T15.4: Large attack step crosses to attenuation (gain +2 - 3 = -1 → 0x9)");
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
// TEST 16: AGC auto gain-up after holdoff
|
||||||
|
// ---------------------------------------------------------------
|
||||||
|
$display("");
|
||||||
|
$display("--- Test 16: AGC gain-up after holdoff ---");
|
||||||
|
|
||||||
|
reset_n = 0;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
reset_n = 1;
|
||||||
|
repeat (2) @(posedge clk);
|
||||||
|
|
||||||
|
// Start with low gain, weak signal, holdoff=2
|
||||||
|
gain_shift = 4'b0_000; // pass-through (internal gain = 0)
|
||||||
|
agc_enable = 0;
|
||||||
|
agc_attack = 4'd1;
|
||||||
|
agc_decay = 4'd1;
|
||||||
|
agc_holdoff = 4'd2;
|
||||||
|
agc_target = 8'd100; // target peak = 100 (in upper 8 bits = 12800 raw)
|
||||||
|
@(posedge clk); @(posedge clk);
|
||||||
|
|
||||||
|
agc_enable = 1;
|
||||||
|
@(posedge clk); @(posedge clk); #1;
|
||||||
|
|
||||||
|
// Frame 1: send weak signal (peak < target), holdoff counter = 2
|
||||||
|
send_sample(16'sd100, 16'sd50); // peak=100, [14:7]=0 (very weak)
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
@(posedge clk); #1;
|
||||||
|
check(current_gain == 4'b0000,
|
||||||
|
"T16.1: Gain held during holdoff (frame 1, holdoff=2)");
|
||||||
|
|
||||||
|
// Frame 2: still weak, holdoff counter decrements to 1
|
||||||
|
send_sample(16'sd100, 16'sd50);
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
@(posedge clk); #1;
|
||||||
|
check(current_gain == 4'b0000,
|
||||||
|
"T16.2: Gain held during holdoff (frame 2, holdoff=1)");
|
||||||
|
|
||||||
|
// Frame 3: holdoff expired (was 0 at start of frame) → gain up
|
||||||
|
send_sample(16'sd100, 16'sd50);
|
||||||
|
@(negedge clk); frame_boundary = 1; @(posedge clk); #1;
|
||||||
|
@(negedge clk); frame_boundary = 0; @(posedge clk); #1;
|
||||||
|
@(posedge clk); #1;
|
||||||
|
check(current_gain == 4'b0001,
|
||||||
|
"T16.3: Gain increased after holdoff expired (gain 0->1)");
|
||||||
|
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
// SUMMARY
|
// SUMMARY
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ module tb_usb_data_interface;
|
|||||||
reg [7:0] status_self_test_detail;
|
reg [7:0] status_self_test_detail;
|
||||||
reg status_self_test_busy;
|
reg status_self_test_busy;
|
||||||
|
|
||||||
|
// AGC status readback inputs
|
||||||
|
reg [3:0] status_agc_current_gain;
|
||||||
|
reg [7:0] status_agc_peak_magnitude;
|
||||||
|
reg [7:0] status_agc_saturation_count;
|
||||||
|
reg status_agc_enable;
|
||||||
|
|
||||||
// ── Clock generators (asynchronous) ────────────────────────
|
// ── Clock generators (asynchronous) ────────────────────────
|
||||||
always #(CLK_PERIOD / 2) clk = ~clk;
|
always #(CLK_PERIOD / 2) clk = ~clk;
|
||||||
always #(FT_CLK_PERIOD / 2) ft601_clk_in = ~ft601_clk_in;
|
always #(FT_CLK_PERIOD / 2) ft601_clk_in = ~ft601_clk_in;
|
||||||
@@ -134,7 +140,13 @@ module tb_usb_data_interface;
|
|||||||
// Self-test status readback
|
// Self-test status readback
|
||||||
.status_self_test_flags (status_self_test_flags),
|
.status_self_test_flags (status_self_test_flags),
|
||||||
.status_self_test_detail(status_self_test_detail),
|
.status_self_test_detail(status_self_test_detail),
|
||||||
.status_self_test_busy (status_self_test_busy)
|
.status_self_test_busy (status_self_test_busy),
|
||||||
|
|
||||||
|
// AGC status readback
|
||||||
|
.status_agc_current_gain (status_agc_current_gain),
|
||||||
|
.status_agc_peak_magnitude (status_agc_peak_magnitude),
|
||||||
|
.status_agc_saturation_count(status_agc_saturation_count),
|
||||||
|
.status_agc_enable (status_agc_enable)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Test bookkeeping ───────────────────────────────────────
|
// ── Test bookkeeping ───────────────────────────────────────
|
||||||
@@ -194,6 +206,10 @@ module tb_usb_data_interface;
|
|||||||
status_self_test_flags = 5'b00000;
|
status_self_test_flags = 5'b00000;
|
||||||
status_self_test_detail = 8'd0;
|
status_self_test_detail = 8'd0;
|
||||||
status_self_test_busy = 1'b0;
|
status_self_test_busy = 1'b0;
|
||||||
|
status_agc_current_gain = 4'd0;
|
||||||
|
status_agc_peak_magnitude = 8'd0;
|
||||||
|
status_agc_saturation_count = 8'd0;
|
||||||
|
status_agc_enable = 1'b0;
|
||||||
repeat (6) @(posedge ft601_clk_in);
|
repeat (6) @(posedge ft601_clk_in);
|
||||||
reset_n = 1;
|
reset_n = 1;
|
||||||
// Wait enough cycles for stream_control CDC to propagate
|
// Wait enough cycles for stream_control CDC to propagate
|
||||||
@@ -902,6 +918,11 @@ module tb_usb_data_interface;
|
|||||||
status_self_test_flags = 5'b11111;
|
status_self_test_flags = 5'b11111;
|
||||||
status_self_test_detail = 8'hA5;
|
status_self_test_detail = 8'hA5;
|
||||||
status_self_test_busy = 1'b0;
|
status_self_test_busy = 1'b0;
|
||||||
|
// AGC status: gain=5, peak=180, sat_count=12, enabled
|
||||||
|
status_agc_current_gain = 4'd5;
|
||||||
|
status_agc_peak_magnitude = 8'd180;
|
||||||
|
status_agc_saturation_count = 8'd12;
|
||||||
|
status_agc_enable = 1'b1;
|
||||||
|
|
||||||
// Pulse status_request (1 cycle in clk domain — toggles status_req_toggle_100m)
|
// Pulse status_request (1 cycle in clk domain — toggles status_req_toggle_100m)
|
||||||
@(posedge clk);
|
@(posedge clk);
|
||||||
@@ -958,8 +979,8 @@ module tb_usb_data_interface;
|
|||||||
"Status readback: word 2 = {guard, short_chirp}");
|
"Status readback: word 2 = {guard, short_chirp}");
|
||||||
check(uut.status_words[3] === {16'd17450, 10'd0, 6'd32},
|
check(uut.status_words[3] === {16'd17450, 10'd0, 6'd32},
|
||||||
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
|
"Status readback: word 3 = {short_listen, 0, chirps_per_elev}");
|
||||||
check(uut.status_words[4] === {30'd0, 2'b10},
|
check(uut.status_words[4] === {4'd5, 8'd180, 8'd12, 1'b1, 9'd0, 2'b10},
|
||||||
"Status readback: word 4 = range_mode=2'b10");
|
"Status readback: word 4 = {agc_gain=5, peak=180, sat=12, en=1, range_mode=2}");
|
||||||
// status_words[5] = {7'd0, busy, 8'd0, detail[7:0], 3'd0, flags[4:0]}
|
// status_words[5] = {7'd0, busy, 8'd0, detail[7:0], 3'd0, flags[4:0]}
|
||||||
// = {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111}
|
// = {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111}
|
||||||
check(uut.status_words[5] === {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111},
|
check(uut.status_words[5] === {7'd0, 1'b0, 8'd0, 8'hA5, 3'd0, 5'b11111},
|
||||||
|
|||||||
@@ -77,7 +77,13 @@ module usb_data_interface (
|
|||||||
// Self-test status readback (opcode 0x31 / included in 0xFF status packet)
|
// Self-test status readback (opcode 0x31 / included in 0xFF status packet)
|
||||||
input wire [4:0] status_self_test_flags, // Per-test PASS(1)/FAIL(0) latched
|
input wire [4:0] status_self_test_flags, // Per-test PASS(1)/FAIL(0) latched
|
||||||
input wire [7:0] status_self_test_detail, // Diagnostic detail byte latched
|
input wire [7:0] status_self_test_detail, // Diagnostic detail byte latched
|
||||||
input wire status_self_test_busy // Self-test FSM still running
|
input wire status_self_test_busy, // Self-test FSM still running
|
||||||
|
|
||||||
|
// AGC status readback
|
||||||
|
input wire [3:0] status_agc_current_gain,
|
||||||
|
input wire [7:0] status_agc_peak_magnitude,
|
||||||
|
input wire [7:0] status_agc_saturation_count,
|
||||||
|
input wire status_agc_enable
|
||||||
);
|
);
|
||||||
|
|
||||||
// USB packet structure (same as before)
|
// USB packet structure (same as before)
|
||||||
@@ -267,8 +273,13 @@ always @(posedge ft601_clk_in or negedge ft601_reset_n) begin
|
|||||||
status_words[2] <= {status_guard, status_short_chirp};
|
status_words[2] <= {status_guard, status_short_chirp};
|
||||||
// Word 3: {short_listen_cycles[15:0], chirps_per_elev[5:0], 10'b0}
|
// 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};
|
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||||
// Word 4: Fix 7 — range_mode in bits [1:0], rest reserved
|
// Word 4: AGC metrics + range_mode
|
||||||
status_words[4] <= {30'd0, status_range_mode};
|
status_words[4] <= {status_agc_current_gain, // [31:28]
|
||||||
|
status_agc_peak_magnitude, // [27:20]
|
||||||
|
status_agc_saturation_count, // [19:12]
|
||||||
|
status_agc_enable, // [11]
|
||||||
|
9'd0, // [10:2] reserved
|
||||||
|
status_range_mode}; // [1:0]
|
||||||
// Word 5: Self-test results {reserved[6:0], busy, reserved[7:0], detail[7:0], reserved[2:0], flags[4:0]}
|
// Word 5: Self-test results {reserved[6:0], busy, reserved[7:0], detail[7:0], reserved[2:0], flags[4:0]}
|
||||||
status_words[5] <= {7'd0, status_self_test_busy,
|
status_words[5] <= {7'd0, status_self_test_busy,
|
||||||
8'd0, status_self_test_detail,
|
8'd0, status_self_test_detail,
|
||||||
|
|||||||
@@ -90,7 +90,13 @@ module usb_data_interface_ft2232h (
|
|||||||
// Self-test status readback
|
// Self-test status readback
|
||||||
input wire [4:0] status_self_test_flags,
|
input wire [4:0] status_self_test_flags,
|
||||||
input wire [7:0] status_self_test_detail,
|
input wire [7:0] status_self_test_detail,
|
||||||
input wire status_self_test_busy
|
input wire status_self_test_busy,
|
||||||
|
|
||||||
|
// AGC status readback
|
||||||
|
input wire [3:0] status_agc_current_gain,
|
||||||
|
input wire [7:0] status_agc_peak_magnitude,
|
||||||
|
input wire [7:0] status_agc_saturation_count,
|
||||||
|
input wire status_agc_enable
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -281,7 +287,12 @@ always @(posedge ft_clk or negedge ft_reset_n) begin
|
|||||||
status_words[1] <= {status_long_chirp, status_long_listen};
|
status_words[1] <= {status_long_chirp, status_long_listen};
|
||||||
status_words[2] <= {status_guard, status_short_chirp};
|
status_words[2] <= {status_guard, status_short_chirp};
|
||||||
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
|
||||||
status_words[4] <= {30'd0, status_range_mode};
|
status_words[4] <= {status_agc_current_gain, // [31:28]
|
||||||
|
status_agc_peak_magnitude, // [27:20]
|
||||||
|
status_agc_saturation_count, // [19:12]
|
||||||
|
status_agc_enable, // [11]
|
||||||
|
9'd0, // [10:2] reserved
|
||||||
|
status_range_mode}; // [1:0]
|
||||||
status_words[5] <= {7'd0, status_self_test_busy,
|
status_words[5] <= {7'd0, status_self_test_busy,
|
||||||
8'd0, status_self_test_detail,
|
8'd0, status_self_test_detail,
|
||||||
3'd0, status_self_test_flags};
|
3'd0, status_self_test_flags};
|
||||||
|
|||||||
@@ -379,6 +379,44 @@ class RadarDashboard:
|
|||||||
command=lambda: self._send_cmd(0x25, 0)).pack(
|
command=lambda: self._send_cmd(0x25, 0)).pack(
|
||||||
side="left", expand=True, fill="x", padx=(2, 0))
|
side="left", expand=True, fill="x", padx=(2, 0))
|
||||||
|
|
||||||
|
# ── AGC (Automatic Gain Control) ──────────────────────────────
|
||||||
|
grp_agc = ttk.LabelFrame(right, text="AGC (Auto Gain)", padding=10)
|
||||||
|
grp_agc.pack(fill="x", pady=(0, 8))
|
||||||
|
|
||||||
|
agc_params = [
|
||||||
|
("AGC Enable", 0x28, "0", 1, "0=manual, 1=auto"),
|
||||||
|
("AGC Target", 0x29, "200", 8, "0-255, peak target"),
|
||||||
|
("AGC Attack", 0x2A, "1", 4, "0-15, atten step"),
|
||||||
|
("AGC Decay", 0x2B, "1", 4, "0-15, gain-up step"),
|
||||||
|
("AGC Holdoff", 0x2C, "4", 4, "0-15, frames"),
|
||||||
|
]
|
||||||
|
for label, opcode, default, bits, hint in agc_params:
|
||||||
|
self._add_param_row(grp_agc, label, opcode, default, bits, hint)
|
||||||
|
|
||||||
|
# AGC quick toggle
|
||||||
|
agc_row = ttk.Frame(grp_agc)
|
||||||
|
agc_row.pack(fill="x", pady=2)
|
||||||
|
ttk.Button(agc_row, text="Enable AGC",
|
||||||
|
command=lambda: self._send_cmd(0x28, 1)).pack(
|
||||||
|
side="left", expand=True, fill="x", padx=(0, 2))
|
||||||
|
ttk.Button(agc_row, text="Disable AGC",
|
||||||
|
command=lambda: self._send_cmd(0x28, 0)).pack(
|
||||||
|
side="left", expand=True, fill="x", padx=(2, 0))
|
||||||
|
|
||||||
|
# AGC status readback labels
|
||||||
|
agc_st = ttk.LabelFrame(grp_agc, text="AGC Status", padding=6)
|
||||||
|
agc_st.pack(fill="x", pady=(4, 0))
|
||||||
|
self._agc_labels = {}
|
||||||
|
for name, default_text in [
|
||||||
|
("enable", "AGC: --"),
|
||||||
|
("gain", "Gain: --"),
|
||||||
|
("peak", "Peak: --"),
|
||||||
|
("sat", "Sat Count: --"),
|
||||||
|
]:
|
||||||
|
lbl = ttk.Label(agc_st, text=default_text, font=("Menlo", 9))
|
||||||
|
lbl.pack(anchor="w")
|
||||||
|
self._agc_labels[name] = lbl
|
||||||
|
|
||||||
# ── Custom Command (advanced / debug) ─────────────────────────
|
# ── Custom Command (advanced / debug) ─────────────────────────
|
||||||
grp_cust = ttk.LabelFrame(right, text="Custom Command", padding=10)
|
grp_cust = ttk.LabelFrame(right, text="Custom Command", padding=10)
|
||||||
grp_cust.pack(fill="x", pady=(0, 8))
|
grp_cust.pack(fill="x", pady=(0, 8))
|
||||||
@@ -521,7 +559,7 @@ class RadarDashboard:
|
|||||||
self.root.after(0, self._update_self_test_labels, status)
|
self.root.after(0, self._update_self_test_labels, status)
|
||||||
|
|
||||||
def _update_self_test_labels(self, status: StatusResponse):
|
def _update_self_test_labels(self, status: StatusResponse):
|
||||||
"""Update the self-test result labels from a StatusResponse."""
|
"""Update the self-test result labels and AGC status from a StatusResponse."""
|
||||||
if not hasattr(self, '_st_labels'):
|
if not hasattr(self, '_st_labels'):
|
||||||
return
|
return
|
||||||
flags = status.self_test_flags
|
flags = status.self_test_flags
|
||||||
@@ -556,6 +594,21 @@ class RadarDashboard:
|
|||||||
self._st_labels[key].config(
|
self._st_labels[key].config(
|
||||||
text=f"{name}: {result_str}", foreground=color)
|
text=f"{name}: {result_str}", foreground=color)
|
||||||
|
|
||||||
|
# AGC status readback
|
||||||
|
if hasattr(self, '_agc_labels'):
|
||||||
|
agc_str = "AUTO" if status.agc_enable else "MANUAL"
|
||||||
|
agc_color = GREEN if status.agc_enable else FG
|
||||||
|
self._agc_labels["enable"].config(
|
||||||
|
text=f"AGC: {agc_str}", foreground=agc_color)
|
||||||
|
self._agc_labels["gain"].config(
|
||||||
|
text=f"Gain: {status.agc_current_gain}")
|
||||||
|
self._agc_labels["peak"].config(
|
||||||
|
text=f"Peak: {status.agc_peak_magnitude}")
|
||||||
|
sat_color = RED if status.agc_saturation_count > 0 else FG
|
||||||
|
self._agc_labels["sat"].config(
|
||||||
|
text=f"Sat Count: {status.agc_saturation_count}",
|
||||||
|
foreground=sat_color)
|
||||||
|
|
||||||
# --------------------------------------------------------- Display loop
|
# --------------------------------------------------------- Display loop
|
||||||
def _schedule_update(self):
|
def _schedule_update(self):
|
||||||
self._update_display()
|
self._update_display()
|
||||||
|
|||||||
@@ -59,9 +59,9 @@ class Opcode(IntEnum):
|
|||||||
0x03 host_detect_threshold 0x16 host_gain_shift
|
0x03 host_detect_threshold 0x16 host_gain_shift
|
||||||
0x04 host_stream_control 0x20 host_range_mode
|
0x04 host_stream_control 0x20 host_range_mode
|
||||||
0x10 host_long_chirp_cycles 0x21-0x27 CFAR / MTI / DC-notch
|
0x10 host_long_chirp_cycles 0x21-0x27 CFAR / MTI / DC-notch
|
||||||
0x11 host_long_listen_cycles 0x30 host_self_test_trigger
|
0x11 host_long_listen_cycles 0x28-0x2C AGC control
|
||||||
0x12 host_guard_cycles 0x31 host_status_request
|
0x12 host_guard_cycles 0x30 host_self_test_trigger
|
||||||
0x13 host_short_chirp_cycles 0xFF host_status_request
|
0x13 host_short_chirp_cycles 0x31/0xFF host_status_request
|
||||||
"""
|
"""
|
||||||
# --- Basic control (0x01-0x04) ---
|
# --- Basic control (0x01-0x04) ---
|
||||||
RADAR_MODE = 0x01 # 2-bit mode select
|
RADAR_MODE = 0x01 # 2-bit mode select
|
||||||
@@ -90,6 +90,13 @@ class Opcode(IntEnum):
|
|||||||
MTI_ENABLE = 0x26
|
MTI_ENABLE = 0x26
|
||||||
DC_NOTCH_WIDTH = 0x27
|
DC_NOTCH_WIDTH = 0x27
|
||||||
|
|
||||||
|
# --- AGC (0x28-0x2C) ---
|
||||||
|
AGC_ENABLE = 0x28
|
||||||
|
AGC_TARGET = 0x29
|
||||||
|
AGC_ATTACK = 0x2A
|
||||||
|
AGC_DECAY = 0x2B
|
||||||
|
AGC_HOLDOFF = 0x2C
|
||||||
|
|
||||||
# --- Board self-test / status (0x30-0x31, 0xFF) ---
|
# --- Board self-test / status (0x30-0x31, 0xFF) ---
|
||||||
SELF_TEST_TRIGGER = 0x30
|
SELF_TEST_TRIGGER = 0x30
|
||||||
SELF_TEST_STATUS = 0x31
|
SELF_TEST_STATUS = 0x31
|
||||||
@@ -135,6 +142,11 @@ class StatusResponse:
|
|||||||
self_test_flags: int = 0 # 5-bit result flags [4:0]
|
self_test_flags: int = 0 # 5-bit result flags [4:0]
|
||||||
self_test_detail: int = 0 # 8-bit detail code [7:0]
|
self_test_detail: int = 0 # 8-bit detail code [7:0]
|
||||||
self_test_busy: int = 0 # 1-bit busy flag
|
self_test_busy: int = 0 # 1-bit busy flag
|
||||||
|
# AGC metrics (word 4, added for hybrid AGC)
|
||||||
|
agc_current_gain: int = 0 # 4-bit current gain encoding [3:0]
|
||||||
|
agc_peak_magnitude: int = 0 # 8-bit peak magnitude [7:0]
|
||||||
|
agc_saturation_count: int = 0 # 8-bit saturation count [7:0]
|
||||||
|
agc_enable: int = 0 # 1-bit AGC enable readback
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -232,8 +244,13 @@ class RadarProtocol:
|
|||||||
# Word 3: {short_listen[31:16], 10'd0, chirps_per_elev[5:0]}
|
# Word 3: {short_listen[31:16], 10'd0, chirps_per_elev[5:0]}
|
||||||
sr.chirps_per_elev = words[3] & 0x3F
|
sr.chirps_per_elev = words[3] & 0x3F
|
||||||
sr.short_listen = (words[3] >> 16) & 0xFFFF
|
sr.short_listen = (words[3] >> 16) & 0xFFFF
|
||||||
# Word 4: {30'd0, range_mode[1:0]}
|
# Word 4: {agc_current_gain[31:28], agc_peak_magnitude[27:20],
|
||||||
|
# agc_saturation_count[19:12], agc_enable[11], 9'd0, range_mode[1:0]}
|
||||||
sr.range_mode = words[4] & 0x03
|
sr.range_mode = words[4] & 0x03
|
||||||
|
sr.agc_enable = (words[4] >> 11) & 0x01
|
||||||
|
sr.agc_saturation_count = (words[4] >> 12) & 0xFF
|
||||||
|
sr.agc_peak_magnitude = (words[4] >> 20) & 0xFF
|
||||||
|
sr.agc_current_gain = (words[4] >> 28) & 0x0F
|
||||||
# Word 5: {7'd0, self_test_busy, 8'd0, self_test_detail[7:0],
|
# Word 5: {7'd0, self_test_busy, 8'd0, self_test_detail[7:0],
|
||||||
# 3'd0, self_test_flags[4:0]}
|
# 3'd0, self_test_flags[4:0]}
|
||||||
sr.self_test_flags = words[5] & 0x1F
|
sr.self_test_flags = words[5] & 0x1F
|
||||||
|
|||||||
@@ -125,7 +125,8 @@ class TestRadarProtocol(unittest.TestCase):
|
|||||||
long_chirp=3000, long_listen=13700,
|
long_chirp=3000, long_listen=13700,
|
||||||
guard=17540, short_chirp=50,
|
guard=17540, short_chirp=50,
|
||||||
short_listen=17450, chirps=32, range_mode=0,
|
short_listen=17450, chirps=32, range_mode=0,
|
||||||
st_flags=0, st_detail=0, st_busy=0):
|
st_flags=0, st_detail=0, st_busy=0,
|
||||||
|
agc_gain=0, agc_peak=0, agc_sat=0, agc_enable=0):
|
||||||
"""Build a 26-byte status response matching FPGA format (Build 26)."""
|
"""Build a 26-byte status response matching FPGA format (Build 26)."""
|
||||||
pkt = bytearray()
|
pkt = bytearray()
|
||||||
pkt.append(STATUS_HEADER_BYTE)
|
pkt.append(STATUS_HEADER_BYTE)
|
||||||
@@ -146,8 +147,11 @@ class TestRadarProtocol(unittest.TestCase):
|
|||||||
w3 = ((short_listen & 0xFFFF) << 16) | (chirps & 0x3F)
|
w3 = ((short_listen & 0xFFFF) << 16) | (chirps & 0x3F)
|
||||||
pkt += struct.pack(">I", w3)
|
pkt += struct.pack(">I", w3)
|
||||||
|
|
||||||
# Word 4: {30'd0, range_mode[1:0]}
|
# Word 4: {agc_current_gain[3:0], agc_peak_magnitude[7:0],
|
||||||
w4 = range_mode & 0x03
|
# agc_saturation_count[7:0], agc_enable, 9'd0, range_mode[1:0]}
|
||||||
|
w4 = (((agc_gain & 0x0F) << 28) | ((agc_peak & 0xFF) << 20) |
|
||||||
|
((agc_sat & 0xFF) << 12) | ((agc_enable & 0x01) << 11) |
|
||||||
|
(range_mode & 0x03))
|
||||||
pkt += struct.pack(">I", w4)
|
pkt += struct.pack(">I", w4)
|
||||||
|
|
||||||
# Word 5: {7'd0, self_test_busy, 8'd0, self_test_detail[7:0],
|
# Word 5: {7'd0, self_test_busy, 8'd0, self_test_detail[7:0],
|
||||||
@@ -723,6 +727,7 @@ class TestOpcodeEnum(unittest.TestCase):
|
|||||||
expected = {0x01, 0x02, 0x03, 0x04,
|
expected = {0x01, 0x02, 0x03, 0x04,
|
||||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
|
||||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||||
|
0x28, 0x29, 0x2A, 0x2B, 0x2C,
|
||||||
0x30, 0x31, 0xFF}
|
0x30, 0x31, 0xFF}
|
||||||
enum_values = {int(m) for m in Opcode}
|
enum_values = {int(m) for m in Opcode}
|
||||||
for op in expected:
|
for op in expected:
|
||||||
@@ -747,5 +752,93 @@ class TestStatusResponseDefaults(unittest.TestCase):
|
|||||||
self.assertEqual(sr.self_test_busy, 1)
|
self.assertEqual(sr.self_test_busy, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAGCOpcodes(unittest.TestCase):
|
||||||
|
"""Verify AGC opcode enum members match FPGA RTL (0x28-0x2C)."""
|
||||||
|
|
||||||
|
def test_agc_enable_opcode(self):
|
||||||
|
self.assertEqual(Opcode.AGC_ENABLE, 0x28)
|
||||||
|
|
||||||
|
def test_agc_target_opcode(self):
|
||||||
|
self.assertEqual(Opcode.AGC_TARGET, 0x29)
|
||||||
|
|
||||||
|
def test_agc_attack_opcode(self):
|
||||||
|
self.assertEqual(Opcode.AGC_ATTACK, 0x2A)
|
||||||
|
|
||||||
|
def test_agc_decay_opcode(self):
|
||||||
|
self.assertEqual(Opcode.AGC_DECAY, 0x2B)
|
||||||
|
|
||||||
|
def test_agc_holdoff_opcode(self):
|
||||||
|
self.assertEqual(Opcode.AGC_HOLDOFF, 0x2C)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAGCStatusParsing(unittest.TestCase):
|
||||||
|
"""Verify AGC fields in status_words[4] are parsed correctly."""
|
||||||
|
|
||||||
|
def _make_status_packet(self, **kwargs):
|
||||||
|
"""Delegate to TestRadarProtocol helper."""
|
||||||
|
helper = TestRadarProtocol()
|
||||||
|
return helper._make_status_packet(**kwargs)
|
||||||
|
|
||||||
|
def test_agc_fields_default_zero(self):
|
||||||
|
"""With no AGC fields set, all should be 0."""
|
||||||
|
raw = self._make_status_packet()
|
||||||
|
sr = RadarProtocol.parse_status_packet(raw)
|
||||||
|
self.assertEqual(sr.agc_current_gain, 0)
|
||||||
|
self.assertEqual(sr.agc_peak_magnitude, 0)
|
||||||
|
self.assertEqual(sr.agc_saturation_count, 0)
|
||||||
|
self.assertEqual(sr.agc_enable, 0)
|
||||||
|
|
||||||
|
def test_agc_fields_nonzero(self):
|
||||||
|
"""AGC fields round-trip through status packet."""
|
||||||
|
raw = self._make_status_packet(agc_gain=7, agc_peak=200,
|
||||||
|
agc_sat=15, agc_enable=1)
|
||||||
|
sr = RadarProtocol.parse_status_packet(raw)
|
||||||
|
self.assertEqual(sr.agc_current_gain, 7)
|
||||||
|
self.assertEqual(sr.agc_peak_magnitude, 200)
|
||||||
|
self.assertEqual(sr.agc_saturation_count, 15)
|
||||||
|
self.assertEqual(sr.agc_enable, 1)
|
||||||
|
|
||||||
|
def test_agc_max_values(self):
|
||||||
|
"""AGC fields at max values."""
|
||||||
|
raw = self._make_status_packet(agc_gain=15, agc_peak=255,
|
||||||
|
agc_sat=255, agc_enable=1)
|
||||||
|
sr = RadarProtocol.parse_status_packet(raw)
|
||||||
|
self.assertEqual(sr.agc_current_gain, 15)
|
||||||
|
self.assertEqual(sr.agc_peak_magnitude, 255)
|
||||||
|
self.assertEqual(sr.agc_saturation_count, 255)
|
||||||
|
self.assertEqual(sr.agc_enable, 1)
|
||||||
|
|
||||||
|
def test_agc_and_range_mode_coexist(self):
|
||||||
|
"""AGC fields and range_mode occupy the same word without conflict."""
|
||||||
|
raw = self._make_status_packet(agc_gain=5, agc_peak=128,
|
||||||
|
agc_sat=42, agc_enable=1,
|
||||||
|
range_mode=2)
|
||||||
|
sr = RadarProtocol.parse_status_packet(raw)
|
||||||
|
self.assertEqual(sr.agc_current_gain, 5)
|
||||||
|
self.assertEqual(sr.agc_peak_magnitude, 128)
|
||||||
|
self.assertEqual(sr.agc_saturation_count, 42)
|
||||||
|
self.assertEqual(sr.agc_enable, 1)
|
||||||
|
self.assertEqual(sr.range_mode, 2)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAGCStatusResponseDefaults(unittest.TestCase):
|
||||||
|
"""Verify StatusResponse AGC field defaults."""
|
||||||
|
|
||||||
|
def test_default_agc_fields(self):
|
||||||
|
sr = StatusResponse()
|
||||||
|
self.assertEqual(sr.agc_current_gain, 0)
|
||||||
|
self.assertEqual(sr.agc_peak_magnitude, 0)
|
||||||
|
self.assertEqual(sr.agc_saturation_count, 0)
|
||||||
|
self.assertEqual(sr.agc_enable, 0)
|
||||||
|
|
||||||
|
def test_agc_fields_set(self):
|
||||||
|
sr = StatusResponse(agc_current_gain=7, agc_peak_magnitude=200,
|
||||||
|
agc_saturation_count=15, agc_enable=1)
|
||||||
|
self.assertEqual(sr.agc_current_gain, 7)
|
||||||
|
self.assertEqual(sr.agc_peak_magnitude, 200)
|
||||||
|
self.assertEqual(sr.agc_saturation_count, 15)
|
||||||
|
self.assertEqual(sr.agc_enable, 1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main(verbosity=2)
|
unittest.main(verbosity=2)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ RadarDashboard is a QMainWindow with five tabs:
|
|||||||
1. Main View — Range-Doppler matplotlib canvas (64x32), device combos,
|
1. Main View — Range-Doppler matplotlib canvas (64x32), device combos,
|
||||||
Start/Stop, targets table
|
Start/Stop, targets table
|
||||||
2. Map View — Embedded Leaflet map + sidebar
|
2. Map View — Embedded Leaflet map + sidebar
|
||||||
3. FPGA Control — Full FPGA register control panel (all 22 opcodes,
|
3. FPGA Control — Full FPGA register control panel (all 27 opcodes incl. AGC,
|
||||||
bit-width validation, grouped layout matching production)
|
bit-width validation, grouped layout matching production)
|
||||||
4. Diagnostics — Connection indicators, packet stats, dependency status,
|
4. Diagnostics — Connection indicators, packet stats, dependency status,
|
||||||
self-test results, log viewer
|
self-test results, log viewer
|
||||||
@@ -681,6 +681,48 @@ class RadarDashboard(QMainWindow):
|
|||||||
|
|
||||||
right_layout.addWidget(grp_cfar)
|
right_layout.addWidget(grp_cfar)
|
||||||
|
|
||||||
|
# ── AGC (Automatic Gain Control) ──────────────────────────────
|
||||||
|
grp_agc = QGroupBox("AGC (Auto Gain)")
|
||||||
|
agc_layout = QVBoxLayout(grp_agc)
|
||||||
|
|
||||||
|
agc_params = [
|
||||||
|
("AGC Enable", 0x28, 0, 1, "0=manual, 1=auto"),
|
||||||
|
("AGC Target", 0x29, 200, 8, "0-255, peak target"),
|
||||||
|
("AGC Attack", 0x2A, 1, 4, "0-15, atten step"),
|
||||||
|
("AGC Decay", 0x2B, 1, 4, "0-15, gain-up step"),
|
||||||
|
("AGC Holdoff", 0x2C, 4, 4, "0-15, frames"),
|
||||||
|
]
|
||||||
|
for label, opcode, default, bits, hint in agc_params:
|
||||||
|
self._add_fpga_param_row(agc_layout, label, opcode, default, bits, hint)
|
||||||
|
|
||||||
|
# AGC quick toggles
|
||||||
|
agc_row = QHBoxLayout()
|
||||||
|
btn_agc_on = QPushButton("Enable AGC")
|
||||||
|
btn_agc_on.clicked.connect(lambda: self._send_fpga_cmd(0x28, 1))
|
||||||
|
agc_row.addWidget(btn_agc_on)
|
||||||
|
btn_agc_off = QPushButton("Disable AGC")
|
||||||
|
btn_agc_off.clicked.connect(lambda: self._send_fpga_cmd(0x28, 0))
|
||||||
|
agc_row.addWidget(btn_agc_off)
|
||||||
|
agc_layout.addLayout(agc_row)
|
||||||
|
|
||||||
|
# AGC status readback labels
|
||||||
|
agc_st_group = QGroupBox("AGC Status")
|
||||||
|
agc_st_layout = QVBoxLayout(agc_st_group)
|
||||||
|
self._agc_labels: dict[str, QLabel] = {}
|
||||||
|
for name, default_text in [
|
||||||
|
("enable", "AGC: --"),
|
||||||
|
("gain", "Gain: --"),
|
||||||
|
("peak", "Peak: --"),
|
||||||
|
("sat", "Sat Count: --"),
|
||||||
|
]:
|
||||||
|
lbl = QLabel(default_text)
|
||||||
|
lbl.setStyleSheet(f"color: {DARK_INFO}; font-size: 10px;")
|
||||||
|
agc_st_layout.addWidget(lbl)
|
||||||
|
self._agc_labels[name] = lbl
|
||||||
|
agc_layout.addWidget(agc_st_group)
|
||||||
|
|
||||||
|
right_layout.addWidget(grp_agc)
|
||||||
|
|
||||||
# Custom Command
|
# Custom Command
|
||||||
grp_custom = QGroupBox("Custom Command")
|
grp_custom = QGroupBox("Custom Command")
|
||||||
cust_layout = QGridLayout(grp_custom)
|
cust_layout = QGridLayout(grp_custom)
|
||||||
@@ -1276,6 +1318,23 @@ class RadarDashboard(QMainWindow):
|
|||||||
self._st_labels["t4"].setText(
|
self._st_labels["t4"].setText(
|
||||||
f"T4 ADC: {'PASS' if flags & 0x10 else 'FAIL'}")
|
f"T4 ADC: {'PASS' if flags & 0x10 else 'FAIL'}")
|
||||||
|
|
||||||
|
# AGC status readback
|
||||||
|
if hasattr(self, '_agc_labels'):
|
||||||
|
agc_str = "AUTO" if st.agc_enable else "MANUAL"
|
||||||
|
agc_color = DARK_SUCCESS if st.agc_enable else DARK_INFO
|
||||||
|
self._agc_labels["enable"].setStyleSheet(
|
||||||
|
f"color: {agc_color}; font-weight: bold;")
|
||||||
|
self._agc_labels["enable"].setText(f"AGC: {agc_str}")
|
||||||
|
self._agc_labels["gain"].setText(
|
||||||
|
f"Gain: {st.agc_current_gain}")
|
||||||
|
self._agc_labels["peak"].setText(
|
||||||
|
f"Peak: {st.agc_peak_magnitude}")
|
||||||
|
sat_color = DARK_ERROR if st.agc_saturation_count > 0 else DARK_INFO
|
||||||
|
self._agc_labels["sat"].setStyleSheet(
|
||||||
|
f"color: {sat_color}; font-weight: bold;")
|
||||||
|
self._agc_labels["sat"].setText(
|
||||||
|
f"Sat Count: {st.agc_saturation_count}")
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# Position / coverage callbacks (map sidebar)
|
# Position / coverage callbacks (map sidebar)
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
|
|||||||
@@ -527,6 +527,8 @@ def parse_verilog_status_word_concats(
|
|||||||
):
|
):
|
||||||
idx = int(m.group(1))
|
idx = int(m.group(1))
|
||||||
expr = m.group(2)
|
expr = m.group(2)
|
||||||
|
# Strip single-line comments before normalizing whitespace
|
||||||
|
expr = re.sub(r'//[^\n]*', '', expr)
|
||||||
# Normalize whitespace
|
# Normalize whitespace
|
||||||
expr = re.sub(r'\s+', ' ', expr).strip()
|
expr = re.sub(r'\s+', ' ', expr).strip()
|
||||||
results[idx] = expr
|
results[idx] = expr
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ module tb_cross_layer_ft2232h;
|
|||||||
reg [4:0] status_self_test_flags;
|
reg [4:0] status_self_test_flags;
|
||||||
reg [7:0] status_self_test_detail;
|
reg [7:0] status_self_test_detail;
|
||||||
reg status_self_test_busy;
|
reg status_self_test_busy;
|
||||||
|
reg [3:0] status_agc_current_gain;
|
||||||
|
reg [7:0] status_agc_peak_magnitude;
|
||||||
|
reg [7:0] status_agc_saturation_count;
|
||||||
|
reg status_agc_enable;
|
||||||
|
|
||||||
// ---- Clock generators ----
|
// ---- Clock generators ----
|
||||||
always #(CLK_PERIOD / 2) clk = ~clk;
|
always #(CLK_PERIOD / 2) clk = ~clk;
|
||||||
@@ -130,7 +134,11 @@ module tb_cross_layer_ft2232h;
|
|||||||
.status_range_mode (status_range_mode),
|
.status_range_mode (status_range_mode),
|
||||||
.status_self_test_flags (status_self_test_flags),
|
.status_self_test_flags (status_self_test_flags),
|
||||||
.status_self_test_detail(status_self_test_detail),
|
.status_self_test_detail(status_self_test_detail),
|
||||||
.status_self_test_busy (status_self_test_busy)
|
.status_self_test_busy (status_self_test_busy),
|
||||||
|
.status_agc_current_gain (status_agc_current_gain),
|
||||||
|
.status_agc_peak_magnitude (status_agc_peak_magnitude),
|
||||||
|
.status_agc_saturation_count(status_agc_saturation_count),
|
||||||
|
.status_agc_enable (status_agc_enable)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ---- Test bookkeeping ----
|
// ---- Test bookkeeping ----
|
||||||
@@ -188,6 +196,10 @@ module tb_cross_layer_ft2232h;
|
|||||||
status_self_test_flags = 5'b00000;
|
status_self_test_flags = 5'b00000;
|
||||||
status_self_test_detail = 8'd0;
|
status_self_test_detail = 8'd0;
|
||||||
status_self_test_busy = 1'b0;
|
status_self_test_busy = 1'b0;
|
||||||
|
status_agc_current_gain = 4'd0;
|
||||||
|
status_agc_peak_magnitude = 8'd0;
|
||||||
|
status_agc_saturation_count = 8'd0;
|
||||||
|
status_agc_enable = 1'b0;
|
||||||
repeat (6) @(posedge ft_clk);
|
repeat (6) @(posedge ft_clk);
|
||||||
reset_n = 1;
|
reset_n = 1;
|
||||||
ft_reset_n = 1;
|
ft_reset_n = 1;
|
||||||
@@ -605,6 +617,10 @@ module tb_cross_layer_ft2232h;
|
|||||||
status_self_test_flags = 5'b10101;
|
status_self_test_flags = 5'b10101;
|
||||||
status_self_test_detail = 8'hA5;
|
status_self_test_detail = 8'hA5;
|
||||||
status_self_test_busy = 1'b1;
|
status_self_test_busy = 1'b1;
|
||||||
|
status_agc_current_gain = 4'd7;
|
||||||
|
status_agc_peak_magnitude = 8'd200;
|
||||||
|
status_agc_saturation_count = 8'd15;
|
||||||
|
status_agc_enable = 1'b1;
|
||||||
|
|
||||||
// Pulse status_request and capture bytes IN PARALLEL
|
// Pulse status_request and capture bytes IN PARALLEL
|
||||||
// (same reason as Exercise B — write FSM starts before CDC wait ends)
|
// (same reason as Exercise B — write FSM starts before CDC wait ends)
|
||||||
|
|||||||
@@ -100,6 +100,11 @@ GROUND_TRUTH_OPCODES = {
|
|||||||
0x25: ("host_cfar_enable", 1),
|
0x25: ("host_cfar_enable", 1),
|
||||||
0x26: ("host_mti_enable", 1),
|
0x26: ("host_mti_enable", 1),
|
||||||
0x27: ("host_dc_notch_width", 3),
|
0x27: ("host_dc_notch_width", 3),
|
||||||
|
0x28: ("host_agc_enable", 1),
|
||||||
|
0x29: ("host_agc_target", 8),
|
||||||
|
0x2A: ("host_agc_attack", 4),
|
||||||
|
0x2B: ("host_agc_decay", 4),
|
||||||
|
0x2C: ("host_agc_holdoff", 4),
|
||||||
0x30: ("host_self_test_trigger", 1), # pulse
|
0x30: ("host_self_test_trigger", 1), # pulse
|
||||||
0x31: ("host_status_request", 1), # pulse
|
0x31: ("host_status_request", 1), # pulse
|
||||||
0xFF: ("host_status_request", 1), # alias, pulse
|
0xFF: ("host_status_request", 1), # alias, pulse
|
||||||
@@ -124,6 +129,11 @@ GROUND_TRUTH_RESET_DEFAULTS = {
|
|||||||
"host_cfar_enable": 0,
|
"host_cfar_enable": 0,
|
||||||
"host_mti_enable": 0,
|
"host_mti_enable": 0,
|
||||||
"host_dc_notch_width": 0,
|
"host_dc_notch_width": 0,
|
||||||
|
"host_agc_enable": 0,
|
||||||
|
"host_agc_target": 200,
|
||||||
|
"host_agc_attack": 1,
|
||||||
|
"host_agc_decay": 1,
|
||||||
|
"host_agc_holdoff": 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
GROUND_TRUTH_PACKET_CONSTANTS = {
|
GROUND_TRUTH_PACKET_CONSTANTS = {
|
||||||
@@ -604,6 +614,10 @@ class TestTier2VerilogCosim:
|
|||||||
# status_self_test_flags = 5'b10101 = 21
|
# status_self_test_flags = 5'b10101 = 21
|
||||||
# status_self_test_detail = 0xA5
|
# status_self_test_detail = 0xA5
|
||||||
# status_self_test_busy = 1
|
# status_self_test_busy = 1
|
||||||
|
# status_agc_current_gain = 7
|
||||||
|
# status_agc_peak_magnitude = 200
|
||||||
|
# status_agc_saturation_count = 15
|
||||||
|
# status_agc_enable = 1
|
||||||
|
|
||||||
# Words 1-5 should be correct (no truncation bug)
|
# Words 1-5 should be correct (no truncation bug)
|
||||||
assert sr.cfar_threshold == 0xABCD, f"cfar_threshold: 0x{sr.cfar_threshold:04X}"
|
assert sr.cfar_threshold == 0xABCD, f"cfar_threshold: 0x{sr.cfar_threshold:04X}"
|
||||||
@@ -618,6 +632,12 @@ class TestTier2VerilogCosim:
|
|||||||
assert sr.self_test_detail == 0xA5, f"self_test_detail: 0x{sr.self_test_detail:02X}"
|
assert sr.self_test_detail == 0xA5, f"self_test_detail: 0x{sr.self_test_detail:02X}"
|
||||||
assert sr.self_test_busy == 1, f"self_test_busy: {sr.self_test_busy}"
|
assert sr.self_test_busy == 1, f"self_test_busy: {sr.self_test_busy}"
|
||||||
|
|
||||||
|
# AGC fields (word 4)
|
||||||
|
assert sr.agc_current_gain == 7, f"agc_current_gain: {sr.agc_current_gain}"
|
||||||
|
assert sr.agc_peak_magnitude == 200, f"agc_peak_magnitude: {sr.agc_peak_magnitude}"
|
||||||
|
assert sr.agc_saturation_count == 15, f"agc_saturation_count: {sr.agc_saturation_count}"
|
||||||
|
assert sr.agc_enable == 1, f"agc_enable: {sr.agc_enable}"
|
||||||
|
|
||||||
# Word 0: stream_ctrl should be 5 (3'b101)
|
# Word 0: stream_ctrl should be 5 (3'b101)
|
||||||
assert sr.stream_ctrl == 5, (
|
assert sr.stream_ctrl == 5, (
|
||||||
f"stream_ctrl: {sr.stream_ctrl} != 5. "
|
f"stream_ctrl: {sr.stream_ctrl} != 5. "
|
||||||
|
|||||||
Reference in New Issue
Block a user