use std::io::{self, BufWriter, Write};
use super::{
Access, AccessKind, Driver, Error, Finished, Host, Settled, 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, Clone, Copy, PartialEq, Eq)]
pub enum Accesses {
Printed,
Counted,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct Printed<D: Board + ?Sized, W> {
pub session: Session<D>,
pub coverage: Coverage,
pub output: W,
pub exit_status: Option<u32>,
}
pub fn run_printed<D, W>(
mut session: Session<D>,
start: &Start<'_>,
outputs: Option<VirtualNanos>,
accesses: Accesses,
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);
}
if outputs.is_some() && session.board_mut().edge().is_none() {
return Err(Error::NoEdge {
machine: session.board().name(),
});
}
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(),
accesses,
failed: None,
};
let Finished {
mut session,
driver,
stop,
exit_status,
interrupted,
} = 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);
}
let ended = stop
.map(|stop| (stop.at, stop.instruction_count, stop_reason(stop.reason)))
.or(interrupted.map(|stood| (stood.at, stood.instruction_count, "interrupted")));
if let Some((at, count, reason)) = ended {
writeln!(output, "stop t={} n={count} reason={reason}", at.0)?;
}
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(),
crate::device::ResetCause::Pin => "pin".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 (address, seen) in session.undefined() {
writeln!(
output,
"undefined instance={} at={address:#010x} pc={:#010x} reads={} writes={}",
seen.instance, 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
)?;
}
if let Some(code) = exit_status {
writeln!(output, "exit code={code}")?;
}
let output = output
.into_inner()
.map_err(io::IntoInnerError::into_error)?;
Ok(Printed {
session,
coverage,
output,
exit_status,
})
}
struct Printing<W: Write + Send> {
output: BufWriter<W>,
coverage: Coverage,
until: VirtualNanos,
period: Option<VirtualNanos>,
outputs: Vec<i64>,
accesses: Accesses,
failed: Option<io::Error>,
}
impl<D: Board + ?Sized, W: Write + Send> Driver<D> for Printing<W> {
fn on_settled(&mut self, _session: &mut Session<D>, _host: &mut dyn Host, settled: Settled) {
let Settled::Access { access, value } = settled else {
return;
};
self.coverage.saw(&access);
if self.accesses == Accesses::Printed && self.failed.is_none() {
self.failed = write_access(&mut self.output, &access, value).err();
}
}
fn on_console(&mut self, at: VirtualNanos, instruction_count: u64, bytes: &[u8]) {
if self.failed.is_none() {
self.failed =
write_text(&mut self.output, "console", at, instruction_count, bytes).err();
}
}
fn on_debugger_print(&mut self, at: VirtualNanos, instruction_count: u64, text: &[u8]) {
if self.failed.is_none() {
self.failed =
write_text(&mut self.output, "dprintf", at, instruction_count, text).err();
}
}
fn on_debugger_attached(&mut self, at: VirtualNanos, instruction_count: u64, attached: bool) {
let what = if attached { "attached" } else { "detached" };
if self.failed.is_none() {
self.failed = writeln!(
self.output,
"debugger {what} t={} n={instruction_count}",
at.0
)
.err();
}
}
fn may_rewind(&self) -> bool {
true
}
fn on_stop(
&mut self,
session: &mut Session<D>,
stop: Stop,
_host: &mut dyn Host,
) -> 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_text(
output: &mut impl Write,
kind: &str,
at: VirtualNanos,
instruction_count: u64,
bytes: &[u8],
) -> io::Result<()> {
writeln!(
output,
"{kind} t={} n={instruction_count} text={}",
at.0,
String::from_utf8_lossy(bytes).escape_debug()
)
}
fn write_access(output: &mut impl Write, access: &Access, value: u64) -> io::Result<()> {
let kind = match access.kind {
AccessKind::Read => "read ",
AccessKind::Write => "write",
};
let (time, count, address, bits) = (
access.virtual_time.0,
access.instruction_count,
access.address,
access.width.bits(),
);
match access.program_counter {
Some(pc) => writeln!(
output,
"{kind} t={time} n={count} pc={pc:#010x} at={address:#010x} w={bits} v={value:#010x}"
),
None => writeln!(
output,
"{kind} t={time} n={count} pc=debugger at={address:#010x} w={bits} v={value:#010x}"
),
}
}
fn stop_reason(reason: StopReason) -> &'static str {
match reason {
StopReason::Deadline => "deadline",
StopReason::Idle => "idle",
StopReason::Reset => "reset",
}
}