rigless 0.3.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 std::collections::VecDeque;

use crate::chip::Timeline;
use crate::snapshot::{Store, 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] {
        &[]
    }
}

/// A peer that answers a known sequence, repeats its last word once that drains, and records every
/// word it was sent.
///
/// It's what a bench hangs on an interface with no chip model for it yet. It answers something
/// known and **different** from what it was given, so a controller shifting its own transmission
/// back is caught rather than passed. A loopback wouldn't catch it. And what it records is what
/// says the far end of the bus got the stream, rather than the controller's own record of what it
/// believes it sent.
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Pattern {
    answers: VecDeque<u32>,
    held: u32,
    received: Vec<u32>,
}

impl Pattern {
    /// A peer answering `words`, oldest first.
    #[must_use]
    pub fn answering(words: impl IntoIterator<Item = u32>) -> Self {
        Self {
            answers: words.into_iter().collect(),
            held: 0,
            received: Vec::new(),
        }
    }
}

store!(Pattern);

impl Peer for Pattern {
    fn received(&self) -> &[u32] {
        &self.received
    }

    fn transfer(&mut self, mosi: u32, _bits: u8, _context: &mut Timeline<'_, '_>) -> u32 {
        self.received.push(mosi);
        if let Some(word) = self.answers.pop_front() {
            self.held = word;
        }
        self.held
    }
}