Docs / API reference / syn_hal_npu.h
syn_hal_npu.hv0.1.0
<synaptic/syn_hal_npu.h> is the NPU hardware abstraction layer. It defines a backend-neutral contract for a neural accelerator — lifecycle, capability discovery, a four-state execution state machine, synchronous and asynchronous invoke, and power management. The same API is served by the eIQ Neutron backend on the FRDM-MCXN947 and by a deterministic software stub on QEMU, so application code and the inference scheduler above it never touch hardware directly.
Types & constants
| Name | Kind | Description |
|---|---|---|
syn_npu_state_t | enum | NPU execution state machine. SYN_NPU_STATE_IDLE — initialized and ready to accept work; SYN_NPU_STATE_BUSY — an invoke is in progress, all other execution calls return -EBUSY; SYN_NPU_STATE_ERROR — the last invoke failed on the hardware; SYN_NPU_STATE_SUSPENDED — clock-gated by syn_hal_npu_suspend(), requires syn_hal_npu_resume() before use. |
syn_npu_dtype_t | enum | Tensor data types a backend may support: SYN_NPU_DTYPE_INT8, SYN_NPU_DTYPE_UINT8, SYN_NPU_DTYPE_INT16, SYN_NPU_DTYPE_FLOAT16, SYN_NPU_DTYPE_FLOAT32. Enumerator positions map to bits in syn_npu_caps_t.supported_dtypes. |
syn_npu_caps_t | struct | Backend capability report: name (backend identifier string), max_ops_per_sec, scratch_size (bytes of scratch memory the backend uses), supported_dtypes (bitmask over syn_npu_dtype_t), supports_async. |
syn_npu_done_cb_t | typedef | void (*)(int status, void *user_data) — completion callback for syn_hal_npu_invoke_async(). status is 0 on success or a negative errno. |
Functions
| Signature | Description |
|---|---|
int syn_hal_npu_init(void) | Initialize the NPU backend and enter the IDLE state. Returns 0 on success, -EALREADY if already initialized. |
void syn_hal_npu_deinit(void) | Release the NPU. Clears the loaded model and returns the backend to its uninitialized state. |
int syn_hal_npu_get_caps(syn_npu_caps_t *caps) | Fill caps with the active backend's capability report. Returns 0 on success, -EINVAL if caps is NULL. |
syn_npu_state_t syn_hal_npu_get_state(void) | Return the current state machine state. Never fails. |
int syn_hal_npu_load_model(const uint8_t *model_data, size_t model_size) | Load a compiled model blob. Returns 0 on success, -EINVAL for a NULL/empty blob, -EPERM if not initialized, -EBUSY while BUSY, -ENOMEM if the blob exceeds the backend's model limit (stub: 256 KB; Neutron: flash-slot capacity, 440 KB − 64 B, since v0.4.0 — see the backend notes below). |
int syn_hal_npu_set_input(uint8_t index, const void *data, size_t size) | Copy input tensor data into the backend's input buffer. Both v0.1.0 backends accept only index == 0. Returns 0 on success, -EINVAL for a bad pointer/size/index, -EPERM if no model is loaded, -EBUSY while BUSY, -ENOMEM if size exceeds the backend input buffer. |
int syn_hal_npu_invoke(void) | Run one inference synchronously. Transitions IDLE → BUSY → IDLE (or ERROR on hardware failure). Returns 0 on success, -EPERM if no model is loaded, -EBUSY if already running. |
int syn_hal_npu_invoke_async(syn_npu_done_cb_t cb, void *user_data) | Start an inference and invoke cb on completion. Returns -ENOTSUP in both backends — interrupt-driven async invoke arrives with the eIQ Neutron SDK integration, still pending after Phase 3 (v0.3.0). |
int syn_hal_npu_get_output(uint8_t index, void *data, size_t *size) | Copy the output tensor into data and write the byte count to *size. Only index == 0 is accepted. Returns 0 on success, -EINVAL for bad arguments, -EPERM if not initialized, -EBUSY while BUSY. |
int syn_hal_npu_suspend(void) | Enter SUSPENDED for power savings. Returns 0 on success, -EPERM if not initialized. |
int syn_hal_npu_resume(void) | Leave SUSPENDED and return to IDLE. Returns 0 on success, -EPERM if not initialized, -EINVAL if the NPU was not suspended. |
Backends
mcxn947 — eIQ Neutron (src/hal/mcxn947/syn_hal_npu_neutron.c). Reports name = "neutron", max_ops_per_sec = 100000000, INT8/UINT8 dtypes (supported_dtypes = 0x03), supports_async = true. Input buffer is 96×96×3 bytes (27,648 — one RGB camera frame); model limit is the flash slot capacity (440 KB − 64 B header); output buffer 256 bytes. The model limit was an arbitrary 256 KB placeholder until the Phase 4 board campaign found it the honest way: a valid, stored, activated 432 KB OTA model was refused at load with -ENOMEM (rollback restored service in one command) — the cap now tracks what a slot can hold, and the real bound arrives with the Neutron SDK. Honest status: this backend still runs the same deterministic software inference as the stub — clock gating, power-domain setup, and real Neutron execution are placeholders pending the eIQ SDK integration. The state machine, buffer limits, and error paths are real and tested.
stub — deterministic software fallback (src/hal/stub/syn_hal_npu_stub.c). Reports name = "stub", max_ops_per_sec = 0, all five dtypes (0x1F), supports_async = false. Input buffer is 1,024 bytes. Inference is a deterministic 10-class classification: the input bytes are summed and sum % 10 selects the winning class, which is written as INT8 confidence 127. Repeatable output is what lets the QEMU test suite assert exact results. Latency is simulated with a plain volatile loop, since QEMU's Cortex-M3 target lacks the timer needed by k_busy_wait().
Usage
Direct HAL use, mirroring what the scheduler does internally for one frame:
#include <synaptic/syn_hal_npu.h>
syn_npu_caps_t caps;
uint8_t result[256];
size_t result_len = sizeof(result);
syn_hal_npu_init();
syn_hal_npu_get_caps(&caps);
printk("NPU: %s, async: %d\n", caps.name, caps.supports_async);
syn_hal_npu_load_model(model_blob, model_blob_len);
syn_hal_npu_set_input(0, frame, frame_len);
if (syn_hal_npu_invoke() == 0) {
syn_hal_npu_get_output(0, result, &result_len);
/* result[0..result_len-1] holds INT8 class scores */
}Notes
Both v0.1.0 backends implement a single input and a single output tensor: any index other than 0 returns -EINVAL. Multi-tensor models arrive together with the Neutron SDK integration.
- All execution calls are rejected with
-EBUSYwhile the state isBUSY; pollsyn_hal_npu_get_state()if you share the NPU between threads. syn_hal_npu_invoke_async()currently returns-ENOTSUPeverywhere, even though the Neutron backend advertisessupports_async = truein anticipation of the interrupt-driven path. Check the return code, not the capability bit.load_model()keeps a pointer to the caller's blob on the Neutron backend — the model data must stay valid (e.g. in flash) for the lifetime of the session.- Most applications should use
syn_infer.hinstead of calling the HAL directly; the scheduler handles buffer staging and profiling hooks.