SPEC-014 - Hardware Description Schema v0 and the Generator¶
Implementation and release scope: Project status records the implemented subset; roadmap defines release gates. Acceptance of this design does not establish implementation, verification, or support. Reconciled 2026-10-14.
Status: Accepted 2026-10-07 by the project owner (“do all”), with the defaults of §13. Specification work item 13 of the roadmap (07 §3).
Requirements: docs/requirements/HW.md (HW-001 to 006 restated; new from 007).
Builds on: 04 §1 (pipeline, model, outputs; ADR-004 LOCKED), §2 (device instances, init order), 03 §9.1 (regions); SPEC-009 §4.1, §5 (capability tables, storage probe), SPEC-010 §2 to §4 (partition description and constraints), SPEC-011 §2 (port manifest), SPEC-012 §11 (a board file), SPEC-005 §3.4 (static ceilings), SPEC-002 §8 (generic interrupt levels), 09 §7, §9 (embld outputs, embsvd); HW-006.
Research: R-002 §1: Devicetree’s “macrobatics” is the most repeated Zephyr complaint; R-001 §10: 19,000 lines of macros in Zephyr’s devicetree layer. The schema below keeps DeviceTree’s good ideas (compatible, references, hierarchy) in plain YAML validated by JSON Schema and turns them into plain C tables with a generator, so no macro layer exists.
1. Scope and terms¶
Term |
Meaning |
|---|---|
SoC description |
|
Board description |
|
System description |
|
Schema |
JSON Schema (draft 2020-12) files in |
Generator |
|
Importer |
|
Compatible |
A |
2. Common conventions¶
Files are YAML 1.2; keys are
snake_case; every file starts withschema_versionandkind: soc | board | system.Numbers accept
0xhexadecimal and the suffixesK,M,G(powers of two for sizes),kHz,MHzfor frequencies; the generator normalizes to integers.References are
ref: <path>where the path is<section>.<name>within the same file or<file>:<section>.<name>across files (soc:peripherals.usart1); every reference is resolved and checked.extends: <file>lets a part inherit a family base (stm32f4.yaml) and override or add nodes; the generator flattens inheritance before validation.Provenance:
imported_from: { tool, source, hash }is recorded by importers and preserved;curated: truemarks a node a human has reviewed. The generator warns on uncurated nodes and thesafetyprofile attribute refuses them (ADR-035).Every node may carry
doc:free text, emitted intoboard.md.
3. SoC schema¶
schema_version: 0
kind: soc
name: stm32f407
vendor: st
family: stm32f4
extends: stm32f4.yaml
arch: { port: cortex_m, variant: armv7m } # must match arch/<port>/arch.yaml
cpu:
core: cortex-m4f
revision: r0p1
fpu: fpv4-sp-d16
mpu: { model: armv7m, regions: 8 }
cache: none
trustzone: false
cores: [ { id: 0, role: main } ]
cycle_counter: dwt
memory:
regions:
- { name: flash, base: 0x08000000, size: 1M, attrs: [r, x] }
- { name: sram1, base: 0x20000000, size: 112K, attrs: [r, w, dma] }
- { name: sram2, base: 0x2001C000, size: 16K, attrs: [r, w, dma] }
- { name: ccm, base: 0x10000000, size: 64K, attrs: [r, w, tcm] } # no DMA
- { name: bkpsram, base: 0x40024000, size: 4K, attrs: [r, w, retained] }
clocks:
sources: [ { name: hsi, hz: 16MHz }, { name: hse, hz: board }, { name: lsi, hz: 32kHz } ]
plls: [ { name: pll, input: [hsi, hse], m: [2, 63], n: [50, 432], p: [2, 4, 6, 8], q: [2, 15] } ]
buses: [ { name: ahb1, max_hz: 168MHz }, { name: apb1, max_hz: 42MHz }, { name: apb2, max_hz: 84MHz } ]
gates: { usart1: apb2, usart2: apb1, gpioa: ahb1 }
interrupts:
controller: nvic
count: 82
priority_bits: 4
systick: true
peripherals:
- { name: usart1, compatible: "st,stm32-usart", base: 0x40011000, size: 0x400, irqs: [ { name: global, number: 37 } ],
clock: usart1, dma_requests: { tx: dma2.s7c4, rx: dma2.s2c4 }, reset: apb2.usart1 }
- { name: gpioa, compatible: "st,stm32-gpio", base: 0x40020000, size: 0x400, clock: gpioa, pins: 16 }
- { name: tim2, compatible: "st,stm32-timer", base: 0x40000000, size: 0x400, irqs: [ { name: global, number: 28 } ],
clock: tim2, features: [32bit, lowpower: false] }
dma:
controllers: [ { name: dma2, base: 0x40026400, streams: 8, channels: 8, irqs: [56, 57, 58, 59, 60, 68, 69, 70] } ]
flash:
geometry: { sectors: [16K, 16K, 16K, 16K, 64K, 128K, 128K, 128K, 128K, 128K, 128K, 128K], write_granularity: 1 }
power:
states: [ { name: sleep, exit_latency_us: 1 }, { name: stop, exit_latency_us: 15, loses: [clocks] }, { name: standby, exit_latency_us: 400, loses: [sram, clocks] } ]
errata:
- { id: "2.1.8", revisions: [r0p1], workaround: usart_rxne_clear }
debug: { swd: true, jtag: true, dwt: true, itm: true }
Required sections: arch, cpu, memory, interrupts, peripherals; the rest are optional with empty defaults. The arch line binds the SoC to a port manifest so the generator can check that the cpu’s protection model, cycle counter, and nesting match the port’s feature flags (PORT-001).
4. Board schema¶
schema_version: 0
kind: board
name: stm32f407g_disc1
vendor: st
soc: st/stm32f4/stm32f407.yaml
oscillators: { hse: { hz: 8MHz, source: external_crystal }, lse: { hz: 32768, source: external_crystal } }
pins:
usart2: { tx: PA2.af7, rx: PA3.af7 }
spi1: { sck: PA5.af5, miso: PA6.af5, mosi: PA7.af5 }
devices:
- { name: accel, compatible: "st,lis3dsh", bus: spi1, cs: PE3, irq: PE0, power: always }
- { name: audio_dac, compatible: "cirrus,cs43l22", bus: i2c1, address: 0x4A, reset: PD4 }
console: { device: usart2, baud: 115200 }
flash_partitions:
- { name: bootloader, base: 0x08000000, size: 128K }
- { name: slot0, base: 0x08020000, size: 384K }
- { name: slot1, base: 0x08080000, size: 384K }
- { name: storage, base: 0x080E0000, size: 128K }
leds: [ { name: green, gpio: PD12 }, { name: orange, gpio: PD13 }, { name: red, gpio: PD14 }, { name: blue, gpio: PD15 } ]
buttons: [ { name: user, gpio: PA0, active: high } ]
debug_probe: { openocd: "board/stm32f4discovery.cfg", pyocd_target: stm32f407vgtx }
defaults: { CONFIG_EMB_PROFILE: base, CONFIG_EMB_TICK_NS: 1000000, CONFIG_EMB_CONSOLE: usart2 }
The illustrative flash layout uses STM32F407 erase-sector boundaries: independent updates/storage must not share an erase unit. A nonoverlapping byte range alone is insufficient. Generation validates actual erase geometry and update trailer/alignment requirements before advertising A/B support.
Pins are P<port><pin>.af<n> for alternate functions or P<port><pin> for GPIO; the generator checks each against the SoC’s GPIO ports and against other assignments (no pin twice).
5. System schema¶
schema_version: 0
kind: system
name: sensor_node
board: st/stm32f407g_disc1.yaml
profile: isolated # tiny | base | isolated | multicore | native
config: { CONFIG_EMB_PRIORITY_COUNT: 32, CONFIG_EMB_LOG_LEVEL_DEFAULT: info }
partitions: [ ... ] # SPEC-010 §2
kernel_partition:
threads: [ { name: main, entry: app_main, priority: 10, stack: 2K } ]
objects: [ { name: cmds, type: msgq, item_size: 16, capacity: 8 } ]
ceiling_mutexes: # SPEC-005 §3.4, ADR-030
- { name: bus_lock, users: [kernel_partition.main, sensors.acq] }
shares: { window_ms: 100 } # ADR-029 partition shares; per-partition percent in partitions[]
trace: { ring_kb: 8, classes: [sched, sync, irq] }
log: { mode: deferred, transport: itm }
images: # ADR-008 slots per board partition
- { name: app, slot: slot0, cores: [0] }
The system file is where the generator learns every statically declared thread and object, so it can size kstores, build capability tables, compute ceilings, and order the init table (SPEC-009 §5.2, SPEC-010 §3).
6. Generated outputs¶
Output |
Content |
Consumer |
Spec |
|---|---|---|---|
|
constants: core count, region bases and sizes, IRQ numbers and encoded priorities for generic levels, vector names, clock frequencies, pin encodings |
everything |
SPEC-002 §8 |
|
|
drivers, kernel init |
04 §2 |
|
the clock tree configuration sequence that reaches the requested frequencies, with the computed PLL factors and bus dividers, checked against |
SoC startup |
04 §1.3 |
|
pin configuration table |
board init |
|
|
linker script and |
linker |
HW-006, SPEC-010 §3 |
|
partition table, region sets in the port’s encoding, descriptors with kstore addresses, device grants |
kernel |
SPEC-010 |
|
initial capability tables |
kernel |
SPEC-009 §4.1 |
|
static ceiling values per declared mutex |
kernel, application |
SPEC-005 §3.4 |
|
ordered init table entries for system-declared objects ( |
kernel start |
SPEC-009 §5.2 |
|
defaults and forced values |
configuration |
|
|
reference page: pins, devices, partitions, memory map, interrupts |
documentation |
|
|
the normalized, flattened, resolved model with provenance and hashes |
EmbStudio, EmbDebug, HIL runner, the benchmark harness metadata |
HW-005 |
|
region budget per partition, kstore sizes, MPU constraint results, clock results, warnings (uncurated nodes) |
reviewer |
SPEC-010 §4.3 |
|
CTF metadata for the configured trace event set |
tracing tools |
SPEC-015 |
Two generated artifacts come from other tools in the same pipeline: <emb/storage.h> from the layout probe (SPEC-009 §5.1) and the syscall stubs and table from the API annotations (SPEC-010 §5.3); CMake orders them.
7. Validation beyond the schema¶
The generator refuses the build, naming file and node (HW-002), on: unresolved reference; duplicate name within a section; overlapping memory regions or flash partitions, independently writable flash partitions sharing erase units or violating write/update alignment; a region or partition not aligned as its attributes or the protection hardware require; an IRQ number used twice or above interrupts.count; a pin assigned twice or to a function the SoC’s GPIO does not offer; a clock DAG cycle or a bus above max_hz; a device dependency cycle; a partition constraint violation (SPEC-010 §4.3); a ceiling mutex whose user is not a declared thread; a port manifest mismatch (arch says MPU but the port lacks mpu); an errata entry whose revision is not in the SoC’s list; a safety profile with an uncurated node.
8. Importers¶
SVD. EmbCC’s parser now exports nested clusters/arrays and structured
embsvd --json(09 §9). The RTOS--emit-emb-yamlmode/adapter remains a planned HW-004 deliverable: it must turn that input into a SoC draft with peripheral base/size/IRQ, memory map, NVIC parameters andimported_fromprovenance.compatiblestrings come from a vendor mapping table (hw/import/compat/<vendor>.yaml) where known, elsetodo; clocks, DMA maps, flash geometry, power and errata remain curatedtodofields. JSON export alone does not implement the RTOS schema or its provenance/curation rules.DeviceTree.
emb-dtsimport(Python, usingdtcto flatten) reads a Zephyr or Linux board.dtsand produces a board draft: pins from pinctrl nodes, devices withcompatible, bus and address, console fromchosen, flash partitions, LEDs and buttons; SoC-level.dtsicontent is used only to resolve references, never to replace the SVD-derived SoC file.Imports are one-shot drafts: re-import produces a diff for the author, never an overwrite of curated nodes.
9. Determinism and reproducibility¶
Outputs are sorted by name, carry no timestamps, and embed the generator version, the schema version, and the SHA-256 of each input file; CI regenerates and fails on any difference with the committed outputs (HW-003); hw.json is the single artifact other tools consume, so they never parse YAML themselves.
10. Versioning and migration¶
schema_version: 0 remains unstable until its own stable schema release: breaking changes ship with emb-hwgen migrate that rewrites descriptions in place and reports what it could not migrate. From the stable schema release it follows the API versioning policy (SPEC-001 §11): additive changes within a major, a migration tool across majors.
11. Target flow: a new board on a supported SoC¶
These CLI/importer/generator operations are planned. The example uses the same F407 SoC; moving from F407 to F446 is a different SoC description even when drivers can be shared.
emb board new --soc st/stm32f4/stm32f407 --from-dts custom_f407.dtsproducesboards/custom/custom_f407.yamlwith provenance andtodomarkers.The author fixes the oscillator, console, and LED, marks nodes curated, and commits.
emb build -b custom_f407regenerates everything;build_report.mdshows the region budget; no C is written (01 §6 success criterion).
12. Profiles and the tiny target¶
The tiny profile uses the same pipeline: the ATmega328P SoC file is 60 lines, the Uno board file 15 (SPEC-012 §11), and the generator’s AVR outputs are hw_config.h, soc_devices.c (small), the embld option set and a GNU ld script, board.md, and hw.json. Tables that would cost SRAM under EmbCC are emitted with EMB_FLASH_CONST (SPEC-012 §8).
13. Decisions taken at acceptance (2026-10-07)¶
Three description kinds (SoC, board, system) with one schema language (JSON Schema 2020-12 over YAML 1.2),
extendsinheritance, cross-file references, and recorded provenance withcuratedmarks.The system description is the single source for statically declared partitions, threads, objects, ceilings, shares, and images.
The generator is one Python tool emitting every output of §6 deterministically with input hashes; CI regenerates and compares.
Semantic validation (§7) is part of the generator and fails the build naming file and node; the
safetyprofile attribute refuses uncurated nodes.The SVD importer is an output mode of EmbCC’s
embsvd; the DeviceTree importer flattens withdtc; both produce one-shot drafts and diffs, never overwrites.hw.jsonis the only artifact downstream tools consume.Schema v0 is unstable until its own stable release with a migration tool; thereafter it follows the API versioning policy.