Skip to content

Commit 90692bc

Browse files
authored
Merge master content into develop (config-driven peripheral layouts + H563 demos + test fixes) (#5)
* Finalize LabWired v0.1.0 release * Finalize LabWired v0.1.0 release * feat(ir): Implement Strict IR and SVD Ingestion CLI * test(ir): Add robust unit and integration tests for Asset Foundry * docs: Add Tier 1 Device Strategy and real-world SVD fixtures * test(ir): Add nRF52832 SVD fixture and verification * docs: Add Architecture guide and CI workflow * feat: Unify Asset Foundry and Simulation Core - Implement native Strict IR (JSON) ingestion in core - Bridged labwired-ir and labwired-config via From traits - Support strict_ir peripheral type in SystemBus - Unify changelogs and update docs * docs: consolidate project plans and remove redundant core/docs * feat(ai): pivot to Agent-First Toolset (AIPi) and release v0.12.0 - Strategic Pivot: LabWired is now primarily an API for Agents (AIPi) - Human interfaces (VS Code) reclassified as Observer/Legacy - Implemented VLM-based Schematic Intelligence - Added Simulation Minutes usage telemetry - Unified E2E test suite for live synthesis - Fixed clippy warnings in core, loader, and dap crates - Updated CHANGELOG.md and README.md * feat: implement VCD trace export for simulation hardening * Revert "feat: implement VCD trace export for simulation hardening" This reverts commit f8901d6. * fix(core): close demo blockers for asset init, strict-ir loading, and docker dap * chore: clean root artifacts and normalize docs * core: avoid side-effecting peripheral reads in write path * core: skip peripheral ticking when cpu step fails * core: unify duplicated peripheral tick pipeline * core: resolve peripheral config paths from chip location * Add NUCLEO-H563ZI demo stack and board-IO telemetry * core: make peripheral layouts config-driven and fix workspace test regressions
1 parent 15f0819 commit 90692bc

56 files changed

Lines changed: 3469 additions & 333 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ members = [
1212
"crates/cli",
1313
"crates/firmware",
1414
"crates/firmware-ci-fixture",
15+
"crates/firmware-h563-demo",
16+
"crates/firmware-h563-io-demo",
17+
"crates/firmware-h563-fullchip-demo",
1518
"crates/riscv-ci-fixture",
1619
"crates/config", "crates/dap", "crates/gdbstub",
1720
"crates/ir",

README.md

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,67 @@
1-
# LabWired Core
1+
# LabWired Core - Firmware Simulation Engine
22

3-
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4-
[![LabWired Core CI](https://github.com/w1ne/labwired-core/actions/workflows/ci.yml/badge.svg)](https://github.com/w1ne/labwired-core/actions/workflows/ci.yml)
5-
6-
High-performance, declarative firmware simulator for ARM Cortex-M and RISC-V microcontrollers.
3+
> High-performance, declarative firmware simulator for ARM Cortex-M and RISC-V microcontrollers.
74
85
## Highlights
9-
- **v0.1.0 Ready**: [Blinky + I2C Sensor Demo](examples/demo-blinky/README.md)
6+
7+
- **v0.1.0 Demo**: [Blinky + I2C Sensor](examples/demo-blinky/README.md)
8+
- **NUCLEO-H563ZI Showcase**: [Human Demo Example](examples/nucleo-h563zi/README.md)
9+
- **NUCLEO-H563ZI Runbook**: [Reproducible Validation Steps](examples/nucleo-h563zi/VALIDATION.md)
1010
- **Case Study**: [Debugging STM32 Without Hardware](docs/case_study_stm32.md)
11-
- **Roadmap**: [Future of LabWired](ROADMAP.md)
1211

1312
## Features
14-
- **Multi-Architecture**: ARM Cortex-M (M0, M3, M4) and RISC-V (RV32I) support.
15-
- **Declarative Hardware**: Define chips and peripherals using YAML (No Rust coding required for basic IP).
16-
- **CI Test Runner**: Headless `labwired test` with JSON/JUnit reports for continuous verification.
17-
- **IDE Integration**: GDB and Debug Adapter Protocol (DAP) support for VS Code integration.
18-
- **High Performance**: Native Rust implementation achieving hundreds of thousands of IPS.
19-
20-
## Community & Governance
21-
We welcome contributions! Please see our community guides:
22-
- [Contributing](CONTRIBUTING.md)
23-
- [Code of Conduct](CODE_OF_CONDUCT.md)
24-
- [Security Policy](SECURITY.md)
13+
14+
- **Multi-Architecture**: ARM Cortex-M (M0, M3, M4) and RISC-V (RV32I) support
15+
- **Declarative Configuration**: YAML-based chip and peripheral definitions
16+
- **CI Test Runner**: Deterministic `labwired test` with JSON/JUnit outputs
17+
- **Debug Protocols**: GDB Remote Serial Protocol and Debug Adapter Protocol (DAP)
18+
- **High Performance**: Native Rust implementation with cycle-accurate simulation
19+
- **HAL Compatible**: Run binaries built with standard HALs (stm32f1xx-hal, etc.)
2520

2621
## Quick Start
27-
1. **Prerequisites**: [Rust 1.75+](https://rustup.rs/) and target support:
28-
```bash
29-
rustup target add thumbv6m-none-eabi thumbv7m-none-eabi riscv32i-unknown-none-elf
30-
```
31-
2. **Build**: `cargo build --release`
32-
3. **Run**: `cargo run -p labwired-cli -- --firmware <path/to/elf> --system system.yaml`
3322

34-
Full setup guide: [Getting Started](./docs/getting_started_firmware.md)
23+
### Prerequisites
24+
- Rust 1.75+ ([Install](https://rustup.rs/))
25+
- ARM/RISC-V targets:
26+
```bash
27+
rustup target add thumbv6m-none-eabi thumbv7m-none-eabi riscv32i-unknown-none-elf
28+
```
29+
30+
### Building
31+
```bash
32+
cargo build --release
33+
```
34+
35+
### Running a Simulation
36+
```bash
37+
cargo run -p labwired-cli -- --firmware path/to/firmware.elf --system system.yaml
38+
```
39+
40+
### Testing
41+
```bash
42+
cargo test --workspace --exclude firmware --exclude firmware-ci-fixture --exclude riscv-ci-fixture
43+
```
44+
45+
## CI Integration
46+
47+
Use `labwired test` for deterministic CI testing:
48+
49+
```bash
50+
labwired test --script tests/uart-ok.yaml --output-dir results
51+
```
52+
53+
See [`docs/ci_integration.md`](./docs/ci_integration.md) for details.
3554

3655
## Documentation
56+
3757
- [Architecture Overview](./docs/architecture.md)
3858
- [Architecture Guide](./docs/architecture_guide.md)
59+
- [Board Onboarding Playbook](./docs/board_onboarding_playbook.md) (config-first)
3960
- [CI Integration Guide](./docs/ci_integration.md)
4061
- [GDB Integration](./docs/gdb_integration.md)
4162
- [Release Strategy](./docs/release_strategy.md)
4263
- [VS Code Debugging](./docs/vscode_debugging.md)
4364

4465
## License
66+
4567
MIT

configs/chips/stm32h563.yaml

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# LabWired - Firmware Simulation Platform
2+
# Copyright (C) 2026 Andrii Shylenko
3+
#
4+
# This software is released under the MIT License.
5+
# See the LICENSE file in the project root for full license information.
6+
#
7+
# Source references:
8+
# - STM32H563xx memory map and IRQ values:
9+
# https://github.com/STMicroelectronics/cmsis-device-h5/blob/main/Include/stm32h563xx.h
10+
# - NUCLEO-H563ZI board BSP mapping (LEDs, COM port):
11+
# https://github.com/STMicroelectronics/stm32h5xx-nucleo-bsp/blob/main/stm32h5xx_nucleo.h
12+
13+
name: "stm32h563zi"
14+
arch: "arm"
15+
flash:
16+
base: 0x08000000
17+
size: "2MB"
18+
ram:
19+
base: 0x20000000
20+
size: "640KB"
21+
peripherals:
22+
- id: "rcc"
23+
type: "rcc"
24+
base_address: 0x44020C00
25+
size: "1KB"
26+
config:
27+
profile: "stm32v2"
28+
- id: "gpioa"
29+
type: "gpio"
30+
base_address: 0x42020000
31+
size: "1KB"
32+
config:
33+
profile: "stm32v2"
34+
- id: "gpiob"
35+
type: "gpio"
36+
base_address: 0x42020400
37+
size: "1KB"
38+
config:
39+
profile: "stm32v2"
40+
- id: "gpioc"
41+
type: "gpio"
42+
base_address: 0x42020800
43+
size: "1KB"
44+
config:
45+
profile: "stm32v2"
46+
- id: "gpiod"
47+
type: "gpio"
48+
base_address: 0x42020C00
49+
size: "1KB"
50+
config:
51+
profile: "stm32v2"
52+
- id: "gpioe"
53+
type: "gpio"
54+
base_address: 0x42021000
55+
size: "1KB"
56+
config:
57+
profile: "stm32v2"
58+
- id: "gpiof"
59+
type: "gpio"
60+
base_address: 0x42021400
61+
size: "1KB"
62+
config:
63+
profile: "stm32v2"
64+
- id: "gpiog"
65+
type: "gpio"
66+
base_address: 0x42021800
67+
size: "1KB"
68+
config:
69+
profile: "stm32v2"
70+
- id: "systick"
71+
type: "systick"
72+
base_address: 0xE000E010
73+
- id: "uart3"
74+
type: "uart"
75+
base_address: 0x40004800
76+
size: "1KB"
77+
irq: 60
78+
config:
79+
profile: "stm32v2"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# LabWired - NUCLEO-H563ZI Demo System Configuration
2+
name: "nucleo-h563zi-demo"
3+
chip: "../chips/stm32h563.yaml"
4+
external_devices: []
5+
board_io:
6+
- id: "led_green_pb0"
7+
kind: "led"
8+
peripheral: "gpiob"
9+
pin: 0
10+
signal: "output"
11+
active_high: true
12+
- id: "led_orange_pf4"
13+
kind: "led"
14+
peripheral: "gpiof"
15+
pin: 4
16+
signal: "output"
17+
active_high: true
18+
- id: "led_red_pg4"
19+
kind: "led"
20+
peripheral: "gpiog"
21+
pin: 4
22+
signal: "output"
23+
active_high: true
24+
- id: "button_blue_pc13"
25+
kind: "button"
26+
peripheral: "gpioc"
27+
pin: 13
28+
signal: "input"
29+
active_high: true

crates/cli/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1986,11 +1986,12 @@ fn write_config_error_outputs(
19861986
fn build_bus(system_path: Option<PathBuf>) -> anyhow::Result<labwired_core::bus::SystemBus> {
19871987
let bus = if let Some(sys_path) = system_path {
19881988
info!("Loading system manifest: {:?}", sys_path);
1989-
let manifest = labwired_config::SystemManifest::from_file(&sys_path)?;
1989+
let mut manifest = labwired_config::SystemManifest::from_file(&sys_path)?;
19901990
let chip_path = sys_path
19911991
.parent()
19921992
.unwrap_or_else(|| std::path::Path::new("."))
19931993
.join(&manifest.chip);
1994+
manifest.chip = chip_path.to_string_lossy().into_owned();
19941995
info!("Loading chip descriptor: {:?}", chip_path);
19951996
let chip = labwired_config::ChipDescriptor::from_file(&chip_path)?;
19961997
labwired_core::bus::SystemBus::from_config(&chip, &manifest)?

crates/codegen/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,10 @@ mod tests {
196196
description: Some("Word length".to_string()),
197197
},
198198
],
199+
side_effects: None,
199200
}],
200201
interrupts: vec![],
202+
timing: vec![],
201203
};
202204

203205
let result = PeripheralGenerator::generate(&peripheral).unwrap();
@@ -248,8 +250,10 @@ mod tests {
248250
description: None,
249251
},
250252
],
253+
side_effects: None,
251254
}],
252255
interrupts: vec![],
256+
timing: vec![],
253257
};
254258

255259
let result = PeripheralGenerator::generate(&peripheral).unwrap();
@@ -295,8 +299,10 @@ mod tests {
295299
description: None,
296300
},
297301
],
302+
side_effects: None,
298303
}],
299304
interrupts: vec![],
305+
timing: vec![],
300306
};
301307

302308
let result = PeripheralGenerator::generate(&peripheral).unwrap();
@@ -321,8 +327,10 @@ mod tests {
321327
reset_value: 0,
322328
description: None,
323329
fields: vec![],
330+
side_effects: None,
324331
}],
325332
interrupts: vec![],
333+
timing: vec![],
326334
};
327335

328336
let result = PeripheralGenerator::generate(&peripheral).unwrap();

crates/config/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ anyhow.workspace = true
1919
thiserror.workspace = true
2020
labwired-ir = { path = "../ir" }
2121
serde_json.workspace = true
22+
tracing.workspace = true

crates/config/src/lib.rs

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,37 @@ pub struct ExternalDevice {
5656
pub config: HashMap<String, serde_yaml::Value>,
5757
}
5858

59+
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)]
60+
#[serde(rename_all = "lowercase")]
61+
pub enum BoardIoKind {
62+
Led,
63+
Button,
64+
}
65+
66+
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default)]
67+
#[serde(rename_all = "lowercase")]
68+
pub enum BoardIoSignal {
69+
#[default]
70+
Output,
71+
Input,
72+
}
73+
74+
fn default_true() -> bool {
75+
true
76+
}
77+
78+
#[derive(Debug, Serialize, Deserialize, Clone)]
79+
pub struct BoardIoBinding {
80+
pub id: String,
81+
pub kind: BoardIoKind,
82+
pub peripheral: String,
83+
pub pin: u8,
84+
#[serde(default)]
85+
pub signal: BoardIoSignal,
86+
#[serde(default = "default_true")]
87+
pub active_high: bool,
88+
}
89+
5990
#[derive(Debug, Serialize, Deserialize, Clone)]
6091
pub struct SystemManifest {
6192
pub name: String,
@@ -64,6 +95,8 @@ pub struct SystemManifest {
6495
pub memory_overrides: HashMap<String, String>,
6596
#[serde(default)]
6697
pub external_devices: Vec<ExternalDevice>,
98+
#[serde(default)]
99+
pub board_io: Vec<BoardIoBinding>,
67100
}
68101

69102
impl ChipDescriptor {
@@ -239,15 +272,59 @@ impl From<labwired_ir::IrPeripheral> for PeripheralDescriptor {
239272
description: f.description,
240273
})
241274
.collect(),
242-
side_effects: None, // IR doesn't explicitly model side effects yet
275+
side_effects: r.side_effects.map(|se| SideEffectsDescriptor {
276+
read_action: se.read_action.and_then(|s| match s.as_str() {
277+
"clear" => Some(ReadAction::Clear),
278+
"none" => Some(ReadAction::None),
279+
_ => None,
280+
}),
281+
write_action: se.write_action.and_then(|s| match s.as_str() {
282+
"one_to_clear" | "oneToClear" | "w1c" => {
283+
Some(WriteAction::WriteOneToClear)
284+
}
285+
"zero_to_clear" | "zeroToClear" | "w0c" => {
286+
Some(WriteAction::WriteZeroToClear)
287+
}
288+
"none" => Some(WriteAction::None),
289+
_ => None,
290+
}),
291+
on_read: None,
292+
on_write: None,
293+
}),
243294
})
244295
.collect(),
245296
interrupts: if interrupts.is_empty() {
246297
None
247298
} else {
248299
Some(interrupts)
249300
},
250-
timing: None,
301+
timing: if ir.timing.is_empty() {
302+
None
303+
} else {
304+
let mut timing = Vec::new();
305+
for t in ir.timing {
306+
// Try to convert JSON trigger/action to enums
307+
let trigger: Result<TimingTrigger, _> = serde_json::from_value(t.trigger);
308+
let action: Result<TimingAction, _> = serde_json::from_value(t.action);
309+
310+
if let (Ok(trig), Ok(act)) = (trigger, action) {
311+
timing.push(TimingDescriptor {
312+
id: t.id,
313+
trigger: trig,
314+
delay_cycles: t.delay_cycles,
315+
action: act,
316+
interrupt: t.interrupt,
317+
});
318+
} else {
319+
tracing::warn!("Failed to convert IR timing hook '{}' to config", t.id);
320+
}
321+
}
322+
if timing.is_empty() {
323+
None
324+
} else {
325+
Some(timing)
326+
}
327+
},
251328
}
252329
}
253330
}

0 commit comments

Comments
 (0)