---
title: "Timing, profiling, heap and stack"
---

# Timing, profiling, heap and stack

The cycle stopwatch between two halts, DWT and TRACE32 BMC benchmark counters per run and per task, SWO statistical profiling per function, and the Heap and Stack view with stack painting.

These tools answer the timing and memory questions of a safety or timing record: how many cycles an interrupt takes, where the time goes, and how deep the stacks and heap really get. They run through whichever GDB server the session uses.

**Verification state:** QEMU does not model the Data Watchpoint and Trace unit (DWT), so **no cycle or event count has been taken on any real Cortex-M core**. SWO profiling needs a probe with SWO and has been tested only on byte streams built from the Armv7-M Architecture Reference Manual. Heap and Stack is verified in QEMU against the firmware's own figures.

## Cycle stopwatch

Measure the PWM update interrupt in cycles and microseconds between two breakpoints, as IAR's and MPLAB's stopwatches do.

1. In a debug session, halt at the first breakpoint and run **Start Cycle Stopwatch**. It turns on the DWT cycle counter (writing DEMCR.TRCENA and DWT_CTRL.CYCCNTENA) and sets the stopwatch's zero at this halt. `neuralInverse.embedded.debug.cycleStopwatch` starts it at the first halt of every session.
2. Run to the second breakpoint. The **Cycle Stopwatch** status bar item shows the last run in cycles and time.
3. Click the item, or run **Reset Cycle Stopwatch**, to set the zero at this halt.

Counter wraps and stalls are reported. A target without a working cycle counter (Cortex-M0, M0+, M23, or an emulator) says so instead of showing a count. Works on Cortex-M3, M4, M7, M33 and later.

State: unit-tested on DWT models (wrap, stall, no counter); on QEMU 8.2.2 with gdb-multiarch 15.1 on Linux, the requests and debug-register writes go through real GDB and the missing counter is reported; the status bar in the running app. No count on a real core.

## Benchmark counters

Between two breakpoints, read cycles and the core's other event counters per run, since a zero, and per task, and export them for the timing record. The **Benchmark Counters** view is in Run and Debug.

1. Halt the target.
2. Choose **Count with**: the core's DWT counters (any GDB server), TRACE32's benchmark counters (BMC) in a TRACE32 session, or TRACE32 BMC in a TRACE32 session and the DWT otherwise.
3. Choose the events:
   - DWT (Cortex-M3, M4, M7, M33 and later): cycles (CYCCNT), extra instruction cycles (CPICNT), exception overhead cycles (EXCCNT), sleep cycles (SLEEPCNT), load-store extra cycles (LSUCNT), folded instructions (FOLDCNT). With all six, the instructions executed are derived. The 8-bit counters are exact for runs of fewer than 256 cycles.
   - TRACE32: add a counter as TRACE32's BMC window names it for the core, with its event (or leave the event empty to keep TRACE32's setting). Each event is set with `BMC.<counter>.EVENT` and each value read with `BMC.COUNTER.BYNAME()` and `BMC.OVERFLOW.BYNAME()`.
4. Select **Start Counting** (or run **Start Benchmark Counters**). It lists the debug-register writes it needs (DEMCR.TRCENA and the DWT_CTRL enable bits) and writes nothing until you confirm.
5. The counters are read at every halt: last run and since the zero, with times from the core clock in `debug.swoCpuClock`, and per task, using the task the RTOS view (ORTI included) shows at each halt.

**Reset Benchmark Counters** takes the current values as zero at this halt (nothing is written to the target). **Export Benchmark Counters as CSV** saves them.

![Start Counting lists the debug-register writes it needs and writes nothing until confirmed.](https://neuralinverse.com/docs-images/modules/embedded/benchmark-counters-confirm.webp)

State: counts tested on DWT models and composed TRACE32 answers; on QEMU 8.2.2 with gdb-multiarch 15.1 on Linux, writes planned from the real registers and written only after confirmation, a target without working counters reported, and the BMC command reaching the GDB server with its refusal shown; the view in the running app. Kit shipped (trace32, `bmc` scenario). **Not yet run against TRACE32**; counter and event names are typed by you, not read from TRACE32. Agents can start, read and reset the counters (**Start Benchmark Counters**, **Read Benchmark Counters**, **Reset Benchmark Counters**); the start waits for your confirmation.

## Statistical profiling over SWO

See which functions take the time, from the DWT's PC sampling over SWO, as STM32CubeIDE's and Keil's profilers do.

1. Turn on SWO (`neuralInverse.embedded.debug.swo`, with `debug.swoCpuClock` set to the real core clock and `debug.swoFrequency` dividing it exactly).
2. In a debug session, run **Start Firmware Profiling**. **Open Firmware Profile** opens the Firmware Profile editor.
3. Let the firmware run, then **Stop Firmware Profiling** (or **Stop** in the editor).

The editor shows the samples per second and the DWT's sampling period, then each function with its samples, share and source link, the share of samples taken while the core slept (WFI or WFE), samples outside known functions (code in ROM or a library without symbols), and **Exception Entries** per exception. If SWO cannot carry as many samples as the DWT sends, it says to lower the sample rate or raise the SWO frequency; trace port overflows are counted.

| Setting | Default | Meaning |
|---|---|---|
| `neuralInverse.embedded.debug.profileSampleRate` | 10000 | PC samples a second |
| `neuralInverse.embedded.debug.profileExceptions` | `true` | Count exception entries |

Function names come from the image through `arm-none-eabi-nm`. The DWT registers are set for the profile and restored afterwards.

State: packet decoding, register setup and restore, and aggregation tested on byte streams built from the Armv7-M Architecture Reference Manual and a fake GDB; the symbol lookup's integration test exists and has not run. **Needs SWO, so it does not work in the emulator; not run with any probe.**

## Heap and Stack

At a halt, see the main stack's deepest use since it was painted, the heap in use and each task's stack, as MCUXpresso IDE's Heap and Stack view shows them. The **Heap and Stack** view is in Run and Debug.

- **Stack**: the main stack's range from the image's linker symbols (CMSIS and NXP SDK, STM32CubeIDE, IAR, Arm Compiler and MCUXpresso names), how much is in use now, and the deepest use since painted, with the never-used part and the pattern. A main stack pointer below the stack's limit is reported as an overflow.
- **Heap**: the heap's range and limit, the allocators linked (newlib, newlib-nano, FreeRTOS heap_1, heap_2, heap_4 and heap_5) and their use when the target halts: in use, free in how many blocks, largest block, lowest free since start, and the program break.
- **Task stacks**, as the RTOS view reads them.

**Paint Stack** writes a pattern below the current stack pointer, so the deepest use can be measured later: run the firmware through its worst case and halt it. It asks first, naming the range below the stack pointer, the pattern and the image. **Read the Heap and Stack Again** refreshes the view.

![The Heap and Stack view: stack and heap regions from the linker symbols, the deepest stack use since the paint, heap use and task stacks.](https://neuralinverse.com/docs-images/modules/embedded/heap-stack-view.webp)

![Paint Stack asks first, naming the range below the stack pointer, the pattern and the image.](https://neuralinverse.com/docs-images/modules/embedded/paint-stack-confirm.webp)

The deepest use is measured from the words that no longer hold the pattern, so a frame that leaves a word unwritten is not counted and the real depth can be a few words more.

State: verified on QEMU 8.2.2 with gdb-multiarch on Linux: newlib and newlib-nano figures equal the target's `mallinfo()`; FreeRTOS heap_4 figures equal `xPortGetFreeHeapSize()` and `xPortGetMinimumEverFreeHeapSize()`; the painted deepest use within 2 words of the lowest stack pointer the firmware recorded; used in the running app. The IAR, armlink and MCUXpresso symbol names come from their documentation (kit heap-stack-symbols shipped). Needs `arm-none-eabi-nm`, also for IAR and Keil images. Agents can read the layout and use and paint the stack (**Read Heap and Stack Layout**, **Read Heap and Stack**, **Paint Stack**); painting waits for your confirmation.
