RA Flexible Software Package Documentation  Release v6.2.0

 
Linker Script Generation Guide

FSP Linker Scripts

FSP prior to v6.0.0 provided linker scripts that were common to all MCUs. Only having a single linker script per toolchain had become increasingly complex as the RA MCU family continued to grow and diversify. Starting in FSP v6.0.0, linker scripts are specific to the toolchain, MCU, and type of project being built. This means that users will need to migrate any existing linker script modifications that might have been made when migrating to FSP v6.0.0 or later.

In this guide, we will examine the methodology behind the FSP6 linker script mechanism, highlighting key areas that have changed from earlier FSP releases and providing details of how to migrate user-modified linker scripts from earlier releases to the new mechanism.

Audience: customers wishing deeper knowledge of the new linker scripting or migrating existing Renesas RA projects utilizing BSP_PLACE_IN_SECTION or custom linker scripts to the policy‑generated linker scripts.

Note: When using a Solution project it is recommended to use the Memories tab in the configuration editor to modify memory region sizes and location as necessary.


Why we changed

Hand‑edited linker scripts had become non‑maintainable as devices and project styles grew more complex:

  • Multiple projects per MCU (bootloader, TrustZone, multicore), each with different memory partitions
  • Device‑specific capabilities (OFS/BPS layouts, ITCM/DTCM, SDRAM, OSPI, etc.)
  • Divergent behavior across toolchains (GCC/LLVM/IAR/ArmCC6)
  • Customer requests for non‑invasive placement (no source edits) and multi‑RAM init

What the new system delivers

  • One policy for four toolchains: scripts are generated from a common policy so behavior is equivalent across GCC, LLVM/Clang, IAR, and AC6.
  • Per‑device awareness: OFS register locations and available memories (ITCM/DTCM/SDRAM/OSPI) are baked in at generation time.
  • Non‑invasive placement: place variables/functions/sections into arbitrary memories without touching source via the DDSC Linker Section Mapping dialog.
  • Multi‑RAM initialization: support for multiple RAM families (internal RAM, TCMs, SDRAM) with correct copy/zero at startup.
  • Solution‑level partitioning: allocate FLASH/RAM across projects in a Solution Partitioning dialog; scripts follow those partitions automatically.
  • Fine‑grained OFS Registers: separate, explicit sections for each register region instead of one all‑or‑nothing block.

What changed (layout & file model)

Each toolchain now uses the same two‑file include pattern:

  • Region Information memory_regions.*generated. Declares device/solution memory bases & lengths (e.g., FLASH/RAM/ITCM/DTCM/SDRAM/OSPI, OFS/OTP, etc.). Do not edit; update via the Solution Partitioning dialog.
  • Linker Script fsp_gen.*generated. Defines output sections and default mappings for the current device and toolchain. Do not edit; change behavior with mapping rules.
  • Top‑level fsp.* – a tiny wrapper that INCLUDEs the two files above. This file is stable and meant to be referenced by the project.

If you need custom script logic, prefer mapping rules first. If absolutely necessary, either: 1) keep a patched copy of the generated files (applied during your build before link), or 2) fork to new filenames and INCLUDE them from fsp.* (works, but you disconnect from tool‑assisted mapping/partitioning).


New section model (high level)

Instead of monolithic .text/.data/.bss outputs, the new scripts split into semantically named outputs per memory and init behavior. These names are consistent across toolchains and expose Base/Limit symbols per region.

Common outputs (examples; presence depends on device/memory availability):

  • FLASH
    • __flash_vectors$$ – Vector table / Reset handler
    • __flash_readonly_gap$$ – On devices with OFS registers at 0x400, this is space before that space, where we default map content to optimize memory utilization via the DDSC linker section mapping
    • __flash_readonly$$ – Code & const data (also GOT/EXIDX/ctor arrays: __flash_.got$$, __flash_arm.exidx$$, __flash_{preinit,init,fini}_array$$)
    • __flash_noinit$$ – Reserved/no‑load content in flash
  • Internal RAM
    • __ram_dtc_vector$$ - The vector table for the dtc peripheral (located at start of ram for alignment restrictions)
    • __ram_from_{ROM REGION}$$ – Initialized data copied from each available persistent source
    • __ram_zero$$ – Zero‑initialized bss/tbss
    • __ram_noinit$$ – Non‑initialized (e.g., heaps, main stack)
    • Thread/ELF TLS helpers: __ram_tdata$$, __ram_tbss$$, __ram_thread_stack$$
    • Optional no‑cache variants (device‑dependent): __ram_zero_nocache$$, __ram_noinit_nocache$$
  • Secondary ROM Memories (such as data flash, qspi, ospi)
    • __{ROM Region}_readonly$$ - Initialized space in this region (constants, code)
    • __{ROM Region}_noinit$$ - Uninitialized space in this region that application code can write at runtime
  • Secondary RAM Memories (such as tcm, ospi, sdram)
    • __{RAM Region}_from_{ROM Region}$$ - Initialized data copied from each available persistent source
    • __{RAM Region}_zero$$ – Zero‑initialized data in this region
    • __{RAM Region}_noinit$$ – Non‑initialized data in this region
  • OFS Registers (fine‑grained)
    • __option_setting_{OFS Register}_reg$$ - Fine grained output section for each OFS register

Each output exposes ...$$Base / ...$$Limit symbols for portable startup code and diagnostics across toolchains.


Interacting with the new flow

A) Place objects or code without source edits (recommended)

Use DDSC → Linker Section Mapping

Here you can see typical default mappings for a device with the OFS flash gap.

  • The heap and stack are mapped to uninitialized RAM (otherwise they would be placed in zeroed RAM memory)
    • Unchecking the default selection will remove the mapping and allow standard linker placement.
    • These allocations can be overridden to other regions. For example the stack could be placed in DTCM by unchecking the selection and creating a new input section mapping.
  • Additionally, we are default placing content into the flash gap to maximize flash utilization.
    linker_default_mappings.png

To create a new user mapping select the desired destination (and the intialization source if appropriate)

  • Here we are selecting ITCM Code initialized from ITCM code from OSPI0_CS1
  • In the dialog that is presented, we enter *(.text.*_isr)
  • The syntax of these mappings follow gnu ld input section selection rules (including wildcard patterns) regardless of the toolchain
    linker_new_mapping_select.png

This means any function that ends in _isr will be persistently stored in OSPI0_CS1 space (in the output elf and srecord) and loaded to ITCM by the fsp startup code.

linker_mapping.png

B) Source‑level placement (still supported)

BSP_PLACE_IN_SECTION("<name>") continues to work. Some legacy section names changed for consistency; prefer mapping rules when migrating. If you keep the macro, use the input names as defined in the appendix.

C) Solution‑level partitioning

Use Solution Partitioning to assign FLASH/RAM ranges across bootloader/app/TZ projects. memory_regions.* is regenerated to reflect those partitions; all scripts honor them automatically. Please refer to solution project documentation for details.


Migration guide (step‑by‑step)

  1. Update to FSP 6.0 or later that includes the new behavior.
  2. Reference any customer edits made to the legacy linker scripts and determine how to apply to the generated scripts.
  3. Review defaults in Linker Section Mapping. Add rules for any special placements you previously had in hand‑edited scripts.
  4. If you used BSP_PLACE_IN_SECTION, either remove it and add a mapping rule, or rename the section to the new input names shown below.
  5. Build & verify the project using the new scripts and compare the map files to prior.
  6. Only if needed, implement a patch step (or a fork + INCLUDE) to carry forward any exotic script logic.

Legacy (gcc, llvm) → New **output** section translation

legacy_to_new_output_sections:
.text: [__flash_vectors$$ (Reset handler & vectors), __flash_readonly$$ (code/rodata)]
.data: __ram_from_flash$$
.bss: __ram_zero$$
.noinit: __ram_noinit$$
.tdata: __ram_tdata$$
.tbss: __ram_tbss$$
.data_flash: [__data_flash_readonly$$, __data_flash_noinit$$]
.qspi_flash/.OSPI_DEVICE_0/.OSPI_DEVICE_1: OSPI family outputs #(e.g., __ospi0_cs0_from_flash$$, __ospi0_cs1_readonly$$) – pick readonly/from/zero/noinit as needed
.got: __flash_.got$$
.ARM.exidx/.ARM.extab: __flash_arm.exidx$$
.option_setting_* (old single blob): fine‑grained __option_setting_*_reg$$

Note: In the e2 Studio tooling, dialogs related to obj-copy do not properly parse the $$ suffix, so use globbing if required (such as --remove-section=__option_setting_*)

Note: exact availability of *_from_* and *_nocache outputs depends on the device and selected memories.


Behavior differences worth noting

  • Generated files overwrite: fsp_gen.* and memory_regions.* are regenerated. Put customizations in mapping rules or in patches/forks.
  • Vectors moved: Reset/ISR vectors live in __flash_vectors$$. General code/rodata live in __flash_readonly$$ (placed after OFS flash gap where required).
  • Copy/zero tables generalized: initialization now spans multiple memories (RAM/TCM/SDRAM/OSPI) with per‑output Base/Limit symbols.
  • Fine‑grained protection: OFS/BPS/OTP/SAS are each their own small section; tools can preserve or omit them independently.

Examples

A) Place a DMA buffer in non‑cached RAM (no source edits)

  • Mapping path: RAM Zero (No‑Cache)
  • Input pattern: my_dma.o(.bss.my_buffer)
  • Input section placed in: .ram_nocache
  • Output section placed in: __ram_zero_nocache$$

B) Run a hot ISR from ITCM (copied from FLASH)

  • Mapping path: ITCM Code from FLASH
  • Input pattern: isr_fast.o(.text.my_isr)
  • Input section placed in: .itcm_code_from_flash
  • Output section placed in: __itcm_from_flash$$

C) Put firmware asset in OSPI1 CS1 ReadOnly

  • Mapping path: OSPI_CS1 Constant Data
  • Input pattern: assets.o(.rodata*)
  • Input section placed in: .ospi1_cs1
  • Output section placed in: __ospi1_cs1_readonly$$

FAQ

Q: Can I still edit the scripts directly? A: You can, but we don’t recommend it. Copy to new filenames and INCLUDE them, or maintain a patch step. Otherwise, tool‑driven mapping/partitioning will be out of sync.

Q: Will old BSP_PLACE_IN_SECTION() break? A: It’s still supported. A few legacy section names changed; prefer the mapping dialog or update the section names as in the quick translation above.

Q: How do I share memory between bootloader and app? A: Use Solution Partitioning. The generator will rebuild memory_regions.* and all projects will align automatically.

Q: What about data that must not be re‑initialized? A: Use .noinit/.ram_noinit (or the mapping to RAM Non‑Initialized__ram_noinit$$).

Q: What is the bsp_linker_info.h file located in the <configuration> folder? A: To support the seamless support of the new linker process, we must pass information to the source code (such as the source and destination of memory initializers). Unfortunately, the current DDSC tooling has limitations that only allow us to generate a header file to this location. Future releases will likely change the location of this information.


Summary

The new generator brings consistency, scalability, and zero‑touch placement across toolchains and devices. Start with Solution Partitioning and the Linker Section Mapping dialog; reserve script edits for exceptional cases via patches or forked includes.


Appendix: Linker Input Sections

This appendix documents the valid input linker_section values that appear in the linker script and may be referenced from user code or configuration macros.

Each section name is based on a RAM or ROM class, using a structured naming convention depending on the content type.

RAM Target Capability Matrix

RAM Class ROM Init Zeroed NoInit
Code Data NoCache NoCache
RAM
TCM

ROM Target Capability Matrix

ROM Class Code ReadOnly NoInit
ROM
FLASH (Gap)

Memory Class Dictionaries

These are the valid memory class identifiers used in linker_section names.

ram_class ::= "ram" | "itcm" | "dtcm" | "stcm" | "ctcm" | "sdram" | "ospi0_cs0"
rom_class ::= "flash" | "data_flash" | "qspi_flash" | "ospi0_cs1" | "ospi1_cs1"

The available memory classes depend on the capabilities of your specific device. Not all devices support all RAM or ROM types.

RAM-Resident Sections

Zero-Initialized RAM

.<ram_class>

Used for memory cleared to zero at startup.

Examples:

  • *(.ram)
  • *(.sdram)
  • *(.dtcm)

Uninitialized RAM

.<ram_class>_noinit
.<ram_class>_noinit_nocache

Used for reserved memory (e.g., heap/stack) that doesn't require initialization at startup. The _nocache variant is only available on devices with caching hardware.

Examples:

  • *(.ram_noinit)
  • *(.sdram_noinit_nocache)

RAM Initialized from ROM

.<ram_class>_code_from_<rom_class>
.<ram_class>_from_<rom_class>

Used for RAM-resident code or data loaded from ROM.

Examples:

  • *(.ram_code_from_flash)
  • *(.dtcm_from_data_flash)
  • *(.tcm_code_from_qspi_flash)

ROM-Resident Sections

Standard ROM Sections

.<rom_class>
.<rom_class>_code
.<rom_class>_noinit
  • .rom_class_code: Executable code
  • .rom_class: Constants or read-only data
  • .rom_class_noinit: Reserved (e.g., alignment)

Examples:

  • *(.flash_code)
  • *(.data_flash)
  • *(.qspi_flash_noinit)

Flash Gap Sections

.flash_gap_code
.flash_gap_readonly

Used only on devices with restricted flash regions (e.g., 0x400–0x43F). These sections support only code and constant data.

Summary

  • linker_section names follow consistent, structured patterns.
  • RAM and ROM classes reflect physical memory categories and device-specific capabilities.
  • Use _nocache suffixes only for memory that must not be cached (e.g., DMA regions).
  • Flash gap sections are a special case for devices with non-contiguous flash address maps.