use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use crate::device::{Level, LineChange, ResetCause, Told};
use crate::emulator::{Access, AccessKind, Error, Host, InterruptLine, Stop, StopReason, Watch};
use crate::machine::Board;
use crate::mcu::{Mcu, Operate, Refused};
use crate::snapshot::{self, Reader, Store, Writer};
use crate::soc::{Bus, Extent, Fault, Width};
use crate::time::VirtualNanos;
#[derive(Debug)]
pub struct Session<D: Board + ?Sized = dyn Board> {
board: Box<D>,
target: usize,
until: VirtualNanos,
pause: Option<VirtualNanos>,
watches: BTreeMap<Watch, u32>,
unsettled: Vec<(Watch, bool)>,
failing: Vec<(u64, u64)>,
failed_access: bool,
refused_access: bool,
report: Report,
failed: Option<Error>,
resetting: bool,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
#[serde(bound(deserialize = "'de: 'static"))]
struct Report {
unmapped: BTreeMap<u64, u64>,
unrouted: BTreeSet<(&'static str, u16)>,
refused: BTreeMap<u64, Fault>,
unimplemented: BTreeMap<u64, Unimplemented>,
undefined: BTreeMap<u64, Undefined>,
resets: Vec<Reset>,
told: BTreeMap<(&'static str, &'static str, &'static str), Told>,
raised: BTreeSet<u16>,
}
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, serde::Serialize, serde::Deserialize)]
#[serde(bound(deserialize = "'de: 'static"))]
pub struct Reset {
pub at: VirtualNanos,
pub instruction_count: u64,
pub cause: ResetCause,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
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(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Undefined {
pub instance: &'static str,
pub program_counter: u64,
pub reads: u64,
pub writes: 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> Store for Session<D> {
fn store(&self, to: &mut Writer) -> Result<(), snapshot::Error> {
let Self {
board,
target: _,
until,
pause,
watches,
unsettled: _,
failing,
failed_access: _,
refused_access: _,
report,
failed,
resetting,
} = self;
if failed.is_some() {
return Err(snapshot::Error::Failed);
}
if *resetting {
return Err(snapshot::Error::Resetting);
}
to.put(until)?;
to.put(pause)?;
to.put(watches)?;
to.put(failing)?;
to.put(report)?;
board.store(to)
}
fn load(&mut self, from: &mut Reader) -> Result<(), snapshot::Error> {
let Self {
board,
target: _,
until,
pause,
watches,
unsettled,
failing,
failed_access: _,
refused_access: _,
report,
failed: _,
resetting,
} = self;
*resetting = false;
*until = from.take()?;
*pause = from.take()?;
*watches = from.take()?;
*unsettled = watches.keys().map(|&watch| (watch, true)).collect();
*failing = from.take()?;
*report = from.take()?;
board.load(from)
}
}
impl<D: Board + ?Sized> Session<D> {
pub fn new(mut board: Box<D>, target: usize, until: VirtualNanos) -> Result<Self, Error> {
if let Some(edge) = board.edge() {
edge.check()?;
}
let machine = board.name();
let count = board.target_names().len();
let (name, aliases, boot) = {
let targets = board.targets();
let chosen = targets.get(target).ok_or(Error::NoTarget {
machine,
target,
count,
})?;
(
chosen.name,
chosen.core.aliases.clone(),
chosen.core.boot.region,
)
};
{
let mcu = board.mcu(target).ok_or(Error::NoTarget {
machine,
target,
count,
})?;
mcu.check()?;
let regions = mcu.bus().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(),
});
}
if let Some(boot) = boot
&& regions.iter().all(|region| region.name != boot)
{
return Err(Error::NoBootRegion {
machine,
target: name,
region: boot,
regions: regions.iter().map(|region| region.name).collect(),
});
}
let size = |name: &str| {
regions
.iter()
.find(|region| region.name == name)
.map_or(0, |region| region.size)
};
let extent = |name, base, size| Extent { name, base, size };
let spans: Vec<Extent> =
regions
.iter()
.map(|region| extent(region.name, region.base, region.size))
.chain(mcu.bus().peripherals().map(|peripheral| {
extent(peripheral.name, peripheral.base, peripheral.size)
}))
.collect();
let placed: Vec<Extent> = aliases
.iter()
.map(|alias| extent(alias.region, alias.at, size(alias.region)))
.collect();
for (index, (alias, at)) in aliases.iter().zip(&placed).enumerate() {
let others = placed
.iter()
.enumerate()
.filter(|&(other, _)| other != index)
.map(|(_, span)| span);
if let Some(clash) = spans.iter().chain(others).find(|span| span.overlaps(at)) {
return Err(Error::OverlappingAlias {
machine,
target: name,
region: alias.region,
at: alias.at,
other: clash.name,
});
}
}
}
Ok(Self {
board,
target,
until,
pause: None,
watches: BTreeMap::new(),
unsettled: Vec::new(),
failing: Vec::new(),
failed_access: false,
refused_access: false,
report: Report::default(),
failed: None,
resetting: false,
})
}
#[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;
}
pub fn watch(&mut self, watch: Watch) {
let asked = self.watches.entry(watch).or_insert(0);
*asked += 1;
if *asked == 1 {
self.unsettled.push((watch, true));
}
}
pub fn unwatch(&mut self, watch: Watch) {
let Some(asked) = self.watches.get_mut(&watch) else {
return;
};
*asked -= 1;
if *asked == 0 {
self.watches.remove(&watch);
self.unsettled.push((watch, false));
}
}
pub fn settle_watches(&mut self, host: &mut dyn Host) {
for (watch, start) in std::mem::take(&mut self.unsettled) {
if !start {
host.unwatch(watch);
} else if !host.watch(watch) {
self.failed.get_or_insert(Error::Unwatched(watch));
}
}
}
pub(crate) fn refuse_watch(&mut self, watch: Watch) {
self.failed.get_or_insert(Error::Unwatched(watch));
}
pub fn fail_next(&mut self, from: u64, to: u64) {
self.failing.push((from, to));
}
pub fn take_failure(&mut self) -> bool {
std::mem::take(&mut self.failed_access)
}
pub(crate) fn take_refused(&mut self) -> bool {
std::mem::take(&mut self.refused_access)
}
pub(crate) fn peek(&mut self, at: VirtualNanos, address: u64, width: Width) -> Option<u64> {
self.mcu().peek(at, address, width).ok()
}
#[must_use]
pub fn pause(&self) -> Option<VirtualNanos> {
self.pause
}
pub fn pause_at(&mut self, at: Option<VirtualNanos>) {
self.pause = at;
}
#[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 horizon = horizon(self.until, self.pause);
deadline_before(self.mcu().next_event(), horizon)
}
pub fn access(&mut self, access: Access, host: &mut dyn Host) -> (u64, VirtualNanos) {
let failing = access
.program_counter
.is_some()
.then(|| {
self.failing
.iter()
.position(|&(from, to)| (from..to).contains(&access.address))
})
.flatten();
if let Some(index) = failing {
self.failing.remove(index);
self.failed_access = true;
let answer = match access.kind {
AccessKind::Read => 0,
AccessKind::Write => access.value,
};
return (answer, self.next_deadline());
}
let Self {
board,
target,
until,
pause,
report,
failed,
resetting,
refused_access,
..
} = self;
let until = horizon(*until, *pause);
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),
};
if let Some(address) = mcu.bus_mut().take_undefined() {
answered_undefined(report, mcu.bus(), address, &access);
}
if let Some((slot, row)) = mcu.bus_mut().take_unbacked() {
answered_from_a_file_alone(report, mcu.bus(), slot, row, &access);
}
let value = match served {
Ok(value) => value,
Err(fault) => {
if access.program_counter.is_some() {
refuse(report, access.address, access.program_counter, fault);
} else if !fault.is_shut_gate() {
*refused_access = true;
}
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,
);
*resetting = restarted;
if !restarted {
apply(mcu, host, report);
}
(value, deadline_before(mcu.next_event(), until))
}
pub fn stop(&mut self, stop: Stop, host: &mut dyn Host) -> VirtualNanos {
if self.pause.is_some_and(|pause| stop.at >= pause) {
self.pause = None;
let due = self.mcu().next_event().is_some_and(|next| next <= stop.at);
if !due && stop.reason != StopReason::Reset && stop.at < self.until {
return self.next_deadline();
}
}
let Self {
board,
target,
until,
pause,
report,
failed,
resetting,
..
} = self;
let until = horizon(*until, *pause);
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, host) {
failed.get_or_insert(error);
}
if stop.reason == StopReason::Reset {
mcu.reset(stop.at, ResetCause::Core);
report.raised.clear();
if report.resets.len() == resets_before {
report.resets.push(Reset {
at: stop.at,
instruction_count: stop.instruction_count,
cause: ResetCause::Core,
});
}
}
*resetting = report.resets.len() > resets_before;
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 until = horizon(until, self.pause);
let Self {
board,
target,
report,
failed,
resetting,
..
} = self;
let mcu = board.mcu(*target).expect("checked at construction");
let resets_before = report.resets.len();
if let Some(error) = report.look_outside(mcu, at, instructions, host) {
failed.get_or_insert(error);
}
*resetting = report.resets.len() > resets_before;
deadline_before(mcu.next_event(), until)
}
pub fn pulse(&mut self, line: InterruptLine, host: &mut dyn Host) {
if !self.report.raised.contains(&line.index()) {
host.set_interrupt(line, Level::High);
host.set_interrupt(line, Level::Low);
}
}
pub fn reset_by_pin(&mut self, at: VirtualNanos, instructions: u64, host: &mut dyn Host) {
let Self {
board,
target,
report,
resetting,
..
} = self;
let mcu = board.mcu(*target).expect("checked at construction");
if *resetting {
mcu.reset(at, ResetCause::Pin);
return;
}
let reset = Reset {
at,
instruction_count: instructions,
cause: ResetCause::Pin,
};
restart(report, mcu, reset, host);
*resetting = true;
}
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 undefined(&self) -> &BTreeMap<u64, Undefined> {
&self.report.undefined
}
#[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")
}
pub(crate) fn new_way(&mut self, at: VirtualNanos) {
for wire in self.board.hosts() {
if let Some(host) = self.board.host_mut(wire.name) {
host.forget_after(at);
}
}
}
}
fn answered_from_a_file_alone(
report: &mut Report,
bus: &Bus,
slot: usize,
row: usize,
access: &Access,
) {
let Some(program_counter) = access.program_counter else {
return;
};
let peripheral = bus.slots()[slot].peripheral();
let entry = report
.unimplemented
.entry(access.address)
.or_insert_with(|| Unimplemented {
instance: peripheral.name,
register: peripheral.registers[row].name,
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 answered_undefined(report: &mut Report, bus: &Bus, address: u64, access: &Access) {
let (Some(slot), Some(program_counter)) = (bus.slot_at(address), access.program_counter) else {
return;
};
let entry = report
.undefined
.entry(address)
.or_insert_with(|| Undefined {
instance: bus.slots()[slot].peripheral().name,
program_counter,
reads: 0,
writes: 0,
});
match access.kind {
AccessKind::Read => entry.reads += 1,
AccessKind::Write => entry.writes += 1,
}
}
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 {
fn look_outside(
&mut self,
mcu: &mut dyn Mcu,
at: VirtualNanos,
instructions: u64,
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(mcu, host, self);
}
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;
};
let reset = Reset {
at,
instruction_count: instructions,
cause,
};
restart(report, mcu, reset, host);
true
}
fn restart(report: &mut Report, mcu: &mut dyn Mcu, reset: Reset, host: &mut dyn Host) {
mcu.reset(reset.at, reset.cause);
report.resets.push(reset);
report.raised.clear();
host.request_reset();
}
fn note(report: &mut Report, refusals: Vec<Refused>) {
for refusal in refusals {
refuse(report, refusal.address, None, refusal.fault);
}
}
fn horizon(until: VirtualNanos, pause: Option<VirtualNanos>) -> VirtualNanos {
pause.map_or(until, |pause| pause.min(until))
}
fn deadline_before(next_event: Option<VirtualNanos>, until: VirtualNanos) -> VirtualNanos {
next_event.map_or(until, |at| at.min(until))
}
fn apply(mcu: &mut dyn Mcu, host: &mut dyn Host, report: &mut Report) {
let mut drive = |line: InterruptLine, level: Level| {
if level == Level::High {
report.raised.insert(line.index());
} else {
report.raised.remove(&line.index());
}
host.set_interrupt(line, level);
};
let mut unrouted = Vec::new();
for change in mcu.drain_interrupts() {
match InterruptLine::new(change.interrupt) {
Some(line) => drive(line, change.level),
None => unrouted.push((change.instance, change.interrupt)),
}
}
for LineChange { line, level } in mcu.drain_lines() {
drive(line, level);
}
report.unrouted.extend(unrouted);
}