GitHub

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.

Note · Honest scope

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 in src/hal/common/syn_dsp_soft.c cover FFT and matmul) and return -ENOTSUP for 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 SYNAPTIC Kconfig dependency (currently SOC_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:

  1. Copy the stub files into src/hal/<your_soc>/ and rename.
  2. Replace the fake-inference body of invoke() with your accelerator driver, keeping every guard clause.
  3. Run the unit suite against your board (the twister testcase.yaml already allows hardware platforms) until syn_npu_suite and syn_dsp_suite pass.
  4. Run samples/hello_inference as 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:

CodeWhen
-EINVALNULL pointers, zero sizes, an input/output index other than 0 (single-tensor I/O in v0.1.0), resume from a non-suspended state.
-EPERMCalled before init(), or execution calls before a model is loaded.
-EBUSYAny execution call while the NPU is BUSY.
-ENOMEMModel or input exceeds the backend's capacity (the stub caps these at 256 KB and 1 KB respectively — report yours truthfully via get_caps()).
-EALREADYDouble init().
-ENOTSUPHonest 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

BackendLocationStatus
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).
MCXN947src/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.