rigless 0.0.5

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; a fork of QEMU is built in and executes the instructions
Documentation
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The parsed SVD reduced to what the tables need: instances, layouts, registers, fields.

use std::collections::BTreeMap;

use svd_parser::svd::{
    Access, DeriveFrom, Device, FieldInfo, ModifiedWriteValues, PeripheralInfo, ReadAction,
    RegisterCluster, RegisterInfo, RegisterProperties,
};

use crate::svd::Error;

pub(crate) struct Model {
    pub(crate) device_name: String,
    pub(crate) cpu: Cpu,
    /// Every instance, by ascending base address.
    pub(crate) instances: Vec<Instance>,
    /// Every register layout by the name of the peripheral that defines it.
    pub(crate) layouts: BTreeMap<String, Vec<Register>>,
}

pub(crate) struct Cpu {
    pub(crate) name: String,
    pub(crate) fpu: bool,
    pub(crate) interrupt_lines: u16,
    /// The SVD stated the count itself, rather than the generator deriving it from the highest line named.
    pub(crate) interrupt_lines_stated: bool,
}

pub(crate) struct Instance {
    pub(crate) name: String,
    pub(crate) base: u64,
    pub(crate) size: u64,
    pub(crate) interrupts: Vec<u16>,
    /// The peripheral whose registers this instance uses: itself, or the sibling it derives from.
    pub(crate) layout: String,
}

pub(crate) struct Register {
    pub(crate) name: String,
    pub(crate) offset: u32,
    pub(crate) bits: u32,
    pub(crate) reset: u64,
    pub(crate) access: Access,
    pub(crate) read: Option<ReadAction>,
    pub(crate) write: Option<ModifiedWriteValues>,
    pub(crate) fields: Vec<Field>,
    /// Declared to share bytes with another register, so overlap is not an error.
    pub(crate) alternate: bool,
}

pub(crate) struct Field {
    pub(crate) name: String,
    pub(crate) bit: u8,
    pub(crate) width: u8,
    pub(crate) access: Access,
    pub(crate) read: Option<ReadAction>,
    pub(crate) write: Option<ModifiedWriteValues>,
}

const BITS_PER_BYTE: u32 = 8;
const DEFAULT_REGISTER_BITS: u32 = 32;
const REGISTER_WIDTHS: [u32; 4] = [8, 16, 32, 64];

impl Register {
    pub(crate) const fn end(&self) -> u32 {
        self.offset + self.bits / BITS_PER_BYTE
    }
}

pub(crate) fn from_device(device: &Device) -> Result<Model, Error> {
    let layout_of = layout_roots(device);
    let expanded = svd_parser::expand::expand(device).map_err(Error::Svd)?;
    let cpu = expanded.cpu.as_ref().ok_or(Error::NoCpu)?;

    let mut instances = Vec::new();
    let mut layouts = BTreeMap::new();
    for peripheral in &expanded.peripherals {
        let defaults = peripheral
            .default_register_properties
            .derive_from(&expanded.default_register_properties);
        let layout = layout_of
            .get(&peripheral.name)
            .cloned()
            .unwrap_or_else(|| peripheral.name.clone());
        if layout == peripheral.name {
            layouts.insert(layout.clone(), registers_of(peripheral, &defaults)?);
        }
        instances.push(instance_from(peripheral, layout)?);
    }
    instances.sort_by(|left, right| (left.base, &left.name).cmp(&(right.base, &right.name)));

    let highest_line = instances
        .iter()
        .flat_map(|instance| &instance.interrupts)
        .max()
        .copied();
    let interrupt_lines = cpu
        .device_num_interrupts
        .unwrap_or_else(|| highest_line.map_or(0, |line| u32::from(line) + 1));
    Ok(Model {
        device_name: expanded.name.clone(),
        cpu: Cpu {
            name: cpu.name.clone(),
            fpu: cpu.fpu_present,
            interrupt_lines: narrow(interrupt_lines.into(), "cpu", "deviceNumInterrupts")?,
            interrupt_lines_stated: cpu.device_num_interrupts.is_some(),
        },
        instances,
        layouts,
    })
}

/// For each peripheral that derives from a sibling, the peripheral at the root of that chain.
fn layout_roots(device: &Device) -> BTreeMap<String, String> {
    let derived: BTreeMap<&str, &str> = device
        .peripherals
        .iter()
        .filter(|peripheral| peripheral.registers.is_none())
        .filter_map(|peripheral| {
            peripheral
                .derived_from
                .as_deref()
                .map(|parent| (peripheral.name.as_str(), parent))
        })
        .collect();
    derived
        .keys()
        .map(|&name| {
            let mut root = name;
            while let Some(&parent) = derived.get(root) {
                root = parent;
            }
            (name.to_owned(), root.to_owned())
        })
        .collect()
}

fn instance_from(peripheral: &PeripheralInfo, layout: String) -> Result<Instance, Error> {
    let blocks = peripheral.address_block.as_deref().unwrap_or(&[]);
    let size = blocks
        .iter()
        .map(|block| u64::from(block.offset) + u64::from(block.size))
        .max()
        .ok_or_else(|| Error::NoAddressBlock {
            instance: peripheral.name.clone(),
        })?;
    let mut interrupts = peripheral
        .interrupt
        .iter()
        .map(|interrupt| narrow(interrupt.value.into(), &peripheral.name, "interrupt"))
        .collect::<Result<Vec<u16>, Error>>()?;
    interrupts.sort_unstable();
    interrupts.dedup();
    Ok(Instance {
        name: peripheral.name.clone(),
        base: peripheral.base_address,
        size,
        interrupts,
        layout,
    })
}

fn registers_of(
    peripheral: &PeripheralInfo,
    defaults: &RegisterProperties,
) -> Result<Vec<Register>, Error> {
    let mut registers = Vec::new();
    for item in peripheral.registers.as_deref().unwrap_or(&[]) {
        let RegisterCluster::Register(info) = item else {
            return Err(Error::Svd(anyhow::anyhow!(
                "{}: a cluster survived expansion",
                peripheral.name
            )));
        };
        registers.push(register_from(&peripheral.name, info, defaults)?);
    }
    registers.sort_by(|left, right| (left.offset, &left.name).cmp(&(right.offset, &right.name)));
    Ok(registers)
}

fn register_from(
    peripheral_name: &str,
    info: &RegisterInfo,
    defaults: &RegisterProperties,
) -> Result<Register, Error> {
    let path = format!("{peripheral_name}.{}", info.name);
    let properties = info.properties.derive_from(defaults);
    let bits = properties.size.unwrap_or(DEFAULT_REGISTER_BITS);
    if !REGISTER_WIDTHS.contains(&bits) {
        return Err(Error::Width {
            register: path,
            bits,
        });
    }
    let access = properties.access.unwrap_or(Access::ReadWrite);
    let mut fields = info
        .fields
        .as_deref()
        .unwrap_or(&[])
        .iter()
        .map(|field| field_from(&path, field, bits, access, info))
        .collect::<Result<Vec<Field>, Error>>()?;
    fields.sort_by(|left, right| (left.bit, &left.name).cmp(&(right.bit, &right.name)));
    Ok(Register {
        name: info.name.clone(),
        offset: info.address_offset,
        bits,
        reset: properties.reset_value.unwrap_or(0),
        access,
        read: info.read_action,
        write: info.modified_write_values,
        fields,
        alternate: info.alternate_register.is_some() || info.alternate_group.is_some(),
    })
}

fn field_from(
    register_path: &str,
    field: &FieldInfo,
    register_bits: u32,
    register_access: Access,
    register: &RegisterInfo,
) -> Result<Field, Error> {
    let end = field.bit_range.offset + field.bit_range.width;
    if end > register_bits {
        return Err(Error::FieldOutOfRange {
            register: register_path.to_owned(),
            field: field.name.clone(),
            end,
            bits: register_bits,
        });
    }
    Ok(Field {
        name: field.name.clone(),
        bit: narrow(field.bit_range.offset.into(), register_path, "bitOffset")?,
        width: narrow(field.bit_range.width.into(), register_path, "bitWidth")?,
        access: field.access.unwrap_or(register_access),
        read: field.read_action.or(register.read_action),
        write: field
            .modified_write_values
            .or(register.modified_write_values),
    })
}

fn narrow<T: TryFrom<u64>>(value: u64, element: &str, what: &'static str) -> Result<T, Error> {
    T::try_from(value).map_err(|_overflow| Error::TooLarge {
        element: element.to_owned(),
        what,
        value,
    })
}