rigless 0.2.0

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, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The chip end of a serial peripheral interface bus.

use std::any::Any;

use crate::chip::Timeline;
use crate::snapshot::Store;
use crate::time::EventId;

/// An integrated circuit on a serial interface's wires: what a board hangs off an MCU.
///
/// The bus is full duplex. A word crosses each way at the same time, so a peer answers in the same
/// call it's given. Both words are as they travel, after the controller's bit order has had its
/// say, because that's what's on the wire. The controller calls this and never learns what's behind
/// it. A chip implements it and never learns there's a controller. What a chip measures is set
/// through the chip's own methods, which the board that hung it reaches through the interface's
/// `chip_mut`. Nothing of the world crosses here. A snapshot keeps a chip through its own
/// [`Store`].
pub trait Peer: Store + std::fmt::Debug + Send + Any {
    /// The word this peer puts on `MISO` while `mosi` crosses on the controller's line, both in
    /// frames of `bits` bits, the controller's data frame format. A bit past the frame is zero both
    /// ways.
    ///
    /// `context` is the controller's, narrowed to the instant of the word and its event queue, so a
    /// chip that needs the time to answer can ask for it, or schedule an event it hears at
    /// [`Peer::on_event`].
    /// [`Card`](crate::chip::sd::Card) has the same shape on the card interface.
    fn transfer(&mut self, mosi: u32, bits: u8, context: &mut Timeline<'_, '_>) -> u32;

    /// Its chip select reaching the level that selects it. Nothing, for a peer with no state to begin.
    fn select(&mut self, _context: &mut Timeline<'_, '_>) {}

    /// Its chip select leaving that level, which for most chips ends whatever command was under way.
    fn deselect(&mut self, _context: &mut Timeline<'_, '_>) {}

    /// An event this chip scheduled through the [`Timeline`] it was handed, at its instant. The
    /// controller hands every event of its instance to every chip it carries, after its own, so a
    /// chip ignores an id it didn't ask for. A silenced chip hears its events all the same, since
    /// what it does with time goes on while it's cut off. Nothing, for a chip that schedules none.
    fn on_event(&mut self, _event: EventId, _context: &mut Timeline<'_, '_>) {}

    /// The part this chip hangs on restarted at the instant `context` gives, and every event the
    /// chip scheduled through its controller went with the reset, since a chip keeps running
    /// through its part's reset. A chip that keeps time schedules again here. Nothing, for a chip
    /// that schedules none.
    fn restarted(&mut self, _context: &mut Timeline<'_, '_>) {}

    /// Every word this peer has been sent, oldest first, or nothing where it keeps no log.
    ///
    /// This is the far end's own record. For a stand-in with no state of its own, it's the only
    /// thing that can say a stream arrived. A model that never reaches its peer would fill what the
    /// controller believes it sent just as happily. A chip answers from its own state instead, and
    /// that's its record.
    fn received(&self) -> &[u32] {
        &[]
    }
}