use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use crate::device::{InterruptChange, ResetCause, Told};
use crate::emulator::{Access, AccessKind, Error, Host, InterruptLine, Stop, StopReason};
use crate::machine::Board;
use crate::mcu::{Mcu, Refused};
use crate::soc::{Bus, Fault};
use crate::time::VirtualNanos;
#[derive(Debug)]
pub struct Session<D: Board + ?Sized = dyn Board> {
board: Box<D>,
target: usize,
route: fn(u16) -> Option<InterruptLine>,
backed: Box<[bool]>,
until: VirtualNanos,
report: Report,
failed: Option<Error>,
}
#[derive(Debug, Default)]
struct Report {
unmapped: BTreeMap<u64, u64>,
unrouted: BTreeSet<(&'static str, u16)>,
refused: BTreeMap<u64, Fault>,
unimplemented: BTreeMap<u64, Unimplemented>,
resets: Vec<Reset>,
told: BTreeMap<(&'static str, &'static str, &'static str), Told>,
}
fn note_told(report: &mut Report, told: impl Iterator<Item = Told>) {
for told in told {
report
.told
.entry((told.instance, told.register, told.reason))
.or_insert(told);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Reset {
pub at: VirtualNanos,
pub instructions: u64,
pub cause: ResetCause,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unimplemented {
pub instance: &'static str,
pub register: &'static str,
pub program_counter: u64,
pub reads: u64,
pub writes: u64,
pub written: Option<u64>,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Unmapped {
pub address: u64,
pub program_counter: u64,
}
impl fmt::Display for Unmapped {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
formatter,
"unmapped at={:#010x} pc={:#010x}",
self.address, self.program_counter
)
}
}
impl fmt::Debug for Unmapped {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("Unmapped")
.field("address", &format_args!("{:#010x}", self.address))
.field(
"program_counter",
&format_args!("{:#010x}", self.program_counter),
)
.finish()
}
}
impl<D: Board + ?Sized> Session<D> {
pub fn new(mut board: Box<D>, target: usize, until: VirtualNanos) -> Result<Self, Error> {
let machine = board.name();
let count = board.target_names().len();
let (name, route, aliases) = {
let targets = board.targets();
let chosen = targets.get(target).ok_or(Error::NoTarget {
machine,
target,
count,
})?;
(
chosen.name,
chosen.interrupt_line,
chosen.emulator.aliases.clone(),
)
};
let backed = {
let mcu = board.mcu(target).ok_or(Error::NoTarget {
machine,
target,
count,
})?;
let regions = mcu.memory();
if let Some(alias) = aliases
.iter()
.find(|alias| regions.iter().all(|region| region.name != alias.region))
{
return Err(Error::NoRegion {
machine,
target: name,
region: alias.region,
at: alias.at,
regions: regions.iter().map(|region| region.name).collect(),
});
}
(0..mcu.bus().slots().len())
.map(|slot| mcu.split(slot).1.is_some())
.collect()
};
Ok(Self {
board,
target,
route,
backed,
until,
report: Report::default(),
failed: None,
})
}
#[must_use]
pub fn target(&self) -> usize {
self.target
}
#[must_use]
pub fn until(&self) -> VirtualNanos {
self.until
}
pub fn run_until(&mut self, until: VirtualNanos) {
self.until = until;
}
#[must_use]
pub fn reached(&self, stop: &Stop) -> bool {
stop.reason != StopReason::Reset && stop.at >= self.until
}
pub fn next_deadline(&mut self) -> VirtualNanos {
let until = self.until;
deadline_before(self.mcu().next_event(), until)
}
pub fn access(&mut self, access: Access, host: &mut dyn Host) -> (u64, VirtualNanos) {
let Self {
board,
target,
route,
backed,
until,
report,
failed,
} = self;
let route = *route;
let until = *until;
let mcu = board.mcu(*target).expect("checked at construction");
let served = match access.kind {
AccessKind::Read => mcu.read(access.virtual_time, access.address, access.width),
AccessKind::Write => mcu
.write(
access.virtual_time,
access.address,
access.width,
access.value,
)
.map(|()| access.value),
};
let value = match served {
Ok(value) => {
answered_from_a_file_alone(report, backed, mcu.bus(), &access);
value
}
Err(fault) => {
refuse(report, access.address, Some(access.program_counter), fault);
0
}
};
match mcu.settle(host) {
Ok(refusals) => note(report, refusals),
Err(error) => {
failed.get_or_insert(error);
}
}
note_told(report, mcu.drain_told().into_iter());
let restarted = take_model_reset(
report,
mcu,
access.virtual_time,
access.instruction_count,
host,
);
if !restarted {
apply(
route,
mcu.drain_interrupts().into_iter(),
host,
&mut report.unrouted,
);
}
(value, deadline_before(mcu.next_event(), until))
}
pub fn stop(&mut self, stop: Stop, host: &mut dyn Host) -> VirtualNanos {
let Self {
board,
target,
route,
until,
report,
failed,
..
} = self;
let route = *route;
let until = *until;
let mcu = board.mcu(*target).expect("checked at construction");
mcu.advance_to(stop.at);
let resets_before = report.resets.len();
if let Some(error) = report.look_outside(mcu, stop.at, stop.instruction_count, route, host)
{
failed.get_or_insert(error);
}
if stop.reason == StopReason::Reset {
mcu.reset(ResetCause::Core);
if report.resets.len() == resets_before {
report.resets.push(Reset {
at: stop.at,
instructions: stop.instruction_count,
cause: ResetCause::Core,
});
}
}
deadline_before(mcu.next_event(), until)
}
pub fn resume(
&mut self,
until: VirtualNanos,
at: VirtualNanos,
instructions: u64,
host: &mut dyn Host,
) -> VirtualNanos {
self.until = until;
let Self {
board,
target,
route,
report,
failed,
..
} = self;
let mcu = board.mcu(*target).expect("checked at construction");
if let Some(error) = report.look_outside(mcu, at, instructions, *route, host) {
failed.get_or_insert(error);
}
deadline_before(mcu.next_event(), until)
}
pub fn take_error(&mut self) -> Option<Error> {
self.failed.take()
}
#[must_use]
pub fn unmapped(&self) -> Vec<Unmapped> {
self.report
.unmapped
.iter()
.map(|(&address, &program_counter)| Unmapped {
address,
program_counter,
})
.collect()
}
#[must_use]
pub fn refused(&self) -> &BTreeMap<u64, Fault> {
&self.report.refused
}
#[must_use]
pub fn unimplemented(&self) -> &BTreeMap<u64, Unimplemented> {
&self.report.unimplemented
}
#[must_use]
pub fn unrouted(&self) -> &BTreeSet<(&'static str, u16)> {
&self.report.unrouted
}
#[must_use]
pub fn resets(&self) -> &[Reset] {
&self.report.resets
}
pub fn told(&self) -> impl Iterator<Item = &Told> {
self.report.told.values()
}
#[must_use]
pub fn board(&self) -> &D {
&self.board
}
pub fn board_mut(&mut self) -> &mut D {
&mut self.board
}
pub fn mcu(&mut self) -> &mut dyn Mcu {
self.board
.mcu(self.target)
.expect("checked at construction")
}
}
fn answered_from_a_file_alone(report: &mut Report, backed: &[bool], bus: &Bus, access: &Access) {
let Some(slot) = bus.slot_at(access.address) else {
return;
};
if backed[slot] {
return;
}
let peripheral = bus.slots()[slot].peripheral();
let entry = report
.unimplemented
.entry(access.address)
.or_insert_with(|| {
let offset = access.address - peripheral.base;
Unimplemented {
instance: peripheral.name,
register: peripheral
.registers
.iter()
.find(|register| {
(u64::from(register.offset)..u64::from(register.end())).contains(&offset)
})
.map_or("?", |register| register.name),
program_counter: access.program_counter,
reads: 0,
writes: 0,
written: None,
}
});
match access.kind {
AccessKind::Read => entry.reads += 1,
AccessKind::Write => {
entry.writes += 1;
entry.written = Some(access.value);
}
}
}
fn refuse(report: &mut Report, address: u64, program_counter: Option<u64>, fault: Fault) {
match program_counter {
Some(program_counter) if !fault.is_shut_gate() => {
report.unmapped.entry(address).or_insert(program_counter);
}
_ => {
report.refused.entry(address).or_insert(fault);
}
}
}
impl Report {
#[expect(
clippy::too_many_arguments,
reason = "each parameter is its own piece of context"
)]
fn look_outside(
&mut self,
mcu: &mut dyn Mcu,
at: VirtualNanos,
instructions: u64,
route: fn(u16) -> Option<InterruptLine>,
host: &mut dyn Host,
) -> Option<Error> {
mcu.poll(at);
let error = match mcu.settle(host) {
Ok(refusals) => {
note(self, refusals);
None
}
Err(error) => Some(error),
};
note_told(self, mcu.drain_told().into_iter());
if !take_model_reset(self, mcu, at, instructions, host) {
apply(
route,
mcu.drain_interrupts().into_iter(),
host,
&mut self.unrouted,
);
}
error
}
}
fn take_model_reset(
report: &mut Report,
mcu: &mut dyn Mcu,
at: VirtualNanos,
instructions: u64,
host: &mut dyn Host,
) -> bool {
let Some(cause) = mcu.take_reset_request() else {
return false;
};
mcu.reset(cause);
report.resets.push(Reset {
at,
instructions,
cause,
});
host.request_reset();
true
}
fn note(report: &mut Report, refusals: Vec<Refused>) {
for refusal in refusals {
refuse(report, refusal.address, None, refusal.fault);
}
}
fn deadline_before(next_event: Option<VirtualNanos>, until: VirtualNanos) -> VirtualNanos {
next_event.map_or(until, |at| at.min(until))
}
fn apply(
route: fn(u16) -> Option<InterruptLine>,
changes: impl Iterator<Item = InterruptChange>,
host: &mut dyn Host,
unrouted: &mut BTreeSet<(&'static str, u16)>,
) {
for change in changes {
match route(change.interrupt) {
Some(line) => host.set_interrupt(line, change.level),
None => {
unrouted.insert((change.instance, change.interrupt));
}
}
}
}