#![cfg_attr(
not(test),
allow(
dead_code,
reason = "a fuzz build uses the bench's core and nothing else of it"
)
)]
use super::super::family::Family;
use crate::emulator::Property;
use crate::emulator::memory::{Mapped, Memory};
use crate::emulator::{Access, AccessKind};
use crate::emulator::{Alias, CoreSpec};
use crate::emulator::{CoreBus, Exit, Processor, Queued};
use crate::machine::armv7m::{
CLOCK_HZ, FPU, Fpu, INTERRUPT_LINES, MPU_REGIONS, MpuRegions, PPB_WINDOWS, PRIORITY_BITS,
REFCLK_DIVIDER, Variant,
};
use crate::soc::{Endian, Region, Width};
use crate::time::VirtualNanos;
pub(crate) const CODE: u32 = 0x0000_0000;
pub(crate) const RAM: u32 = 0x2000_0000;
pub(crate) const SIZE: usize = 0x1_0000;
pub(crate) const DEVICE: u32 = 0x4000_0000;
pub(crate) fn spec() -> CoreSpec {
spec_of(Variant::M4, Fpu::FpV4Sp, MpuRegions::Eight)
}
pub(crate) fn spec_of(variant: Variant, fpu: Fpu, regions: MpuRegions) -> CoreSpec {
CoreSpec {
processor: crate::cpu::armv7m::processor,
cpu: variant.name(),
properties: vec![
Property::new(CLOCK_HZ, 168_000_000u64),
Property::new(INTERRUPT_LINES, 91u32),
Property::new(PRIORITY_BITS, 4u8),
Property::new(REFCLK_DIVIDER, 8u32),
Property::new(PPB_WINDOWS, "0xe0001000:0x8"),
Property::new(FPU, fpu),
Property::new(MPU_REGIONS, regions.count()),
],
instructions_per_second: std::num::NonZeroU64::new(250_000_000).expect("a running core"),
aliases: Vec::new(),
boot: crate::emulator::Boot {
reset: 0,
alignment: std::num::NonZeroU64::new(512).expect("not zero"),
},
}
}
#[derive(Debug, Default)]
pub(crate) struct Recorder {
pub(crate) accesses: Vec<Access>,
pub(crate) answer: u64,
pub(crate) fails_from: Option<u64>,
pub(crate) queued: Vec<Queued>,
pub(crate) on_access: Vec<Queued>,
pub(crate) watched: Option<(u64, AccessKind)>,
pub(crate) traced: Vec<u8>,
}
impl CoreBus for Recorder {
fn access(&mut self, access: Access, _from_code: bool) -> Option<u64> {
self.accesses.push(access);
self.queued.append(&mut self.on_access);
if self.fails_from.is_some_and(|from| access.address >= from) {
return None;
}
Some(match access.kind {
AccessKind::Read => self.answer,
AccessKind::Write => access.value,
})
}
fn time(&self, instructions: u64) -> VirtualNanos {
VirtualNanos(instructions * 4)
}
fn queued(&mut self) -> Vec<Queued> {
std::mem::take(&mut self.queued)
}
fn send_trace(&mut self, bytes: &[u8]) {
self.traced.extend_from_slice(bytes);
}
fn stops_before(&mut self, access: &Access) -> bool {
self.watched == Some((access.address, access.kind))
}
}
pub(crate) struct Bench {
pub(crate) core: Box<Family>,
pub(crate) bus: Recorder,
memory: Mapped,
}
impl Bench {
pub(crate) fn new(code: &[u16]) -> Self {
Self::of(&spec(), code)
}
pub(crate) fn of(spec: &CoreSpec, code: &[u16]) -> Self {
Self::ordered(spec, code, Endian::Little, &[])
}
pub(crate) fn big_endian(code: &[u16]) -> Self {
Self::ordered(&spec(), code, Endian::Big, &[])
}
pub(crate) fn aliased(code: &[u16], at: u32) -> Self {
let alias = Alias {
region: "ram",
at: u64::from(at),
};
Self::ordered(&spec(), code, Endian::Little, &[alias])
}
fn ordered(spec: &CoreSpec, code: &[u16], endian: Endian, aliases: &[Alias]) -> Self {
let mut core = Family::new(spec, endian).expect("a processor");
let memory = Memory::new(
&[
Region::flash("code", u64::from(CODE), SIZE as u64),
Region::ram("ram", u64::from(RAM), SIZE as u64),
],
aliases,
endian,
);
#[expect(
unsafe_code,
reason = "the core keeps pointers into the bench's memory"
)]
unsafe {
memory.map_into(&mut *core);
}
let mut bench = Self {
core,
bus: Recorder::default(),
memory: Mapped::new(memory),
};
bench.place(CODE, code);
bench.core.state.r[13] = RAM + u32::try_from(SIZE).expect("a bench-sized region");
bench.core.state.r[15] = CODE;
bench
}
#[cfg(fuzzing)]
pub(crate) fn restart(&mut self, code: &[u16]) {
use crate::emulator::Processor as _;
self.core.reset();
self.core.scs.write(0xDFC, 0, VirtualNanos(0));
let zeros = vec![0; SIZE];
self.write(CODE, &zeros);
self.write(RAM, &zeros);
self.place(CODE, code);
self.core.state.r[13] = RAM + u32::try_from(SIZE).expect("a bench-sized region");
self.core.state.r[15] = CODE;
self.bus = Recorder::default();
}
pub(crate) fn place(&mut self, address: u32, halfwords: &[u16]) {
let bytes: Vec<u8> = halfwords
.iter()
.flat_map(|halfword| halfword.to_le_bytes())
.collect();
self.write(address, &bytes);
}
pub(crate) fn write(&mut self, address: u32, bytes: &[u8]) {
assert!(
self.memory.place(u64::from(address), bytes),
"inside a region"
);
self.core
.invalidate(u64::from(address), u64::from(address) + bytes.len() as u64);
}
pub(crate) fn word(&self, address: u32) -> u32 {
let word = self
.memory
.read(u64::from(address), Width::W32)
.expect("inside a region");
u32::try_from(word).expect("a word")
}
pub(crate) fn run(&mut self, instructions: u32) -> (Exit, u64) {
self.core.run(instructions, &mut self.bus)
}
}