PMU counters
Purpose
Section titled “Purpose”Choose what the per-layer measurement counts. hpx captures cycles, instructions, cache and bus activity, and on a Cortex-M55 the MVE vector unit and, on Atomiq110, the Ethos-U NPU. The tables on this page are generated from the counter registry of the hpx you are reading about, so the names and groups are the ones the profiler accepts.
Prerequisites
Section titled “Prerequisites”A working profile (First profile) and a board whose family you know from Boards and targets.
Which groups your board has
Section titled “Which groups your board has”| SoC | Profiling domains | Counter groups | Per-operator record budget |
|---|---|---|---|
apollo3p |
cpu | cpu |
2048 |
apollo4p |
cpu | cpu |
2048 |
apollo4l |
cpu | cpu |
2048 |
apollo510 |
cpu, memory, mve | cpu, memory, mve |
4096 |
apollo510b |
cpu, memory, mve | cpu, memory, mve |
4096 |
apollo5b |
cpu, memory, mve | cpu, memory, mve |
4096 |
apollo330P |
cpu, memory, mve | cpu, memory, mve |
512 |
apollo510L |
cpu, memory, mve | cpu, memory, mve |
512 |
atomiq110 |
cpu, memory, mve, ethos_npu | cpu, ethos_npu, memory, mve |
4096 |
The rows come from list_socs() and SocDef.profiling_domains through
supported_groups_for_domains() in src/helia_profiler/platform/. On a
DWT-only SoC the profiler warns and captures cycle counts only; a
pmu_counters selection has no effect there, and the mve group is
rejected at preflight.
1. Select counters
Section titled “1. Select counters”profiling.pmu_counters maps a group to a selection:
| Selection | Meaning |
|---|---|
default |
the curated subset for that group, sized to fit one pass |
all |
every counter in the group, captured over several passes |
[name, ...] |
an explicit list of counter names |
profiling: pmu_counters: cpu: default memory: [ARM_PMU_L1D_CACHE, ARM_PMU_L1D_CACHE_MISS_RD, ARM_PMU_DTCM_ACCESS] mve: allhpx profile model.tflite --pmu-counters cpu:default --pmu-counters mve:allhpx profile model.tflite --pmu-counters mve:ARM_PMU_MVE_INST_RETIRED,ARM_PMU_MVE_STALLper_layer defaults to true, iterations to 100, warmup to 5 and
aggregation to median. Names are case-sensitive and must match the
catalogue exactly; an unknown name fails with the full sorted list.
2. The catalogue
Section titled “2. The catalogue”Each group below lists every counter hpx knows, with the curated default
selection first.
cpu: 21 counters
Section titled “cpu: 21 counters”default selects ARM_PMU_CPU_CYCLES, ARM_PMU_INST_RETIRED, ARM_PMU_STALL_FRONTEND, ARM_PMU_STALL_BACKEND.
| Counter | Event | Description |
|---|---|---|
ARM_PMU_BR_IMMED_RETIRED |
13 | Immediate branch architecturally executed |
ARM_PMU_BR_MIS_PRED_RETIRED |
34 | Mispredicted branch instruction architecturally executed |
ARM_PMU_BR_RETIRED |
33 | Branch instruction architecturally executed |
ARM_PMU_BR_RETURN_RETIRED |
14 | Function return instruction architecturally executed and the condition code check pass |
ARM_PMU_CPU_CYCLES (default) |
17 | Cycle |
ARM_PMU_EXC_RETURN |
10 | Exception return instruction architecturally executed and the condition code check pass |
ARM_PMU_EXC_TAKEN |
9 | Exception entry |
ARM_PMU_INST_RETIRED (default) |
8 | Instruction architecturally executed |
ARM_PMU_L1I_CACHE_REFILL |
1 | L1 I-Cache refill |
ARM_PMU_LD_RETIRED |
6 | Memory-reading instruction architecturally executed and condition code check pass |
ARM_PMU_LE_CANCEL |
264 | Loop end instruction not taken |
ARM_PMU_LE_RETIRED |
256 | Loop end instruction executed |
ARM_PMU_PC_WRITE_RETIRED |
12 | Software change to the Program Counter (PC). Instruction is architecturally executed and condition code check pass |
ARM_PMU_SE_CALL_NS |
277 | Call to non-secure function, resulting in Security state change |
ARM_PMU_SE_CALL_S |
276 | Call to secure function, resulting in Security state change |
ARM_PMU_STALL |
60 | Stall cycle for instruction or operation not sent for execution |
ARM_PMU_STALL_BACKEND (default) |
36 | No operation issued because of the backend |
ARM_PMU_STALL_FRONTEND (default) |
35 | No operation issued because of the frontend |
ARM_PMU_ST_RETIRED |
7 | Memory-writing instruction architecturally executed and condition code check pass |
ARM_PMU_SW_INCR |
0 | Software update to the PMU_SWINC register, architecturally executed and condition code check pass |
ARM_PMU_UNALIGNED_LDST_RETIRED |
15 | Unaligned memory memory-reading or memory-writing instruction architecturally executed and condition code check pass |
ethos_npu: 9 counters
Section titled “ethos_npu: 9 counters”default selects ETHOSU_PMU_CYCLE, ETHOSU_PMU_NPU_ACTIVE, ETHOSU_PMU_MAC_ACTIVE, ETHOSU_PMU_SRAM_RD_DATA_BEAT_RECEIVED.
| Counter | Event | Description |
|---|---|---|
ETHOSU_PMU_CYCLE (default) |
0 | Total NPU cycles while the command stream runs |
ETHOSU_PMU_EXT_RD_DATA_BEAT_RECEIVED |
0 | External (flash/MRAM) read data beats |
ETHOSU_PMU_EXT_WR_DATA_BEAT_WRITTEN |
0 | External (flash/MRAM) write data beats |
ETHOSU_PMU_MAC_ACTIVE (default) |
0 | Cycles the MAC engine is active |
ETHOSU_PMU_NPU_ACTIVE (default) |
0 | Cycles the NPU is active (not idle/stalled) |
ETHOSU_PMU_NPU_IDLE |
0 | Cycles the NPU is idle |
ETHOSU_PMU_SRAM_RD_DATA_BEAT_RECEIVED (default) |
0 | SRAM interface read data beats |
ETHOSU_PMU_SRAM_WR_DATA_BEAT_WRITTEN |
0 | SRAM interface write data beats |
ETHOSU_PMU_WD_ACTIVE |
0 | Cycles the weight decoder is active |
memory: 15 counters
Section titled “memory: 15 counters”default selects ARM_PMU_MEM_ACCESS, ARM_PMU_L1D_CACHE_REFILL, ARM_PMU_BUS_ACCESS, ARM_PMU_BUS_CYCLES.
| Counter | Event | Description |
|---|---|---|
ARM_PMU_BUS_ACCESS (default) |
25 | Bus access |
ARM_PMU_BUS_CYCLES (default) |
29 | Bus cycles |
ARM_PMU_DTCM_ACCESS |
16392 | Data TCM access |
ARM_PMU_ITCM_ACCESS |
16391 | Instruction TCM access |
ARM_PMU_L1D_CACHE |
4 | L1 D-Cache access |
ARM_PMU_L1D_CACHE_ALLOCATE |
31 | Level 1 data cache allocation without refill |
ARM_PMU_L1D_CACHE_MISS_RD |
57 | Level 1 data cache read miss |
ARM_PMU_L1D_CACHE_RD |
64 | Level 1 data cache read |
ARM_PMU_L1D_CACHE_REFILL (default) |
3 | L1 D-Cache refill |
ARM_PMU_L1D_CACHE_WB |
21 | Level 1 data cache write-back |
ARM_PMU_L1I_CACHE |
20 | Level 1 instruction cache access |
ARM_PMU_LL_CACHE_MISS_RD |
55 | Last level data cache read miss |
ARM_PMU_LL_CACHE_RD |
54 | Last level data cache read |
ARM_PMU_MEMORY_ERROR |
26 | Local memory error |
ARM_PMU_MEM_ACCESS (default) |
19 | Data memory access |
mve: 34 counters
Section titled “mve: 34 counters”default selects ARM_PMU_MVE_INST_RETIRED, ARM_PMU_MVE_INT_MAC_RETIRED, ARM_PMU_MVE_LDST_RETIRED, ARM_PMU_MVE_STALL.
| Counter | Event | Description |
|---|---|---|
ARM_PMU_MVE_FP_HP_RETIRED |
520 | MVE half-precision floating-point instruction architecturally executed |
ARM_PMU_MVE_FP_MAC_RETIRED |
532 | MVE floating-point multiply or multiply-accumulate instruction architecturally executed |
ARM_PMU_MVE_FP_RETIRED |
516 | MVE floating-point instruction architecturally executed |
ARM_PMU_MVE_FP_SP_RETIRED |
524 | MVE single-precision floating-point instruction architecturally executed |
ARM_PMU_MVE_INST_RETIRED (default) |
512 | MVE instruction architecturally executed |
ARM_PMU_MVE_INT_MAC_RETIRED (default) |
552 | MVE multiply or multiply-accumulate instruction architecturally executed |
ARM_PMU_MVE_INT_RETIRED |
548 | MVE integer instruction architecturally executed |
ARM_PMU_MVE_LDST_CONTIG_RETIRED |
580 | MVE contiguous load or store instruction architecturally executed |
ARM_PMU_MVE_LDST_MULTI_RETIRED |
604 | MVE memory instruction targeting multiple registers architecturally executed |
ARM_PMU_MVE_LDST_NONCONTIG_RETIRED |
592 | MVE non-contiguous load or store instruction architecturally executed |
ARM_PMU_MVE_LDST_RETIRED (default) |
568 | MVE load or store instruction architecturally executed |
ARM_PMU_MVE_LDST_UNALIGNED_NONCONTIG_RETIRED |
664 | MVE unaligned noncontiguous load or store instruction architecturally executed |
ARM_PMU_MVE_LDST_UNALIGNED_RETIRED |
652 | MVE unaligned memory load or store instruction architecturally executed |
ARM_PMU_MVE_LD_CONTIG_RETIRED |
584 | MVE contiguous load instruction architecturally executed |
ARM_PMU_MVE_LD_MULTI_RETIRED |
608 | MVE memory load instruction targeting multiple registers architecturally executed |
ARM_PMU_MVE_LD_NONCONTIG_RETIRED |
596 | MVE non-contiguous load instruction architecturally executed |
ARM_PMU_MVE_LD_RETIRED |
572 | MVE load instruction architecturally executed |
ARM_PMU_MVE_LD_UNALIGNED_RETIRED |
656 | MVE unaligned load instruction architecturally executed |
ARM_PMU_MVE_PRED |
696 | Cycles where one or more predicated beats architecturally executed |
ARM_PMU_MVE_STALL (default) |
716 | Stall cycles caused by an MVE instruction |
ARM_PMU_MVE_STALL_BREAK |
723 | Stall cycles caused by an MVE chain break |
ARM_PMU_MVE_STALL_DEPENDENCY |
724 | Stall cycles caused by MVE register dependency |
ARM_PMU_MVE_STALL_RESOURCE |
717 | Stall cycles caused by an MVE instruction because of resource conflicts |
ARM_PMU_MVE_STALL_RESOURCE_FP |
719 | Stall cycles caused by an MVE instruction because of floating-point resource conflicts |
ARM_PMU_MVE_STALL_RESOURCE_INT |
720 | Stall cycles caused by an MVE instruction because of integer resource conflicts |
ARM_PMU_MVE_STALL_RESOURCE_MEM |
718 | Stall cycles caused by an MVE instruction because of memory resource conflicts |
ARM_PMU_MVE_ST_CONTIG_RETIRED |
588 | MVE contiguous store instruction architecturally executed |
ARM_PMU_MVE_ST_MULTI_RETIRED |
612 | MVE memory store instruction targeting multiple registers architecturally executed |
ARM_PMU_MVE_ST_NONCONTIG_RETIRED |
600 | MVE non-contiguous store instruction architecturally executed |
ARM_PMU_MVE_ST_RETIRED |
576 | MVE store instruction architecturally executed |
ARM_PMU_MVE_ST_UNALIGNED_RETIRED |
660 | MVE unaligned store instruction architecturally executed |
ARM_PMU_MVE_VREDUCE_FP_RETIRED |
676 | MVE floating-point vector reduction instruction architecturally executed |
ARM_PMU_MVE_VREDUCE_INT_RETIRED |
680 | MVE integer vector reduction instruction architecturally executed |
ARM_PMU_MVE_VREDUCE_RETIRED |
672 | MVE vector reduction instruction architecturally executed |
Generated from the counter registry at source tree fe269596788d6ab1604cf433559e96b5f48c1d13: 79 counters in 4 groups across 9 SoCs.
The Ethos-U counters are sampled from the NPU’s own PMU through the core
driver, per command stream, and only mean something for Vela-compiled
models on the ethos_u engine backend; layers the NPU never dispatched
are reported through the NPU_DISPATCHED column rather than as zeros.
3. Multi-pass capture
Section titled “3. Multi-pass capture”The Cortex-M55 PMU has 8 hardware counter slots; hpx chains pairs into
32-bit logical counters to avoid overflow, so each firmware pass captures
4 counters per group. A larger selection is planned into passes, each pass
builds, flashes and captures warmup + iterations inferences, and the
results merge into one per-layer table. profile_results.csv is always
the merged result; output.detailed: true adds detailed/profile_<group>.csv
and profile_<group>_<pass>.csv. Selecting all on every group spans
the whole catalogue and runs many independent passes; use default or an
explicit list while iterating.
4. Aggregation and iterations
Section titled “4. Aggregation and iterations”profiling.iterations inferences per pass, after profiling.warmup
discarded runs, combined per layer by profiling.aggregation:
| Method | Behaviour |
|---|---|
median (default) |
robust to corrupted or outlier iterations |
mean |
arithmetic average |
trimmed |
drops the high and low extremes, then averages |
All three first reject structurally invalid samples (a uint32 wrap, a
frozen-zero row) and log how many; a counter with no surviving samples is
omitted rather than replaced. profiling.per_layer: false captures
whole-model counters only. Every run also performs a clean end-to-end
pass with no per-layer instrumentation and reports its cycle count next
to the per-layer sum; a small delta between the two is the
instrumentation overhead and is shown in the terminal.
5. Read the results
Section titled “5. Read the results”- The terminal’s top-layers table: layers by cycles with their share of the total, and on a Cortex-M55 the MVE instruction share and MACs per MVE instruction.
profile_results.csv: one row per layer, one column per captured counter, merged across passes;overflowisTruefor a layer whose counter saturated, whose values are then a lower bound.detailed/profile_<group>.csvand per-pass files withoutput.detailed.model_explorer/me_overlay_<COUNTER>.json, one per counter, for Model Explorer.
| Derived metric | Formula | Where |
|---|---|---|
| L1D hit rate | (1 - L1D_CACHE_MISS_RD / L1D_CACHE_RD) x 100%, falling back to L1D_CACHE and L1D_CACHE_REFILL as a complete pair |
summary.json, detailed/memory.json |
| MVE instruction share | MVE_INST_RETIRED / INST_RETIRED x 100% |
terminal |
| MVE MAC density | MVE_INT_MAC_RETIRED / MVE_INST_RETIRED |
terminal |
| MVE load/store density | MVE_LDST_RETIRED / MVE_INST_RETIRED |
terminal |
| MVE stall share | MVE_STALL / CPU_CYCLES x 100% |
terminal |
A derived rate is omitted, not guessed, when no consistent counter pair covers every layer.
6. Apollo3 and Apollo4: the debugger stays attached
Section titled “6. Apollo3 and Apollo4: the debugger stays attached”On the Cortex-M4F families the DWT->CYCCNT cycle counter lives in the
core debug power domain, powered only while a debugger asserts the Debug
Access Port’s CDBGPWRUPREQ, which firmware cannot set. If the host
released the probe after reset the domain would power down mid-run and
every per-layer cycle count would read 0. The rtt and swo transports
hold a debugger attached anyway; for uart and usb_cdc hpx detects
these families and keeps a pylink session attached for the whole
capture. Apollo5 boards keep releasing the probe after reset: their
Armv8-M PMU does not need it and the secure bootloader prefers it. The
dedicated power binary never has a debugger, which is why it times its
window with STIMER on those families (SocCapabilities.power_window_timer).
Each SoC also reserves a fixed number of per-operator PMU records
(SocDef.pmu_max_ops). A model that runs more operators than that fails
the capture with HPX_ERROR=operator_count_exceeds_capacity on every
engine; the limit is per SoC rather than a configuration key.
Expected output
Section titled “Expected output”profile_results.csv with one column per requested counter,
overflow_detected: false in summary.json, and a clean-pass cycle count
close to the per-layer sum.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
overflow_detected: true |
a counter wrapped its 32-bit accumulator on a very long or hot layer, or that layer’s PMU read failed | fewer iterations, or fewer or narrower counters for that layer |
| selections silently ignored | a DWT-only SoC | expected; hpx profile --verbose shows the warning |
| per-layer cycles read 0 on Apollo4 | the probe was released early over uart or usb_cdc |
check for a custom reset or lifecycle override |
all takes a long time |
many independent build, flash and capture passes | narrow the selection |
| unknown counter name | case or spelling | copy it from the catalogue above |
| group not supported for this target | mve needs a Cortex-M55; ethos_npu needs Atomiq110 |
pick a group the family table lists |
What next
Section titled “What next”Power measurement to add energy to the same window, or Output files for every column the CSV carries.