Docs / Guides / The samples
The samplesv0.4.0
The repository ships five samples under samples/. Four are implemented and hardware-verified: hello_inference (verified on the FRDM-MCXN947 in Phase 1 and re-verified through the Phase 2 scheduler on 2026-07-12), face_detection (the Phase 2 vision-pipeline demo, QEMU-tested and verified on the board), dual_model (the Phase 3 dual-core demo, board-verified on 2026-07-14 and extended with the dual-core OTA demo in Phase 4), and ota_update (the Phase 4 OTA demo, board-verified on 2026-07-15). keyword_spotting remains an intentional skeleton: it builds, boots, and prints a banner, but its body is marked "Implementation pending" on the roadmap.
At a glance
| Sample | Demonstrates | Status |
|---|---|---|
hello_inference | Full runtime round-trip: init, model registry, scheduler-driven inference (syn_infer_run_sync()), DSP argmax, memory stats, profiling summary, shell | Implemented; hardware-verified on both the Phase 1 direct-HAL path and the Phase 2 scheduler path |
face_detection | Continuous vision pipeline: resize → normalize → quantize → model → decode → NMS, with per-frame profiling | Implemented and hardware-verified (Phase 2) — synthetic frames; camera/LCD bring-up deferred |
keyword_spotting | Audio keyword detection pipeline (planned) | Skeleton — implementation pending |
dual_model | Asymmetric dual-core: CPU0 serves the AI runtime, CPU1 alternates two models over IPC | Implemented and hardware-verified (Phase 3) — stub-NPU model path |
ota_update | End-to-end OTA model update over UART: factory install, streamed .synm update, activate, hot reload, rollback | Implemented and hardware-verified (Phase 4) — stub-NPU model path |
hello_inference
The reference sample and the one used for all Phase 1 verification. As of Phase 2 its inference step runs through the scheduler rather than the raw HAL, so the profiler now captures real per-stage data. main() walks the API surface in order:
- Initialize the runtime with
syn_init() - Register a model (
test_classifyv1.0.0, INT8 in/out) in the registry - Load the model binary via
syn_hal_npu_load_model() - Allocate a 1×16×16×3 INT8 input tensor (768 bytes) from the arena with
syn_mem_tensor_alloc() - Fill it with a gradient test pattern
- Run inference through the scheduler with
syn_infer_run_sync(), timed with the cycle counter — the profiling marks fire at the stage boundaries, sosyn prof lasthas real data afterwards - Find the top prediction with
syn_hal_dsp_argmax() - Print memory statistics (
syn_mem_print_stats()) - Print the profiling summary (
syn_prof_print_summary()), then keep the shell alive
The 16×16×3 input size is chosen deliberately: it fits both the QEMU NPU stub (1 KB max input) and the FRDM hardware path. The Phase 1 direct-HAL path was verified at 1038 us end-to-end on the FRDM board versus 781 us on QEMU. On QEMU, the Phase 2 scheduler path (syn_infer_run_sync()) measures 1361 us wall against the same 781 us direct-HAL baseline — icount timing, stub NPU backend; the prediction is identical (class 0, confidence 127). On the FRDM board (v0.2.0, 2026-07-12) the scheduler path measures 1130 us wall against the 1038 us Phase 1 direct-HAL capture: about 92 us for the full scheduler path, of which the profiled dispatch overhead is only ~1 us (total 1069 us vs 1068 us NPU) — the rest is job submission, the scheduler thread, and completion signaling outside the marked region. All NPU timings are the deterministic stub backend.
# FRDM-MCXN947 (with shell enabled) west build -b frdm_mcxn947/mcxn947/cpu0 synaptic-os/samples/hello_inference --pristine # QEMU (boards/qemu_cortex_m3.conf shrinks the arena and disables the shell) west build -b qemu_cortex_m3 synaptic-os/samples/hello_inference --pristine west build -t run
The "model" is still a 64-byte zero-filled placeholder and the NPU backend produces a deterministic stub result. The point of the sample is exercising the full API path — real TFLite model execution on the Neutron NPU is still pending.
face_detection
Implemented in Phase 2: a continuous vision-pipeline demo that runs 30 frames through the full production path. Each frame is a deterministic synthetic 24×24×3 image (a bright blob moving across a gradient background, standing in for the OV7670 camera), pushed through resize (bilinear, to 12×12) → normalize (per-channel mean/std) → quantize (int8) → detector model (NPU HAL) → grid decode → non-maximum suppression, with the detections and per-frame profiling reported on the console. syn_mem_reset_ephemeral() runs after every frame, so the arena footprint stays constant across frames — confirmed on the board: peak 2784 bytes per frame, returning to 0 after every frame, 120 allocations and 30 ephemeral resets over the full run.
Measured on the FRDM-MCXN947 (v0.2.0, 2026-07-12; Cortex-M33 @ 150 MHz with hardware FPU, stub NPU backend): 4632 us per frame average — 215.8 FPS over 30 frames, with the last-frame profile splitting into 3562 us preprocess, 1040 us stub NPU, and 1 us postprocess. Measured on QEMU (icount, stub NPU, soft-float): 31.1 ms per frame average, 32.1 FPS effective — the M33's FPU cuts the float-heavy preprocessing roughly 8×. Preprocessing dominates the hardware frame time; with real Neutron kernels it becomes the bottleneck to attack (PowerQuad batch operations remain on the backlog). The final summary prints average frame time, FPS, and the profiling breakdown of the last inference.
# QEMU (boards/qemu_cortex_m3.conf shrinks the arena and disables the shell) west build -b qemu_cortex_m3 synaptic-os/samples/face_detection --pristine west build -t run # FRDM-MCXN947 (with shell enabled) west build -b frdm_mcxn947/mcxn947/cpu0 synaptic-os/samples/face_detection --pristine
The frame source is synthetic and results go to the console: OV7670 (DVP) camera capture and LCD-PAR-S035 rendering are hardware bring-up steps deferred past Phase 2 (tracked). The pipeline, scheduler, and post-processing are unchanged by them. On the stub NPU the detections derive deterministically from a hash of the input; on Neutron hardware the model's real detections appear instead.
keyword_spotting
Placeholder for an audio keyword-detection demo. Its dependencies have arrived in Phase 2 — syn_preprocess_audio_mfcc and syn_hal_dsp_fft_f32() are implemented — but the sample body itself is still pending.
west build -b frdm_mcxn947/mcxn947/cpu0 synaptic-os/samples/keyword_spotting --pristine
dual_model
Implemented in Phase 3: the asymmetric dual-core demo. CPU0 boots the full AI runtime (both models registered, shell, cross-core serving), verifies flash bank 1 holds an image, and releases CPU1; CPU1 runs a minimal application image that alternates cross-core inference requests — face_detect (96×96×3 INT8 frame, REALTIME priority) and keyword_spot (49×10 INT8 MFCC window, NORMAL priority) — at a 20 Hz sensor cadence, staging tensors zero-copy in the shared exchange slot. Only CPU0 has a console; syn ipc status shows the link, serve counters, and CPU1-measured round-trip statistics (see the syn shell).
Measured on the FRDM-MCXN947 (v0.3.0, 2026-07-14): CPU1 ready 1,514 µs after release, handshake at 2,554 µs, IPC round-trip 15 µs typical / 81 µs worst-case, and a 1,913-serve soak with zero errors (average serve 2,290 µs — stub-NPU baseline; real compiled Neutron models arrive in a later phase). If CPU1's flash bank is blank, CPU0 logs it and continues single-core with the full shell.
Two separate images: CPU0 uses the stock Zephyr board target, CPU1 uses the out-of-tree board port in boards/nxp/frdm_mcxn947_cpu1 (Zephyr 3.7 has no in-tree cpu1 target):
# CPU0 (AI runtime + shell), flash bank 0 west build -b frdm_mcxn947/mcxn947/cpu0 synaptic-os/samples/dual_model --pristine -d build-dm-cpu0 # CPU1 (application), flash bank 1 west build -b frdm_mcxn947_cpu1/mcxn947/cpu1 synaptic-os/samples/dual_model/remote --pristine -d build-dm-cpu1
One cross-core request is in flight at a time (CPU1 callers serialize on a mutex); MPU write-protection is enforced on CPU0's side only, because CPU1 has no MPU; and the model path is the deterministic stub NPU — the latencies above bracket the runtime and transport, not silicon inference throughput.
ota_update
Implemented in Phase 4: the end-to-end OTA demo. On first boot with a blank store it installs a factory stub model into slot 0, then serves an inference tick every 3 seconds, printing which model and slot answered. From the host, tools/syn_ota_send.py streams a .synm image built by tools/syn_model_pack.py over the serial port using the syn ota shell transport (ack-paced 1024-byte hex lines; the sample raises the shell RX ring to 1 KB and the command buffer to 2320 bytes for exactly this). After syn ota activate the very next tick serves the updated model from the other slot; syn ota rollback returns to the previous one. Persistence comes free from the store: reboot mid-anything and the previous active model boots.
Measured on the FRDM-MCXN947 (v0.4.0, 2026-07-15): registry commit 1.9–2.6 ms and boot scan 23–189 µs against the 50 ms criterion; a slot-maximum 442,432-byte update staged, CRC-verified from flash, and activated in 80.5 s at 5.4 KB/s — transport-bound by ack-paced hex over 115200-baud UART (line-rate ceiling ≈5.6 KB/s), reported as measured; power loss mid-transfer left the factory model intact and serving; a staged update survived reboot and activated from IDLE. On QEMU the same demo runs against the RAM-emulated flash port. All inference is the deterministic stub NPU.
# FRDM-MCXN947 west build -b frdm_mcxn947/mcxn947/cpu0 synaptic-os/samples/ota_update --pristine # QEMU (RAM-backed flash port) west build -b qemu_cortex_m3 synaptic-os/samples/ota_update --pristine west build -t run # then, from the host, stream an update over the serial port: python3 synaptic-os/tools/syn_model_pack.py --input model.bin --name demo_model --output demo.synm python3 synaptic-os/tools/syn_ota_send.py demo.synm --port /dev/ttyACM0
On a board flashed with dual_model on both cores, the same transport drives the dual-core OTA path: syn ota begin parks CPU1 (its XIP bank hosts the model slots) and offload pauses while local CPU0 inference keeps serving; activation releases CPU1 with a normal 1,514 µs boot + 1,519 µs handshake. Captured live in the Phase 4 OTA session.