hilt 0.2.0

Renode-based hardware-in-the-loop test fixtures for embedded Rust projects
Documentation
//! Target platform descriptions.
//!
//! A [`Platform`] is pure data describing how to bring up one emulated machine
//! in Renode: which platform description (`.repl`) to load, how to initialize
//! the CPU, and how it connects to a virtual CAN bus. Built-in constructors
//! cover the boards used in practice; for anything else every field is `pub`,
//! so build one with a struct literal (e.g. `Platform { name: "myboard", ..Platform::rp2040() }`).

use std::fmt;

/// Where a Renode platform description (`.repl`) comes from.
#[derive(Debug, Clone, Copy)]
pub enum ReplSource {
    /// A `.repl` body embedded in the binary. It is written into the work
    /// directory and loaded from `@/hil/<machine>.repl` inside the container.
    Embedded(&'static str),
    /// A `.repl` path that already exists inside the Renode container image,
    /// e.g. `platforms/boards/nucleo_h753zi.repl`. Loaded as-is.
    ImagePath(&'static str),
}

/// How to initialize the CPU after `LoadELF`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CpuInit {
    /// Extract the vector table from the ELF (read in pure Rust) and set
    /// `VectorTableOffset` / `SP` / `PC`. Used for generic Cortex-M `.repl`s
    /// whose reset behavior is not wired up by the platform description.
    VectorTable,
    /// Rely on the board's platform description to reset the CPU; just
    /// `LoadELF`. Optionally set `PerformanceInMips` (see [`Platform::mips`]).
    Board,
}

/// A target platform: one emulated machine's bring-up recipe.
#[derive(Debug, Clone, Copy)]
pub struct Platform {
    /// Short identifier, e.g. `"rp2040"`. Used in logs and work-dir names.
    pub name: &'static str,
    /// Source of the platform description.
    pub repl: ReplSource,
    /// CPU initialization strategy.
    pub init: CpuInit,
    /// CPU speed in MIPS, emitted as `cpu PerformanceInMips` when set.
    pub mips: Option<u32>,
    /// Name of the CAN peripheral connector to wire to the CAN hub in
    /// multi-machine runs, e.g. `"fdcan1"`. `None` disables CAN hub wiring.
    pub can_connector: Option<&'static str>,
    /// Console UART peripheral, e.g. `"sysbus.uart0"`. Its output is captured
    /// into [`HilOutput::uart`](crate::HilOutput::uart). `None` when the
    /// platform models no UART.
    pub uart: Option<&'static str>,
    /// Default simulation timeout in seconds.
    pub default_timeout_secs: u32,
}

impl Platform {
    /// RP2040 (Cortex-M0+). Generic Cortex-M bring-up via the ELF vector table.
    #[must_use]
    pub const fn rp2040() -> Self {
        Self {
            name: "rp2040",
            repl: ReplSource::Embedded(include_str!("repl/rp2040.repl")),
            init: CpuInit::VectorTable,
            mips: None,
            can_connector: None,
            uart: Some("sysbus.uart0"),
            default_timeout_secs: 10,
        }
    }

    /// nRF52840 (Cortex-M4F).
    #[must_use]
    pub const fn nrf52840() -> Self {
        Self {
            name: "nrf52840",
            repl: ReplSource::Embedded(include_str!("repl/nrf52840.repl")),
            init: CpuInit::VectorTable,
            mips: None,
            can_connector: None,
            uart: None,
            default_timeout_secs: 10,
        }
    }

    /// STM32F411 (Cortex-M4F).
    #[must_use]
    pub const fn stm32f4() -> Self {
        Self {
            name: "stm32f4",
            repl: ReplSource::Embedded(include_str!("repl/stm32f4.repl")),
            init: CpuInit::VectorTable,
            mips: None,
            can_connector: None,
            uart: None,
            default_timeout_secs: 10,
        }
    }

    /// STM32F303xB (Cortex-M4F), e.g. the ZSA Voyager keyboard.
    #[must_use]
    pub const fn stm32f3() -> Self {
        Self {
            name: "stm32f3",
            repl: ReplSource::Embedded(include_str!("repl/stm32f3.repl")),
            init: CpuInit::VectorTable,
            mips: None,
            can_connector: None,
            uart: None,
            default_timeout_secs: 10,
        }
    }

    /// STM32H7 Nucleo-H753ZI. Uses the board description bundled in the Renode
    /// image, runs at 125 MIPS, and bridges its FDCAN1 to the CAN hub.
    #[must_use]
    pub const fn stm32h7() -> Self {
        Self {
            name: "stm32h7",
            repl: ReplSource::ImagePath("platforms/boards/nucleo_h753zi.repl"),
            init: CpuInit::Board,
            mips: Some(125),
            can_connector: Some("fdcan1"),
            // The ST-LINK virtual COM port.
            uart: Some("sysbus.usart3"),
            default_timeout_secs: 15,
        }
    }

    /// Returns the platform description text or in-image path.
    ///
    /// For [`ReplSource::Embedded`] this is the `.repl` body (which callers may
    /// write to disk); for [`ReplSource::ImagePath`] it is the in-image path.
    #[must_use]
    pub fn repl_content(&self) -> &'static str {
        match self.repl {
            ReplSource::Embedded(s) | ReplSource::ImagePath(s) => s,
        }
    }
}

impl fmt::Display for Platform {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.name)
    }
}

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

    #[test]
    fn builtin_display_names() {
        assert_eq!(Platform::rp2040().to_string(), "rp2040");
        assert_eq!(Platform::nrf52840().to_string(), "nrf52840");
        assert_eq!(Platform::stm32f4().to_string(), "stm32f4");
        assert_eq!(Platform::stm32h7().to_string(), "stm32h7");
    }

    #[test]
    fn cortex_m_boards_use_vector_table() {
        assert_eq!(Platform::rp2040().init, CpuInit::VectorTable);
        assert_eq!(Platform::nrf52840().init, CpuInit::VectorTable);
        assert_eq!(Platform::stm32f4().init, CpuInit::VectorTable);
    }

    #[test]
    fn rp2040_repl_is_embedded_cortex_m0() {
        assert!(Platform::rp2040().repl_content().contains("cortex-m0+"));
        assert!(Platform::rp2040()
            .repl_content()
            .contains("uart0: UART.PL011"));
        assert_eq!(Platform::rp2040().uart, Some("sysbus.uart0"));
    }

    #[test]
    fn stm32h7_is_board_init_with_hub_connector() {
        let p = Platform::stm32h7();
        assert_eq!(p.init, CpuInit::Board);
        assert_eq!(p.mips, Some(125));
        assert_eq!(p.can_connector, Some("fdcan1"));
        assert!(p.repl_content().contains("nucleo_h753zi"));
    }

    #[test]
    fn custom_platform_via_struct_literal() {
        let p = Platform {
            name: "myboard",
            repl: ReplSource::Embedded("cpu: CPU.CortexM @ sysbus"),
            mips: Some(48),
            default_timeout_secs: 7,
            ..Platform::rp2040()
        };
        assert_eq!(p.to_string(), "myboard");
        assert_eq!(p.mips, Some(48));
        assert_eq!(p.default_timeout_secs, 7);
    }
}