rigless 0.0.2

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; needs the rigless-sidecar package from rigless-labs/qemu
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: Apache-2.0
//! A machine as its module describes it: the MCUs on the board, each with its SoC, its engine, and its interrupts.
//!
//! A machine module hands the runtime a `Machine`, in `rigless::machine`: its name and its `Target`s, one per MCU on the board, each with its name, its `Soc` with the machine's models bound, the `SidecarCommand` its engine is started as, and the function that turns one of that SoC's interrupt numbers into the engine's `InterruptLine`. Most machines carry one target; the type is a list because a board with a second MCU is a second engine, not a second session. `machine::all()` lists every machine this build carries by the name `rigless run --machine` matches.
//!
//! One module per machine returns a [`Machine`], and [`all`] lists every machine this build
//! carries by the name `rigless run --machine` matches. A core family gets a module of its own,
//! [`armv7m`] first, mirroring the machine the sidecar carries for it.

use crate::sidecar::{InterruptLine, SidecarCommand};
use crate::soc::{Error, Soc};

pub mod armv7m;
#[cfg(feature = "stm32f427")]
pub mod pixracer;

/// A machine's constructor: the SoC built and the sidecar configured, or the SoC's assembly error.
pub type Constructor = fn() -> Result<Machine, Error>;

/// Every machine this build carries, by the name `rigless run --machine` matches, with its constructor.
#[must_use]
pub fn all() -> &'static [(&'static str, Constructor)] {
    const MACHINES: &[(&str, Constructor)] = &[
        #[cfg(feature = "stm32f427")]
        (pixracer::NAME, pixracer::machine),
    ];
    MACHINES
}

/// One MCU of a board: its SoC, how its engine is started, and how its interrupts route.
#[derive(Debug)]
pub struct Target {
    /// The name the command line matches and the output labels this target with.
    pub name: &'static str,
    /// The SoC, built with the machine's models bound.
    pub soc: Soc,
    /// What this target's engine is started as.
    pub sidecar: SidecarCommand,
    /// The engine's line for one of the SoC's interrupt numbers, or `None` when it has no line for it.
    pub interrupt_line: fn(u16) -> Option<InterruptLine>,
}

/// One machine, ready for a session: its name and the targets on it.
///
/// A session steps the targets in the order the machine lists them, so the order is the machine's to
/// choose and is the same on every run.
#[derive(Debug)]
pub struct Machine {
    /// The name `rigless run --machine` matches.
    pub name: &'static str,
    /// The MCUs on the board, at least one.
    pub targets: Vec<Target>,
}

impl Machine {
    /// The index of the target called `name`.
    #[must_use]
    pub fn target(&self, name: &str) -> Option<usize> {
        self.targets.iter().position(|target| target.name == name)
    }

    /// Every target's name, in the machine's order.
    pub fn target_names(&self) -> impl Iterator<Item = &'static str> {
        self.targets.iter().map(|target| target.name)
    }
}