use std::collections::{BTreeMap, BTreeSet};
use crate::device::InterruptChange;
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 {
unmodeled: BTreeMap<u64, u64>,
unrouted: BTreeSet<(&'static str, u16)>,
refused: BTreeMap<u64, Fault>,
unbacked: BTreeMap<u64, Unbacked>,
resets: Vec<VirtualNanos>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Unbacked {
pub instance: &'static str,
pub register: &'static str,
pub program_counter: u64,
pub reads: u64,
pub writes: u64,
pub written: Option<u64>,
}
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 route = {
let targets = board.targets();
targets
.get(target)
.map(|chosen| chosen.interrupt_line)
.ok_or(Error::NoTarget {
machine,
target,
count,
})?
};
let backed = {
let mcu = board.mcu(target).ok_or(Error::NoTarget {
machine,
target,
count,
})?;
(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);
}
}
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);
mcu.poll(stop.at);
match mcu.settle(host) {
Ok(refusals) => note(report, refusals),
Err(error) => {
failed.get_or_insert(error);
}
}
apply(
route,
mcu.drain_interrupts().into_iter(),
host,
&mut report.unrouted,
);
if stop.reason == StopReason::Reset {
mcu.reset();
report.resets.push(stop.at);
}
deadline_before(mcu.next_event(), until)
}
pub fn take_error(&mut self) -> Option<Error> {
self.failed.take()
}
#[must_use]
pub fn unmodeled(&self) -> &BTreeMap<u64, u64> {
&self.report.unmodeled
}
#[must_use]
pub fn refused(&self) -> &BTreeMap<u64, Fault> {
&self.report.refused
}
#[must_use]
pub fn unbacked(&self) -> &BTreeMap<u64, Unbacked> {
&self.report.unbacked
}
#[must_use]
pub fn unrouted(&self) -> &BTreeSet<(&'static str, u16)> {
&self.report.unrouted
}
#[must_use]
pub fn resets(&self) -> &[VirtualNanos] {
&self.report.resets
}
#[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.unbacked.entry(access.address).or_insert_with(|| {
let offset = access.address - peripheral.base;
Unbacked {
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 !matches!(fault, Fault::Gated { .. }) => {
report.unmodeled.entry(address).or_insert(program_counter);
}
_ => {
report.refused.entry(address).or_insert(fault);
}
}
}
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));
}
}
}
}