pub mod isa;
pub mod sys;
mod dsp;
mod exec;
#[cfg(test)]
mod tests;
#[cfg(all(test, feature = "cpu-arm-aprofile"))]
mod differential;
#[cfg(all(test, feature = "std"))]
mod conformance;
#[cfg(all(test, feature = "std"))]
mod corpus;
#[cfg(all(test, feature = "std"))]
mod elf;
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt;
use crate::core::device::{Device, DeviceClass, Initiator, PropertySpec, RealizeCtx, ResetKind};
use crate::core::error::Result;
use crate::core::props::{Props, ValueKind};
use crate::core::registry::Registry;
use crate::core::sched::{Budget, Consumed};
use crate::core::space::{AddressSpace, MemAttrs, RequesterId};
use crate::core::state::{ChunkReader, ChunkWriter, Sink, Source};
use crate::core::sync::{self, AtomicU32, LockRank, Ordering};
use crate::core::value::{Endian, Width};
use crate::core::wire::{FanIn, Level, Resolve, WireId, WireSink};
use exec::{Exec, State};
use sys::{Exception, Sys};
pub use sys::{CPUID_CORTEX_M4, CPUID_CORTEX_M7};
pub mod xpsr {
pub const N: u32 = 1 << 31;
pub const Z: u32 = 1 << 30;
pub const C: u32 = 1 << 29;
pub const V: u32 = 1 << 28;
pub const Q: u32 = 1 << 27;
pub const T: u32 = 1 << 24;
pub const GE: u32 = 0xf << 16;
pub const IT_MASK: u32 = (0x3f << 10) | (3 << 25);
pub const EXCEPTION: u32 = 0x1ff;
pub const FLAGS: u32 = N | Z | C | V | Q;
pub const WRITABLE: u32 = FLAGS | GE | T | IT_MASK;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Extensions {
pub dsp: bool,
pub fp: bool,
pub mpu: bool,
}
impl Extensions {
pub const CORTEX_M3: Extensions = Extensions {
dsp: false,
fp: false,
mpu: true,
};
pub const CORTEX_M4: Extensions = Extensions {
dsp: true,
fp: false,
mpu: true,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub requester: RequesterId,
pub endian: Endian,
pub ext: Extensions,
pub cpuid: u32,
pub priority_bits: u8,
}
impl Config {
pub const CORTEX_M4: Config = Config {
requester: RequesterId::ANONYMOUS,
endian: Endian::Little,
ext: Extensions::CORTEX_M4,
cpuid: CPUID_CORTEX_M4,
priority_bits: 3,
};
pub const CORTEX_M7: Config = Config {
cpuid: CPUID_CORTEX_M7,
priority_bits: 4,
..Config::CORTEX_M4
};
pub const CORTEX_M3: Config = Config {
ext: Extensions::CORTEX_M3,
cpuid: 0x412f_c231,
..Config::CORTEX_M4
};
#[must_use]
pub const fn with_requester(mut self, id: RequesterId) -> Config {
self.requester = id;
self
}
#[must_use]
pub const fn with_endian(mut self, endian: Endian) -> Config {
self.endian = endian;
self
}
#[must_use]
pub const fn with_priority_bits(mut self, bits: u8) -> Config {
self.priority_bits = if bits == 0 {
1
} else if bits > 8 {
8
} else {
bits
};
self
}
}
impl Default for Config {
fn default() -> Config {
Config::CORTEX_M4
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Regs {
pub r: [u32; 16],
pub msp: u32,
pub psp: u32,
pub xpsr: u32,
pub primask: bool,
pub faultmask: bool,
pub basepri: u8,
pub control: u32,
}
impl Regs {
#[must_use]
pub const fn new() -> Regs {
Regs {
r: [0; 16],
msp: 0,
psp: 0,
xpsr: xpsr::T,
primask: false,
faultmask: false,
basepri: 0,
control: 0,
}
}
#[must_use]
pub const fn exception(&self) -> Exception {
Exception((self.xpsr & xpsr::EXCEPTION) as u16)
}
#[must_use]
pub const fn in_handler(&self) -> bool {
self.xpsr & xpsr::EXCEPTION != 0
}
}
impl Default for Regs {
fn default() -> Regs {
Regs::new()
}
}
impl fmt::Display for Regs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, value) in self.r.iter().enumerate() {
write!(f, "r{i}:{value:08x} ")?;
}
write!(
f,
"xpsr:{:08x} [{}{}{}{}{}] {}",
self.xpsr,
if self.xpsr & xpsr::N != 0 { 'N' } else { 'n' },
if self.xpsr & xpsr::Z != 0 { 'Z' } else { 'z' },
if self.xpsr & xpsr::C != 0 { 'C' } else { 'c' },
if self.xpsr & xpsr::V != 0 { 'V' } else { 'v' },
if self.xpsr & xpsr::Q != 0 { 'Q' } else { 'q' },
self.exception()
)
}
}
const IRQ_WORDS: usize = Exception::COUNT / 32;
#[derive(Debug)]
struct Lines {
level: [AtomicU32; IRQ_WORDS],
}
impl Default for Lines {
fn default() -> Lines {
Lines {
level: [const { AtomicU32::new(0) }; IRQ_WORDS],
}
}
}
impl Lines {
fn set(&self, irq: u16, asserted: bool) {
let n = usize::from(irq);
if n >= Exception::COUNT - 16 {
return;
}
let bit = 1u32 << (n % 32);
if asserted {
self.level[n / 32].fetch_or(bit, Ordering::Release);
} else {
self.level[n / 32].fetch_and(!bit, Ordering::Release);
}
}
fn get(&self, irq: u16) -> bool {
let n = usize::from(irq);
n < Exception::COUNT - 16
&& self.level[n / 32].load(Ordering::Acquire) & (1 << (n % 32)) != 0
}
fn snapshot(&self) -> [u32; IRQ_WORDS] {
let mut out = [0u32; IRQ_WORDS];
for (slot, atomic) in out.iter_mut().zip(self.level.iter()) {
*slot = atomic.load(Ordering::Acquire);
}
out
}
fn restore(&self, values: &[u32; IRQ_WORDS]) {
for (atomic, value) in self.level.iter().zip(values.iter()) {
atomic.store(*value, Ordering::Release);
}
}
}
struct Session {
state: State,
space: Option<Arc<AddressSpace>>,
}
impl fmt::Debug for Session {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Session")
.field("state", &self.state)
.field("space", &self.space.as_ref().map(|s| s.name()))
.finish()
}
}
#[derive(Debug)]
pub struct ArmV7m {
cfg: Config,
lines: Lines,
session: sync::Mutex<Session>,
}
impl ArmV7m {
#[must_use]
pub fn new(cfg: Config) -> ArmV7m {
let cfg = Config {
ext: Extensions {
fp: false,
..cfg.ext
},
..cfg
};
ArmV7m {
cfg,
lines: Lines::default(),
session: sync::Mutex::with_rank(
LockRank::BUS,
Session {
state: State::new(&cfg),
space: None,
},
),
}
}
pub fn from_props(props: &Props) -> Result<ArmV7m> {
let mut r = props.reader();
let part = if props.contains("part") {
r.require_enum("part", &["cortex-m3", "cortex-m4", "cortex-m7"])?
} else {
"cortex-m4"
};
let big_endian = r.or("big-endian", false)?;
let priority_bits = r.or_range("priority-bits", 0u64, 0..=8)?;
let dsp_override = r.or("dsp", true)?;
let mpu_override = r.or("mpu", true)?;
r.finish()?;
let base = match part {
"cortex-m3" => Config::CORTEX_M3,
"cortex-m4" => Config::CORTEX_M4,
"cortex-m7" => Config::CORTEX_M7,
_ => Config::CORTEX_M4,
};
let mut cfg = Config {
endian: if big_endian {
Endian::Big
} else {
Endian::Little
},
ext: Extensions {
dsp: base.ext.dsp && dsp_override,
fp: false,
mpu: base.ext.mpu && mpu_override,
},
..base
};
if priority_bits != 0 {
cfg = cfg.with_priority_bits(priority_bits as u8);
}
Ok(ArmV7m::new(cfg))
}
#[must_use]
pub fn config(&self) -> Config {
self.cfg
}
pub fn attach_space(&self, space: Arc<AddressSpace>) {
self.session.lock().space = Some(space);
}
#[must_use]
pub fn space(&self) -> Option<Arc<AddressSpace>> {
self.session.lock().space.clone()
}
#[must_use]
pub fn regs(&self) -> Regs {
let s = &self.session.lock().state;
Regs {
r: s.r,
msp: s.msp(),
psp: s.psp(),
xpsr: s.xpsr,
primask: s.primask,
faultmask: s.faultmask,
basepri: s.basepri,
control: s.control,
}
}
pub fn set_regs(&self, regs: Regs) {
let s = &mut self.session.lock().state;
s.r = regs.r;
s.xpsr = regs.xpsr;
s.primask = regs.primask;
s.faultmask = regs.faultmask;
s.basepri = regs.basepri;
s.control = regs.control;
s.sp_is_psp = false;
s.r[13] = regs.msp;
s.sp_other = regs.psp;
s.sync_stack();
s.r[13] = if s.sp_is_psp { regs.psp } else { regs.msp };
s.sp_other = if s.sp_is_psp { regs.msp } else { regs.psp };
}
#[must_use]
pub fn reg(&self, index: u8) -> u32 {
self.session.lock().state.r[(index & 0xf) as usize]
}
pub fn set_reg(&self, index: u8, value: u32) {
self.session.lock().state.r[(index & 0xf) as usize] = value;
}
#[must_use]
pub fn pc(&self) -> u32 {
self.session.lock().state.r[15]
}
pub fn set_pc(&self, value: u32) {
self.session.lock().state.r[15] = value;
}
#[must_use]
pub fn xpsr(&self) -> u32 {
self.session.lock().state.xpsr
}
pub fn set_xpsr(&self, value: u32) {
let s = &mut self.session.lock().state;
s.xpsr = value;
s.sync_stack();
}
#[must_use]
pub fn msp(&self) -> u32 {
self.session.lock().state.msp()
}
#[must_use]
pub fn psp(&self) -> u32 {
self.session.lock().state.psp()
}
#[must_use]
pub fn current_exception(&self) -> Exception {
self.session.lock().state.current_exception()
}
#[must_use]
pub fn execution_priority(&self) -> i32 {
self.session.lock().state.execution_priority()
}
#[must_use]
pub fn cycles(&self) -> u64 {
self.session.lock().state.cycles
}
#[must_use]
pub fn is_asleep(&self) -> bool {
self.session.lock().state.asleep
}
#[must_use]
pub fn is_locked_up(&self) -> bool {
self.session.lock().state.locked_up
}
#[must_use]
pub fn reset_pending(&self) -> bool {
self.session.lock().state.reset_pending
}
#[must_use]
pub fn reset_requested(&self) -> bool {
self.session.lock().state.sys.reset_requested
}
pub fn clear_reset_request(&self) {
self.session.lock().state.sys.reset_requested = false;
}
#[must_use]
pub fn bus_faults(&self) -> (u64, u32) {
let s = &self.session.lock().state;
(s.faults, s.last_fault)
}
#[must_use]
pub fn last_svc(&self) -> u8 {
self.session.lock().state.last_svc
}
#[must_use]
pub fn last_bkpt(&self) -> u8 {
self.session.lock().state.last_bkpt
}
pub fn with_sys<T>(&self, f: impl FnOnce(&mut Sys) -> T) -> T {
f(&mut self.session.lock().state.sys)
}
#[must_use]
pub fn vtor(&self) -> u32 {
self.session.lock().state.sys.vtor
}
pub fn set_vtor(&self, value: u32) {
self.session.lock().state.sys.vtor = value & 0xffff_ff80;
}
pub fn set_irq(&self, irq: u16, asserted: bool) {
self.lines.set(irq, asserted);
}
#[must_use]
pub fn irq_asserted(&self, irq: u16) -> bool {
self.lines.get(irq)
}
pub fn pend_irq(&self, irq: u16) {
if usize::from(irq) + 16 < Exception::COUNT {
self.session
.lock()
.state
.sys
.set_pending(Exception(irq + 16), true);
}
}
pub fn request_reset(&self) {
self.session.lock().state.reset_pending = true;
}
pub fn step(&self) -> u64 {
let external = self.lines.snapshot();
let mut session = self.session.lock();
let Session { state, space } = &mut *session;
let Some(space) = space.clone() else {
return 0;
};
Exec::new(state, &space, &self.cfg).step(&external)
}
pub fn run(&self, budget: u64) -> u64 {
let mut used = 0;
while used < budget {
let n = self.step();
if n == 0 {
break;
}
used += n;
}
used
}
#[must_use]
pub fn disassemble(&self, addr: u32, count: usize) -> Vec<Listed> {
let Some(space) = self.space() else {
return Vec::new();
};
let read = |a: u32| {
space
.read(u64::from(a), Width::U16, MemAttrs::DEBUG)
.ok()
.map(|v| v as u16)
};
let mut out = Vec::with_capacity(count);
let mut pc = addr;
for _ in 0..count {
let Some(first) = read(pc) else { break };
let wide = isa::is_32bit(first);
let second = if wide {
read(pc.wrapping_add(2))
} else {
Some(0)
};
let Some(second) = second else { break };
out.push(Listed {
addr: pc,
raw: (u32::from(first) << 16) | u32::from(second),
width: if wide { 4 } else { 2 },
insn: isa::decode(first, second),
});
pc = pc.wrapping_add(if wide { 4 } else { 2 });
}
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Listed {
pub addr: u32,
pub raw: u32,
pub width: u32,
pub insn: isa::Insn,
}
impl fmt::Display for Listed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.width == 2 {
write!(
f,
"{:08x}: {:04x} {}",
self.addr,
self.raw >> 16,
self.insn
)
} else {
write!(
f,
"{:08x}: {:04x} {:04x} {}",
self.addr,
self.raw >> 16,
self.raw & 0xffff,
self.insn
)
}
}
}
pub static CLASS: DeviceClass = DeviceClass {
name: "cpu.arm.v7m",
version: 1,
summary: "ARMv7E-M (Cortex-M4/M7 class) CPU core with Thumb-2, DSP, NVIC and MPU",
properties: &[
PropertySpec {
name: "part",
kind: ValueKind::Str,
required: false,
summary: "which part to model: cortex-m3, cortex-m4 or cortex-m7",
},
PropertySpec {
name: "big-endian",
kind: ValueKind::Bool,
required: false,
summary: "use BE-8 byte order for data accesses",
},
PropertySpec {
name: "priority-bits",
kind: ValueKind::Uint,
required: false,
summary: "how many NVIC priority bits are implemented (1-8; 0 keeps the part default)",
},
PropertySpec {
name: "dsp",
kind: ValueKind::Bool,
required: false,
summary: "whether the DSP (E) extension is present",
},
PropertySpec {
name: "mpu",
kind: ValueKind::Bool,
required: false,
summary: "whether a PMSAv7 memory protection unit is present",
},
],
construct: |props| Ok(Box::new(ArmV7m::from_props(props)?)),
};
pub fn register(reg: &mut Registry) -> Result<()> {
reg.add(&CLASS)
}
impl Device for ArmV7m {
fn class(&self) -> &'static DeviceClass {
&CLASS
}
fn realize(&self, ctx: &mut RealizeCtx<'_>) -> Result<()> {
if self.session.lock().space.is_none() {
return Err(ctx.error("no address space attached to this core"));
}
Ok(())
}
fn reset(&self, kind: ResetKind) {
{
let mut session = self.session.lock();
if kind == ResetKind::Cold {
session.state = State::new(&self.cfg);
} else {
session.state.reset_pending = true;
session.state.asleep = false;
session.state.locked_up = false;
}
}
if kind == ResetKind::Cold {
self.lines.restore(&[0; IRQ_WORDS]);
}
}
fn save(&self, w: &mut ChunkWriter<'_>) -> Result<()> {
let state = self.session.lock().state.clone();
for value in state.r {
w.write_u32(value)?;
}
w.write_u32(state.sp_other)?;
w.write_bool(state.sp_is_psp)?;
w.write_u32(state.xpsr)?;
w.write_bool(state.primask)?;
w.write_bool(state.faultmask)?;
w.write_u8(state.basepri)?;
w.write_u32(state.control)?;
w.write_u64(state.cycles)?;
w.write_bool(state.asleep)?;
w.write_bool(state.event)?;
w.write_bool(state.reset_pending)?;
w.write_bool(state.locked_up)?;
w.write_bool(state.exclusive.is_some())?;
w.write_u32(state.exclusive.unwrap_or(0))?;
w.write_u64(state.faults)?;
w.write_u32(state.last_fault)?;
w.write_u8(state.last_svc)?;
w.write_u8(state.last_bkpt)?;
save_sys(&state.sys, w)?;
for word in self.lines.snapshot() {
w.write_u32(word)?;
}
Ok(())
}
fn load(&self, r: &mut ChunkReader<'_>) -> Result<()> {
let mut state = State::new(&self.cfg);
for value in &mut state.r {
*value = r.read_u32()?;
}
state.sp_other = r.read_u32()?;
state.sp_is_psp = r.read_bool()?;
state.xpsr = r.read_u32()?;
state.primask = r.read_bool()?;
state.faultmask = r.read_bool()?;
state.basepri = r.read_u8()?;
state.control = r.read_u32()?;
state.cycles = r.read_u64()?;
state.asleep = r.read_bool()?;
state.event = r.read_bool()?;
state.reset_pending = r.read_bool()?;
state.locked_up = r.read_bool()?;
let has_exclusive = r.read_bool()?;
let exclusive = r.read_u32()?;
state.exclusive = has_exclusive.then_some(exclusive);
state.faults = r.read_u64()?;
state.last_fault = r.read_u32()?;
state.last_svc = r.read_u8()?;
state.last_bkpt = r.read_u8()?;
load_sys(&mut state.sys, r)?;
let mut lines = [0u32; IRQ_WORDS];
for word in &mut lines {
*word = r.read_u32()?;
}
self.session.lock().state = state;
self.lines.restore(&lines);
Ok(())
}
fn is_runnable(&self) -> bool {
true
}
fn run(&self, budget: Budget) -> Consumed {
Consumed::new(self.run(budget.ticks))
}
}
fn save_sys(sys: &Sys, w: &mut ChunkWriter<'_>) -> Result<()> {
for word in sys.enable {
w.write_u32(word)?;
}
for word in sys.pending {
w.write_u32(word)?;
}
for word in sys.active {
w.write_u32(word)?;
}
for value in sys.priority {
w.write_u8(value)?;
}
w.write_u8(sys.priority_bits)?;
w.write_u32(sys.vtor)?;
w.write_u8(sys.prigroup)?;
w.write_u32(sys.scr)?;
w.write_u32(sys.ccr)?;
w.write_u32(sys.shcsr)?;
w.write_u32(sys.cfsr)?;
w.write_u32(sys.hfsr)?;
w.write_u32(sys.mmfar)?;
w.write_u32(sys.bfar)?;
w.write_u32(sys.afsr)?;
w.write_u32(sys.cpacr)?;
w.write_u32(sys.cpuid)?;
w.write_bool(sys.reset_requested)?;
w.write_u32(sys.syst_csr)?;
w.write_u32(sys.syst_rvr)?;
w.write_u32(sys.syst_cvr)?;
w.write_u32(sys.syst_calib)?;
w.write_u32(sys.mpu_ctrl)?;
w.write_u32(sys.mpu_rnr)?;
w.write_u8(sys.mpu_regions)?;
for value in sys.mpu_rbar {
w.write_u32(value)?;
}
for value in sys.mpu_rasr {
w.write_u32(value)?;
}
Ok(())
}
fn load_sys(sys: &mut Sys, r: &mut ChunkReader<'_>) -> Result<()> {
for word in &mut sys.enable {
*word = r.read_u32()?;
}
for word in &mut sys.pending {
*word = r.read_u32()?;
}
for word in &mut sys.active {
*word = r.read_u32()?;
}
for value in &mut sys.priority {
*value = r.read_u8()?;
}
sys.priority_bits = r.read_u8()?;
sys.vtor = r.read_u32()?;
sys.prigroup = r.read_u8()?;
sys.scr = r.read_u32()?;
sys.ccr = r.read_u32()?;
sys.shcsr = r.read_u32()?;
sys.cfsr = r.read_u32()?;
sys.hfsr = r.read_u32()?;
sys.mmfar = r.read_u32()?;
sys.bfar = r.read_u32()?;
sys.afsr = r.read_u32()?;
sys.cpacr = r.read_u32()?;
sys.cpuid = r.read_u32()?;
sys.reset_requested = r.read_bool()?;
sys.syst_csr = r.read_u32()?;
sys.syst_rvr = r.read_u32()?;
sys.syst_cvr = r.read_u32()?;
sys.syst_calib = r.read_u32()?;
sys.mpu_ctrl = r.read_u32()?;
sys.mpu_rnr = r.read_u32()?;
sys.mpu_regions = r.read_u8()?;
for value in &mut sys.mpu_rbar {
*value = r.read_u32()?;
}
for value in &mut sys.mpu_rasr {
*value = r.read_u32()?;
}
Ok(())
}
impl Initiator for ArmV7m {
fn requester(&self) -> RequesterId {
self.cfg.requester
}
}
#[derive(Debug)]
pub struct InterruptPin {
cpu: Arc<ArmV7m>,
irq: u16,
inputs: FanIn,
resolve: Resolve,
}
impl InterruptPin {
#[must_use]
pub fn new(cpu: Arc<ArmV7m>, irq: u16, sources: &[WireId]) -> InterruptPin {
InterruptPin {
cpu,
irq,
inputs: FanIn::new(sources),
resolve: Resolve::Or,
}
}
#[must_use]
pub fn with_resolve(mut self, resolve: Resolve) -> InterruptPin {
self.resolve = resolve;
self
}
#[must_use]
pub fn irq(&self) -> u16 {
self.irq
}
#[must_use]
pub fn inputs(&self) -> &FanIn {
&self.inputs
}
}
impl WireSink for InterruptPin {
fn set_level(&self, src: WireId, _line: u32, level: Level) {
self.inputs.set(src, level);
let asserted = self.inputs.resolve(self.resolve).is_high();
self.cpu.set_irq(self.irq, asserted);
}
}