Skip to content

Ethos-U with heliaAOT

heliaAOT understands the Ethos-U command streams generated by Arm Vela. When you feed a Vela-converted LiteRT/TFLite model into heliaAOT, every Ethos-U-supported subgraph is collapsed into a single ETHOS_U operator whose first input is the command stream. The generated C module emits a thin wrapper around ethosu_invoke_v3, automatically wiring up the command stream plus all required base addresses for inputs, outputs, and Vela-added auxiliary buffers.

The Ethos-U driver is a hard dependency

A module containing ETHOS_U operators will not compile without the Ethos-U core driver on the include path. If <ethosu_driver.h> is not found, the generated translation unit stops with an #error.

This is deliberate. A missing header used to compile a stub that returned success without touching the NPU, so an integrator could ship a "passing" build whose accelerator never ran. Failing the compile makes the missing dependency impossible to ignore.

A non-functional stub is still available for host-side codegen tests and for bring-up on a machine without the driver tree, but it must be requested explicitly:

-DHELIA_ETHOSU_ALLOW_STUB=1

The stub returns success without executing the NPU. Never use it in a build that is expected to run the model.

Run a LiteRT model through Vela and heliaAOT

  1. Compile with Vela. Install Vela (python -m pip install ethos-u-vela) and run it on the original .tflite:
vela ./models/my_model.tflite \
  --accelerator-config ethos-u85-256 \
  --memory-mode Shared_Sram \
  --output-dir build/vela_out \
  --output-format tflite

Adjust the accelerator/system configs to match your target — the example above matches the Ethos-U85-256 in the Atomiq110. Vela produces a new .tflite where each Ethos-U region is encoded as a single custom op plus a command stream tensor.

  1. Convert with heliaAOT. Point heliaAOT at the Vela output to generate the C module:
helia-aot convert \
  --model.path build/vela_out/my_model_vela.tflite \
  --module.path ./out/ethos_u_module \
  --platform.name atomiq110 \
  --test.enabled

No extra flags are needed for Ethos-U; the converter detects the ETHOS_U custom op, lifts the command stream into a const tensor, and keeps any non-Ethos-U operators as standard CPU/Helium kernels.

Pick --platform.name to match the silicon you are targeting. atomiq110 is the Atomiq110, which pairs a Cortex-M55 host with an Arm Ethos-U85-256 NPU, and is the only registered platform that declares SocCapability.NPU today. Targeting a platform without an NPU is rejected at conversion time — see Platform gating below.

  1. Integrate the driver. Link the Ethos-U core driver (or the FVP-provided driver) into your application. The generated CMake module advertises and wires this dependency for you — see the emitted CMakeLists.txt. Without it the build fails by design; see the driver-contract note above.

Memory placement

Ethos-U requires its buffers on 16-byte boundaries. heliaAOT stamps a 16-byte alignment hint on the command stream, every Vela-added auxiliary tensor, and the operator's inputs and outputs, so the memory planner places all NPU-visible buffers correctly without any configuration on your part.

Vela emits its own OfflineMemoryAllocation alongside the command stream. heliaAOT honors those offsets rather than re-planning the region, so the addresses the command stream was compiled against remain valid.

Platform gating

Converting a Vela model against a platform without an NPU is rejected at conversion time. Platforms declare an accelerator via SocCapability.NPU (atomiq110 does); targeting any other platform fails with an actionable error instead of surfacing later in the integrator's build.

Opting out

Some setups legitimately run Ethos-U kernels on a host platform that declares no SoC NPU — most notably the Corstone-300 FVP, where the accelerator is provided by the simulator rather than by the modelled silicon. For those cases, set the require_platform_npu attribute to "false" on the ETHOS_U operator in your config:

operators:
  - type: ETHOS_U
    attributes:
      require_platform_npu: "false"

This disables the gate only; nothing else about codegen changes. Use it for simulation and experimentation, not to ship a build against silicon that has no NPU.

The config file is the only route: --operators is a nested-model flag, so it rejects inline values on the command line (a bare --operators only clears a list supplied by YAML). Put the ruleset above in your config and pass it with --path config.yaml.

Status and limitations

Known gaps

  • Ethos-U85 has no automated coverage. End-to-end tests exercise Ethos-U55 on the Corstone-300 FVP (with the gate opted out, as above). The U85 path that atomiq110 targets is not covered by CI. Tracked in #78.

Platform gating: converting a Vela model against a platform without an NPU is rejected at conversion time. Platforms declare an accelerator via SocCapability.NPU (atomiq110 does); targeting any other platform fails with an actionable error instead of surfacing later in the integrator's build.