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 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 (libraries:, 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-AEN801 # 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-AEN801 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]
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: # top-level — one place, optionally core-scoped
- mbedtls # project-wide (every core whose OS supports it)
- { name: nlohmann-json, cores: [a55_cluster] }
- { name: cmsis-dsp, cores: [m33_sm] }
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.confdirectly. It stays empty; the loader emitsalp.confand Zephyr layers it on viaOVERLAY_CONFIG. - Don't pass SDK
-Dflags tocmake. The loader emits the right set per slice. - Don't hand-edit
local.conf'sMACHINE. 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.
schemaVersion: — lazy versioning
board.yaml may carry an optional top-level schemaVersion: integer. You almost certainly don't need it.
Versioning is deliberately lazy: an absent schemaVersion: is version 1 — permanently, not provisionally. It's the floor, not a missing value. So hand-written and external projects keep loading unchanged, are never reported as drift, and never need to add the key. The key only appears in a file once a migration has bumped it to v2 or later.
# Perfectly current. Not "unversioned", not out of date — this IS v1.
som:
sku: E1M-AEN801
cores:
m55_hp:
app: ./src
west alp-migrate is the engine that versions and migrates the file when that day comes. It rewrites byte-faithfully — comments, flow style and indentation all survive — so an applied migration reads as a reviewable diff, not a reformat.
The migration registry is empty as of v0.10.1. The engine shipped ahead of any schema change so the first real one can arrive as a migration rather than a breakage — but today west alp-migrate --check passes on every file and --apply does nothing. Bump schemaVersion: by hand and you'll simply be ahead of a schema that doesn't exist.
Released MPNs
Paste any of these into som.sku:
| Family | MPNs |
|---|---|
| Alif Ensemble | E1M-AEN301, AEN401, AEN501, AEN601, AEN701, AEN801 |
| Renesas RZ/V2N | E1M-V2N101, V2N102 |
| RZ/V2N + DEEPX | E1M-V2M101, V2M102 |
| NXP i.MX 93 | E1M-NX9101 (hardware-fact fields carry TBD pending HW config) |
Stock board presets
| Preset | Form factor | Hosts |
|---|---|---|
e1m-evk | 35 × 35 | E1M-AEN family, E1M-N93 family |
e1m-x-evk | 45 × 65 | E1M-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
| Field | Required | What it picks |
|---|---|---|
som | yes | SoM SKU (+ optional hw_rev). Silicon-level facts live in the SKU preset and are NOT customer-tunable here. |
name | inline* | Board name (^[A-Za-z][A-Za-z0-9_-]*$) — required in inline mode, forbidden when using preset:. Used in alp_<name>_routes.h. |
description | no | Free-form one-line description. |
hw_rev | no | Board hardware revision. Defaults to the preset's default_hw_rev (preset mode) or unrevisioned (inline). |
populated | inline* | Chips populated on this board. Each true → CONFIG_ALP_SDK_{CHIP,BLOCK}_<name>=y. Mutually exclusive with preset:. |
e1m_routes | inline* | E1M-pad → board-side macro routing. Read by gen_board_header.py → include/alp/boards/alp_<name>_routes.h. Mutually exclusive with preset:. |
preset | preset* | 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_boards | no | Board 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. |
pins | no | Optional array naming the E1M pads the project actively uses. Validated against the resolved board's e1m_routes:. |
cores | yes | Per-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. |
ipc | no | Cross-core IPC carve-outs (rpmsg / raw_shmem / mailbox_only). |
boot | no | MCUboot + image-signing configuration (project-wide). See Boot block. |
ota | no | OTA provider + server config (project-wide). Mender / Hawkbit / MCUmgr emit on Zephyr; Mender server-mode on Yocto. See OTA block. |
storage | no | Filesystem partitions. Deterministic flash-partition allocator + DTS/Kconfig emit. See Storage block. |
security | no | security.psa: — PSA Crypto + optional TF-M sysbuild child image. See Security block. |
chips | no | Project-wide chip-driver opt-ins (each → CONFIG_ALP_SDK_CHIP_<NAME>=y). |
models | no | AI models to compile + package into .alpmodel (silicon-agnostic; back-ends derived from som.sku). See models: block. |
diagnostics | no | last_error, default log_level, and per-module log levels under diagnostics.modules:. |
features | no | Free-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-AEN801 # required
hw_rev: r1 # optional — defaults to the preset's default_hw_rev.
# Validated build-time against the family's
# hw_revisions table — it must exist, be buildable,
# and cover this SDK version; cross-checked at
# runtime via the BOARD_ID ADC.
The hw_rev: you name is enforced, three independent ways — see Hardware revision tracking.
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.6som.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 namespace — E1M_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:
| Section | Pads | Notes |
|---|---|---|
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:
| Field | Required | Values | Notes |
|---|---|---|---|
e1m | yes | E1M pad name | Validated against the pad class for the enclosing section. |
macro | yes | ^[A-Z_][A-Z0-9_]*$ | Uppercase C identifier emitted into alp_<name>_routes.h. |
doc | no | string | One-line Doxygen @brief documenting what the pad is wired to. |
active_low | no | bool | Board convention — pressed/asserted = 0 V. Documentation only; the app passes ALP_GPIO_ACTIVE_LOW at alp_gpio_open() time if it wants auto-inversion. |
pull | no | up / down / none | Physical 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_ms | no | integer (≥0) | RC-debounce time constant in milliseconds for hardware debounce (buttons mostly). Documentation hint; the app may add software debounce on top. |
board_alias | no | ^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:
| Field | Values | Notes |
|---|---|---|
os | zephyr / yocto / baremetal / off | Optional — 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. |
app | Path to the app source directory | Zephyr/baremetal: must contain prj.conf or CMakeLists.txt. Yocto: a project-root-relative source directory, not a bitbake target — pair it with recipe:. |
image | Yocto image recipe name | Yocto slices only (alp-image-edge is the stock reference — includes the Weston/Wayland compositor + libdrm test tools for the display stack). Takes priority over app:/recipe: when both are set. |
recipe | Yocto bitbake recipe name | Required for an app-only os: yocto slice (app: set, no image:) — see below. |
peripherals | Enum subset | Per-slice (not project-wide). Each entry enables the matching Zephyr Kconfig / Yocto package. Allowed: adc, can, counter, dac, emmc, ethernet, flash, gpio, i2c, i2s, i3c, pwm, rtc, sensor, spi, uart, usb, watchdog. Carrier peripherals declared here are auto-wired by codegen from the board's routing. |
extra_libraries | List 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 the top-level libraries: / this core's 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). |
peripherals: — the 18 classes
The peripherals: enum in metadata/schemas/board.schema.json holds 18 members. Two are new:
| Class | Portable surface | Notes |
|---|---|---|
dac | <alp/dac.h> | Analog output. Pads are declared under e1m_routes.dac: (E1M_DAC<N>). |
i3c | <alp/i3c.h> | MIPI I3C bus. Pads are declared under e1m_routes.buses: (E1M_I3C<N>). |
Which cores does my SoM expose?
| SoM family | A-class | M-class | Heterogeneous? |
|---|---|---|---|
| AEN E3/E4 | — | m55_hp, m55_he (Zephyr) | No — RTOS-only |
| AEN E5..E8 | a32_cluster (Yocto) | m55_hp, m55_he (Zephyr) | Yes |
| V2N / V2N-M1 | a55_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.
Yocto slices: app: + recipe: vs image:
A Yocto slice can be declared two ways, and the distinction is load-bearing:
cores:
a55_cluster:
image: alp-image-edge # (a) build a stock image — app: is ignored if also set
# ── or ──
a55_cluster:
app: ./linux # (b) app-only slice: a SOURCE DIRECTORY…
recipe: alp-lvgl-dashboard # …plus the bitbake recipe that packages it
For os: yocto, app: is a project-root-relative source directory — not a bitbake target. An app-only slice therefore needs recipe: to name the bitbake recipe that packages it. Omit it and the slice doesn't silently build the wrong thing: the emitted build plan carries command: null plus a yocto-recipe-missing warning, rather than an invalid bitbake <path> command. When image: is set it takes priority and any app: is retained for tooling but not built directly.
libraries: block (curated libraries)
One top-level key, optionally core-scoped. There is a single place libraries are declared:
libraries:
- mbedtls # shorthand: project-wide
- { name: cmsis-dsp, cores: [m33_sm] } # scoped to named cores
- { name: lvgl, cores: [a55_cluster] }
Each entry is a {name, cores?} object, or a bare name as shorthand for a project-wide {name}. Omit cores: and the library applies to every core whose OS the manifest supports; list core ids to scope it. name resolves to a manifest at metadata/libraries/<name>.yaml — the manifest directory is the curated set. There's no closed enum in the schema: a typo is caught by the loader, which errors listing every available library, so it's self-correcting.
The orchestrator emits the per-OS wiring (Zephyr Kconfig / Yocto IMAGE_INSTALL / baremetal CMake) through the --emit contract and rejects a selection incompatible with the resolved SoM/core, naming the failing constraint. It never forks or fetches — west and OpenEmbedded own the actual pin.
cores.<id>.libraries: was removedLibraries used to be declared per-core (cores.<id>.libraries:). That key no longer exists — move every selection to the top-level libraries: block and use cores: on the entry to scope it. cores.<id>.extra_libraries: is unaffected and stays per-core.
Canonical names are hyphenated. The manifest names are cmsis-dsp, nlohmann-json, madgwick-ahrs, tflite-micro, coremqtt-sn, gfx-compat. The older underscore spellings (cmsis_dsp, nlohmann_json, madgwick_ahrs, tflite_micro, coremqtt_sn, gfx_compat, plus libcoap → coap) still resolve through an alias table (metadata/library-aliases-v1.json), which the loader canonicalises on load — but prefer the canonical hyphenated name in new files.
Two opt-in tokens worth calling out, both of which make a previously metadata-only profile consumable:
| Token | Emits | Portable surface |
|---|---|---|
madgwick-ahrs | CONFIG_ALP_SDK_AHRS | <alp/ahrs.h> — Madgwick IMU orientation filter |
pid | CONFIG_ALP_SDK_PID | <alp/pid.h> — portable PID controller |
Licence policy — copyleft is not admitted
The license field on every manifest is validated against a permissive allowlist: Apache-2.0, MIT, BSD-2-Clause, BSD-3-Clause, Zlib, MIT-0, BSL-1.0, CC0-1.0. A GPL-family or proprietary licence is rejected, so a copyleft surprise can't ride in through a libraries: selection. Extending that allowlist is a deliberate human legal-review decision, not a metadata edit.
tinygsm and libhelix were removed — no compatibility aliasA maintainer legal review dropped both curated tokens entirely: tinygsm (LGPL-3.0) and libhelix (RPSL-1.0) are copyleft / source-available licences unwanted in a SoM manufacturer's firmware distribution. Gone with them: the west.yml pins, the manifests and HW-backend profiles, their teaching examples, and every Kconfig / loader reference. No ABI shim and no back-compat alias survives — a retired curated token carries no compatibility promise. LGPL-3.0 and RPSL-1.0 are deliberately not on the allowlist above.
catch2 (BSL-1.0) and minimp3 (CC0-1.0) are permissive and stay — the same review added those two SPDX ids to the allowlist to accommodate them.
If you depend on tinygsm or libhelix, route it through extra_libraries: and own the licence obligation yourself.
extra_libraries: (open-set escape hatch)
extra_libraries: is the per-core open-set escape hatch for anything the SDK doesn't curate — one-off vendor SDKs, research-only deps, or libraries on their way into the curated set.
libraries: [mbedtls] # curated — top-level
cores:
m55_hp:
extra_libraries: # non-curated — per-core
- 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.
| Field | When 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:orprofile:per entry. name:is globally unique across every core'sextra_libraries:.name:must NOT collide with a curated library name — use the top-levellibraries: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
swap_algorithm: scratch # scratch | move | overwrite (default scratch)
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, swap algorithm). 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; V2N / V2N-M1 → method: none on the Zephyr slice (U-Boot owns boot on the Linux side).
slots:, scratch_size_kib:, anti_rollback:Slot and scratch sizes are not configurable in board.yaml. MCUboot takes its slot geometry from the board DT partitions — an SDK build-policy choice, not a per-project field. Use storage: to declare the actual partition table.
anti_rollback: was removed because only software downgrade prevention is wired today (ota.rollback.min_version); the OPTIGA / OTP hardware-counter tier is unbuilt. The field promised a security guarantee the SDK did not deliver — if you relied on it, ota.rollback.min_version is what was actually enforcing your floor.
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).
For method: mcuboot, rsa3072 is additionally rejected at emit time: sysbuild's BOOT_SIGNATURE_TYPE choice carries no RSA key-length knob, so there is no way to express 3072 until a per-image sysbuild/mcuboot.conf overlay seam ships. Use rsa2048 (mcuboot's RSA default length), ecdsa_p256, or ed25519. rsa3072 stays load-bearing for the Renesas RZ/V2N and NXP i.MX 9 U-Boot / FIT signing paths, which don't go through sysbuild.
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:
| Provider | Zephyr emit | Yocto emit |
|---|---|---|
mender | CONFIG_MENDER_MCU_CLIENT=y + server URL / tenant / artifact name / poll interval (Mender-MCU-client) | MENDER_* weak-assignments in local.conf + INHERIT += "mender-full" |
hawkbit | CONFIG_HAWKBIT=y + CONFIG_HAWKBIT_SHELL=y + HAWKBIT_SERVER + HAWKBIT_POLL_INTERVAL (Zephyr upstream) | — |
mcumgr | CONFIG_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 apartitions { compatible = "fixed-partitions"; … }child node under&. Apps reach partitions via Zephyr'sFIXED_PARTITION_ID(<name>_partition).build/generated/storage-mounts.c— optional staticfs_mount_t alp_storage_mounts[]table for boot-time iteration.- Per-fs Kconfig in
alp.conf:CONFIG_FILE_SYSTEM=y+ the matchingCONFIG_FILE_SYSTEM_LITTLEFS=y/CONFIG_FAT_FILESYSTEM_ELM=y/CONFIG_FILE_SYSTEM_EXT4=y+ per-littlefsCONFIG_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; stderrWARN: …line emitted. - (W5)
power.sleep_mode != disabledwith nowakeup_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)
console: auto # auto | alp | uart | ram | linux | none (default: auto)
sim_console: false # opt-in RAM console for headless cores (default: false)
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.
console: — the backend, not the wire
Picks the console backend the loader wires per slice. The board layer still owns the physical console UART (the zephyr,console DT chosen node + pinmux); this knob only selects the backend.
| Value | Effect |
|---|---|
auto (default) | Resolve from the slice's OS and the SoM topology — see below |
alp / uart | Zephyr UART console; printf/LOG land on the module's console UART |
ram | Zephyr RAM console — read ram_console_buf over SWD on a serial-less bench |
linux | Yocto/Ubuntu stdio/tty console |
none | Emit no console Kconfig; inherit the board default |
auto resolution depends on whether the core has a hardware console. A Zephyr slice on a core the SoM topology marks hw_console: true resolves to uart; a Yocto/Ubuntu slice resolves to linux. But a headless core (hw_console: false — no hardware UART, e.g. the RZ/V2N M33 system-manager) resolves to none, not uart.
That last case matters: emitting CONFIG_UART_CONSOLE=y on a core whose board has no serial driver is an "assigned y but got n" Kconfig error and a fatal build failure. An explicit console: always wins over the auto path, on headless cores too.
sim_console: — give a headless core a console in the simulator
Opt-in. When true, emits CONFIG_RAM_CONSOLE=y for every headless core, so the studio hardware simulator's uart_socket can stream that core's boot/console output instead of sitting silent. Cores that already have a UART console are unchanged, and absent/false emits nothing — no RAM cost on production images.
Precedence is narrow by design: sim_console: only upgrades a core whose console auto-resolved to none because it's headless. An explicit diagnostics.console: is never overridden. The emitted Kconfig is identical to console: ram; only the teaching comment differs, so a reader can tell a bench SWD flow from a simulator one.
See tan renode --sim-mode for the consuming side.
Cross-field validation
scripts/alp_orchestrate/'s _validate_consistency() runs after the JSON Schema pass and the per-core loader rules. Five rules, two warnings:
| ID | Severity | Rule |
|---|---|---|
| R1 | ERROR | ota.provider: mender works on both Yocto + Zephyr; hawkbit / mcumgr require at least one Zephyr core. |
| R2 | ERROR | boot.signing.algorithm: must match the SoM family allow-list. |
| R3 | ERROR | cores.<id>.iot.tls: true requires mbedtls or bearssl in the top-level libraries: / the core's extra_libraries:. |
| R4 | WARN | cores.<id>.inference.default_arena_kib > cores.<id>.memory.heap_kib — inference may OOM. |
| R5 | WARN | cores.<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_*.
kind: raw_shmem auto-emits CONFIG_DCACHE=n
Declaring an ipc entry with kind: raw_shmem causes the build to emit CONFIG_DCACHE=n automatically for every core named in that entry's endpoints, unless the entry sets cacheable: true. It does not apply to kind: rpmsg or kind: mailbox_only.
ipc:
- { kind: raw_shmem, endpoints: [m55_hp, m55_he], carve_out_kb: 4, name: alp_shmem0 }
This closes a cross-core SRAM0 D-cache incoherence that produced silent false-negative results — one core's writes sat in its own D-cache while the peer read stale SRAM0 and saw a plausible-looking wrong answer rather than a failure.
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 tan 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
| Field | Required | Notes |
|---|---|---|
name | yes | ^[A-Za-z][A-Za-z0-9_-]*$. Names the output .alpmodel. |
source | yes | Path (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. |
spec | no | $ref to an external model spec, for large or shared models. |
inputs | no | App-side input hints (e.g. layout). |
compile | no | Per-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 tan model build (see Inference); load them at runtime with alp_inference_open_alpmodel().
How the orchestrator compiles the file
scripts/alp_orchestrate/ (driven by tan 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)
| Path | What it carries |
|---|---|
build/system-manifest.yaml | Per-slice status, log paths, artefact paths, boot order, blocked storage rows. |
build/generated/alp/system_ipc.h | Endpoint IDs, addresses, mailbox channel macros — shared by all slices. |
build/generated/dts-reservations.dtsi | reserved-memory: carve-outs shipped into Linux + Zephyr DTs. |
build/generated/dts-partitions.dtsi | fixed-partitions overlay from the resolved storage: block. |
build/sysbuild/tfm/tfm.conf | TF-M sysbuild child-image overlay (when security.psa.tfm: true). |
build/<core>-zephyr/alp.conf | Kconfig fragment layered onto each Zephyr slice's prj.conf. |
build/<core>-yocto/conf/alp-generated.conf | local.conf snippet consumed by bitbake (MACHINE=…, IMAGE_INSTALL). |
The system manifest is consumed downstream by tan image (assembles the bundle), tan flash (walks boot_order: per artefact), tan renode (boots the dual-OS image in simulation), and OTA (<alp/ota.h>).
Per-slice emit modes (driven manually if you bypass tan build)
--emit mode | Output |
|---|---|
zephyr-conf | alp.conf — Kconfig fragment, layered over prj.conf via OVERLAY_CONFIG. |
cmake-args | A list of -D flags for plain CMake / bare-metal. |
yocto-conf | local.conf snippet (MACHINE=…, IMAGE_INSTALL deltas). |
dts-overlay | Zephyr DTS overlay with alp,pin-array + bus aliases. |
dts-partitions | Fixed-partitions overlay for the resolved storage: block. |
storage-mounts-c | Static fs_mount_t alp_storage_mounts[] table. |
tfm-sysbuild-conf | TF-M sysbuild child-image overlay. |
system-manifest | Final system manifest YAML. |
ipc-contract-h | <alp/system_ipc.h>. |
hw-info-h | <alp_hw_info_build.h> with ALP_HW_BUILD_* macros. |
west-libraries | west.yml fragment pinning the libraries board.yaml requested. |
build-plan | Deterministic, write-free JSON build plan (one slice per non-off core, sorted by coreId) — the contract IDE/CLI tooling consumes (ADR 0014). |
os-topology | Per-core natural-vs-effective OS facts as JSON (core_type, runtime_class, default_os, effective_os, allowed_os). |
carrier-netlist | Schema-versioned JSON handoff for Alp Studio — carrier-facing nets + carrier BOM. See below. |
There is no single tan verb covering the whole catalog. tan generate writes the six build-generation targets (zephyr-conf, dts-overlay, native-sim-overlay, cmake-args, yocto-conf, carrier-netlist); west alp-emit prints the orchestrator subset (system-manifest, ipc-contract-h, dts-reservations, dts-partitions, storage-mounts-c, tfm-sysbuild-conf, build-plan); and the remaining modes above — including the demonstrator composed-route-table — are reachable only from scripts/alp_project.py --emit.
carrier-netlist — Alp Studio handoff
tan generate --target carrier-netlist
# 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'spad_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 —
scripts/alp_orchestrate/loader.py'sload_board_yamlresolves thehw_revisions:table for the SKU and refuses the build on any of three independent grounds (below). - 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 reportsALP_ERR_NOT_PROVISIONED. Boards may additionally encode their own revision on a board-side BOARD_ID ADC divider — an independent, board-side path.
The three build-time refusals
Checked in this order. Each is fatal, each has its own exit code from scripts/validate_board_yaml.py.
1. Exists — the value must be a declared key in the resolved hw_revisions table for that SoM/board. Otherwise SdkRevisionUnknown:
SoM <sku> hw_rev <value> is not a known hardware revision. Available hw_rev(s) for <sku>: <list>.
2. Buildable — the entry's status must be outside {reserved, tbd}. status is a required key on every hw_revisions entry (metadata/schemas/hw-revisions-v1.schema.json), and a missing status is treated as refuse, not pass. Otherwise SdkRevisionNotBuildable:
SoM <sku> hw_rev <value> exists but is not buildable (<status: 'reserved'> | carries no `status:` key).
status | Buildable? |
|---|---|
production | yes |
preview | yes |
preliminary | yes |
deprecated | yes |
reserved | no |
tbd | no |
| (key absent) | no |
3. In SDK range — the running SDK version must fall inside the entry's [min_sdk_version, max_sdk_version]. Otherwise SdkRevisionUnsupported:
SDK <version> does not support this hardware revision: <reason>. Pin an SDK inside the declared range, or set a hw_rev this SDK supports.
Exit codes
scripts/validate_board_yaml.py returns a distinct code per refusal, so CI can branch on which one fired:
| Constant | Code |
|---|---|
EXIT_SDK_REVISION_UNSUPPORTED | 3 |
EXIT_SDK_REVISION_UNKNOWN | 4 |
EXIT_SDK_REVISION_NOT_BUILDABLE | 5 |
board.yaml that validated before can now be refusedSpecifically one naming an hw_rev whose hw_revisions table entry carries no status: key. status is now required, and its absence is a refusal (SdkRevisionNotBuildable, exit 5) rather than a pass. If your build starts failing here, add the correct status: to the revision entry — don't switch hw_rev to hide it.
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. |
false | DNI — 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
- Heterogeneous Builds — dual-OS project walkthrough
<alp/rpc.h>— framed RPC over OpenAMP/RPMsgmetadata/templates/board.yaml— the canonical commented template- Architecture — how the orchestrator fits into the build
- Quick start — concrete walk-through