# Engine adapters

Show the seam that keeps the pipeline engine-agnostic. Everything an
engine contributes to a run passes through one `prepare()` call and one
typed artifact object; the rest of hpx reads those and never imports an
engine package.

## Diagram

Engine dispatch: From the engine.type key to the objects the rest of the pipeline reads. The shared helpers are called by more than one adapter; the consumers never import an adapter.

- Registry: engines/__init__.py
  - EngineType
  - adapter factories
  - get_adapter
- Adapters
  - tflm
  - helia_rt
  - helia_aot
  - executorch
- Shared helpers
  - cmsis_nn
  - ethos_u
- Artifacts: engines/base.py
  - EngineArtifacts
  - TflmArtifacts
  - HeliaRtArtifacts
  - HeliaAotArtifacts
  - ExecutorchArtifacts
- Consumers
  - firmware render
  - memory planning
  - workspace fingerprint
  - model analysis
Text description of the diagram

- Registry (`engines/__init__.py`): `EngineType`, adapter factories, `get_adapter`
- Adapters: `tflm`, `helia_rt`, `helia_aot`, `executorch`
- Shared helpers: `cmsis_nn`, `ethos_u`
- Artifacts (`engines/base.py`): `EngineArtifacts`, `TflmArtifacts`, `HeliaRtArtifacts`, `HeliaAotArtifacts`, `ExecutorchArtifacts`
- Consumers: firmware render, memory planning, workspace fingerprint, model analysis

## The blocks

### Registry

`EngineType` is a `StrEnum` with four members, `tflm`, `helia-rt`,
`helia-aot` and `executorch`, so YAML and Jinja compare against the hyphen
form without unwrapping. `_ADAPTER_FACTORIES` maps each member to a
deferred import, so registering an engine does not import its package
until `get_adapter(engine_type)` asks for it.

### The adapter protocol

`EngineAdapter` in `engines/base.py` has a `name`, an `engine_type`, and
`prepare(config, work_dir)` returning that engine's artifact subtype. Two
hooks let an engine shape memory placement:
`apply_arena_placement_override()` for engines that emit typed arena
regions, and `check_psram_placement()` together with
`psram_weights_source`, which is how the PSRAM preflight on
[Memory placement](https://ambiqai.github.io/helia-profiler/guide/memory/#6-psram) is enforced per engine.

### What each adapter's `prepare()` does

| Engine | Module | Steps | Modules it adds |
|---|---|---|---|
| tflm | `engines/tflm.py` | resolves the `nsx-tflite-micro` module and the reference or CMSIS-NN backend | `nsx-tflite-micro` (plus the CMSIS-NN provider for that backend) |
| helia-rt | `engines/helia_rt/` | validates the variant and toolchain; with no override returns the pinned registry module; `source_path` installs a local tree as a module; `dist_path` or a release source verifies the matching core and toolchain archive and generates a wrapper (`nsx_module.py`, `artifacts.py`, `download.py`) | `nsx-helia-rt` |
| helia-aot | `engines/helia_aot/` | maps the board to a compiler platform and runs the `helia-aot` compiler (`compile.py`); extracts the operator manifest and memory plan from the compiler's context (`manifest.py`); vendors the generated model as a local module named by `engine.config.module_name` (default `hpx_model`); resolves the CMSIS-NN provider | `nsx-cmsis-nn`, the model module |
| executorch | `engines/executorch.py` | resolves the `nsx-executorch` checkout at the commit the compatibility baseline pins (auto-cloned into the hpx cache, or `engine.config.source_path`); reads the optional `<model>.pte.json` sidecar for the provider, op lists, planned arena and I/O sizes; resolves the region of each static RAM buffer; generates a wrapper module and emits the provider module before it | the CMSIS-NN provider, the `nsx-executorch` wrapper |

The shared helpers sit beside the adapters: `engines/cmsis_nn.py` resolves
the `nsx-cmsis-nn` module reference and its CMake variables for every
engine that links it, and `engines/ethos_u.py` holds the NSX module
identity of the Ethos-U backend. Support for each engine and backend per
SoC is a registry question, answered on [Engines](https://ambiqai.github.io/helia-profiler/guide/engines/) and
[Boards and targets](https://ambiqai.github.io/helia-profiler/guide/boards/), not here.

### Artifacts

`EngineArtifacts` carries only what every engine produces: `engine_type`,
`engine_header` (no default; each adapter states its own), `extra_modules`,
`cmake_vars` and `memory_plan`. Engine-specific fields live on the subclass, so reading
another engine's field is an `AttributeError` at the access site rather
than a silent `None`; each subclass pins its `engine_type` and raises
`ValueError` if constructed with another. Four `resolved_*` properties
(`backend`, `version`, `variant`, `toolchain_tag`) are how the dependency
workspace fingerprint records engine identity without knowing which engine
it holds. `tests/contracts/test_engine_artifact_types.py` asserts the
adapter-to-artifact map is complete over `EngineType`.

### Consumers

| Consumer | Reads | Module |
|---|---|---|
| firmware render | `engine_header`, `cmake_vars`, `extra_modules`, the subtype's fields (AOT operator manifest and arena regions, ExecuTorch buffer sizes) | `firmware/context.py`, `firmware/project.py` |
| memory planning | `memory_plan`, the adapter's placement hooks | `stages/plan_memory.py`, `placement.py` |
| workspace fingerprint | the `resolved_*` properties | `deps/dependencies.py` |
| model analysis | the engine's view of the graph for `hpx analyze --engine` | `evaluation/engine_analysis.py` |

### How the engine shapes the firmware

| Engine | Main template | Module graph on top of the board's starter profile |
|---|---|---|
| tflm | `main.cc.j2` | `nsx-tflite-micro`, with the CMSIS-NN provider first for that backend |
| helia-rt | `main.cc.j2` | `nsx-helia-rt` |
| helia-aot | `main_aot.cc.j2` | `nsx-cmsis-nn`, then the generated model module |
| executorch | `main_executorch.cc.j2` | the CMSIS-NN provider, then `nsx-executorch` |

The provider is ordered before `nsx-executorch` on purpose: NSX must
configure exactly one CMSIS-NN provider before the runtime creates its
bridge target. The binary section sizes in `summary.json` record what the
engine choice cost in flash and RAM for the run at hand.

## Where the code lives

| Block | Path under `src/helia_profiler/` |
|---|---|
| registry | `engines/__init__.py` |
| protocol and artifacts | `engines/base.py` |
| adapters | `engines/tflm.py`, `engines/helia_rt/`, `engines/helia_aot/`, `engines/executorch.py` |
| shared helpers | `engines/cmsis_nn.py`, `engines/ethos_u.py` |
| stage | `stages/prepare_engine.py` |
| contract test | `tests/contracts/test_engine_artifact_types.py` |

## What next

[Firmware generation](https://ambiqai.github.io/helia-profiler/guide/concepts/firmware/) for what the render context does with
the artifacts; [Adding an engine](https://ambiqai.github.io/helia-profiler/guide/concepts/adding-an-engine/) for the touch
points a fifth adapter has to cover.
