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 layouts the part's timers are addressed through, the kinds of timer each carries, and the
//! instance tables.

use crate::soc::st::tim::{Counter, Family, Flags, Kind, Part, Timer};
use crate::soc::stm32h743::generated::{tim1, tim2, tim6, tim15, tim16, tim17};

/// The general-purpose timers of chapters 39 and 40, in the order of the manual and of an array of
/// `Tim<General>`: TIM2 and TIM5 count in 32 bits, the rest in 16, and TIM12 to TIM14 have fewer channels.
/// ST's file gives all seven one table, TIM2's, and the model reaches the rows each has.
pub const TIMERS: [Timer; 7] = [
    Timer::new("TIM2", Counter::Wide, Kind::General),
    Timer::new("TIM3", Counter::Narrow, Kind::General),
    Timer::new("TIM4", Counter::Narrow, Kind::General),
    Timer::new("TIM5", Counter::Wide, Kind::General),
    Timer::new("TIM12", Counter::Narrow, Kind::TwoChannelSlave),
    Timer::new("TIM13", Counter::Narrow, Kind::OneChannel),
    Timer::new("TIM14", Counter::Narrow, Kind::OneChannel),
];

/// The advanced-control timers of chapter 38, in the order of an array of `Tim<Advanced>`.
pub const ADVANCED_TIMERS: [Timer; 2] = [
    Timer::new("TIM1", Counter::Narrow, Kind::Advanced),
    Timer::new("TIM8", Counter::Narrow, Kind::Advanced),
];

/// The basic timers of chapter 42, in the order of an array of `Tim<Basic>`.
pub const BASIC_TIMERS: [Timer; 2] = [
    Timer::new("TIM6", Counter::Narrow, Kind::Basic),
    Timer::new("TIM7", Counter::Narrow, Kind::Basic),
];

/// TIM15, which has a table of its own, for a `Tim<Tim15>`.
pub const TIMER_15: Timer = Timer::new("TIM15", Counter::Narrow, Kind::TwoChannelBreak);

/// TIM16, which has a table of its own, for a `Tim<Tim16>`.
pub const TIMER_16: Timer = Timer::new("TIM16", Counter::Narrow, Kind::OneChannelBreak);

/// TIM17, which has a table of its own, for a `Tim<Tim17>`.
pub const TIMER_17: Timer = Timer::new("TIM17", Counter::Narrow, Kind::OneChannelBreak);

/// A family of this part's timers, which a part builds from its row of the instance tables.
pub trait Stm32h743Family: Family<Instance = Timer> {}

/// The timers addressed through the general-purpose layout: TIM2 to TIM5 and TIM12 to TIM14.
#[derive(Debug, Clone, Copy)]
pub struct General;

/// The advanced-control timers' layout.
#[derive(Debug, Clone, Copy)]
pub struct Advanced;

/// The basic timers' layout.
#[derive(Debug, Clone, Copy)]
pub struct Basic;

/// TIM15's layout.
#[derive(Debug, Clone, Copy)]
pub struct Tim15;

/// TIM16's layout.
#[derive(Debug, Clone, Copy)]
pub struct Tim16;

/// TIM17's layout, which is TIM16's under other names.
#[derive(Debug, Clone, Copy)]
pub struct Tim17;

// Table 147's four lines of the advanced timers.
const BREAK: usize = 0;
const UPDATE: usize = 1;
const TRIGGER_AND_COMMUTATION: usize = 2;
const CAPTURE_COMPARE: usize = 3;

impl Family for General {
    type Register = tim2::Register;
    type Instance = Timer;
    const PART: &'static Part = &super::PART;
    const KINDS: &'static [Kind] = &[Kind::General, Kind::TwoChannelSlave, Kind::OneChannel];
}

impl Family for Advanced {
    type Register = tim1::Register;
    type Instance = Timer;
    const PART: &'static Part = &super::PART;
    const KINDS: &'static [Kind] = &[Kind::Advanced];
    const LINES: usize = 4;
    fn line(flags: Flags) -> usize {
        match flags {
            Flags::Break => BREAK,
            Flags::Update => UPDATE,
            Flags::TriggerAndCommutation => TRIGGER_AND_COMMUTATION,
            Flags::CaptureCompare => CAPTURE_COMPARE,
        }
    }
}

impl Family for Basic {
    type Register = tim6::Register;
    type Instance = Timer;
    const PART: &'static Part = &super::PART;
    const KINDS: &'static [Kind] = &[Kind::Basic];
}

impl Family for Tim15 {
    type Register = tim15::Register;
    type Instance = Timer;
    const PART: &'static Part = &super::PART;
    const KINDS: &'static [Kind] = &[Kind::TwoChannelBreak];
}

impl Family for Tim16 {
    type Register = tim16::Register;
    type Instance = Timer;
    const PART: &'static Part = &super::PART;
    const KINDS: &'static [Kind] = &[Kind::OneChannelBreak];
}

impl Family for Tim17 {
    type Register = tim17::Register;
    type Instance = Timer;
    const PART: &'static Part = &super::PART;
    const KINDS: &'static [Kind] = &[Kind::OneChannelBreak];
}

impl Stm32h743Family for General {}
impl Stm32h743Family for Advanced {}
impl Stm32h743Family for Basic {}
impl Stm32h743Family for Tim15 {}
impl Stm32h743Family for Tim16 {}
impl Stm32h743Family for Tim17 {}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::soc::Layout;
    use crate::soc::st::tim::family::{Map, ROWS, SLOTS, Slot, offsets};
    use crate::soc::{FieldSpec, RegisterSpec};

    fn layouts() -> [(&'static str, &'static [RegisterSpec]); 6] {
        [
            ("tim1", <tim1::Register as Layout>::REGISTERS),
            ("tim2", <tim2::Register as Layout>::REGISTERS),
            ("tim6", <tim6::Register as Layout>::REGISTERS),
            ("tim15", <tim15::Register as Layout>::REGISTERS),
            ("tim16", <tim16::Register as Layout>::REGISTERS),
            ("tim17", <tim17::Register as Layout>::REGISTERS),
        ]
    }

    fn slot_at(offset: u32) -> Option<Slot> {
        let offsets = offsets(&super::super::PART);
        Slot::ALL
            .into_iter()
            .find(|slot| offsets[slot.index()] == Some(offset))
    }

    #[test]
    fn the_slots_are_the_advanced_timers_registers_in_offset_order() {
        let offsets: Vec<u32> = offsets(&super::super::PART).into_iter().flatten().collect();
        let mut sorted = offsets.clone();
        sorted.sort_unstable();
        sorted.dedup();
        assert_eq!(offsets, sorted, "ascending and distinct");
        let distinct: std::collections::BTreeSet<u32> = tim1::REGISTERS
            .iter()
            .map(|register| register.offset)
            .collect();
        assert_eq!(
            distinct.into_iter().collect::<Vec<u32>>(),
            offsets,
            "a slot for every offset of the advanced timer's table, the two views of a mode register one"
        );
        assert!(SLOTS > offsets.len(), "and the slots this part has none of");
    }

    #[test]
    fn every_row_of_every_layout_is_a_slot_at_its_offset() {
        assert_eq!(tim1::REGISTERS.len(), ROWS);
        let part = offsets(&super::super::PART);
        for (name, table) in layouts() {
            assert!(table.len() <= ROWS, "{name}");
            for (row, register) in table.iter().enumerate() {
                let slot = slot_at(register.offset)
                    .unwrap_or_else(|| panic!("{name} {} has no slot", register.name));
                assert_eq!(
                    part[slot.index()],
                    Some(register.offset),
                    "{name} {}",
                    register.name
                );
                assert!(row < ROWS);
            }
        }
        let map = Map::of(&tim1::REGISTERS, &part);
        assert_eq!(
            map.row(Slot::Ccmr1),
            Some(tim1::Register::Ccmr1Input.index())
        );
        assert_eq!(map.row(Slot::Ccr6), Some(tim1::Register::Ccr6.index()));
        assert_eq!(
            map.slot(tim1::Register::Ccmr1Output.index()),
            Some(Slot::Ccmr1)
        );
        let basic = Map::of(&tim6::REGISTERS, &part);
        assert_eq!(
            basic.row(Slot::Smcr),
            None,
            "the basic timer has no slave mode controller"
        );
        assert_eq!(basic.row(Slot::Cnt), Some(tim6::Register::Cnt.index()));
        let small = Map::of(&tim16::REGISTERS, &part);
        assert_eq!(small.row(Slot::Ccr2), None);
        assert_eq!(
            small.row(Slot::Af1),
            Some(tim16::Register::Tim16Af1.index())
        );
    }

    fn field_of(
        table: &'static [RegisterSpec],
        register: &str,
        name: &str,
    ) -> Option<&'static FieldSpec> {
        table
            .iter()
            .find(|candidate| candidate.name == register)?
            .fields
            .iter()
            .find(|field| field.name == name)
    }

    #[test]
    fn a_field_the_layouts_share_sits_at_the_same_bits_in_each() {
        const RENAMED: [(&str, &str, &str, &str); 3] = [
            ("tim2", "CCMR2_Output", "O24CE", "OC4CE"),
            ("tim15", "SMCR", "TS_2_0", "TS"),
            ("tim1", "CCMR2_Output", "OC4M_4", "OC4M_3"),
        ];
        let mut compared = 0;
        for (name, table) in layouts() {
            if name == "tim1" {
                continue;
            }
            for register in table {
                let Some(twin) = tim1::REGISTERS.iter().find(|candidate| {
                    candidate.offset == register.offset
                        && candidate.name.split('_').next() == register.name.split('_').next()
                }) else {
                    continue;
                };
                for field in register.fields {
                    let renamed = RENAMED
                        .iter()
                        .find(|(layout, owner, from, _)| {
                            *layout == name && *owner == register.name && *from == field.name
                        })
                        .map(|(_, _, _, to)| *to);
                    let wanted = renamed.unwrap_or(field.name);
                    let Some(counterpart) = twin
                        .fields
                        .iter()
                        .find(|candidate| candidate.name == wanted)
                    else {
                        continue;
                    };
                    assert_eq!(
                        (field.bit, field.width),
                        (counterpart.bit, counterpart.width),
                        "{name} {}.{}",
                        register.name,
                        field.name
                    );
                    compared += 1;
                }
            }
        }
        assert!(compared > 150, "{compared} fields compared");
        assert!(field_of(tim1::REGISTERS.as_slice(), "SMCR", "TS_4_3").is_some());
    }

    #[test]
    fn each_kind_belongs_to_the_family_whose_layout_has_its_channels() {
        fn channels<F: Family>() -> Vec<(Kind, usize)> {
            F::KINDS
                .iter()
                .map(|kind| {
                    let capture = <F::Register as Layout>::REGISTERS
                        .iter()
                        .filter(|register| {
                            (13..=16).contains(&(register.offset / 4))
                                || (22..=23).contains(&(register.offset / 4))
                        })
                        .count();
                    (*kind, capture)
                })
                .collect()
        }
        for (kind, registers) in channels::<Advanced>() {
            assert_eq!(registers, usize::from(kind.channels()), "{kind:?}");
        }
        for (kind, registers) in channels::<General>() {
            assert!(registers >= usize::from(kind.channels()), "{kind:?}");
        }
        for (kind, registers) in channels::<Tim15>() {
            assert_eq!(registers, usize::from(kind.channels()), "{kind:?}");
        }
        for (kind, registers) in channels::<Tim16>() {
            assert_eq!(registers, usize::from(kind.channels()), "{kind:?}");
        }
        for (kind, registers) in channels::<Basic>() {
            assert_eq!(registers, usize::from(kind.channels()), "{kind:?}");
        }
        let every: Vec<Kind> = [
            General::KINDS,
            Advanced::KINDS,
            Basic::KINDS,
            Tim15::KINDS,
            Tim16::KINDS,
            Tim17::KINDS,
        ]
        .concat();
        for kind in [
            Kind::Advanced,
            Kind::General,
            Kind::TwoChannelSlave,
            Kind::OneChannel,
            Kind::TwoChannelBreak,
            Kind::OneChannelBreak,
            Kind::Basic,
        ] {
            assert!(every.contains(&kind), "{kind:?} has a family");
        }
    }

    // 39.2
    #[test]
    fn the_instance_tables_name_the_fourteen_timers_once() {
        let mut names: Vec<&str> = TIMERS
            .iter()
            .chain(&ADVANCED_TIMERS)
            .chain(&BASIC_TIMERS)
            .chain([&TIMER_15, &TIMER_16, &TIMER_17])
            .map(|timer| timer.instance)
            .collect();
        assert_eq!(names.len(), 14, "TIM9 to TIM11 are not in this part");
        names.sort_unstable();
        names.dedup();
        assert_eq!(names.len(), 14);
        let wide: Vec<&str> = TIMERS
            .iter()
            .filter(|timer| timer.counter == Counter::Wide)
            .map(|timer| timer.instance)
            .collect();
        assert_eq!(wide, ["TIM2", "TIM5"], "39.2");
        for timer in TIMERS {
            assert!(General::KINDS.contains(&timer.kind));
        }
    }
}