Docs / Guides / Porting to a new board
Porting to a new boardv0.1.0
All hardware dependence in SynapticOS is concentrated behind three frozen HAL headers in include/synaptic/: syn_hal_npu.h, syn_hal_dsp.h, and syn_hal_dma.h. A port is a new set of C files implementing those functions, plus the Kconfig and devicetree wiring to select them. The core runtime — arena, registry, scheduler, profiler, shell — never touches a register directly.
The port surface is deliberately vendor-neutral — the same function contracts are designed to fit accelerators like Arm Ethos-U or RISC-V NPUs. But exactly two backends exist: the software stub (src/hal/stub/) and the MCXN947 backend (src/hal/mcxn947/). As of v0.2.0 the MCXN947 PowerQuad DSP driver drives real hardware; its Neutron NPU driver is still stub-backed pending SDK integration. No third-party port has been done yet; expect to be the first.
What a port consists of
- NPU backend (required) — implement the full function set of
syn_hal_npu.h: lifecycle (init/deinit/get_caps/get_state), execution (load_model,set_input,invoke,invoke_async,get_output), and power management (suspend/resume). - DSP backend (optional) —
syn_hal_dsp.h: normalize, softmax, argmax, FFT, Q15 matrix multiply. You may fall back to the pure-software versions (the shared kernels insrc/hal/common/syn_dsp_soft.ccover FFT and matmul) and return-ENOTSUPfor anything you neither accelerate nor fall back on. - DMA backend (optional) —
syn_hal_dma.h: channel configure/start/stop/remaining with peripheral endpoints (camera, SPI, memory) and an optional circular mode. - Board wiring — extend the
SYNAPTICKconfig dependency (currentlySOC_SERIES_MCXNX4X || BOARD_QEMU_CORTEX_M3) to cover your SoC, add your backend sources to the build for that board, and describe the accelerator and any dedicated SRAM in the board devicetree.
The stub is the reference implementation
Start by reading src/hal/stub/syn_hal_npu_stub.c and syn_hal_dsp_stub.c. They are small, complete, and are exactly what the 108-test suite validates against — so a backend that behaves like the stub passes the suite by construction. The stub documents the expected semantics precisely: which argument combinations are rejected, which state each call is legal in, and what each error code means. A practical porting flow:
- Copy the stub files into
src/hal/<your_soc>/and rename. - Replace the fake-inference body of
invoke()with your accelerator driver, keeping every guard clause. - Run the unit suite against your board (the twister
testcase.yamlalready allows hardware platforms) untilsyn_npu_suiteandsyn_dsp_suitepass. - Run
samples/hello_inferenceas the end-to-end smoke test.
The contract your backend must honor
State machine. The NPU reports one of four states: IDLE, BUSY, ERROR, SUSPENDED. A normal inference traverses IDLE → BUSY → IDLE. suspend() moves any initialized backend to SUSPENDED; resume() is only legal from SUSPENDED (return -EINVAL otherwise) and returns to IDLE. While BUSY, load_model, set_input, invoke, and get_output must all refuse with -EBUSY.
Error codes. Standard errno values, consistently:
| Code | When |
|---|---|
-EINVAL | NULL pointers, zero sizes, an input/output index other than 0 (single-tensor I/O in v0.1.0), resume from a non-suspended state. |
-EPERM | Called before init(), or execution calls before a model is loaded. |
-EBUSY | Any execution call while the NPU is BUSY. |
-ENOMEM | Model or input exceeds the backend's capacity (the stub caps these at 256 KB and 1 KB respectively — report yours truthfully via get_caps()). |
-EALREADY | Double init(). |
-ENOTSUP | Honest signal for anything you do not implement — async invoke, FFT, matmul. Never fake success. |
Capabilities. get_caps() must fill syn_npu_caps_t accurately: backend name, max_ops_per_sec, required scratch size, a bitmask of supported dtypes (INT8/UINT8/INT16/FLOAT16/FLOAT32), and whether invoke_async actually works. The runtime and the syn npu caps shell command surface these directly.
Memory and alignment. Tensor buffers handed to set_input() come from the SynapticOS arena, which is 16-byte aligned. If your accelerator demands stricter alignment or a dedicated SRAM window, express it via scratch_size in the caps and your board devicetree rather than assuming the caller knows.
Determinism where possible. The stub's deterministic output is what makes the CI suite meaningful. Keep your backend's validation-mode behavior reproducible so the same test assertions can run against hardware.
The two existing backends
| Backend | Location | Status |
|---|---|---|
| Stub (QEMU / any board) | src/hal/stub/ | Complete software fallback: deterministic NPU emulation, pure-software DSP including FFT and Q15 matmul (shared kernels from src/hal/common/, Phase 2). |
| MCXN947 | src/hal/mcxn947/ | syn_hal_npu_neutron.c (eIQ Neutron NPU), syn_hal_dsp_pq.c (PowerQuad), syn_hal_dma_sdma.c (SmartDMA). As of v0.2.0 the PowerQuad driver runs the FFT and Q15 matmul on real hardware (boot-time self-calibration, software fallback, measured 5.51×/1.66× vs software); the Neutron path still runs the deterministic software inference pending NXP SDK integration, and SmartDMA is a placeholder. |