rigless 0.0.5

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model, and unit of virtual time in Rust; a fork of QEMU is built in and executes the instructions
Documentation
The STM32F427 as a Rigless SoC: the vendor's own register description turned into Rust a person can implement peripherals against.

## What is here

| Path | Contents |
|---|---|
| `../stm32f427.rs` | The index: what this module is made of, and the surface a board uses — `Stm32f427`, the MCU itself, plus `CPU`, `MEMORY`, `PORTS`, `SERIAL_INTERFACES`, and `builder()` for a test that wants the tables alone. The tables a board needs less often stay where they are declared, `peripherals::TIMERS` and `peripherals::CONTROLLERS` among them. The memory map is private to the module |
| `STM32F427.svd` | ST's CMSIS-SVD file for the SoC, committed unchanged |
| `generated.rs`, `generated/` | What `rigless svd generate` emits from it: for each register layout the `Register` enum, a constant per field, and the table; never edited by hand |
| `memory_map.rs` | What the SVD cannot say: the memory map from RM0090, every correction where the file disagrees with the manual, with its section named, and what joins one instance to another — the clock enable each answers behind (section 2.1.11), the bit that unlocks the backup domain (section 5.5.1), which controllers read the requests the peripherals drive (tables 43 and 44), that every serial interface reads them too, because a chip select is a port pin's level arriving as that port's request line (ADR-0023), and which output of the clock tree each instance that keeps time runs on (section 6.2 figure 16, ADR-0025) |
| `peripherals.rs`, `peripherals/` | The models of release 0.1, one module per peripheral and each written against one generated layout, each opening with a table of what it covers, what it corrects, and which erratum of ES0206 it models or why it does not: `gpio.rs`, the GPIO port of RM0090 chapter 8; `pwr.rs`, the power controller of sections 5.5.1 and 5.5.2, which are the STM32F42xxx's and not the STM32F405's in 5.4; `rcc.rs`, the reset and clock controller of chapter 6, which is likewise this part's and not chapter 7; `spi.rs`, the serial peripheral interface of chapter 28, which is the SPI half of a register block the I2S shares; `tim.rs`, the general-purpose timer of chapter 18; `dma.rs`, the direct memory access controller of chapter 10, one model for both of them; `sdio.rs`, the card interface of chapter 31, whose far end is a chip rather than a peripheral and lives under `src/chip`; `usart.rs`, the serial ports of chapter 30, one model answering for all eight from that chapter's two tables; `adc.rs`, the analogue-to-digital converter of chapter 13, answering for all three of them and for the common block they share; `otg.rs`, the reset register of the USB on-the-go full-speed core of chapter 34 and nothing else of it, which is a deliberate partial and opens by saying so; and `rtc.rs`, the backup domain of chapter 26 as far as the reset that clears it, which is a partial for the same reason and says so. One more model the part binds is not here and not the vendor's: the processor's cycle counter, at `src/cpu/armv7m.rs`, which belongs to the core family rather than to this part (ADR-0024) |

## The SVD

| | |
|---|---|
| Origin | ST, as published in the Arm STM32F4xx device family pack, tag `v3.1.1`, `CMSIS/SVD/STM32F427.svd` at <https://github.com/Open-CMSIS-Pack/STM32F4xx_DFP> |
| Version | SVD version 1.7, copyright 2024 ST |
| License | Apache-2.0, stated in the file's own header |
| Corrections | Fifteen, all in `memory_map.rs`: the file names no interrupt count, so the CPU gets the 91 lines of RM0090 rev 22 table 63; it gets eleven instances' interrupt lines wrong, which `INTERRUPTS` corrects against table 63 — nine of the lines in that table are shared by two peripherals and the file gives each to one of the pair and leaves the other empty, picking inconsistently; SAI1 (line 87) and DMA2D (line 90) it omits outright; and RNG it gives line 81, which is the FPU's, where line 80 `HASH_RNG` is the generator's and the hash processor's both. Table 62 is the STM32F405's vector table and 63 is this part's, which is the same trap chapter 6 sets against chapter 7; it carries no write rules, so every timer's status register gets the manual's `rc_w0`, cleared by writing zero; and it gets seven reset values wrong. `RCC_PLLI2SCFGR` it gives as `0x2000_3000` where section 6.3.23 gives `0x2400_3000`, dropping a bit of `PLLI2SQ`, which section 6.3.24 confirms for the identical `PLLSAICFGR` the file gets right. The three low-power clock enable registers of sections 6.3.15, 6.3.18, and 6.3.19 come out of reset with every `LPEN` bit set, and the file leaves clear exactly the peripherals the STM32F42xxx adds over the STM32F405: DMA2D, SRAM3, GPIOK, and GPIOJ; UART8 and UART7; and LTDC, SAI1, SPI6, SPI5, and SPI4. Section 6.3.19 prints its own value with a typo, `0x0x0477 7F33`, which its bit map settles. `DBGMCU_IDCODE` it gives as `0x1000_6411`, the STM32F405's device identifier with a reserved nibble set, where section 38.6.1 gives this family `0x419`; the revision that section leaves product-dependent, and the correction picks `0x1007`, revision 1, with its reason. And `USART_SR` it gives as `0x00C0_0000` on both U(S)ART layouts, which is section 30.6.1's printed `0x000C0 00C0` read with its extra digit intact; that section's own bit map reserves bits 31:10 and puts `TXE` at 7 and `TC` at 6, so the value is `0x0000_00C0` and a firmware's first polled write waits on a flag that is already standing. And `SPI_I2SPR` it gives as `0x000A` where section 28.5.9 gives `0x0002`, on all eight instances of that layout. And the eleven ports are pointed at the one layout they share and each given the reset values sections 8.4.1, 8.4.3, and 8.4.4 print for it, thirty-three in all, written out for every port so that none is right by inheriting port A's; among them `GPIOA_OSPEEDR`, which section 8.4.3 gives as `0x0C00_0000` for the debug pins' very high speed and the file gives as zero. And every timer's `TIMx_ARR` it gives as zero where section 18.4.12 and table 100 give all ones, in the width the instance counts in — which is the one value 18.4.12 says blocks the counter, so a firmware that starts a timer before programming its reload counts nowhere; the four of chapter 18 the part binds are corrected, and RL-117 carries the other ten. And `OTG_FS_GRSTCTL` it gives as `0x2000_0000`, a bit section 34.16.2 reserves, where that section gives `0x8000 0000`: bit 31 is `AHBIDL`, the AHB master idle flag, and a USB driver waits on it before it does anything else, so the vendor's value is a boot that never starts. Every other reset value checked against RM0090 rev 22 matched, `RCC_CSR`'s `0x0E00_0000` included |

The file needs no special treatment from the generator: no clusters, no arrays, every register 32 bits wide, and 33 of its 83 peripherals derive their layout from a sibling. Interrupt lines are not unique: RM0090 rev 22 table 63 gives position 24 to `TIM1_BRK_TIM9` and position 80 to `HASH_RNG`, one vector for two peripherals each. ST's file does not say so, and gets both wrong in its own way — it leaves TIM9 no interrupt at all, and gives RNG position 81, which table 63 assigns to the FPU. Thirty-four of its instances carry no interrupt where the manual gives several of them one. Eleven are corrected in `INTERRUPTS`, against table 63 rather than against the file. A correction is a struct update in `memory_map.rs`, never an edit to the file or the generated tables.

To bump the file, replace it, regenerate, and review the diff of the tables:

```text
rigless svd generate src/soc/stm32f427
```

CI runs the same command and fails when the committed tables differ from the result.

## Writing a model against it

```rust
use rigless::device::{Device, DeviceContext, RegisterFile};
use rigless::time::VirtualNanos;
use rigless::soc::Width;
use rigless::soc::stm32f427::generated::spi1::{cr1, Register};

#[derive(Default)]
struct Spi { pub enabled: bool }
impl Device for Spi {
    type Register = Register;
    fn write(&mut self, register: Register, value: u64, _registers: &mut RegisterFile, _context: &mut DeviceContext<'_>) {
        if register == Register::Cr1 { self.enabled = cr1::SPE.is_set(value); }
    }
}

let mut soc = rigless::mcu::Single::new(
    rigless::soc::stm32f427::builder(),
    "SPI1",
    Spi::default(),
)?;
rigless::mcu::Mcu::write(&mut soc, VirtualNanos(0), rigless::soc::stm32f427::generated::SPI1.base, Width::W32, 1 << 6)?;
assert!(soc.model.enabled);
# Ok::<(), Box<dyn std::error::Error>>(())
```

`Single` puts one model on a bus of its own, and checks it was written for that instance's layout. That is what a test wants when it puts a model under the microscope; the model is then `soc.model`, a field. `Stm32f427::new()` is the whole MCU, with a model on every instance this part has one for, each its own field. An instance the part has no model for answers from its register file alone.

How far a model reaches is therefore how far the vendor's file lets it. `spi.rs` reaches six of the eight instances that carry its layout — `I2S2ext` and `I2S3ext` are the two section 28.4.2 gives another job, and the part answers for neither until RL-116. `gpio.rs` reaches all eleven ports: ST's file gives three of them a layout of its own because sections 8.4.1, 8.4.3, and 8.4.4 give ports A and B reset values of their own, and a reset value belongs to the instance, so the memory map points every port at the one layout and states each one's values as corrections (ADR-0022). `dma.rs` reaches both controllers, which are one layout and differ only in what the part wires to them. `tim.rs` reaches all four timers of chapter 18 across two layouts, ADR-0022's rule a second time: section 18.2 gives TIM2 and TIM5 a thirty-two-bit counter where TIM3 and TIM4 have sixteen, and table 100 gives the wide pair an option register the narrow pair has not, so the counter's width is a property of the instance and each pair answers from its own table.

`otg.rs` reaches one instance and one of its nineteen registers, which is a different thing entirely. It is a deliberate partial with its own row in its second table for every register it leaves out; RL-162 is the epic that finishes the job and makes the interface a device on the developer's own machine. `rtc.rs` is the other partial: it reaches one instance and answers for the twenty backup registers of section 26.6.20 and the reset that clears them, and the other nineteen registers of chapter 26 are rows in its second table. Binding a model claims the whole instance either way, so both are walked whole by the bench and both account for every register they do not answer for.

`src/cpu/armv7m.rs` is the one model the part binds that is not under `peripherals/`. The cycle counter is at the same address on every ARMv7-M core, so it belongs to the family and not to this part, and neither ST manual describes it — ARM DDI 0403E.b and the Cortex-M4 technical reference manual do. It reaches the part because `memory_map.rs` places the instance beside the vendor's, and it reaches the runtime at all because the machine description claims a window of the processor's own peripheral bus for it (ADR-0024).