use std::any::Any;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
use super::{Advance, Gap, LinkError, Report, bytes_of};
use crate::chip::uart;
use crate::device::DeviceContext;
use crate::emulator::NO_DEADLINE;
use crate::machine::Board;
use crate::session::{Session, Unimplemented, Unmapped};
use crate::soc::Fault;
use crate::time::VirtualNanos;
#[derive(Debug, Default)]
pub struct EnginePort {
toward_firmware: VecDeque<u8>,
from_firmware: Vec<u8>,
}
impl EnginePort {
pub fn deliver(&mut self, bytes: &[u8]) {
self.toward_firmware.extend(bytes);
}
pub fn take_sent(&mut self) -> Vec<u8> {
std::mem::take(&mut self.from_firmware)
}
}
impl uart::Peer for EnginePort {
fn receive(&mut self, word: u16, _context: &DeviceContext<'_>) {
self.from_firmware.push(word.to_le_bytes()[0]);
}
fn transmit(&mut self, _now: VirtualNanos) -> Option<u16> {
self.toward_firmware.pop_front().map(u16::from)
}
}
fn engine_port<'a>(board: &'a mut dyn Board, name: &str) -> Result<&'a mut EnginePort, LinkError> {
let peer: &mut dyn Any = board.host_mut(name).ok_or_else(|| {
LinkError::Misfit(format!("names a port {name} the board has no host on"))
})?;
peer.downcast_mut::<EnginePort>().ok_or_else(|| {
LinkError::Misfit(format!(
"names a port {name} whose far end isn't the engine"
))
})
}
pub fn attach_engine_ports(board: &mut dyn Board) -> Result<Vec<&'static str>, LinkError> {
let ports: Vec<&'static str> = board.hosts().into_iter().map(|wire| wire.name).collect();
for &port in &ports {
if !board.attach_host(port, Box::new(EnginePort::default())) {
return Err(LinkError::Board(format!(
"names a port {port} it won't take"
)));
}
let handed_back = board.host_mut(port).is_some_and(|peer| {
let peer: &dyn Any = peer;
peer.is::<EnginePort>()
});
if !handed_back {
return Err(LinkError::Board(format!(
"takes a port on {port} and doesn't hand it back from host_mut"
)));
}
}
Ok(ports)
}
pub fn deliver(
board: &mut dyn Board,
inputs: &mut [i64],
ports: &[&str],
advance: &Advance,
now: VirtualNanos,
) -> Result<(), LinkError> {
if advance.until >= NO_DEADLINE {
return Err(LinkError::Misfit(format!(
"asked to reach {} ns, and the virtual clock stops short of {} ns",
advance.until.0, NO_DEADLINE.0
)));
}
if advance.until < now {
return Err(LinkError::Misfit(format!(
"asked to reach {} ns when the run already stands at {} ns",
advance.until.0, now.0
)));
}
if advance.ports.len() != ports.len() {
return Err(LinkError::Misfit(format!(
"sent bytes for {} ports and the board has {}",
advance.ports.len(),
ports.len()
)));
}
for set in &advance.inputs {
let count = inputs.len();
let input = inputs.get_mut(usize::from(set.input)).ok_or_else(|| {
LinkError::Misfit(format!("set input {} and the board has {count}", set.input))
})?;
*input = set.value;
}
if !advance.inputs.is_empty() {
let edge = board
.edge()
.ok_or_else(|| LinkError::Board("has no edge to sense the inputs on".to_owned()))?;
edge.sense_wire(&bytes_of(edge.fields(), inputs)?)?;
}
for (name, bytes) in ports.iter().zip(&advance.ports) {
if !bytes.is_empty() {
engine_port(board, name)?.deliver(bytes);
}
}
Ok(())
}
pub fn sent(board: &mut dyn Board, ports: &[&str]) -> Result<Vec<Vec<u8>>, LinkError> {
ports
.iter()
.map(|name| Ok(engine_port(board, name)?.take_sent()))
.collect()
}
#[derive(Debug, Default)]
pub struct Gaps {
unmapped: BTreeSet<u64>,
refused: BTreeSet<u64>,
unimplemented: BTreeSet<u64>,
unrouted: BTreeSet<(&'static str, u16)>,
}
impl Gaps {
pub fn fresh<D: Board + ?Sized>(&mut self, session: &Session<D>) -> Vec<Gap> {
self.fresh_in(
&session.unmapped(),
session.refused(),
session.unimplemented(),
session.unrouted(),
)
}
fn fresh_in(
&mut self,
unmapped: &[Unmapped],
refused: &BTreeMap<u64, Fault>,
unimplemented: &BTreeMap<u64, Unimplemented>,
unrouted: &BTreeSet<(&'static str, u16)>,
) -> Vec<Gap> {
let mut gaps = Vec::new();
for entry in unmapped {
if self.unmapped.insert(entry.address) {
gaps.push(Gap::Unmapped {
address: entry.address,
program_counter: entry.program_counter,
});
}
}
let outstanding = refused.iter().filter(|(_, fault)| !fault.is_shut_gate());
for (&address, fault) in outstanding {
if self.refused.insert(address) {
gaps.push(Gap::Refused {
address,
why: fault.to_string(),
});
}
}
for (&address, entry) in unimplemented {
if self.unimplemented.insert(address) {
gaps.push(Gap::Unimplemented {
instance: entry.instance.to_owned(),
register: entry.register.to_owned(),
address,
program_counter: entry.program_counter,
});
}
}
for &(instance, line) in unrouted {
if self.unrouted.insert((instance, line)) {
gaps.push(Gap::Unrouted {
instance: instance.to_owned(),
line,
});
}
}
gaps
}
}
#[must_use]
pub fn report<D: Board + ?Sized>(session: &Session<D>) -> Report {
let count = |entries: usize| u64::try_from(entries).expect("a count fits");
Report {
resets: session.resets().iter().map(|reset| reset.at).collect(),
unmapped: count(session.unmapped().len()),
refused: count(
session
.refused()
.values()
.filter(|fault| !fault.is_shut_gate())
.count(),
),
unimplemented: count(session.unimplemented().len()),
unrouted: count(session.unrouted().len()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chip::uart::Peer;
use crate::machine::{HostWire, Target};
use crate::mcu::Mcu;
use crate::soc::Gate;
#[derive(Debug)]
struct Wired {
peer: Option<Box<dyn Peer>>,
hands_back: bool,
}
impl Wired {
fn handing_back(hands_back: bool) -> Self {
Self {
peer: None,
hands_back,
}
}
}
impl Board for Wired {
fn name(&self) -> &'static str {
"wired"
}
fn targets(&mut self) -> Vec<Target<'_>> {
Vec::new()
}
fn target_names(&self) -> Vec<&'static str> {
Vec::new()
}
fn mcu(&mut self, _target: usize) -> Option<&mut dyn Mcu> {
None
}
fn hosts(&self) -> Vec<HostWire> {
vec![HostWire {
name: "wire1",
port: 0,
}]
}
fn attach_host(&mut self, name: &str, peer: Box<dyn Peer>) -> bool {
self.peer = Some(peer);
name == "wire1"
}
fn host_mut(&mut self, name: &str) -> Option<&mut dyn Peer> {
match &mut self.peer {
Some(peer) if self.hands_back && name == "wire1" => Some(peer.as_mut()),
_ => None,
}
}
}
fn quiet(until: u64) -> Advance {
Advance {
until: VirtualNanos(until),
inputs: Vec::new(),
ports: vec![Vec::new()],
}
}
#[test]
fn a_board_that_does_not_hand_its_port_back_is_refused_before_the_manifest() {
let mut board = Wired::handing_back(true);
assert_eq!(attach_engine_ports(&mut board).unwrap(), ["wire1"]);
let mut board = Wired::handing_back(false);
assert!(matches!(
attach_engine_ports(&mut board),
Err(LinkError::Board(why)) if why.contains("wire1")
));
}
#[test]
fn an_instant_behind_the_run_or_past_the_clock_is_refused_and_the_one_it_stands_at_is_not() {
let mut board = Wired::handing_back(true);
let ports = attach_engine_ports(&mut board).unwrap();
let now = VirtualNanos(10);
let mut deliver_at = |until| deliver(&mut board, &mut [], &ports, &quiet(until), now);
assert!(matches!(deliver_at(9), Err(LinkError::Misfit(_))));
assert!(matches!(
deliver_at(NO_DEADLINE.0),
Err(LinkError::Misfit(_))
));
assert!(matches!(deliver_at(u64::MAX), Err(LinkError::Misfit(_))));
assert!(
deliver_at(10).is_ok(),
"a step of no length is still a step"
);
assert!(deliver_at(NO_DEADLINE.0 - 1).is_ok());
}
#[test]
fn an_engine_port_hands_the_controller_its_bytes_in_order_and_keeps_what_the_firmware_sent() {
let mut port = EnginePort::default();
port.deliver(b"ab");
port.deliver(b"c");
let taken: Vec<Option<u16>> = (0..4).map(|_| port.transmit(VirtualNanos(0))).collect();
assert_eq!(
taken,
[
Some(u16::from(b'a')),
Some(u16::from(b'b')),
Some(u16::from(b'c')),
None
]
);
port.from_firmware.extend([1, 2]);
assert_eq!(port.take_sent(), [1, 2]);
assert!(port.take_sent().is_empty(), "each byte is told once");
}
#[test]
fn each_gap_is_told_once_the_first_time_it_is_met_and_a_shut_gate_is_none() {
let mut gaps = Gaps::default();
let unmapped = [Unmapped {
address: 0x1000,
program_counter: 0x20,
}];
let refused = BTreeMap::from([
(0x2000, Fault::Unmapped { address: 0x2000 }),
(
0x2400,
Fault::Gated {
instance: "TIMER",
register: "CONTROL",
by: Gate {
instance: "CLOCKS",
register: "ENABLE",
field: "TIMEREN",
},
},
),
]);
let unimplemented = BTreeMap::from([(
0x3000,
Unimplemented {
instance: "TIMER",
register: "CONTROL",
program_counter: 0x30,
reads: 1,
writes: 0,
written: None,
},
)]);
let unrouted = BTreeSet::from([("TIMER", 7)]);
let first = gaps.fresh_in(&unmapped, &refused, &unimplemented, &unrouted);
assert_eq!(
first,
[
Gap::Unmapped {
address: 0x1000,
program_counter: 0x20
},
Gap::Refused {
address: 0x2000,
why: "no register at 0x2000".to_owned()
},
Gap::Unimplemented {
instance: "TIMER".to_owned(),
register: "CONTROL".to_owned(),
address: 0x3000,
program_counter: 0x30
},
Gap::Unrouted {
instance: "TIMER".to_owned(),
line: 7
},
]
);
assert!(
gaps.fresh_in(&unmapped, &refused, &unimplemented, &unrouted)
.is_empty(),
"nothing new the second time"
);
}
}