Skip to content

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:

  1. NVM is RRAM, not flash. It is based at 0x0 and 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 at 0x5004_B000.
  2. 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 (7) + 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 SP 0x2004_0000 (256 KB SRAM)
  • .data copy out of RRAM, .bss zero
  • 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: None for 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_9B00 / 0x500D_7D00 / 0x5004_FF00 ✅ modeled 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.

A Nordic GPIO devicetree node points at the OUT register, which lives at peripheral_base + 0x500 — not at the peripheral base. The DT gives gpio2@50400 (absolute 0x5005_0400), so the peripheral base is 0x5004_FF00. The gpio model uses peripheral-relative offsets (OUT 0x504, OUTSET 0x508, IN 0x510, DIR 0x514, DIRSET 0x518), so this subtraction is what makes a firmware write to the DT address land on the right register.

Map the DT address directly and every GPIO register sits 0x500 too high. The UART still works. The board still boots. The banner still prints. 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.

The identical bug was found and fixed in configs/chips/nrf5340.yaml during this onboarding — see commit fix(nrf5340): GPIO peripheral bases were 0x500 too high.

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.dtsi and nrf54l15.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 for nrf5340.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:

  1. Promote the GRTC to a full behavioural model and boot unmodified Zephyr samples/hello_world for nrf54l15dk/nrf54l15/cpuapp. That is the step that moves this profile to the same tier as nRF5340.
  2. TWIM/SPIM personalities. uart20/spi20/twi20 are one instance at 0x500C_6000 in three personalities; only UARTE is mapped. A sensor-carrying system manifest needs TWIM on a different instance (e.g. twi21 at 0x500C_7000).
  3. Silicon diff over SWD, which is the only thing that turns "✅ behavioural" into a validated claim.
  4. RADIO, PPR/FLPR — large, and only worth it with a concrete firmware driving them.