| description | LabWired uses YAML configuration to define the simulated hardware, so the same firmware binary runs against different hardware configurations. |
|---|
LabWired uses a YAML-based configuration system to define the simulated hardware environment. This separation allows the same firmware binary to be tested against different hardware configurations (e.g., changing memory sizes or remapping peripherals) without recompilation.
A complete simulation requires two descriptor files:
- Chip Descriptor (
chips/<name>.yaml): Defines the internal architecture of the SoC (Flash/RAM size, internal peripheral addresses). - System Manifest (
systems/<name>.yaml): Instantiates a chip and defines board-level wiring (external sensors, UART loopbacks).
Defines the invariant properties of the silicon.
name: "STM32F103"
arch: "arm"
core: "cortex-m3" # Exact CPU core; gates core-specific behavior
# (e.g. bit-band aliasing exists only on M3/M4)
flash:
base: 0x08000000
size: "64KB" # Supports KB/MB suffixes
ram:
base: 0x20000000
size: "20KB"
peripherals:
# Internal Peripheral Definition
- id: "usart1"
type: "uart"
base_address: 0x40013800
irq: 37
config:
profile: "stm32f1" # Loads architecture-specific register map
- id: "gpioa"
type: "gpio"
base_address: 0x40010800
config:
profile: "stm32f1"
# Declarative Peripheral (Custom)
- id: "my_custom_timer"
type: "declarative"
base_address: 0x40004000
config:
path: "../peripherals/custom_timer.yaml"uart,usart: Universal Asynchronous Receiver Transmittergpio: General Purpose I/Orcc: Reset and Clock Controltimer: Basic Timeri2c: Inter-Integrated Circuitspi: Serial Peripheral Interfaceexti: External Interrupt Controllerafio: Alternate Function I/Odma: Direct Memory Access Controllersystick: System Tick Timerdeclarative: Loads a generic peripheral from a YAML register description.
Defines the board-level environment.
name: "BluePill Board"
chip: "../chips/stm32f103.yaml" # Path relative to this file
# External Device Connections (Planned)
connectors:
- type: "uart"
peripheral: "usart1"
endpoint: "host_console" # Pipes UART output to simulator stdoutFor st7789-170x320 external devices, the optional config keys col_offset,
row_offset, cols, and rows describe the glass in physical 240×320
frame-memory coordinates. Supply all four together. For a module exposing
columns 35–204 and all 320 rows, use col_offset: 35, row_offset: 0,
cols: 170, and rows: 320; the offsets are module integration values.
The configured framebuffer and artifact dimensions remain 170×320 regardless of MADCTL rotation or mirroring. MADCTL controls how firmware writes map into physical memory, while the glass stays fixed. Without a configured window, the framebuffer uses firmware coordinates and reports 240×320 or 320×240 according to MADCTL's row/column exchange bit.
debug_uart: "usb_serial_jtag" # ESP32-C3 SuperMini, ESP32-S3 Zero
debug_uart: "uart0" # CP210x/CH34x bridge boards
debug_uart: "uart1" # a board that routes its console elsewhereAn ESP32-C3 or -S3 has two consoles in silicon: UART0 and the chip's own
USB-Serial-JTAG block. Which one carries Serial to the developer's cable is
neither a chip fact nor a firmware fact — it is a BOARD fact, decided by what
the USB socket is soldered to:
| board's USB socket | console | Serial build flag |
|---|---|---|
| native USB | USB-Serial-JTAG | ARDUINO_USB_CDC_ON_BOOT=1 |
| USB-UART bridge IC | UART0 | ARDUINO_USB_CDC_ON_BOOT=0 |
The build flags and this key must come from the SAME board fact. If they
disagree, firmware built for one console runs against a twin listening on the
other and the Serial pane stays empty — which is exactly what a real board does
when the flags are wrong, and exactly what shipped: a hosted build flashed to a
real ESP32-C3 SuperMini printed the ROM and bootloader banners and then nothing,
because Serial was on UART0 and a SuperMini's USB-C is not.
The engine taps ONE console, the declared one, because a real board gives you
one cable. It records the other and reports what the firmware said there
(WasmSimulator::console_mismatch) so a silent pane has a reason. A declared
console the bus does not have is refused at construction rather than silently
substituted — see crates/core/src/console.rs.
Omit the key to keep each path's historical default (UART0 on the ESP32-C3 merged-flash paths; every console-capable UART elsewhere).
For an inputs.env CI world, each nodes[].system value points to this
same System Manifest format used by the Playground. The environment manifest
adds node IDs, firmware paths, and explicit interconnects; it does not create a
second board-configuration dialect. v0.19 world manifests reject
nodes[].config_overrides: the field must be omitted, including {} and null.
Each resolved node chip must resolve to arch: arm and declare
core: cortex-m*; its firmware must be an EM_ARM ELF with a valid Cortex-M
Thumb reset vector at flash.base + reset_vector_offset (SP in RAM and
Thumb-bit reset handler in flash). See the CI Test Runner
for the closed world-interconnect schema.
A node may set profile: arduino-esp32, the same boot profile a test script
names in inputs.profile: a classic-ESP32 (Xtensa LX6) node running an
Arduino-ESP32 sketch ELF then takes the Arduino fast boot
(install_arduino_esp32_profile). Any other value, or the profile on any other
chip, is refused, and a world admits one such node (the profile's boot thunks
keep per-thread state). ESP32-S3 and ESP32-C3 nodes need no profile; their mask
ROMs are provisioned like the single-chip engine's (in the browser, passed as
the node's blobs).
World completion stays in the inputs.env test script, not the manifest. Set
limits.stop_when_assertions_pass: true to opt into a durable early completion:
all node-qualified assertions must pass at or after the optional
stop_when_assertions_pass_min_steps floor and remain true for the optional
stop_when_assertions_pass_settle_steps window (default 100000). The default
is false; a same-round runtime failure or wall_time_ms, max_cycles, or
max_uart_bytes limit takes precedence over assertions_passed. max_steps
remains the outer execution bound, so an all-pass final world round reports
assertions_passed.
To run a simulation, provide both the firmware and the system manifest:
labwired --firmware firmware.elf --system configs/systems/bluepill.yamlThe simulator loads the system manifest, resolves the chip descriptor, initializes the memory map, and begins execution at the Reset Vector.