Skip to content

v0.6.0

Choose a tag to compare

@github-actionsgithub-actions released this 06 Jun 18:27
· 123 commits to main since this release
v0.6.0

Added — gd32-bridge v0.2.9 / protocol v0.7: the stale-reply kill + OTA version plumb (2026-06-06)

Wire protocol 0.6.0 → 0.7.0 (MINOR; the un-negotiated wire is
byte-identical to v0.6) and firmware 0.2.8 → 0.2.9.

  • CMD_LINK_FEATURES (0x81) + the negotiated STATUS_SEQ reply
    stamp
    — the architected kill for the transport's residual
    stale-reply hazard, silicon-fingerprinted the day before
    (byte-exact COUNTER_READ replays whose rate swung 0↔100 % on
    host timing phase). Once negotiated, every SPI reply carries a
    4-bit slave-side sequence stamp in STATUS[7:4], advanced per
    freshly-decoded request; the drain/rewind re-serves keep the same
    stamp, so a CRC-valid reply whose stamp has not advanced tells the
    host its request was never decoded — and since it was never
    executed, the host driver's automatic single re-send is safe for
    every opcode. Host telemetry in ctx->seq_stale_count;
    gd32g553_init() negotiates automatically and degrades to legacy
    framing against older firmware. I2C replies are never stamped
    (STATUS_NO_PENDING owns bit 7 there). Unit-pinned in
    tests/unit/gd32_bridge_transport (stamp advance / rewind-keeps-
    stamp / error-envelope stamping / mod-16 wrap / disable restores
    byte-identical legacy framing) + new fuzz corpus seeds + §12
    protocol vectors.
  • OTA_BEGIN carries the incoming image's version (additive
    3-byte triple): recorded into the A/B metadata fw_version[slot]
    at COMMIT — the record had reserved the field ("0 = unknown")
    since its v2 layout. Legacy 8-byte BEGIN still accepted;
    pre-v0.7 firmware ignores the trailing bytes.
    gd32g553_ota_begin() gains a nullable fw_version parameter
    (no-legacy-compat: signature changed, callers updated, ABI
    snapshot regenerated).
  • The HIL soak exports v0.7 link telemetry (seq_forensics[]:
    negotiated flag + stale-catch count) next to the existing
    counter-row discriminator, so the bench can WATCH the kill work.

Fixed — gd32-bridge v0.2.8: ISR-safety + error-masking fixes from the delta review (2026-06-05)

Three behavior fixes found by an adversarial review of the
v0.2.3→v0.2.7 delta (one critical, two major). Bench-validated:
YES (2026-06-06)
— v0.2.8 + the hal/gd32 TU split + the real-SHA
build-id smoked together on silicon: GET_BUILD_ID round-tripped
0.2.8+d038f981186a20 (exact HEAD match); the 20-row HIL soak ran
253/253 clean on every row including the new adc_stream_guard
converter-ownership probe; the Tier-B loopback repeated its 5/6
(DAC raws {151,450,900,1350} mV vs commanded {150,450,900,1350};
capture 5.000 ms / 2.500 ms exact; qenc still blocked by the known
carrier-wiring item). A counter-row anomaly during the first soak
window was root-caused to the transport's documented stale-reply
residual hazard (byte-exact reply replays, phase-dependent; raw
back-to-back pairs read equal 0/277 once re-phased) — transport-level
and pre-existing, not a v0.2.8 defect; the soak now carries a
permanent discriminator row for it.

  • CRITICAL — unbounded ADC-calibration spin reachable from the
    CS-EXTI request handler
    (hal/bridge_hw_gd32.c). The vendor
    SPL's adc_calibration_enable() spins on RSTCLB/CLB with no
    timeout, and adc_periph_init() routed through it from two
    in-handler sites: STREAM_END's converter restore and ADC_READ's
    timeout self-heal. A converter wedged hard enough to need the
    self-heal is exactly the one that can hold those bits forever — the
    recovery for the wedged-ADC case could itself wedge the whole SPI
    link (the same failure class the bounded EOC waits were added to
    stop). Calibration is now a bounded reimplementation; STREAM_END
    reports STATUS_IO if the restore calibration never completes (the
    stream still tears down).
  • MAJOR — single-shot ADC_READ could corrupt a live stream: two
    logical channels share each ADC converter, and a read on the sibling
    of a streaming channel re-pointed routine rank 0 out from under the
    stream's DMA, injected an unpaced software trigger, and consumed the
    EOC the DDM path depends on. Now refused (STATUS_IO) while the
    stream owns the converter; retry after STREAM_END.
  • MAJOR — a never-ready analog reference was silently swallowed:
    v0.2.6's bounded VREFRDY wait discarded its verdict, so a reference
    buffer that never locks would let every ADC/DAC op answer
    STATUS_OK with garbage millivolts — the exact masking class the
    VREF fix targeted, and unobservable on the wire (no GET_FAULT
    opcode). The readiness verdict is now latched; ADC_READ,
    ADC_STREAM_BEGIN, DAC_SET and DAC_GET answer STATUS_IO
    while the reference is dead, re-probing on each attempt so a
    late-locking buffer self-promotes.
  • Documentation from the same review: loopback verdict-slot legend
    corrected to the real {150, 450, 900, 1350} setpoints; "firmware-
    averaged" ADC wording removed (4 independent samples); host
    gd32g553_pwm_get doc now states hardware-readback semantics
    (shared-per-timer period, 65.536 ms / 0-duty boot default);
    PWM-capture single-wrap validity bound documented (spans ≥ CAR+1
    ticks alias undetected); TRNG fault-recover contract (one honest
    STATUS_IO, next pull succeeds) added to the wire spec.
  • firmware-version.txt 0.2.7 → 0.2.8.

Fixed — gd32-bridge v0.2.7: PWM-capture wrap-aware deltas (2026-06-05)

  • PWM input-capture read a wrapped-garbage period and a zero pulse
    width
    (hal/bridge_hw_gd32.c). pwm_capture_drain subtracted raw
    capture counts (now - last_tick); on the capture timer's own
    up-counter consecutive edges straddle the 0..CAR wrap, so the
    subtraction underflowed. All edge deltas are now taken modulo the
    counter period (CAR + 1). Most visible on the Tier-B PWM→PWM
    loopback where stimulus and capture share TIMER0: the same-edge
    "period" is now a clean ~0 (documented shared-timer degeneracy
    instead of 0xFFFB6C20 garbage) and the adjacent-edge pulse width
    is meaningful. The loopback example's capture test moves to a
    200 Hz / 50 % stimulus polled in a tight loop — 50 % removes the
    arm-phase ambiguity (high = low = period/2) and the slow rate lets
    the host catch three consecutive edges (the old 1 kHz + 5 ms retry
    ladder sampled non-consecutive edges, which is why it read 0).
    Silicon-validated end-to-end through the carrier jumper.
  • firmware-version.txt 0.2.6 → 0.2.7.

Fixed — gd32-bridge v0.2.6: enable the internal VREF (the analog subsystem was dead) (2026-06-05)

  • The GD32's entire ADC + DAC subsystem read garbage because the
    analog reference was never driven
    (hal/bridge_hw_gd32.c). On
    this module revision the converters' reference node depends on the
    GD32's on-chip reference buffer (no external reference source); at
    reset VREF_CS = 0x02 (HIPM high-impedance) parks the buffer, so
    every ADC channel + both DACs referenced an undriven node. It went
    unnoticed for the bridge's whole life because the ADC self-tests
    were ceiling-only (sample < 3400 mV passes on a zero reading) and
    DAC_GET echoes the digital hold register, not the pad — the
    jumpered Tier-B loopback is what finally caught it (DAC→ADC read 0
    with a known driven input). bridge_hw_init now enables the
    on-chip VREF buffer (RCU_VREF clock + vref_enable() at the
    2.048 V target, bounded VREFRDY wait) before any ADC/DAC
    bring-up, so the converters self-calibrate against a live
    reference. Bench-proven over SWD: with VREF up, a DAC→ADC copper
    loopback tracks 1:1 (DAC 2730 → ADC 2730; DAC 600 → ADC 599). The
    buffer's three targets all exceed the 1.8 V VDDA so it regulates at
    the rail (~VDDA); correctness in a loopback is ratiometric and
    independent of the exact value, and the absolute mV scale tracks
    VDDA (ADC_VREF_MV = 1800).
  • firmware-version.txt 0.2.5 → 0.2.6.

Added — gd32-bridge Tier-B jumpered loopback example (2026-06-05)

New examples/v2n/v2n-gd32-bridge-loopback: a maintainer bench tool
that closes three GD32 bridge signal paths in copper on the X-EVK
carrier and value-asserts them end-to-end — DAC0→ADC0 (direct 1:1
after the VREF fix), PWM→PWM input-capture (pulse width), and
PWM→quadrature-encoder stimulus. Documents the exact jumper header
pins (the buffered DAC output path is inoperable on this carrier rev —
see the internal carrier errata; the loopback taps the raw DAC0 net
instead), and reads its verdict block over SWD. Also fixes the input-capture HAL to use the GD32G5 MCH
capture units (the E1M PWM pads are MCH, not the classic CHx input
the engine previously armed) and to restore the channel's output
stage on capture_end (a capture session previously left the PWM
channel output-dead until reboot). metadata/boards/e1m-x-evk.yaml:
E1M_X_ADC0 corrected to the mikroBUS AN (XEVK_ADC_MIKROBUS_AN,
net CK_ANA) — the netlist routes only that to ANA_S0, not "Arduino
A0".

Fixed — gd32-bridge v0.2.4: ADC stream really streams (DDM + pacing timer) + the 216 MHz clock truth (2026-06-04)

  • ADC streaming produced zero samples since it shipped
    (hal/bridge_hw_gd32.c). Three stacked defects, root-caused by a
    three-angle audit (firmware vs vendor SPL vs host) and the vendor's
    own ADC0_routine_channel_with_DMA reference: (1) CTL1.DDM
    (adc_dma_request_after_last_enable) was never set, so the ADC
    stopped issuing DMA requests after one run — circular DMA without
    DDM stalls by design; (2) continuous/DMA controls were programmed
    onto an already-running converter — streaming now reconfigures with
    ADCON clear in the vendor's order; (3) the stream path never enabled
    the RCU_DMAMUX clock, working only because the SPI transport
    happened to enable it first (an I2C-only build would stream
    nothing).
  • sample_rate_hz is now a real hardware contract. v0.2.3 and
    earlier silently ignored it (free-run "aspirational hint"). Each
    stream now owns a pacing timer (stream 0: TIMER5, stream 1: TIMER6)
    whose update-event TRGO triggers exactly one conversion per period
    via TRIGSEL: 1 Hz..100 kHz, tick-quantised, STATUS_OUT_OF_RANGE
    above the cap. One stream per ADC converter (second BEGIN on a
    shared converter answers STATUS_INVAL); STREAM_END stops the
    pacer and restores the converter's single-shot state. The HiL soak
    row now PROVES pacing with a two-read assertion (a free-running
    stream answers the 32-cap twice; a paced one leaves ~18 samples).
  • Every GD32 timer-clock constant was wrong: the part runs 216 MHz,
    not 240 MHz.
    The SoM's SystemInit override is 216M-PLL-IRC8M with
    APB at DIV1, and the GD32G5x3 has no separate timer PLL (GigaDevice's
    own example states "TIMER0 frequency is fixed to 216MHz") — the
    "240 MHz advanced-timer clock" in metadata/chips/gd32g553.yaml was
    an invented value that propagated into the firmware. Consequence:
    every PWM period ever generated was ~11 % long (a commanded
    1 kHz physically ran ~900 Hz). Fixed: PWM_TIMER_PRESCALER 240→216
    (true 1 µs tick), pwm_routing.counter_clock_hz 240 M→216 M, LSB
    4.16→4.63 ns and max single-counter period 273→303 µs corrected
    across metadata/, include/alp/chips/gd32g553.h, include/alp/pwm.h,
    docs/gd32-bridge-protocol.md, and firmware comments.
  • Doc drift: STREAM_BEGIN on an active slot answers STATUS_INVAL
    (the doc claimed STATUS_BUSY); host header's "~1.5 Msps cap"
    replaced with the real 100 kHz pacing contract.
  • firmware-version.txt 0.2.3 → 0.2.4.

Added — --emit build-plan: machine-readable build plan for external front-ends (2026-06-04)

  • python3 scripts/alp_orchestrate.py --emit build-plan emits the
    resolved build plan as deterministic, write-free JSON: one slice per
    non-off core (build dir, exact tool command, env) plus every
    generated artefact with its contents (sharedArtefacts +
    per-slice configArtefacts), so a consumer materialises files and
    runs commands with zero planner logic of its own. This is the
    agreed contract for the alp CLI / IDE extension (alp-sdk-vscode
    "Wave C") — see ADR 0014
    for the settlement: camelCase keys, independent schemaVersion
    (bumped + flagged here on breaking shape changes), no inputHash /
    sequential (cache keys + parallelism belong to the consumer), and
    command-less slices carried as command: null + a no-command
    warning. Consumers: pin to release tags; the per-slice command
    shape is not frozen
    (it will grow --sysbuild flags when the
    conf→build wiring lands).
  • The Orchestrator's materialise step and the plan emit now read the
    same single sources (_shared_artefacts / _slice_config_artefact),
    so the plan and the on-disk build cannot drift by construction.
    This also fixes a latent gap: emit_sysbuild_conf was emit-only —
    a boot: block now materialises build/alp_sysbuild.conf during
    west alp-build too, matching its long-documented destination.

Fixed — gd32-bridge v0.2.3: honest PWM read-back + ISR-bounded ADC waits (2026-06-04)

  • PWM_GET reads the silicon, not a cache (hal/bridge_hw_gd32.c).
    bridge_hw_pwm_get answered from a last-request software cache, so a
    host could "verify" a PWM that never reached the pad — exactly what
    happened on the bench: every PWM pass to date was the firmware echoing
    the host's own request (maintainer's scope showed no signal ever).
    The handler now reads the live CAR/CHxCV registers back and
    converts ticks to ns, honouring the read-back contract
    docs/gd32-bridge-protocol.md §3.8 always documented. Consequences
    now stated in the doc + hal/bridge_hw.h: per-timer shared period is
    visible in the read-back, and a never-set channel reports the boot
    default (65.536 ms / 0) instead of zeros. The PWM output stage
    itself was proven good
    by driving TIMER7 CAR/CH3CV over SWD
    and watching PD0 toggle — the "dead PWM" was the cache echo masking
    the fact that nothing on the bench was driving the channel.
  • Unbounded waits removed from the CS-EXTI handler path
    (hal/bridge_hw_gd32.c). bridge_hw_adc_read's EOC poll could spin
    forever inside the CS interrupt handler; one wedged conversion froze
    the handler, SPI RX DMA never re-armed, and the whole link rotted
    (caught live on SWD: DMA0 CH3 CHEN=0, count frozen mid-frame). The
    poll is now bounded (timeout → ADC re-init + BRIDGE_HW_ERR_IO);
    adc_stream_begin pre-clears stale EOC + re-inits the DMA param
    struct, adc_stream_end adds a settle dwell + unconditional EOC
    clear, and adc_periph_init gains a stabilization spin + explicit
    calibration. Soak-validated: 18/18 active rows clean for 410+
    cycles with adc_stream active alongside (its own zero-sample
    failure is still open, tracked separately).
  • HiL soak: every remaining quarantine row re-activated (qenc is
    status-only — floating A/B inputs free-run; trng retries once per
    the documented recovery cycle).
  • firmware-version.txt 0.2.1 → 0.2.3 (0.2.2 was consumed by the
    OTA bench as the slot-B upgrade-test image version).

Added — full-bridge functional tier + math/TRNG silicon fixes (2026-06-04)

New examples/v2n/v2n-gd32-bridge-functional: single-pass, VALUE-asserting
validation of the bridge surfaces (the soak proves the link; this proves
the functions) — 18 TMU math probes against known-exact results (incl.
Q31), TRNG entropy/boundary pulls, PWM setpoint readback + configure
contract, ADC configure error-path + all-channel sweep, DSP-chain pool
lifecycle, identity — then a forever PWM7 duty staircase (the EVK LED
pad) as a live oscilloscope observable. 26/26 FUNCTIONAL-CLEAN on
silicon
, after the suite itself caught and drove the fixes below:

  • TMU (firmware): the vendor tmu_two_*_write() issues its two
    IDATA stores back-to-back; with a warm i-cache the TMU swallows the
    second word and ENDF never sets (computed exactly once per power-up —
    the cache-cold first call). Writes are now paced by a CS read-back.
    Angle units fixed in BOTH directions: the CORDIC takes and returns
    angles in units of pi, the wire contract is RADIANS (documented in
    the host header) — sin/cos inputs are normalised, atan/atan2 outputs
    scaled back. sin(0)-only probes could never see either bug.
  • TRNG (firmware): the unit takes intermittent SEED ERRORS and
    parks with the LATCHED flags set (TRNG_STAT = 0x48 = ERRSTA+SEIF
    while current-status SECS reads clear) — the old CECS/SECS-only check
    was blind to it and burned its whole DRDY budget on a dead unit.
    Now: latched-fault detection first (vendor trng_ready_check
    parity), incremental bring-up + lazy per-read rebuild (full re-seed),
    and a whole-request DRDY budget that answers the new STATUS_BUSY
    (BRIDGE_HW_ERR_BUSY) instead of overrunning the reply window; the
    host driver retries BUSY. Recovery silicon-validated.
  • Host driver: firmware ERROR replies carry no payload, so for any
    payload-bearing opcode the fixed-width reply read CRC-failed on a
    legitimate error and masked the real status as ALP_ERR_IO — an
    entire class of "-5 from cycle 1" HiL rows (pwm_capture's documented
    NOSUPPORT among them) was this. The host now decodes the short
    error envelope, and after any failed command sends one throwaway
    PING so a stale staged reply can never be mis-attributed to the next
    command; the firmware bounds drain rewinds (tar-pit breaker) on its
    side.
  • HiL soak: pwm_capture / qenc / tmu / trng / ota_get_state
    un-quarantined (their "-5 from cycle 1" was the transport bug + the
    masked-status bug, not broken HAL bodies); qenc gets a floating-input
    noise bound; trng one retry per the documented fault-recover cycle.
    adc_stream stays quarantined (destructive failure mode — its own
    supervised pass is next).

Changed — 25 MHz link: SCI-B FIFO burst engine + reply re-read made real (2026-06-04)

The GD32↔CM33 SPI link's wire density and per-command latency were
rebuilt around silicon measurements (scope + CM33 bus/timer probes: one
SCI7 register access ≈ 341 ns; k_busy_wait(10) ≈ 15 µs measured):

  • CM33 SCI-B driver (zephyr/drivers/spi/spi_renesas_rz_sci_b.c):
    the polled engine now runs the SCI's 32-deep FIFOs (CCR3.FM, armed
    outside the FSP whose FIFO path is DMA-gated) with a credit-bound
    burst loop sized off the FRSR.R fill counter — at most 31 bytes in
    flight, which provably bounds both FIFO occupancies, so there is no
    per-byte flag poll at all. Inter-byte wire gaps drop from ~5 µs
    (serialized one-in-flight walk) to wire-rate bursts, scope-confirmed
    at 25 MHz. CS setup/hold windows laddered 60/30 → 3/2 µs and moved
    off k_busy_wait onto a raw SysTick spin (the kernel's
    k_cycle_get_32 poll is µs-coarse, overshooting every wait by ~50%;
    delta-capped so tickless LOAD rewrites can only lengthen a window);
    the dead spi_context gpio-CS path is skipped entirely under the
    direct-latch shim (single CS owner). recover() is FIFO-aware
    (flush + the FIFO-mode TEND-anomaly-safe TE clearing per the
    vendor's own Close sequence). The ALP_V2N_SCI7_DMAC DMA path was
    re-baselined against the e2-studio FSP generator's SCI+DMAC pairing
    (ack MASK_DACK_OUTPUT, NO_DETECTION, request direction
    SOURCE_MODULE both ways, DREQ/DACK/TEND pins explicitly unused —
    our sweep had REQD inverted on RX and the pin fields zero-initialised
    to PIN0) and wired end-to-end (transceive() FSP branch, cfg gate
    follows the same switch, RX arm moved off the rzv R_DMAC_B_Reset
    stub onto reconfigure()). Benched: the DMAC now streams
    complete transactions (the old parks-after-one-beat diagnosis is
    dead); remaining blocker is an intermittent CHSTAT.ER AXI fault on
    TDR writes through the convert window — bus-master security lead,
    see docs/gd32-link-sci7-next-rev.md. Gate stays default-off; the
    polled FIFO engine is the production path.
  • gd32-bridge firmware v0.2.1: a reply-drain transaction now
    rewinds the staged-reply cursor before re-arming TX DMA, and an
    EMPTY transaction (CS pulse whose bytes were lost to edge coalescing
    mid-handler) does the same (src/transport_spi.c). Previously the
    gd32 backend's stage-time drain left the cursor spent, so a host
    reply read landing before a slow handler (ADC burst ≈ 18-20 µs per
    sample) finished staging disarmed the reply permanently — caught
    live on SWD as 255-of-256 failures with the correct reply intact in
    the buffer and the TX DMA count frozen mid-prefill; the empty-path
    variant turned one collision miss into a guaranteed double miss.
    With the rewind, every drain re-stages the same reply and the host's
    re-read schedule converges. New
    tests/unit/gd32_bridge_transport pins the seam contract (6 cases,
    linked against the production transport + dispatcher).
  • Host driver (chips/gd32g553/gd32g553.c): replies are read
    after an opcode-aware staging gap (35 µs base + 8 µs/sample for
    ADC_READ — the measured optimum against the slave's ~18-20
    µs/sample conversion cost) and re-read down a short-first backoff
    ladder (25 µs → 1.6 ms, ~3.2 ms bound) instead of the flat 250 µs
    schedule. V2N link consumers (bridge examples, quickstart snippet)
    now pass ALP_SPI_NO_CS — the platform SPI driver owns the CS pad;
    routing it through the generic gpio-CS path double-drove P97.
  • docs/gd32-bridge-protocol.md §4.1 now specifies the re-read
    schedule as the host contract (the fixed ≥ 100 µs pre-read wait
    remains a valid but slower alternative). The link's transport is
    SCI7 permanently (an RSPI reroute was rejected — pads committed
    elsewhere); docs/gd32-link-sci7-next-rev.md replaces the RSPI
    migration plan, and stale "RSPI master" wording was corrected across
    the docs/headers. firmware/gd32-bridge/CMakeLists.txt now re-runs
    configure when firmware-version.txt changes (incremental builds
    used to bake a stale GET_BUILD_ID).

Measured on the bench (256-op averages, 25 MHz, errors 0/256 across
the board), before → after: ping 130 → 120 µs, get_version 151 →
133 µs, gpio_read 2680 µs at 255/256 FAILING → 153 µs/0 errors,
adc_read(4) 2789 µs at 254/256 failing → 196 µs/0, adc_read(8)
3428 µs at 256/256 failing → 261 µs/0. Raw transaction floor 49 →
27 µs; marginal wire cost ≈ 0.85 µs/byte (engine-bound; true wire
rate stays the DMAC path's job).

Changed — gd32-bridge protocol v0.6 + OTA Path-A real, silicon-validated (2026-06-04)

Wire MINOR bump PROTOCOL_VERSION 0.5.0 → 0.6.0 (firmware/gd32-bridge/ src/protocol.h, host mirror in <alp/chips/gd32g553.h> in lock-step):
OTA_WRITE_CHUNK's request payload is now length-checked
(offset:u32 len:u8 data[len]), closing a silicon-caught wire hole —
a slave re-capture merged with the next transaction's zero filler still
passes the span CRC whenever the frame's own CRC-16/CCITT-FALSE is
byte-palindromic (self-consumption to 0x0000; ~1 chunk in 256), which
inflated the payload length and double-programmed the ECC flash. A
len/span mismatch now answers STATUS_INVAL without touching the
session; OTA_BEGIN's chunk_max drops 61 → 60. Pre-1.0 the host
and firmware must ship in lock-step including MINOR
— the handshake
gates on MAJOR only.

The OTA 0xF0..0xF6 range also went from scaffold to real Path-A,
silicon-validated end-to-end
(stream → verify → commit → boot slot B →
rollback → boot slot A, proven by wire build-id reads + the A/B metadata
generation history): RAM-resident dual-bank FMC backend
(hal/fmc_ota.c, OBCTL.DBS-aware paging + sticky-PGERR clearing),
struct-v2 per-slot metadata (rollback no longer bricks), WRITE/VERIFY
session gates, replay dedupe, PRIMASK bootloader-handoff fix, corrected
CRC-32 polynomial (0xEDB88320), real GET_BUILD_ID content
(<fw-version>+<sha-placeholder>), and host OTA state/slot enum values
aligned to the firmware wire (GD32G553_OTA_SLOT_A=0/B=1/NONE=0xFF;
READY/BUSY/VERIFIED/ERROR). New tools/gen_ota_metadata.py factory
provisioning helper; new examples/v2n/v2n-gd32-bridge-hil-soak
full-command-set pass/fail soak (which caught five pre-existing HAL
defects on first silicon exercise — quarantined in its table with
notes). Protocol vectors regenerated (--check green).

Changed — DA9292_STATUS_FORWARD semantics re-spec (2026-06-03; corrected 2026-06-04)

Docs/comments re-spec of the DA9292_STATUS_FORWARD (opcode 0x40)
surface to match the wiring: the GD32 supervisor has no I2C path to
the DA9292 PMIC. 2026-06-04 schematic verification went further: on the
current SoM revision the DA9292 fault nets reach only the Renesas
(DA9292_INT → P37, DA9292_TW → P36) — the GD32 has no connection to
them at all, so the forwarded byte is always the 0xFF "no sample"
sentinel
on this hardware; the bit0 = INT / bit1 = TW packing is
reserved for a future HW rev that mirrors the nets onto GD32 inputs.
Removed all "cached PMC_STATUS_00 / periodic I2C-master poll / ≤ 20 ms
cache age" language from the host header, firmware comments, protocol
spec, tutorials, and the bridge-ping example. The host observes the
fault pins directly via the new da9292_get_fault_pins() (same bit
packing) and reads register-level PMIC status over BRD_I2C via
da9292_get_status() — both in the chips/da9292 driver.

Changed — hw_info: the EEPROM manifest is the sole SoM-rev source (2026-05-31)

The on-module EEPROM manifest (magic + schema_version + CRC32) is now
the single authoritative source of the SoM hardware revision; the
SoM-side BOARD_ID ADC cross-check path was removed outright
(no-legacy-compat). Breaking (pre-1.0 minor): the [ABI-STABLE]
struct alp_hw_info_t drops the som_board_id_mv field; ABI snapshot
regenerated. alp_hw_info_read() return-code contract sharpened:
blank manifest → ALP_ERR_NOT_PROVISIONED, corrupt manifest →
ALP_ERR_IO, EEPROM unreachable → ALP_ERR_NOT_READY, no bus
configured → ALP_ERR_NOSUPPORT. Carrier-side board_id_mv /
board_hw_rev stay (board-side BOARD_ID is a separate, carrier-owned
path).

Added — ALP_ERR_NOT_PROVISIONED status code (2026-05-31)

New alp_status_t enumerator ALP_ERR_NOT_PROVISIONED (= -15) in
<alp/peripheral.h>. Returned when the on-module EEPROM manifest is blank
(all-0xFF or all-0x00) — i.e. the module has not yet been through the factory
provisioning tool. Distinct from ALP_ERR_IO (= -5), which covers a CRC
mismatch or corrupted manifest on an otherwise-reachable EEPROM. Callers that
previously conflated the two conditions can now present a clearer diagnostic
("unprogrammed" vs "corrupted").

Added — doc-drift CI gate (2026-05-27)

scripts/check_doc_drift.py + .github/workflows/pr-doc-drift.yml: an
objective gate that fails when (a) a customer doc references an ALP_* / alp_*
identifier that exists in no SDK source layer (public + internal headers,
Kconfig, CMake, generators, schemas, Yocto), or (b) a top-level docs/*.md is
not linked from the docs/README.md index. "Known" identifiers are harvested
from the real sources (not a hand-kept allowlist), so the gate stays
low-maintenance and free of build-identifier false positives. Runnable locally
(python scripts/check_doc_drift.py); wired into the local-CI cheat sheet.
Catches exactly the stale-reference class the .alpmodel / DEEPX_DX → DEEPX_DXM1 rename left behind.

Added — .alpmodel Stage 2 compile-config plumbing (2026-05-27)

Board.yaml models[].compile: block threads per-backend compile configuration
(DRP-AI spec path, DEEPX JSON config + calibration dir) from board.yaml into
alp model build. Backends that need a per-model config (drpai, deepx_dxm1)
record a coverage: skipped ("no compile config") entry when no block is
supplied — instead of silently skipping on toolchain-absent check alone. The
compile: block is validated by metadata/schemas/board.schema.json
(additionalProperties: false; deepx_dxm1 requires both config: and
calibration:; drpai requires spec:; unknown backend keys are rejected).
All path values are resolved relative to the board.yaml file before being
passed to the adapter. No vendor tools required; fully testable on any host.
Real DeepxAdapter.compile() is now implemented: it shells out
dxcom -m <onnx> -c <config.json> -o <dir> and returns the single <stem>.dxnn
the compile produces (blob_format: dxnn, raw bytes — matching the device-side
ALP_INFERENCE_MODEL_DXNN). Confirmed against the licensed dx-com 2.3.0 wheel
by a real compile of both a tiny CNN and a real yolo11n object detector: the
-o dir holds one <stem>.dxnn flatbuffer (compiler.log only with
--gen_log); calibration is referenced from the JSON config, not a CLI flag;
dxcom needs >15 GiB host RAM. Covered by a mocked shell-out test, a
which("dxcom") version smoke, and skipif-gated end-to-end real-compile tests —
a hermetic tiny ONNX fixture (public) plus the real yolo11n (alp-sdk-internal).
DrpaiAdapter.compile() (open DRP-AI TVM) and the Yocto dx_rt / DRP-AI runtime
backends remain Stage 2 (gated on the DRP-AI TVM build / licensed dx_rt SDK +
bench silicon; tracked by issues #58/#59).

Added — portable .alpmodel model pipeline (Stages 1a–1c, 2026-05-26..27)

End-to-end AI-model pipeline so one model is portable across NPU back-ends.
Host side: alp model build (scripts/alp_model/) compiles a source model
for every backend the SoM declares into a fat multi-backend .alpmodel
package (CBOR manifest + per-backend blobs + a capability requires envelope).
On-device: alp_inference_open_alpmodel() + a pure selection engine
(src/backends/inference/alp_model_select.*) picks the matching blob by
silicon-ref + SRAM-fit (ALP_SOC_NPU_ARENA_SRAM_KIB) + preferred_backend
tiebreak — ALP_ERR_NO_FIT / ALP_ERR_NO_BACKEND / ALP_ERR_NOT_FOUND
otherwise — then delegates to the existing backend registry. On-device reader
gated behind CONFIG_ALP_SDK_MODEL_READER. Renamed the backend enum
ALP_INFERENCE_BACKEND_DEEPX_DX…DEEPX_DXM1. Real proprietary DRP-AI /
DEEPX compiles + Yocto runtime backends = Stage 2 (gated on licensed tools +
bench; tracked by issues #58/#59).

Added — per-bridge firmware release versions (2026-05-26)

The on-module GD32 and CC3501E bridge firmwares each gained an
independent firmware release version (firmware/<mcu>/firmware-version.txt,
semver), tracked separately from the wire-protocol version and the
build-id so a fielded device's firmware is trackable on its own. The
GD32 build embeds it (GD32_BRIDGE_FW_VERSION, surfaced via GET_BUILD_ID
as <ver>+<sha>); the CC3501E reports it as fw_version via GET_VERSION
and names its prebuilt blob from it. The three-axis model (firmware
release / wire protocol / build-id) is documented in
docs/gd32-bridge.md + docs/cc3501e-bridge.md.

Changed — V2N Linux build + bridge docs reconciled to validated reality (2026-05-26)

The Renesas RZ/V2N Linux docs were reconciled to the WSL-validated build:
the Yocto path is the bitbake-layers flow in meta-alp-sdk/README.md
(kas retired, kas/e1m-v2n.yml removed); the version framing is AI SDK
platform 7.1 / BSP v6.30
(linux-renesas 6.1.141-cip43) throughout; and
the layer is meta-alp-sdk (the deleted yocto/meta-alp/ name was
scrubbed from live docs). The shipped-but-undocumented <alp/*> surfaces
(storage, dsp, tmu, power, rpc, gpu2d, backend,
e1m_x_pinout, ext/<vendor>) are now reflected in the README stack
graph + Public API table, docs/architecture.md, and
docs/os-support-matrix.md. The pre-flash provisioning model (V2N
bootloader / CC3501E / GD32 — both MCU bridge firmwares open) is
documented, and login-gated vendor download links were removed from
public docs.

Changed — examples reorganised into category subdirectories (2026-05-24)

The 43 flat top-level example folders now live under category
subdirectories matching the examples/README.md functional index:
peripheral-io/, audio/, camera-vision/, ai/, connectivity/,
display/, power-timing/, multicore/ (the aen/ and v2n/
platform groups already used this layout). History is preserved via
git mv; every examples/<name> reference -- per-example west build
commands, the functional index, .vscode tasks, scripts/bootstrap.sh,
CI workflows, docs -- is updated to examples/<category>/<name>. No
source or build-system changes: twister discovers examples recursively,
so build invocations simply gain the category path segment. (Past
CHANGELOG entries keep their original flat paths -- they record the
state at the time they were written.)

Changed — E1M-X pinout header synced to the full x-v1.0 connector (2026-05-24)

<alp/e1m_x_pinout.h> was reconciled against the authoritative
alplabai/e1m-spec pinout/x-v1.json (E1M-X x-v1.0), which already
enumerates the entire ASS6880 LGA496 module connector. The earlier
interim edit that added I2C2/I2C3 "pending upstream e1m-spec sync" is
superseded — x-v1.0 already defines those pads, so the caveat is gone.
No e1m-spec change was needed; the header was simply behind the spec.

  • Peripheral instance IDs now cover the full connector and are
    formatting-normalised: E1M_X_I2C0..3, E1M_X_SPI0..2,
    E1M_X_CAN0..1, E1M_X_CSI0..3, E1M_X_DSI0..1, E1M_X_USB0..2,
    E1M_X_PCIE0..1.
  • New parallel-RGB-LCD class E1M_X_LCD0 (pads LCD_B0..LCD_B23 +
    LCD_HSYNC + LCD_VSYNC; the connector has no separate PCLK/DE pad),
    with count macro E1M_X_LCD_COUNT (1).
  • GPIO-secondary indices appended at index >= 62 for the new
    single-ended digital pads (I2C2/3, SPI2, CAN1, and the LCD0 block);
    E1M_X_GPIO_COUNT grows 62 -> 99.

This extension is additive and ABI-safe — every pre-existing
instance ID and GPIO index keeps its value. Differential pairs
(CSI/DSI/PCIe/USB/ETH) are not GPIO-capable and are omitted from the
GPIO index space.

Added — Linker-section backend registry (Slices 0..7) (2026-05-23)

Replaces the per-class #if-ladder dispatch that lived in
src/zephyr/peripheral_<class>.c with a portable, linker-section
backend registry. Every supported peripheral class (adc, i2c,
spi, uart, gpio, pwm, i2s, can, counter, qenc, rtc,
wdt, usb, ble, wifi, mqtt, audio, mproc, rpc,
security, dsp, tmu, storage, power, camera, inference,
display, gpu2d) now dispatches through a class-specific section
walked by alp_backend_select() in src/backend.c.

  • Class dispatchers live at src/<class>_dispatch.c and own the
    handle pool. They are OS-agnostic — no <zephyr/...> types in the
    shared <class>_ops.h headers per issue #34's cleanup.
  • Backends live at src/backends/<class>/<vendor>.c and register
    via ALP_BACKEND_REGISTER(<class>, <vendor>, { … }). Variants today:
    • Real Zephyr backends (zephyr_drv.c / zephyr_video.c /
      zephyr_pm_policy.c / zephyr_flash.c / zephyr_littlefs.c)
    • SW fallbacks (sw_fallback.c) registered at silicon_ref="*"
      priority 0 — universal floor.
    • Vendor-specific bridges (gd32_bridge.c for adc/counter/qenc
      on V2N) registered at silicon_ref="renesas:rzv2n:n44" priority
      100.
    • NOT_IMPLEMENTED stubs for backends whose vendor pack hasn't
      landed yet (DRP-AI3 → issue #58, DEEPX DX-M1 → issue #59).
  • Selector tiebreaker (3 tiers, documented in src/backend.c +
    <alp/backend.h>): (1) higher priority wins; (2) exact silicon_ref
    beats * wildcard at equal priority; (3) alphabetic vendor at
    same priority + match-type. Test coverage in
    tests/unit/backend_registry/src/test_registry.c and
    tests/unit/backend_registry/src/test_bridge_selection.c.
  • Capability getters added: alp_<class>_capabilities() for
    every migrated class, returning the shared alp_capabilities_t
    • alp_instance_cap_t flags populated by the backend's
      ops->probe() at open time.
  • Vendor extensions (<alp/ext/<vendor>/<class>.h>) for the
    silicon-specific surfaces that don't fit the portable API:
    • <alp/ext/alif/adc.h> (oversampling, trigger source)
    • <alp/ext/alif/storage.h> (OSPI SecAES — body NOSUPPORT until
      Alif HAL pack)
    • <alp/ext/nxp/storage.h> (FlexSPI OTFAD — body NOSUPPORT until
      NXP pack)
    • <alp/ext/renesas/power.h> (GD32 supervisor mode set via
      CMD_POWER_MODE_SET opcode 0x28)
    • <alp/ext/renesas/camera.h> (V2N N44 ISP fine-grained knobs)
    • <alp/ext/alif/camera.h> (Mali-C55 — body NOSUPPORT until
      Alif HAL Mali-C55 pack)
    • <alp/ext/renesas/inference.h> (DRP-AI3 pipeline-stage knobs)
    • <alp/ext/deepx/inference.h> (DX-M1 slot + tile management)
  • Inference Kconfig ladder retired. CONFIG_ALP_SDK_INFERENCE_TFLM
    _BACKEND_TFLM; _DRPAI_BACKEND_DRPAI_V2N; _ETHOS_U
    _BACKEND_ETHOS_U_AEN / _ETHOS_U_N93; _ETHOS_U_U55
    _ETHOS_U_VARIANT_U55 etc.; _TFLM_NEON/HELIUM/REF
    _TFLM_KERNEL_NEON/HELIUM/REF. Customer-facing
    ALP_INFERENCE_BACKEND_AUTO/CPU/ETHOS_U/DRPAI/DEEPX_DX enum
    unchanged.
  • CI gates (scripts/check_stub_issues.py, _sw_fallback_tags.py,
    _vendor_ext_tags.py) enforce per-backend documentation
    conventions (issue trackers on stubs, @par Cost/Performance on
    SW fallbacks, @par Supported silicon on vendor extensions).
  • Yocto-side migration deferred to tracking issue #33.

ADR / spec: docs/architecture/backend-registry.md +
docs/superpowers/specs/2026-05-21-backend-registry-design.md.

Added — OTA Zephyr-client provider-driven dispatch (ADR 0009 resolved) (2026-05-20)

Lands the v0.6 follow-on to ADR 0009: ota.provider: is now a
provider-driven dispatch with real Zephyr-side emit for every
recognised provider, not just Yocto-side Mender.

  • Schema enum widened: ota.provider: hawkbit joins mender /
    mcumgr / none (metadata/schemas/board.schema.json).
  • _slice_alp_conf() emits per-provider Kconfig on every Zephyr
    slice when ota: is declared:
    • menderCONFIG_MENDER_MCU_CLIENT=y + server URL / tenant
      token / artifact name / poll interval (Mender-MCU-client,
      out-of-tree BSD-3).
    • 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 (UART / BLE / UDP) stays
      the app's call.
  • Yocto-side dispatch unchanged: provider: mender keeps the
    existing MENDER_* weak-assignments in local.conf.
  • west.yml fragment emit (scripts/alp_project.py _emit_west_libraries)
    auto-adds a mender-mcu-client name-allowlist: entry when
    ota.provider: mender is declared. Hawkbit and MCUmgr are
    Zephyr-upstream so no west.yml change is needed.
  • Validator rule 1 (P2.3) relaxed for the new dispatch:
    • provider: mender accepts EITHER a Yocto OR a Zephyr core
      (was Yocto-only).
    • provider: hawkbit requires at least one Zephyr core.
    • provider: mcumgr requires at least one Zephyr core.
      Errors point at the offending rule with the resolved-dispatch
      hint.
  • Tests: 6 new cases under tests/scripts/test_alp_orchestrate.py
    (mender-on-zephyr ok, mender-with-no-target rejected, hawkbit
    requires zephyr, hawkbit Kconfig emit shape, mcumgr requires
    zephyr, mcumgr SMP Kconfig emit). Pre-existing
    mender_without_yocto_rejected test rewritten to assert the
    new "ok" behaviour.
  • Docs: docs/board-config.md § "OTA" rewritten with the
    per-provider emit matrix + validator rule listing.

Added — per-library HW-backend profile coverage (25 profiles) (2026-05-20)

Priority 2.2 of the v0.6 milestone lands as a regression guard.
All 25 libraries enumerated in cores.<id>.libraries: in
metadata/schemas/board.schema.json already ship a
metadata/library-profiles/<libname>/hw-backends.yaml table
declaring their per-class accelerator binding (crypto, gpu_2d, dma,
simd, cordic, fft, ml_npu_primary, ...) against the SoM
preset's capabilities: matrix. Audit revealed full coverage; the
gap was test-side -- no regression test enforced that the schema
enum and the on-disk profile set stay in lock-step.

  • New tests/scripts/test_library_profiles.py (77 parametrised
    cases) enforces four invariants:

    1. Coverage — every library in the schema enum has a matching
      hw-backends.yaml; every shipped profile directory maps back
      to an enum entry (no orphans). The single cmsis_dsp
      cmsis-dsp directory rename is accommodated explicitly,
      mirroring _LIBRARY_WEST_MODULES in scripts/alp_project.py.
    2. Shape — each profile carries the required top-level fields
      (schema_version, library, class, accelerators,
      sw_fallback, verification) and the library: slug matches
      the directory's normalised name.
    3. Binding axis — each profile surfaces at least one of an
      accelerators[] entry, a sw_fallback.kconfig: knob, or a
      verification: block. Empty profiles are rejected.
    4. Kconfig well-formedness — every emitted kconfig: line is
      a real-looking CONFIG_<NAME>=<value> token (y / n / m /
      quoted string / integer / hex) OR a comment-only sentinel
      (# foo: ...) for header-only libraries. We intentionally do
      NOT verify that each symbol exists in Zephyr's Kconfig tree
      — that's a build-time concern that depends on the pinned
      Zephyr version.
  • docs/recommended-libraries.md grows a "HW-backend profiles
    (per-library accelerator binding)" subsection summarising the
    coverage matrix (crypto / ML / DSP / FS / graphics / sensor-fusion
    / industrial / IoT / audio / header-only / test) and pointing at
    the new regression test.

The 18 libraries carrying at least one requires_cap:-gated
accelerator entry today (every SoM-relevant library): bearssl,
cmsis_dsp, coremqtt_sn, gfx_compat, libcoap, libhelix,
libwebsockets, littlefs, lvgl, madgwick_ahrs, mbedtls,
minimp3, modbus, opus, pid, tflite_micro, tinygsm,
u8g2. The remaining 7 (etl, fmt, nlohmann_json, doctest,
catch2, jsmn, nanopb) declare an empty accelerators: list —
their value lives in the pure-SW path with no accelerator class
to bind.

No source / loader / data changes outside the test + docs +
CHANGELOG. Existing 402-test suite still green (now 479 with the
77 new parametrised cases).

Added — HiL coverage for boot: / memory: / power: / diagnostics.modules: (2026-05-20)

  • Four new portable HiL specs under tests/hil/_common/, one per
    declarative board.yaml block landed at schema level in PR #3.
    Each spec asserts the orchestrator's CONFIG_* emit actually reaches
    real silicon (MCUboot is the first boot stage; CONFIG_MAIN_STACK_SIZE
    shows up in the runtime trace; PM subsystem suspend/resume cycle
    fires from a declared wakeup source; per-module log levels filter):
    • boot_mcuboot.yaml — covers boot:
    • memory_stacks.yaml — covers cores.<id>.memory:
    • power_sleep_wake.yaml — covers cores.<id>.power:
      (flags pending_hardware_support: deep-sleep-current-draw — the
      AEN HiL rig doesn't carry an inline ammeter yet; the spec falls
      back to serial-trace assertions)
    • diagnostics_modules.yaml — covers diagnostics.modules:
  • Host-side cross-check at tests/scripts/test_hil_blocks_coverage.py
    (23 tests) validates each spec parses + the matching emit code
    produces the CONFIG_* lines the spec claims to observe. A schema
    field the emit silently drops fails CI on a normal machine, before
    the HiL runner ever sees it.
  • .github/workflows/nightly-aen-hil.yml now invokes
    tests/hil/run_smoke.py against tests/hil/aen701-evk/ after the
    existing ztest build, picking up the new specs automatically via
    the _common/ discovery flow.
  • tests/hil/README.md documents the block-coverage convention and
    the pending_hardware_support: flag.

Added — storage: block: deterministic flash-partition allocator + DTS/Kconfig emit (2026-05-20)

Closes the first v0.6 schema-only gap: storage: lands its real
emit pipeline so the schema-authoritative partitions become
build-system artefacts.

  • New resolver resolve_storage_partitions() in
    scripts/alp_orchestrate.py allocates physical offsets for every
    storage[] entry, name-sorted and page-aligned to 4 KiB within
    each flash_device:. Mirrors the IPC carve-out pattern: blocked
    entries (TBD capacity, unknown device, page-misaligned offset,
    sibling overlap) land in system-manifest.yaml with status: blocked + reason: so reviewers see the gap. Byte-stable
    allocation across rebuilds (per resolved design Q on storage
    address determinism — "pin in orchestrator").
  • Schema additive: new optional storage[].offset_kib: (integer,
    4 KiB-aligned) — explicit offset override for partitions that need
    to coexist with bootloader-managed slots or migrate a legacy
    layout. When supplied, the allocator does NOT shift its
    high-water mark, so the bump-allocated siblings stay byte-stable.
    Also new optional memory_region.dt_label: on the SoM preset
    schema for regions whose Zephyr DT label differs from the SDK
    name (defaults to the region name).
  • New emitters:
    • emit_dts_partitions(project)dts-partitions.dtsi (a
      DTS overlay decorating &<dt_label> with a partitions { compatible = "fixed-partitions"; ... } child node carrying
      label = "<name>"; and reg = <offset size>; per partition).
      Apps reach partitions via Zephyr's
      FIXED_PARTITION_ID(<name>_partition).
    • emit_storage_mounts_c(project) → optional static
      fs_mount_t alp_storage_mounts[] table for boot-time iteration.
      Per-fs declarations (FS_LITTLEFS_DECLARE_DEFAULT_CONFIG,
      FATFS, FS_EXT2) emitted under /* clang-format off */.
    • Per-fs Kconfig in _slice_alp_conf(): CONFIG_FILE_SYSTEM=y
      plus the matching CONFIG_FILE_SYSTEM_LITTLEFS=y /
      CONFIG_FAT_FILESYSTEM_ELM=y / CONFIG_FILE_SYSTEM_EXT2=y
      • per-littlefs partition CONFIG_FS_LITTLEFS_PARTITION_<NAME>=y.
  • CLI: python3 scripts/alp_orchestrate.py --emit dts-partitions | storage-mounts-c — two new emit modes for inspecting the
    resolved layout. Orchestrator._materialise_shared() now writes
    build/generated/dts-partitions.dtsi alongside the existing
    dts-reservations.dtsi.
  • Cross-field validation: load_board_yaml() rejects typoed
    flash_device: references at parse time with the list of known
    devices for the project's SoM (memory_map regions + ospi keys);
    duplicate partition names within storage: error eagerly.
  • System manifest: storage: round-trips through
    build/system-manifest.yaml carrying every resolved partition's
    offset_kib, size_kib, dt_label, mount, and (when blocked)
    reason:.
  • Tests: 14 new cases under
    tests/scripts/test_alp_orchestrate.py covering: happy-path
    allocation, unknown / duplicate / page-misaligned / overflow /
    overlap blocking, byte-stable determinism, explicit offset_kib:
    override, DTS shape, Kconfig fragment, C mount table, manifest
    round-trip, and AEN301 OSPI TBD-capacity blocking. Total
    orchestrator suite: 28 → 42 cases.
  • Docs: docs/board-config.md storage section rewritten with
    the full v0.6 contract (allocator semantics, emit artefacts,
    inspection commands). Tutorial 09 (board.yaml deep dive) gains
    a storage: block walkthrough with a worked allocation table.
    Template at metadata/templates/board.yaml carries an annotated
    storage example.

Added — security.psa: TF-M sysbuild integration + ADR 0013 (2026-05-20)

Pulls the security.psa: block from "schema authoritative, emit
no-op" to "real build-system artefacts". Scoped to the v0.6
TF-M cross-core trust-boundary land.

  • scripts/alp_orchestrate.py gains emit_tfm_sysbuild_conf(project).
    When security.psa.tfm: true it returns a SB_CONFIG_TFM=y +
    SB_CONFIG_TFM_BUILD_TYPE=<Release|Debug|MinSizeRel> +
    CONFIG_PSA_CRYPTO_PERSISTENT_SLOT_COUNT=<n> +
    CONFIG_PSA_CRYPTO_{ITS,PS}_BACKING_STORE="<name>" overlay.
    attestation_root: optiga_trust_m adds
    CONFIG_ALP_SDK_PSA_ATTESTATION_OPTIGA=y + a comment pointing at
    the PSA <-> OPTIGA bridge driver. Returns "" when the block is
    absent or tfm: false (PSA Crypto then runs non-secure-only).
  • Orchestrator._materialise_shared() writes the overlay to
    build/sysbuild/tfm/tfm.conf when non-empty; no directory is
    created otherwise.
  • New CLI mode: python3 scripts/alp_orchestrate.py --emit tfm-sysbuild-conf (alongside the existing
    system-manifest / ipc-contract-h / dts-reservations
    emitters).
  • load_board_yaml() gains three cross-field checks:
    security.psa.its_storage: and ps_storage: must resolve to a
    storage[].name OR a SoM memory_map: region name; and
    attestation_root: optiga_trust_m is rejected when the SoM
    preset doesn't ship OPTIGA Trust M (on-module or via
    capabilities:). Errors point at the offending YAML path.
  • New boot.build_type: enum (Release | Debug | MinSizeRel,
    default Release). Propagates to both the MCUboot and TF-M
    sysbuild child images so they ship the same flavour.
  • New ADR: docs/adr/0013-tfm-boundary-m55-hp-trustzone.md
    -- captures the locked-in cross-core trust-boundary decision
    (TrustZone-M on M55-HP, not a dedicated M55-HE). Refines ADR
    0010. M55-HE stays available for compute / inference offload.
  • Schema: security.psa: description refreshed (the "pre-v0.6 ...
    emit path is a no-op" note is gone); boot.build_type:
    documented. Template metadata/templates/board.yaml grows a
    commented security.psa: example.
  • Docs: docs/board-config.md §PSA Crypto + TF-M now describes
    the v0.6 emit contract; docs/security-audit-plan.md gains a
    short TF-M trust boundary section pointing at ADR 0013.
  • Tests: 11 new cases in
    tests/scripts/test_alp_orchestrate.py covering happy-path
    emit, schema field round-trip, ITS/PS backing-store reference
    validation (happy + rejection), attestation-root OPTIGA
    cross-check, absence-emits-nothing, materialise round-trip, and
    build-type inheritance. Full suite: 413 passed / 5 skipped (was
    402 / 5).

Added — extra_libraries: escape hatch for non-curated libraries (2026-05-20)

cores.<id>.extra_libraries: joins libraries: (the closed,
25-entry curated enum) with an open-set escape hatch for libraries
the SDK doesn't curate -- one-off vendor SDKs, research-only deps,
or libraries on their way into the curated set. Each entry MUST
declare exactly one of:

  • kconfig: -- inline Kconfig fragment lines emitted verbatim
    into the slice's alp.conf. Fast path for one-off entries.
  • profile: -- path to a hw-backends.yaml-style profile file.
    The loader walks the file with the same silicon / soc_family /
    requires_cap matcher used by curated libraries; first match per
    class: wins, sw_fallback: always emits.

The "exactly-one" rule is enforced by the new
_validate_consistency() pass (see below), along with global
uniqueness of name: across cores and a check that
profile: paths resolve to a real file. Names that collide with
the curated libraries: enum are rejected -- the curated path is
the right way to wire curated libraries.

Schema lives in metadata/schemas/board.schema.json under
cores.<id>.extra_libraries; reference doc at
docs/board-config.md § extra_libraries: and tutorial coverage
at docs/tutorials/09-board-yaml-deep-dive.md § extra_libraries.

Added — cross-field validator pass with 5 rules (2026-05-20)

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

  1. (ERROR) ota.provider: mender requires at least one
    cores.<id>.os: yocto slice. Mender server-mode is a
    Yocto-only flow today; the Zephyr-side dispatch
    (Mender-MCU-client) lands separately per ADR 0009.
  2. (ERROR) boot.signing.algorithm: must be supported by the SoM
    family. Per-family allow-lists: Alif Ensemble (with OPTIGA
    Trust M attestation root) ecdsa_p256 / ed25519; Renesas
    RZ/V2N + NXP i.MX 9 ecdsa_p256 / rsa2048 / rsa3072.
    Unknown families fall through (no capability-block enforcement).
  3. (ERROR) cores.<id>.iot.tls: true requires mbedtls or
    bearssl in libraries: (curated) or extra_libraries:
    (open-set).
  4. (WARN) cores.<id>.inference.default_arena_kib > cores.<id>.memory.heap_kib -- inference may OOM. Build
    continues; stderr WARN: ... line emitted.
  5. (WARN) cores.<id>.power.sleep_mode != disabled with no
    wakeup_sources: declared -- device will sleep but cannot
    wake.

Reference doc: docs/board-config.md § Cross-field validation.
Two in-repo examples (iot-fleet-ota, iot-dashboard,
production-deployment) updated to satisfy rule 3 by declaring
mbedtls in libraries:.

Added — examples adopting v0.6 declarative blocks (iot-fleet-ota promotion + production-deployment + power-managed-sensor) (2026-05-20)

  • examples/iot-fleet-ota/board.yaml promoted from prose-only OTA
    narration to declarative boot: + ota: blocks (MCUboot +
    ECDSA-P256 + Mender HTTPS-poll with ${MENDER_TENANT_TOKEN}
    placeholder). README rewritten with the "What lands
    declaratively in v0.6" walkthrough.
  • examples/production-deployment/board.yaml refactored into the
    SDK's declarative-stack flagship: every v0.6 block applied at
    production stance (boot:/ota:/security.psa:/storage:/
    cores.<id>.memory:/cores.<id>.power:/diagnostics.modules:)
    on AEN701 with TF-M + OPTIGA-rooted attestation. README gains
    a full per-block walkthrough.
  • examples/power-managed-sensor/ (new): reference for the v0.6
    cores.<id>.power: block. AEN301 + M55-HE deep sleep with
    wakeup_sources: [uart, gpio_int, rtc] covering the periodic
    sample / motion-event / console-debug duty cycle. Memory tuned
    for the short-lived per-wake task (4 KiB stack, 16 KiB heap).

Changed — board.yaml flatten + carrier→board rename + 7 declarative blocks (2026-05-20)

Breaking schema changes (no migration script — every in-repo
board.yaml rewritten in the same change).

  • board.yaml schema flattened: dropped the carrier: wrapper.
    name, description, hw_rev, populated, e1m_routes,
    pins, preset are now top-level fields. Two modes: inline
    (customer path) or preset: (SDK-internal shortcut, used by
    the ~40 EVK demos under examples/).
  • schema_version field removed entirely. One live schema at
    metadata/schemas/board.schema.json.
  • "Carrier" noun retired in favour of "board" everywhere: file
    paths (metadata/carriers/metadata/boards/,
    scripts/gen_carrier_header.pyscripts/gen_board_header.py,
    metadata/schemas/board-config-v2.schema.json
    metadata/schemas/board.schema.json, new
    metadata/schemas/board-preset.schema.json for the shared
    YAMLs); SoM presets' default_carrier:default_board:; C
    API alp_hw_info_t.carrier_*board_* fields and
    ALP_HW_BUILD_CARRIER_*ALP_HW_BUILD_BOARD_* macros.

Schema additions (additive; existing board.yaml files validate):

  • e1m_routes: grows 5 sections beyond the original
    gpio/buses/pwm: adc, dac, i2s, can, qenc (E1M_ENC
    pads). Per-section pad-class validation: misclassified pads
    (e.g. E1M_I2C0 under adc:) error at validate time.
  • e1m_routes: entry shape gains optional pull: (up|down|none)
    and debounce_ms: (board-static electrical facts).
    routes_via: removed (SoM concern; moved to SoM preset's
    pad_routes.dispatch:).
  • pins: entries can be bare strings OR {e1m, macro?, doc?}
    rich form. Validator cross-checks the macro against the
    resolved board.
  • cores.<id>.memory: { stack_kib, heap_kib, isr_stack_kib }
    → emits CONFIG_MAIN_STACK_SIZE / CONFIG_HEAP_MEM_POOL_SIZE
    / CONFIG_ISR_STACK_SIZE.
  • cores.<id>.power: { sleep_mode, wakeup_sources } → emits
    CONFIG_PM + CONFIG_PM_DEVICE_WAKE_<SUBSYS>.
  • diagnostics.modules: { <name>: <level> } → per-module
    CONFIG_<MODULE>_LOG_LEVEL=<n> (supports off).
  • New top-level boot: block (MCUboot configuration). Loader's
    new emit_sysbuild_conf() produces a SB_CONFIG_* overlay
    consumed via west build --sysbuild-config build/alp_sysbuild.conf.
  • New top-level ota: block (Mender / MCUmgr). Loader appends
    MENDER_* weak-assignments (?=) + INHERIT += "mender-full"
    to the Yocto slice's local.conf.
  • New top-level storage: block (filesystem partitions).
    Schema authoritative; DTS-overlay emit lands in v0.6 alongside
    the deterministic flash-allocator.
  • New top-level security.psa: block (TF-M + PSA Crypto).
    Schema authoritative; TF-M sysbuild child-image plumbing lands
    in v0.6.

EVK preset seeded: metadata/boards/e1m-evk.yaml now declares
the full canonical EVK wiring across all 8 sections — ADC0..ADC7
(board-id, Arduino A1..A5, mikroBUS AN, VBAT sense), DAC0/DAC1,
I2S0/I2S1, CAN0, ENC0 (PEC12R rotary). Generated routes header
grows 79 → 125 lines.

Migration delta: 41 example board.yaml files migrated to the
flat form with annotated pins: arrays where applicable; 9
test files in tests/scripts/ updated; ~200 source files swept
for prose-level carrier→board rename. Full docs sweep across
README, architecture, board-config, getting-started,
firmware-quickstart, heterogeneous-builds, portability,
porting-new-som, secure-boot, ota, and tutorials 09/10/11/12/16.
ABI snapshot at docs/abi/v0.5-snapshot.json regenerated to
match the renamed alp_hw_info_t fields. All tests green:
402 passed / 5 skipped.

Added — Intra-family portability proof + Phase B/C audit cleanups (2026-05-18)

Portability is now empirically proven. Phase A swap-test
matrix: 21/21 E1M cells green (i2c-scanner / gpio-button-led /
pwm-led-fade across all 7 E1M SKUs) + 12/12 E1M-X cells green
(adc-voltmeter / pwm-led-fade / v2n-pwm-fan-control across all 4
E1M-X SKUs). Within the AEN sub-family, generated alp.conf is
byte-identical across all 6 SKUs after stripping the SoC-enable
line. Customer-facing matrix at
docs/portability-matrix.md (new).

Phase B gap fixes (all 5 surfaced gaps resolved):

  • A2-1 metadata/e1m_modules/E1M-V2M102.yaml pad_routes: use
    E1M_X_* namespace (was E1M_* — E1M form factor leaked into
    an E1M-X SoM). Apps consuming <alp/e1m_x_pinout.h> now resolve
    on V2M102 like the other 3 E1M-X SoMs.
  • A2-2 V2M101 + V2M102 gain the 8 E1M_X_GPIO_IO27..IO35
    extension routes V2N already had (maintainer confirmed GD32
    routing is identical on V2M).
  • G-1 Per-variant Ethos-U Kconfig: orchestrator walks SoM
    inference.npu_population[] and emits
    CONFIG_ALP_SDK_INFERENCE_ETHOS_U_U55=y / _U65=y / _U85=y
    per variant present. AEN401/601/801 now emit BOTH _U55 and
    _U85; previously the U85's TensorOptimized kernels were
    invisible to the build. Driver dispatch in
    src/zephyr/inference_tflm.cpp selects per-variant kernel pack
    at compile time; alp_inference_tflm_npu_variant_name() exposes
    the active variant for HiL operators.
  • G-2 Per-CPU-class TFLM kernel selector: orchestrator emits
    CONFIG_ALP_SDK_INFERENCE_TFLM_NEON=y (A-cluster) /
    _HELIUM=y (M55) / _REF=y (baseline Cortex-M) per slice
    from SoC-JSON cores[<core_id>].vector_extension.
  • G-4 cores.<key> rename diagnostic: hard-fails with "did
    you mean one of: [...]" hint when no cores: key intersects
    the preset's topology: keys. Soft-warns per dropped key on
    partial matches.

Phase C cleanups (bundled — audit-deferred items):

  • C.1 button_led + pdm_mic relocated from chips/ to
    blocks/ (alp_*-prefixed helpers are SDK abstractions, not
    chip drivers, per [[chip-driver-naming]]). History-preserving
    git mv. Kconfig CONFIG_ALP_SDK_CHIP_BUTTON_LED
    _BLOCK_BUTTON_LED + _BLOCK_PDM_MIC; orchestrator dispatches
    per slug. Updated 25+ consumers; new blocks/README.md.
  • C.2 Four Yocto NOSUPPORT stubs added
    (src/yocto/peripheral_{can,i2s,rtc,wdt}.c) — Yocto link line
    now provides every <alp/*> symbol the public headers declare.
  • C.3 _Static_assert ALP↔GD32 PWM align wire-encoding parity
    in src/zephyr/peripheral_pwm.c; cast now fails to compile if
    either enum is reordered.
  • C.4 Comment density boost on examples/rpmsg-imx93/m33/src/main.c
    (21 % → 67 %) and examples/drone-autopilot/src/main.c
    (30 % → 60 %); both clear the ~50 % examples-are-documentation target.
  • C.5 [PAPER-CORRECT-STUB] @par Verification status blocks
    added to act8760.h / da9292.h / ov5640.h public headers so
    API consumers see which surfaces return ALP_ERR_NOSUPPORT.
  • C.6 metadata/socs/nxp/imx9/imx93.json flagged with
    pending_reference_manual_ingestion: true (no invented peripheral
    counts); schema + validate_metadata.py extended to accept + WARN
    on the new field.

Verification: pytest tests/scripts/ 367 passed (357 baseline + 10
new G-1/G-2/G-4 tests, 0 regressions); validate_metadata.py 0
failures; check_example_portability.py 47/0.

Added — Phase D documentation push (2026-05-18)

Six-pack of documentation work anchoring the just-proven intra-
family portability promise:

  • D.1 docs/portability.md (NEW, 814 lines) — customer-facing
    portability cookbook with 6 sections: swap-and-run scope, the
    swap-test recipe (two worked examples), dual-namespace decision
    tree, intentional gaps (NPU model artefacts, form-factor symbol
    errors, heterogeneous-OS topology, carrier-specific HW),
    capability validation via alp_last_error() + ALP_ERR_NOSUPPORT
    • the runtime fallback ladder, link to the empirical matrix.
  • D.2 docs/porting-new-som.md rewritten as a 30-minute
    walkthrough of adding a hypothetical 7th AEN SKU (E1M-AEN901).
  • D.3 docs/architecture.md repository-layout tree refreshed
    to the post-slice-3a/3b state; four new sub-sections under
    "Build orchestration" (per-core slice fan-out, sparse capabilities
    flow, on_module: auto-enable, generators inventory).
  • D.4 docs/v1.0-readiness.md gains Pillar 3f "Intra-family
    portability proven" with checkboxes for the matrix, cookbook,
    ADR 0011, and the 5 Phase B gap closures.
  • D.5 All 16 docs/tutorials/*.md cross-checked. Tutorial-04
    rewritten around the intra-family promise (old three-rings
    cross-family model superseded). Tutorial-09 + tutorial-16
    light-edited to fix board.yaml + inference API drift + add G-1/G-2
    variant-selector pointers. Other 13 got a "Last verified:
    2026-05-18" header line.
  • D.6 docs/adr/0011-intra-family-portability.md (NEW, 191
    lines) — Architecture Decision Record ratifying the load-bearing
    intra-family portability scope.

Plus docs/README.md (NEW) — top-level doc navigation hub linking
all the new and existing docs into topic groupings.

Added — Cross-platform developer host first-class (2026-05-18)

Codify cross-platform Win + Mac + Linux developer support as a
load-bearing SDK principle so customers never feel "I need Linux
to use the alp-sdk." Yocto remains Linux-only by upstream
constraint; the Zephyr-on-M-class workflow is first-class on every
host.

  • docs/adr/0012-cross-platform-developer-host.md (NEW) — ADR
    codifying the principle, alternatives rejected (Linux-first via
    WSL2, Linux-only, per-OS forks), consequences (CI cost rises
    but adoption uplift justifies it).
  • docs/cross-platform-setup.md (NEW, 720 lines) — 8-section
    per-OS quickstart (Linux Debian/Ubuntu/Fedora, macOS Homebrew
    • Apple Silicon, Windows native PowerShell + winget + Arm GNU
      MSI + setx, Windows + WSL2 for Yocto), verification walkthrough,
      known gotchas (MAX_PATH, AV, CRLF, Gatekeeper, symlinks, serial
      device naming), what's-Linux-only-and-why scoping.
  • scripts/check_cross_platform.py (NEW, 618 lines) — soft-warn
    lint for Linux-only idioms (hard-coded /dev/ paths,
    ~/.bashrc mentions, bash-only shebangs, make in
    tutorial-grade docs, forward-slash absolute paths). Two
    suppression mechanisms: INTENTIONALLY_DISCUSSES_OS_PATHS
    allowlist (collapses N findings to one summary line) and inline
    <!-- cross-platform-lint:ignore --> skip markers. CLI flags
    --fail-on-warning, --quiet, --json. 37 pytest cases at
    tests/scripts/test_check_cross_platform.py (NEW).
  • .github/workflows/cross-platform-zephyr.yml (NEW) — CI matrix
    scaffolding. Ubuntu strict; macOS + Windows
    continue-on-error: true per ADR 0012 (surface drift, don't
    block while runners prove out).
  • Lint cleanup of 28 → 0 findings on the existing tree (placeholder
    serial device paths in example READMEs; <your-shell-rc>
    placeholder in docs/local-ci.md; cross-platform header notes
    on scripts/bootstrap.sh + scripts/test-all.sh).

Added — 8 vendor-SDK-style peripheral tutorial examples (2026-05-18)

Vendor-SDK-style canonical "first program" + per-peripheral
tutorial set. Each example follows the standard six-file layout
(board.yaml, prj.conf, CMakeLists.txt, README.md, src/main.c,
testcase.yaml) plus native_sim overlays where needed; comment
density on main.c targets ~50 % per [[examples-are-documentation]].

  • examples/hello-world — zero-peripheral printf heartbeat via
    alp_log_*. The canonical first build (74 % comment density).
  • examples/uart-hello-world — producer-only "printf via UART"
    walkthrough (companion to bidirectional uart-echo).
  • examples/i2c-master — read TMP112 temperature sensor; contrasts
    with i2c-scanner (discovery vs known-address read).
  • examples/i2c-slave — slave-mode SHAPE + explicit SDK gap notice
    (<alp/peripheral.h> is master-only today; local NOSUPPORT shim
    templates the proposed alp_i2c_slave_* surface for v0.7).
  • examples/spi-master — discrete master demonstrating write /
    transceive / read patterns.
  • examples/spi-slave — slave-mode SHAPE + SDK gap notice (same
    class as i2c-slave).
  • examples/dac-waveform — 100 Hz sine on E1M_X_DAC0 via the
    V2N GD32 supervisor bridge (E1M-X targeted).
  • examples/timer-periodic-interrupt — re-arming periodic alarm
    via alp_counter_* + ISR-safe coordination pattern.

examples/README.md index updated. check_example_portability.py:
55 examples (was 47), 0 errors.

Schema tightening — silicon-determined fields removed from board.yaml (2026-05-16)

Customer-facing knobs in board.yaml v2 are now restricted to
project-level choices. Anything dictated by the SoM SKU's silicon
(NPU presence, on-module component populations, memory capacities,
per-core natural runtime) is read from the SoM preset under
metadata/e1m_modules/<MPN>.yaml and is no longer overridable at
the project level.

Schema changes (breaking — board.yaml v2):

  • cores.<id>.inference.backend removed. The build now compiles
    in every dispatcher the SoM preset's capabilities: block
    declares (TFLM CPU always, plus Ethos-U / DRP-AI3 / DEEPX DX-M1
    per the SoM caps). Apps pick which to use per-handle at runtime
    via alp_inference_open(.backend = …). This unblocks concurrent
    multi-NPU dispatch on V2M101 (DRP-AI3 + DEEPX DX-M1 running
    independent models simultaneously) which the old "pick one
    backend" wiring silently broke.
  • cores.<id>.os is now optional. Every core has a natural
    runtime baked into the SoM preset's topology: block (Cortex-M
    → Zephyr, Cortex-A → Yocto Linux); customers write os: only to
    override (os: off to skip a core, os: baremetal for hand-
    written firmware on a core that normally runs Zephyr).
  • som.overrides removed. For a custom SoM variant (e.g. an
    AEN without the OPTIGA Trust M), create a new MPN preset under
    metadata/e1m_modules/ rather than overriding here.
  • som.memory removed. Same reasoning — memory capacities are
    silicon-determined. Custom memory variants get their own MPN
    preset.

Loader changes:

  • scripts/alp_orchestrate.py:_slice_alp_conf now emits
    CONFIG_ALP_SDK_INFERENCE_* from the SoM capabilities: matrix
    (was: emitted nothing — examples carried hand-written
    CONFIG_ALP_SDK_INFERENCE_TFLM=y in prj.conf).
  • scripts/alp_orchestrate.py:_slice_cmake_args now emits
    -DALP_SDK_USE_DRPAI=ON / -DALP_SDK_USE_DEEPX_DXM1=ON per
    the SoM caps; V2M101 baremetal/Yocto builds get both.

Migration:

 cores:
   m55_hp:
-    os: zephyr                          # delete -- topology default
     app: ./src
     inference:
-      backend: ethos_u                  # delete -- silicon-determined
       default_arena_kib: 256
 som:
   sku: E1M-AEN701
-  overrides:                            # delete -- create new MPN preset instead
-    secure_element: none
-  memory:                               # delete -- create new MPN preset instead
-    flash_mbit: 131072

Tests:

  • tests/scripts/test_alp_project.py adds TestInferenceFromSomCaps
    covering: SoM-caps-driven CONFIG emission per family, schema-level
    rejection of inference.backend, V2M101 cmake-args emitting both
    DRPAI + DEEPX_DXM1 flags.

Principle: silicon-determined facts live in
metadata/e1m_modules/<MPN>.yaml; board.yaml carries only
project-level choices. See
docs/board-config.md "Silicon-determined
fields never appear in board.yaml".

(Tracks metadata/sdk_version.yaml's declared version. Per
VERSIONS.md, every point release ships the surface first
and accumulates runtime implementations in subsequent point
releases -- entries below collect every Added / Changed item
that lands before the v0.6.0 tag.)

Verification status. Entries below describe what's been
coded and merged on main; passing pr-plain-cmake /
pr-twister / pr-static-analysis / pr-alp-build is
necessary but not sufficient to call an item "GA". Real-hardware
verification is tracked separately in
docs/test-plan.md -- a release does not
tag until every row gating it flips to ✅.

v0.6.0 — Heterogeneous OS orchestration (2026-05-15)

The v0.6 redesign drops the framing that os: is a single
SoM-wide choice and turns Zephyr / Yocto / bare-metal into
peers that coexist on the same SoM, declared per-core from one
board.yaml. Full design at
docs/superpowers/specs/2026-05-15-heterogeneous-os-orchestration-design.md;
decision recorded in
docs/adr/0010-heterogeneous-os-orchestration.md.

[UNTESTED] across the board -- AEN + iMX93 hardware-fact
fields carry TBD strings pending the maintainer's hand-written
HW config per the project's "never invent values" convention.

Added

  • Heterogeneous OS orchestration. Per-core cores: block in
    board.yaml v2 lets one project run Yocto on a Cortex-A cluster,
    Zephyr on a Cortex-M peer, and bare-metal on a third core -- all
    from one declarative config. Each on-die programmable core takes
    its own os: / app: / peripherals: / libraries: /
    inference: / iot: block.
  • <alp/rpc.h> framed RPC layer on top of OpenAMP / RPMsg.
    Zephyr backend (full) + Linux backend (full per Wave 5A); apps
    never type endpoint IDs or carve-out addresses by hand.
  • Five west extension commands replacing the single
    west alp-build from v0.5:
    • west alp-build -- fan board.yaml out into per-core slices,
      emit system-manifest.yaml.
    • west alp-image -- consume the manifest, assemble a flashable
      bundle (build/image-bundle/).
    • west alp-flash -- walk the manifest's boot_order: and
      program each piece with the right backend tool.
    • west alp-clean -- tear down per-slice build dirs idempotently.
    • west alp-renode -- boot the dual-OS image in Renode and
      drive the RPMsg handshake smoke test.
  • Generated artefacts emitted by scripts/alp_orchestrate.py:
    • build/system-manifest.yaml -- per-slice status + log paths +
      artefact paths + boot order, byte-stable across rebuilds.
    • build/generated/dts-reservations.dtsi -- shared-memory
      carve-outs as Linux + Zephyr DT reservations.
    • build/generated/alp/system_ipc.h -- name + address + endpoint
      ID + mailbox channel macros that both halves #include via
      <alp/system_ipc.h>. Carries an #error directive for any
      blocked channel so the slice build trips when the SoM metadata
      isn't ready yet (see "Blocked carve-outs" below).
  • 3 flagship heterogeneous examples plus an offload demo:
    • examples/rpmsg-v2n/ -- V2N101 A55 (Yocto) ↔ M33-SM (Zephyr)
      over RPMsg.
    • examples/rpmsg-aen/ -- AEN701 A32 (Yocto) ↔ M55-HP (Zephyr).
    • examples/rpmsg-imx93/ -- iMX93 A55 (Yocto) ↔ M33 (Zephyr).
    • examples/heterogeneous-offload/ -- A-cluster delegates an FFT
      computation to the M peer over RPMsg.
  • 14 Zephyr DT overlays covering every SoM × M-class core combo.
  • Yocto recipes under meta-alp-sdk/recipes-alp/:
    • alp-remoteproc_0.6.bb -- userspace remoteproc helper.
    • alp-dts-reservations_0.6.bb -- generated reserved-memory:
      fragment shipped into the kernel DT.
    • alp-chips_0.6.bb -- absorbed from the deleted
      yocto/meta-alp/ layer.
    • alp-edgeai_0.6.bb -- path-rebased to track the renamed
      examples/aen/edgeai-vision-aen/ source tree.
  • Per-SoM topology:, memory_map:, mailbox:,
    helper_firmware: blocks
    in metadata/e1m_modules/E1M-*.yaml
    drive the orchestrator. The topology key set mirrors the SoC's
    cores[].id set exactly (cross-checked by
    pr-metadata-validate.yml).
  • ADR 0010 + docs/heterogeneous-builds.md
    walkthrough + glossary terms (system manifest, slice, carve-out,
    helper-MCU firmware, topology key set).
  • 3 new CI workflows:
    • .github/workflows/pr-alp-build.yml -- orchestrator gate
      across SoM × scenario matrix, asserts the system-manifest is
      byte-stable across rebuilds.
    • .github/workflows/pr-bitbake.yml -- self-hosted runner gate
      that builds alp-image-edge per A-cluster MACHINE.
    • .github/workflows/pr-renode-dual-os.yml -- Renode dual-OS
      smoke test (advisory until the self-hosted Renode runner
      ships in v0.6.1).
  • Real flash backends under scripts/flash_backends/ (Wave 5B):
    vendor flasher for the SoC, openocd-via-SWD for the GD32 helper,
    USB-CDC bootloader for CC3501E. west alp-flash walks the
    manifest and dispatches to the right backend per artefact -- no
    developer-side tool-selection.

Changed

  • board.yaml schema bumped to v2 -- top-level os: field
    removed; peripherals: / libraries: / inference: / iot:
    moved per-core under cores.<id>. carrier.populated: +
    chips: and diagnostics: stay top-level (they describe
    physical assembly + project-wide diagnostics). No migration
    script -- no shipping customers yet; every in-repo board.yaml
    is rewritten in the same redesign PR (32 files).
  • examples/mproc-dual-os-yocto-zephyr/
    examples/rpmsg-v2n/
    (renamed + sub-tree restructured;
    m33/m33_sm/ to match the SoM topology key).
  • metadata/e1m_modules/E1M-*.yaml gained topology: +
    memory_map: + mailbox: + helper_firmware: blocks. V2N
    preset is fully authored from the maintainer-confirmed pinmap;
    AEN + iMX93 carry TBD strings on hardware-fact fields
    (memory_map.base, mailbox.controller, AEN cc3501e_otp
    firmware path).
  • metadata/socs/.../*.json cores[] -- every entry now
    carries an id: field used as the topology key.
  • docs/os-support-matrix.md restructured from per-(SoM, OS)
    columns to per-(SoM, core, OS) columns; split into Cortex-A and
    Cortex-M tables.
  • README "30-second quick start" rewritten with a v2 per-core
    example; "Status" section updated to v0.6 ramp.
  • meta-alp-sdk/conf/machine/ MACHINEs renamed from
    e1m-v2n101.conf to e1m-v2n101-a55.conf (per-cluster
    naming -- the M33-SM is no longer implied to be part of the same
    MACHINE). pr-bitbake.yml matrix updated accordingly.
  • AEN + iMX93 SoM presets carry TBD strings on hardware-fact
    fields pending the maintainer's hand-written HW config. Per the
    project's "never invent values" convention these stay TBD
    rather than guessed defaults; the AEN audit at
    docs/aen-feature-audit-2026-05.md and the iMX93 reference
    manual sections to cross-check are linked from each preset.

Removed

  • metadata/schemas/board-config-v1.schema.json -- v2 fully
    replaces; no dual-schema fallback in the loader.
  • yocto/meta-alp/ (entire tree) -- content absorbed into
    meta-alp-sdk/; the duplicate layer is deleted. Customers
    consuming the old layer name should re-point at meta-alp-sdk.
  • scripts/west_commands/alp.py -- split into 5 focused
    command modules (alp_build.py, alp_image.py, alp_flash.py,
    alp_clean.py, alp_renode.py) under the same directory. The
    west alp-build entry point keeps its CLI surface; the v0.5
    monolithic file is gone.

Fixed

  • The 13-line workaround comment at the top of the old
    examples/mproc-dual-os-yocto-zephyr/board.yaml apologising
    that the two halves could not share one declaration -- removed.
    Both halves now drive from one v2 board.yaml, the example
    shape the schema expects to be used.
  • resolve_carve_outs() emits status: blocked instead of
    raising on TBD SoM metadata
    (mailbox controller, memory_map
    base / size). The manifest stays emit-able when the preset
    isn't fully HW-mapped yet — CI's manifest-shape + determinism
    gates pass while the actual slice build is what trips (via the
    generated <alp/system_ipc.h> #error directive). Required
    for the AEN + iMX93 examples to flow through pr-alp-build while
    the preset's hardware-fact fields are still TBD.
  • system-manifest.yaml is byte-stable across rebuilds.
    Dropped the wall-clock duration_s field from
    Slice.to_manifest_entry() — the metric stays on the runtime
    Slice dataclass for west alp-build logging but never lands in
    the declarative manifest. Satisfies spec §6.1's determinism
    clause: west alp-clean && rebuild && diff is now a no-op.
    Enforced by pr-alp-build.yml's "Determinism check" step.
  • <alp/system_ipc.h> path canonical at generated/alp/
    (was generated/alp_system_ipc.h) — matches the
    #include <alp/system_ipc.h> consumer pattern documented in
    include/alp/rpc.h. Slice CMakeLists drop
    ${CMAKE_BINARY_DIR}/generated onto zephyr_include_directories
    and the include resolves with no munging.
  • v2 zephyr-conf emit now carries chip drivers + subsystem deps.
    _slice_alp_conf was only emitting baseline + silicon +
    per-core peripherals + libraries; it dropped the carrier.populated:
    chip-driver block that the v1 emit had under _emit_zephyr.
    Apps that depend on a populated chip (e.g. iot-connected-camera's
    ssd1306 + button_led) lost CONFIG_ALP_SDK_CHIP_* under v2 so
    twister's native_sim link hit undefined reference to ssd1306_init.
    v2 path now merges the SoM/carrier preset's populated: block with
    the board.yaml override and emits both the chip-driver Kconfigs and
    the matching subsystem enables (CONFIG_GPIO=y / CONFIG_I2C=y /
    ...) so GPIO_EMUL / I2C_EMUL deps are satisfied.
  • examples/rpmsg-v2n/m33_sm/testcase.yaml (moved from project
    root). The old top-level location made twister try to configure
    the multi-slice project root as a Zephyr app — there's no
    CMakeLists.txt finding Zephyr at that level, so configure
    crashed and aborted the whole run with
    FileNotFoundError: zephyr/.config. Move puts the testcase next
    to its real CMakeLists.txt + adds extra_args: CONFIG_ALP_SDK_RPC=y so the producer's <alp/rpc.h> symbols link.
  • Pre-existing chip header gaps surfaced once twister stopped
    aborting mid-run.
    a4988.h / drv8825.h / drv8833.h now
    #include "alp/pwm.h" (they declare alp_pwm_t fields but only
    pulled in peripheral.h). tests/zephyr/chips/prj.conf now
    enables every chip the suite calls *_init() on (the v0.5
    §D.AI / §D.industrial / §D.iot / §D.audio batches were on by-include
    but off by-Kconfig, so 80+ chip _init calls hit
    undefined reference).
  • tests/zephyr/library_knobs/src/main.c stopped using
    defined(CONFIG_##x) as a runtime expression (the C preprocessor
    operator only works in #if directives — Zephyr provides
    IS_ENABLED() for the C-expression form). Dropped the
    CONFIG_FILE_SYSTEM_LITTLEFS=y pin and the mbedtls header
    include — Zephyr v4.4's subsys/fs/littlefs_fs.c + ssl_misc.h
    trip -Werror on this profile and the knob smoke doesn't need them.

Changed (CI)

  • pr-twister.yml drops the ghcr.io/zephyrproject-rtos/ci:v0.29.1
    Docker container
    (~17 GB on disk; the public ubuntu-latest
    runners only ship ~14 GB free, so the image pull intermittently
    failed with no space left on device). Replaced with a native
    ubuntu-latest job that pip-installs west + the Zephyr Python
    requirements and uses ZEPHYR_TOOLCHAIN_VARIANT=host so
    native_sim/native/64 builds use the runner's stock gcc. Saves
    ~5 minutes per run; eliminates the disk-pressure failure mode.

References

Decided (hardware-design decisions captured 2026-05-12)

  • All E1M PWM channels are GD32-driven; Renesas drives no PWMs.
    The "either GD32 or Renesas, picked SoM-wide via resistor strap"
    cross-source design is collapsed to a single GD32-only path.
    Removed GPT13_GTIOC13A (P64) / GPT13_GTIOC13B (P65) /
    GPT4_GTIOC4B (P75) rows from
    metadata/e1m_modules/v2n/renesas-peripheral-map.{tsv,csv}.
    GD32-side mapping unchanged: E1M PWM0..PWM7 on GD32
    PA11 / PB1 / PB14 / PC5 / PC10 / PC11 / PC12 / PD0. The
    resistor-strap selection is no longer applicable -- there's
    nothing to switch between.
  • GD32 in-field reflash uses SWD-from-host, not factory-ISP / BOOT0.
    The earlier "BOOT0 -> Renesas P75" plan is dropped in favour of a
    software SWD bit-bang from the Renesas host -- SWD works regardless
    of GD32 firmware state, where factory-ISP depends on the GD32 boot
    ROM staying intact and would have required a third pad (a USART
    line) on the V2N side. Net pad assignments (maintainer-confirmed):
    • GD32_SWDIO -> Renesas P70 (was GPT0_GTIOC0A).
    • GD32_SWCLK -> Renesas P71 (was GPT0_GTIOC0B).
    • P75 becomes unassigned on the Renesas side (BOOT0 dropped,
      PWM5 moved entirely to GD32).
      See [memory project_gd32_boot0_to_v2n_planned.md] for the design
      rationale.
  • GD32_NRST -> Renesas P74 (was E1M PWM4 / GPT4_GTIOC4A). PWM4
    becomes GD32-only. Line is shared with the primary PMIC reset
    out
    -- host pad MUST be configured open-drain (drive low to
    assert reset; HiZ to release). An external pull-up returns the
    line to its released state.
  • DEEPX M1_RESET polarity -> active-LOW. The
    chips/deepx_dxm1/ driver default flipped from ACTIVE_HIGH
    (placeholder pending the schematic check) to ACTIVE_LOW
    (confirmed). deepx_dxm1_set_reset_polarity() still lets
    carrier code override.
  • Murata BT_DEV_WAKE intentionally not routed on V2N. The
    chips/murata_lbee5hy2fy/ driver already supported the NULL-
    pin-handle case; metadata + header doc updated to make the
    "not-routed" status explicit.
  • 5L35023B I2C address -> 7-bit 0x68 (8-bit write 0xD0) per
    the Renesas 5L35023 public datasheet.

Fixed (2026-05-14 -- DA9292 OTP-variant bring-up correction)

  • da9292_v2n_m1_enable_deepx_rail no longer over-volts DEEPX
    to 1.50 V.
    The DA9292-AROVx OTP variant on V2N boots with
    CH2_VSTEP=1 (PMC_CTRL_01 = 0x80, doubled-range encoding).
    The previous bring-up wrote byte 0x96 to VOUT_CH2_VSEL_LO
    expecting it to decode as 0.75 V (the VSTEP=0 mapping), but
    silicon interpreted it as 2 x 750 = 1500 mV. Fix: clear
    CH2_VSTEP + CH2_EN together in a single PMC_CTRL_01
    write before programming the voltage byte. Datasheet
    constraint: CHx_VSTEP is only writable while
    CHx_EN=0, so the EN clear must precede. See
    chips/da9292/da9292.c::da9292_v2n_m1_enable_deepx_rail and
    the @warning block in include/alp/chips/da9292.h.
  • DA9292_I2C_ADDR_V2N corrected from 0x1Cu to 0x1Eu.
    The macro had carried the wrong 7-bit address since the driver
    landed; the AROVx OTP variant's PMC_CFG_0A defaults to 8-bit
    0x3C -> 7-bit 0x1E. Affects nobody at runtime today (the
    V2N firmware module that calls da9292_init hasn't landed yet)
    but the macro is exposed in include/alp/chips/da9292.h so it
    needed correcting before downstream consumers caught the bad
    value. docs/abi/v0.5-snapshot.json regenerated.

Changed (2026-05-14 -- Zephyr v3.7.0 LTS → v4.4.0 stable bump)

The SDK's west.yml Zephyr pin moves from v3.7.0 LTS to v4.4.0
stable. Mainline feature access (LVGL v9, upstream Alif Ensemble
board files, the I2S _CONTROLLER naming, mbedtls 3.6) at the
cost of LTS support window. Customers shipping product with a
24-month support requirement should re-pin to v3.7.x in their own
manifest -- the SDK's <alp/*> surface stays binary-compatible.

Knock-on changes:

  • .github/workflows/pr-twister.yml + nightly-aen-hil.yml --
    --mr v3.7.0--mr v4.4.0; cache key bumped.
  • All examples/*/testcase.yaml + README.md entries that named
    the v3.x board alif_e7_dk_rtss_hp / _rtss_he retargeted to
    the v4.4 upstream Alif board ensemble_e8_dk/ae402fa0e5597le0/rtss_hp
    (closest available -- AEN-specific boards land later in
    Alif's own zephyr_alif fork).
  • LVGL Kconfig knob renames (LVGL v8 → v9):
    • CONFIG_LV_DISP_DEF_REFR_PERIODCONFIG_LV_DEF_REFR_PERIOD.
    • CONFIG_LV_INDEV_DEF_READ_PERIOD -- removed in v9; runtime
      lv_indev_set_read_timer_period() instead.
  • LVGL demo source sets (lv_demo_widgets.c, lv_demo_benchmark.c,
    lv_demo_music*.c) are no longer compiled by the Zephyr lvgl
    module's CMakeLists; demo examples add them to their own
    target_sources() explicitly.
  • src/zephyr/peripheral_i2s.c -- I2S_OPT_FRAME_CLK_MASTER /
    I2S_OPT_BIT_CLK_MASTER are deprecated in v4.4; renamed to
    _CONTROLLER per Zephyr's inclusive-language pass.
  • docs/zephyr-version-policy.md, getting-started.md,
    troubleshooting.md, glossary.md updated.
  • New docs/local-ci.md -- Windows-native + WSL2 paths for
    running twister locally so contributors don't bounce off CI for
    every iteration.

Added (2026-05-14 -- 7 demo source-level fixes + 6 new flagship demos)

Real bugs the v4.4 bump surfaced + new demo apps for the website
gallery. All marked [UNTESTED] -- they build on native_sim
but haven't been HIL-validated.

Source fixes (real API drift in v0.5 demos):

  • drone-autopilot/src/{main,autopilot,mavlink}.c + board.yaml
    -- chip API mismatches (lsm6dso_axes_t, ina236 signature, UART
    field name baudrate, INA236 default I²C addr literal, PWM
    header include, missing <stdio.h>, float/double promotion in
    the GPS pack, MAVLink UART moved off the non-existent
    E1M_UART2). Board.yaml gains a carrier.populated: override
    for bmp390 + ublox_neo_m9n so the loader emits their chip
    knobs (the loader doesn't yet honour a top-level chips: block).
  • drone-hud/src/sensors.c -- same class of fixes.
  • ai-camera-viewer/src/inference_loop.c -- alp_camera_config_t
    and alp_inference_config_t reconciled with the real header
    field names.
  • iot-dashboard/src/main.c -- bme280_compensate signature
    rewrite (it returns a struct, not three out-params), <stdio.h>
    for snprintf, CONFIG_LV_FONT_MONTSERRAT_28=y added to prj.conf.
  • production-deployment/prj.conf -- CONFIG_MBEDTLS=n on
    native_sim to dodge Zephyr 4.4's PSA-crypto wiring (real fix in
    v0.6 via tf-psa-crypto).
  • All 15 examples/*-*/testcase.yaml files that an earlier
    auto-edit had left with harness: console indented inside the
    tags: list re-shaped to the correct flat layout.
  • 7 new demo CMakeLists.txt files -- find_package(Python3 REQUIRED COMPONENTS Interpreter) moved BEFORE
    find_package(Zephyr) so ${Python3_EXECUTABLE} is defined
    when the alp_project loader runs.

New flagship demos (paper-correct stubs, ~50 % comment ratio):

  • examples/ai-object-detection-realtime/ -- YOLOv8-tiny on
    V2N-M1 (DEEPX 29 TOPS) + V2H-M1 (DEEPX 113 TOPS). Camera →
    inference → bounding-box overlay → FPS counter.
  • examples/ai-anomaly-detection-vibration/ -- accelerometer →
    1D-CNN → anomaly score for predictive maintenance. AEN
    always-on Ethos-U + V2N.
  • examples/iot-fleet-ota/ -- secure OTA + rollback signed by
    the OPTIGA Trust M secure element. All E1M-X SoMs.
  • examples/audio-noise-suppression/ -- DSP + AI pipeline,
    ~10 ms latency. V2N + V2H DSP cores.
  • examples/audio-wake-word/ -- always-on keyword spotting on
    AEN's Ethos-U at sub-mW. E1M-AEN.
  • examples/mproc-dual-os-yocto-zephyr/ -- Yocto/A55 + Zephyr/M33
    shared-memory IPC, dual-firmware shape, dual-update lifecycle.
    V2N + V2H.

Added (2026-05-14 -- meta-alp-sdk Yocto layer + drone-autopilot MAVLink GCS link)

[UNTESTED] -- both pieces are paper-correct v0.5 scaffolding.

  • meta-alp-sdk/ -- top-level Yocto layer that wires the SDK into
    ROS 2 Humble + DEEPX-on-Linux images for V2N + V2N-M1 Linux
    targets. Layer compatibility: kirkstone scarthgap. Ships:
    • conf/layer.conf + conf/machine/e1m-v2n101.conf +
      conf/machine/e1m-v2m101.conf (V2N base vs V2N + DEEPX SoM).
    • recipes-core/alp-sdk/alp-sdk_0.5.bb -- builds + installs
      libalp_sdk.so + the <alp/*> headers.
    • recipes-ros/alp-perception/alp-perception_0.5.bb -- builds
      the v2n-m1-ros-perception ROS 2 node from examples/v2n/.
    • recipes-deepx/dx-rt/dx-rt_2.4.bb -- pins DEEPX runtime +
      kernel module to v2.4.0; V2N base machines get a stub.
    • recipes-images/alp-image-edge.bb -- reference edge AI
      image bundling alp-sdk + dx-rt + ROS 2 + the perception node.
  • examples/drone-autopilot/src/mavlink.{c,h} -- minimal MAVLink
    v2 stack (no upstream c_library_v2 dependency). Pack/parse
    for HEARTBEAT / ATTITUDE / GPS_RAW_INT / GLOBAL_POSITION_INT /
    BATTERY_STATUS / RC_CHANNELS outbound; HEARTBEAT + COMMAND_LONG
    (arm/disarm + mode set) inbound. Wired into main.c as two
    threads (mav_tx @ 10 Hz, mav_rx byte-driven, both priority
    5). Customers drop in the upstream c_library_v2 when they need
    the full message dialect -- symbol prefixes don't collide
    (alp_mavlink_* vs mavlink_*). CRC-X.25 + per-message CRC
    extras hard-coded for the 7 message types we touch.

Added (2026-05-14 -- twister scenarios for the 6 LVGL + application demos)

Each of the new demo examples got a testcase.yaml registering
it with twister + matching tags so the demos build on every push:

  • lvgl-widgets-demo, lvgl-benchmark, lvgl-music-player --
    tags lvgl demo example (+ benchmark performance /
    audio codec per demo).
  • drone-hud -- tags lvgl demo example marketing showcase drone uav.
  • iot-dashboard -- tags lvgl iot mqtt tls demo example.
  • ai-camera-viewer -- tags lvgl ai inference demo example marketing showcase.

Two scenarios per demo:

  1. *.native_sim -- platform_allow: native_sim/native/64,
    build_only: true (LVGL needs an SDL2 host runtime; twister
    build-only is enough for v0.5).
  2. *.aen -- platform_allow: alif_e7_dk_rtss_hp,
    build_only: true (no EVK runner online; v0.6 HiL fills in
    the flash-and-run path).

LVGL upstream demos/widgets/ + demos/benchmark/ + demos/music/
sources are already pulled in via the existing
name-allowlist: - lvgl line in west.yml -- no west.yml change
needed.

Added (2026-05-14 -- §D.lib.loader: extras-tier1 CI pin-check workflow + baseline SW-fallback emission)

Two follow-ups so the §D.lib batch is fully self-validating:

  1. .github/workflows/nightly-extras-tier1-pins.yml (new) --
    nightly + PR-on-touch + manual workflow that runs
    west update --group-filter +extras-tier1 and asserts each
    pinned library directory under modules/lib/ ends up populated.

    • 9 stable pins (u8g2, libcoap, tinygsm, libwebsockets, jsmn,
      opus, Catch2, libmodbus, coreMQTT-SN) -- pin breakage FAILS
      the workflow.
    • 4 TBD pins (minimp3, libhelix, bearssl, madgwick_ahrs) --
      warn-only until the maintainer locks specific SHAs.
      Audit artefact uploaded with the first line of each fetched
      library's README so reviewers can eyeball "did this land the
      right repo?".
  2. Baseline library SW-fallback emission -- the 4 pre-existing
    Tier 1 libraries with HW bindings (lvgl / mbedtls / cmsis_dsp /
    littlefs) now emit their matching CONFIG_ALP_<LIB>_<FALLBACK>=y
    line in alp.conf alongside the upstream Zephyr-module knob:

    • lvglCONFIG_LVGL=y + CONFIG_ALP_LVGL_SW_BLIT=y
    • mbedtlsCONFIG_MBEDTLS=y + CONFIG_MBEDTLS_BUILTIN=y +
      CONFIG_ALP_MBEDTLS_PURE_C=y
    • cmsis_dspCONFIG_CMSIS_DSP=y + CONFIG_ALP_CMSIS_DSP_SCALAR=y
    • littlefsCONFIG_FILE_SYSTEM_LITTLEFS=y +
      CONFIG_FILE_SYSTEM=y + CONFIG_ALP_LITTLEFS_SYNC_IO=y
      Redundant with Kconfig.alp-libraries' default y on those
      symbols, but documents the fallback choice next to the
      library-enable line in the emitted alp.conf.

TestHwBackendsLoader.test_sw_fallback_always_emitted extended
from 8 to 12 assertions to lock the four new emissions in.

Added (2026-05-14 -- §D.lib.loader: native_sim twister scenario for library knobs)

tests/zephyr/library_knobs/ -- new twister test scenario that
exercises the alp.conf → Kconfig → Zephyr build chain end-to-end
for the §D.lib library set on native_sim/native/64. Three
on-purpose checks:

  1. mbedtls links -- includes mbedtls/version.h + asserts
    MBEDTLS_VERSION_NUMBER != 0 and mbedtls_version_get_string
    resolves. Catches CONFIG_MBEDTLS=y emission breakage that
    the existing alp_project.py Python tests can't see (those
    only check the emitted CONFIG_*=y lines, not whether they
    produce a buildable Zephyr image).
  2. cmsis_dsp links -- includes arm_math.h + calls
    arm_copy_f32(...). Confirms CMSIS-DSP's symbol surface
    resolves at link time.
  3. §D.lib SW-fallback knobs reachable from Kconfig -- compile-
    time check counts 21 CONFIG_ALP_<LIB>_<FALLBACK>=y knobs
    declared in zephyr/Kconfig.alp-libraries and asserts each
    one is defined. Catches a broken rsource "Kconfig.alp-libraries"
    line in zephyr/Kconfig (the most likely v0.6 regression).

Test registers with twister under tag library_knobs.smoke;
runs on every push that touches metadata/library-profiles/,
zephyr/Kconfig.alp-libraries, or scripts/alp_project.py.

extras-tier1-only libraries (u8g2, opus, libcoap, libwebsockets,
...) are NOT exercised here because their west pin is disabled
by default; a follow-up workflow that flips
--group-filter +extras-tier1 will cover them once those pins
stabilise from # TBD: pin SHA to specific revisions.

Added (2026-05-14 -- §D.lib.loader: unit tests + west.yml extras-tier1 group)

Two follow-ups so the §D.lib batch + loader stay regression-safe
and the libraries we don't currently ship under Zephyr's tree
have a pinned consumption path.

Loader unit tests -- new TestHwBackendsLoader class in
tests/scripts/test_alp_project.py (9 tests). For each SoM SKU
(AEN301, AEN401, AEN601, AEN801, V2N101, V2M101, NX9101) +
12 libraries, asserts the expected CONFIG_ALP_*=y emission set.
Locks in the per-SKU wiring -- any future metadata change (new
SoM cap, silicon family, library) that drops or duplicates a
binding fails CI at the per-priority-match level, not at twister.
Cases covered:

  • E3 emits U55 only, never U85.
  • E4 emits BOTH U85 (primary) + U55 (secondary) driver shims.
  • E6 emits LVGL_GPU2D + GFX_COMPAT_GPU2D (gpu2d cap present).
  • E8 routes LITTLEFS_XSPI_DMA through the hexspi_dma cap path.
  • V2N101 emits DRP_AI + NEON + CAU + EMMC_DMA; nothing AEN-specific.
  • NX9101 emits ETHOS_U65 + N93 driver shim.
  • OPTIGA cross-family: fires on NX9101 where no higher-priority
    crypto wins, suppressed on AEN401 by CryptoCell.
  • Unconditional DMA fallbacks (TFLM_DMA_COPY, MINIMP3_I2S_DMA)
    always emit.
  • SW-fallback knobs for §D.lib libraries always emit.

extras-tier1 west.yml group -- 13 upstream pins for the
Tier 1 libraries that aren't already in Zephyr's modules tree:
u8g2 (v2.36.5), libcoap (v4.3.5), TinyGSM (v0.11.7),
libwebsockets (v4.3.4), jsmn (v1.1.0), opus (v1.5.2),
Catch2 (v3.7.1), libmodbus (v3.1.10), coreMQTT-SN (v1.0.1).
Pinned to main/master with # TBD: pin SHA after maintainer audit for upstreams that have no semver releases (minimp3,
libhelix) or are tarball-only (bearssl uses the community
mirror at github.com/bearsslmirror/BearSSL; madgwick_ahrs
uses x-io Technologies' Fusion successor library).

gfx_compat is maintainer-written and ships in-tree -- no west
pin needed. tflite-micro and nanopb already live in Zephyr's
own west.yml; allowlisted in our import filter.

The extras-tier1 group is disabled by default
(-extras-tier1 in group-filter:), so v0.5 workspaces stay
light. Customers flip via west update --group-filter +extras-tier1 when their board.yaml libraries: lists any
of these libraries.

Added (2026-05-14 -- §D.lib.loader: requires_cap matcher + capability-keyed bindings audit)

Phase 2b's loader grew a requires_cap: matcher so library
priority entries can key off the per-SoM capabilities: block
directly, rather than the family-coarse soc_family: token.
Three concrete wins:

  1. Cross-family bindings collapse to one entry. optiga_trust_m
    is populated on AEN + V2N + NX9101 SKUs. Old form needed
    three priority entries (one per family); new form is one
    requires_cap: optiga_trust_m.
  2. Sub-family bindings stop over-firing. gpu2d is on E6 /
    E7 / E8 but not E3 / E4 / E5. soc_family: alif_ensemble fired
    on every Ensemble SKU; requires_cap: gpu2d only fires where
    the silicon actually carries it.
  3. NPU population gets surgical. Numeric caps
    (ethos_u55_count, ethos_u85_count, ethos_u65_count) are
    treated as truthy when > 0 -- requires_cap: ethos_u85_count
    handles "this SKU has at least one U85" directly.

Loader (scripts/alp_project.py::_emit_library_hw_backends) now
parses the SKU's capabilities: block (line-driven, no PyYAML
dep) and exposes a _cap_truthy() helper that handles booleans +
numeric counts. Per-priority match: all specified keys
(silicon: / soc_family: / requires_cap:) must match; any
omitted key is wildcarded.

Library hw-backends.yaml rewrite (capability-keyed where it beats
soc_family): tflite_micro / lvgl / mbedtls / cmsis_dsp / littlefs /
bearssl / madgwick_ahrs / u8g2 / gfx_compat / minimp3 / opus.
Notable: tflite_micro grew an ethos_u65_count entry so NX9101
emits CONFIG_ALP_TFLM_ETHOS_U65=y; littlefs now resolves E8's
HexSPI separately from E3..E7's OctalSPI (both share the
CONFIG_ALP_LITTLEFS_XSPI_DMA=y driver shim).

New Kconfig knob: ALP_TFLM_ETHOS_U65 (depends on
ALP_SOC_NXP_IMX9_IMX93). The legacy preferred_backend: ethos_u
code path in scripts/alp_project.py also emits it alongside
CONFIG_ALP_SDK_INFERENCE_ETHOS_U_N93=y for the N93 driver shim.

include/alp/inference.h ALP_INFERENCE_BACKEND_ETHOS_U enum doc
enumerates all three U55 / U65 / U85 variants explicitly.

Cross-SKU smoke-test (driver dump per SKU):

  • AEN401 (E4): U85 primary + U55 secondary + HELIUM + DMA_COPY +
    LVGL_DMA2D + MBEDTLS_CRYPTOCELL + LITTLEFS_XSPI_DMA +
    BEARSSL_CRYPTOCELL.
  • AEN601 (E6): same + LVGL_GPU2D + MADGWICK_FPU + MINIMP3_HELIUM +
    OPUS_HELIUM.
  • AEN801 (E8): same as AEN601 but HexSPI resolves the
    LITTLEFS_XSPI_DMA gate.
  • V2N101: DRP_AI + NEON; LVGL_TMU; MBEDTLS_CAU; LITTLEFS_EMMC_DMA.
  • NX9101: ETHOS_U65 + NEON; MBEDTLS_OPTIGA; LITTLEFS_EMMC_DMA.

Added (2026-05-14 -- §D.lib.loader: per-SoM capabilities blocks + baseline hw-backends + ml_npu split)

Three audit follow-ups in one batch, after the user pointed out
that E4 / E6 / E8 actually carry TWO Ethos-U55s alongside the
single Ethos-U85 -- the NPU population is U55-HE (paired with
M55-HE) + U55-HP (paired with M55-HP) + U85-HG (Hyper-Generative,
Transformer-capable):

  1. tflite_micro/hw-backends.yaml ml_npu split -- single
    ml_npu class restructured into ml_npu_primary +
    ml_npu_secondary. On E4 / E6 / E8 the primary emits
    CONFIG_ALP_TFLM_ETHOS_U85=y and the secondary emits
    CONFIG_ALP_TFLM_ETHOS_U55=y, so BOTH driver shims now
    link. On E3 / E5 / E7 only the primary fires (U55-only).

  2. Baseline-library hw-backends.yaml fill-in -- the
    pre-existing 4 libraries with HW bindings (lvgl, mbedtls,
    cmsis_dsp, littlefs) gained their own hw-backends.yaml. Now
    every Tier 1 library either ships a profile or has a
    documented "pure-SW only" status. Matching Kconfig knobs
    added to Kconfig.alp-libraries:

    • ALP_LVGL_GPU2D / _DAVE2D / _TMU / _DMA2D / _SW_BLIT
    • ALP_MBEDTLS_CRYPTOCELL / _INLINE_AES / _CAU / _OPTIGA
      / _PURE_C
    • ALP_CMSIS_DSP_HELIUM / _NEON / _TMU_CORDIC / _TMU_FFT
      / _ADC_DMA / _SCALAR
    • ALP_LITTLEFS_XSPI_DMA / _EMMC_DMA / _QSPI_DMA / _SYNC_IO
  3. Per-SoM capabilities: blocks -- new top-level section
    in every metadata/e1m_modules/E1M-*.yaml declares the
    accelerator population per SKU. Field shape:
    ethos_u55_count: <int> / ethos_u85_count: <int> /
    ethos_u65_count: <int> for NPU counts; booleans for the
    remaining accelerators (drp_ai / deepx_dx / helium_mve /
    neon / gpu2d / dave2d / cryptocell / inline_aes / cau /
    optiga_trust_m / xspi_dma / emmc_dma / quadspi_dma / dma2d /
    tmu_cordic / tmu_fft / tmu_fac).
    Populated for AEN301 / AEN401 / AEN501 / AEN601 / AEN701 /
    AEN801 / V2N101 / V2N102 / V2M101 / V2M102 / NX9101 -- every
    SoM SKU in the registry. Per the "Pending exact HW
    configurations" rule, items not yet datasheet-verified (E3
    GPU2D presence, E4 DAVE2D presence) are explicitly marked
    false + # TBD. in a trailing comment so the maintainer
    can flip them once the SoM BOM finalises.

  4. npu_population: lists -- alongside ethos_u_variants:,
    each AEN SKU now declares the full NPU population as a list
    of named instances with role: + paired_with: tags
    (NPU-HE / M55-HE, NPU-HP / M55-HP, NPU-HG / HG-subsystem).
    Matches the Alif Ensemble block diagram in the datasheet.

The loader (scripts/alp_project.py) still picks matches via
silicon: / soc_family: -- the new capabilities: block is
declarative now, available for a future requires_cap: key in
hw-backends.yaml.

Fixed (2026-05-14 -- §D.lib.loader follow-up: Ethos-U85 propagated through every consumer)

Phase 2b's per-NPU split landed in the new tflite_micro library-
profile loader but didn't propagate to the existing inference
dispatcher path or the SoM-preset metadata. This commit closes
those gaps so every layer reflects the U85 / U55 / U65 split:

  • scripts/alp_project.py: the legacy preferred_backend: ethos_u
    path now emits the per-NPU driver Kconfigs alongside the legacy
    dispatcher gate. Silicon alif:ensemble:e4 | e6 | e8
    CONFIG_ALP_TFLM_ETHOS_U85=y + CONFIG_ALP_TFLM_ETHOS_U55=y.
    Other Alif Ensemble SKUs (E3 / E5 / E7) → CONFIG_ALP_TFLM_ETHOS_U55=y
    only. nxp:imx9:imx93CONFIG_ALP_SDK_INFERENCE_ETHOS_U_N93=y
    (unchanged). CONFIG_ALP_SDK_INFERENCE_ETHOS_U=y stays as the
    customer-facing dispatcher gate.

  • metadata/e1m_modules/E1M-AEN*.yaml: new ethos_u_variants:
    list captures the full NPU population per SKU. AEN401 / AEN601 /
    AEN801 now declare [u85, u55]; the others stay [u55]. The
    singular ethos_u_variant: field promotes to the primary
    (U85 on the U85-bearing SKUs).

  • include/alp/inference.h: ALP_INFERENCE_BACKEND_ETHOS_U enum
    doc enumerates the three variants the token covers (U85 / U65 /
    U55) + the per-SKU loader behaviour. Public ABI stays generic.

  • src/zephyr/inference_tflm.cpp: file-header comment generalised
    from "U55 + U65" to "U55 + U65 + U85" with the per-Kconfig matrix.

  • README.md inference-dispatcher row: notes the U55 / U85 / U65
    coverage.

Fixed (2026-05-14 -- §D.lib.loader: Ethos-U85 vs U55 differentiation for tflite_micro)

§D.lib batch collapsed both Alif NPUs (Ethos-U55 + Ethos-U85) under
a single ethos_u backend. In silicon they are two different IPs:

  • Ethos-U55: on every Ensemble SKU (E3, E4, E5, E6, E7, E8) -- two
    instances per SoC.
  • Ethos-U85: on E4, E6, E8 only -- one instance per SoC, Transformer-
    capable, the Generative-AI forward path.

The loader and the tflite_micro hw-backends.yaml now distinguish
the two:

  • scripts/alp_project.py::_emit_library_hw_backends learns a
    silicon: matcher (in addition to soc_family:). When a
    priority entry sets silicon: alif:ensemble:e4, the loader only
    emits it on the SKU whose silicon: field in
    metadata/e1m_modules/<sku>.yaml matches exactly.
  • metadata/library-profiles/tflite_micro/hw-backends.yaml now
    declares three U85 entries (E4 / E6 / E8) as priority 1, then a
    family-wide U55 entry for the remaining SKUs. Result on an
    AEN401 / AEN601 / AEN801 board: CONFIG_ALP_TFLM_ETHOS_U85=y
    emitted. On AEN301 / AEN501 / AEN701: CONFIG_ALP_TFLM_ETHOS_U55=y.
  • zephyr/Kconfig.alp-libraries splits the old ALP_TFLM_ETHOS_U
    symbol into ALP_TFLM_ETHOS_U85 (depends on E4 || E6 || E8) +
    ALP_TFLM_ETHOS_U55 (depends on any E* SoC).

Other libraries (cmsis_dsp / minimp3 / opus) only bind to Helium
MVE on the M55 cores, which every E SoC ships -- no per-silicon
split needed there.

Changed (2026-05-14 -- §D.closeout: v1.0-readiness + README + test-coverage closeout)

Phase 5 (closeout) of the chip-and-library ecosystem expansion per
docs/superpowers/specs/2026-05-14-chip-and-library-ecosystem-design.md.

  • docs/v1.0-readiness.md: new Pillar 4-bis section declaring the
    three-tier ecosystem in operation (Tier 1 = 80 chips + 25 libs;
    Tier 2 in alp-sdk-community = skeleton + 10 seed chips;
    Tier 3 = customer / private repos via pattern-only support).
  • README.md: chip-count bump 20+ → 80 + Tier 2 pointer.
  • docs/test-coverage-audit.md: chips/ row file count 108 → 254,
    driver count 21 → 80; notes the [UNTESTED] badge convention.

Total v0.5 §D-batch delivery:

  • 49 new Tier 1 chip drivers (§D.AI 18, §D.industrial 18, §D.iot 9,
    §D.audio 6)
  • 17 new Tier 1 library knobs + per-library hw-backends.yaml
  • §D.lib.loader cross-library HW-backend loader + Kconfig.alp-libraries
  • alp-sdk-community public repo + skeleton + 10 seed contributions

Added (2026-05-14 -- §D.community: Tier 2 contribution surfaces in alp-sdk)

Phase 3 of the chip-and-library ecosystem expansion per
docs/superpowers/specs/2026-05-14-chip-and-library-ecosystem-design.md.

alp-sdk-side artefacts that pair with the new alplabai/alp-sdk-
community repo (created separately):

  • metadata/schemas/contribution-v1.schema.json (new) -- JSON Schema
    that every Tier 2 contribution's metadata.yaml validates against
    on PR. Permits Apache-2.0 / MIT / BSD only (GPL rejected by
    pr-lint); enforces name: matches the parent directory; pins
    the family: enum (sensor / display / camera / motor / encoder /
    audio / power / cellular / lora / wifi / ble / gnss / crypto /
    memory / io_expander / serdes / switch / graphics / ml / control /
    iot / networking / parsing / serialization / testing / other);
    pins interfaces: enum (i2c / spi / uart / i2s / pwm / gpio /
    can / adc / dac / rtc / watchdog / usb / ethernet / mipi_csi2 /
    dvp / fpd_link / gmsl / pcie / gpio_bitbang / gpio_pwm).
  • docs/contributing-tier-2.md (new) -- customer-facing
    walkthrough of the three-tier model + per-contribution
    checklist + the three integration patterns (pull-everything /
    per-contribution selection / search-then-clone) + the
    "Verified" → Tier-1 promotion path.

The alplabai/alp-sdk-community repo + its skeleton (registry.yaml,
west.yml, templates/, .github/workflows/, CODEOWNERS) lives in a
separate commit on that repo's main branch.

Added (2026-05-14 -- §D.lib.loader: cross-library HW-backend loader hook)

Phase 2b of the chip-and-library ecosystem expansion. Adds the
loader hook that picks the highest-priority HW backend per
(library × accelerator class) from each enabled library's
metadata/library-profiles/<name>/hw-backends.yaml, cross-
referencing the active SoM family.

  • scripts/alp_project.py grows _emit_library_hw_backends()
    (~70 LoC) — runs after the SW-fallback emission and walks each
    library's priority list, emitting the first matching backend per
    accelerator class as CONFIG_*=y.
  • zephyr/Kconfig.alp-libraries (~200 LoC, new file) declares every
    per-library + per-backend Kconfig symbol the loader can emit.
    Sourced from zephyr/Kconfig via rsource under the ALP_SDK
    if-block. SW-fallback symbols default y; HW-acceleration
    symbols default off and turn on only when the loader writes them.

NOT in this commit (deferred per the design spec): capabilities:
blocks in metadata/e1m_modules/E1M-*.yaml. Loader currently maps
SoM family directly from SKU; finer-grained per-SoM capability
flags (has_ethos_u: true, has_dave2d: true, ...) belong to the
maintainer per the "Pending exact HW configurations" rule.

Added (2026-05-14 -- §D.lib: 17 library knobs + per-library hw-backends)

Phase 2 of the chip-and-library ecosystem expansion per
docs/superpowers/specs/2026-05-14-chip-and-library-ecosystem-design.md.

17 new libraries available via board.yaml's libraries: enum
(was 8, now 25 -- on the v1.0 target). Each library ships:

  • One metadata/library-profiles/<name>/hw-backends.yaml declaring
    which accelerator classes (NPU / GPU / SIMD / DMA / crypto /
    cordic / timing) it binds to per SoM family, with priority order
    • matching CONFIG_* symbol. Pure-SW fallback required + always
      available.
  • An entry in scripts/alp_project.py's _LIBRARY_KCONFIG table
    that emits the SW-fallback CONFIG_* unconditionally. The
    cross-library loader hook (§D.lib.loader, next commit) layers
    the HW-backend CONFIG_* on top by cross-referencing
    hw-backends.yaml against the active SoM's capabilities:
    block in metadata/soms/*.yaml.
  • An entry in metadata/schemas/board-config-v1.schema.json's
    libraries: enum so the schema validator accepts the new names.

Libraries by domain (§D.lib. tag):

  • §D.lib.ai (3): tflite_micro, u8g2, gfx_compat
  • §D.lib.industrial (3): madgwick_ahrs, pid, modbus
  • §D.lib.iot (7): coremqtt_sn, libcoap, tinygsm, nanopb,
    libwebsockets, jsmn, bearssl
  • §D.lib.audio (3): minimp3, opus, libhelix
  • §D.lib.test (1): catch2

Verification: every hw-backends.yaml carries verification: hil_silicon: untested / smoke_tests: build_only -- the wiring is
schema-validated but no per-(library × SoM-family) HiL bring-up
has been run yet.

west.yml pins for the libraries NOT already in Zephyr's modules
tree (u8g2, libcoap, tinygsm, libwebsockets, jsmn, bearssl,
minimp3, opus, libhelix, catch2, libmodbus, madgwick_ahrs,
coremqtt_sn, gfx_compat) are NOT in this commit -- they land in
a follow-up feat(west): extras-tier1 group once the maintainer
picks the per-library tagged revisions. Tier 1 libraries that
ARE already in Zephyr's west.yml (tflite_micro, nanopb)
flow through the existing name-allowlist filter.

Added (2026-05-14 -- §D.audio: 6 audio chip drivers)

Tier 1 ecosystem expansion -- Phase 1 §D.audio batch. All headers
carry the [ABI-EXPERIMENTAL] + [UNTESTED] badges.

  • ics_43434 -- InvenSense ICS-43434 omnidirectional
    MEMS mic (I2S, channel-binding helper).
  • inmp441 -- InvenSense INMP441 low-cost MEMS mic.
  • wm8960 -- Cirrus / Wolfson WM8960 stereo codec
    (I2C config + I2S data; packed 9-bit register write).
  • tlv320aic3204 -- TI TLV320AIC3204 premium codec w/
    miniDSP (page-paged I2C control surface).
  • max98357a -- ADI MAX98357A 3 W mono class-D amp
    (shutdown / mode-select pin control).
  • es8388 -- Everest Semi ES8388 stereo codec
    (China-domestic; same shape as WM8960).

Audio sample path on these chips goes through the portable
<alp/i2s.h> peripheral surface; the drivers above only own the
I2C control / GPIO power-rail surfaces and channel-binding helpers.

Fixed (2026-05-14 -- §D.iot ZTEST tests follow-up)

§D.iot ZTESTs were added in this commit as a follow-up — the
prior §D.iot commit (6fbb14a) landed the chip drivers +
Kconfig/CMake glue but the NULL-arg-guard ZTEST entries didn't
make it in due to an Edit-mismatch on the test file. Captured
here so the audit trail stays clean.

Added (2026-05-14 -- §D.iot: 9 IoT / connectivity chip drivers)

Tier 1 ecosystem expansion -- Phase 1 §D.iot batch. Every public
header in this batch carries the [ABI-EXPERIMENTAL] + [UNTESTED]
badges. The chip headers added in this batch + the prior §D.AI +
§D.industrial batches are all tagged [UNTESTED] -- the verification
badge captures the v0.5 truth: drivers compile + pass the NULL-arg
ZTESTs but have no HiL silicon bring-up yet, so customers should
treat all timing / register-value / lifecycle sequencing as paper-
correct only until the v1.0 verification sweep lands.

  • quectel_bg95 -- Quectel LTE-M / NB-IoT / EGPRS module
    (UART AT shell + PWRKEY pulse).
  • quectel_bg77 -- Quectel LTE-M / NB-IoT module with
    integrated GNSS.
  • ublox_sara_r5 -- u-blox LTE-M carrier-certified module
    (1500 ms PWR_ON pulse).
  • semtech_sx1262 -- Semtech SX1262 LoRa / FSK transceiver
    (opcode shell + BUSY-wait + GetStatus probe).
  • semtech_sx1276 -- Semtech SX1276 legacy LoRa transceiver
    (register R/W + REG_VERSION probe).
  • ublox_neo_m9n -- u-blox NEO-M9N multi-constellation GNSS
    (UART NMEA line read).
  • ublox_max_m10s -- u-blox MAX-M10S small-footprint GNSS.
  • atgm336h -- AllyStar ATGM336H cost-optimised GNSS.
  • atecc608b -- Microchip ATECC608B EC P-256 + AES
    secure element (wake / idle / sleep shell; ATCA crypto pending
    CryptoAuthLib import).

Changed (2026-05-14 -- §D.AI + §D.industrial: [UNTESTED] verification badges)

Retro-fit @par Verification status: [UNTESTED] Doxygen tag onto
every chip header added in the §D.AI + §D.industrial batches earlier
in this run, plus a matching verification: { hil_silicon: untested, smoke_tests: null_arg_guard } block in each chip's metadata YAML.
Customer-visible signal that the v0.5 drivers compile + pass the
NULL-arg ZTEST surface but have not yet seen HiL silicon bring-up.

Added (2026-05-14 -- §D.industrial: 18 industrial sensing / control chip drivers)

Tier 1 ecosystem expansion -- chip-and-library-ecosystem-design.md
Phase 1 §D.industrial batch. Drivers ship with Doxygen-clean public
headers in include/alp/chips/<name>.h, implementations under
chips/<name>/, metadata YAML, Kconfig / CMakeLists / alp_project.py
hooks, and per-chip NULL-arg-guard ZTESTs.

  • bmp390 -- Bosch BMP390 high-precision pressure sensor (I2C).
  • ms5611 -- TE MS5611 drone-grade barometer (I2C; PROM
    read at init).
  • lps22hb -- ST LPS22HB MEMS barometer (I2C).
  • vl53l1x -- ST VL53L1X single-zone ToF ranger (I2C).
  • vl53l5cx -- ST VL53L5CX 8x8 multi-zone ToF ranger (I2C).
  • a02yyuw -- DFRobot A02YYUW waterproof ultrasonic ranger
    (UART; checksum-validated 4-byte distance frame).
  • drv8833 -- TI DRV8833 dual H-bridge brushed-DC driver
    (PWM; signed pulse-width per channel, sleep gate).
  • drv8825 -- TI DRV8825 bipolar stepper (PWM step + GPIO
    dir / microstep / nEnable).
  • tmc2209 -- Trinamic TMC2209 silent stepper driver
    (UART register read / write with CRC-8/ATM).
  • a4988 -- Allegro A4988 stepper driver (PWM step + GPIO
    dir / microstep / nEnable).
  • as5048a_b -- ams AS5048B 14-bit magnetic encoder (I2C).
  • mt6701 -- MagnTek MT6701 14-bit magnetic encoder (I2C).
  • hx711 -- Avia Semi HX711 24-bit load-cell ADC
    (bit-banged 2-wire, 128 / 64 / 32 gain).
  • max31855 -- ADI MAX31855 K-type thermocouple-to-digital
    (SPI; signed milli-C decode + fault flags).
  • max31865 -- ADI MAX31865 PT100 / PT1000 RTD-to-digital
    (SPI; 15-bit ratio + fault bit).
  • tsl2591 -- ams TSL2591 wide-dynamic-range light sensor
    (I2C; visible + IR channels).
  • qmc5883l -- QST QMC5883L 3-axis compass (I2C; continuous
    mode at 200 Hz / 2 G / OSR 512).
  • veml7700 -- Vishay VEML7700 ALS (I2C; 16-bit count read).

Validators clean before push:

  • scripts/validate_metadata.py
  • scripts/check_example_portability.py
  • scripts/check_pin_conflicts.py
  • scripts/abi_snapshot.py --diff docs/abi/v0.5-snapshot.json

Added (2026-05-14 -- §D.AI: 18 vision / display / accelerator chip drivers)

Tier 1 ecosystem expansion -- chip-and-library-ecosystem-design.md
Phase 1 §D.AI batch. All drivers ship with a Doxygen-clean public
header in include/alp/chips/<name>.h, implementation under
chips/<name>/<name>.c, metadata at metadata/chips/<name>.yaml,
Zephyr Kconfig + CMakeLists glue, and a NULL-arg-guard ZTEST under
tests/zephyr/chips/src/main.c. Driver bodies follow the
[stub-impl] pattern: chip-ID + soft-reset + lifecycle is real;
vendor-specific register tables (camera resolution / pixel format
profiles, e-paper waveform LUTs) land in follow-up commits once the
maintainer adds the reference init scripts to the internal design
archive. All public symbols are tagged [ABI-EXPERIMENTAL] until
the first SoM verification.

  • ov2640 -- OmniVision 2 MP UXGA DVP camera (ESP32-CAM default).
  • ov5645 -- OmniVision 5 MP MIPI CSI-2 camera.
  • ov7670 -- OmniVision VGA DVP camera (reference classic).
  • ov9281 -- OmniVision 1 MP global-shutter mono MIPI CSI-2
    (AR/VR / ALPR / industrial tracking).
  • ar0234 -- onsemi 1080p global-shutter colour MIPI CSI-2.
  • imx219 -- Sony 8 MP MIPI CSI-2 (RPi Cam v2 standard).
  • imx477 -- Sony 12.3 MP MIPI CSI-2 (RPi HQ Camera).
  • gc2145 -- GalaxyCore 2 MP cost-sensitive DVP camera.
  • ti_ds90ub953_954 -- TI FPD-Link III camera SerDes pair
    (long-cable industrial machine vision).
  • maxim_max9295_9296 -- ADI (Maxim) GMSL2 6 Gbps automotive
    camera SerDes pair.
  • st7789 -- Sitronix ST7789V 240x240 / 240x320 IPS TFT
    (init + window + write-pixels).
  • ili9341 -- Ilitek ILI9341 240x320 SPI TFT.
  • ili9488 -- Ilitek ILI9488 320x480 SPI TFT.
  • ra8875 -- RAiO RA8875 5-7" LCD controller + resistive
    touch (SPI; register-level access + soft reset).
  • sh1106 -- Sino Wealth SH1106 128x64 monochrome OLED (I2C;
    drop-in alternative to SSD1306, accounts for 132-column RAM
    offset). Driver runs full lifecycle including frame push.
  • il3820 -- Solomon IL3820 4.2" tri-colour e-paper (SPI;
    init + busy-wait + soft reset; waveform LUT pending).
  • gdew0154t8 -- GoodDisplay GDEW0154T8 1.54" monochrome
    e-paper (SPI; same lifecycle shape as il3820).
  • hailo_8l -- Hailo-8L 13 TOPS NPU host-side bring-up
    driver for the M.2 PCIe form factor (host RESETB sequence +
    WAKE# read; PCIe enumeration + HailoRT runtime live on the
    Linux side).

Validators that ran clean on this batch (each before push):
validate_metadata.py / check_example_portability.py /
check_pin_conflicts.py / abi_snapshot.py --diff docs/abi/v0.5-snapshot.json.

Added (2026-05-14 -- V2N DEEPX rail GPIO map)

  • DEEPX_PWR_EN_REQ (P65) + DEEPX_CORE_0P75_EN (P64) added
    to metadata/e1m_modules/v2n/renesas-peripheral-map.{tsv,csv}.
    P65 is the rising-edge "DEEPX should come up now" signal from
    the primary PMIC into V2N; P64 is V2N's drive to the DA9292
    EN2 pin (and other shared-rail consumers) that physically
    enables the 0.75 V DEEPX rail. Sequence captured in
    [memory project_v2n_da9292_core_0p75_control.md]. V2N
    firmware module (src/zephyr/v2n_power_mgmt.c) wiring lands
    next.

Changed (2026-05-14 -- top-level UX simplification cont.)

  • docs/ota-device-contract.md cross-ref to
    notes/morning-handoff-2026-05-13.md removed.
    The notes/
    directory is gitignored (maintainer handoff drafts that are
    not part of the public SDK); a doc line that referenced a
    non-tracked file was dead-link bait for anyone cloning the
    public repo. Removed; the "Open questions" stand on their
    own.
  • tools/program_eeprom.py moved to scripts/program_eeprom.py.
    The tools/ directory only held one script (the EEPROM
    manifest packer); scripts/ already hosts 13 similar Python
    utilities (abi_snapshot.py, validate_metadata.py,
    validate_board_yaml.py, check_pin_conflicts.py, etc.).
    Folding the single-file directory into scripts/ removes one
    top-level entry and makes the "Alp SDK has one place for
    helper scripts" rule unambiguous. Files touched: README.md,
    docs/board-id.md, docs/bring-up-v2n.md,
    docs/getting-started.md, docs/test-plan.md,
    docs/troubleshooting.md,
    examples/v2n/v2n-board-id-readout/README.md,
    examples/v2n/v2n-eeprom-manifest-dump/README.md,
    include/alp/hw_info.h, src/zephyr/hw_info_zephyr.c,
    tests/fuzz/eeprom_manifest_fuzz.c,
    tests/scripts/test_program_eeprom.py, plus the script's own
    self-reference docstring. Stale tools/__pycache__/
    directory (the only remaining content after the move, and
    untracked) deleted to fully remove the top-level entry.
    Historical tools/ mentions in this CHANGELOG.md left
    untouched.
  • dts/bindings/ moved to zephyr/dts/bindings/. Zephyr
    devicetree bindings are Zephyr-only artefacts and belong under
    the zephyr/ module subtree alongside zephyr/Kconfig /
    zephyr/CMakeLists.txt / zephyr/module.yml. Folding them
    in removes the top-level dts/ directory.
    zephyr/module.yml updated with dts_root: zephyr so
    Zephyr's binding scanner finds them at the new location (the
    default <module-root>/dts/bindings/ no longer applies).
    Two binding files moved verbatim:
    alp,pin-array.yaml, vendor-prefixes.txt. Comment update
    in src/zephyr/peripheral_gpio.c. Top-level dts/
    directory deleted.
  • sysbuild/aen/ moved to zephyr/sysbuild/aen/. Sysbuild
    configs are Zephyr-only artefacts (MCUboot + ECDSA-P256
    secure-boot profile for AEN-Zephyr applications); folding them
    under the Zephyr module subtree means the top-level repo only
    shows directories that are OS-agnostic or OS-router
    (chips/, examples/, src/, etc.) -- Zephyr-specific
    artefacts cluster under zephyr/. Files touched:
    VERSIONS.md, docs/adr/0006-secure-boot-secure-ota.md,
    docs/secure-boot.md, docs/test-plan.md, keys/README.md,
    keys/generate_dev_key.sh, plus the moved README +
    sysbuild.conf internal self-refs. Top-level sysbuild/
    directory deleted (no longer holds any siblings).
  • examples/README.md audit. After the §A.6 SoM-subfolder
    refactor (examples/v2n/v2n-* and examples/aen/*), the
    cross-family table still listed edgeai-vision-aen as if it
    lived at the top level, and the V2N-specific table used bare
    directory names (v2n-gd32-bridge-ping) instead of the
    v2n/v2n-gd32-bridge-ping paths that actually exist on disk.
    Split into three sections (cross-family / AEN-specific /
    V2N-M1-specific) with correct relative paths for every row.

Added (2026-05-14 -- peripheral thin-spot test fills §C.22)

Closes the §1c "thin-spot fills" carry-forward from the
readiness doc.