From e2cb7bd2d6e3da2e31f8495fc1b107fe3e118a03 Mon Sep 17 00:00:00 2001 From: Jason <83615043+JJassonn69@users.noreply.github.com> Date: Tue, 5 May 2026 12:12:04 +0545 Subject: [PATCH] =?UTF-8?q?fix(fpga):=20PR-X.1=20F-7.6=20=E2=80=94=20corre?= =?UTF-8?q?ct=20offset-binary=20mid-scale=20conversion=20in=20DDC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stage-7 ADC chain audit. ddc_400m.v line 273-274 converted AD9484 offset-binary 8-bit codes to MIXER_WIDTH-bit signed via `(adc << 9) - (8'hFF << 8)`. The right operand evaluates to `0xFF / 2 = 0x7F` (integer divide drops 0.5), so the formula subtracted 65280 instead of the correct mid-scale offset 65536: - code 0x80 (0V analog, mid-scale) → +256 (expected 0) - code 0x00 (-fs) → -65280 (expected -65536) - code 0xFF (+fs - 1 LSB) → +65280 (expected +65024) A 0.5-LSB DC bias on the AD9484 stream is functionally invisible in production: after the 120 MHz NCO mixer it lands at ±120 MHz, which the CIC + FIR LPF (~50 MHz rolloff) rejects. But the offbin and twoc paths disagreed on the same analog input, and any reuse of `adc_signed_offbin` outside the mixer chain would carry the bias. Replace with an MSB-flip + sign-extend + 9-zero pad that mirrors the twoc branch: `{~msb, ~msb, low7, 9'b0}`. Mid-scale code 0x80 now produces 0 exactly, matching the twoc path bit-for-bit. Local FPGA regression 36/1/6 unchanged (1 FAIL = pre-existing T-6 drift, deferred PR-M.4). --- 9_Firmware/9_2_FPGA/ddc_400m.v | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/9_Firmware/9_2_FPGA/ddc_400m.v b/9_Firmware/9_2_FPGA/ddc_400m.v index 1abbf5d..3793310 100644 --- a/9_Firmware/9_2_FPGA/ddc_400m.v +++ b/9_Firmware/9_2_FPGA/ddc_400m.v @@ -270,8 +270,18 @@ 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; +// Audit F-7.6: AD9484 offset-binary → signed two's-complement is just an +// MSB flip. Place the converted 8-bit signed value into the top of the +// MIXER_WIDTH-bit field with one leading sign-extension copy and the +// usual 9-zero LSB pad — exactly mirrors the twoc branch below. The +// previous formula `(adc<<9) - (8'hFF<<8)` used `0xFF/2 = 0x7F` (integer +// divide drops 0.5 LSB), which produced +256 at mid-scale code 0x80 +// instead of 0 and made the offbin and twoc paths disagree on the same +// analog input. Functionally invisible in production (120 MHz IF mix + +// LPF rejects the 0.5-LSB DC tone) but mathematically wrong. +assign adc_signed_offbin = {~adc_data[ADC_WIDTH-1], ~adc_data[ADC_WIDTH-1], + adc_data[ADC_WIDTH-2:0], + {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}}; 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)