# FVP runtime contract

## Validated combinations

| Subsystem | FVP model | CPU / NPU | Runtime engines | Memory modes |
| --- | --- | --- | --- | --- |
| Corstone-300 | `FVP_Corstone_SSE-300_Ethos-U55` | Cortex-M55 / Ethos-U55 | LiteRT, heliaRT, heliaAOT | `Shared_Sram` |
| Corstone-300 | `FVP_Corstone_SSE-300_Ethos-U65` | Cortex-M55 / Ethos-U65 | LiteRT, heliaRT, heliaAOT (LiteRT and heliaRT run; heliaAOT is accepted but not yet run on it) | `Shared_Sram` |
| Corstone-320 | `FVP_Corstone_SSE-320` | Cortex-M85 / Ethos-U85 | LiteRT, heliaRT, heliaAOT | `Sram_Only`, `Shared_Sram`, `Dedicated_Sram` |

The platform's NPU picks the model (`helia_npu.fvp.resolve_fvp_target_profile`);
`fvp.subsystem` only insists on a subsystem, and one that does not simulate the
platform's NPU is refused. The Corstone-300 archive carries both of its models,
and the U65 model shares the U55 model's memory map, timing adapters and
parameter names, so it differs only in the binary and the NPU the runner is
built for. Corstone-310 and 315 pair a Cortex-M85 with the same U55 and U65;
helx does not use them, since the Corstone-300 already runs both NPUs, and has
not been validated on them.

Unsupported combinations are rejected before CMake/FVP execution.

Per-segment PMU profiling is currently validated for LiteRT and heliaRT. The
pipeline rejects heliaAOT per-segment requests rather than silently omitting
the fenced measurement.

## Placement contract

Vela exposes two logical memory areas (the naming across Vela, the SoC, the PMU and the FVP adapters is in [port_mapping.md](https://ambiqai.github.io/helia-estimator/guides/port-mapping/)). Platform memory names describe timing and energy; the FVP linker uses its physical SRAM and DDR regions.

| Mode | Model / constants | Tensor arena | Fast NPU cache |
| --- | --- | --- | --- |
| `Sram_Only` | AXI0 / SRAM | AXI0 / SRAM | none |
| `Shared_Sram` | AXI1 / FVP DDR | AXI0 / SRAM | none |
| `Dedicated_Sram` | AXI1 / FVP DDR | AXI1 / FVP DDR | AXI0 / SRAM |

For generic MRAM/PSRAM platforms, FVP DDR is a physical proxy. Platform timing-adapter and energy parameters model the selected memory technology; the simulator does not contain literal MRAM or PSRAM.

Corstone-300 is currently restricted to `Shared_Sram` because:

- Vela model/weights/command stream are safely loaded through the FVP DDR region.
- The TFLM tensor arena is placed in SRAM.
- Both C300 timing adapters are configured from platform-derived values.
- `Sram_Only` initialized-model loading and `Dedicated_Sram` fast-cache allocation have not been validated.

### heliaAOT placement

Externally bound heliaAOT constant arenas use `network_model_sec` and follow
the model/constant placement. Mutable arenas use `.bss.tensor_arena` and follow
the arena placement. Codegen tests assert these section annotations and exact
constant-blob sizes. LiteRT, heliaRT, and heliaAOT KWS executions have been
validated on C300; automated linker-map assertions remain future coverage.

The C300 smoke configuration uses `apollo510_evb` only as a Cortex-M55
heliaAOT generation and memory-planner proxy. The execution target, linker,
timing adapters, PMU counters, and result data are Corstone-300.

## PMU profiles

The table below describes U85. U55/U65 `default` and
`default_ext_rd_stall` do not count EXT write beats. Missing counts stay
unknown, so those profiles cannot supply complete counter-based energy.
U85 SRAM stall events sum across AXI ports: event/NPU-active-cycle ratios
can exceed 100% and do not measure elapsed-time occupancy. See
[Arm's PMU event definitions](https://documentation-service.arm.com/static/67b5ba01ce2747241fce860f).

`defaults.fvp.pmu_profile` selects which counter set the runner firmware programs and prints. The Ethos-U85 has eight event counters plus the cycle counter; `NPU_ACTIVE`, `NPU_IDLE` and the cycle count are always reported.

| Profile | Extra counters | Use |
| --- | --- | --- |
| `default` | SRAM / EXT read and write data beats (four counters) | Energy estimation; every standard run |
| `ext_stall` | EXT read/write request-stall and write-beat-stall counters | Diagnosing port-1 (HBLRAM / PSRAM) back-pressure |
| `default_ext_rd_stall` | The `default` beat counters with EXT read-request-stalled instead of the EXT1 read split | Energy plus one stall signal |
| `dv_align` | `MAC_ACTIVE`, `MAC_STALLED_BY_IB`, `MAC_STALLED_BY_W_OR_ACC` plus the four beat counters (Ethos-U85 only) | Correlating against RTL testbench and FPGA (heliaPROFILER) captures, which report the same set |
| `port_stall` | `MAC_ACTIVE`, SRAM and EXT `RD_STALL_LIMIT` and `RD_TRAN_REQ_STALLED`, SRAM and EXT read beats (Ethos-U85 only) | Locating where the NPU waits for memory, per port; the capture the RTL testbench and the FPGA used to find the HP stall. No write-beat counters, so the measured energy estimate is skipped for these runs |
| `sram_stall` | `MAC_ACTIVE`, SRAM read/write `STALL_LIMIT` and `TRAN_REQ_STALLED`, SRAM read/write beats (Ethos-U85 only) | Read/write queue diagnostics in one eight-counter capture. EXT traffic is uncounted, so the measured energy estimate is skipped |

Queue counters indicate request pressure. Reads, writes and useful NPU work can overlap, so these counters are not additive lost time or a predicted speedup. The fitted write limit is a timing-model parameter, not proof of the physical queue depth. Keep normal inference results separate from the recompiled fenced run. Fenced captures with a reported PMU overflow are rejected before diagnostic tables or timelines are written. Legacy logs without an overflow flag remain readable, but do not establish that overflow was absent.

Each profile is a `helia_npu.pmu.PmuProgram` (ordered driver event per slot,
unit, printed label); helx writes `render_c_header(program)` into the build
directory and the runner compiles its counter table from it, so the firmware
and the host's `required_pmu_fields` come from one definition.

The runner executes one shape of program: the dedicated cycle counter (CCNT,
ungated, 64-bit) is the total, slot 0 is `NPU_ACTIVE`, and the one derived
field `NPU IDLE` is CCNT minus slot 0. A program with another cycle source or
derived set, such as heliaPROFILER's `hpx_default_v1` (cycle count in an event
slot, nothing derived), or one that wants the CCNT gated, is available as data
but is refused by name before the runner build is configured
(`helia_estimator.runtime.pmu_program`; the config schema's `PmuProfile` enum
rejects unknown names even earlier) and again by the preprocessor in
`ethosu_profiler.h`, which re-checks the cycle source, gating, width, slot 0
and derived-field count (the printed label is checked in Python only); no
idle field is synthesised and no other program is substituted.

How the counters are read is the consumer's, not the program's, and is
recorded per consumer as `PmuProgram.acquisition`. helx's runner resets the
counters before each command-stream dispatch, sums the per-dispatch reads in
64-bit in firmware, and marks the capture overflowed if any counter (event
slot or CCNT) sets its overflow bit. heliaPROFILER's firmware, as that tool
documents it, accumulates 32-bit per layer and checks overflow over its
selected event mask. Two captures are comparable only when they ran the same
program under the same acquisition (`PmuProgram.comparable_with`); equal
event lists alone do not make helx and heliaPROFILER numbers interchangeable.

The Vela system-config section a run compiles with is `<soc id>_<mode>` (`atomiq110_ULP`); it names the summary CSV and is recorded as `vela.profile` / `vela.system_config` in the bundle.

On the FVP the cycle counter is quantised (totals end in `…068` because the simulator advances in 1,000-cycle steps around the measurement window); the event counters are exact.

## Target-specific hardware

| Setting | Corstone-300 | Corstone-320 |
| --- | ---: | ---: |
| NPU base | `0x48102000` | `0x50004000` |
| NPU IRQ | 56 | 16 |
| UART0 base | `0x49303000` | `0x48203000` |
| TA0 / TA1 | `0x48103000` / `0x48103200` | `0x50006000` / `0x50006800` |
| FVP NPU parameter | `ethosu.num_macs` | `mps4_board.subsystem.ethosu.num_macs` |

## Host communication

Production runs use UART0 redirected to a result file:

```text
-C <board>.uart0.out_file=<path>
-C <board>.uart0.unbuffered_output=1
-C <board>.uart0.shutdown_on_eot=1
-C <board>.uart0.shutdown_tag=EXITTHESIM
```

The firmware retarget sends human-readable results, EOT, and `EXITTHESIM`. The host applies the independent `host_timeout_seconds` wall-clock timeout, because FVP `--simlimit` limits simulated time rather than host elapsed time.

Alternatives:

- `uart0.out_file=-`: useful for interactive debugging, but still UART transport.
- Semihosting: possible, but requires a separate firmware retarget/link contract and is not currently validated.
- Iris/CADI: appropriate for debug, registers, and memory inspection; not used as the result channel.
- Telnet terminals: modeled UART endpoints; disabled for automated runs.

Standalone validation:

```bash
scripts/run_corstone300_uart_probe.sh
```

This independently verifies UART output, EOT/tag termination, U55 driver initialization, and IRQ vector wiring.
