Summary
`hpx profile`'s "Binary Sections" report (and `summary.json`'s
`memory.binary_sections`) computes text/data/bss via
`arm-none-eabi-size` (see `toolchain_probe.py::binary_sections()`).
That tool's classic Berkeley output lumps every allocated
`SHT_NOBITS` section into the "bss" column — including linker-reserved
regions that are never touched at runtime, not just real zero-initialized
globals.
On AP5 boards (apollo510_evb, apollo510b_evb, ...), the NSX-provided
linker script (`nsx-ambiq-sdk/modules/nsx-core/src/apollo510/gcc/linker_script_nbl.ld`)
deliberately fills all remaining DTCM as `.heap` (NOLOAD):
```ld
/* Heap occupies all remaining TCM between .bss and the top of the region.
- _sbrk() in nsx-core enforces __HeapBase <= heap_end <= __HeapLimit so
- unbounded malloc cannot silently corrupt .data/.bss. /
.heap (NOLOAD):
{
. = ALIGN(8);
__HeapBase = .;
...
KEEP((.heap))
. = ORIGIN(MCU_TCM) + LENGTH(MCU_TCM); /* <- fills to end of DTCM */
__HeapLimit = .;
heap_end = .;
} > MCU_TCM
This is a reasonable safety convention upstream (it lets a link-time
\`ASSERT\` catch \"stack+data+bss leave < 1 KiB of heap\"), and it costs
nothing at runtime (NOLOAD, no flash bytes, no init cost) — but it means
\`hpx profile\`'s reported \`bss\` figure for a small KWS model on AP510 is
**~476 KB**, when the real zero-initialized data (\`.bss\` proper) is only
~147 KB and the rest (~312 KB) is just unused, reserved-but-never-touched
heap address space.
## Impact
- Misleading at a glance: a user comparing two configs/boards, or
comparing HPX's reported footprint against another tool's (e.g. a
hand-rolled AmbiqSuite/neuralSPOT project that reserves a small fixed
heap instead of filling-to-end), will see HPX's \"total\" binary size
look far larger than the real utilized memory, with no indication that
most of the delta is an inert reservation.
- Confirmed while investigating an AP510 KWS power/energy gap against
AutoDeploy: an early size comparison mistakenly treated this bss/heap
inflation as a real code-size difference before the NOLOAD nature of
\`.heap\` was traced back to the linker script.
## Suggested fix (one or more of)
1. In \`toolchain_probe.py\`'s binary-size computation, additionally parse
section headers (e.g. via \`arm-none-eabi-readelf -S\` /
\`objdump -h\`) and separately report NOLOAD reservation sections
(\`.heap\`, \`.stack\`, \`.sram_bss\`, \`.shared\`) from true
zero-initialized \`.bss\`, so the CLI/summary can show something like
\`bss (real): 147 KB, heap (reserved): 312 KB\`.
2. Document the fill-to-end heap convention in
\`docs/guide/output.md\` / \`docs/architecture/pipeline.md\` next to the
existing \"Binary section sizes\" description, so readers know not to
read \`bss\` as \"all real static memory usage\" on AP5 boards.
3. Optionally, consider whether NSX's linker script itself should reserve
a smaller, fixed heap by default instead of filling to the end of DTCM
— but that's an upstream \`nsx-ambiq-sdk\` change, out of scope for this
repo; (1)/(2) are the actionable items here.
## Where this was found
Repo memory: \`ap510-autodeploy-power-methodology-gap-resolved.md\`,
section \"ITCM/linker-wildcard hypothesis (2026-07-03)\".
Summary
`hpx profile`'s "Binary Sections" report (and `summary.json`'s
`memory.binary_sections`) computes text/data/bss via
`arm-none-eabi-size` (see `toolchain_probe.py::binary_sections()`).
That tool's classic Berkeley output lumps every allocated
`SHT_NOBITS` section into the "bss" column — including linker-reserved
regions that are never touched at runtime, not just real zero-initialized
globals.
On AP5 boards (apollo510_evb, apollo510b_evb, ...), the NSX-provided
linker script (`nsx-ambiq-sdk/modules/nsx-core/src/apollo510/gcc/linker_script_nbl.ld`)
deliberately fills all remaining DTCM as `.heap` (NOLOAD):
```ld
/* Heap occupies all remaining TCM between .bss and the top of the region.
.heap (NOLOAD):
{
. = ALIGN(8);
__HeapBase = .;
...
KEEP((.heap))
. = ORIGIN(MCU_TCM) + LENGTH(MCU_TCM); /* <- fills to end of DTCM */
__HeapLimit = .;
heap_end = .;
} > MCU_TCM