#![expect(
unsafe_code,
reason = "generated code runs on the core through a raw pointer, and reaches the host's memory"
)]
use std::collections::{BTreeMap, BTreeSet};
use std::ptr::NonNull;
use serde::{Deserialize, Serialize};
use super::exception::Refused;
use super::itm::{Itm, Writer};
use super::mpu::{Context, Kind, Mpu};
use super::registers::{
self, ccr, control, cpacr, demcr, dfsr, dhcsr, exc_return, fpcar, fpccr, fpdscr, is_set, masks,
set, vtor,
};
use super::scs::{Masks, SCS_BASE, SCS_SIZE, Scs};
use super::semihosting;
use super::state::State;
use super::translate::{self, Attributes, Fault, LONGEST_BLOCK, Size, Start, block_key, status};
use super::{PPB_BASE, PPB_SIZE};
use crate::emulator::core::{Blocks, Jit, Pages, Tier};
use crate::emulator::core::{PAGE_BITS, PAGE_SIZE};
use crate::emulator::{
Access, AccessKind, BitAlias, CoreBus, CoreSpec, Error, Exit, HaltReason, HostCall, Processor,
Queued,
};
use crate::machine::armv7m::{
CLOCK_HZ, CORE, FPU, Fpu, INTERRUPT_LINES, MPU_REGIONS, MpuRegions, PPB_WINDOWS, PRIORITY_BITS,
REFCLK_DIVIDER, Variant, WINDOW_SEPARATOR,
};
use crate::soc::{Endian, Width};
use crate::time::VirtualNanos;
pub(crate) mod offsets {
use super::Family;
pub(crate) const STATE: usize = std::mem::offset_of!(Family, state);
pub(crate) const TABLE: i32 = offset(std::mem::offset_of!(Family, table));
pub(crate) const BUDGET: i32 = offset(std::mem::offset_of!(Family, budget));
pub(crate) const RETURNING: i32 = offset(std::mem::offset_of!(Family, returning));
pub(crate) const LINK: i32 = offset(std::mem::offset_of!(Family, link));
pub(crate) const CPACR: i32 =
offset(std::mem::offset_of!(Family, system) + std::mem::offset_of!(super::System, cpacr));
pub(crate) const FPCCR: i32 =
offset(std::mem::offset_of!(Family, system) + std::mem::offset_of!(super::System, fpccr));
#[expect(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
reason = "asserted below 1 << 16, and a const fn has no try_from"
)]
const fn offset(bytes: usize) -> i32 {
assert!(bytes < 1 << 16, "near the start");
bytes as i32
}
}
const CCR_WRITABLE: u32 = registers::mask(ccr::STKALIGN)
| registers::mask(ccr::BFHFNMIGN)
| registers::mask(ccr::DIV_0_TRP)
| registers::mask(ccr::UNALIGN_TRP)
| registers::mask(ccr::USERSETMPEND)
| registers::mask(ccr::NONBASETHRDENA);
const FPCCR_WRITABLE: u32 = registers::mask(fpccr::ASPEN)
| registers::mask(fpccr::LSPEN)
| registers::mask(fpccr::MONRDY)
| registers::mask(fpccr::BFRDY)
| registers::mask(fpccr::MMRDY)
| registers::mask(fpccr::HFRDY)
| registers::mask(fpccr::THREAD)
| registers::mask(fpccr::USER)
| registers::mask(fpccr::LSPACT);
const CPACR_FP: u32 = registers::mask(cpacr::CP10) | registers::mask(cpacr::CP11);
const FPCCR_RESET: u32 = registers::mask(fpccr::ASPEN) | registers::mask(fpccr::LSPEN);
const FPDSCR_WRITABLE: u32 = registers::mask(fpdscr::AHP)
| registers::mask(fpdscr::DN)
| registers::mask(fpdscr::FZ)
| registers::mask(fpdscr::RMODE);
const CPUID: u32 = 0x410F_C241;
pub(super) const ICSR: u64 = 0xD04;
const DHCSR: u64 = 0xDF0;
const FP_REGISTERS: std::ops::RangeInclusive<u64> = 0xF34..=0xF4B;
const MVFR0: u64 = 0xF40;
const MVFR1: u64 = 0xF44;
const MVFR2: u64 = 0xF48;
fn fpv5_feature(fpu: Fpu, offset: u64) -> u32 {
let double = fpu == Fpu::FpV5Dp;
match offset {
MVFR0 if double => 0x1011_0221,
MVFR0 => 0x1011_0021,
MVFR1 if double => 0x1200_0011,
MVFR1 => 0x1100_0011,
_ => 0x0000_0040,
}
}
const ID_ISAR_WITHOUT_DSP: [u32; 3] = [0x0211_1000, 0x2111_2231, 0x0111_1110];
const ID_ISAR1: u64 = 0xD64;
const CFSR: u64 = 0xD28;
const MMFAR: u64 = 0xD34;
const BFAR: u64 = 0xD38;
const STIR: u64 = 0xF00;
const ITM: std::ops::Range<u64> = 0xE000_0000..0xE000_1000;
const BIT_BANDS: [(u32, u32); 2] = [(0x2000_0000, 0x2200_0000), (0x4000_0000, 0x4200_0000)];
const BIT_BAND_ALIAS_SIZE: u32 = 0x0200_0000;
const ALIAS_BYTES_PER_BYTE: u32 = 32;
const ALIAS_BYTES_PER_BIT: u32 = 4;
const BYTE_BITS: u32 = 8;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct System {
pub(crate) vtor: u32,
pub(crate) ccr: u32,
pub(crate) cfsr: u32,
pub(crate) mmfar: u32,
pub(crate) bfar: u32,
pub(crate) cpacr: u32,
pub(crate) fpccr: u32,
pub(crate) fpcar: u32,
pub(crate) fpdscr: u32,
pub(crate) sleep_on_exit: bool,
pub(crate) event_on_pending: bool,
}
impl Default for System {
fn default() -> Self {
Self {
vtor: 0,
ccr: registers::mask(ccr::STKALIGN),
cfsr: 0,
mmfar: 0,
bfar: 0,
cpacr: 0,
fpccr: FPCCR_RESET,
fpcar: 0,
fpdscr: 0,
sleep_on_exit: false,
event_on_pending: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Master {
Privileged,
Unprivileged,
Debugger,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Features {
pub(crate) variant: Variant,
pub(crate) dsp: bool,
pub(crate) fpu: Fpu,
pub(crate) endian: Endian,
}
impl Default for Features {
fn default() -> Self {
Self::of(Variant::M4, Variant::M4.default_fpu())
}
}
impl Features {
pub(crate) fn of(variant: Variant, fpu: Fpu) -> Self {
Self {
variant,
dsp: variant.dsp(),
fpu,
endian: Endian::Little,
}
}
pub(crate) fn fp(self) -> bool {
self.fpu != Fpu::None
}
pub(crate) fn control(self) -> u32 {
let base = registers::mask(control::NPRIV) | registers::mask(control::SPSEL);
if self.fp() {
base | registers::mask(control::FPCA)
} else {
base
}
}
}
#[repr(C)]
pub(crate) struct Family {
pub(crate) state: State,
table: *const usize,
budget: u32,
returning: u32,
link: usize,
entered: u32,
pub(crate) features: Features,
pub(crate) system: System,
pub(crate) scs: Scs,
pub(crate) mpu: Mpu,
pages: Pages,
jit: Jit,
blocks: Blocks,
most_blocks: u64,
breakpoints: BTreeMap<u64, u32>,
looping: BTreeSet<u64>,
passing: Option<u64>,
retired: u64,
bus: Option<NonNull<&'static mut dyn CoreBus>>,
windows: Vec<std::ops::Range<u64>>,
bit_band: bool,
reset_requested: bool,
held: bool,
pub(super) debugger: bool,
pub(super) halted: u32,
in_debug_state: bool,
pub(super) host_calling: bool,
itm: Itm,
traced: Vec<u8>,
unlinked: Option<u32>,
carried: u32,
quiet: Option<(u64, u32, u32, u32, u32, u32)>,
panicked: Option<Box<dyn std::any::Any + Send>>,
}
impl std::fmt::Debug for Family {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("Family")
.field("state", &self.state)
.field("system", &self.system)
.field("retired", &self.retired)
.field("blocks", &self.blocks.len())
.finish_non_exhaustive()
}
}
unsafe impl Send for Family {}
const ADDRESS_SPACE: u64 = 1 << 32;
const MOST_INTERRUPT_LINES: u16 = 496;
fn optional<'a>(spec: &'a CoreSpec, name: &str) -> Option<&'a str> {
spec.properties
.iter()
.find(|property| property.name == name)
.map(|property| property.value.as_str())
}
fn property<'a>(spec: &'a CoreSpec, name: &str) -> Result<&'a str, Error> {
optional(spec, name).ok_or_else(|| Error::Machine(format!("{CORE} needs the property {name}")))
}
fn parts(spec: &CoreSpec) -> Result<(Variant, Fpu, MpuRegions), Error> {
let cpu = spec.cpu;
let variant = Variant::named(cpu)
.ok_or_else(|| Error::Machine(format!("the core has no CPU called {cpu}")))?;
let fpu = match optional(spec, FPU) {
Some(name) => Fpu::named(name).ok_or_else(|| {
Error::Machine(format!("{CORE} has no floating-point unit called {name}"))
})?,
None => variant.default_fpu(),
};
if !variant.fpus().contains(&fpu) {
return Err(Error::Machine(format!("a {cpu} can't have {fpu}")));
}
let regions = match optional(spec, MPU_REGIONS) {
Some(_) => {
let count = number(spec, MPU_REGIONS)?;
MpuRegions::of(count)
.ok_or_else(|| Error::Machine(format!("{CORE} has no MPU of {count} regions")))?
}
None => MpuRegions::default(),
};
if !variant.mpus().contains(®ions) {
return Err(Error::Machine(format!(
"a {cpu} can't have an MPU of {} regions",
regions.count()
)));
}
Ok((variant, fpu, regions))
}
fn number(spec: &CoreSpec, name: &str) -> Result<u64, Error> {
let value = property(spec, name)?;
value
.parse()
.map_err(|_unparsed| Error::Machine(format!("{CORE}'s {name} of {value:?} isn't a number")))
}
fn window(rendered: &str) -> Option<std::ops::Range<u64>> {
let (base, size) = rendered.split_once(':')?;
let parse = |hex: &str| u64::from_str_radix(hex.trim_start_matches("0x"), 16).ok();
let base = parse(base)?;
Some(base..base.checked_add(parse(size)?)?)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Position {
Instruction {
pc: u32,
index: u32,
},
Processor,
Vector,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Request {
pub(crate) address: u32,
pub(crate) size: Size,
pub(crate) write: Option<u32>,
}
impl Request {
pub(crate) fn read(address: u32, size: Size) -> Self {
Self {
address,
size,
write: None,
}
}
pub(crate) fn write(address: u32, size: Size, value: u32) -> Self {
Self {
address,
size,
write: Some(value & mask(size)),
}
}
}
macro_rules! target_xml {
($($floating:expr)?) => {
concat!(
r#"<?xml version="1.0"?><!DOCTYPE target SYSTEM "gdb-target.dtd"><target>"#,
"<architecture>arm</architecture>",
r#"<feature name="org.gnu.gdb.arm.m-profile">"#,
r#"<reg name="r0" bitsize="32"/><reg name="r1" bitsize="32"/><reg name="r2" bitsize="32"/>"#,
r#"<reg name="r3" bitsize="32"/><reg name="r4" bitsize="32"/><reg name="r5" bitsize="32"/>"#,
r#"<reg name="r6" bitsize="32"/><reg name="r7" bitsize="32"/><reg name="r8" bitsize="32"/>"#,
r#"<reg name="r9" bitsize="32"/><reg name="r10" bitsize="32"/><reg name="r11" bitsize="32"/>"#,
r#"<reg name="r12" bitsize="32"/><reg name="sp" bitsize="32" type="data_ptr"/>"#,
r#"<reg name="lr" bitsize="32"/><reg name="pc" bitsize="32" type="code_ptr"/>"#,
r#"<reg name="xpsr" bitsize="32"/></feature>"#,
$($floating,)?
r#"<feature name="org.gnu.gdb.arm.m-system">"#,
r#"<reg name="msp" bitsize="32" type="data_ptr"/><reg name="psp" bitsize="32" type="data_ptr"/>"#,
r#"<reg name="primask" bitsize="32"/><reg name="basepri" bitsize="32"/>"#,
r#"<reg name="faultmask" bitsize="32"/><reg name="control" bitsize="32"/></feature>"#,
"</target>",
)
};
}
macro_rules! floating_point_registers {
() => {
concat!(
r#"<feature name="org.gnu.gdb.arm.vfp">"#,
r#"<reg name="d0" bitsize="64" type="ieee_double"/><reg name="d1" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d2" bitsize="64" type="ieee_double"/><reg name="d3" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d4" bitsize="64" type="ieee_double"/><reg name="d5" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d6" bitsize="64" type="ieee_double"/><reg name="d7" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d8" bitsize="64" type="ieee_double"/><reg name="d9" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d10" bitsize="64" type="ieee_double"/><reg name="d11" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d12" bitsize="64" type="ieee_double"/><reg name="d13" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="d14" bitsize="64" type="ieee_double"/><reg name="d15" bitsize="64" type="ieee_double"/>"#,
r#"<reg name="fpscr" bitsize="32" type="int" group="float"/></feature>"#,
)
};
}
impl Family {
pub(crate) fn new(spec: &CoreSpec, endian: Endian) -> Result<Box<Self>, Error> {
let (variant, fpu, regions) = parts(spec)?;
let lines = number(spec, INTERRUPT_LINES)?;
let lines = u16::try_from(lines)
.ok()
.filter(|&lines| lines <= MOST_INTERRUPT_LINES)
.ok_or_else(|| {
Error::Machine(format!(
"{CORE} can't have {lines} interrupt lines, since B3.4 allows at most {MOST_INTERRUPT_LINES}"
))
})?;
let priority_bits = number(spec, PRIORITY_BITS)?;
let priority_bits = u8::try_from(priority_bits)
.ok()
.filter(|bits| *bits == 0 || (3..=8).contains(bits))
.ok_or_else(|| {
Error::Machine(format!(
"{CORE} can't have {priority_bits} priority bits, since B1.5.4 allows 3 to 8"
))
})?;
let windows = property(spec, PPB_WINDOWS)?
.split(WINDOW_SEPARATOR)
.filter(|rendered| !rendered.is_empty())
.map(|rendered| {
window(rendered).ok_or_else(|| {
Error::Machine(format!("{CORE}'s window {rendered:?} isn't base:size"))
})
})
.collect::<Result<_, _>>()?;
let pages = Pages::new();
let mut family = Box::new(Self {
state: State::default(),
table: pages.table(),
budget: 0,
returning: 0,
link: 0,
entered: 0,
features: Features {
endian,
..Features::of(variant, fpu)
},
system: System::default(),
scs: Scs::new(
lines,
if priority_bits == 0 { 8 } else { priority_bits },
number(spec, CLOCK_HZ)?,
u32::try_from(number(spec, REFCLK_DIVIDER)?).unwrap_or(u32::MAX),
)
.endian(endian),
mpu: Mpu::new(regions.count()),
pages,
jit: Jit::new()?,
blocks: Blocks::default(),
breakpoints: BTreeMap::new(),
looping: BTreeSet::new(),
most_blocks: MOST_BLOCKS,
passing: None,
retired: 0,
bus: None,
windows,
bit_band: matches!(variant, Variant::M3 | Variant::M4),
reset_requested: false,
held: false,
debugger: false,
halted: 0,
in_debug_state: false,
host_calling: false,
itm: Itm::default(),
traced: Vec::new(),
unlinked: None,
carried: 0,
quiet: None,
panicked: None,
});
family.table = family.pages.table();
Ok(family)
}
fn on_bus<T>(&mut self, otherwise: T, call: impl FnOnce(&mut dyn CoreBus) -> T) -> T {
if self.panicked.is_some() {
return otherwise;
}
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| call(self.bus()))) {
Ok(answer) => answer,
Err(panic) => {
self.panicked = Some(panic);
self.returning = RETURN;
otherwise
}
}
}
fn bus(&mut self) -> &mut dyn CoreBus {
let mut bus = self.bus.expect("a bus while running");
unsafe { *bus.as_mut() }
}
fn run_blocks(&mut self) -> Exit {
let mut raised = false;
loop {
if self.host_calling {
return Exit::HostCall;
}
if std::mem::take(&mut raised) && self.halted == 0 {
self.take_exception();
if self.held && self.halted == 0 {
return Exit::Budget;
}
}
if self.halted != 0 {
return self.halt();
}
if self.returned() {
return Exit::Returned;
}
if !self.held && self.exceptions_changed() {
self.take_exception();
if self.halted != 0 {
return self.halt();
}
}
if self.state.locked_up != 0 {
return Exit::Lockup;
}
if self.state.sleeping != 0 {
return Exit::Waiting;
}
if self.budget == 0 {
return Exit::Budget;
}
if self.returned() {
return Exit::Returned;
}
if self.state.t == 0 {
self.raise_fault(Fault::InvalidState);
raised = true;
continue;
}
let pc = self.state.r[15];
let passing = self.passing.take();
if self.breakpoints.contains_key(&u64::from(pc)) && passing != Some(u64::from(pc)) {
return Exit::Breakpoint;
}
let start = self.start_at(pc);
let block = match self.block(start) {
Ok(block) => block,
Err(error) => panic!("{error}"),
};
let budget = self.budget;
self.entered = budget;
self.link = 0;
let entry = self.jit.entry();
let answer = unsafe { entry.run(std::ptr::from_mut(self).cast(), block) };
let ran = budget - self.budget;
self.retired += u64::from(ran);
self.entered = self.budget;
if answer == status::NEXT && self.link != 0 {
self.unlinked = self.blocks.cell_number(self.link);
}
match answer {
status::EXCEPTION => {
raised = true;
if self.returning == CARRY_ON {
self.returning = RETURN;
}
}
status::HOT => {
let full = Start {
pc: self.state.r[15],
it: self.state.it,
longest: LONGEST_BLOCK,
};
if passing == Some(u64::from(full.pc)) {
self.passing = passing;
}
let made = if self.blocks.get(self.key_of(full).0).is_some() {
self.promote(full)
} else {
self.block(full).map(drop)
};
if let Err(error) = made {
panic!("{error}");
}
}
status::SPIN | status::WAIT_FOR_INTERRUPT if self.held => {}
status::WAIT_FOR_EVENT if self.held => self.state.event = 0,
status::WAIT_FOR_INTERRUPT | status::SPIN => self.sleep(false),
status::WAIT_FOR_EVENT => self.sleep(true),
status::EXCEPTION_RETURN => self.exception_return(self.state.r[15]),
_ => {}
}
}
}
fn halt(&mut self) -> Exit {
self.scs.halted_by(std::mem::take(&mut self.halted));
self.in_debug_state = true;
Exit::Halted
}
fn dhcsr(&self) -> u32 {
if !self.debugger {
return 0;
}
let halted = self.in_debug_state;
[
(dhcsr::C_DEBUGEN, true),
(dhcsr::C_HALT, halted),
(dhcsr::S_HALT, halted),
(dhcsr::S_REGRDY, halted),
(dhcsr::C_STEP, self.held),
(dhcsr::C_MASKINTS, self.held),
(dhcsr::S_SLEEP, !halted && self.state.sleeping != 0),
(dhcsr::S_LOCKUP, !halted && self.state.locked_up != 0),
]
.into_iter()
.fold(0, |value, (field, on)| set(field, value, u32::from(on)))
}
fn returned(&mut self) -> bool {
match std::mem::take(&mut self.returning) {
0 => false,
CARRY_ON => {
self.budget = self.carried;
self.entered = self.carried;
false
}
_ => true,
}
}
fn exceptions_changed(&mut self) -> bool {
let now = (
self.scs.generation(),
self.state.primask,
self.state.faultmask,
self.state.locked_up,
self.state.sleeping,
self.state.event,
);
if self.quiet == Some(now) {
return false;
}
self.quiet = Some(now);
true
}
fn start_at(&mut self, pc: u32) -> Start {
let full = Start {
pc,
it: self.state.it,
longest: LONGEST_BLOCK,
};
if self.blocks.get(self.key_of(full).0).is_some() || !self.blocks.covers(u64::from(pc)) {
return full;
}
Start { longest: 1, ..full }
}
fn block(&mut self, start: Start) -> Result<crate::emulator::core::Block, Error> {
let (key, context) = self.key_of(start);
if let Some(block) = self.blocks.get(key) {
self.link_to(key, start, block);
return Ok(block);
}
if self.jit.made() > self.most_blocks {
self.start_over()?;
}
let (block, range, looped) = self.compile_with_room(start, context, Tier::Quick)?;
self.keep(key, block, range, looped);
self.link_to(key, start, block);
Ok(block)
}
fn promote(&mut self, start: Start) -> Result<(), Error> {
let (key, context) = self.key_of(start);
match self.compile(start, context, Tier::Best) {
Ok((block, range, looped)) => {
self.blocks.replace(key, block);
self.keep(key, block, range, looped);
Ok(())
}
Err(_) if self.jit.out_of_room() => self.start_over(),
Err(error) => Err(error),
}
}
fn compile_with_room(
&mut self,
start: Start,
context: Context,
tier: Tier,
) -> Result<Translated, Error> {
match self.compile(start, context, tier) {
Err(_) if self.jit.out_of_room() => {
self.start_over()?;
self.compile(start, context, tier)
}
made => made,
}
}
fn start_over(&mut self) -> Result<(), Error> {
self.drop_translations();
unsafe { self.jit.clear()? };
self.blocks.free_cells();
self.unlinked = None;
Ok(())
}
fn keep(
&mut self,
key: u64,
block: crate::emulator::core::Block,
(first, last): (u32, u32),
looped: bool,
) {
let (first, last) = (u64::from(first), u64::from(last.max(first)));
self.blocks.insert(key, first, last, block);
for page in first >> PAGE_BITS..=last >> PAGE_BITS {
self.pages.hold_code(page << PAGE_BITS);
}
if looped {
self.looping.insert(key);
} else {
self.looping.remove(&key);
}
}
fn rekey_at(&mut self, address: u64, apart: bool) {
let moving: Vec<u64> = self
.blocks
.starting_at(address)
.into_iter()
.filter(|(key, _)| (key & AT_BREAKPOINT == 0) == apart && !self.looping.contains(key))
.map(|(key, _)| key)
.collect();
let blocks = self.blocks.len();
let mut dropped = self
.blocks
.rekey_at(address, &moving, |key| key ^ AT_BREAKPOINT);
for (start, end) in self.pages.views(address, address.saturating_add(1)) {
if start != address {
dropped.extend(self.blocks.drop_range(start, end));
}
}
for (page, held) in dropped {
if !held {
self.pages.release_code(page);
}
}
if self.blocks.len() < blocks {
self.returning = RETURN;
}
}
fn key_of(&self, start: Start) -> (u64, Context) {
let key = block_key(start)
| if self.breakpoints.contains_key(&u64::from(start.pc)) {
AT_BREAKPOINT
} else {
0
};
if self.mpu.enabled() {
let context = self.mpu_context();
let key = key
| u64::from(context.privileged) << 48
| u64::from(context.below_priority_zero) << 49;
return (key, context);
}
(
key,
Context {
privileged: true,
below_priority_zero: false,
},
)
}
fn compile(&mut self, start: Start, context: Context, tier: Tier) -> Result<Translated, Error> {
let chains = !self.mpu.enabled();
let counter = (tier == Tier::Quick).then(|| self.blocks.new_counter());
let recent = self.blocks.recent();
let features = self.features;
let Self {
jit,
pages,
breakpoints,
mpu,
blocks,
..
} = self;
let mut fetch = |address: u32| {
let (host, _) = pages.host(u64::from(address))?;
if address & 1 != 0
|| u64::from(address) & (PAGE_SIZE - 1) == PAGE_SIZE - 1
|| !mpu.fetch_allows(address, context)
{
return None;
}
Some(u16::from_le_bytes(unsafe { host.cast::<[u8; 2]>().read() }))
};
let stop_before = |address: u32| breakpoints.contains_key(&u64::from(address));
let mut cells = |pc: u32, it: u32| {
blocks.new_cell(block_key(Start {
pc,
it,
longest: LONGEST_BLOCK,
}))
};
let cells: Option<(translate::Cells<'_>, usize)> = chains.then_some((&mut cells, recent));
translate::translate(
jit,
features,
start,
&mut fetch,
&stop_before,
cells,
tier,
counter,
)
}
fn link_to(&mut self, key: u64, start: Start, block: crate::emulator::core::Block) {
if let Some(cell) = self.unlinked.take()
&& !self.breakpoints.contains_key(&u64::from(start.pc))
{
self.blocks.link(cell, key, block);
}
}
pub(crate) fn mpu_changed(&mut self, enabled: bool) {
self.drop_translations();
self.pages.check_all(enabled);
}
fn drop_translations(&mut self) {
self.blocks.clear();
self.looping.clear();
self.pages.release_all_code();
}
pub(crate) fn invalidate(&mut self, start: u64, end: u64) {
let (start, end) = (start.min(ADDRESS_SPACE), end.min(ADDRESS_SPACE));
if !self.pages.touches_code(start, end) {
return;
}
let blocks = self.blocks.len();
for (start, end) in self.pages.views(start, end) {
for (page, held) in self.blocks.drop_range(start, end) {
if !held {
self.pages.release_code(page);
}
}
}
if self.blocks.len() != blocks {
self.returning = RETURN;
}
}
fn settle(&mut self) {
for queued in self.on_bus(Vec::new(), |bus| bus.queued()) {
self.ask(queued);
}
}
pub(crate) fn access(
&mut self,
request: Request,
attributes: Attributes,
position: Position,
) -> Result<u32, Refused> {
let Request {
address,
size,
write,
} = request;
let bytes = size.bytes();
if address.is_multiple_of(bytes) {
return self.access_aligned(request, attributes, position, self.bit_band);
}
if attributes.aligned || is_set(ccr::UNALIGN_TRP, self.system.ccr) {
return Err(Refused::Unaligned);
}
let mut value = 0;
for byte in 0..bytes {
let lane = match self.features.endian {
Endian::Little => byte,
Endian::Big => bytes - 1 - byte,
};
let part = Request {
address: address.wrapping_add(byte),
size: Size::Byte,
write: write.map(|value| (value >> (8 * lane)) & 0xFF),
};
let piece = self
.access_aligned(part, attributes, position, false)
.map_err(|refused| match refused {
Refused::Bus(_) => Refused::Bus(address),
other => other,
})?;
value |= piece << (8 * lane);
}
Ok(value)
}
fn access_aligned(
&mut self,
request: Request,
attributes: Attributes,
position: Position,
bit_band: bool,
) -> Result<u32, Refused> {
let address = request.address;
let privileged = self.access_privileged(attributes.unprivileged);
if position != Position::Vector {
let kind = if request.write.is_some() {
Kind::Write
} else {
Kind::Read
};
self.mpu_check(address, kind, attributes.unprivileged)?;
if !self.ppb_allows(u64::from(address), privileged) {
return Err(Refused::Bus(address));
}
}
let master = if privileged {
Master::Privileged
} else {
Master::Unprivileged
};
match bit_band_bit(address) {
Some((byte, bit)) if bit_band => self.bit_band_access(request, byte, bit, position),
_ => self.reach(request, position, master),
}
}
fn bit_band_access(
&mut self,
request: Request,
byte: u32,
bit: u32,
position: Position,
) -> Result<u32, Refused> {
let Request {
address,
size,
write,
} = request;
let (unit, bit) = self.bit_band_unit(byte, bit, size.bytes());
let at_alias = |refused| match refused {
Refused::Watched => refused,
_ => Refused::Bus(address),
};
let held = self
.reach(Request::read(unit, size), position, Master::Privileged)
.map_err(at_alias)?;
let Some(value) = write else {
return Ok(held >> bit & 1);
};
let written = held & !(1 << bit) | (value & 1) << bit;
self.reach(
Request::write(unit, size, written),
position,
Master::Privileged,
)
.map_err(at_alias)?;
Ok(value)
}
fn bit_band_unit(&self, byte: u32, bit: u32, bytes: u32) -> (u32, u32) {
let unit = byte & !(bytes - 1);
let lane = match self.features.endian {
Endian::Little => byte - unit,
Endian::Big => bytes - 1 - (byte - unit),
};
(unit, lane * BYTE_BITS + bit)
}
fn reach(
&mut self,
request: Request,
position: Position,
master: Master,
) -> Result<u32, Refused> {
let Request {
address,
size,
write,
} = request;
let bytes = size.bytes();
let wide = u64::from(address);
if (PPB_BASE..PPB_BASE + PPB_SIZE).contains(&wide)
&& !self.windows.iter().any(|window| window.contains(&wide))
{
let instructions = self.instructions(position);
let now = self.on_bus(VirtualNanos(0), |bus| bus.time(instructions));
if self.panicked.is_some() {
return Ok(0);
}
let answer = self.processor_bus(request, now, master);
self.hand_back(instructions);
return Ok(answer);
}
if let Some((host, writable)) = self.pages.host(wide)
&& !self.pages.routed(wide)
{
if self.mpu.enabled() && !self.pages.decided(wide) {
let page = address & !u32::try_from(PAGE_SIZE - 1).expect("a page");
let inline = self
.mpu
.inline_throughout(page, u32::try_from(PAGE_SIZE).expect("a page"));
self.pages.allow(wide, inline);
}
match write {
None => return Ok(read_host(host, size, self.features.endian)),
Some(value) if writable => {
write_host(host, size, value, self.features.endian);
self.invalidate(wide, wide + u64::from(bytes));
return Ok(value);
}
Some(_) => {}
}
}
let instructions = self.instructions(position);
let (pc, from_code) = match position {
Position::Instruction { pc, .. } => (pc, true),
Position::Processor | Position::Vector => (self.state.r[15], false),
};
let width = match size {
Size::Byte => Width::W8,
Size::Halfword => Width::W16,
Size::Word => Width::W32,
};
let answer = self.on_bus(Ok(None), |bus| {
let access = Access {
kind: if write.is_some() {
AccessKind::Write
} else {
AccessKind::Read
},
address: wide,
width,
value: u64::from(write.unwrap_or(0)),
instruction_count: instructions,
virtual_time: bus.time(instructions),
program_counter: Some(u64::from(pc)),
};
if from_code && bus.stops_before(&access) {
return Err(Refused::Watched);
}
Ok(bus.access(access, from_code))
});
if self.panicked.is_some() {
return Ok(0);
}
let Ok(answer) = answer else {
self.halted |= registers::mask(dfsr::DWTTRAP);
return Err(Refused::Watched);
};
self.settle();
if answer.is_some() {
self.hand_back(instructions);
} else {
self.returning = RETURN;
}
answer
.map(|value| u32::try_from(value & u64::from(mask(size))).expect("masked"))
.ok_or(Refused::Bus(address))
}
fn hand_back(&mut self, instructions: u64) {
if self.returning == RETURN {
return;
}
let event = self.scs.next_event();
let left = if self.reset_requested {
None
} else {
self.on_bus(None, |bus| bus.carry_on(instructions, event))
};
match left {
Some(left) => {
self.returning = CARRY_ON;
self.carried = left;
}
None => self.returning = RETURN,
}
}
fn instructions(&self, position: Position) -> u64 {
let before = self.retired + u64::from(self.entered - self.budget);
match position {
Position::Instruction { index, .. } => before + u64::from(index) + 1,
Position::Processor | Position::Vector => before,
}
}
fn ppb_allows(&self, address: u64, privileged: bool) -> bool {
privileged
|| !(PPB_BASE..PPB_BASE + PPB_SIZE).contains(&address)
|| ITM.contains(&address)
|| ((SCS_BASE + STIR..SCS_BASE + STIR + 4).contains(&address)
&& is_set(ccr::USERSETMPEND, self.system.ccr))
}
fn itm_access(&mut self, request: Request, master: Master) -> u32 {
let Request {
address,
size,
write,
} = request;
let offset = u32::try_from(u64::from(address) - ITM.start).expect("inside the ITM");
let tracing = self.scs.tracing();
match write {
None => {
let word = Itm::identification(offset & !3)
.filter(|_| tracing && self.features.variant == Variant::M4)
.unwrap_or_else(|| self.itm.read(offset & !3, tracing));
(word >> (8 * (offset & 3))) & mask(size)
}
Some(value) => {
let writer = Writer {
privileged: master != Master::Unprivileged,
enabled: tracing,
};
if let Some(packet) = self.itm.write(offset & !3, size, value, writer) {
self.traced.extend(packet);
}
value
}
}
}
fn processor_bus(&mut self, request: Request, now: VirtualNanos, master: Master) -> u32 {
let Request {
address,
size,
write,
} = request;
let debugger = master == Master::Debugger;
let address = u64::from(address);
if ITM.contains(&address) {
return self.itm_access(request, master);
}
if !(SCS_BASE..SCS_BASE + SCS_SIZE).contains(&address) {
return write.unwrap_or(0);
}
while self.scs.next_event().is_some_and(|event| event <= now) {
self.on_event(now);
}
let offset = address - SCS_BASE;
match write {
None => {
let word = self.read_word(offset & !3, now, debugger);
(word >> (8 * (offset & 3))) & mask(size)
}
Some(value) => {
self.write_scs(offset, size, value, now);
self.note_pended();
value
}
}
}
pub(crate) fn note_pended(&mut self) {
if self.scs.take_pended() && self.system.event_on_pending {
self.state.event = 1;
}
}
fn gdb_number(&self, number: usize) -> Option<GdbRegister> {
let number = if !self.features.fp() && number > 16 {
number.checked_add(17)?
} else {
number
};
Some(match number {
0..=15 => GdbRegister::Core(number),
16 => GdbRegister::Xpsr,
17..=32 => GdbRegister::Double(number - 17),
33 => GdbRegister::Fpscr,
34 => GdbRegister::Msp,
35 => GdbRegister::Psp,
36 => GdbRegister::Primask,
37 => GdbRegister::Basepri,
38 => GdbRegister::Faultmask,
39 => GdbRegister::Control,
_ => return None,
})
}
fn read_word(&mut self, offset: u64, now: VirtualNanos, debugger: bool) -> u32 {
match offset {
0xD00 => CPUID,
DHCSR => self.dhcsr(),
ICSR => self.scs.icsr(self.state.ipsr, self.held),
0xD08 => self.system.vtor,
0xD14 => self.system.ccr,
CFSR => self.system.cfsr,
MMFAR => self.system.mmfar,
BFAR => self.system.bfar,
0xD88 => self.system.cpacr,
0xD90..=0xDBB => self.mpu_read(offset),
ID_ISAR1..=0xD6F if !self.features.dsp => {
ID_ISAR_WITHOUT_DSP[usize::try_from((offset - ID_ISAR1) / 4).expect("small")]
}
_ if FP_REGISTERS.contains(&offset) && !self.features.fp() => 0,
0xF34 => self.system.fpccr,
0xF38 => self.system.fpcar,
0xFE0 if self.features.fp() => 0x0C,
0xF3C => self.system.fpdscr,
MVFR0..=MVFR2 if self.features.fpu != Fpu::FpV4Sp => {
fpv5_feature(self.features.fpu, offset)
}
_ if debugger => self.scs.peek(offset, now).unwrap_or(0),
_ => self.scs.read(offset, now).unwrap_or(0),
}
}
fn write_scs(&mut self, offset: u64, size: Size, value: u32, now: VirtualNanos) {
let bytes = value.to_le_bytes();
if self.scs.write_priority_byte(offset, bytes[0]) {
for (at, &byte) in (offset + 1..).zip(&bytes[1..size.bytes() as usize]) {
self.scs.write_priority_byte(at, byte);
}
return;
}
if (CFSR..CFSR + 4).contains(&offset) {
let shifted = (value & mask(size)) << (8 * (offset - CFSR));
self.system.cfsr &= !shifted;
return;
}
if size != Size::Word || offset & 3 != 0 {
return;
}
match offset {
0xD08 => self.system.vtor = value & registers::mask(vtor::TBLOFF),
0xD14 => self.system.ccr = value & CCR_WRITABLE,
MMFAR | BFAR => {
self.system.mmfar = value;
self.system.bfar = value;
}
0xD88 if self.features.fp() => self.system.cpacr = value & CPACR_FP,
0xD88 => {}
0xD90..=0xDBB => self.mpu_write(offset, value),
_ if FP_REGISTERS.contains(&offset) && !self.features.fp() => {}
0xF34 => self.system.fpccr = value & FPCCR_WRITABLE,
0xF38 => self.system.fpcar = value & registers::mask(fpcar::ADDRESS),
0xF3C => self.system.fpdscr = value & FPDSCR_WRITABLE,
_ => {
let asked = self.scs.write(offset, value, now);
if let Some(sleep) = asked.sleep {
self.system.sleep_on_exit = sleep.on_exit;
self.system.event_on_pending = sleep.event_on_pending;
}
self.reset_requested |= asked.reset;
}
}
}
pub(crate) fn masks(&self) -> Masks {
Masks {
primask: is_set(masks::PM, self.state.primask),
faultmask: is_set(masks::FM, self.state.faultmask),
locked_up: self.state.locked_up != 0,
held: self.held,
}
}
pub(crate) fn set_line(&mut self, line: u16, high: bool) {
self.scs.set_line(line, high);
self.note_pended();
}
pub(crate) fn route_page(&mut self, address: u64, routed: bool) {
self.pages.route(address, routed);
}
#[cfg(test)]
pub(crate) fn inline(&self, address: u32) -> crate::emulator::core::Inline {
self.pages.inline(u64::from(address))
}
const TARGET_XML: &'static str = target_xml!(floating_point_registers!());
const TARGET_XML_WITHOUT_FP: &'static str = target_xml!();
}
const RETURN: u32 = 1;
const CARRY_ON: u32 = 2;
const MOST_BLOCKS: u64 = 1 << 16;
const AT_BREAKPOINT: u64 = 1 << 50;
type Translated = (crate::emulator::core::Block, (u32, u32), bool);
fn mask(size: Size) -> u32 {
match size {
Size::Byte => 0xFF,
Size::Halfword => 0xFFFF,
Size::Word => u32::MAX,
}
}
fn bit_band_bit(address: u32) -> Option<(u32, u32)> {
BIT_BANDS.iter().find_map(|&(region, alias)| {
let offset = address
.checked_sub(alias)
.filter(|&offset| offset < BIT_BAND_ALIAS_SIZE)?;
Some((
region + offset / ALIAS_BYTES_PER_BYTE,
offset / ALIAS_BYTES_PER_BIT % BYTE_BITS,
))
})
}
fn read_host(host: *mut u8, size: Size, endian: Endian) -> u32 {
let mut bytes = [0; 4];
let count = size.bytes() as usize;
unsafe { std::ptr::copy_nonoverlapping(host, bytes.as_mut_ptr(), count) };
u32::try_from(crate::emulator::memory::from_target(
&bytes[..count],
endian,
))
.expect("four bytes at most")
}
fn write_host(host: *mut u8, size: Size, value: u32, endian: Endian) {
let count = size.bytes() as usize;
let bytes = crate::emulator::memory::to_target(u64::from(value), count, endian);
unsafe {
std::ptr::copy_nonoverlapping(bytes.as_ptr(), host, size.bytes() as usize);
}
}
impl Processor for Family {
fn debugger_access(
&mut self,
write: Option<u64>,
address: u64,
width: Width,
now: VirtualNanos,
) -> Option<u64> {
if !(PPB_BASE..PPB_BASE + PPB_SIZE).contains(&address)
|| self.windows.iter().any(|window| window.contains(&address))
{
return None;
}
let size = match width {
Width::W8 => Size::Byte,
Width::W16 => Size::Halfword,
_ => Size::Word,
};
let request = Request {
address: u32::try_from(address).expect("the PPB is in the 32-bit address space"),
size,
write: write.map(|value| u32::try_from(value & u64::from(u32::MAX)).expect("masked")),
};
Some(u64::from(self.processor_bus(
request,
now,
Master::Debugger,
)))
}
fn take_debugger_trace(&mut self) -> Vec<u8> {
std::mem::take(&mut self.traced)
}
fn target_xml(&self) -> &'static str {
if self.features.fp() {
Self::TARGET_XML
} else {
Self::TARGET_XML_WITHOUT_FP
}
}
fn byte_order(&self, address: u64) -> Endian {
if (PPB_BASE..PPB_BASE + PPB_SIZE).contains(&address) {
Endian::Little
} else {
self.features.endian
}
}
fn run(&mut self, budget: u32, mut bus: &mut dyn CoreBus) -> (Exit, u64) {
self.bus = Some(NonNull::from(&mut bus).cast());
self.budget = budget;
self.entered = budget;
self.returning = 0;
self.in_debug_state = false;
let start = self.retired;
let exit = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
self.settle();
let exit = self.run_blocks();
if !self.traced.is_empty() {
let traced = std::mem::take(&mut self.traced);
self.on_bus((), |bus| bus.send_trace(&traced));
}
exit
}));
self.bus = None;
let panic = match exit {
Ok(exit) => self.panicked.take().map_or(Ok(exit), Err),
Err(panic) => {
self.panicked = None;
self.start_over().ok();
Err(panic)
}
};
match panic {
Ok(exit) => (exit, self.retired - start),
Err(panic) => std::panic::resume_unwind(panic),
}
}
fn retired(&self) -> u64 {
self.retired
}
fn ask(&mut self, queued: Queued) {
match queued {
Queued::Line(line, high) => self.set_line(line, high),
Queued::Invalidate(start, end) => self.invalidate(start, end),
Queued::Route(address) => self.route_page(address, true),
Queued::Unroute(address) => self.route_page(address, false),
Queued::Return => self.returning = RETURN,
}
}
fn maps(&self, start: u64, length: u64) -> bool {
let end = start.saturating_add(length);
end <= ADDRESS_SPACE
&& self
.own_ranges()
.iter()
.all(|own| end <= own.start || start >= own.end)
}
fn page_size(&self) -> u64 {
PAGE_SIZE
}
unsafe fn map(&mut self, start: u64, length: u64, host: *mut u8, writable: bool) {
unsafe { self.pages.map(start, length, host, writable) };
}
fn boot(&mut self, table: u64, read: &dyn Fn(u64, Width) -> Option<u64>) {
let word =
|offset: u64| u32::try_from(read(table + offset, Width::W32).unwrap_or(0)).unwrap_or(0);
let (sp, reset) = (word(0), word(4));
self.system.vtor = u32::try_from(table).unwrap_or(0) & registers::mask(vtor::TBLOFF);
self.state.t = reset & 1;
self.state.r[13] = sp & !3;
self.state.r[14] = 0xFFFF_FFFF;
self.state.r[15] = reset & !1;
}
fn reset(&mut self) {
self.state = State::default();
self.system = System::default();
self.scs.reset();
self.mpu = Mpu::new(self.mpu.regions());
self.reset_requested = false;
self.in_debug_state = false;
self.host_calling = false;
self.mpu_changed(false);
self.halted = if self.debugger && self.scs.catches(demcr::VC_CORERESET) {
registers::mask(dfsr::VCATCH)
} else {
0
};
}
fn take_reset_request(&mut self) -> bool {
std::mem::take(&mut self.reset_requested)
}
fn next_event(&self) -> Option<VirtualNanos> {
self.scs.next_event()
}
fn on_event(&mut self, now: VirtualNanos) {
self.scs.on_event(now);
self.note_pended();
}
fn waiting(&self) -> bool {
self.state.sleeping != 0
}
fn pc(&self) -> u64 {
u64::from(self.state.r[15])
}
fn add_breakpoint(&mut self, address: u64) -> bool {
let word = u32::try_from(address).ok();
let exception_return =
word.is_some_and(|word| registers::get(exc_return::PREFIX, word) == 0xF);
if word.is_none() || (!address.is_multiple_of(2) && !exception_return) {
return false;
}
let asked = self.breakpoints.entry(address).or_default();
*asked += 1;
if *asked == 1 {
self.rekey_at(address, true);
}
true
}
fn remove_breakpoint(&mut self, address: u64) {
if let Some(asked) = self.breakpoints.get_mut(&address) {
*asked -= 1;
if *asked == 0 {
self.breakpoints.remove(&address);
self.rekey_at(address, false);
}
}
}
fn pass_breakpoint(&mut self, address: u64) {
self.passing = Some(address);
}
fn hold_interrupts(&mut self, held: bool) {
self.quiet = None;
self.held = held;
}
fn attach_debugger(&mut self, attached: bool) {
self.debugger = attached;
if !attached {
self.halted = 0;
}
}
fn gdb_register(&self, number: usize) -> Option<(u64, usize)> {
let state = &self.state;
let word = |value: u32| Some((u64::from(value), 4));
match self.gdb_number(number)? {
GdbRegister::Core(number) => word(state.r[number]),
GdbRegister::Xpsr => word(state.xpsr()),
GdbRegister::Double(number) => Some((
u64::from(state.s[2 * number]) | u64::from(state.s[2 * number + 1]) << 32,
8,
)),
GdbRegister::Fpscr => word(state.fpscr),
GdbRegister::Msp => word(state.msp()),
GdbRegister::Psp => word(state.psp()),
GdbRegister::Primask => word(state.primask),
GdbRegister::Basepri => word(u32::from(self.scs.basepri())),
GdbRegister::Faultmask => word(state.faultmask),
GdbRegister::Control => word(state.control),
}
}
fn set_gdb_register(&mut self, number: usize, value: u64) -> bool {
let Some(register) = self.gdb_number(number) else {
return false;
};
let word = u32::try_from(value & u64::from(u32::MAX)).expect("masked");
let control = self.features.control();
let state = &mut self.state;
match register {
GdbRegister::Core(13) => state.r[13] = word & !3,
GdbRegister::Core(15) => state.r[15] = word & !1,
GdbRegister::Core(number) => state.r[number] = word,
GdbRegister::Xpsr => state.set_xpsr(word),
GdbRegister::Double(number) => {
state.s[2 * number] = word;
state.s[2 * number + 1] = u32::try_from(value >> 32).expect("the high word");
}
GdbRegister::Fpscr => state.fpscr = word,
GdbRegister::Msp => state.set_msp(word),
GdbRegister::Psp => state.set_psp(word),
GdbRegister::Primask => state.primask = registers::get(masks::PM, word),
GdbRegister::Basepri => self.scs.write_basepri(
u8::try_from(registers::get(masks::BASEPRI, word)).expect("eight bits"),
),
GdbRegister::Faultmask => state.faultmask = registers::get(masks::FM, word),
GdbRegister::Control => {
let ipsr = state.ipsr;
state.set_mode(ipsr, word & control);
}
}
self.drop_translations();
true
}
fn save(&self) -> Vec<u8> {
postcard::to_allocvec(&(
&self.state,
&self.system,
&self.scs,
&self.mpu,
&self.itm,
self.features.dsp,
self.features.fpu.name(),
self.retired,
))
.expect("the processor's state serializes")
}
fn load(&mut self, bytes: &[u8]) -> Result<(), Error> {
let (state, system, scs, mpu, itm, dsp, fpu, retired): (
State,
System,
Scs,
Mpu,
Itm,
bool,
&str,
u64,
) = match postcard::take_from_bytes(bytes) {
Ok((saved, [])) => saved,
Ok(_) | Err(_) => return Err(Error::State),
};
if !state.consistent()
|| !scs.can_replace(&self.scs)
|| mpu.regions() != self.mpu.regions()
|| (dsp, fpu) != (self.features.dsp, self.features.fpu.name())
|| state.ipsr > u32::from(self.scs.lines()) + 15
|| !mpu.selects_a_region()
{
return Err(Error::State);
}
let changed = mpu != self.mpu;
self.state = state;
self.system = system;
self.scs = scs;
self.mpu = mpu;
self.itm = itm;
self.retired = retired;
if changed {
self.mpu_changed(self.mpu.enabled());
}
self.quiet = None;
Ok(())
}
fn debugger_halted(&mut self, why: HaltReason) -> bool {
let pending = std::mem::take(&mut self.halted);
let event = match why {
HaltReason::Requested => dfsr::HALTED,
HaltReason::Breakpoint => dfsr::BKPT,
HaltReason::Watchpoint => dfsr::DWTTRAP,
};
if !self.debugger {
return pending != 0;
}
self.in_debug_state = true;
self.scs.halted_by(pending | registers::mask(event)) || pending != 0
}
fn monitor_command(&mut self, command: &str) -> Option<String> {
let mut words = command.split_whitespace();
match words.next()? {
"help" => Some(super::exception::VECTOR_CATCH_HELP.to_owned()),
"vector-catch" => Some(self.vector_catch(words)),
_ => None,
}
}
fn bit_alias(&self, address: u64, width: Width) -> Option<BitAlias> {
let (byte, bit) = bit_band_bit(u32::try_from(address).ok()?).filter(|_| self.bit_band)?;
let (unit, bit) = self.bit_band_unit(byte, bit, width.bytes());
Some(BitAlias {
unit: u64::from(unit),
bit,
})
}
fn own_ranges(&self) -> Vec<std::ops::Range<u64>> {
let aliases = BIT_BANDS
.iter()
.filter(|_| self.bit_band)
.map(|&(_, alias)| u64::from(alias)..u64::from(alias + BIT_BAND_ALIAS_SIZE));
std::iter::once(PPB_BASE..PPB_BASE + PPB_SIZE)
.chain(aliases)
.collect()
}
fn host_call(&self, read: &mut dyn FnMut(u64, Width) -> Option<u64>) -> Option<HostCall> {
let [operation, parameter] = [self.state.r[0], self.state.r[1]];
self.host_calling
.then(|| semihosting::decode(operation, parameter, read))
.flatten()
}
fn answer_host_call(&mut self, answer: Option<u64>) {
if !std::mem::take(&mut self.host_calling) {
return;
}
let Some(answer) = answer else {
self.raise_fault(Fault::Breakpoint);
return;
};
let answer = u32::try_from(answer & u64::from(u32::MAX)).expect("masked");
if self.state.r[0] == semihosting::operation::ELAPSED && answer != 0 {
self.state.r[1] = answer;
}
self.state.r[0] = answer;
self.state.r[15] = self.state.r[15].wrapping_add(2);
self.state.it = translate::it_advance(self.state.it);
self.retired += 1;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum GdbRegister {
Core(usize),
Xpsr,
Double(usize),
Fpscr,
Msp,
Psp,
Primask,
Basepri,
Faultmask,
Control,
}
pub(crate) mod helpers {
use super::super::translate::FAULTED;
use super::{Attributes, Endian, Family, Fault, Position, Request, Size};
fn size(bytes: u32) -> Size {
match bytes {
1 => Size::Byte,
2 => Size::Halfword,
_ => Size::Word,
}
}
pub(crate) extern "C" fn read(
core: &mut Family,
address: u32,
bytes: u32,
attributes: u32,
pc: u32,
index: u32,
) -> u64 {
let position = Position::Instruction { pc, index };
let request = Request::read(address, size(bytes));
match core.access(request, Attributes::from_bits(attributes), position) {
Ok(value) => u64::from(value),
Err(refused) if core.raise_data_fault(refused) => FAULTED,
Err(_) => 0,
}
}
pub(crate) extern "C" fn write(
core: &mut Family,
address: u32,
value: u32,
bytes: u32,
attributes: u32,
pc: u32,
index: u32,
) -> u64 {
let position = Position::Instruction { pc, index };
let request = Request::write(address, size(bytes), value);
match core.access(request, Attributes::from_bits(attributes), position) {
Err(refused) if core.raise_data_fault(refused) => FAULTED,
Ok(_) | Err(_) => 0,
}
}
pub(crate) extern "C" fn raise(core: &mut Family, code: u32, pc: u32) -> u32 {
core.state.r[15] = pc;
let Some(fault) = Fault::from_code(code) else {
return 0;
};
core.raise_fault(fault);
u32::from(fault == Fault::SupervisorCall && core.state.locked_up != 0)
}
pub(crate) fn mapped_halfword(core: &Family, address: u32) -> Option<u16> {
if address & 1 != 0 {
return None;
}
let (host, _) = core.pages.host(u64::from(address))?;
u16::try_from(super::read_host(host, Size::Halfword, Endian::Little)).ok()
}
#[cfg(test)]
pub(crate) fn run_built(
core: &mut Family,
build: impl FnOnce(&mut cranelift_frontend::FunctionBuilder<'_>, cranelift_codegen::ir::Value),
) -> u32 {
let block = core
.jit
.compile(crate::emulator::core::Tier::Best, |builder, value, _| {
build(builder, value);
})
.expect("a block");
let budget = core.budget;
let entry = core.jit.entry();
let status = unsafe { entry.run(std::ptr::from_mut(core).cast(), block) };
budget.wrapping_sub(core.budget) << 8 | status
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::super::registers::{get, icsr, itm_tcr};
use super::super::translate::testing::{Bench, CODE, DEVICE, RAM, spec, spec_of};
use super::*;
use crate::emulator::Property;
#[test]
fn a_window_reads_as_rendered() {
assert_eq!(window("0xe0001000:0x8"), Some(0xE000_1000..0xE000_1008));
assert_eq!(window("e0001000"), None);
}
#[test]
fn a_target_needs_every_property_the_family_reads() {
let mut spec = spec();
let family = Family::new(&spec, Endian::Little).unwrap();
assert_eq!(family.windows.len(), 1);
assert_eq!(family.windows[0], 0xE000_1000..0xE000_1008);
assert_eq!(family.scs.lines(), 91);
spec.properties.remove(1);
assert!(matches!(
Family::new(&spec, Endian::Little),
Err(Error::Machine(_))
));
}
#[test]
fn the_registers_the_processor_holds_take_a_write_as_the_manual_has_them() {
let mut family = Family::new(&spec(), Endian::Little).unwrap();
let now = VirtualNanos(0);
let word = |family: &mut Family, offset: u64, value: Option<u32>| {
if let Some(value) = value {
family.write_scs(offset, Size::Word, value, now);
}
family.read_word(offset, now, false)
};
assert_eq!(word(&mut family, 0xD08, Some(0x0800_0123)), 0x0800_0100);
family.system.cfsr = 0x0001_8200;
family.write_scs(0xD29, Size::Byte, 0x82, now);
assert_eq!(
word(&mut family, CFSR, None),
0x0001_0000,
"BFSR cleared, UFSR kept"
);
assert_eq!(word(&mut family, 0xD88, Some(0xFFFF_FFFF)), 0x00F0_0000);
}
#[test]
fn icsr_reads_the_exception_ipsr_holds() {
let mut family = Family::new(&spec(), Endian::Little).unwrap();
let now = VirtualNanos(0);
family.scs.pend(crate::cpu::armv7m::scs::exception::PEND_SV);
let masks = family.masks();
let taken = family.scs.acknowledge(masks).unwrap();
family.state.ipsr = u32::from(taken);
assert_eq!(
get(icsr::VECTACTIVE, family.read_word(ICSR, now, false)),
u32::from(taken)
);
family.state.ipsr = 0;
assert_eq!(get(icsr::VECTACTIVE, family.read_word(ICSR, now, false)), 0);
}
#[test]
fn a_write_to_either_fault_address_register_sets_both() {
let mut family = Family::new(&spec(), Endian::Little).unwrap();
let now = VirtualNanos(0);
family.system.mmfar = 0xE000_ED14;
family.system.bfar = 0xE000_ED14;
family.write_scs(BFAR, Size::Word, 0, now);
assert_eq!(
(
family.read_word(MMFAR, now, false),
family.read_word(BFAR, now, false)
),
(0, 0)
);
family.write_scs(MMFAR, Size::Word, 0x2000_0040, now);
assert_eq!(family.read_word(BFAR, now, false), 0x2000_0040);
}
#[test]
fn every_model_the_machine_offers_is_one_the_family_makes() {
for variant in Variant::ALL {
for ®ions in variant.mpus() {
let family =
Family::new(&spec_of(variant, Fpu::None, regions), Endian::Little).unwrap();
assert_eq!(family.mpu.regions(), regions.count());
assert_eq!(family.features.dsp, variant != Variant::M3);
}
}
let mut spec = spec();
spec.cpu = "cortex-m0";
assert!(Family::new(&spec, Endian::Little).is_err());
}
#[test]
fn interrupt_lines_and_priority_bits_the_architecture_lacks_are_refused() {
let with = |name: &'static str, value: u32| {
let mut spec = spec();
let property = spec
.properties
.iter_mut()
.find(|property| property.name == name)
.expect("set by the bench");
*property = Property::new(name, value);
Family::new(&spec, Endian::Little).map(|family| family.scs.lines())
};
assert_eq!(with(INTERRUPT_LINES, 496).unwrap(), 496);
assert!(matches!(with(INTERRUPT_LINES, 497), Err(Error::Machine(_))));
assert!(matches!(
with(INTERRUPT_LINES, 65_536),
Err(Error::Machine(_))
));
for bits in [0, 3, 8] {
assert!(with(PRIORITY_BITS, bits).is_ok(), "{bits} bits");
}
for bits in [1, 2, 9, 256] {
assert!(
matches!(with(PRIORITY_BITS, bits), Err(Error::Machine(_))),
"{bits} bits"
);
}
}
#[test]
fn a_part_the_core_cant_be_is_refused() {
let refused = |model, fpu, regions| {
matches!(
Family::new(&spec_of(model, fpu, regions), Endian::Little),
Err(Error::Machine(_))
)
};
assert!(refused(Variant::M3, Fpu::FpV4Sp, MpuRegions::Eight));
assert!(refused(Variant::M4, Fpu::FpV5Sp, MpuRegions::Eight));
assert!(refused(Variant::M4, Fpu::FpV4Sp, MpuRegions::Sixteen));
assert!(!refused(Variant::M7, Fpu::FpV5Dp, MpuRegions::Sixteen));
assert!(!refused(Variant::M7, Fpu::FpV5Sp, MpuRegions::Eight));
assert!(refused(Variant::M7, Fpu::FpV4Sp, MpuRegions::Eight));
assert!(!refused(Variant::M7, Fpu::None, MpuRegions::Sixteen));
}
#[test]
fn a_target_that_doesnt_say_gets_the_cortex_m4fs_parts() {
for (model, fpu) in [
(Variant::M3, Fpu::None),
(Variant::M4, Fpu::FpV4Sp),
(Variant::M7, Fpu::FpV5Dp),
] {
let mut spec = spec_of(model, Fpu::None, MpuRegions::None);
spec.properties
.retain(|property| ![FPU, MPU_REGIONS].contains(&property.name));
let family = Family::new(&spec, Endian::Little).unwrap();
assert_eq!(family.features, Features::of(model, fpu), "{model:?}");
assert_eq!(family.mpu.regions(), 8, "{model:?}");
}
}
#[test]
fn a_core_without_an_extension_says_so() {
let now = VirtualNanos(0);
let mut family = Family::new(
&spec_of(Variant::M3, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
family.write_scs(0xD88, Size::Word, CPACR_FP, now);
assert_eq!(family.read_word(0xD88, now, false), 0);
family.write_scs(0xF3C, Size::Word, FPDSCR_WRITABLE, now);
for offset in (0xF34..=0xF48).step_by(4) {
assert_eq!(family.read_word(offset, now, false), 0, "{offset:#x}");
}
let isar = |family: &mut Family| {
[0xD64, 0xD68, 0xD6C].map(|offset| family.read_word(offset, now, false))
};
assert_eq!(isar(&mut family), [0x0211_1000, 0x2111_2231, 0x0111_1110]);
let mut m4f = Family::new(&spec(), Endian::Little).unwrap();
assert_eq!(isar(&mut m4f), [0x0211_2000, 0x2123_2231, 0x0111_1131]);
assert_eq!(m4f.read_word(0xF34, now, false), FPCCR_RESET);
assert_eq!(m4f.read_word(0xF40, now, false), 0x1011_0021);
}
#[test]
fn an_fpv5_unit_says_what_it_has() {
let now = VirtualNanos(0);
for (fpu, expected) in [
(Fpu::FpV5Dp, [0x1011_0221, 0x1200_0011, 0x0000_0040]),
(Fpu::FpV5Sp, [0x1011_0021, 0x1100_0011, 0x0000_0040]),
] {
let mut family = Family::new(
&spec_of(Variant::M7, fpu, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
let read = [MVFR0, MVFR1, MVFR2].map(|offset| family.read_word(offset, now, false));
assert_eq!(read, expected, "{fpu}");
}
}
#[test]
fn a_state_from_another_machine_is_refused() {
let mut family = Family::new(&spec(), Endian::Little).unwrap();
family.state.r[3] = 0x1234;
let saved = family.save();
let mut other = spec();
other.properties[1] = Property::new(INTERRUPT_LINES, 90u32);
let elsewhere = Family::new(&other, Endian::Little).unwrap().save();
assert!(matches!(family.load(&elsewhere), Err(Error::State)));
for (model, fpu, regions) in [
(Variant::M4, Fpu::None, MpuRegions::Eight),
(Variant::M4, Fpu::FpV4Sp, MpuRegions::None),
(Variant::M7, Fpu::None, MpuRegions::Eight),
] {
let other = Family::new(&spec_of(model, fpu, regions), Endian::Little)
.unwrap()
.save();
assert!(matches!(family.load(&other), Err(Error::State)));
}
let without_dsp = Family::new(
&spec_of(Variant::M3, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap()
.save();
let mut with_dsp = Family::new(
&spec_of(Variant::M4, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
assert!(
matches!(with_dsp.load(&without_dsp), Err(Error::State)),
"the DSP extension alone differs"
);
assert!(matches!(family.load(b"not a state"), Err(Error::State)));
family.state.r[3] = 0;
family.load(&saved).unwrap();
assert_eq!(family.state.r[3], 0x1234);
assert_eq!(family.save(), saved);
}
#[test]
fn a_state_the_processor_could_not_be_in_is_refused() {
let mut family = Family::new(&spec(), Endian::Little).unwrap();
let mut padded = family.save();
padded.push(0);
assert!(matches!(family.load(&padded), Err(Error::State)));
let corrupt: [fn(&mut State); 5] = [
|state| state.it = 0x1_00,
|state| state.z = 2,
|state| state.ge = 0x10,
|state| state.r[13] = 0x2000_0002,
|state| state.control = Features::default().control() + 1,
];
for corrupt in corrupt {
let mut other = Family::new(&spec(), Endian::Little).unwrap();
corrupt(&mut other.state);
assert!(matches!(family.load(&other.save()), Err(Error::State)));
}
let mut without_mpu = Family::new(
&spec_of(Variant::M4, Fpu::FpV4Sp, MpuRegions::None),
Endian::Little,
)
.unwrap();
let saved = without_mpu.save();
without_mpu.load(&saved).unwrap();
}
const LDR: u16 = 0x6808;
const STR: u16 = 0x6008;
const LDRB: u16 = 0x7808;
const STRB: u16 = 0x7008;
const STRH: u16 = 0x8008;
fn sram_alias(byte: u32, bit: u32) -> u32 {
BIT_BANDS[0].1 + (byte - BIT_BANDS[0].0) * ALIAS_BYTES_PER_BYTE + bit * ALIAS_BYTES_PER_BIT
}
fn one(bench: &mut Bench, code: u16, r0: u32, r1: u32) -> u32 {
bench.place(CODE, &[code]);
bench.core.state.r[..2].copy_from_slice(&[r0, r1]);
bench.core.state.r[15] = CODE;
assert_eq!(bench.run(1).1, 1);
bench.core.state.r[0]
}
#[test]
fn an_sram_alias_word_is_one_bit_of_the_bit_band_region() {
let mut bench = Bench::new(&[]);
bench.write(RAM + 4, &0x8000_0001_u32.to_le_bytes());
one(&mut bench, STR, 0xFF, sram_alias(RAM + 5, 3));
assert_eq!(bench.word(RAM + 4), 0x8000_0801);
assert_eq!(one(&mut bench, LDR, 0, sram_alias(RAM + 5, 3)), 1);
assert_eq!(one(&mut bench, LDR, 0, sram_alias(RAM + 5, 4)), 0);
one(&mut bench, STR, 0x0E, sram_alias(RAM + 7, 7));
assert_eq!(
bench.word(RAM + 4),
0x0000_0801,
"bits [31:1] of the value count for nothing"
);
assert_eq!(one(&mut bench, LDRB, 0, sram_alias(RAM + 4, 0)), 1);
assert!(bench.bus.accesses.is_empty());
}
#[test]
fn a_peripheral_alias_reaches_the_bus_at_the_instructions_size_read_then_written() {
const PERIPHERAL: u32 = 0x4000_0010;
let alias = |byte: u32, bit: u32| {
BIT_BANDS[1].1
+ (byte - BIT_BANDS[1].0) * ALIAS_BYTES_PER_BYTE
+ bit * ALIAS_BYTES_PER_BIT
};
let mut bench = Bench::new(&[]);
bench.bus.answer = 0x0000_0100;
one(&mut bench, STR, 1, alias(PERIPHERAL + 1, 2));
let seen: Vec<_> = bench
.bus
.accesses
.iter()
.map(|access| (access.kind, access.address, access.width, access.value))
.collect();
let wide = u64::from(PERIPHERAL);
assert_eq!(
seen,
[
(AccessKind::Read, wide, Width::W32, 0),
(AccessKind::Write, wide, Width::W32, 0x0000_0500),
]
);
bench.bus.accesses.clear();
bench.bus.answer = 1;
one(&mut bench, STRB, 0, alias(PERIPHERAL + 1, 0));
assert_eq!(bench.bus.accesses[1].address, wide + 1);
assert_eq!(bench.bus.accesses[1].width, Width::W8);
assert_eq!(bench.bus.accesses[1].value, 0, "bit 0 cleared");
bench.bus.accesses.clear();
one(&mut bench, STRH, 1, alias(PERIPHERAL + 3, 0));
assert_eq!(bench.bus.accesses[1].address, wide + 2);
assert_eq!(bench.bus.accesses[1].width, Width::W16);
assert_eq!(
bench.bus.accesses[1].value, 0x0101,
"bit 8 of the halfword set"
);
bench.bus.answer = 0x0000_0400;
assert_eq!(one(&mut bench, LDR, 0, alias(PERIPHERAL + 1, 2)), 1);
}
#[test]
fn an_unaligned_access_or_a_core_without_bit_banding_sees_the_alias_as_it_is() {
let mut bench = Bench::new(&[]);
one(&mut bench, LDR, 0, sram_alias(RAM, 0) + 1);
let addresses: Vec<u64> = bench
.bus
.accesses
.iter()
.map(|access| access.address)
.collect();
let at = u64::from(sram_alias(RAM, 0));
assert_eq!(addresses, [at + 1, at + 2, at + 3, at + 4]);
let m7 = spec_of(Variant::M7, Fpu::FpV5Dp, MpuRegions::Sixteen);
assert!(!Family::new(&m7, Endian::Little).unwrap().bit_band);
let m3 = spec_of(Variant::M3, Fpu::None, MpuRegions::Eight);
assert!(Family::new(&m3, Endian::Little).unwrap().bit_band);
bench.core.bit_band = false;
bench.bus.accesses.clear();
one(&mut bench, STR, 1, sram_alias(RAM, 0));
assert_eq!(bench.bus.accesses.len(), 1);
assert_eq!(bench.bus.accesses[0].address, at);
assert_eq!(bench.word(RAM), 0);
}
#[test]
fn an_access_on_a_later_pass_round_a_block_counts_the_passes_before() {
let mut bench = Bench::new(&[
0x3A01, 0xBF08, 0x6808, 0xD1FB, 0xE7FE, ]);
bench.core.state.r[1] = DEVICE;
bench.core.state.r[2] = 3;
bench.run(100);
assert_eq!(bench.bus.accesses.len(), 1);
assert_eq!(
bench.bus.accesses[0].instruction_count, 11,
"two passes of four, then SUBS, IT and the load"
);
}
#[test]
fn a_store_drops_the_hot_translation_of_the_bytes_it_reaches() {
let mut bench = Bench::new(&[
0x3001, 0xE7FD, ]);
bench.core.add_breakpoint(u64::from(CODE + 2));
assert_eq!(bench.run(10).0, Exit::Breakpoint);
bench.core.remove_breakpoint(u64::from(CODE + 2));
bench.run(u32::try_from(4 * translate::HOT_RUNS).expect("a budget"));
assert!(
i64::from(bench.core.state.r[0]) > translate::HOT_RUNS,
"the loop ran hot"
);
bench.place(CODE + 2, &[0x2107, 0xE7FE]); bench.core.state.r[15] = CODE;
bench.run(10);
assert_eq!(bench.core.state.r[1], 7);
}
#[test]
fn the_pc_a_debugger_writes_is_halfword_aligned() {
let mut bench = Bench::new(&[]);
assert!(bench.core.set_gdb_register(15, u64::from(RAM + 0xFFFF)));
assert_eq!(bench.core.state.r[15], RAM + 0xFFFE);
assert_eq!(helpers::mapped_halfword(&bench.core, RAM + 0xFFFF), None);
}
#[test]
fn a_narrow_load_keeps_only_its_own_bytes_of_the_bus_answer() {
const LDRB_R1_R2: u16 = 0x7811;
const LDRH_R1_R2: u16 = 0x8811;
for (load, kept) in [(LDRB_R1_R2, 0x78), (LDRH_R1_R2, 0x5678)] {
let mut bench = Bench::new(&[load]);
bench.bus.answer = 0x1234_5678;
bench.core.state.r[2] = DEVICE;
bench.run(1);
assert_eq!(bench.core.state.r[1], kept, "{load:#06x}");
}
}
#[test]
fn a_stack_pointer_a_debugger_writes_is_word_aligned() {
let mut bench = Bench::new(&[]);
for number in [13, 34, 35] {
assert!(bench.core.set_gdb_register(number, u64::from(RAM + 0xFFFE)));
assert_eq!(
bench.core.gdb_register(number),
Some((u64::from(RAM + 0xFFFC), 4)),
"register {number}"
);
}
}
#[test]
fn a_stop_inside_a_block_goes_on_one_instruction_at_a_time_until_hot() {
let mut bench = Bench::new(&[0xBF00, 0xBF00, 0xBF00, 0xE7FB]); let at = |pc, longest| block_key(Start { pc, it: 0, longest });
assert_eq!(bench.run(2), (Exit::Budget, 2));
assert_eq!(bench.core.state.r[15], CODE + 4);
for _ in 1..translate::HOT_RUNS {
bench.core.state.r[15] = CODE + 4;
assert_eq!(bench.run(1), (Exit::Budget, 1));
assert!(bench.core.blocks.get(at(CODE + 4, 1)).is_some());
assert!(bench.core.blocks.get(at(CODE + 4, LONGEST_BLOCK)).is_none());
}
bench.core.state.r[15] = CODE + 4;
bench.run(1);
assert!(bench.core.blocks.get(at(CODE + 4, LONGEST_BLOCK)).is_some());
}
#[test]
fn a_return_finds_its_block_and_stops_at_a_breakpoint_there() {
let mut bench = Bench::new(&[
0xF000, 0xF802, 0x3101, 0xE7FB, 0x4770, ]);
assert_eq!(bench.run(400), (Exit::Budget, 400));
assert_eq!(bench.core.state.r[1], 100);
let at = u64::from(CODE + 4);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(400).0, Exit::Breakpoint);
for _ in 0..3 {
assert_eq!(bench.core.state.r[15], CODE + 4);
bench.core.pass_breakpoint(at);
assert_eq!(bench.run(400), (Exit::Breakpoint, 4));
}
}
#[test]
fn starting_over_changes_nothing_the_run_does() {
let mut code = Vec::new();
for _ in 0..8 {
code.extend([
0x6010, 0x3001, 0xE7FF, ]);
}
code.push(0xE7E6); let run = |most_blocks| {
let mut bench = Bench::new(&code);
bench.core.most_blocks = most_blocks;
bench.core.state.r[2] = DEVICE;
let mut retired = 0;
while retired < 3000 {
let (exit, ran) = bench.run(97);
assert!(ran > 0, "the loop stopped running: {exit:?}");
retired += ran;
}
assert!(
bench.core.jit.made() <= most_blocks + 1,
"the code generator started over"
);
let accesses: Vec<_> = bench
.bus
.accesses
.iter()
.map(|access| (access.address, access.value, access.instruction_count))
.collect();
(accesses, bench.core.save())
};
let (accesses, state) = run(MOST_BLOCKS);
assert!(accesses.len() > 500);
assert_eq!(run(3), (accesses, state));
}
#[test]
fn a_held_step_over_a_wait_goes_on() {
const WFI: u16 = 0xBF30;
let mut bench = Bench::new(&[WFI, 0xBF00]);
bench.core.scs.write(0x100, 1, VirtualNanos(0));
bench.core.set_line(0, true);
bench.core.state.primask = 1;
bench.core.hold_interrupts(true);
assert_eq!(bench.run(1), (Exit::Budget, 1));
assert_eq!(bench.core.state.r[15], CODE + 2);
}
#[test]
fn a_held_step_over_wfe_takes_the_event() {
const WFE: u16 = 0xBF20;
let mut bench = Bench::new(&[WFE, 0xBF00]);
bench.core.state.event = 1;
bench.core.hold_interrupts(true);
assert_eq!(bench.run(1), (Exit::Budget, 1));
assert_eq!(bench.core.state.event, 0);
assert_eq!(bench.core.state.r[15], CODE + 2);
}
#[test]
fn the_masks_built_from_fields_are_the_manuals() {
assert_eq!(CCR_WRITABLE, 0x0000_031B);
assert_eq!(FPCCR_WRITABLE, 0xC000_017B);
assert_eq!(FPDSCR_WRITABLE, 0x07C0_0000);
assert_eq!(CPACR_FP, 0x00F0_0000);
}
#[test]
fn a_breakpoint_is_kept_only_where_an_instruction_can_start() {
let mut bench = Bench::new(&[]);
assert!(bench.core.add_breakpoint(u64::from(CODE)));
assert!(!bench.core.add_breakpoint(u64::from(CODE) + 1));
assert!(!bench.core.add_breakpoint(ADDRESS_SPACE));
assert!(
bench.core.add_breakpoint(0xFFFF_FFF9),
"a handler's return address, where GDB stops its caller"
);
bench.core.remove_breakpoint(0xFFFF_FFF9);
assert_eq!(
bench.core.breakpoints.keys().copied().collect::<Vec<_>>(),
[u64::from(CODE)]
);
}
#[test]
fn a_passed_breakpoint_at_a_loop_head_stops_the_next_lap() {
let mut bench = Bench::new(&[
0x3801, 0xD1FD, ]);
bench.core.state.r[0] = 3;
let at = u64::from(CODE);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 0));
for left in [2, 1] {
bench.core.pass_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 2));
assert_eq!(
(bench.core.state.r[0], bench.core.state.r[15]),
(left, CODE)
);
}
}
#[test]
fn a_lap_round_a_breakpoint_translates_the_block_at_it_once() {
let mut bench = Bench::new(&[
0xBF00, 0x3001, 0xE7FC, ]);
let at = u64::from(CODE + 2);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 1));
let mut made = None;
for lap in 1..=20 {
bench.core.remove_breakpoint(at);
assert_eq!(bench.run(1), (Exit::Budget, 1), "the step");
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 2), "lap {lap}");
assert_eq!(bench.core.state.r[0], lap);
if lap == 2 {
made = Some(bench.core.jit.made());
}
}
assert_eq!(
Some(bench.core.jit.made()),
made,
"nothing translated after the second lap"
);
for lap in 21..=320 {
bench.core.pass_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 3), "lap {lap}");
assert_eq!(bench.core.state.r[0], lap);
}
}
#[test]
fn laps_round_a_breakpoint_hold_while_the_generator_starts_over() {
let mut bench = Bench::new(&[
0xBF00, 0x3001, 0xBF00, 0xE7FB, ]);
bench.core.most_blocks = 1;
let at = u64::from(CODE + 2);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 1));
for lap in 1..=50 {
bench.core.remove_breakpoint(at);
assert_eq!(bench.run(1), (Exit::Budget, 1), "the step");
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 3), "lap {lap}");
assert_eq!(bench.core.state.r[0], lap);
assert!(
bench.core.jit.made() <= 2,
"the code generator started over"
);
}
}
#[test]
fn laps_round_a_breakpoint_at_a_block_across_two_pages_list_it_once() {
const LOOP: u32 = CODE + 0x3FA;
let mut code = vec![0xBF00; 0x3FA / 2];
code.extend([
0xBF00, 0x3001, 0xBF00, 0xBF00, 0xE7FA, ]);
let mut bench = Bench::new(&code);
bench.core.state.r[15] = LOOP;
let at = u64::from(LOOP + 2);
let next_page = u64::from(LOOP + 6);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 1));
let mut listed = None;
for lap in 1..=1000 {
bench.core.remove_breakpoint(at);
assert_eq!(bench.run(1), (Exit::Budget, 1));
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 4), "lap {lap}");
if lap == 300 {
listed = Some(bench.core.blocks.listed_on(next_page));
}
}
assert_eq!(Some(bench.core.blocks.listed_on(next_page)), listed);
assert_eq!(bench.core.state.r[0], 1000);
}
#[test]
fn a_block_passed_at_a_breakpoint_is_never_found_by_an_exit() {
let mut bench = Bench::new(&[
0xF000, 0xF802, 0x3101, 0xE7FB, 0x4770, ]);
let at = u64::from(CODE + 4);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(400), (Exit::Breakpoint, 2), "the BL and the BX");
for _ in 0..3 {
bench.core.pass_breakpoint(at);
assert_eq!(bench.run(400), (Exit::Breakpoint, 4));
}
}
#[test]
fn a_block_that_loops_to_its_start_is_dropped_for_a_breakpoint_there() {
let mut bench = Bench::new(&[
0x3001, 0xE7FD, ]);
assert_eq!(bench.run(10), (Exit::Budget, 10));
let at = u64::from(CODE);
bench.core.add_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 0));
bench.core.pass_breakpoint(at);
assert_eq!(bench.run(100), (Exit::Breakpoint, 2));
assert_eq!(bench.core.state.r[0], 6);
}
#[test]
fn a_bkpt_halts_the_core_while_a_debugger_is_attached() {
const BKPT: u16 = 0xBE00;
let mut bench = Bench::new(&[BKPT, 0xE7FE]);
let dhcsr = |bench: &mut Bench| {
bench
.core
.debugger_access(None, 0xE000_EDF0, Width::W32, VirtualNanos(0))
.expect("the processor's own bus")
};
assert_eq!(dhcsr(&mut bench), 0);
bench.core.attach_debugger(true);
assert_eq!(dhcsr(&mut bench), 1);
for _ in 0..2 {
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], CODE, "held at the BKPT");
assert_eq!(bench.core.state.ipsr, 0, "and no exception taken");
}
bench.core.attach_debugger(false);
assert_eq!(dhcsr(&mut bench), 0);
bench.run(10);
assert_eq!(bench.core.state.ipsr, 3, "HardFault");
}
#[test]
fn bkpt_0xab_stops_for_a_host_call_and_any_other_bkpt_is_a_breakpoint() {
const SEMIHOSTING: u16 = 0xBEAB;
let mut bench = Bench::new(&[0xBE01, 0xE7FE]);
bench.core.attach_debugger(true);
assert_eq!(bench.run(10), (Exit::Halted, 0), "BKPT #1 is a breakpoint");
let mut memory = |_: u64, _: Width| None;
let mut bench = Bench::new(&[SEMIHOSTING, SEMIHOSTING, 0xE7FE]);
bench.core.state.r[0] = 0x10;
assert_eq!(bench.run(10), (Exit::HostCall, 0));
assert_eq!(bench.run(10), (Exit::HostCall, 0), "until it's answered");
assert_eq!(bench.core.host_call(&mut memory), Some(HostCall::Clock));
bench.core.answer_host_call(Some(42));
assert_eq!(bench.core.host_call(&mut memory), None, "answered");
assert_eq!(
(
bench.core.state.r[0],
bench.core.state.r[15],
bench.core.retired()
),
(42, CODE + 2, 1)
);
bench.core.state.r[0] = 0x30;
bench.core.state.r[1] = RAM + 8;
assert_eq!(bench.run(10), (Exit::HostCall, 0));
assert_eq!(
bench.core.host_call(&mut memory),
Some(HostCall::Elapsed {
block: u64::from(RAM + 8)
})
);
bench.core.answer_host_call(Some(u64::MAX));
assert_eq!(
(bench.core.state.r[0], bench.core.state.r[1]),
(u32::MAX, u32::MAX),
"a block the run couldn't write"
);
assert_eq!(
bench.run(10),
(Exit::Waiting, 1),
"past both, to the loop, which waits"
);
}
#[test]
fn an_unanswered_host_call_is_the_bkpt_it_was() {
let mut bench = Bench::new(&[0xBEAB, 0xE7FE]);
assert_eq!(bench.run(10), (Exit::HostCall, 0));
bench.core.answer_host_call(None);
bench.run(10);
assert_eq!(bench.core.state.ipsr, 3, "HardFault");
let hfsr = bench
.core
.debugger_access(None, 0xE000_ED2C, Width::W32, VirtualNanos(0));
assert_eq!(
hfsr,
Some(u64::from(registers::mask(registers::hfsr::DEBUGEVT)))
);
let mut bench = Bench::new(&[0xBEAB, 0xE7FE]);
bench.core.attach_debugger(true);
assert_eq!(bench.run(10), (Exit::HostCall, 0));
bench.core.answer_host_call(None);
assert_eq!(bench.run(0), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], CODE, "held at the BKPT");
assert_eq!(
bench
.core
.debugger_access(None, 0xE000_ED30, Width::W32, VirtualNanos(0)),
Some(u64::from(registers::mask(dfsr::BKPT)))
);
}
#[test]
fn the_firmware_reaches_the_itm_as_c1_7_says() {
const STRB: u16 = 0x7001; let mut bench = Bench::new(&[STRB, 0xE7FE]);
let write = |bench: &mut Bench, address: u64, value: u64| {
bench
.core
.debugger_access(Some(value), address, Width::W32, VirtualNanos(0));
};
let store = |bench: &mut Bench| {
bench.core.state.r[15] = CODE;
bench.core.state.r[0] = 0xE000_0000;
bench.core.state.r[1] = u32::from(b'A');
bench.run(1);
std::mem::take(&mut bench.bus.traced)
};
write(
&mut bench,
0xE000_0E80,
u64::from(registers::mask(itm_tcr::ITMENA)),
);
write(&mut bench, 0xE000_0E00, 1);
assert!(store(&mut bench).is_empty(), "TRCENA clear");
write(
&mut bench,
0xE000_EDFC,
u64::from(registers::mask(demcr::TRCENA)),
);
assert!(
store(&mut bench).is_empty(),
"the ITM's own writes were dropped too"
);
write(
&mut bench,
0xE000_0E80,
u64::from(registers::mask(itm_tcr::ITMENA)),
);
write(&mut bench, 0xE000_0E00, 1);
assert_eq!(store(&mut bench), [0b0000_0001, b'A']);
write(&mut bench, 0xE000_0E40, 1);
let ipsr = bench.core.state.ipsr;
bench
.core
.state
.set_mode(ipsr, registers::mask(control::NPRIV));
assert!(store(&mut bench).is_empty(), "port 0 is privileged");
assert_eq!(bench.core.state.ipsr, 0, "and no BusFault");
write(&mut bench, 0xE000_0E40, 0);
assert_eq!(store(&mut bench), [0b0000_0001, b'A'], "and then it isn't");
}
#[test]
fn a_reset_leaves_the_itm_as_it_was() {
let mut bench = Bench::new(&[0xE7FE]);
let access = |bench: &mut Bench, address: u64, value: Option<u64>| {
bench
.core
.debugger_access(value, address, Width::W32, VirtualNanos(0))
};
let set = [
(0xE000_EDFC, u64::from(registers::mask(demcr::TRCENA))),
(0xE000_0E80, u64::from(registers::mask(itm_tcr::ITMENA))),
(0xE000_0E00, 0b101),
(0xE000_0E40, 0b10),
];
for (address, value) in set {
access(&mut bench, address, Some(value));
}
bench.core.reset();
for (address, value) in set {
assert_eq!(
access(&mut bench, address, None),
Some(value),
"{address:#x}"
);
}
}
#[test]
fn a_bit_alias_is_the_unit_and_bit_the_core_uses() {
const LDRH: u16 = 0x8808;
let peripheral = BIT_BANDS[1].1 + (DEVICE + 5 - BIT_BANDS[1].0) * ALIAS_BYTES_PER_BYTE + 24;
for endian in [Endian::Little, Endian::Big] {
for alias in [sram_alias(RAM + 0x13, 3), peripheral] {
for (load, width) in [(LDRB, Width::W8), (LDRH, Width::W16), (LDR, Width::W32)] {
let mut bench = match endian {
Endian::Little => Bench::new(&[load, 0xE7FE]),
Endian::Big => Bench::big_endian(&[load, 0xE7FE]),
};
bench.core.route_page(u64::from(RAM), true);
let BitAlias { unit, bit } =
bench.core.bit_alias(u64::from(alias), width).unwrap();
bench.bus.answer = 1 << bit;
bench.core.state.r[1] = alias;
assert_eq!(bench.run(1).1, 1);
let read = bench.bus.accesses[0];
assert_eq!(
(read.address, read.width, bench.core.state.r[0]),
(unit, width, 1),
"{alias:#x}, {width:?}, {endian:?}"
);
}
}
}
let last = u64::from(sram_alias(RAM + 0x13, 3));
let word = u64::from(RAM + 0x10);
let little = Bench::new(&[]).core.bit_alias(last, Width::W32);
let big = Bench::big_endian(&[]).core.bit_alias(last, Width::W32);
let alias = |unit, bit| Some(BitAlias { unit, bit });
assert_eq!((little, big), (alias(word, 27), alias(word, 3)));
let bench = Bench::new(&[]);
assert_eq!(bench.core.bit_alias(u64::from(RAM), Width::W32), None);
assert_eq!(
bench.core.bit_alias(u64::from(BIT_BANDS[0].1), Width::W32),
alias(u64::from(RAM), 0)
);
let m7 = Family::new(
&spec_of(Variant::M7, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
assert_eq!(m7.bit_alias(u64::from(BIT_BANDS[0].1), Width::W32), None);
}
fn system_word(bench: &mut Bench, offset: u64, write: Option<u64>) -> u64 {
bench
.core
.debugger_access(write, SCS_BASE + offset, Width::W32, VirtualNanos(0))
.expect("the processor's own bus")
}
#[test]
fn dfsr_says_what_halted_the_core_and_clears_by_writing_one() {
const BKPT: u16 = 0xBE00;
const DFSR: u64 = 0xD30;
let bkpt = u64::from(registers::mask(dfsr::BKPT));
let watched = u64::from(registers::mask(dfsr::DWTTRAP));
let halted = u64::from(registers::mask(dfsr::HALTED));
let mut bench = Bench::new(&[BKPT, 0xBF00, STR, 0xE7FE]);
bench.core.attach_debugger(true);
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(system_word(&mut bench, DFSR, None), bkpt);
assert!(
bench.core.debugger_halted(HaltReason::Requested),
"HALTED is new"
);
assert!(
!bench.core.debugger_halted(HaltReason::Requested),
"and then it isn't"
);
assert_eq!(system_word(&mut bench, DFSR, None), bkpt | halted);
system_word(&mut bench, DFSR, Some(bkpt | halted));
assert_eq!(system_word(&mut bench, DFSR, None), 0);
let nop = CODE + 2;
bench.core.state.r[15] = nop;
bench.core.add_breakpoint(u64::from(nop));
assert_eq!(bench.run(10), (Exit::Breakpoint, 0));
assert_eq!(system_word(&mut bench, DFSR, None), 0);
bench.core.debugger_halted(HaltReason::Breakpoint);
bench.core.remove_breakpoint(u64::from(nop));
let word = RAM + 0x10;
bench.core.state.r[1] = word;
bench.core.route_page(u64::from(RAM), true);
bench.bus.watched = Some((u64::from(word), AccessKind::Write));
assert_eq!(bench.run(10), (Exit::Halted, 1));
assert_eq!(system_word(&mut bench, DFSR, None), bkpt | watched);
system_word(&mut bench, DFSR, Some(bkpt));
assert_eq!(system_word(&mut bench, DFSR, None), watched);
system_word(&mut bench, DFSR, Some(watched));
bench.core.debugger_halted(HaltReason::Watchpoint);
assert_eq!(
system_word(&mut bench, DFSR, None),
watched,
"a watch stop after its access"
);
system_word(&mut bench, DFSR, Some(watched));
bench.core.attach_debugger(false);
bench.core.debugger_halted(HaltReason::Requested);
bench.core.state.r[15] = CODE;
bench.run(10);
assert_eq!(bench.core.state.ipsr, 3, "HardFault");
assert_eq!(system_word(&mut bench, DFSR, None), 0);
}
#[test]
fn dhcsr_says_whether_the_core_runs_halts_or_steps() {
const BKPT: u16 = 0xBE00;
const DHCSR_AT: u32 = 0xE000_EDF0;
let bits = |fields: &[&crate::soc::FieldSpec]| {
u64::from(
fields
.iter()
.fold(0, |value, field| value | registers::mask(field)),
)
};
let on = bits(&[dhcsr::C_DEBUGEN]);
let held = bits(&[
dhcsr::C_DEBUGEN,
dhcsr::C_HALT,
dhcsr::S_HALT,
dhcsr::S_REGRDY,
]);
let stepping = bits(&[dhcsr::C_DEBUGEN, dhcsr::C_STEP, dhcsr::C_MASKINTS]);
let mut bench = Bench::new(&[LDR, LDR, BKPT, LDR, LDR, 0xE7FE]);
bench.core.state.r[1] = DHCSR_AT;
let read = |bench: &mut Bench| system_word(bench, 0xDF0, None);
let loaded = |bench: &mut Bench| {
assert_eq!(bench.run(1).1, 1);
u64::from(bench.core.state.r[0])
};
assert_eq!(
(read(&mut bench), loaded(&mut bench)),
(0, 0),
"no debugger"
);
bench.core.attach_debugger(true);
assert_eq!(loaded(&mut bench), on, "running");
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(read(&mut bench), held, "halted at the BKPT");
bench.core.state.r[15] = CODE + 6;
bench.core.hold_interrupts(true);
assert_eq!(loaded(&mut bench), stepping, "the step");
bench.core.debugger_halted(HaltReason::Requested);
assert_eq!(read(&mut bench), held | stepping, "halted after the step");
bench.core.hold_interrupts(false);
assert_eq!(loaded(&mut bench), on, "running on");
bench.core.state.sleeping = 1;
assert_eq!(read(&mut bench), on | bits(&[dhcsr::S_SLEEP]), "waiting");
bench.core.state.sleeping = 0;
bench.core.state.locked_up = 1;
assert_eq!(read(&mut bench), on | bits(&[dhcsr::S_LOCKUP]), "locked up");
bench.core.state.locked_up = 0;
bench.core.debugger_halted(HaltReason::Requested);
assert_eq!(read(&mut bench), held, "halted by the debugger");
bench.core.attach_debugger(false);
assert_eq!(read(&mut bench), 0);
}
#[test]
fn demcr_keeps_its_vector_catch_across_a_local_reset() {
const DEMCR: u64 = 0xDFC;
let mut bench = Bench::new(&[]);
system_word(&mut bench, DEMCR, Some(0x010F_07F1));
assert_eq!(system_word(&mut bench, DEMCR, None), 0x010F_07F1);
bench.core.reset();
assert_eq!(system_word(&mut bench, DEMCR, None), 0x0100_07F1);
}
#[test]
fn a_core_reset_under_vector_catch_halts_before_its_first_instruction() {
const RESET: u32 = CODE + 8;
let mut bench = Bench::new(&[0x1000, 0x2000, 0x0009, 0x0000, 0xBF00, 0xE7FE]);
let reset = |bench: &mut Bench| {
bench.core.reset();
let (sp, handler) = (bench.word(CODE), bench.word(CODE + 4));
bench.core.boot(u64::from(CODE), &|address, _| {
Some(u64::from(if address == u64::from(CODE) {
sp
} else {
handler
}))
});
};
let dfsr = |bench: &mut Bench| system_word(bench, 0xD30, None);
let vcatch = u64::from(registers::mask(dfsr::VCATCH));
bench.core.attach_debugger(true);
reset(&mut bench);
assert_eq!(
bench.run(10),
(Exit::Waiting, 2),
"no catch asked for, so the NOP and the wait"
);
system_word(&mut bench, 0xDFC, Some(1));
reset(&mut bench);
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], RESET);
assert_eq!(dfsr(&mut bench), vcatch);
assert_eq!(bench.run(1), (Exit::Budget, 1), "then the handler runs");
reset(&mut bench);
bench.core.debugger_halted(HaltReason::Requested);
assert_eq!(
dfsr(&mut bench),
vcatch | u64::from(registers::mask(dfsr::HALTED))
);
assert_eq!(bench.run(1), (Exit::Budget, 1));
bench.core.attach_debugger(false);
reset(&mut bench);
assert_eq!(bench.run(1), (Exit::Budget, 1));
bench.core.attach_debugger(true);
reset(&mut bench);
bench.core.attach_debugger(false);
assert_eq!(bench.run(1), (Exit::Budget, 1), "the handler runs");
}
#[test]
fn a_core_without_the_floating_point_extension_tells_gdb_it_has_none() {
let with = Family::new(&spec(), Endian::Little).unwrap();
let mut without = Family::new(
&spec_of(Variant::M4, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
assert!(with.target_xml().contains("org.gnu.gdb.arm.vfp"));
assert!(!without.target_xml().contains("org.gnu.gdb.arm.vfp"));
assert_eq!(with.gdb_register(17).map(|(_, bytes)| bytes), Some(8), "d0");
without.state.set_msp(0x2000_1000);
assert_eq!(without.gdb_register(17), Some((0x2000_1000, 4)), "msp");
assert!(without.set_gdb_register(18, 0x2000_0800));
assert_eq!(without.gdb_register(18), Some((0x2000_0800, 4)), "psp");
assert_eq!(
without.gdb_register(22).map(|(_, bytes)| bytes),
Some(4),
"control"
);
assert_eq!(without.gdb_register(23), None);
}
#[test]
fn a_register_number_past_the_last_is_none_without_the_floating_point_extension() {
let mut without = Family::new(
&spec_of(Variant::M4, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
for number in [usize::MAX - 16, usize::MAX] {
assert_eq!(without.gdb_register(number), None, "{number:#x}");
assert!(!without.set_gdb_register(number, 0), "{number:#x}");
}
}
#[test]
fn a_big_endian_part_keeps_data_big_endian_and_fetches_little_endian() {
let mut bench = Bench::big_endian(&[
0x6008, 0x780A, 0x880B, 0x680C, 0x6835, 0xE7FE, ]);
bench.core.state.r[0] = 0x1122_3344;
bench.core.state.r[1] = RAM + 0x10;
bench.core.state.r[6] = RAM + 0x11;
assert_eq!(bench.run(5), (Exit::Budget, 5));
let r = &bench.core.state.r;
assert_eq!(
(r[2], r[3], r[4], r[5]),
(0x11, 0x1122, 0x1122_3344, 0x2233_4400)
);
assert_eq!(bench.word(RAM + 0x10), 0x1122_3344);
let aircr = bench
.core
.debugger_access(None, 0xE000_ED0C, Width::W32, VirtualNanos(0))
.expect("the processor's own bus");
assert_eq!(aircr >> 15 & 1, 1);
assert_eq!(bench.core.byte_order(0xE000_ED00), Endian::Little);
assert_eq!(bench.core.byte_order(u64::from(RAM)), Endian::Big);
}
#[test]
fn memory_goes_only_where_the_processor_answers_nothing_itself() {
let m4 = Family::new(&spec(), Endian::Little).unwrap();
assert!(m4.maps(0x2000_0000, 0x3_0000), "SRAM");
assert!(!m4.maps(0x2200_0000, 0x400), "SRAM's bit-band alias");
assert!(
!m4.maps(0x41FF_FC00, 0x800),
"running into the peripherals' alias"
);
assert!(!m4.maps(0xE000_E000, 0x400), "the private peripheral bus");
assert!(!m4.maps(0xFFFF_FC00, 0x800), "past 32 bits");
let m7 = Family::new(
&spec_of(Variant::M7, Fpu::None, MpuRegions::Eight),
Endian::Little,
)
.unwrap();
assert!(m7.maps(0x2200_0000, 0x400), "no bit-banding on an M7");
}
#[test]
fn a_word_or_halfword_sets_each_priority_it_covers() {
let mut bench = Bench::new(&[]);
let core = &mut bench.core;
let mut access = |write, address, width| {
core.debugger_access(write, address, width, VirtualNanos(0))
.expect("the processor's own bus")
};
access(Some(0x00F0_0000), 0xE000_ED20, Width::W32);
access(Some(0x8040), 0xE000_E400, Width::W16);
assert_eq!(access(None, 0xE000_ED20, Width::W32), 0x00F0_0000, "PendSV");
assert_eq!(
access(None, 0xE000_E400, Width::W32),
0x0000_8040,
"IRQ0 and IRQ1"
);
}
#[test]
fn a_store_through_one_view_drops_code_translated_through_another() {
const ALIAS: u32 = 0x1000_0000;
const MOVS_R0_1: u16 = 0x2001;
const MOVS_R0_2: u16 = 0x2002;
const STRH_R1_R2: u16 = 0x8011;
const BX_R3: u16 = 0x4718;
const SPIN: u16 = 0xE7FE;
let mut bench = Bench::aliased(&[STRH_R1_R2, BX_R3], ALIAS);
bench.place(RAM, &[MOVS_R0_1, SPIN]);
bench.core.state.r[15] = RAM;
bench.run(1);
assert_eq!(bench.core.state.r[0], 1);
bench.core.state.r[..4].copy_from_slice(&[0, u32::from(MOVS_R0_2), ALIAS, RAM | 1]);
bench.core.state.r[15] = CODE;
for _ in 0..3 {
bench.run(1);
}
assert_eq!(bench.core.state.r[15], RAM + 2);
assert_eq!(bench.core.state.r[0], 2, "the new instruction ran");
}
fn stores(bench: &Bench) -> Vec<(u64, u64)> {
bench
.bus
.accesses
.iter()
.filter(|access| access.kind == AccessKind::Write)
.map(|access| (access.address, access.value))
.collect()
}
#[test]
fn a_watched_store_halts_before_it_lands_with_the_instruction_unretired() {
let mut bench = Bench::new(&[STR, 0xE7FE]);
let word = RAM + 0x10;
bench.core.state.r[..2].copy_from_slice(&[0x1234_5678, word]);
bench.core.route_page(u64::from(RAM), true);
bench.bus.watched = Some((u64::from(word), AccessKind::Write));
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], CODE, "held at the store");
assert_eq!(bench.core.retired(), 0);
assert_eq!(stores(&bench), []);
bench.bus.watched = None;
assert_eq!(bench.run(1).1, 1);
assert_eq!(stores(&bench), [(u64::from(word), 0x1234_5678)]);
bench.bus.accesses.clear();
bench.core.state.r[..2].copy_from_slice(&[1, sram_alias(word, 0)]);
bench.core.state.r[15] = CODE;
bench.bus.watched = Some((u64::from(word), AccessKind::Write));
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], CODE, "held at the alias store");
assert_eq!(bench.core.system.cfsr, 0, "no BusFault");
assert_eq!(kinds(&bench), [(AccessKind::Read, u64::from(word))]);
}
fn kinds(bench: &Bench) -> Vec<(AccessKind, u64)> {
bench
.bus
.accesses
.iter()
.map(|access| (access.kind, access.address))
.collect()
}
#[test]
fn a_peripheral_alias_store_reads_then_halts_before_its_write() {
let mut bench = Bench::new(&[STR, 0xE7FE]);
let alias = BIT_BANDS[1].1 + (DEVICE - BIT_BANDS[1].0) * ALIAS_BYTES_PER_BYTE;
bench.core.state.r[..2].copy_from_slice(&[1, alias]);
bench.bus.watched = Some((u64::from(DEVICE), AccessKind::Write));
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], CODE, "held at the alias store");
assert_eq!(kinds(&bench), [(AccessKind::Read, u64::from(DEVICE))]);
bench.bus.watched = None;
assert_eq!(bench.run(1).1, 1);
assert_eq!(
kinds(&bench),
[
(AccessKind::Read, u64::from(DEVICE)),
(AccessKind::Read, u64::from(DEVICE)),
(AccessKind::Write, u64::from(DEVICE))
],
"the instruction runs again from its read"
);
}
#[test]
fn a_push_whose_second_store_is_watched_restarts_from_its_first() {
const PUSH: u16 = 0xB403;
let mut bench = Bench::new(&[PUSH, 0xE7FE]);
let top = bench.core.state.r[13];
bench.core.state.r[..2].copy_from_slice(&[0xAAAA_AAAA, 0xBBBB_BBBB]);
bench.core.route_page(u64::from(top - 4), true);
let (first, second) = (u64::from(top - 8), u64::from(top - 4));
bench.bus.watched = Some((second, AccessKind::Write));
assert_eq!(bench.run(10), (Exit::Halted, 0));
assert_eq!(bench.core.state.r[15], CODE, "held at the push");
assert_eq!(bench.core.state.r[13], top, "the stack pointer as it was");
assert_eq!(stores(&bench), [(first, 0xAAAA_AAAA)]);
bench.bus.watched = None;
assert_eq!(bench.run(1).1, 1);
assert_eq!(bench.core.state.r[13], top - 8);
assert_eq!(
stores(&bench),
[
(first, 0xAAAA_AAAA),
(first, 0xAAAA_AAAA),
(second, 0xBBBB_BBBB)
]
);
}
}