Skip to main content

board.yaml Reference

board.yaml is the single declarative file that describes a firmware project: which SoM SKU, which board, which runtime on each on-die core (optional — defaults from the SoM topology), which optional libraries, which IPC carve-outs, plus the v0.6 declarative blocks for boot, OTA, storage, PSA security, per-core memory + power, and per-module diagnostics. The SDK's orchestrator compiles it down to per-core build slices (Zephyr Kconfig, Yocto local.conf, plain CMake), a system manifest, and generated headers — your build plumbing stays unchanged.

The customer surface is flat — som:, cores:, plus the board declaration (preset: shortcut or inline name: + populated: + e1m_routes: + optional pins:) all sit at the top level. Optional blocks (ipc:, boot:, ota:, storage:, security:, chips:, diagnostics:) land at the top level too. This page is the customer contract; if you need to extend the file's shape, file an issue.

Minimum-viable example

som:
sku: E1M-AEN701 # your MPN — preset under metadata/e1m_modules/<MPN>.yaml

preset: e1m-evk # stock board preset, or write the board out inline (see below)

cores:
m55_hp:
# os: omitted → SoM topology default (Cortex-M → zephyr).
# Write os: only to override (off | baremetal).
app: ./src
peripherals: [gpio]

That's the whole config for a vanilla "E1M-AEN701 on the EVK running Zephyr on the M55-HP" build. All other blocks are optional. Cores omitted from cores: inherit the SoM preset's topology: defaults; use os: off to skip a peer core explicitly.

Heterogeneous example (V2N: A55 Yocto + M33 Zephyr)

som:
sku: E1M-V2N101
hw_rev: r1

preset: e1m-x-evk

cores:
a55_cluster:
# os: omitted → topology default (a55_cluster → yocto).
app: ./linux
image: alp-image-edge
peripherals: [ethernet, usb, emmc]
libraries: [mbedtls, nlohmann_json]
iot: { wifi: true, mqtt: true, tls: true }
m33_sm:
# os: omitted → topology default (m33_sm → zephyr).
app: ./m33_sm
peripherals: [adc, pwm, i2c, gpio]
libraries: [cmsis_dsp]

ipc:
- kind: rpmsg
endpoints: [a55_cluster, m33_sm]
carve_out_kb: 512
name: alp_default_rpmsg

diagnostics:
log_level: info

One declaration drives both halves. The orchestrator fans out into per-slice build directories, allocates the RPMsg carve-out from the SoM preset's memory_map:, and emits <alp/system_ipc.h> with the resolved addresses + endpoint IDs that both halves #include.

File location

your-app/
├── board.yaml # this file
├── linux/ # Yocto slice (path matches cores.a55_cluster.app)
│ └── CMakeLists.txt
└── m33_sm/ # Zephyr slice (path matches cores.m33_sm.app)
├── prj.conf # empty; the loader generates alp.conf
└── src/main.c

Slice sub-directory names are conventions, not magic — they're bound by cores.<id>.app:. Matching the core ID keeps the layout readable. Single-OS projects keep a flat src/ layout under one core.

Single source of truth

Pre-board.yaml you tracked configuration across prj.conf, CMake -D flags, local.conf, vendor Kconfig knobs, and library opt-ins. board.yaml collapses all of that into one place.

  • Don't edit prj.conf directly. It stays empty; the loader emits alp.conf and Zephyr layers it on via OVERLAY_CONFIG.
  • Don't pass SDK -D flags to cmake. The loader emits the right set per slice.
  • Don't hand-edit local.conf's MACHINE. The orchestrator picks the per-cluster MACHINE (e.g. e1m-v2n101-a55) from the SoM preset.
  • Don't type endpoint IDs or carve-out addresses in app code. They live in the generated <alp/system_ipc.h> that both slices include.

If board.yaml can't express something you need, file an issue — the right fix is to extend the schema, not bypass it.

Released MPNs

Paste any of these into som.sku:

FamilyMPNs
Alif EnsembleE1M-AEN301, AEN401, AEN501, AEN601, AEN701, AEN801
Renesas RZ/V2NE1M-V2N101, V2N102
RZ/V2N + DEEPXE1M-V2M101, V2M102
NXP i.MX 93E1M-NX9101 (hardware-fact fields carry TBD pending HW config)

Stock board presets

PresetForm factorHosts
e1m-evk35 × 35E1M-AEN family, E1M-N93 family
e1m-x-evk45 × 65E1M-X V2N family, V2N-M1 family

preset: is the SDK-internal shortcut the demos use. For a custom board (your own PCB design), drop the preset: line and write the board out inline at the top level (name:, populated:, e1m_routes:) — see the "Board declaration" section below.

Top-level fields

FieldRequiredWhat it picks
somyesSoM SKU (+ optional hw_rev). Silicon-level facts live in the SKU preset and are NOT customer-tunable here.
nameinline*Board name (^[A-Za-z][A-Za-z0-9_-]*$) — required in inline mode, forbidden when using preset:. Used in alp_<name>_routes.h.
descriptionnoFree-form one-line description.
hw_revnoBoard hardware revision. Defaults to the preset's default_hw_rev (preset mode) or unrevisioned (inline).
populatedinline*Chips populated on this board. Each trueCONFIG_ALP_SDK_{CHIP,BLOCK}_<name>=y. Mutually exclusive with preset:.
e1m_routesinline*E1M-pad → board-side macro routing. Read by gen_board_header.pyinclude/alp/boards/alp_<name>_routes.h. Mutually exclusive with preset:.
presetpreset*SDK-internal shortcut (e1m-evk / e1m-x-evk): pulls a shared board definition from metadata/boards/<preset>.yaml. Mutually exclusive with inline populated: / e1m_routes:.
supported_boardsnoBoard presets this project is verified to build for (e.g. [e1m-evk, e1m-x-evk]). Examples only — per ADR-0011 application firmware still targets one SoM family. CI builds one scenario per entry. Defaults to the single preset: when omitted. See Cross-EVK portability.
pinsnoOptional array naming the E1M pads the project actively uses. Validated against the resolved board's e1m_routes:.
coresyesPer-core app + library/peripheral knobs (min 1). Each core's os: is optional; the runtime is class-derived (Cortex-M → Zephyr, Cortex-A → Yocto) and only off / baremetal may be set explicitly.
ipcnoCross-core IPC carve-outs (rpmsg / raw_shmem / mailbox_only).
bootnoMCUboot + image-signing configuration (project-wide). See Boot block.
otanoOTA provider + server config (project-wide). Mender / Hawkbit / MCUmgr emit on Zephyr; Mender server-mode on Yocto. See OTA block.
storagenoFilesystem partitions. Deterministic flash-partition allocator + DTS/Kconfig emit. See Storage block.
securitynosecurity.psa: — PSA Crypto + optional TF-M sysbuild child image. See Security block.
chipsnoProject-wide chip-driver opt-ins (each → CONFIG_ALP_SDK_CHIP_<NAME>=y).
modelsnoAI models to compile + package into .alpmodel (silicon-agnostic; back-ends derived from som.sku). See models: block.
diagnosticsnolast_error, default log_level, and per-module log levels under diagnostics.modules:.
featuresnoFree-form map for app-specific feature config that doesn't fit the strict blocks.

* Either preset: (preset mode) OR inline name: + populated: + e1m_routes: (customer path). Both omitted is fine too — a headless / inference-only build with no board declaration.

som block

som:
sku: E1M-AEN701 # required

hw_rev: r1 # optional — defaults to the preset's default_hw_rev.
# Validated build-time against the family's
# hw_revisions table; cross-checked at runtime via
# the BOARD_ID ADC.

The SoM block describes the module itself — silicon, on-module radio, on-module secure element / RTC / temperature sensor / EEPROM. Fixed at SoM-fab time; you can't DNI on-module parts after order.

Custom SoM variants (e.g. an AEN built without the OPTIGA Trust M, or a non-stock DRAM/flash population) are expressed as new SKU presets under metadata/e1m_modules/<MPN>.yaml, not as project-level overrides. The pre-v0.6 som.overrides: / som.memory: knobs are gone — silicon-determined fields aren't customer-facing.

Board declaration

The board the firmware targets is declared at the top level of board.yaml in one of two mutually-exclusive modes.

Inline mode (the customer path)

Self-contained — write your board's chip population + E1M-pad wiring directly at the top level of your project's board.yaml:

name: my-sensor-board # required; used in alp_<name>_routes.h
description: "..." # optional
hw_rev: r1 # optional board hardware revision

populated: # chips populated on this board
lsm6dso: true
bmi323: true
bmp581: true
ssd1306: true

e1m_routes: # E1M-pad → board-side macro
gpio:
- { e1m: E1M_GPIO_IO15, macro: PIN_BMI323_INT1,
doc: "BMI323 INT1 (data-ready / motion / FIFO)." }
buses:
- { e1m: E1M_I2C0, macro: I2C_BUS_SENSORS,
doc: "Shared sensor bus." }
pwm:
- { e1m: E1M_PWM3, macro: PWM_LED_RED,
doc: "Status LED red channel." }

Each populated.<name>: true enables the matching CONFIG_ALP_SDK_CHIP_<NAME>=y (or CONFIG_ALP_SDK_BLOCK_<NAME>=y for the SDK-level helpers button_led / pdm_mic — the loader picks the right symbol per slug).

Preset mode (SDK-internal shortcut)

The in-repo example projects all target the EVK or X-EVK, so they share a single board definition each via the preset: field:

preset: e1m-evk # or e1m-x-evk

The resolved file lives at metadata/boards/<preset>.yaml and supplies name, populated, e1m_routes, default_hw_rev, and hw_revisions wholesale. When preset: is set, top-level name:, populated:, e1m_routes: are forbidden — the schema rejects mixing.

e1m_routes: — E1M-pad assignments

e1m_routes: is the single authoritative source of pin assignments for the board. Each entry binds one E1M-standard pad (E1M_GPIO_IO<N>, E1M_PWM<N>, E1M_I2C<N>, E1M_SPI<N>, E1M_UART<N>, E1M_I3C<N>, E1M_ADC<N>, E1M_DAC<N>, E1M_I2S<N>, E1M_CAN<N>, E1M_ENC<N>) to a board-side C macro. scripts/gen_board_header.py reads the block and emits include/alp/boards/alp_<name>_routes.h with one #define <MACRO> ALP_E1M_<…> line per entry; hand-written firmware references those macros.

:::note Two namespaces: E1M_* in YAML, ALP_E1M_* in C The pad names you write inside e1m_routes: (and inside a SoM preset's pad_routes: / e1m: blocks) stay in the unprefixed E1M connector namespaceE1M_GPIO_IO15, E1M_SPI1, E1M_X_GPIO_IO28, … — because they name pads on the open-standard E1M / E1M-X connector, not C symbols. gen_board_header.py prefixes ALP_ when it emits the C instance macro, so the generated header defines your board macro against ALP_E1M_* (E1M-X boards against ALP_E1M_X_*). Rule of thumb: write E1M_* in board.yaml, reference ALP_E1M_* (or your own board macro) in C. :::

e1m_routes:
gpio:
- { e1m: E1M_GPIO_IO15, macro: PIN_BMI323_INT1,
doc: "BMI323 INT1 (data-ready / motion / FIFO)." }
- { e1m: E1M_GPIO_IO4, macro: PIN_USER_BUTTON,
active_low: true, pull: up, debounce_ms: 20,
doc: "User push-button." }
buses:
- { e1m: E1M_I2C0, macro: I2C_BUS_SENSORS,
doc: "Shared sensor bus." }
- { e1m: E1M_SPI1, macro: SPI_BUS_DISPLAY,
doc: "ST7789 panel." }
pwm:
- { e1m: E1M_PWM3, macro: PWM_LED_RED,
doc: "Status LED red channel." }
adc:
- { e1m: E1M_ADC2, macro: ADC_BOARD_ID,
doc: "Board-ID divider — encodes the board-side hw_rev." }
dac:
- { e1m: E1M_DAC0, macro: DAC_AUDIO_LINE_OUT,
doc: "Line-out via op-amp." }
i2s:
- { e1m: E1M_I2S0, macro: I2S_AUDIO_CODEC,
doc: "WM8960 codec." }
can:
- { e1m: E1M_CAN0, macro: CAN_BUS_VEHICLE,
doc: "Vehicle CAN-FD via TCAN1042." }
qenc:
- { e1m: E1M_ENC0, macro: QENC_UI_DIAL,
doc: "PEC12R rotary on the front panel." }

Sections, one per E1M pad class:

SectionPadsNotes
gpio:E1M_GPIO_IO<N>Per-pin board-side macros.
buses:E1M_I2C<N> / E1M_I3C<N> / E1M_SPI<N> / E1M_UART<N>Bus aliases.
pwm:E1M_PWM<N>Channel aliases.
adc:E1M_ADC<N>Channel aliases (board-id, Arduino A1..A5, mikroBUS AN, …).
dac:E1M_DAC<N>Output aliases.
i2s:E1M_I2S<N>Codec / mic / amp bus aliases.
can:E1M_CAN<N>Network aliases.
qenc:E1M_ENC<N>Quadrature encoder aliases (PEC12R rotary, …).

Per-section pad-class validation rejects misclassified pads at validate time (e.g. an E1M_I2C0 placed under adc:).

Per-entry fields:

FieldRequiredValuesNotes
e1myesE1M pad nameValidated against the pad class for the enclosing section.
macroyes^[A-Z_][A-Z0-9_]*$Uppercase C identifier emitted into alp_<name>_routes.h.
docnostringOne-line Doxygen @brief documenting what the pad is wired to.
active_lownoboolBoard convention — pressed/asserted = 0 V. Documentation only; the app passes ALP_GPIO_ACTIVE_LOW at alp_gpio_open() time if it wants auto-inversion.
pullnoup / down / nonePhysical pull resistor on the PCB (board-static fact). Apps still pass the matching ALP_GPIO_PULL_{UP,DOWN} at alp_gpio_open() time — this field documents what the board ships with, it does not drive the controller's internal pulls.
debounce_msnointeger (≥0)RC-debounce time constant in milliseconds for hardware debounce (buttons mostly). Documentation hint; the app may add software debounce on top.
board_aliasno^BOARD_[A-Z0-9_]+$Portable cross-EVK alias for an e1m-spec common role (STANDARD.md §7.2). gen_board_header.py emits #define <board_alias> <macro> into the generated routes header. The same board_alias must be declared on every board that exposes the role (CI-enforced parity). See Cross-EVK portability.

Pin direction is NOT a board declaration

board.yaml describes the wiring (pad ↔ feature) — universal across every app that uses the board. Direction is a per-app runtime choice set at the alp_*_open() call site:

alp_gpio_t *btn = alp_gpio_open(PIN_USER_BUTTON,
ALP_GPIO_INPUT | ALP_GPIO_INT_EDGE_FALLING);
alp_gpio_t *led = alp_gpio_open(PWM_LED_RED, ALP_GPIO_OUTPUT);

The same physical pad can have multiple legitimate directions in different apps — gpio-button-led uses E1M_PWM3 as a GPIO output for the status LED; drone-autopilot uses the same pad as a PWM output driving an ESC channel. Drive strength / slew rate / etc. are SoC-controller settings (Kconfig), not board-level facts.

For peripheral use (UART / SPI / I²C / PWM / …) the alp_<class>_open() call muxes the pads to the right function automatically; the app doesn't say "TX is output" by hand.

pins: (optional E1M-pad usage list)

pins:
- { e1m: E1M_GPIO_IO4, macro: EVK_PIN_ENCODER_SW, doc: "user button" }
- { e1m: E1M_PWM3, macro: EVK_PWM_LED_RED, doc: "red status LED" }
- E1M_I2C0 # bare form OK

Optional top-level array. Names the E1M pads the project actively uses. Most useful in preset mode, where the resolved board carries the full wiring but readers can't tell which subset this firmware touches.

Each entry is either a bare E1M pad name (e.g. E1M_GPIO_IO4) or a {e1m, macro?, doc?} mapping. The loader cross-checks every entry against the resolved board's e1m_routes: block: the e1m pad must exist, and when macro: is supplied it must match the board's macro for that pad (catches drift if the demo references EVK_PWM_LED_RED but the preset moved it).

cores block (per-core runtime)

The core of the schema. Keys are canonical core IDs from the SoM preset's topology: block (a55_cluster, m33_sm, m55_hp, m55_he, a32_cluster, m33).

Per-core fields:

FieldValuesNotes
oszephyr / yocto / baremetal / offOptional — and not a free choice. The OS runtime is derived from the core's silicon class (Cortex-M → zephyr, Cortex-A → yocto); omit os: to take it. The only values you may set explicitly are off (skip the slice) and baremetal (rare hand-written firmware). Setting the other class's OS (zephyr on a Cortex-A, yocto on a Cortex-M) is rejected by the loader's cross-file core-class check.
appPath to the app source directoryZephyr/baremetal: CMakeLists.txt. Yocto: recipe dir.
imageYocto image recipe nameYocto slices only (alp-image-edge is the stock reference — includes the Weston/Wayland compositor + libdrm test tools for the display stack).
peripheralsEnum subsetPer-slice (not project-wide). Each entry enables the matching Zephyr Kconfig / Yocto package. Allowed: adc, can, counter, emmc, ethernet, flash, gpio, i2c, i2s, pwm, rtc, sensor, spi, uart, usb, watchdog.
librariesList (curated 25-entry enum)Scoped to this slice's include path + link line.
extra_librariesList of { name, kconfig? | profile? }Open-set escape hatch for non-curated libraries — see below.
inference{ default_arena_kib: N }App-level inference tuning. Backend set is silicon-driven (TFLM CPU always; Ethos-U / DRP-AI3 / DEEPX DX-M1 per the SoM capabilities: matrix). Apps pick per-handle at runtime via alp_inference_open(.backend = ...).
iot{ wifi, mqtt, ble, tls }Connectivity toggles per-slice. tls: true requires mbedtls or bearssl in libraries: / extra_libraries: (validator rule 3).
memory{ stack_kib, heap_kib, isr_stack_kib }Emits CONFIG_MAIN_STACK_SIZE / CONFIG_HEAP_MEM_POOL_SIZE / CONFIG_ISR_STACK_SIZE.
power{ sleep_mode, wakeup_sources: [...] }Emits CONFIG_PM + CONFIG_PM_DEVICE_WAKE_<SUBSYS>. sleep_mode != disabled without wakeup_sources: warns (validator rule 5).

Which cores does my SoM expose?

SoM familyA-classM-classHeterogeneous?
AEN E3/E4m55_hp, m55_he (Zephyr)No — RTOS-only
AEN E5..E8a32_cluster (Yocto)m55_hp, m55_he (Zephyr)Yes
V2N / V2N-M1a55_cluster (Yocto)m33_sm (Zephyr)Yes
iMX93 (N93)a55_cluster (Yocto)m33 (Zephyr)Yes

A bare som: { sku: <MPN> } plus an empty cores: block produces a working dual-image build for every heterogeneous SoM — the per-core OS defaults come from the SoM preset.

extra_libraries: (open-set escape hatch)

The curated libraries: enum is closed — it lists the 25 libraries the SDK ships first-class (etl, fmt, nlohmann_json, doctest, lvgl, mbedtls, cmsis_dsp, littlefs, tflite_micro, u8g2, gfx_compat, madgwick_ahrs, pid, modbus, coremqtt_sn, libcoap, tinygsm, nanopb, libwebsockets, jsmn, bearssl, minimp3, opus, libhelix, catch2). extra_libraries: is the open-set escape hatch for everything else — one-off vendor SDKs, research-only deps, or libraries on their way into the curated set.

cores:
m55_hp:
libraries: [mbedtls] # curated — closed enum
extra_libraries:
- name: zforce # one-off vendor SDK, inline Kconfig path
include_path: third_party/zforce/include
kconfig:
- CONFIG_ZFORCE=y
- CONFIG_ZFORCE_I2C_ADDR=0x50

- name: mycrypto # library with per-silicon backend selection
profile: third_party/mycrypto/hw-backends.yaml

Each entry must declare exactly one of kconfig: (inline fragment) or profile: (path to a hw-backends.yaml-style file). The include_path: is optional and adds the directory to the slice's compile include path.

FieldWhen to use
kconfig:Fast path for one-off libraries. Lines emit verbatim into the slice's alp.conf.
profile:Library wants per-silicon backend selection consistent with the curated libraries: (same matcher).

Loader-enforced rules:

  • Exactly one of kconfig: or profile: per entry.
  • name: is globally unique across every core's extra_libraries:.
  • name: must NOT collide with the curated libraries: enum — use the curated path for curated entries.
  • profile: must resolve to a file (repo-relative).

boot: block (MCUboot)

boot:
method: mcuboot # mcuboot | none (none = unverified, dev/lab only)
signing: # required when method: mcuboot
algorithm: ecdsa_p256 # ecdsa_p256 | rsa2048 | rsa3072 | ed25519
key_file: keys/release.pub.pem
slots:
primary: { size_kib: 480 }
secondary: { size_kib: 480 }
swap_algorithm: scratch # scratch | move | overwrite (default scratch)
scratch_size_kib: 32 # only when swap_algorithm is scratch
anti_rollback: false # monotonic image counters; needs OTP fuse provisioning
build_type: Release # Release | Debug | MinSizeRel (propagates to MCUboot + TF-M)

Project-wide — the bootloader is a device-level artifact, not a per-OS choice. The loader emits a SB_CONFIG_* sysbuild overlay (signature type, slot sizes, swap algorithm, scratch size, anti-rollback counter). build_type: propagates to both the MCUboot and TF-M sysbuild child images so they ship the same flavour.

Defaults from the SoM family apply when boot: is omitted: AEN / N93 → MCUboot + ECDSA-P256 + swap-using-scratch + 480 KiB slots; V2N / V2N-M1 → method: none on the Zephyr slice (U-Boot owns boot on the Linux side).

Cross-field validator rule R2 rejects signing.algorithm: values not supported by the SoM family (Alif Ensemble: ecdsa_p256 / ed25519; Renesas RZ/V2N + NXP i.MX 9: ecdsa_p256 / rsa2048 / rsa3072).

ota: block

ota:
provider: mender # mender | hawkbit | mcumgr | none (REQUIRED)
artifact_name: alp-v0.4-v2n101-build42
signing_key: keys/ota.pub.pem
server:
url: https://hosted.mender.io
tenant: ${MENDER_TENANT_TOKEN}
tls_ca_bundle: certs/prod-ca.pem
poll_interval_s: 1800 # default 1800 (30 min); min 60
rollback:
enabled: true
retries: 3
min_version: 100 # anti-downgrade floor
storage: # Yocto/Linux-only A/B rootfs geometry
device: /dev/mmcblk0p
boot_part_mb: 256
rootfs_ab: true
total_size_mb: 4096

ota.provider: is provider-driven dispatch (ADR 0009 follow-up). The loader emits real Kconfig + machine config per provider:

ProviderZephyr emitYocto emit
menderCONFIG_MENDER_MCU_CLIENT=y + server URL / tenant / artifact name / poll interval (Mender-MCU-client)MENDER_* weak-assignments in local.conf + INHERIT += "mender-full"
hawkbitCONFIG_HAWKBIT=y + CONFIG_HAWKBIT_SHELL=y + HAWKBIT_SERVER + HAWKBIT_POLL_INTERVAL (Zephyr upstream)
mcumgrCONFIG_MCUMGR=y + CONFIG_MCUMGR_GRP_IMG=y + CONFIG_MCUMGR_GRP_OS=y (transport selection is the app's call)
none

Validator rule R1 enforces target presence per provider: mender accepts either a Yocto or Zephyr core; hawkbit / mcumgr require at least one Zephyr core. The --emit west-libraries fragment auto-adds the mender-mcu-client name-allowlist: entry when provider: mender is declared.

ota.storage: is Yocto/Linux-only (the kernel-side A/B partition geometry); ignored on Zephyr where MCUboot's slot layout governs.

storage: block

storage:
- name: settings # required; pattern ^[a-z][a-z0-9_]*$
size_kib: 64 # required; min 4
fs: littlefs # littlefs | fat | ext4 | raw (default: raw)
mount: /lfs/settings # optional; omit to skip auto-mount
flash_device: mram_main # SoM memory_map: region OR on_module.ospi_memories: key

- name: app_data
size_kib: 128
fs: littlefs
mount: /lfs/app
flash_device: mram_main

- name: mcuboot_scratch # raw partition for MCUboot swap scratch
size_kib: 32
fs: raw
flash_device: mram_main

- name: legacy_blob
size_kib: 1024
flash_device: ospi0
offset_kib: 4096 # explicit 4 KiB-aligned offset override

Schema-authoritative filesystem partitions with a deterministic flash-partition allocator. The orchestrator name-sorts entries within each flash_device:, page-aligns to 4 KiB, and writes byte-stable offsets across rebuilds. Blocked entries (TBD capacity, unknown device, page-misaligned offset, sibling overlap) land in system-manifest.yaml with status: blocked so reviewers see the gap.

flash_device: references either a SoM memory_map: region (e.g. mram_main, ocram_low) or an on_module.ospi_memories: key (e.g. ospi0).

Emit artefacts:

  • build/generated/dts-partitions.dtsi — partial DTS overlay attaching a partitions { compatible = "fixed-partitions"; … } child node under &. Apps reach partitions via Zephyr's FIXED_PARTITION_ID(<name>_partition).
  • build/generated/storage-mounts.c — optional static fs_mount_t alp_storage_mounts[] table for boot-time iteration.
  • Per-fs Kconfig in alp.conf: CONFIG_FILE_SYSTEM=y + the matching CONFIG_FILE_SYSTEM_LITTLEFS=y / CONFIG_FAT_FILESYSTEM_ELM=y / CONFIG_FILE_SYSTEM_EXT4=y + per-littlefs CONFIG_FS_LITTLEFS_PARTITION_<NAME>=y.

The loader rejects typoed flash_device: references at parse time with the list of known devices, and duplicate partition names within storage: error eagerly.

security: block (PSA + TF-M)

security:
psa:
persistent_slots: 16 # CONFIG_PSA_CRYPTO_PERSISTENT_SLOT_COUNT (default 16)
its_storage: mram_secure # ITS backing region (SoM memory_map: name)
ps_storage: ospi0 # optional PS backing (e.g. OSPI EEPROM)
tfm: true # enable TF-M sysbuild child image
attestation_root: optiga_trust_m # optiga_trust_m | tfm_internal | none (default)

When security.psa.tfm: true the loader emits a SB_CONFIG_TFM=y + SB_CONFIG_TFM_BUILD_TYPE=<flavour> overlay at build/sysbuild/tfm/tfm.conf (build type is inherited from boot.build_type:). With tfm: false, PSA Crypto still runs — entirely non-secure (mbedTLS-only).

its_storage: / ps_storage: reference either a SoM memory_map: region name (typically mram_secure on AEN, ocram_secure on V2N) or a storage[].name from the storage: block. Errors point at the offending YAML path.

attestation_root: optiga_trust_m requires the SoM to physically ship OPTIGA Trust M (AEN + V2N families do). The loader rejects this value on SoMs that don't carry OPTIGA.

See ADR 0013 for the M55-HP TrustZone-M trust-boundary decision.

cores.<id>.memory: and cores.<id>.power:

cores:
m55_he:
memory:
stack_kib: 4 # CONFIG_MAIN_STACK_SIZE = 4096
heap_kib: 16 # CONFIG_HEAP_MEM_POOL_SIZE = 16384
isr_stack_kib: 2 # CONFIG_ISR_STACK_SIZE = 2048
power:
sleep_mode: standby # disabled | idle | standby | deep (default: disabled)
wakeup_sources: [uart, gpio, rtc]

Per-core tuning that previously needed hand-written Kconfig. Zephyr slices only — ignored on Yocto / baremetal.

sleep_mode != disabled emits CONFIG_PM=y + CONFIG_PM_DEVICE=y and selects the matching PM_STATE_* hierarchy. Each entry in wakeup_sources: is either a peripheral subsystem name (uart, gpio, rtc, i2c, …) which emits CONFIG_PM_DEVICE_WAKE_<SUBSYS>=y, or an E1M_* pad name (emitted as a hint comment until per-silicon wake-pin Kconfig lands).

Validator warnings:

  • (W4) inference.default_arena_kib > memory.heap_kib — inference may OOM. Build continues; stderr WARN: … line emitted.
  • (W5) power.sleep_mode != disabled with no wakeup_sources: declared — device will sleep but cannot wake.

diagnostics: block

diagnostics:
log_level: info # error | warn | info | debug | trace (default: info)
last_error: true # alp_last_error() thread-local slot (default: true)
modules:
alp_iot: debug
alp_inference: warn
alp_security: "off" # quote 'off' — bare off parses as YAML false

diagnostics.modules: emits per-module CONFIG_<MODULE>_LOG_LEVEL_<LEVEL>=y per entry so you can dial individual subsystems up or down without flooding the console. Per-module values use the same enum as log_level: plus off (silence the module entirely). Quote off — bare off parses as YAML false and the loader rejects it.

Cross-field validation

scripts/alp_orchestrate.py:_validate_consistency() runs after the JSON Schema pass and the per-core loader rules. Five rules, two warnings:

IDSeverityRule
R1ERRORota.provider: mender works on both Yocto + Zephyr; hawkbit / mcumgr require at least one Zephyr core.
R2ERRORboot.signing.algorithm: must match the SoM family allow-list.
R3ERRORcores.<id>.iot.tls: true requires mbedtls or bearssl in libraries: / extra_libraries:.
R4WARNcores.<id>.inference.default_arena_kib > cores.<id>.memory.heap_kib — inference may OOM.
R5WARNcores.<id>.power.sleep_mode != disabled with no wakeup_sources: declared.

Errors fail the build with the offending YAML path; warnings emit a stderr WARN: line and continue.

ipc block (cross-core carve-outs)

ipc:
- kind: rpmsg # rpmsg | raw_shmem | mailbox_only (REQUIRED)
endpoints: [a55_cluster, m33_sm] # ≥2 core IDs (REQUIRED)
carve_out_kb: 512 # KiB requested (REQUIRED)
name: alp_default_rpmsg # ^[a-z][a-z0-9_]*$ (REQUIRED)
cacheable: true # optional — default is non-cacheable
address: 0x10078000 # optional — pin to an explicit base

Each entry declares a shared-memory region the orchestrator allocates from the SoM preset's memory_map: regions whose accessible_from: covers every listed endpoint. Addresses + endpoint IDs + mailbox channel macros are emitted into build/generated/alp/system_ipc.h, which both halves #include. App code uses <alp/rpc.h> — never raw addresses.

Kinds:

  • rpmsg — OpenAMP-framed.
  • raw_shmem — plain shared memory + customer-owned synchronisation.
  • mailbox_only — doorbell + signal only, no shared memory.

Default cacheable: flips per silicon: V2N's M33-SM has no data cache so the carve-out is non-cacheable; AEN's M55 cores have caches so the default is cacheable + auto-generated cache-maintenance points in alp_rpc_*.

peripherals / chips

cores:
m55_hp:
peripherals: [i2c, pwm, adc] # per-slice

chips:
- lsm6dso # project-wide chip drivers
- ssd1306

The orchestrator gates each peripheral and chip driver on a Kconfig symbol so unused code doesn't cost flash. Listing a chip in board.yaml is equivalent to setting CONFIG_ALP_SDK_CHIP_<NAME>=y by hand — but with validation that the SoM actually routes the bus the chip needs.

models: block (AI model packaging)

Top-level optional array. Each entry names a source model the SDK compiles for every NPU back-end the SoM can drive (derived from som.sku — you don't list back-ends here) and packages into one fat .alpmodel via alp model build.

models:
- name: detector # required; ^[A-Za-z][A-Za-z0-9_-]*$
source: models/yolov8n.onnx # required; path relative to board.yaml
compile: # per-backend config the SDK can't derive
deepx_dxm1:
config: models/yolov8n.dxcom.json # dxcom quant/compile config
calibration: models/calib/ # PTQ calibration dataset dir
drpai:
spec: models/yolov8n.drpai.yaml # DRP-AI TVM compile spec
FieldRequiredNotes
nameyes^[A-Za-z][A-Za-z0-9_-]*$. Names the output .alpmodel.
sourceyesPath (relative to board.yaml) to the source model. .tflite compiles for CPU (TFLM) + Ethos-U (Vela) out of the box; .onnx needs a vendor toolchain (DRP-AI / DEEPX) on the build host, else it's recorded as a coverage skip.
specno$ref to an external model spec, for large or shared models.
inputsnoApp-side input hints (e.g. layout).
compilenoPer-backend compile config for NPU toolchains that need a per-model config + calibration the SDK can't derive. Keys are back-end ids; a back-end with no block is recorded as a coverage skip ("no compile config") rather than guessed. compile.deepx_dxm1 requires config + calibration; compile.drpai requires spec. Paths are relative to this board.yaml.

Compile the packages with alp model build (see Inference); load them at runtime with alp_inference_open_alpmodel().

How the orchestrator compiles the file

scripts/alp_orchestrate.py (driven by west alp-build) reads board.yaml, validates against the schema, resolves the SoM SKU + board preset + per-core topology, applies overrides, runs the cross-field validator, and emits per-slice native configs plus cross-slice generated artefacts.

Generated artefacts (deterministic, byte-stable across rebuilds)

PathWhat it carries
build/system-manifest.yamlPer-slice status, log paths, artefact paths, boot order, blocked storage rows.
build/generated/alp/system_ipc.hEndpoint IDs, addresses, mailbox channel macros — shared by all slices.
build/generated/dts-reservations.dtsireserved-memory: carve-outs shipped into Linux + Zephyr DTs.
build/generated/dts-partitions.dtsifixed-partitions overlay from the resolved storage: block.
build/sysbuild/tfm/tfm.confTF-M sysbuild child-image overlay (when security.psa.tfm: true).
build/<core>-zephyr/alp.confKconfig fragment layered onto each Zephyr slice's prj.conf.
build/<core>-yocto/conf/alp-generated.conflocal.conf snippet consumed by bitbake (MACHINE=…, IMAGE_INSTALL).

The system manifest is consumed downstream by west alp-image (assembles the bundle), west alp-flash (walks boot_order: per artefact), west alp-renode (boots the dual-OS image in simulation), and OTA (<alp/ota.h>).

Per-slice emit modes (driven manually if you bypass west alp-build)

--emit modeOutput
zephyr-confalp.conf — Kconfig fragment, layered over prj.conf via OVERLAY_CONFIG.
cmake-argsA list of -D flags for plain CMake / bare-metal.
yocto-conflocal.conf snippet (MACHINE=…, IMAGE_INSTALL deltas).
dts-overlayZephyr DTS overlay with alp,pin-array + bus aliases.
dts-partitionsFixed-partitions overlay for the resolved storage: block.
storage-mounts-cStatic fs_mount_t alp_storage_mounts[] table.
tfm-sysbuild-confTF-M sysbuild child-image overlay.
system-manifestFinal system manifest YAML.
ipc-contract-h<alp/system_ipc.h>.
hw-info-h<alp_hw_info_build.h> with ALP_HW_BUILD_* macros.
west-librarieswest.yml fragment pinning the libraries board.yaml requested.
build-planDeterministic, write-free JSON build plan (one slice per non-off core, sorted by coreId) — the contract IDE/CLI tooling consumes (ADR 0014).
os-topologyPer-core natural-vs-effective OS facts as JSON (core_type, runtime_class, default_os, effective_os, allowed_os).
carrier-netlistSchema-versioned JSON handoff for Alp Studio — carrier-facing nets + carrier BOM. See below.

The full catalog (including the orchestrator-only modes above and the demonstrator composed-route-table) is reachable from the alp emit verb, which is a superset of --emit.

carrier-netlist — Alp Studio handoff

alp emit carrier-netlist --output nets.json
# or, driving the loader directly:
python scripts/alp_project.py --input board.yaml --emit carrier-netlist

Produces a schema-versioned JSON handoff for Alp Studio describing the carrier PCB's electrical interface to the SoM. Two things go in the file:

  • Carrier-facing nets — composed from the board's e1m_routes: and the SoM preset's pad_routes:, so a net that reaches the silicon through an on-module mediator (e.g. a CC3501E or GD32 bridge) is resolved end-to-end.
  • Carrier BOM rows — from the board's populated: chips plus the chip/block manifests.

It is a netlist + BOM handoff only. SoM internals stay excluded, and the SDK does not claim to emit KiCad projects, Gerbers, DRC results, or PCB layout — Studio owns the board-design surface downstream. The generated_by field records scripts/alp_project.py --emit carrier-netlist for traceability.

Build-time hw_info header

#include "alp/hw_info.h"
#include "alp_hw_info_build.h" /* generated by --emit hw-info-h */

alp_hw_info_t info;
alp_hw_info_read(&info);
alp_hw_info_assert_matches_build(&info,
ALP_HW_BUILD_SOM_SKU,
ALP_HW_BUILD_SOM_HW_REV);

ALP_HW_BUILD_SOM_SKU, ALP_HW_BUILD_SOM_FAMILY, ALP_HW_BUILD_SOM_HW_REV, and (when a board is declared) ALP_HW_BUILD_BOARD_NAME + ALP_HW_BUILD_BOARD_HW_REV are all emitted per project. Per-slice ALP_HW_BUILD_CORE_ID + ALP_HW_BUILD_OS live in each slice's generated header.

Hardware revision tracking

Every released SoM family and board carries a hw_revisions: table. The SDK uses it to detect "wrong firmware for this hardware" two ways:

  • Build-time — the orchestrator reads metadata/sdk_version.yaml and fails-fast if the chosen hw_rev's [min_sdk_version, max_sdk_version] window doesn't cover the current SDK version. Validator exit code 3.
  • Runtime — the SDK reads the SoM's on-module 24C128 EEPROM manifest for the authoritative MPN string + hw_rev + serial + mfg date (alp_hw_info_read()); a blank EEPROM reports ALP_ERR_NOT_PROVISIONED. Boards may additionally encode their own revision on a board-side BOARD_ID ADC divider — an independent, board-side path.

Modular SoMs: optional chip populations

The SoM YAML carries a per-chip assembled: flag for every entry in its i2c_devices: topology so the SDK can express SoMs that ship in multiple BOM variants:

assembled:Meaning
true (default)Chip is always populated on every BOM variant of this SKU.
falseDNI — the chip footprint exists but is empty.
"optional"Per-BOM-variant — some units have it, some don't.

Customer code that uses an optional chip MUST handle alp_*_init returning ALP_ERR_NOT_READY gracefully (skip the demo, log a clear message, fall back) instead of crashing.

Versioning

board.yaml has one live shape. Breaking changes are flagged in the SDK's CHANGELOG with a migration table; every in-repo example is rewritten in the same change.

See also

Questions about this page? Discuss in Community Forum