# ITCM placement

Use ITCM placement when profiling identifies code or tensors worth moving into
a target's instruction tightly coupled memory. Check the
[registered target](https://ambiqai.github.io/helia-aot/reference/targets/) and your board's linker and
power configuration first. Code and data share the region: the converter's
tensor budget does not reserve space for linked code.

## Choose what to place

| Item | Control | Placement owner |
| --- | --- | --- |
| Constant, scratch or persistent tensors | `memory.tensors` with `memory: ITCM` | heliaAOT planner and application linker |
| Constants copied into ITCM at initialization | `memory: MRAM` with `constant_destination_memory: ITCM` | heliaAOT planner and hydration code |
| Generated operator run code | `operators[].attributes.code_placement: ITCM` | heliaAOT code generation and application linker |
| Kernel-library functions | Linker script or code relocation rules | Application build |

Moving a generated wrapper does not move the kernel function it calls. Measure
the resulting firmware before deciding whether the change helps your model.

## Budget tensor memory

ITCM is not in the registered targets' default memory preference order. Include
it in `memory.constraints` to make it available to the planner. This example
allows a **128 KiB tensor budget**, not a statement of the target's capacity:

```yaml
memory:
  constraints:
    - name: DTCM
    - name: SRAM
    - name: MRAM
    - name: ITCM
      max_size: 131072
```

Use only memory regions exposed by your target. The order gives unpinned
tensors their placement preference; putting ITCM last allows eligible tensors
to spill there after earlier compatible regions. Reserve room for generated
code, library functions and other application sections when choosing
`max_size`. The linker map must fit the combined usage.

:::note[Arenas at address zero]
ITCM starts at address zero on Apollo5-class parts. A generated module
reserves one alignment unit (16 bytes by default) ahead of each arena it
allocates in ITCM, and ahead of a staged constant source kept in ITCM, so no
arena base, tensor or hydration source is ever a null pointer, whatever
the link order. The guard is not counted in the planned arena size, so leave
that much ITCM spare. With `memory.allocate_arenas: false`, you supply the buffers:
`bind_arena` rejects a buffer at address zero with status 2
(`bind_null_buffer`), so bind ITCM buffers above zero. See
[issue #500](https://github.com/AmbiqAI/helia-aot/issues/500).
:::

## Place tensors

For constant weights, choose which code initializes the ITCM storage:

| Rule | Initialization |
| --- | --- |
| `memory: ITCM` | The firmware's startup code copies the section's load image into ITCM |
| `memory: MRAM`, `constant_destination_memory: ITCM` | Generated model initialization hydrates the destination from the source blob |

```yaml
memory:
  tensors:
    - type: constant
      attributes:
        memory: ITCM
```

For constants staged from MRAM, use this rule instead:

```yaml
memory:
  tensors:
    - type: constant
      attributes:
        memory: MRAM
        constant_destination_memory: ITCM
```

A cold arena remains resident for the firmware's lifetime. Staging is useful
when the application supplies buffers that models reuse in turn. Follow the
[caller-owned arena contract](https://ambiqai.github.io/helia-aot/guide/memory-placement/#caller-supplied-arenas)
and initialize each model before it uses the shared buffer.

Scratch and persistent tensors use the same placement mechanism:

```yaml
memory:
  tensors:
    - type: scratch
      attributes:
        memory: ITCM
    - type: persistent
      attributes:
        memory: ITCM
```

The tensor rules above complement the constraints block; they do not replace
it. Check the [residency report](https://ambiqai.github.io/helia-aot/guide/memory-reports/) to see the
actual arenas and bytes assigned.

:::note[Account for the firmware image too]
A zero-initialized array placed in a loaded ITCM section can occupy its full
size in the firmware load image, even if an ordinary `.bss` array would not.
Staged constants can therefore need both the source weights and zero-filled
destination bytes in the image. Inspect section sizes and startup behavior.
If needed, use application-owned arenas in a suitable uninitialized section;
the application then owns their placement and initialization requirements.
:::

## Place generated code

```yaml
operators:
  - type: DILATE
    attributes:
      code_placement: ITCM
```

The converter annotates the matching operator's run declaration with
`<PREFIX>_PUT_CODE_IN_ITCM`. Shared generated helpers receive the annotation
when one of their callers requests ITCM. Operator initialization and the model
schedule keep their default placement. Library calls still follow the linker
script, including compiler-generated calls such as `memset`.

`code_placement` accepts `ITCM` and `MRAM`. MRAM emits no placement annotation.
An unsupported value or a target without ITCM produces a warning and keeps the
default placement. The code macro is defined in `<prefix>_platform.h` and can
be overridden:

| Module format | Default code macro | Build requirement |
| --- | --- | --- |
| `nsx`, `neuralspot` | `__attribute__((section(".itcm_text")))` | The selected linker script must collect this section in ITCM |
| `zephyr` | `__itcm_section` | The board must configure the `zephyr,itcm` region |
| `cmake`, `cmsis_pack` | Empty | Supply a placement definition and matching linker section; conversion warns otherwise |

For NSX, the **data** macros `<PREFIX>_PUT_IN_ITCM` and
`<PREFIX>_PUT_IN_ITCM_INIT` have no placement default. Supply the module's
attributes header through `<MODULE>_ATTRIBUTES_HEADER` when placing arenas.
The code macro has the default shown above. A planner report alone does not
prove either kind of symbol landed in ITCM.

Keep application data and functions in separate source files or distinct
sections when using section attributes. Toolchains can reject code and data
that share a named section within one source file.

## Place library code and configure power

Use the application linker script for kernel-library functions. NSX boards can
provide an ITCM-oriented script selected through `NSX_LINKER_SCRIPT`; inspect
its object-name patterns because they may also match generated model code or
data outside the planner's budget. For Zephyr, use the board's supported code
relocation facilities, such as `zephyr_code_relocate()` with
`CONFIG_CODE_DATA_RELOCATION`.

Check the board SDK's TCM power and retention configuration. The powered region
must cover all placed code and tensors, and data required after sleep must be
retained or restored before inference resumes. A linker capacity check cannot
establish that the corresponding memory remains powered.

## Verify placement

Inspect the final firmware, including symbol addresses and load sections:

```bash
arm-none-eabi-nm -n app.axf | grep -E '_run$|arena'
arm-none-eabi-objdump -h app.axf
```

Compare addresses with the selected board's memory map. Confirm that arenas
have nonzero addresses, startup copies initialized sections correctly, and the
combined code/data allocation fits the powered ITCM region. Then validate
model outputs on the device and [profile the result](https://ambiqai.github.io/helia-aot/guide/measure/).
Keep the linker map and build settings with the measurement.
