use std::num::{NonZeroU32, NonZeroU64};
use crate::emulator::{Alias, Boot, CoreSpec, Property};
#[cfg(not(emulator_absent))]
pub(crate) const CORE: &str = "the ARMv7-M core";
pub(crate) const WINDOW_SEPARATOR: &str = "+";
const RESET_VECTOR: u64 = 0;
const SYSTEM_EXCEPTIONS: u64 = 16;
const VECTOR_BYTES: u64 = 4;
const TABLE_ALIGNMENT: u64 = 128;
pub(crate) const CLOCK_HZ: &str = "clock-hz";
pub(crate) const INTERRUPT_LINES: &str = "interrupt-lines";
pub(crate) const PRIORITY_BITS: &str = "priority-bits";
pub(crate) const REFCLK_DIVIDER: &str = "refclk-divider";
pub(crate) const PPB_WINDOWS: &str = "ppb-windows";
pub(crate) const FPU: &str = "fpu";
pub(crate) const MPU_REGIONS: &str = "mpu-regions";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum Variant {
M3,
M4,
M7,
}
impl Variant {
pub const ALL: [Self; 3] = [Self::M3, Self::M4, Self::M7];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::M3 => "cortex-m3",
Self::M4 => "cortex-m4",
Self::M7 => "cortex-m7",
}
}
#[must_use]
pub(crate) fn named(name: &str) -> Option<Self> {
Self::ALL.into_iter().find(|variant| variant.name() == name)
}
#[must_use]
pub(crate) const fn dsp(self) -> bool {
matches!(self, Self::M4 | Self::M7)
}
#[must_use]
pub(crate) const fn fpus(self) -> &'static [Fpu] {
match self {
Self::M3 => &[Fpu::None],
Self::M4 => &[Fpu::None, Fpu::FpV4Sp],
Self::M7 => &[Fpu::None, Fpu::FpV5Sp, Fpu::FpV5Dp],
}
}
#[must_use]
pub(crate) const fn mpus(self) -> &'static [MpuRegions] {
match self {
Self::M3 | Self::M4 => &[MpuRegions::None, MpuRegions::Eight],
Self::M7 => &[MpuRegions::None, MpuRegions::Eight, MpuRegions::Sixteen],
}
}
#[must_use]
pub(crate) const fn default_fpu(self) -> Fpu {
match self {
Self::M3 => Fpu::None,
Self::M4 => Fpu::FpV4Sp,
Self::M7 => Fpu::FpV5Dp,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Fpu {
None,
FpV4Sp,
FpV5Sp,
FpV5Dp,
}
impl Fpu {
pub(crate) const ALL: [Self; 4] = [Self::None, Self::FpV4Sp, Self::FpV5Sp, Self::FpV5Dp];
#[must_use]
pub(crate) const fn name(self) -> &'static str {
match self {
Self::None => "none",
Self::FpV4Sp => "fpv4-sp",
Self::FpV5Sp => "fpv5-sp",
Self::FpV5Dp => "fpv5-dp",
}
}
#[must_use]
pub(crate) fn named(name: &str) -> Option<Self> {
Self::ALL.into_iter().find(|fpu| fpu.name() == name)
}
}
impl std::fmt::Display for Fpu {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(self.name())
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum MpuRegions {
None = 0,
#[default]
Eight = 8,
Sixteen = 16,
}
impl MpuRegions {
pub(crate) const ALL: [Self; 3] = [Self::None, Self::Eight, Self::Sixteen];
#[must_use]
pub(crate) const fn count(self) -> u8 {
self as u8
}
#[must_use]
pub(crate) fn of(regions: u64) -> Option<Self> {
Self::ALL
.into_iter()
.find(|count| u64::from(count.count()) == regions)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Core {
pub variant: Variant,
pub clock_hz: NonZeroU64,
pub interrupt_lines: NonZeroU32,
pub priority_bits: u8,
pub refclk_divider: NonZeroU32,
pub instructions_per_second: NonZeroU64,
pub boot: &'static str,
}
impl Core {
#[must_use]
pub fn spec(self, ppb: &[crate::cpu::armv7m::Window]) -> CoreSpec {
CoreSpec {
processor: crate::cpu::armv7m::processor,
cpu: self.variant.name(),
properties: vec![
Property::new(CLOCK_HZ, self.clock_hz),
Property::new(INTERRUPT_LINES, self.interrupt_lines),
Property::new(PRIORITY_BITS, self.priority_bits),
Property::new(REFCLK_DIVIDER, self.refclk_divider),
Property::new(
PPB_WINDOWS,
ppb.iter()
.map(ToString::to_string)
.collect::<Vec<String>>()
.join(WINDOW_SEPARATOR),
),
],
instructions_per_second: self.instructions_per_second,
aliases: vec![Alias {
region: self.boot,
at: RESET_VECTOR,
}],
boot: Boot {
reset: RESET_VECTOR,
alignment: self.table_alignment(),
},
}
}
#[must_use]
pub fn spec_with_parts(
self,
ppb: &[crate::cpu::armv7m::Window],
fpu: Fpu,
mpu_regions: MpuRegions,
) -> CoreSpec {
let mut spec = self.spec(ppb);
spec.properties.extend([
Property::new(FPU, fpu),
Property::new(MPU_REGIONS, mpu_regions.count()),
]);
spec
}
fn table_alignment(self) -> NonZeroU64 {
let size = (SYSTEM_EXCEPTIONS + u64::from(self.interrupt_lines.get())) * VECTOR_BYTES;
NonZeroU64::new(size.next_power_of_two().max(TABLE_ALIGNMENT))
.expect("a power of two is above zero")
}
}
#[cfg(test)]
mod tests {
use super::*;
fn core() -> Core {
Core {
variant: Variant::M4,
clock_hz: NonZeroU64::new(168_000_000).unwrap(),
interrupt_lines: NonZeroU32::new(91).unwrap(),
priority_bits: 4,
refclk_divider: NonZeroU32::new(8).unwrap(),
instructions_per_second: NonZeroU64::new(250_000_000).unwrap(),
boot: "flash",
}
}
const WINDOWS: [crate::cpu::armv7m::Window; 2] = [
crate::cpu::armv7m::Window {
base: 0xE000_0000,
size: 0x1000,
},
crate::cpu::armv7m::Window {
base: 0xE000_1000,
size: 0x8,
},
];
fn rendered(core: Core) -> Vec<String> {
core.spec(&WINDOWS)
.properties
.iter()
.map(ToString::to_string)
.collect()
}
#[test]
fn the_properties_render_as_the_machine_spells_them() {
assert_eq!(
rendered(core()),
[
"clock-hz=168000000",
"interrupt-lines=91",
"priority-bits=4",
"refclk-divider=8",
"ppb-windows=0xe0000000:0x1000+0xe0001000:0x8",
]
);
assert_eq!(core().spec(&WINDOWS).cpu, "cortex-m4");
}
#[test]
fn a_part_that_names_its_units_adds_them_and_nothing_else() {
let plain = core().spec(&WINDOWS);
let named = core().spec_with_parts(&WINDOWS, Fpu::None, MpuRegions::Sixteen);
assert_eq!(named.properties[..plain.properties.len()], plain.properties);
assert_eq!(
named.properties[plain.properties.len()..]
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>(),
["fpu=none", "mpu-regions=16"]
);
}
#[test]
fn a_vector_table_is_aligned_to_its_size_and_never_less_than_128_bytes() {
let alignment = |lines| {
Core {
interrupt_lines: NonZeroU32::new(lines).unwrap(),
..core()
}
.spec(&WINDOWS)
.boot
.alignment
.get()
};
assert_eq!(alignment(91), 512, "107 entries are 428 bytes");
assert_eq!(alignment(1), 128, "17 entries are 68 bytes");
assert_eq!(alignment(16), 128, "32 entries are 128 bytes");
assert_eq!(alignment(17), 256);
}
#[test]
fn the_boot_region_is_the_one_the_core_also_finds_at_its_reset_vector() {
assert_eq!(
core().spec(&WINDOWS).aliases,
[Alias {
region: "flash",
at: 0
}]
);
let from_system = Core {
boot: "system",
..core()
};
assert_eq!(
from_system.spec(&WINDOWS).aliases,
[Alias {
region: "system",
at: 0
}],
"the SoC's boot configuration says which region, not the family"
);
}
}