Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@ jobs:
CXX: g++-14
steps:
- uses: actions/checkout@v4
with:
# STM32CubeF1 submodule is needed by hal_uart/hal_blink firmware, which
# the E2E.HalUartTransmit test loads. Without it that test doesn't
# configure and the test-count floor gate falls short by one.
submodules: recursive

- name: Install GCC-14
- name: Install GCC-14 + ARM toolchain
run: |
sudo apt-get update
sudo apt-get install -y g++-14
# gcc-arm-none-eabi: builds the Cortex-M3 test firmware (hello/blink/
# systick) so test_e2e/test_cli/INTROSPECTION_HELLO_ELF configure and
# the test-count floor gate reaches its baseline on this workflow.
sudo apt-get install -y g++-14 gcc-arm-none-eabi binutils-arm-none-eabi

- name: Cache GoogleTest
uses: actions/cache@v4
Expand Down
60 changes: 60 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Changelog

All notable changes to micro-forge are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.0] - 2026-07-07

### Added
- **CLI runner** — `micro-forge run` with `--snapshot-json` (5-region JSON state
export: CPU / fault / run / peripherals / events), `--trace-mmio`, `--max-steps`,
and `--base` for raw binaries.
- **`micro-forge dump-mem`** subcommand — loads firmware and dumps a memory window.
- **Structured introspection API** (`read_introspection`) — the single source of
truth consumed by both the CLI JSON serializer and the GUI dashboard.
- **Cortex-M3 correctness foundation** — interrupt preemption and nesting,
MSP/PSP dual-stack, PRIGROUP priority grouping, NVIC lazy priority cache,
bit-band alias regions (SRAM `0x22……` + peripheral `0x42……`).
- **Full Thumb-2 instruction coverage** for the ARMv7-M base profile; real
Keil/MDK-ARM STM32F103 HAL firmware boots end-to-end.
- **Three end-to-end interrupt paths** — Timer UIF, EXTI GPIO edge, USART RXNE
— all wired through a common `raise_irq` channel.
- **Event-bus hooks subsystem** — typed `Signal<E>`, non-blocking `RingSink<E>`,
`EventBus`, emitting cycle-timestamped `GpioEdge` / `UartByte` events.
- **Qt6 GUI dashboard** (opt-in, `-DMICRO_FORGE_GUI=ON`) — step / run / reset,
visual CPU registers and GPIO output.
- **Coverage pipeline** (`-DMICRO_FORGE_COVERAGE=ON` + `gcovr`) — lines 81.6% /
branches 61.4% / functions 90.2%.
- **QEMU differential oracle** (`scripts/qemu_cortex_m3_oracle.sh`) — checks
SDIV/UDIV/ADC/SBC + xPSR semantics against real QEMU.
- **Test-count floor gate** (`scripts/check_test_count.sh`) — pins the baseline
so refactors can't silently drop tests.
- **Install rules** — `cmake --install` deploys the library, CLI, and headers.
- Firmware corpus: 12 AC6 `.axf` images (3 examples × `-O0/-O2/-Oz`) for
optimization-level regression.

### Changed
- **Project layout cleanup** — GUI and CLI moved to top-level consumer
directories (`gui/`, `cli/`); introspection split into its own library module
(`src/introspection/` + `include/introspection/`, namespace
`micro_forge::introspection`); `src/chips/stm32f1/` split into `periph/`
(devices) and `soc/` (assembly); all headers unified to `.hpp`; the two
generic `def.h` renamed to `cortex_m3_defs.hpp` and `elf_defs.hpp`.
- Test count 217 → 353 product tests (+ 2 meta-guards).

### Fixed
- Thumb-2 decoding gaps that blocked real HAL firmware (`LDR.W` PC-literal,
`BLX Rm` not setting LR, `ADR.W`, `SUB.W/SUBW` plain imm12, load/store
`hw1[7]` addressing, shifted-reg `CMP/CMN` clobbering PC, `B.N` self-loop).
- `SDIV/0` returns 0 (initial implementation returned INT_MIN; corrected against
the QEMU oracle); `ADC/SBC` C/V carry flags; 32-bit `ADC.W`/`SBC.W`
shifted-register operands; INT_MIN / -1 saturation guards.

## [0.1.0] - 2026-05-20
- Initial release: Cortex-M3 Thumb/Thumb-2 decoder, FlatMemory + Bus + Region
address routing, NVIC / SysTick / SCB, RCC / GPIO (A/B/C) / USART1 / TIM2 /
AFIO / FLASH, ELF + BIN loaders, 217 GoogleTest cases + 4 end-to-end
examples (hello / gpio_blink / systick / HAL UART).
68 changes: 43 additions & 25 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25)

project(micro-forge
VERSION 0.1.0
VERSION 0.2.0
LANGUAGES CXX
)

Expand All @@ -13,6 +13,7 @@ set(MICRO_FORGE_ARCH_BIT 32 CACHE STRING "Target architecture bit width (32 or 6
set(ARCH_BIT ${MICRO_FORGE_ARCH_BIT})

option(MICRO_FORGE_COVERAGE "Instrument micro_forge with gcov for coverage (COVERAGE-METHODOLOGY §2; build in a dedicated dir, e.g. -B build-cov -DMICRO_FORGE_COVERAGE=ON)" OFF)
option(MICRO_FORGE_GUI "Build the Qt6 dashboard (milestone 04); lives in gui/ and never enters the core library" OFF)

add_compile_options(-Wall -Wextra -Werror)

Expand All @@ -36,17 +37,17 @@ set(MICRO_FORGE_SOURCES
src/arch/arm/cortex_m3/cortex_m3_reset.cpp
src/arch/toy/cpu.cpp
src/chips/machine.cpp
src/chips/stm32f1/interrupt_config.cpp
src/chips/stm32f1/memory_bus.cpp
src/chips/stm32f1/peripheral_config.cpp
src/chips/stm32f1/stm32f103_soc.cpp
src/chips/stm32f1/stm32f1_afio.cpp
src/chips/stm32f1/stm32f1_exti.cpp
src/chips/stm32f1/stm32f1_flash.cpp
src/chips/stm32f1/stm32f1_gpio.cpp
src/chips/stm32f1/stm32f1_rcc.cpp
src/chips/stm32f1/stm32f1_timer.cpp
src/chips/stm32f1/stm32f1_usart.cpp
src/chips/stm32f1/soc/interrupt_config.cpp
src/chips/stm32f1/soc/memory_bus.cpp
src/chips/stm32f1/soc/peripheral_config.cpp
src/chips/stm32f1/soc/stm32f103_soc.cpp
src/chips/stm32f1/periph/stm32f1_afio.cpp
src/chips/stm32f1/periph/stm32f1_exti.cpp
src/chips/stm32f1/periph/stm32f1_flash.cpp
src/chips/stm32f1/periph/stm32f1_gpio.cpp
src/chips/stm32f1/periph/stm32f1_rcc.cpp
src/chips/stm32f1/periph/stm32f1_timer.cpp
src/chips/stm32f1/periph/stm32f1_usart.cpp
src/cpu/cpu.cpp
src/loader/binary_loader.cpp
src/loader/elf_loader.cpp
Expand All @@ -61,7 +62,7 @@ set(MICRO_FORGE_SOURCES
src/tools/memory_dump.cpp
src/tools/mmio_trace.cpp
src/util/logger.cpp
src/cli/introspection.cpp
src/introspection/introspection.cpp
)

add_library(micro_forge STATIC ${MICRO_FORGE_SOURCES})
Expand All @@ -81,24 +82,41 @@ if(MICRO_FORGE_COVERAGE)
endif()

# Unified CLI runner.
add_executable(micro-forge src/cli/main.cpp src/cli/snapshot.cpp)
add_executable(micro-forge cli/main.cpp cli/snapshot.cpp)
# CLI is a top-level consumer (lives in cli/); snapshot.hpp sits next to its
# .cpp there, so the include root is the repo top, not include/ alone.
target_include_directories(micro-forge PRIVATE ${CMAKE_SOURCE_DIR})
target_link_libraries(micro-forge PRIVATE micro_forge)

enable_testing()
add_subdirectory(examples)
add_subdirectory(test)
add_subdirectory(bench)

# ── GUI dashboard (milestone 04; see DIRECTIVES §E) ──
# Opt-in: Qt6 is a heavy runtime dep, so the GUI is a separate target that
# defaults OFF and never enters the core `micro_forge` library. The core and
# CLI stay zero-dep; only micro-forge-gui links Qt6::Widgets. Build in a
# dedicated dir, e.g. -B build-gui -DMICRO_FORGE_GUI=ON.
option(MICRO_FORGE_GUI "Build the Qt6 dashboard (milestone 04)" OFF)
# GUI dashboard lives in gui/ as a top-level consumer (milestone 04; DIRECTIVES
# §E). Built only when MICRO_FORGE_GUI=ON — the option is declared above next to
# MICRO_FORGE_COVERAGE. The gui/ subdirectory does all the Qt wiring.
if(MICRO_FORGE_GUI)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
set(CMAKE_AUTOMOC ON)
add_executable(micro-forge-gui src/gui/main.cpp src/gui/main_window.cpp)
target_include_directories(micro-forge-gui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
target_link_libraries(micro-forge-gui PRIVATE micro_forge Qt6::Widgets)
add_subdirectory(gui)
endif()

# ── Install rules (release engineering) ──
# The core static library, CLI runner, and public headers install cleanly via
# `cmake --install`. GUI is installed only when built. No CPack packaging —
# this project targets teaching/portfolio use, not distribution packages.
include(GNUInstallDirs)
install(TARGETS micro_forge
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
install(TARGETS micro-forge
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.hpp")
# Generated header (configure_file output in the binary dir, not under
# include/) — needed so an installed micro_forge can actually be linked
# against, since autogen/arch_details.hpp is a PUBLIC include of the library.
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/autogen/arch_details.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/autogen)
if(MICRO_FORGE_GUI)
install(TARGETS micro-forge-gui
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
67 changes: 50 additions & 17 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ An ARM Cortex-M3 (STM32F103) simulator written in modern C++23 — run and test
- **Peripheral Suite** — NVIC, SCB, SysTick, RCC, GPIO (A/B/C), USART1, TIM2, AFIO, FLASH
- **Firmware Loading** — ELF loader and raw binary loader
- **CLI & Diagnostics** — `micro-forge run` drives firmware with MMIO trace, memory dump, fault recording with context, and `--snapshot-json` JSON state export (CPU / fault / peripherals / recent MMIO)
- **GUI Dashboard** — Optional Qt6 debug dashboard (`-DMICRO_FORGE_GUI=ON`): step / run / reset with visual CPU registers and GPIO output; the simulator runs synchronously on the Qt main thread, preserving single-threaded determinism
- **Event Bus & Hooks** — Typed observer subsystem (`Signal<E>`, non-blocking `RingSink<E>`, `EventBus`) emitting `GpioEdge` / `UartByte` events with CPU-cycle timestamps — single-threaded and deterministic, drained offline rather than via a thread pool
- **Real-Firmware Proven** — Boots **real Keil/MDK-ARM STM32F103 HAL firmware** end-to-end: reset → `__main` scatter-load → `main` → `HAL_Init` → `SystemClock_Config` (PLL switch) → `MX_GPIO_Init` → `HAL_GPIO_WritePin(PA1)` → `while(1)` with SysTick IRQ
- **Well Tested** — 244 test cases across 19 test files with GoogleTest
- **Well Tested** — 353 test cases across 19 test files with GoogleTest

## Quick Start

Expand Down Expand Up @@ -69,24 +70,42 @@ ctest --output-on-failure -j$(nproc)
> Want to observe pin toggles live? `examples/hook_demo` subscribes to the
> event bus and captures PA1 toggles emitted by the GPIO peripheral.

### GUI Dashboard (optional)

The default build omits the GUI (core library and CLI stay Qt-free). For the
visual debug dashboard:

```bash
cmake -B build-gui -DMICRO_FORGE_GUI=ON
cmake --build build-gui -j$(nproc)
./build-gui/gui/micro-forge-gui firmware.axf # step / run / reset, inspect registers & GPIO
```

The GUI is a pure consumer of the core library, reusing the same
`read_introspection` data; the simulator runs synchronously on the Qt main
thread for deterministic replay.

## Project Structure

```
include/
core/ Base types and interfaces (IPeripheral, types)
cpu/ CPU framework (ICore, RegisterFile, ToyCore)
memory/ Memory system (FlatMemory, Bus, Region, bit-band aliases)
periph/ Peripheral abstractions (Device, Gpio, SerialPort, Timer)
hooks/ Event bus (Signal, RingSink, EventBus, GpioEdge/UartByte)
cli/ Command-line interface (snapshot, main)
util/ WeakPtr lifecycle management
src/ Implementation files
test/ GoogleTest suite (244 tests)
examples/ Firmware examples (bare-metal + HAL + Keil/MDK + hook demos)
core/ Base types and interfaces (IPeripheral, types)
cpu/ CPU framework (ICore, RegisterFile, FaultRecord)
memory/ Memory system (FlatMemory, Bus, Region, bit-band aliases)
periph/ Peripheral abstractions (Device, Gpio, SerialPort, Timer)
hooks/ Event bus (Signal, RingSink, EventBus, GpioEdge/UartByte)
introspection/ Structured state export (read_introspection — single source of truth shared by CLI/GUI)
util/ WeakPtr lifecycle management
src/ Core library implementation (arch / chips / cpu / memory / periph / sim / loader / tools / util)
cli/ CLI executable (main, snapshot) — top-level consumer
gui/ Qt6 dashboard executable (main, main_window) — top-level consumer, opt-in
test/ GoogleTest suite (353 tests)
examples/ Firmware examples (bare-metal + HAL + Keil/MDK + hook demos)
document/
milestones/ Version roadmap (v0.1.0 → v1.0.0)
notes/ Design notes by topic
scripts/ Utility scripts
milestones/ Version roadmap (v0.1.0 → v1.0.0)
notes/ Design notes by topic
scripts/ Utility scripts (test-count floor / QEMU oracle / venv gateway)
bench/ Performance benchmarks (with baseline regression gate)
```

## Examples
Expand All @@ -101,13 +120,27 @@ scripts/ Utility scripts
| `F103` | Real Keil/MDK-ARM STM32F103 HAL GPIO firmware — the end-to-end regression carrier (`examples/F103/MDK-ARM/F103/F103.axf`) |
| `hook_demo` | Subscribes to the event bus to capture PA1 toggles live |

## Development Tooling

- **Coverage** — Build gcov-instrumented binaries with `-DMICRO_FORGE_COVERAGE=ON` and generate reports via `gcovr` (currently lines 81.6% / branches 61.4%).
- **Benchmarks** — `bench/` with a baseline regression gate; the perf campaign lifted GPIO / USART / TIM instruction throughput by 51-57%.
- **QEMU differential oracle** — `scripts/qemu_cortex_m3_oracle.sh` checks instruction semantics (SDIV / UDIV / ADC / SBC + xPSR) against real QEMU, byte-for-byte.
- **Test-count floor** — `scripts/check_test_count.sh` pins the baseline so refactors can't silently drop tests.

## Roadmap

See [document/milestones/](document/milestones/) for the full version roadmap.
See [document/milestones/](document/milestones/) for the full version roadmap; the v0.2.0 [support matrix](document/SUPPORT.md) and [CHANGELOG](CHANGELOG.md) cover what's supported and what changed.

Already landed:

Already landed: the **`micro-forge run` CLI** with `--snapshot-json` state export and MMIO tracing, the **Cortex-M3 correctness foundation** (interrupt preemption, MSP/PSP dual-stack, PRIGROUP, NVIC priority cache, bit-band aliases), the **Thumb-2 instruction-coverage fix** that lets real Keil/MDK-ARM HAL firmware boot end-to-end, and the **event-bus hooks subsystem**.
- **CLI & observability** — `micro-forge run` + `--snapshot-json` + `--trace-mmio`; structured introspection is the single source of truth shared by CLI and GUI
- **Cortex-M3 correctness foundation** — interrupt preemption and nesting, MSP/PSP dual-stack, PRIGROUP priority grouping, NVIC priority cache, bit-band alias regions
- **Full Thumb-2 coverage** — real Keil/MDK-ARM HAL firmware boots end-to-end
- **Three interrupt paths end-to-end** — Timer UIF / EXTI GPIO edge / USART RXNE, all via the common `raise_irq` channel
- **Event-bus hooks** — typed observers with CPU-cycle timestamps
- **Qt6 GUI dashboard** — step / run / reset, visual CPU registers and GPIO (opt-in)

Key milestones ahead: full exception semantics tail-chaining, EXTI / Timer-IRQ / USART-RX-IRQ end-to-end paths, DMA / SPI / FLASH depth, extended HAL peripheral coverage, and a GUI debug dashboard.
Key milestones ahead: DMA / SPI / FLASH peripheral depth, extended HAL peripheral coverage, and more GUI debug panels (full fault detail, serial terminal, interrupt-system visualization).

## License

Expand Down
Loading
Loading