use std::io::{self, BufWriter, Write};
use super::{Access, AccessKind, Driver, Error, Finished, Start, Stop, StopReason, run};
use crate::coverage::Coverage;
use crate::link::values_of;
use crate::machine::Board;
use crate::session::Session;
use crate::time::VirtualNanos;
pub const NO_DEADLINE: VirtualNanos = VirtualNanos(i64::MAX as u64);
#[derive(Debug)]
pub struct Printed<D: Board + ?Sized, W> {
pub session: Session<D>,
pub coverage: Coverage,
pub output: W,
}
pub fn run_printed<D, W>(
mut session: Session<D>,
start: &Start<'_>,
outputs: Option<VirtualNanos>,
output: W,
) -> Result<Printed<D, W>, Error>
where
D: Board + ?Sized + 'static,
W: Write + Send + 'static,
{
if outputs.is_some_and(|period| period.0 == 0) {
return Err(Error::ZeroPeriod);
}
let until = session.until();
session.run_until(outputs.map_or(until, |period| period.min(until)));
let target = session.target();
let coverage = Coverage::of(
session
.board_mut()
.mcu(target)
.expect("the session checked the target"),
);
let driver = Printing {
output: BufWriter::new(output),
coverage,
until,
period: outputs,
outputs: Vec::new(),
failed: None,
};
let Finished {
mut session,
driver,
stop,
} = run(session, start, driver)?;
let Printing {
mut output,
coverage,
failed,
..
} = driver;
if let Some(error) = failed {
return Err(error.into());
}
if let Some(error) = session.take_error() {
return Err(error);
}
if let Some(stop) = stop {
writeln!(
output,
"stop t={} n={} reason={}",
stop.at.0,
stop.instruction_count,
stop_reason(stop.reason)
)?;
}
for reset in session.resets() {
let cause = match reset.cause {
crate::device::ResetCause::Core => "core".to_owned(),
crate::device::ResetCause::Model(instance) => instance.to_owned(),
};
writeln!(
output,
"reset t={} n={} cause={cause}",
reset.at.0, reset.instructions
)?;
}
for unmapped in session.unmapped() {
writeln!(output, "{unmapped}")?;
}
for (address, fault) in session.refused() {
if !fault.is_shut_gate() {
writeln!(output, "refused at={address:#010x} why={fault}")?;
}
}
for (address, seen) in session.unimplemented() {
let wrote = seen
.written
.map_or_else(String::new, |value| format!(" wrote={value:#010x}"));
writeln!(
output,
"unimplemented instance={} register={} at={address:#010x} pc={:#010x} reads={} \
writes={}{wrote}",
seen.instance, seen.register, seen.program_counter, seen.reads, seen.writes
)?;
}
for (instance, interrupt) in session.unrouted() {
writeln!(output, "unrouted instance={instance} interrupt={interrupt}")?;
}
for told in session.told() {
writeln!(
output,
"told instance={} register={} t={} reason={}",
told.instance, told.register, told.at.0, told.reason
)?;
}
let output = output
.into_inner()
.map_err(io::IntoInnerError::into_error)?;
Ok(Printed {
session,
coverage,
output,
})
}
struct Printing<W: Write + Send> {
output: BufWriter<W>,
coverage: Coverage,
until: VirtualNanos,
period: Option<VirtualNanos>,
outputs: Vec<i64>,
failed: Option<io::Error>,
}
impl<D: Board + ?Sized, W: Write + Send> Driver<D> for Printing<W> {
fn on_access(&mut self, access: &Access, value: u64) {
self.coverage.saw(access);
if self.failed.is_none() {
self.failed = write_access(&mut self.output, access, value).err();
}
}
fn on_stop(&mut self, session: &mut Session<D>, stop: Stop) -> Option<VirtualNanos> {
let period = self.period?;
if let Some(edge) = session.board_mut().edge() {
let fields = edge.output_fields();
match values_of(fields, &edge.drive_wire()) {
Ok(read) => {
for (index, (field, value)) in fields.iter().zip(&read).enumerate() {
if self.outputs.get(index) != Some(value) && self.failed.is_none() {
self.failed = writeln!(
self.output,
"output t={} {}={value}",
stop.at.0, field.name
)
.err();
}
}
self.outputs = read;
}
Err(error) => {
self.failed.get_or_insert_with(|| io::Error::other(error));
}
}
}
if stop.at >= self.until {
return None;
}
Some(VirtualNanos(
stop.at.0.saturating_add(period.0).min(self.until.0),
))
}
}
fn write_access(output: &mut impl Write, access: &Access, value: u64) -> io::Result<()> {
let kind = match access.kind {
AccessKind::Read => "read ",
AccessKind::Write => "write",
};
writeln!(
output,
"{kind} t={} n={} pc={:#010x} at={:#010x} w={} v={value:#010x}",
access.virtual_time.0,
access.instruction_count,
access.program_counter,
access.address,
access.width.bits(),
)
}
fn stop_reason(reason: StopReason) -> &'static str {
match reason {
StopReason::Deadline => "deadline",
StopReason::Idle => "idle",
StopReason::Reset => "reset",
}
}