Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@
/samples/nrf5340/empty_app_core/ @nrfconnect/ncs-si-muffin
/samples/nrf5340/extxip_smp_svr/ @nrfconnect/ncs-eris
/samples/nrf54h20/empty_app_core/ @nrfconnect/ncs-aurora
/samples/nrf54h20/idle_relocated_tcm/ @nrfconnect/ncs-si-muffin
/samples/nrf54h20/radio_loader/ @nrfconnect/ncs-si-muffin
/samples/ironside_se/ @nrfconnect/ncs-aurora
/samples/nrf_compress/ @nordicjm
/samples/nrf_profiler/ @nrfconnect/ncs-si-bluebagel
Expand Down Expand Up @@ -646,6 +648,7 @@
/samples/net/**/*.rst @nrfconnect/ncs-cia-doc
/samples/net/coap_client/*.rst @nrfconnect/ncs-iot-oulu-tampere-doc
/samples/nfc/**/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/nrf54h20/idle_relocated_tcm/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/nrf5340/empty_app_core/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/nrf5340/extxip_smp_svr/*.rst @nrfconnect/ncs-eris-doc
/samples/nrf5340/netboot/*.rst @nrfconnect/ncs-eris-doc
Expand Down
13 changes: 13 additions & 0 deletions cmake/modules/kconfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ if(CONFIG_NCS_IS_VARIANT_IMAGE)
dt_partition_addr(code_partition_offset PATH "${code_partition}" REQUIRED)
dt_reg_size(code_partition_size PATH "${code_partition}" REQUIRED)

# Needed for the CONFIG_BUILD_OUTPUT_ADJUST_LMA calculation.
dt_partition_addr(code_partition_abs_addr PATH "${code_partition}" REQUIRED ABSOLUTE)
dt_chosen(sram_property PROPERTY "zephyr,sram")
dt_reg_addr(sram_addr PATH "${sram_property}" REQUIRED)

set(preload_autoconf_h ${PRELOAD_BINARY_DIR}/zephyr/include/generated/zephyr/autoconf.h)
set(preload_dotconfig ${PRELOAD_BINARY_DIR}/zephyr/.config)

Expand All @@ -56,6 +61,10 @@ if(CONFIG_NCS_IS_VARIANT_IMAGE)
string(REGEX REPLACE "primary" "secondary" line ${line})
endif()

if("${line}" MATCHES "^CONFIG_BUILD_OUTPUT_ADJUST_LMA=.*$")
string(REGEX REPLACE "CONFIG_BUILD_OUTPUT_ADJUST_LMA=(.*)" "CONFIG_BUILD_OUTPUT_ADJUST_LMA=\"${code_partition_abs_addr}-${sram_addr}\"" line ${line})
endif()

list(APPEND dotconfig_variant_content "${line}\n")
endforeach()

Expand All @@ -73,6 +82,10 @@ if(CONFIG_NCS_IS_VARIANT_IMAGE)
string(REGEX REPLACE "primary" "secondary" line ${line})
endif()

if("${line}" MATCHES "^#define CONFIG_BUILD_OUTPUT_ADJUST_LMA .*$")
string(REGEX REPLACE "#define CONFIG_BUILD_OUTPUT_ADJUST_LMA (.*)" "#define CONFIG_BUILD_OUTPUT_ADJUST_LMA \"${code_partition_abs_addr}-${sram_addr}\"" line ${line})
endif()

list(APPEND autoconf_variant_content "${line}\n")
endforeach()

Expand Down
1 change: 1 addition & 0 deletions cmake/sysbuild/image_signing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function(zephyr_mcuboot_tasks)
if(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT OR CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP)
dt_chosen(code_partition PROPERTY "zephyr,code-partition")
dt_partition_addr(code_partition_offset PATH "${code_partition}" REQUIRED)
dt_reg_size(slot_size PATH "${code_partition}" REQUIRED)
set(imgtool_rom_command --rom-fixed ${code_partition_offset})
endif()
set(imgtool_sign ${PYTHON_EXECUTABLE} ${IMGTOOL} sign --version ${CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION} --align ${write_block_size} --slot-size ${slot_size} --header-size ${CONFIG_ROM_START_OFFSET} ${imgtool_rom_command})
Expand Down
1 change: 1 addition & 0 deletions cmake/sysbuild/sign_nrf54h20.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ function(check_merged_slot_boundaries merged_partition images)
sysbuild_get(start_offset IMAGE ${image} VAR CONFIG_ROM_START_OFFSET KCONFIG)
sysbuild_get(end_offset IMAGE ${image} VAR CONFIG_ROM_END_OFFSET KCONFIG)
dt_chosen(code_flash TARGET ${image} PROPERTY "zephyr,code-partition")

dt_partition_addr(code_addr PATH "${code_flash}" TARGET ${image} REQUIRED ABSOLUTE)
dt_reg_size(code_size TARGET ${image} PATH ${code_flash})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,11 @@ Other samples
* Added a new testing step demonstrating how to calculate event propagation statistics.
Also added the related test preset for the :file:`calc_stats.py` script (:file:`nrf/scripts/nrf_profiler/stats_nordic_presets/app_event_manager_profiler_tracer.json`).

* Added:

* The :ref:`idle_relocated_tcm_sample` sample to demonstrate how to relocate the firmware to the TCM memory at boot time.
The sample also uses the ``radio_loader`` sample image (located in :file:`nrf/samples/nrf54h20/radio_loader`), which cannot be tested as a standalone sample, to relocate the firmware from the MRAM to the TCM memory at boot time.

Drivers
=======

Expand Down
1 change: 1 addition & 0 deletions doc/nrf/samples/other.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ This section lists single |NCS| samples for various uses that are not part of ot
../../../tests/benchmarks/multicore/*/README
../../../samples/zephyr/smp_svr_mini_boot/README
../../../samples/basic/*/README
../../../samples/nrf54h20/*/README
19 changes: 19 additions & 0 deletions samples/nrf54h20/idle_relocated_tcm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

if(NOT SYSBUILD)
message(FATAL_ERROR
" This is a multi-image application that should be built using sysbuild.\n"
" Add --sysbuild argument to west build command to prepare all the images.")
endif()

project(idle)

target_sources(app PRIVATE src/main.c)
24 changes: 24 additions & 0 deletions samples/nrf54h20/idle_relocated_tcm/Kconfig.sysbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

source "share/sysbuild/Kconfig"

choice NETCORE

default NETCORE_CUSTOM_RADIO

config NETCORE_CUSTOM_RADIO
bool "Custom radio"
help
Use custom radio.

endchoice

config NETCORE_IMAGE_NAME
default "remote_rad" if NETCORE_CUSTOM_RADIO

config NETCORE_IMAGE_PATH
default "$(ZEPHYR_NRF_MODULE_DIR)/samples/nrf54h20/idle_relocated_tcm/remote" if NETCORE_CUSTOM_RADIO
230 changes: 230 additions & 0 deletions samples/nrf54h20/idle_relocated_tcm/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
.. _idle_relocated_tcm_sample:

Multicore idle test with firmware relocated to radio core TCM
#############################################################

.. contents::
:local:
:depth: 2

The test benchmarks the idle behavior of an application that runs on multiple cores.
It demonstrates a radio loader pattern where the radio core firmware is loaded from MRAM into TCM (Tightly Coupled Memory) at runtime.

Requirements
************

The test supports the following development kit:

.. table-from-rows:: /includes/sample_board_rows.txt
:header: heading
:rows: nrf54h20dk_nrf54h20_cpuapp

Overview
********

This test demonstrates how to build a multicore idle application with :ref:`configuration_system_overview_sysbuild` using a two-stage boot process for the radio core:

* Radio Loader - A small bootloader that runs on the radio core, copies firmware from MRAM to TCM, and jumps to it.
* Remote Firmware - The actual application that runs from TCM after being loaded.

The test automatically relocates the remote firmware binary to the correct MRAM address during build time, ensuring it can be loaded by the radio loader.

Architecture
============

The system uses the following memory layout:

* **MRAM (Non-volatile):**

* ``cpurad_loader_partition`` @ 0x92000 - Contains the radio loader (8 KB)
* ``cpurad_loaded_fw`` @ 0x94000 - Contains the remote firmware binary (128 KB)

* **TCM (Volatile, fast execution):**

* ``cpurad_ram0`` @ 0x23000000 - Code execution region (128 KB)
* ``cpurad_data_ram`` @ 0x23020000 - Data region (64 KB)

Additional files
================

The test comes with the following additional files:

* :file:`sysbuild.conf` - Enables the radio loader by setting ``CONFIG_NRF_RADIO_LOADER=y``.
* :file:`boards/memory_map.overlay` - Shared memory map configuration for both loader and remote firmware.
* :file:`sysbuild/radio_loader/` - Radio loader configuration overrides (:file:`prj.conf`, overlay).
* :file:`sysbuild/remote_rad/` - Radio core firmware configuration overrides (:file:`prj.conf`, overlay).

Enabling the Radio Loader
*************************

The radio loader is automatically added to the build when you enable it in sysbuild configuration.

In :file:`sysbuild.conf`:

.. code-block:: kconfig

SB_CONFIG_NRF_RADIO_LOADER=y

This single configuration option:

#. Automatically adds the ``radio_loader`` application located in the :file:`nrf/samples/nrf54h20/radio_loader` folder.
#. Builds it for the CPURAD core.
#. No manual ``ExternalZephyrProject_Add()`` needed in sysbuild.

Memory map configuration
========================

The memory map is defined in :file:`boards/memory_map.overlay` and is shared between the radio loader and remote firmware to ensure consistency.

The overlay defines:

#. TCM regions:

.. code-block:: devicetree

cpurad_ram0: sram@23000000 {
compatible = "mmio-sram";
reg = <0x23000000 0x20000>; /* 128 KB for code */
};
cpurad_data_ram: sram@23020000 {
compatible = "mmio-sram";
reg = <0x23020000 0x10000>; /* 64 KB for data */
};

#. MRAM partitions:

.. code-block:: devicetree

&mram1x {
/delete-node/ partitions;

partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;

cpurad_loader_partition: partition@92000 {
label = "cpurad_loader_partition";
reg = <0x92000 DT_SIZE_K(8)>; /* 8 KB allocated (~4 KB actual) */
};

cpurad_loaded_fw: partition@94000 {
label = "cpurad_loaded_fw";
reg = <0x94000 DT_SIZE_K(128)>; /* 128 KB fixed */
};
};
};

Automatic firmware relocation
*****************************

The remote firmware must be relocated to match the MRAM partition address where it will be stored.
This is automatically done by Zephyr's ``CONFIG_BUILD_OUTPUT_ADJUST_LMA`` feature when the devicetree chosen nodes are configured correctly.

How it works
============

Firmware relocation is handled automatically by Zephyr's build system using the ``CONFIG_BUILD_OUTPUT_ADJUST_LMA`` configuration option, which is configured in ``zephyr/soc/nordic/nrf54h/Kconfig.defconfig.nrf54h20_cpurad`` for all nRF54H20 CPURAD projects.

The configuration automatically detects the ``fw-to-relocate`` chosen node in your devicetree.
When present, it calculates the LMA adjustment to relocate firmware from MRAM to TCM.
Without this chosen node, firmware runs directly from the ``zephyr,code-partition`` location (standard XIP behavior).

Simply configure the devicetree chosen nodes correctly in your firmware's overlay:

.. code-block:: devicetree

/{
chosen {
/* VMA: where code runs (TCM) */
zephyr,code-partition = &cpurad_ram0;
zephyr,sram = &cpurad_data_ram;

/* LMA: where to load from (MRAM partition) - enables relocation */
fw-to-relocate = &cpurad_loaded_fw;
};
};

Zephyr automatically calculates the Load Memory Address (LMA) adjustment based on your chosen nodes:

**With fw-to-relocate chosen node** (for radio loader pattern):

.. code-block:: text

LMA_adjustment = fw-to-relocate address - zephyr,code-partition address
= cpurad_loaded_fw - cpurad_ram0
= 0x94000 - 0x23000000

**Without fw-to-relocate** (standard behavior):

.. code-block:: text

LMA_adjustment = zephyr,code-partition address - zephyr,sram address

The build system then adjusts the hex file so that the firmware is loaded from MRAM (``0x94000``), but runs from TCM (``0x23000000``).

Building and running
********************

.. |test path| replace:: :file:`samples/nrf54h20/idle_relocated_tcm`

.. include:: /includes/build_and_run_test.txt

Testing
=======

After programming the test to your development kit, complete the following steps to test it:

1. |connect_terminal|
#. Reset the kit.
#. Observe the console output for both cores:

* For the application core, the output should be as follows:

.. code-block:: console

*** Booting nRF Connect SDK zephyr-v3.5.0-3517-g9458a1aaf744 ***
build time: Nov 22 2025 17:00:59
Multicore idle test on nrf54h20dk@0.9.0/nrf54h20/cpuapp
Multicore idle test iteration 0
Multicore idle test iteration 1
...

* For the radio core, the output should be as follows:

.. code-block:: console

*** Booting nRF Connect SDK zephyr-v3.5.0-3517-g9458a1aaf744 ***
build time: Nov 22 2025 17:00:29
Multicore idle test on nrf54h20dk@0.9.0/nrf54h20/cpurad
Current PC (program counter) address: 0x23000ae0
Multicore idle test iteration 0
Multicore idle test iteration 1
...

The radio loader first loads the firmware from MRAM (``0x0e094000``) to TCM (``0x23000000``) and then jumps to the loaded firmware.
This process is transparent and happens during the early boot stage.

#. Verify the DFU process:

#. Build the firmware for the secondary app slot, increase the version number in the :file:`prj.conf` file (uncomment the line):

.. code-block:: kconfig

CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.1+0"

#. Build the firmware:

.. code-block:: console

west build -p -b nrf54h20dk/nrf54h20/cpuapp

#. Program the firmware to the secondary application slot:

.. code-block:: console

nrfutil device program --firmware build/zephyr_secondary_app.merged.hex --options chip_erase_mode=ERASE_NONE

Reset the development kit.
The firmware must boot from the secondary application slot.
Observe the change in build time in the console output.
Loading