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 power stage: the repetition counter, the dead-time, the break inputs and their filters, the
//! `MOE` they clear, the commutation of the preloaded control bits, and the `LOCK` that freezes it all.

use crate::device::{DeviceContext, Level, RegisterFile};
use crate::soc::FieldSpec;
use crate::soc::stm32h743::generated::tim1::{af1, af2, bdtr, cr1, cr2, dier, sr};

use super::family::{Family, Slot};
use super::fields::{CC_E, CC_NE, CC_NP, CC_P, LOCK_LEVEL_1, LOCK_LEVEL_2, OC_M, OIS, OIS_N};
use super::inputs::start_filter;
use super::{BREAK_SOURCES, BreakInput, BreakSource, Controls, Tim, present};

// 38.4.26 to 38.4.29
const BREAK_ENABLES: [[&FieldSpec; BREAK_SOURCES]; 2] = [
    [af1::BKINE, af1::BKCMP1E, af1::BKCMP2E, af1::BKDF1BK0E],
    [af2::BK2INE, af2::BK2CMP1E, af2::BK2CMP2E, af2::BK2DF1BK1E],
];

const BREAK_POLARITIES: [[Option<&FieldSpec>; BREAK_SOURCES]; 2] = [
    [
        Some(af1::BKINP),
        Some(af1::BKCMP1P),
        Some(af1::BKCMP2P),
        None,
    ],
    [
        Some(af2::BK2INP),
        Some(af2::BK2CMP1P),
        Some(af2::BK2CMP2P),
        None,
    ],
];

const BREAK_CONTROLS: [(&FieldSpec, &FieldSpec, &FieldSpec, Slot); 2] = [
    (bdtr::BKE, bdtr::BKP, bdtr::BKF, Slot::Af1),
    (bdtr::BK2E, bdtr::BK2P, bdtr::BK2F, Slot::Af2),
];

// 38.4.20
const LOCKED_AT_1: u64 = bdtr::DTG.mask()
    | bdtr::BKE.mask()
    | bdtr::BKP.mask()
    | bdtr::BKF.mask()
    | bdtr::BK2E.mask()
    | bdtr::BK2P.mask()
    | bdtr::BK2F.mask()
    | bdtr::AOE.mask();
const LOCKED_AT_2: u64 = bdtr::OSSR.mask() | bdtr::OSSI.mask();

const fn idle_levels() -> u64 {
    let mut mask = 0;
    let mut channel = 0;
    while channel < OIS.len() {
        mask |= OIS[channel].mask();
        channel += 1;
    }
    let mut channel = 0;
    while channel < OIS_N.len() {
        mask |= OIS_N[channel].mask();
        channel += 1;
    }
    mask
}
const CR2_LOCKED_AT_1: u64 = idle_levels();

impl<F: Family> Tim<F> {
    pub(super) fn repetitions(&self, registers: &RegisterFile) -> u64 {
        if !self.kind.repetition() {
            return 0;
        }
        let width = if self.kind.breaks() == 2 {
            super::HALF_WORD
        } else {
            super::fields::BYTE
        };
        self.reg(registers, Slot::Rcr) & width
    }

    pub(super) fn dead_time(&self, registers: &RegisterFile) -> u128 {
        let dtg = bdtr::DTG.get(self.reg(registers, Slot::Bdtr));
        let units = match dtg >> 5 {
            0..=3 => dtg,
            4 | 5 => (64 + (dtg & 0x3F)) * 2,
            6 => (32 + (dtg & 0x1F)) * 8,
            _ => (32 + (dtg & 0x1F)) * 16,
        };
        u128::from(units) << cr1::CKD.get(self.reg(registers, Slot::Cr1))
    }

    // The control bits of the channels that have a complementary output, which `CCPC` preloads:
    // `CCxE`, `CCxNE`, `CCxP` and `CCxNP`, 38.4.11.
    fn preloaded_ccer(&self) -> u64 {
        (0..self.kind.complementary()).fold(0, |mask, channel| {
            mask | CC_E[channel].mask()
                | CC_NE[channel].mask()
                | CC_P[channel].mask()
                | CC_NP[channel].mask()
        })
    }

    // The output modes of the channels of one mode register that have a complementary output.
    pub(super) fn preloaded_modes(&self, pair: usize) -> u64 {
        [pair * 2, pair * 2 + 1]
            .into_iter()
            .filter(|channel| *channel < self.kind.complementary())
            .fold(0, |mask, channel| mask | OC_M[channel].mask())
    }

    pub(super) fn ccer(&self, registers: &RegisterFile) -> u64 {
        let written = self.reg(registers, Slot::Ccer);
        match self.power.controls {
            Some(controls) => {
                let held = self.preloaded_ccer();
                (written & !held) | (controls.ccer & held)
            }
            None => written,
        }
    }

    fn snapshot_controls(&self, registers: &RegisterFile) -> Controls {
        Controls {
            ccer: self.reg(registers, Slot::Ccer),
            ccmr: [
                self.reg(registers, Slot::Ccmr1),
                self.reg(registers, Slot::Ccmr2),
            ],
        }
    }

    fn refresh_all_pwm(&mut self, registers: &RegisterFile, context: &DeviceContext<'_>) {
        let cycle = self.counter_cycle(context.now());
        for channel in present(self.kind.channels()) {
            self.refresh_pwm(channel, cycle, registers);
        }
    }

    pub(super) fn commutate(&mut self, registers: &mut RegisterFile, context: &DeviceContext<'_>) {
        if !cr2::CCPC.is_set(self.reg(registers, Slot::Cr2)) {
            return;
        }
        self.power.controls = Some(self.snapshot_controls(registers));
        self.flag(registers, sr::COMIF);
        if self.kind.dma() && dier::COMDE.is_set(self.reg(registers, Slot::Dier)) {
            self.dma_requests.commutation += 1;
            self.requested[super::COMMUTATION_REQUEST] = true;
        }
        self.refresh_all_pwm(registers, context);
    }

    // A break, 38.3.16: `MOE` cleared, and the flag of the break set.
    pub(super) fn take_break(&self, which: usize, registers: &mut RegisterFile) {
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        self.put(registers, Slot::Bdtr, bdtr::MOE.set(bdtr_value, 0));
        self.flag(registers, [sr::BIF, sr::B2IF][which]);
    }

    pub(super) fn software_break(&self, which: usize, registers: &mut RegisterFile) {
        if which < self.kind.breaks() {
            self.take_break(which, registers);
        }
    }

    pub(super) fn system_break(&self, registers: &mut RegisterFile) {
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        if self.kind.breaks() == 0 || !bdtr::BKE.is_set(bdtr_value) {
            return;
        }
        self.put(registers, Slot::Bdtr, bdtr::MOE.set(bdtr_value, 0));
        self.flag(
            registers,
            if self.kind.system_break_flag() {
                sr::SBIF
            } else {
                sr::BIF
            },
        );
    }

    // Whether break `which` is requested now: its enable, and any source it gathers that is enabled
    // and at its active level, 38.3.16. A source asserts at the level the break's polarity names,
    // inverted by the source's own.
    fn break_request(&self, which: usize, registers: &RegisterFile) -> bool {
        let (enable, polarity, _, slot) = BREAK_CONTROLS[which];
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        if which >= self.kind.breaks() || !enable.is_set(bdtr_value) {
            return false;
        }
        let gathered = self.reg(registers, slot);
        let active_high = polarity.is_set(bdtr_value);
        (0..BREAK_SOURCES).any(|source| {
            let inverted =
                BREAK_POLARITIES[which][source].is_some_and(|field| field.is_set(gathered));
            BREAK_ENABLES[which][source].is_set(gathered)
                && self.sources.breaks[which][source]
                    .is_some_and(|level| (level == Level::High) == (active_high != inverted))
        })
    }

    pub(super) fn refresh_breaks(&mut self, registers: &mut RegisterFile) {
        let clock = self.clock_cycle(self.seen);
        for (which, &(_, _, filter, _)) in
            BREAK_CONTROLS.iter().enumerate().take(self.kind.breaks())
        {
            let request = Level::from(self.break_request(which, registers));
            if request == self.power.filters[which].level {
                continue;
            }
            self.power.filters[which].level = request;
            let delay = self.filter_delay(filter.get(self.reg(registers, Slot::Bdtr)), registers);
            if delay == 0 {
                // No filter and the break acts asynchronously.
                self.power.filters[which].pending = None;
                self.power.filters[which].filtered = request;
                self.break_edge(which, request == Level::High, registers);
            } else {
                start_filter(&mut self.power.filters[which], request, clock, delay);
            }
        }
        for (which, held) in self.power.active.into_iter().enumerate() {
            if held && which < self.kind.breaks() {
                self.take_break(which, registers);
            }
        }
    }

    pub(super) fn break_edge(&mut self, which: usize, active: bool, registers: &mut RegisterFile) {
        self.power.active[which] = active;
        if active {
            self.take_break(which, registers);
        }
    }

    pub(super) fn auto_output(&self, registers: &mut RegisterFile) {
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        if self.kind.breaks() > 0
            && bdtr::AOE.is_set(bdtr_value)
            && !self.power.active.iter().any(|held| *held)
        {
            self.put(registers, Slot::Bdtr, bdtr::MOE.set(bdtr_value, 1));
        }
    }

    pub(super) fn lock_level(&self, registers: &RegisterFile) -> u64 {
        bdtr::LOCK.get(self.reg(registers, Slot::Bdtr))
    }

    // Puts the bits of `mask` in `slot` back to `before`, as a locked field does to a write.
    pub(super) fn keep(&self, registers: &mut RegisterFile, slot: Slot, mask: u64, before: u64) {
        let written = self.reg(registers, slot);
        self.put(registers, slot, (written & !mask) | (before & mask));
    }

    pub(super) fn write_cr2(
        &mut self,
        before: u64,
        registers: &mut RegisterFile,
        context: &DeviceContext<'_>,
    ) {
        if self.lock_level(registers) >= LOCK_LEVEL_1 {
            self.keep(registers, Slot::Cr2, CR2_LOCKED_AT_1, before);
        }
        if self.kind.complementary() == 0 {
            return;
        }
        let preloading = cr2::CCPC.is_set(self.reg(registers, Slot::Cr2));
        match (preloading, self.power.controls) {
            (true, None) => self.power.controls = Some(self.snapshot_controls(registers)),
            (false, Some(_)) => {
                self.power.controls = None;
                self.refresh_all_pwm(registers, context);
            }
            _ => {}
        }
    }

    pub(super) fn write_bdtr(&mut self, before: u64, registers: &mut RegisterFile) {
        if self.kind.breaks() == 0 {
            return;
        }
        if self.power.lock_frozen {
            self.keep(registers, Slot::Bdtr, bdtr::LOCK.mask(), before);
        }
        self.power.lock_frozen = true;
        let level = bdtr::LOCK.get(before);
        if level >= LOCK_LEVEL_1 {
            self.keep(registers, Slot::Bdtr, LOCKED_AT_1, before);
        }
        if level >= LOCK_LEVEL_2 {
            self.keep(registers, Slot::Bdtr, LOCKED_AT_2, before);
        }
        if self.power.active.iter().any(|held| *held) {
            let bdtr_value = self.reg(registers, Slot::Bdtr);
            self.put(registers, Slot::Bdtr, bdtr::MOE.set(bdtr_value, 0));
        }
    }

    pub(super) fn write_alternate(&self, slot: Slot, before: u64, registers: &mut RegisterFile) {
        if self.lock_level(registers) >= LOCK_LEVEL_1 {
            self.keep(registers, slot, u64::MAX, before);
        }
    }

    /// The level on one source of one break, section 38.3.16. A source nothing has driven asserts nothing.
    /// The break takes the request after its polarity and its filter.
    #[expect(
        clippy::too_many_arguments,
        reason = "the break, its source and the level, and what every call is handed"
    )]
    pub fn drive_break(
        &mut self,
        input: BreakInput,
        source: BreakSource,
        level: Level,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        if input.index() >= self.kind.breaks() {
            return;
        }
        self.settle(registers, context);
        self.sources.breaks[input.index()][source.index()] = Some(level);
        self.finish(registers, context);
    }
}