#![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, csselr, ctype, 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::device::Level;
use crate::emulator::core::{Blocks, Jit, Pages, Tier};
use crate::emulator::core::{PAGE_BITS, PAGE_SIZE};
use crate::emulator::{
Access, AccessKind, BitAlias, CoreBus, DataModel, Error, Exit, HaltReason, HostCall, Processor,
Queued,
};
use crate::machine::armv7m::{
BranchPrediction, CORE, Caches, Config, Core, Fpu, ImplementationRegister, MpuRegions, Variant,
};
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);
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 CLIDR: u64 = 0xD78;
const CTR: u64 = 0xD7C;
const CCSIDR: u64 = 0xD80;
const CSSELR: u64 = 0xD84;
const CSSELR_WRITABLE: u32 = registers::mask(csselr::LEVEL) | registers::mask(csselr::IND);
const MOST_CACHES: usize = 14;
const NO_CACHE: u32 = 0b000;
const INSTRUCTION_CACHE: u32 = 0b001;
const DATA_CACHE: u32 = 0b010;
const SEPARATE_CACHES: u32 = 0b011;
const UNIFIED_CACHE: u32 = 0b100;
const CACHE_LEVELS: std::ops::RangeInclusive<u8> = 1..=7;
const IMPLEMENTATION_DEFINED: std::ops::RangeInclusive<u64> = 0xF90..=0xFCC;
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,
pub(crate) csselr: u32,
pub(crate) defined: Vec<u32>,
}
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,
csselr: 0,
defined: Vec::new(),
}
}
}
#[derive(Debug)]
struct Implementation {
cpuid: u32,
caches: Option<Caches>,
ccr_writable: u32,
ccr_ones: u32,
registers: Vec<ImplementationRegister>,
}
impl Implementation {
fn of(config: &Config) -> Result<Self, Error> {
let registers = &config.implementation_registers;
if let Some(register) = registers.iter().find(|register| {
!IMPLEMENTATION_DEFINED.contains(®ister.offset)
|| !register.offset.is_multiple_of(4)
|| registers
.iter()
.filter(|other| other.offset == register.offset)
.count()
> 1
}) {
return Err(Error::Machine(format!(
"{CORE} can't have a register of its own at {:#x} into the system control space, since Table B3-6 leaves the implementation one each of the words from {:#x} to {:#x}",
register.offset,
IMPLEMENTATION_DEFINED.start(),
IMPLEMENTATION_DEFINED.end()
)));
}
if let Some(caches) = config.caches
&& caches.ccsidr.len() > MOST_CACHES
{
return Err(Error::Machine(format!(
"{CORE} can't have {} caches, since B4.8.3's CSSELR selects among {MOST_CACHES}",
caches.ccsidr.len()
)));
}
let alignment = config.core.table_alignment().get();
if let Some(table) = config.vector_table
&& !u64::from(table).is_multiple_of(alignment)
{
return Err(Error::Machine(format!(
"{CORE}'s vector table can't start at {table:#x}, since B1.5.3 aligns it to {alignment} bytes"
)));
}
Ok(Self {
cpuid: config.cpuid,
caches: config.caches,
ccr_writable: CCR_WRITABLE
| cache_enables(config.caches.map_or(0, |caches| caches.clidr)),
ccr_ones: match config.branch_prediction {
BranchPrediction::Always => registers::mask(ccr::BP),
BranchPrediction::Unsupported => 0,
},
registers: registers.clone(),
})
}
fn reset(&self) -> System {
System {
defined: self
.registers
.iter()
.map(|register| register.reset)
.collect(),
..System::default()
}
}
fn register(&self, offset: u64) -> Option<usize> {
self.registers
.iter()
.position(|register| register.offset == offset)
}
}
fn cache_enables(clidr: u32) -> u32 {
let kinds: Vec<u32> = CACHE_LEVELS
.map(|level| registers::get(&ctype(level), clidr))
.take_while(|&kind| kind != NO_CACHE)
.collect();
let has = |cache: &[u32]| kinds.iter().any(|kind| cache.contains(kind));
let enable = |present: bool, field| if present { registers::mask(field) } else { 0 };
enable(has(&[INSTRUCTION_CACHE, SEPARATE_CACHES]), ccr::IC)
| enable(has(&[DATA_CACHE, SEPARATE_CACHES, UNIFIED_CACHE]), ccr::DC)
}
#[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,
implementation: Implementation,
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 DATA_MODEL: DataModel = DataModel::Ilp32;
const ADDRESS_SPACE: u64 = 1 << 32;
const MOST_INTERRUPT_LINES: u16 = 496;
fn parts(config: &Config) -> Result<(Variant, Fpu, MpuRegions), Error> {
let Config {
core: Core { variant, .. },
fpu,
mpu_regions: regions,
..
} = *config;
let cpu = variant.name();
if !variant.fpus().contains(&fpu) {
return Err(Error::Machine(format!("a {cpu} can't have {fpu}")));
}
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))
}
#[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(config: &Config, endian: Endian) -> Result<Box<Self>, Error> {
let (variant, fpu, regions) = parts(config)?;
let implementation = Implementation::of(config)?;
let core = config.core;
let lines = core.interrupt_lines.get();
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 = Some(core.priority_bits)
.filter(|bits| *bits == 0 || (3..=8).contains(bits))
.ok_or_else(|| {
Error::Machine(format!(
"{CORE} can't have {} priority bits, since B1.5.4 allows 3 to 8",
core.priority_bits
))
})?;
let windows = config
.ppb
.iter()
.map(|window| {
window
.base
.checked_add(window.size)
.map(|end| window.base..end)
.ok_or_else(|| {
Error::Machine(format!(
"{CORE}'s window at {:#x} runs past the end of the address space",
window.base
))
})
})
.collect::<Result<_, _>>()?;
let pages = Pages::new(DATA_MODEL)?;
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: implementation.reset(),
implementation,
scs: Scs::new(
lines,
if priority_bits == 0 { 8 } else { priority_bits },
core.clock_hz.get(),
core.refclk_divider.get(),
)
.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;
}
}
pub(crate) fn gdb_value(&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),
}
}
pub(crate) fn set_gdb_value(&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 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 {
if let Some(index) = self.implementation.register(offset) {
return self.system.defined[index];
}
let caches = self.implementation.caches;
match offset {
0xD00 => self.implementation.cpuid,
DHCSR => self.dhcsr(),
ICSR => self.scs.icsr(self.state.ipsr, self.held),
0xD08 => self.system.vtor,
0xD14 => self.system.ccr | self.implementation.ccr_ones,
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")]
}
CLIDR => caches.map_or(0, |caches| caches.clidr),
CTR => caches.map_or(0, |caches| caches.ctr),
CCSIDR => caches
.and_then(|caches| {
let selected = usize::try_from(self.system.csselr).ok()?;
caches.ccsidr.get(selected).copied()
})
.unwrap_or(0),
CSSELR => self.system.csselr,
_ 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;
}
if let Some(index) = self.implementation.register(offset) {
let writable = self.implementation.registers[index].writable;
let held = &mut self.system.defined[index];
*held = value & writable | *held & !writable;
return;
}
match offset {
0xD08 => self.system.vtor = value & registers::mask(vtor::TBLOFF),
0xD14 => self.system.ccr = value & self.implementation.ccr_writable,
CSSELR if self.implementation.caches.is_some() => {
self.system.csselr = value & CSSELR_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 data_model(&self) -> DataModel {
DATA_MODEL
}
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, level) => self.set_line(line.index(), level == Level::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
&& Pages::holds(start, length)
&& 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 = self.implementation.reset();
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<Vec<u8>> {
let (value, width) = self.gdb_value(number)?;
Some(
crate::emulator::memory::to_target(value, width, self.features.endian)[..width]
.to_vec(),
)
}
fn set_gdb_register(&mut self, number: usize, bytes: &[u8]) -> bool {
self.gdb_value(number)
.is_some_and(|(_, width)| width == bytes.len())
&& self.set_gdb_value(
number,
crate::emulator::memory::from_target(bytes, self.features.endian),
)
}
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()
|| system.defined.len() != self.system.defined.len()
{
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::cpu::armv7m::Window;
#[test]
fn a_core_is_made_as_its_configuration_says() {
let mut config = spec();
let family = Family::new(&config, Endian::Little).unwrap();
assert_eq!(family.windows, vec![0xE000_1000..0xE000_1008]);
assert_eq!(family.scs.lines(), 91);
config.ppb = vec![Window {
base: u64::MAX - 3,
size: 8,
}];
assert!(matches!(
Family::new(&config, 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);
}
}
}
#[test]
fn interrupt_lines_and_priority_bits_the_architecture_lacks_are_refused() {
let with = |lines: u32, bits: u8| {
let mut config = spec();
config.core.interrupt_lines = std::num::NonZeroU32::new(lines).unwrap();
config.core.priority_bits = bits;
Family::new(&config, Endian::Little).map(|family| family.scs.lines())
};
assert_eq!(with(496, 4).unwrap(), 496);
assert!(matches!(with(497, 4), Err(Error::Machine(_))));
assert!(matches!(with(65_536, 4), Err(Error::Machine(_))));
for bits in [0, 3, 8] {
assert!(with(91, bits).is_ok(), "{bits} bits");
}
for bits in [1, 2, 9, 255] {
assert!(
matches!(with(91, 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 core = spec_of(model, Fpu::None, MpuRegions::None).core;
let family = Family::new(&Config::new(core, &[]), 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}");
}
}
const CACHES: Caches = Caches {
clidr: 0x0900_0003,
ctr: 0x8303_C003,
ccsidr: &[0xF00F_E019, 0x201F_E009],
};
const CACHE_MAINTENANCE: std::ops::RangeInclusive<u64> = 0xF50..=0xF78;
fn m7(change: impl FnOnce(&mut Config)) -> Result<Box<Family>, Error> {
let mut config = spec_of(Variant::M7, Fpu::FpV5Dp, MpuRegions::Sixteen);
change(&mut config);
Family::new(&config, Endian::Little)
}
fn written(family: &mut Family, offset: u64, value: u32) -> u32 {
let now = VirtualNanos(0);
family.write_scs(offset, Size::Word, value, now);
family.read_word(offset, now, false)
}
#[test]
fn the_cache_identification_registers_answer_through_csselr() {
let mut family = m7(|config| config.caches = Some(CACHES)).unwrap();
let now = VirtualNanos(0);
assert_eq!(family.read_word(CLIDR, now, false), CACHES.clidr);
assert_eq!(family.read_word(CTR, now, false), CACHES.ctr);
assert_eq!(written(&mut family, CSSELR, u32::MAX), CSSELR_WRITABLE);
for (selected, ccsidr) in [(0, 0xF00F_E019), (1, 0x201F_E009), (2, 0), (13, 0)] {
written(&mut family, CSSELR, selected);
assert_eq!(family.read_word(CCSIDR, now, false), ccsidr, "{selected}");
}
family.reset();
assert_eq!(family.read_word(CSSELR, now, false), 0);
}
#[test]
fn the_maintenance_operations_are_taken_and_do_nothing() {
for caches in [Some(CACHES), None] {
let mut family = m7(|config| config.caches = caches).unwrap();
written(&mut family, CSSELR, 1);
let (system, state) = (family.system.clone(), family.state.clone());
for offset in CACHE_MAINTENANCE.step_by(4) {
assert_eq!(written(&mut family, offset, u32::MAX), 0, "{offset:#x}");
}
assert_eq!((&family.system, &family.state), (&system, &state));
}
}
#[test]
fn ccrs_cache_and_prediction_bits_are_what_the_core_has() {
let enables = registers::mask(ccr::IC) | registers::mask(ccr::DC);
let bp = registers::mask(ccr::BP);
let stkalign = registers::mask(ccr::STKALIGN);
let mut cached = m7(|config| {
config.caches = Some(CACHES);
config.branch_prediction = BranchPrediction::Always;
})
.unwrap();
assert_eq!(written(&mut cached, 0xD14, stkalign), stkalign | bp);
assert_eq!(
written(&mut cached, 0xD14, stkalign | enables),
stkalign | enables | bp
);
cached.reset();
assert_eq!(
cached.read_word(0xD14, VirtualNanos(0), false),
stkalign | bp
);
let level_1 = |kind| Caches {
clidr: set(&ctype(1), 0, kind),
..CACHES
};
let past_a_gap = Caches {
clidr: set(&ctype(2), 0, SEPARATE_CACHES),
..CACHES
};
let dc = registers::mask(ccr::DC);
for (caches, kept) in [
(Some(level_1(DATA_CACHE)), dc),
(Some(level_1(UNIFIED_CACHE)), dc),
(Some(past_a_gap), 0),
(None, 0),
] {
let mut family = m7(|config| config.caches = caches).unwrap();
assert_eq!(
written(&mut family, 0xD14, enables | bp),
kept,
"{caches:?}"
);
}
}
#[test]
fn the_implementations_registers_hold_what_the_machine_says() {
let register = |offset, reset, writable| ImplementationRegister {
offset,
reset,
writable,
};
let now = VirtualNanos(0);
let mut family = m7(|config| {
config.implementation_registers = vec![
register(0xF90, 0x0000_0038, 0x0000_0007),
register(0xFCC, 0x0000_0800, 0x0000_0FFF),
];
})
.unwrap();
assert_eq!(family.read_word(0xF90, now, false), 0x38);
assert_eq!(family.read_word(0xFCC, now, false), 0x800);
assert_eq!(written(&mut family, 0xF90, u32::MAX), 0x3F);
assert_eq!(written(&mut family, 0xFCC, 0xFFFF_F000), 0);
family.write_scs(0xF90, Size::Byte, 0, now);
assert_eq!(
family.read_word(0xF90, now, false),
0x3F,
"a word or nothing"
);
assert_eq!(written(&mut family, 0xF94, u32::MAX), 0);
family.reset();
assert_eq!(family.read_word(0xF90, now, false), 0x38);
for offsets in [&[0xF8C][..], &[0xFD0], &[0xF92], &[0xF90, 0xF90]] {
let refused = m7(|config| {
config.implementation_registers = offsets
.iter()
.map(|&offset| register(offset, 0, u32::MAX))
.collect();
});
assert!(matches!(refused, Err(Error::Machine(_))), "{offsets:x?}");
}
}
#[test]
fn cpuid_is_the_machines() {
let now = VirtualNanos(0);
let mut given = m7(|config| config.cpuid = 0x4100_0001).unwrap();
assert_eq!(given.read_word(0xD00, now, false), 0x4100_0001);
let mut m4 = Family::new(&spec(), Endian::Little).unwrap();
assert_eq!(m4.read_word(0xD00, now, false), 0x410F_C241);
}
#[test]
fn a_machine_that_gives_none_answers_as_before() {
for mut family in [
Family::new(&spec(), Endian::Little).unwrap(),
m7(|_| {}).unwrap(),
] {
for offset in [CLIDR, CTR, CCSIDR, CSSELR, 0xF90, 0xFCC] {
assert_eq!(written(&mut family, offset, u32::MAX), 0, "{offset:#x}");
}
assert_eq!(written(&mut family, 0xD14, u32::MAX), CCR_WRITABLE);
assert!(family.system.defined.is_empty());
}
}
#[test]
fn a_vector_table_off_its_alignment_is_refused() {
assert!(matches!(
m7(|config| config.vector_table = Some(0x0010_0080)),
Err(Error::Machine(_))
));
assert!(m7(|config| config.vector_table = Some(0x0010_0200)).is_ok());
}
#[test]
fn a_saved_state_keeps_the_implementations_registers() {
let own = |count: u64| {
move |config: &mut Config| {
config.caches = Some(CACHES);
config.implementation_registers = (0..count)
.map(|index| ImplementationRegister {
offset: 0xF90 + 4 * index,
reset: 0,
writable: u32::MAX,
})
.collect();
}
};
let mut family = m7(own(2)).unwrap();
written(&mut family, 0xF94, 0x1234);
written(&mut family, CSSELR, 1);
let saved = family.save();
family.reset();
family.load(&saved).unwrap();
assert_eq!(
(family.system.defined.clone(), family.system.csselr),
(vec![0, 0x1234], 1)
);
let elsewhere = m7(own(1)).unwrap().save();
assert!(matches!(family.load(&elsewhere), Err(Error::State)));
}
#[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.core.interrupt_lines = std::num::NonZeroU32::new(90).unwrap();
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_value(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_value(number, u64::from(RAM + 0xFFFE)));
assert_eq!(
bench.core.gdb_value(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);
let bp_ic_dc = [ccr::BP, ccr::IC, ccr::DC].map(registers::mask);
assert_eq!(bp_ic_dc, [0x0004_0000, 0x0002_0000, 0x0001_0000], "B3.2.8");
assert_eq!(CSSELR_WRITABLE, 0x0000_000F, "B4.8.3");
assert_eq!(
(registers::mask(&ctype(1)), registers::mask(&ctype(7))),
(0x0000_0007, 0x001C_0000),
"B4.8.1"
);
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_value(17).map(|(_, bytes)| bytes), Some(8), "d0");
without.state.set_msp(0x2000_1000);
assert_eq!(without.gdb_value(17), Some((0x2000_1000, 4)), "msp");
assert!(without.set_gdb_value(18, 0x2000_0800));
assert_eq!(without.gdb_value(18), Some((0x2000_0800, 4)), "psp");
assert_eq!(
without.gdb_value(22).map(|(_, bytes)| bytes),
Some(4),
"control"
);
assert_eq!(without.gdb_value(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_value(number), None, "{number:#x}");
assert!(!without.set_gdb_value(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 a_register_crosses_as_its_bytes_in_the_parts_order() {
let mut little = Bench::new(&[]);
let mut big = Bench::big_endian(&[]);
for bench in [&mut little, &mut big] {
bench.core.state.r[1] = 0x1122_3344;
bench.core.state.s[0] = 0x5566_7788;
bench.core.state.s[1] = 0x99AA_BBCC;
}
assert_eq!(
little.core.gdb_register(1),
Some(vec![0x44, 0x33, 0x22, 0x11])
);
assert_eq!(big.core.gdb_register(1), Some(vec![0x11, 0x22, 0x33, 0x44]));
assert_eq!(
big.core.gdb_register(17),
Some(vec![0x99, 0xAA, 0xBB, 0xCC, 0x55, 0x66, 0x77, 0x88]),
"d0, its high word first"
);
assert!(big.core.set_gdb_register(2, &[0xDE, 0xAD, 0xBE, 0xEF]));
assert_eq!(big.core.state.r[2], 0xDEAD_BEEF);
assert!(!big.core.set_gdb_register(2, &[0xDE, 0xAD]));
assert!(!big.core.set_gdb_register(2, &[0; 8]));
assert_eq!(big.core.state.r[2], 0xDEAD_BEEF);
}
#[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)
]
);
}
}