diff --git a/9_Firmware/9_2_FPGA/ddc_400m.v b/9_Firmware/9_2_FPGA/ddc_400m.v index f26a06e..604de4c 100644 --- a/9_Firmware/9_2_FPGA/ddc_400m.v +++ b/9_Firmware/9_2_FPGA/ddc_400m.v @@ -8,6 +8,15 @@ module ddc_400m_enhanced ( input wire [7:0] adc_data, // ADC data at 400MHz input wire adc_data_valid_i, // Valid at 400MHz input wire adc_data_valid_q, + // AUDIT-C3: AD9484 sign-conversion select (driven from host opcode 0x33). + // 2'b00 = offset-binary (default; matches SJ1 pins 1-2 bridged) + // 2'b01 = two's-complement (SJ1 pins 2-3 bridged) + // 2'b1x = reserved (treated as offset-binary) + // CSB is hard-tied HIGH on the Main Board so SPI cannot reconfigure the + // chip; this register lets the host adapt the RTL to whichever SJ1 strap + // the board was assembled with. Static after boot; synchronized clk_100m + // -> clk_400m internally. + input wire [1:0] adc_format, output wire signed [17:0] baseband_i, output wire signed [17:0] baseband_q, output wire baseband_valid_i, @@ -236,9 +245,37 @@ nco_400m_enhanced nco_core ( // In simulation (Icarus), uses behavioral equivalent since DSP48E1 is Xilinx-only // ============================================================================ +// AUDIT-C3: Two-segment ADC sign-conversion. AD9484 SCLK/DFS strap (jumper +// SJ1 on RADAR_Main_Board.sch) selects the chip's output format at assembly +// time; SPI is unavailable (CSB hard-tied to +1V8_CLOCK_F). Host opcode 0x33 +// drives `adc_format` so the host can match either jumper position without +// rebuilding the bitstream. Default 2'b00 matches the offset-binary strap. +// +// adc=0x80, format=00 → ~0 (mid-scale offset-binary, 0V analog) +// adc=0x00, format=01 → 0 (mid-scale 2's-complement, 0V analog) +// +// Unsynchronized adc_format would cross clk_100m → clk_400m without protection; +// 2-FF synchronizer below resolves metastability. Static after boot. +(* ASYNC_REG = "TRUE" *) reg [1:0] adc_format_400m_meta; +(* ASYNC_REG = "TRUE" *) reg [1:0] adc_format_400m; +always @(posedge clk_400m) begin + if (reset_400m) begin + adc_format_400m_meta <= 2'b00; + adc_format_400m <= 2'b00; + end else begin + adc_format_400m_meta <= adc_format; + adc_format_400m <= adc_format_400m_meta; + end +end + +wire signed [MIXER_WIDTH-1:0] adc_signed_offbin; +wire signed [MIXER_WIDTH-1:0] adc_signed_twoc; +assign adc_signed_offbin = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} - + {1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2; +assign adc_signed_twoc = {adc_data[ADC_WIDTH-1], adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}}; + // Combinational ADC sign conversion (no register — DSP48E1 AREG handles it) -assign adc_signed_w = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} - - {1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2; +assign adc_signed_w = (adc_format_400m == 2'b01) ? adc_signed_twoc : adc_signed_offbin; // Valid pipeline: 5-stage shift register (1 NCO pipe + 3 DSP48E1 AREG+MREG+PREG + 1 retiming) always @(posedge clk_400m) begin diff --git a/9_Firmware/9_2_FPGA/radar_receiver_final.v b/9_Firmware/9_2_FPGA/radar_receiver_final.v index ff28244..68ea5c2 100644 --- a/9_Firmware/9_2_FPGA/radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/radar_receiver_final.v @@ -76,6 +76,12 @@ module radar_receiver_final ( input wire host_mti_enable, // 1=MTI active, 0=pass-through input wire [2:0] host_dc_notch_width, // DC notch: zero Doppler bins within ±width of DC + // AUDIT-C3: AD9484 sign-conversion select (opcode 0x33). Selects DDC + // sign-conversion to match the SCLK/DFS strap (SJ1) on the Main Board. + // 2'b00 = offset-binary (default), 2'b01 = two's-complement. + // (Opcode 0x32 is reserved for the future S-25 fix: adc_pwdn host control.) + input wire [1:0] host_adc_format, + // 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_q, // DDC output Q (16-bit signed, 100 MHz) @@ -307,6 +313,7 @@ ddc_400m_enhanced ddc( .adc_data(adc_data_cmos), // ADC data at 400MHz (direct from ADC interface) .adc_data_valid_i(adc_valid), // Valid at 400MHz .adc_data_valid_q(adc_valid), // Valid at 400MHz + .adc_format(host_adc_format), // AUDIT-C3: opcode 0x33 selects offset-binary vs 2C .baseband_i(ddc_out_i), // I output at 100MHz .baseband_q(ddc_out_q), // Q output at 100MHz .baseband_valid_i(ddc_valid_i), // Valid at 100MHz diff --git a/9_Firmware/9_2_FPGA/radar_system_top.v b/9_Firmware/9_2_FPGA/radar_system_top.v index 24371b7..7c41f86 100644 --- a/9_Firmware/9_2_FPGA/radar_system_top.v +++ b/9_Firmware/9_2_FPGA/radar_system_top.v @@ -296,6 +296,17 @@ reg [3:0] host_agc_attack; // Opcode 0x2A: gain-down step on clipping (de 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) +// AUDIT-C3: ADC format register (opcode 0x33). Selects DDC sign-conversion +// to match the AD9484 SCLK/DFS strap (jumper SJ1 on Main Board). +// 2'b00 = offset-binary (default; matches SJ1 pins 1-2 bridged) +// 2'b01 = two's-complement (SJ1 pins 2-3 bridged) +// 2'b1x = reserved (treated as offset-binary) +// CSB is hard-tied HIGH on the production Main Board so SPI cannot reconfigure +// the AD9484 — see RADAR_Main_Board.sch:46719. This register is the host's +// only path to align RTL with the physical strap without a board rework. +// Opcode 0x32 is reserved for the future S-25 fix (host-driven adc_pwdn). +reg [1:0] host_adc_format; + // Board bring-up self-test registers (opcode 0x30 trigger, 0x31 readback) reg host_self_test_trigger; // Opcode 0x30: self-clearing pulse wire self_test_busy; @@ -578,6 +589,8 @@ radar_receiver_final rx_inst ( // Ground clutter removal .host_mti_enable(host_mti_enable), .host_dc_notch_width(host_dc_notch_width), + // AUDIT-C3: ADC format select (opcode 0x33) -> DDC sign-conversion + .host_adc_format(host_adc_format), // ADC debug tap (for self-test / bring-up) .dbg_adc_i(rx_dbg_adc_i), .dbg_adc_q(rx_dbg_adc_q), @@ -994,6 +1007,8 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin host_agc_holdoff <= 4'd4; // 4 frames before gain-up // Self-test defaults host_self_test_trigger <= 1'b0; // Self-test idle + // AUDIT-C3: ADC format default (offset-binary matches SJ1 default) + host_adc_format <= 2'b00; end else begin host_trigger_pulse <= 1'b0; // Self-clearing pulse host_status_request <= 1'b0; // Self-clearing pulse @@ -1046,6 +1061,9 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin 8'h30: host_self_test_trigger <= 1'b1; // Trigger self-test 8'h31: host_status_request <= 1'b1; // Self-test readback (status alias) // 0x31: readback handled via status mechanism (latched results) + // AUDIT-C3: ADC format select (matches AD9484 SCLK/DFS strap SJ1). + // 0x32 reserved for S-25 (adc_pwdn host control); using 0x33 here. + 8'h33: host_adc_format <= usb_cmd_value[1:0]; 8'hFF: host_status_request <= 1'b1; // Gap 2: status readback default: ; endcase diff --git a/9_Firmware/9_2_FPGA/tb/tb_ddc_400m.v b/9_Firmware/9_2_FPGA/tb/tb_ddc_400m.v index b974e34..5c12296 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_ddc_400m.v +++ b/9_Firmware/9_2_FPGA/tb/tb_ddc_400m.v @@ -14,6 +14,7 @@ module tb_ddc_400m; reg [7:0] adc_data; reg adc_data_valid_i; reg adc_data_valid_q; + reg [1:0] adc_format; // AUDIT-C3: ADC sign-conversion select wire signed [17:0] baseband_i; wire signed [17:0] baseband_q; wire baseband_valid_i; @@ -53,6 +54,7 @@ module tb_ddc_400m; .adc_data (adc_data), .adc_data_valid_i (adc_data_valid_i), .adc_data_valid_q (adc_data_valid_q), + .adc_format (adc_format), .baseband_i (baseband_i), .baseband_q (baseband_q), .baseband_valid_i (baseband_valid_i), @@ -99,6 +101,7 @@ module tb_ddc_400m; adc_data = 0; adc_data_valid_i = 0; adc_data_valid_q = 0; + adc_format = 2'b00; // Default offset-binary test_mode = 2'b00; test_phase_inc = 0; force_saturation = 0; @@ -221,6 +224,84 @@ module tb_ddc_400m; $display(" debug_sample_count = %0d", debug_sample_count); check(debug_sample_count > 0, "Sample counter increments"); + // ════════════════════════════════════════════════════════ + // TEST GROUP 5: AUDIT-C3 — ADC format selection + // + // Exercises the new opcode-0x33 path that picks offset-binary or 2C + // sign-conversion to match the AD9484 SCLK/DFS strap (SJ1) on the + // Main Board. Probes adc_signed_w via hierarchical reference because + // the wire is internal to the DUT. + // + // Expected pre-DSP values (MIXER_WIDTH=18, ADC_WIDTH=8): + // format=00 (offset-binary), adc=0x80 -> +256 (mid-scale ≈ 0V) + // format=00 (offset-binary), adc=0x00 -> -65280 (full negative) + // format=00 (offset-binary), adc=0xFF -> +65280 (full positive) + // format=01 (2's-complement), adc=0x00 -> 0 (mid-scale 0V) + // format=01 (2's-complement), adc=0x80 -> -65536 (full negative) + // format=01 (2's-complement), adc=0x7F -> +65024 (full positive) + // ════════════════════════════════════════════════════════ + $display("\n--- Test Group 5: AUDIT-C3 ADC format selection ---"); + reset_n = 0; + adc_data_valid_i = 0; + adc_data_valid_q = 0; + repeat (10) @(posedge clk_400m); + reset_n = 1; + repeat (5) @(posedge clk_400m); + + // Offset-binary mid-scale (adc=0x80) + adc_format = 2'b00; + repeat (5) @(posedge clk_400m); // 2-FF sync settle + adc_data = 8'h80; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === 18'sd256, + "format=00 adc=0x80 -> +256 (offset-binary mid-scale)"); + + // Offset-binary full negative (adc=0x00) + adc_data = 8'h00; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === -18'sd65280, + "format=00 adc=0x00 -> -65280 (offset-binary min)"); + + // Offset-binary full positive (adc=0xFF) + adc_data = 8'hFF; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === 18'sd65280, + "format=00 adc=0xFF -> +65280 (offset-binary max)"); + + // Switch to 2's-complement and let the synchronizer settle + adc_format = 2'b01; + repeat (5) @(posedge clk_400m); + + // 2's-complement mid-scale (adc=0x00) + adc_data = 8'h00; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === 18'sd0, + "format=01 adc=0x00 -> 0 (2's-complement mid-scale)"); + + // 2's-complement full negative (adc=0x80) + adc_data = 8'h80; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === -18'sd65536, + "format=01 adc=0x80 -> -65536 (2's-complement min)"); + + // 2's-complement full positive (adc=0x7F) + adc_data = 8'h7F; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === 18'sd65024, + "format=01 adc=0x7F -> +65024 (2's-complement max)"); + + // Reserved 2'b1x must fall back to offset-binary + adc_format = 2'b10; + repeat (5) @(posedge clk_400m); + adc_data = 8'h80; + @(posedge clk_400m); #1; + check(uut.adc_signed_w === 18'sd256, + "format=10 (reserved) -> offset-binary fallback"); + + // Restore default for any later tests + adc_format = 2'b00; + repeat (5) @(posedge clk_400m); + // ════════════════════════════════════════════════════════ // Summary // ════════════════════════════════════════════════════════ diff --git a/9_Firmware/9_2_FPGA/tb/tb_ddc_cosim.v b/9_Firmware/9_2_FPGA/tb/tb_ddc_cosim.v index 2ca485b..8490bc3 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_ddc_cosim.v +++ b/9_Firmware/9_2_FPGA/tb/tb_ddc_cosim.v @@ -88,6 +88,7 @@ module tb_ddc_cosim; .adc_data (adc_data), .adc_data_valid_i (adc_data_valid), .adc_data_valid_q (adc_data_valid), + .adc_format (2'b00), // AUDIT-C3: offset-binary (cosim baseline) .baseband_i (baseband_i), .baseband_q (baseband_q), .baseband_valid_i (baseband_valid_i), diff --git a/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v b/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v index 224847d..2747c30 100644 --- a/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v +++ b/9_Firmware/9_2_FPGA/tb/tb_radar_receiver_final.v @@ -173,6 +173,8 @@ radar_receiver_final dut ( // Fix 3: digital gain control — pass-through for golden reference .host_gain_shift(4'd0), + // AUDIT-C3: ADC format select — offset-binary baseline + .host_adc_format(2'b00), // CFAR: frame-complete output (not used in this TB) .doppler_frame_done_out() ); diff --git a/9_Firmware/9_3_GUI/radar_protocol.py b/9_Firmware/9_3_GUI/radar_protocol.py index 69e1d2d..26b81ec 100644 --- a/9_Firmware/9_3_GUI/radar_protocol.py +++ b/9_Firmware/9_3_GUI/radar_protocol.py @@ -62,6 +62,8 @@ class Opcode(IntEnum): 0x11 host_long_listen_cycles 0x28-0x2C AGC control 0x12 host_guard_cycles 0x30 host_self_test_trigger 0x13 host_short_chirp_cycles 0x31/0xFF host_status_request + 0x33 host_adc_format (AD9484 SCLK/DFS strap; AUDIT-C3) + (0x32 reserved for the future S-25 adc_pwdn host-control fix) """ # --- Basic control (0x01-0x04) --- RADAR_MODE = 0x01 # 2-bit mode select @@ -102,6 +104,15 @@ class Opcode(IntEnum): SELF_TEST_STATUS = 0x31 STATUS_REQUEST = 0xFF + # --- AD9484 ADC sign-convention (0x33, AUDIT-C3) --- + # 2'b00 = offset-binary (default; SJ1 jumper pins 1-2 bridged) + # 2'b01 = two's-complement (SJ1 jumper pins 2-3 bridged) + # AD9484 CSB is hard-tied HIGH on the Main Board (SPI unavailable); + # this opcode lets the host adapt the DDC to the physical strap + # without rebuilding the bitstream. + # (Opcode 0x32 is reserved for the future AUDIT-S25 adc_pwdn fix.) + ADC_FORMAT = 0x33 + # ============================================================================ # Data Structures