fix(pre-bringup): resolve P0 + quick-win P1 findings from 2026-04-19 audit

Addresses findings from docs/DEVELOP_AUDIT_2026-04-19.md:

P0 source-level:
- F-4.3 ADAR1000_Manager::adarSetTxPhase now writes REG_LOAD_WORKING
  with LD_WRK_REGS_LDTX_OVERRIDE (0x02) instead of 0x01. Previous value
  toggled the LDRX latch on a TX-phase write, so host TX phase updates
  never reached the working registers.
- F-6.1 DDC mixer_saturation / filter_overflow / diagnostics were deleted
  at the receiver boundary. Now plumbed to new outputs on
  radar_receiver_final (ddc_overflow_any, ddc_saturation_count) and
  aggregated into gpio_dig5 in radar_system_top. Added mark_debug
  attributes for ILA visibility. Test/debug inputs tied low explicitly.
- F-0.8 adc_clk_mmcm.xdc set_clock_uncertainty: removed invalid -add
  flag (Vivado silently rejected it, applying zero guardband). Now uses
  absolute 0.150 ns which covers 53 ps jitter + ~100 ps PVT margin.

P1:
- F-4.2 adarSetBit / adarResetBit reject broadcast=ON — the RMW sampled
  a single device but wrote to all four, clobbering the other three's
  state.
- F-4.4 initializeSingleDevice returns false and leaves initialized=false
  when scratchpad verification fails; previously marked the device
  initialized anyway so downstream PA enable could drive a dead bus.
- F-6.2 FIR I/Q filter_overflow ports, previously unconnected, now OR'd
  into the module-level filter_overflow output.
- F-6.3 mti_canceller exposes 8-bit saturation counter. Saturation was
  previously invisible and produces spurious Doppler harmonics.

Verification:
- 27/27 iverilog testbenches pass
- 228/228 pytest pass (cross-layer contract + cosim)
- MCU unit tests 51/51 + 24/24 pass
- Remote Vivado 2025.2 build: bitstream writes; 400 MHz mixer pipeline
  now shows WNS -0.109 ns which MATCHES the audit's F-0.9 prediction
  that the design only closed because F-0.8's guardband was silently
  dropped. ft_clkout F-0.9 remains a show-stopper (requires MRCC pin
  move), tracked separately.

Not addressed in this PR (larger scope, follow-up tickets):
F-0.4, F-0.5, F-0.6, F-0.7, F-0.9, F-1.1, F-1.2, F-2.2, F-3.2, F-4.1,
F-4.7, F-6.4, F-6.5.
This commit is contained in:
Jason
2026-04-20 13:48:36 +05:45
parent c82b25f7a0
commit 3f47d1ef71
6 changed files with 190 additions and 92 deletions
@@ -10,15 +10,15 @@ extern SPI_HandleTypeDef hspi1;
extern UART_HandleTypeDef huart3;
// Chip Select GPIO definitions
static const struct {
GPIO_TypeDef* port;
uint16_t pin;
} CHIP_SELECTS[4] = {
{ADAR_1_CS_3V3_GPIO_Port, ADAR_1_CS_3V3_Pin}, // ADAR1000 #1
{ADAR_2_CS_3V3_GPIO_Port, ADAR_2_CS_3V3_Pin}, // ADAR1000 #2
{ADAR_3_CS_3V3_GPIO_Port, ADAR_3_CS_3V3_Pin}, // ADAR1000 #3
{ADAR_4_CS_3V3_GPIO_Port, ADAR_4_CS_3V3_Pin} // ADAR1000 #4
};
static const struct {
GPIO_TypeDef* port;
uint16_t pin;
} CHIP_SELECTS[4] = {
{ADAR_1_CS_3V3_GPIO_Port, ADAR_1_CS_3V3_Pin}, // ADAR1000 #1
{ADAR_2_CS_3V3_GPIO_Port, ADAR_2_CS_3V3_Pin}, // ADAR1000 #2
{ADAR_3_CS_3V3_GPIO_Port, ADAR_3_CS_3V3_Pin}, // ADAR1000 #3
{ADAR_4_CS_3V3_GPIO_Port, ADAR_4_CS_3V3_Pin} // ADAR1000 #4
};
// ADAR1000 Vector Modulator lookup tables (128-state phase grid, 2.8125 deg step).
//
@@ -255,15 +255,15 @@ bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction)
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
for (uint8_t ch = 0; ch < 4; ++ch) {
if (direction == BeamDirection::TX) {
adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
adarSetTxVgaGain(dev, ch + 1, kDefaultTxVgaGain, BROADCAST_OFF);
} else {
adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
adarSetRxVgaGain(dev, ch + 1, kDefaultRxVgaGain, BROADCAST_OFF);
}
}
}
if (direction == BeamDirection::TX) {
adarSetTxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
adarSetTxVgaGain(dev, ch + 1, kDefaultTxVgaGain, BROADCAST_OFF);
} else {
adarSetRxPhase(dev, ch + 1, phase_settings[ch], BROADCAST_OFF);
adarSetRxVgaGain(dev, ch + 1, kDefaultRxVgaGain, BROADCAST_OFF);
}
}
}
return true;
}
@@ -433,10 +433,15 @@ bool ADAR1000Manager::initializeSingleDevice(uint8_t deviceIndex) {
adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN, BROADCAST_OFF);
// Verify communication with scratchpad test
// Audit F-4.4: on SPI failure, previously marked the device initialized
// anyway, so downstream (e.g. PA enable) could drive PA gates out-of-spec
// on a dead bus. Now propagate the failure so initializeAllDevices aborts.
DIAG("BF", " dev[%u] verifying SPI communication...", deviceIndex);
bool comms_ok = verifyDeviceCommunication(deviceIndex);
if (!comms_ok) {
DIAG_WARN("BF", " dev[%u] scratchpad verify FAILED but marking initialized anyway", deviceIndex);
DIAG_ERR("BF", " dev[%u] scratchpad verify FAILED -- device NOT marked initialized", deviceIndex);
devices_[deviceIndex]->initialized = false;
return false;
}
devices_[deviceIndex]->initialized = true;
@@ -522,7 +527,7 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
HAL_UART_Transmit(&huart3, success, sizeof(success) - 1, 1000);
return true;
}
}
bool ADAR1000Manager::setAllDevicesTXMode() {
DIAG("BF", "setAllDevicesTXMode(): ADTR1107 -> TX, then configure ADAR1000s");
@@ -648,7 +653,7 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
}
DIAG("BF", " ADTR1107 RX mode complete");
}
}
}
void ADAR1000Manager::setADTR1107Control(bool tx_mode) {
DIAG("BF", "setADTR1107Control(%s): setting TR switch on all %u devices, settling %lu us",
@@ -727,7 +732,7 @@ void ADAR1000Manager::setLNABias(bool enable) {
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
adarWrite(dev, REG_LNA_BIAS_ON, lna_bias, BROADCAST_OFF);
}
}
}
void ADAR1000Manager::delayUs(uint32_t microseconds) {
// Simple implementation - for F7 @ 216MHz, each loop ~7 cycles ≈ 0.032us
@@ -835,12 +840,26 @@ uint8_t ADAR1000Manager::adarRead(uint8_t deviceIndex, uint32_t mem_addr) {
}
void ADAR1000Manager::adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
// Audit F-4.2: broadcast-RMW is unsafe. The read samples a single device
// but the write fans out to all four, overwriting the other three with
// deviceIndex's state. Reject and surface the mistake.
if (broadcast == BROADCAST_ON) {
DIAG_ERR("BF", "adarSetBit: broadcast RMW is unsafe, ignored (dev=%u addr=0x%03lX bit=%u)",
deviceIndex, (unsigned long)mem_addr, bit);
return;
}
uint8_t temp = adarRead(deviceIndex, mem_addr);
uint8_t data = temp | (1 << bit);
adarWrite(deviceIndex, mem_addr, data, broadcast);
}
void ADAR1000Manager::adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
// Audit F-4.2: see adarSetBit.
if (broadcast == BROADCAST_ON) {
DIAG_ERR("BF", "adarResetBit: broadcast RMW is unsafe, ignored (dev=%u addr=0x%03lX bit=%u)",
deviceIndex, (unsigned long)mem_addr, bit);
return;
}
uint8_t temp = adarRead(deviceIndex, mem_addr);
uint8_t data = temp & ~(1 << bit);
adarWrite(deviceIndex, mem_addr, data, broadcast);
@@ -904,7 +923,7 @@ void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
}
void ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
@@ -929,11 +948,11 @@ void ADAR1000Manager::adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uin
adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
}
void ADAR1000Manager::adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast) {
adarWrite(deviceIndex, REG_BIAS_CURRENT_TX, kTxBiasCurrent, broadcast);
adarWrite(deviceIndex, REG_BIAS_CURRENT_TX_DRV, kTxDriverBiasCurrent, broadcast);
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x2, broadcast);
}
void ADAR1000Manager::adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast) {
adarWrite(deviceIndex, REG_BIAS_CURRENT_TX, kTxBiasCurrent, broadcast);
adarWrite(deviceIndex, REG_BIAS_CURRENT_TX_DRV, kTxDriverBiasCurrent, broadcast);
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x2, broadcast);
}
uint8_t ADAR1000Manager::adarAdcRead(uint8_t deviceIndex, uint8_t broadcast) {
adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_ST_CONV, broadcast);