Skip to content
neuralSPOT-X
User guide
HELIA HUB

Startup and linker

Nothing in a generated app hides the startup path. The board fragment vendored into boards/<board>/ names the startup source and the linker script explicitly, and both are files you can open.

boards/<board>/memory.cmake is the fragment that does it. For apollo510_evb it reads:

nsx_module_dir_for_name(_nsx_core_module_dir "nsx-core")
set(NSX_CORE_DIR "${NSX_ROOT}/${_nsx_core_module_dir}")
if(NSX_TOOLCHAIN_FAMILY STREQUAL "armclang")
set(NSX_STARTUP_SOURCE "${NSX_CORE_DIR}/src/apollo510/armclang/startup_keil6.c")
set(NSX_SYSTEM_SOURCE "${NSX_AMBIQSUITE_ROOT}/CMSIS/AmbiqMicro/Source/system_apollo510.c")
set(_nsx_linker_script_default "${NSX_CORE_DIR}/src/apollo510/armclang/linker_script_sbl.sct")
set(_nsx_linker_script_itcm "${NSX_CORE_DIR}/src/apollo510/armclang/linker_script_itcm_sbl.sct")
else()
set(NSX_STARTUP_SOURCE "${NSX_CORE_DIR}/src/apollo510/gcc/startup_gcc.c")
set(NSX_SYSTEM_SOURCE "${NSX_AMBIQSUITE_ROOT}/CMSIS/AmbiqMicro/Source/system_apollo510.c")
set(_nsx_linker_script_default "${NSX_CORE_DIR}/src/apollo510/gcc/linker_script_sbl.ld")
set(_nsx_linker_script_itcm "${NSX_CORE_DIR}/src/apollo510/gcc/linker_script_itcm_sbl.ld")
endif()

Three things follow from that.

The startup file comes from the nsx-core module, per SoC and per toolchain family. Arm Compiler for Embedded gets startup_keil6.c; GCC and ATfE get startup_gcc.c. Both live under modules/.../nsx-core/src/<soc>/ in your app.

The CMSIS system file comes from the SDK, under NSX_AMBIQSUITE_ROOT, not from NSX. Which SDK tree that is depends on the provider resolution described in SDK foundation and overrides.

Linker scripts are per SoC, per toolchain and per profile. Arm Compiler for Embedded uses scatter files (.sct); GCC and ATfE use linker scripts (.ld).

Scripts come in two families, named for whether the image sits behind Ambiq’s secure bootloader:

  • linker_script_sbl.*, the secure-bootloader layout, used by the Apollo boards.
  • linker_script_nbl.*, the no-bootloader layout, used by atomiq110_fpga_turbo.

The difference is where the image starts in non-volatile memory, which is why an image built for one will not run under the other. Your board’s fragment picks the right one; you do not choose between them.

Each board names two scripts, a default and an ITCM variant, and hands both to a selector:

if(COMMAND nsx_select_linker_script)
nsx_select_linker_script(
DEFAULT "${_nsx_linker_script_default}"
ITCM "${_nsx_linker_script_itcm}"
)
else()
# SDK predates named linker profiles — fall back to the default script.
set(NSX_LINKER_SCRIPT "${_nsx_linker_script_default}")
endif()

Setting NSX_LINKER_PROFILE to itcm selects the ITCM variant, which lays the image out to execute from tightly coupled instruction memory rather than from NVM. That is the layout the coremark example uses, and it is the one to reach for when instruction fetch is what you are measuring.

nsx_select_linker_script is provided by the nsx-core module rather than by NSX itself, which is why the fragment guards on if(COMMAND ...). Against an SDK revision that predates the feature, the default script is used and the profile is ignored.

The whole selection is skipped if NSX_LINKER_SCRIPT is already set:

if(NOT DEFINED NSX_LINKER_SCRIPT)

So pointing the build at a script of your own is one CMake variable:

Terminal window
nsx configure -- -DNSX_LINKER_SCRIPT=/path/to/my_layout.ld

Start from the script your board would have selected, since it already has the correct regions and section names for the part. The power_benchmark example ships its own linker_script_itcm.ld for exactly this reason, and it is a readable starting point.

The board’s board.cmake passes the startup code its stack and heap sizes as compile definitions, for example:

# STACK_SIZE: startup C-runtime stack size in bytes (used by SDK startup).
STACK_SIZE=4096

The startup source in nsx-core is what consumes them, and it is the file to read if you need to know exactly how a given SDK revision interprets the value. Both come out of the same region as your data, so raising either one takes space from everything else placed there. See Memory placement.

build/<board>/ holds the linked ELF, a .bin and a .map. The map file is the record of what actually ended up where: if you placed something with an NSX_MEM_ macro and want to confirm it landed in the region you meant, the map is the evidence, not the source.