rigless 0.2.1

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 processor's state an instruction reads and writes, laid out for generated code to reach at
//! fixed offsets, DDI 0403E.b B1.4.

use serde::{Deserialize, Serialize};

use super::registers::{control, get, is_set, mask, set, xpsr};

/// Every register and flag of one ARMv7-M processor, and what it holds between instructions.
///
/// Each flag is a word of its own holding 0 or 1, so generated code loads and stores it whole.
/// `r[13]` is the stack pointer in use, and `other_sp` the one that isn't, B1.4.1.
#[repr(C)]
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct State {
    /// `R0` to `R15`. `r[15]` is the address of the next instruction, which is only kept here
    /// between blocks.
    pub(crate) r: [u32; 16],
    /// `APSR.N`, `Z`, `C`, `V` and `Q`, each 0 or 1.
    pub(crate) n: u32,
    pub(crate) z: u32,
    pub(crate) c: u32,
    pub(crate) v: u32,
    pub(crate) q: u32,
    /// `APSR.GE`, in its low four bits.
    pub(crate) ge: u32,
    /// `EPSR.IT`, the eight bits of `ITSTATE` as A7.3.2 lays them out.
    pub(crate) it: u32,
    /// `EPSR.T`, 0 or 1.
    pub(crate) t: u32,
    /// `IPSR`, the exception number being handled, 0 in Thread mode.
    pub(crate) ipsr: u32,
    /// `PRIMASK` and `FAULTMASK` as B1.4.3 has them, and `CONTROL`, B1.4.4. `BASEPRI` is the
    /// interrupt controller's, which decides with it.
    pub(crate) primask: u32,
    pub(crate) faultmask: u32,
    pub(crate) control: u32,
    /// The stack pointer not in use: `SP_process` while `r[13]` is `SP_main`, and the other way
    /// about.
    pub(crate) other_sp: u32,
    /// `S0` to `S31` of the floating-point extension, B1.4.7.
    pub(crate) s: [u32; 32],
    pub(crate) fpscr: u32,
    /// The processor is locked up, B1.5.15, and runs nothing until it's reset or takes an NMI.
    pub(crate) locked_up: u32,
    /// The processor is waiting for an interrupt or an event, after `WFI` or `WFE`.
    pub(crate) sleeping: u32,
    /// Whether the local exclusive monitor is in the Exclusive Access state, A3.4. A Cortex-M4's
    /// monitor holds no address, 100166 section 3.6.
    pub(crate) exclusive_valid: u32,
    /// The event register, B1.5.18.
    pub(crate) event: u32,
}

impl Default for State {
    fn default() -> Self {
        Self {
            r: [0; 16],
            n: 0,
            z: 0,
            c: 0,
            v: 0,
            q: 0,
            ge: 0,
            it: 0,
            t: 1,
            ipsr: 0,
            primask: 0,
            faultmask: 0,
            control: 0,
            other_sp: 0,
            s: [0; 32],
            fpscr: 0,
            locked_up: 0,
            sleeping: 0,
            exclusive_valid: 0,
            event: 0,
        }
    }
}

impl State {
    /// `xPSR`, the three program status registers in one word, B1.4.2.
    pub(crate) fn xpsr(&self) -> u32 {
        let it = self.it;
        set(xpsr::N, 0, self.n)
            | set(xpsr::Z, 0, self.z)
            | set(xpsr::C, 0, self.c)
            | set(xpsr::V, 0, self.v)
            | set(xpsr::Q, 0, self.q)
            | set(xpsr::IT_1_0, 0, it)
            | set(xpsr::T, 0, self.t)
            | set(xpsr::GE, 0, self.ge)
            | set(xpsr::IT_7_2, 0, it >> xpsr::IT_1_0.width)
            | set(xpsr::EXCEPTION_NUMBER, 0, self.ipsr)
    }

    /// Whether every field holds a value the processor could, B1.4: each flag 0 or 1, four bits
    /// of `GE`, eight of `ITSTATE`, the bits of `CONTROL` there are, a sleep the processor knows,
    /// and word-aligned stack pointers, B1.4.7. A saved state is refused unless it does.
    pub(crate) fn consistent(&self) -> bool {
        let flags = [
            self.n,
            self.z,
            self.c,
            self.v,
            self.q,
            self.t,
            self.primask,
            self.faultmask,
            self.locked_up,
            self.exclusive_valid,
            self.event,
        ];
        flags.iter().all(|&flag| flag <= 1)
            && self.ge <= 0xF
            && self.it <= 0xFF
            && self.control <= mask(control::NPRIV) | mask(control::SPSEL) | mask(control::FPCA)
            && self.sleeping <= super::exception::ON_EXIT
            && self.r[13].trailing_zeros() >= 2
            && self.other_sp.trailing_zeros() >= 2
    }

    /// Sets every field `xPSR` holds from `value`.
    pub(crate) fn set_xpsr(&mut self, value: u32) {
        self.n = get(xpsr::N, value);
        self.z = get(xpsr::Z, value);
        self.c = get(xpsr::C, value);
        self.v = get(xpsr::V, value);
        self.q = get(xpsr::Q, value);
        self.it = get(xpsr::IT_1_0, value) | get(xpsr::IT_7_2, value) << xpsr::IT_1_0.width;
        self.t = get(xpsr::T, value);
        self.ge = get(xpsr::GE, value);
        self.ipsr = get(xpsr::EXCEPTION_NUMBER, value);
    }

    /// Whether the processor is in Handler mode, B1.4.
    pub(crate) fn handler(&self) -> bool {
        self.ipsr != 0
    }

    /// Whether software runs privileged now, B1.4.4.
    pub(crate) fn privileged(&self) -> bool {
        self.handler() || !is_set(control::NPRIV, self.control)
    }

    /// Whether `r[13]` is the process stack pointer.
    pub(crate) fn on_process_stack(&self) -> bool {
        !self.handler() && is_set(control::SPSEL, self.control)
    }

    /// `SP_main`.
    pub(crate) fn msp(&self) -> u32 {
        if self.on_process_stack() {
            self.other_sp
        } else {
            self.r[13]
        }
    }

    /// `SP_process`.
    pub(crate) fn psp(&self) -> u32 {
        if self.on_process_stack() {
            self.r[13]
        } else {
            self.other_sp
        }
    }

    /// Writes `SP_main`, word-aligned as B1.4.7's `R[]` writes a stack pointer.
    pub(crate) fn set_msp(&mut self, value: u32) {
        let value = value & !3;
        if self.on_process_stack() {
            self.other_sp = value;
        } else {
            self.r[13] = value;
        }
    }

    /// Writes `SP_process`, word-aligned as B1.4.7's `R[]` writes a stack pointer.
    pub(crate) fn set_psp(&mut self, value: u32) {
        let value = value & !3;
        if self.on_process_stack() {
            self.r[13] = value;
        } else {
            self.other_sp = value;
        }
    }

    /// Changes what `ipsr` and `CONTROL` say together, keeping `SP_main` and `SP_process` where
    /// they are whichever is in use before and after.
    pub(crate) fn set_mode(&mut self, ipsr: u32, control: u32) {
        let (msp, psp) = (self.msp(), self.psp());
        self.ipsr = ipsr;
        self.control = control;
        self.set_msp(msp);
        self.set_psp(psp);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn xpsr_holds_every_field_where_the_manual_puts_it() {
        let mut state = State::default();
        state.set_xpsr(0xF900_FC23);
        assert_eq!(
            (state.n, state.z, state.c, state.v, state.q),
            (1, 1, 1, 1, 1)
        );
        assert_eq!(state.t, 1);
        assert_eq!(state.ipsr, 0x23);
        assert_eq!(state.it, 0b1111_1100);
        assert_eq!(state.xpsr(), 0xF900_FC23);
    }

    #[test]
    fn a_change_of_mode_keeps_both_stack_pointers() {
        let mut state = State::default();
        state.r[13] = 0x100;
        state.other_sp = 0x200;
        state.set_mode(0, mask(control::SPSEL));
        assert_eq!(
            (state.msp(), state.psp(), state.r[13]),
            (0x100, 0x200, 0x200)
        );
        state.set_mode(15, mask(control::SPSEL));
        assert_eq!(
            (state.msp(), state.psp(), state.r[13]),
            (0x100, 0x200, 0x100)
        );
    }
}