SoftDevice Controller assert during DTM receiver test at dead/marginal RF link (nRF52833)

# SoftDevice Controller assert during DTM receiver test at dead/marginal RF link (nRF52833)

## Summary
On an nRF52833, running the nRF Connect SDK Direct Test Mode (DTM) 2-wire sample,
starting an **LE receiver test** on a **dead or near-dead RF link** intermittently
triggers an **internal SoftDevice Controller (SDC) assertion raised from the RADIO
interrupt**. The assertion escalates to a Zephyr kernel panic / HardFault. With the
stock configuration the device then halts forever and stops servicing the 2-wire
UART (it does not even answer `RESET`) until it is power-cycled.

The **same firmware source on an nRF52840-DK never reproduces** this under identical
host/RF conditions, which points to an nRF52833-specific SDC/silicon issue rather
than an application bug.

We have a working application-level **recovery** (reset-on-fatal, see "Workaround"
below), but it does **not prevent** the SDC assert — it only turns the permanent
wedge into an automatic ~4 ms reboot. This report is to ask Nordic to identify and
fix the underlying assert.

## Environment
| Item | Value |
|------|-------|
| SoC | nRF52833 (QIAA / `nRF52833_xxAA`), Cortex-M4 |
| Reference device (no repro) | nRF52840-DK |
| nRF Connect SDK | `v3.4.99-481b5685488a` (west manifest `sdk-nrf` @ `main`) |
| Zephyr | `v4.4.0-b8e6b9b3ed1f` |
| SDC build revision | `84 d6 38 af 90 80 24 b8 90 a2 fa 5f 15 8f ac 7c b2 ed 6d dc` |
| Sample | `samples/bluetooth/direct_test_mode` (2-wire UART -> HCI -> SDC), vendored |
| Transport | 2-wire UART, 8N1, 19200 baud |
| PHY under test | LE 1M |

The application is the standard DTM 2-wire firmware: `main.c` reads a 2-wire word,
`lib/dtm_twowire/dtm_twowire_to_hci.c` translates it to an HCI command, `bt_send()`
hands it to the SDC, and the main loop waits for the HCI event.

## Symptom (precise)
While the device is executing an **LE receiver test** (`HCI_LE_Receiver_Test`) at a
dead/marginal link, an SDC-internal assertion fires **inside the RADIO ISR**,
roughly tens of milliseconds into the receive window. The assertion handler
(`hci_driver.c:363`, `sdc_assertion_handler()`) prints:

```
ASSERTION FAIL [0] @ WEST_TOPDIR/nrf/subsys/bluetooth/controller/hci_driver.c:363
    SoftDevice Controller ASSERT: 0, 0x0c5a
```

i.e. **SDC assert file id `0`, line/code `0x0C5A` (3162 decimal)**. With
`CONFIG_ASSERT=y` this executes `__ASSERT(false, ...)`, which panics. Because the
context is an ISR, Zephyr escalates to a HardFault and (with the default handler)
halts:

```
<err> os: ***** HARD FAULT *****
<err> os:   Fault escalation (see below)
<err> os: ARCH_EXCEPT with reason 4                 (K_ERR_KERNEL_PANIC)
<err> os:  xpsr:  0x01000011                          (IPSR = 17 -> RADIO_IRQn, IRQ #1)
<err> os: Faulting instruction address (r15/pc): 0x000222da   (assert_post_action)
<err> os: >>> ZEPHYR FATAL ERROR 4: Kernel panic on CPU 0
<err> os: Fault during interrupt handling
<err> os: Halting system
```

## On-target evidence (J-Link)
Freezing the wedged target (before any recovery workaround) showed the CPU parked
in the fatal-halt spin with interrupts disabled — confirming a crash, not a blocked
wait:

- `PC = arch_system_halt` (`zephyr/kernel/fatal.c`), infinite loop, `PRIMASK=1`.
- `LR = k_sys_fatal_error_handler` (default weak handler).
- Halted in **HardFault** (exception #3), `HFSR.FORCED=1` (escalation from the
  `ARCH_EXCEPT` raised inside the SDC assert).
- The exception stack frame's pre-fault `xPSR=0x01000011` => `IPSR=17` =>
  **RADIO_IRQn** — the assert originates in the SDC radio interrupt.
- RADIO peripheral at the freeze: `STATE=DISABLED`, `RSSISAMPLE≈0x56` (~ -86 dBm,
  i.e. a dead link), `EVENTS_CRCOK=1` and `EVENTS_CRCERROR=1` both latched,
  `SHORTS=0x2`, `INTENSET=0x10`.

## Reproduction
Two nRF devices; the nRF52833 is the DUT/receiver, a second device is the reference
transmitter. Drive both over 2-wire DTM. The trigger is the DUT running a **receiver
test with essentially no usable signal**.

Minimal DUT 2-wire sequence (as observed on the wire):

```
0x0000  RESET                              -> 0x0000
0x0204  TEST_SETUP: SET_PHY = LE 1M        -> 0x0000
0x09C4  TEST_SETUP: SET TX POWER = -60 dBm -> 0x03B0   (via HCI LE_Read_TX_Power)
0x4000  RECEIVER_TEST, channel 0           -> 0x0000   (RX starts OK)
        ... ~34 ms into the receive window: SDC assert in RADIO ISR -> crash ...
0xC000  TEST_END                           -> (no response; CPU already halted)
```

Conditions:
- Requested TX power -60 dBm on both ends (both clamp to the SoC minimum ~ -40 dBm),
  over a heavily attenuated / effectively dead RF path (channel 0 / 2402 MHz).
- **Intermittent and RF-dependent.** Over 40 receiver-test rounds we saw the assert
  fire on **35** of them; **2** rounds completed a full RX with real packet counts
  (472, 494); the rest were dead. A metal object placed over the DUT antenna makes
  it fire much more readily (even at otherwise strong signal).

## Ruled out (not the cause)
- Host tooling / 2-wire command encoding: reproduces with both a custom 2-wire host
  tool and the official nRF Connect DTM app; the 2-wire words are byte-identical.
- Host command ordering / timing: varying the stop order and adding settle delays
  does not prevent it.
- The nRF52840-DK running the same firmware source never wedges under identical
  conditions.

## Application-level workaround (recovery only — does not fix the assert)
To stop the permanent wedge we enabled auto-reset on fatal error so the device recovers 
by itself in ~4 ms instead of hanging:

- `CONFIG_RESET_ON_FATAL_ERROR=y` (NCS `lib/fatal_error`) — `k_sys_fatal_error_handler`
  resets the SoC instead of halting.

## What we would like Nordic to investigate
1. What does SDC assert **file id 0, line/code `0x0C5A` (3162)** correspond to in this
   SDC build (`84 d6 38 af 90 80 24 b8 90 a2 fa 5f 15 8f ac 7c b2 ed 6d dc`)?
2. Why does an `HCI_LE_Receiver_Test` at a dead/marginal link assert in the RADIO ISR
   on **nRF52833** but not on **nRF52840** with the same SDC/DTM source?
3. Is there a known anomaly/errata or an SDC configuration/workaround that prevents
   this assert (so a dead-link receiver test simply reports 0 packets instead of
   faulting)?

Related