nRF54L15 (Nordic ultra-low-power wireless SoC) — application core
The nRF54L15 pairs a 128 MHz Cortex-M33 application core with a RISC-V PPR (peripheral processor) and FLPR (fast lightweight processor), plus a Bluetooth 5.4 radio. This LabWired profile models the application core — the half that runs user firmware.
Two things make this part different from every Nordic device already onboarded, and both are places where a wrong profile fails silently rather than loudly:
- NVM is RRAM, not flash. It is based at
0x0and is 1524 KB — an odd number that is correct, not a typo to be rounded to 1.5 MB. There is no NVMC; there is an RRAM controller at0x5004_B000. - Timekeeping is the GRTC (Global RTC), not RTC0/RTC1. The nRF52 RTC model is deliberately not mapped on this part.
The address map is otherwise conventional for a modern Nordic device: every
peripheral sits behind one devicetree node with
ranges = <0x0 0x5000_0000 0x1000_0000>, so a DT reg = <0xc6000> is absolute
0x500C_6000. 0x5000_0000 is the secure alias and is the devicetree
default (0x4000_0000 is the non-secure alias, selected by
USE_NON_SECURE_ADDRESS_MAP). This profile maps the secure view. FICR, UICR and
the RRAM controller are outside that window at their own absolute addresses.
Status at a glance
| Aspect | Status |
|---|---|
| Chip yaml | configs/chips/nrf54l15.yaml |
| System yaml | configs/systems/nrf54l15dk.yaml |
| Reference firmware | examples/nrf54l15-dk/ (bare-metal C, no SDK) |
| Validation | tests/nrf54l15_boot.rs (13) + firmware_survival::test_nrf54l15_smoke_survival + test_nrf54l15_lights_dk_led0 |
| Tier | sim-validated, bare-metal only — no Zephyr boot yet, no silicon diff |
What is proven
A bare-metal C firmware, built with arm-none-eabi-gcc and no vendor SDK, no
CMSIS and no Zephyr, boots end to end:
- reset vector at RRAM
0x0; initial SP0x2004_0000(256 KB SRAM) .datacopy out of RRAM,.bsszero- GPIO P2: DIRSET/OUTSET drive DK LED0 (P2.09) — asserted at the pin, not just assumed
- UARTE20 EasyDMA TX at 115200 → console sink, banner captured
Deliberately dependency-free: the less code between reset and the first UARTE byte, the more precisely a failure localises to the simulator rather than to a vendor HAL.
What is NOT proven
Stated plainly, because a fidelity table that only lists successes is marketing:
- No silicon diff. Nothing here has been compared against a real nRF54L15
over SWD. Every "✅ behavioural" below means the model satisfies the firmware
we have run, not the model matches silicon. The board is classified
NOT_SHIPPED with
reset_oracle: Nonefor exactly this reason. - The radio RF/PHY is not modelled — no 2.4 GHz link, no BLE link-layer, no 802.15.4. What is modelled is the CPU-time contention a BLE connection event causes: the smart-ring demo schedules a periodic SysTick "connection event" ISR that busy-holds the core for an event window, and because the TWIM advances each sensor's sample clock on real (GRTC) time, a PPG drain that falls behind during the event overflows its FIFO exactly as silicon would. This reproduces the digital/timing class of the "PPG unreliable under BLE load" failure (CPU starvation + poll jitter). It does not model the analog class — radio-TX current spikes coupling into the PPG analog front-end — which a digital simulator cannot represent.
- The PPR and FLPR coprocessors are not modelled; this is the M33 only.
What is proven: unmodified upstream Zephyr v4.4 boots byte-exactly
(test_nrf54l15_zephyr_survival), and the realistic ring BOM (BMI270 IMU with HW
step counter, MAX30102 PPG, TMP117 skin temp, DRV2605 haptic, 1-channel GPIO
touch) enumerates and runs the Part-2 sensor manager end to end.
Peripherals (from chip yaml)
| Peripheral | Base (secure) | Status | Notes |
|---|---|---|---|
| Cortex-M33 | — | ✅ modeled | Application core only; PPR/FLPR not modeled |
| UARTE20 (console) | 0x500C_6000 | ✅ behavioural | EasyDMA TX → console sink (shared nRF UARTE model) |
| UARTE30 | 0x5010_4000 | ✅ behavioural | Same model |
| TIMER10 | 0x5008_5000 | ✅ behavioural | cc-num = 8 (not the nRF52 default of 4) |
| TIMER20/21/22 | 0x500C_A000 … | ✅ behavioural | cc-num = 6 |
| GPIO P0 / P1 / P2 | 0x5010_A000 / 0x500D_8200 / 0x5005_0400 | ✅ modeled | nRF54L register layout (OUT +0x000, PIN_CNF +0x080). Widths differ: 7 / 17 / 11 pins. See the base-address note below. |
| TEMP | 0x500D_7000 | ✅ behavioural | Die temperature; nRF52-compatible layout |
| EGU10 / EGU20 | 0x5008_7000 / 0x500C_9000 | ✅ behavioural | Shared nRF EGU model |
| GPIOTE20 / GPIOTE30 | 0x500D_A000 / 0x5010_C000 | ✅ behavioural | Shared nRF GPIOTE model |
| GRTC | 0x500E_2000 | ⚠️ see below | Global RTC — the part's primary timekeeper |
| CLOCK / POWER | 0x5010_E000 | ⚪ benign stub | nRF54L oscillator control is not nRF52-compatible, so the nRF52 CLOCK model is deliberately not reused. A bare-metal boot runs from the default HFINT and never touches it. |
| RRAMC | 0x5004_B000 | ⚪ benign stub | Firmware executing from RRAM never needs it |
| Regulators | 0x5012_0000 | ⚪ benign stub | Write-only on the boot path |
| DPPIC20 / DPPIC30 | 0x500C_2000 / 0x5010_2000 | ⚪ benign stub | Event fabric; poked but not polled on a UART boot |
| FICR / UICR | 0x00FF_C000 / 0x00FF_D000 | ⚪ benign stub | Outside the peripheral window; unprovisioned UICR reads 0 |
| RADIO (BLE / 802.15.4) | 0x5008_A000 | ❌ not mapped | RF/PHY not modelled; BLE CPU-time contention is (SysTick event ISR + wall-clock TWIM) |
| SysTick | 0xE000_E010 (SCS) | ✅ modeled | ARMv8-M core timer; raises exception 15. configure_cortex_m omits it, so declared in the profile |
The GPIO base-address trap
Worth its own section, because it cost real debugging time and it is the single most likely mistake when extending this profile.
Nordic changed the GPIO port struct every generation. On the nRF54L family the
MDK NRF_GPIO_Type has no RESERVED prefix: OUT at +0x000, DIRSET at
+0x014 and PIN_CNF[n] at +0x080 + 4n. The devicetree's gpio2@50400
(absolute 0x5005_0400) is therefore the block base itself, and the ports are
declared with profile: "nrf54l" at those MDK/SVD bases.
Do not "simplify" them back to profile: "nrf52" with a -0x504 base: this
block moved OUT by 0x504 but PIN_CNF by 0x680 (0x700 → 0x080), so
no single constant shift delivers both. The old arrangement lit LEDs — they
need only DIR and OUT — while dropping every PIN_CNF access, and PIN_CNF
is the only register through which Zephyr/nrfx configure an input's pull-up.
The DK's four buttons are all GPIO_PULL_UP, so the old arrangement lost the
pull configuration itself — this model stores PIN_CNF but derives no idle level
from it. A press still reads because board_io drives the input latch directly;
the fidelity gap is the pull, not the button.
Map the old nRF52 offsets directly instead and every GPIO register sits 0x500
too high. Either way the failure is silent: the UART still works, the board
still boots, the banner still prints, and a UART-only survival test still
passes. Only the pins never move.
This is exactly why test_nrf54l15_lights_dk_led0 asserts DIR and OUT at
the pin after booting the real ELF, rather than trusting the banner, and why
nrf54l_gpio_offsets.rs pins down the compacted register map against the
constant-shift control that drops PIN_CNF.
The same class of bug was found and fixed in configs/chips/nrf5340.yaml during
this onboarding — see commit fix(nrf5340): GPIO peripheral bases were 0x500
too high — and shaped configs/chips/nrf54lm20a.yaml, whose P1/P3 are only
0x400 apart so a back-offset window would overlap its neighbour.
Board I/O (nRF54L15-DK)
From the Zephyr board devicetree
boards/nordic/nrf54l15dk/nrf54l15dk_common.dtsi. Note the LEDs are split
across two ports, which is what makes the per-port ngpios values load-bearing.
| Signal | Port.pin | Polarity |
|---|---|---|
| LED0 | P2.09 | active high |
| LED1 | P1.10 | active high |
| LED2 | P2.07 | active high |
| LED3 | P1.14 | active high |
| Button0 | P1.13 | active low, pull-up |
| Button1 | P1.09 | active low, pull-up |
| Button2 | P1.08 | active low, pull-up |
| Button3 | P0.04 | active low, pull-up |
Sources
- Zephyr devicetree
dts/vendor/nordic/nrf54l_05_10_15.dtsiandnrf54l15.dtsi— address map, IRQ numbers,ngpios,cc-num, memory sizes. This is the exact view firmware is linked against, which is the same sourcing rule used fornrf5340.yaml. - Zephyr board devicetree
boards/nordic/nrf54l15dk/nrf54l15dk_common.dtsi— LED/button pin map. - nRF54L15 Product Specification — cross-check of the memory map.
Next steps
In the order I would do them:
- Promote the GRTC to a full behavioural model and boot unmodified Zephyr
samples/hello_worldfornrf54l15dk/nrf54l15/cpuapp. That is the step that moves this profile to the same tier as nRF5340. - TWIM/SPIM personalities.
uart20/spi20/twi20are one instance at0x500C_6000in three personalities; only UARTE is mapped. A sensor-carrying system manifest needs TWIM on a different instance (e.g.twi21at0x500C_7000). - Silicon diff over SWD, which is the only thing that turns "✅ behavioural" into a validated claim.
- RADIO, PPR/FLPR — large, and only worth it with a concrete firmware driving them.