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 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 super::family::{COMMUTATION_REQUEST, Family, Slot};
use super::fields::{LOCK_LEVEL_1, LOCK_LEVEL_2};
use super::inputs::start_filter;
use super::{BREAK_SOURCES, BreakInput, BreakSource, Controls, Tim, present};

impl<F: Family> Tim<F> {
    // 17.4.18 and 38.4.20
    const LOCKED_AT_1: u64 = Self::DTG.mask()
        | Self::BKE.mask()
        | Self::BKP.mask()
        | Self::BKF.mask()
        | Self::BK2E.mask()
        | Self::BK2P.mask()
        | Self::BK2F.mask()
        | Self::AOE.mask();
    const LOCKED_AT_2: u64 = Self::OSSR.mask() | Self::OSSI.mask();
    const CR2_LOCKED_AT_1: u64 = {
        let mut mask = 0;
        let mut channel = 0;
        while channel < Self::OIS.len() {
            mask |= Self::OIS[channel].mask();
            channel += 1;
        }
        let mut channel = 0;
        while channel < Self::OIS_N.len() {
            mask |= Self::OIS_N[channel].mask();
            channel += 1;
        }
        mask
    };

    pub(super) fn repetitions(&self, registers: &RegisterFile) -> u64 {
        self.reg(registers, Slot::Rcr) & self.kind.repetition_mask()
    }

    // `DTG`'s four ranges of `tDTS`, in `CK_INT` cycles, 17.4.18 and 38.4.20.
    pub(super) fn dead_time(&self, registers: &RegisterFile) -> u128 {
        let dtg = Self::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) << Self::CKD.get(self.reg(registers, Slot::Cr1))
    }

    // 17.3.14 and 38.3.19
    fn preloaded_ccer(&self) -> u64 {
        (0..self.kind.complementary()).fold(0, |mask, channel| {
            mask | Self::CC_E[channel].mask()
                | Self::CC_NE[channel].mask()
                | Self::CC_P[channel].mask()
                | Self::CC_NP[channel].mask()
        })
    }

    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 | Self::OC_M[channel].mask())
    }

    // The written value with the bits `CCPC` took at the last commutation, 17.3.14 and 38.3.19.
    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);
        }
    }

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

    // 17.3.12 and 38.3.16
    pub(super) fn take_break(&self, which: usize, registers: &mut RegisterFile) {
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        self.put(registers, Slot::Bdtr, Self::MOE.set(bdtr_value, 0));
        self.flag(registers, [Self::BIF, Self::B2IF][which]);
    }

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

    // Not latched: ES0206 rev 24 2.7.1, and RM0433 rev 9 38.3.16 gives no latch either.
    pub(super) fn system_break(&self, registers: &mut RegisterFile) {
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        if self.kind.breaks() == 0 || !Self::BKE.is_set(bdtr_value) {
            return;
        }
        self.put(registers, Slot::Bdtr, Self::MOE.set(bdtr_value, 0));
        self.flag(
            registers,
            if self.kind.system_break_flag() {
                Self::SBIF
            } else {
                Self::BIF
            },
        );
    }

    // 17.3.12 and 38.3.16. A break of the pin alone has no source polarity of its own.
    fn break_request(&self, which: usize, registers: &RegisterFile) -> bool {
        let (enable, polarity, _, slot) = Self::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 enabled = if self.kind.gathers_break_sources() {
                Self::BREAK_ENABLES[which][source].is_set(gathered)
            } else {
                source == BreakSource::Pin.index()
            };
            let inverted =
                Self::BREAK_POLARITIES[which][source].is_some_and(|field| field.is_set(gathered));
            enabled
                && 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 Self::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);
        }
    }

    // `AOE`: the update sets `MOE` again unless a break still holds it, 17.4.18 and 38.4.20.
    pub(super) fn auto_output(&self, registers: &mut RegisterFile) {
        let bdtr_value = self.reg(registers, Slot::Bdtr);
        if self.kind.breaks() > 0
            && Self::AOE.is_set(bdtr_value)
            && !self.power.active.iter().any(|held| *held)
        {
            self.put(registers, Slot::Bdtr, Self::MOE.set(bdtr_value, 1));
        }
    }

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

    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, Self::CR2_LOCKED_AT_1, before);
        }
        if self.kind.complementary() == 0 {
            return;
        }
        let preloading = Self::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, Self::LOCK.mask(), before);
        }
        self.power.lock_frozen = true;
        let level = Self::LOCK.get(before);
        if level >= LOCK_LEVEL_1 {
            self.keep(registers, Slot::Bdtr, Self::LOCKED_AT_1, before);
        }
        if level >= LOCK_LEVEL_2 {
            self.keep(registers, Slot::Bdtr, Self::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, Self::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, 17.3.12 and 38.3.16. A source nothing has driven asserts
    /// nothing, and on a part whose `BKIN` pin rests at a level, [`Part::break_pin`], the pin starts
    /// there. The break takes the request after its polarity and its filter.
    ///
    /// [`Part::break_pin`]: super::Part::break_pin
    #[expect(
        clippy::too_many_arguments,
        reason = "the break, its source and the level, and what every call is handed"
    )]
    pub fn drive_break_source(
        &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);
    }
}