use super::{Armed, Code, Fault, Fired, Held, Link, LinkError, bytes_of, fits, serve::apply};
use crate::chip::Placed;
use crate::emulator::{
Access, AccessKind, Hit, HitKind, Host, InterruptLine, Settled, Stop, Watch,
};
use crate::machine::Board;
use crate::mcu::Operate;
use crate::session::Session;
use crate::soc::{Endian, lane_shift};
use crate::symbols::{self, Symbols};
use crate::time::VirtualNanos;
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
pub struct Triggers {
armed: Vec<Counting>,
#[serde(skip)]
fired: Vec<Fired>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Counting {
id: u32,
links: Vec<Link>,
fault: Fault,
next: usize,
progress: Progress,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
enum Progress {
Until(VirtualNanos),
Accesses {
from: u64,
to: u64,
kind: AccessKind,
mask: u64,
value: u64,
left: u32,
},
Transactions { chip: u16, ended: u64 },
Hits { watch: Watch, left: u32 },
}
#[derive(Debug, Clone, Copy)]
struct Point {
at: VirtualNanos,
instruction_count: u64,
program_counter: Option<u64>,
}
impl Point {
fn of(settled: Settled) -> Self {
match settled {
Settled::Access { access, .. } => Self {
at: access.virtual_time,
instruction_count: access.instruction_count,
program_counter: access.program_counter,
},
Settled::Stop(stop) => Self {
at: stop.at,
instruction_count: stop.instruction_count,
program_counter: None,
},
Settled::Hit(hit) => Self {
at: hit.at,
instruction_count: hit.instruction_count,
program_counter: (hit.kind == HitKind::Instruction).then_some(hit.address),
},
}
}
}
impl Triggers {
pub fn arm<D: Board + ?Sized>(
&mut self,
mut faults: Vec<Armed>,
session: &mut Session<D>,
stop: Stop,
symbols: Result<&Symbols, &symbols::Error>,
) -> Result<(), LinkError> {
for link in faults.iter_mut().flat_map(|armed| &mut armed.links) {
if let Link::Execute { code, .. } = link
&& let Code::Symbol(name) = code
{
let address = symbols
.map_err(Clone::clone)
.and_then(|symbols| symbols.get(name))
.map_err(|error| {
LinkError::Misfit(format!(
"names an instruction by its symbol, and {error}"
))
})?;
*code = Code::Address(address);
}
}
for (index, armed) in faults.iter().enumerate() {
let mut seen = self
.armed
.iter()
.map(|counting| counting.id)
.chain(faults[..index].iter().map(|earlier| earlier.id));
if seen.any(|id| id == armed.id) {
return Err(LinkError::Misfit(format!(
"armed a fault {} that's already armed",
armed.id
)));
}
check(armed, session)?;
}
let watched: Vec<(u32, (u64, u64))> = self
.armed
.iter()
.flat_map(|counting| {
counting.links[counting.next..]
.iter()
.map(move |link| (counting.id, link))
})
.chain(
faults
.iter()
.flat_map(|armed| armed.links.iter().map(move |link| (armed.id, link))),
)
.filter_map(|(id, link)| match *link {
Link::Write {
address, length, ..
} => Some((id, (address, address + length.get()))),
_ => None,
})
.collect();
if let Some(&(id, (from, to))) = watched.iter().find(|&&(id, range)| {
watched
.iter()
.any(|&(other, watching)| other != id && watching != range)
}) {
return Err(LinkError::Misfit(format!(
"armed fault {id} to watch writes from {from:#x} to {to:#x} beside another fault \
watching another range, and the emulator reports a store to one range only"
)));
}
let point = Point::of(Settled::Stop(stop));
let counting = faults
.into_iter()
.map(|armed| {
Ok(Counting {
progress: start(&armed.links[0], session, point)?,
id: armed.id,
links: armed.links,
fault: armed.fault,
next: 0,
})
})
.collect::<Result<Vec<_>, LinkError>>()?;
self.armed.extend(counting);
Ok(())
}
pub fn settled<D: Board + ?Sized>(
&mut self,
session: &mut Session<D>,
host: &mut dyn Host,
held: &mut Held,
settled: Settled,
) -> Result<(), LinkError> {
if self.armed.is_empty() {
return Ok(());
}
match settled {
Settled::Access { access, .. } if access.program_counter.is_none() => return Ok(()),
Settled::Access { access, value } => {
let endian = session.mcu().cpu().endian;
for counting in &mut self.armed {
counting.progress.saw(&access, value, endian);
}
}
Settled::Hit(hit) => {
for counting in &mut self.armed {
counting.progress.hit(&hit);
}
}
Settled::Stop(_) => {}
}
self.fire_due(session, host, held, Point::of(settled))?;
session.pause_at(self.pause());
Ok(())
}
fn pause(&self) -> Option<VirtualNanos> {
self.armed
.iter()
.filter_map(|counting| match counting.progress {
Progress::Until(at) => Some(at),
_ => None,
})
.min()
}
pub fn take_fired(&mut self) -> Vec<Fired> {
std::mem::take(&mut self.fired)
}
fn fire_due<D: Board + ?Sized>(
&mut self,
session: &mut Session<D>,
host: &mut dyn Host,
held: &mut Held,
point: Point,
) -> Result<(), LinkError> {
let mut index = 0;
while index < self.armed.len() {
let counting = &mut self.armed[index];
let mut done = false;
while counting.progress.due(session, point)? {
if let Progress::Hits { watch, .. } = counting.progress {
session.unwatch(watch);
}
counting.next += 1;
let Some(link) = counting.links.get(counting.next) else {
done = true;
break;
};
counting.progress = start(link, session, point)?;
}
if done {
let counting = self.armed.remove(index);
let conditions = fire(&counting.fault, session, host, held, point)?;
self.fired.push(Fired {
id: counting.id,
at: point.at,
instruction_count: point.instruction_count,
program_counter: point.program_counter,
conditions,
});
} else {
index += 1;
}
}
Ok(())
}
}
impl Progress {
fn saw(&mut self, access: &Access, read_or_written: u64, endian: Endian) {
if let Self::Accesses {
from,
to,
kind,
mask,
value,
left,
} = self
&& access.program_counter.is_some()
&& access.kind == *kind
&& (*from..*to).contains(&access.address)
&& carries(
access,
(*from, *to),
endian,
read_or_written,
(*mask, *value),
)
{
*left = left.saturating_sub(1);
}
}
fn hit(&mut self, hit: &Hit) {
if let Self::Hits { watch, left } = self {
let counts = match (*watch, hit.kind) {
(Watch::Instruction(address), HitKind::Instruction) => address == hit.address,
(Watch::Writes { address, length }, HitKind::Write) => {
(address..address.saturating_add(length)).contains(&hit.address)
}
_ => false,
};
if counts {
*left = left.saturating_sub(1);
}
}
}
fn due<D: Board + ?Sized>(
self,
session: &mut Session<D>,
point: Point,
) -> Result<bool, LinkError> {
Ok(match self {
Self::Until(at) => point.at >= at,
Self::Accesses { left, .. } | Self::Hits { left, .. } => left == 0,
Self::Transactions { chip, ended } => place(session, chip)?.ended() >= ended,
})
}
}
fn carries(
access: &Access,
(from, to): (u64, u64),
endian: Endian,
read_or_written: u64,
(mask, value): (u64, u64),
) -> bool {
let bytes = u64::from(access.width.bytes());
let shift = access
.address
.checked_add(bytes)
.filter(|&end| end <= to)
.and_then(|_| {
let width = u32::try_from(to - from).ok()?;
let byte = u32::try_from(access.address - from).ok()?;
Some(lane_shift(endian, width, byte, access.width))
});
let (lanes, placed) = shift.map_or((0, 0), |shift| {
(
access.width.mask() << shift,
(read_or_written & access.width.mask()) << shift,
)
});
mask & !lanes == 0 && placed & mask == value
}
fn start<D: Board + ?Sized>(
link: &Link,
session: &mut Session<D>,
point: Point,
) -> Result<Progress, LinkError> {
Ok(match *link {
Link::After(length) => Progress::Until(VirtualNanos(point.at.0.saturating_add(length.0))),
Link::Access {
instance,
register,
kind,
mask,
value,
count,
} => {
let (from, to) = span(session, instance, register)?;
Progress::Accesses {
from,
to,
kind,
mask,
value,
left: count - 1,
}
}
Link::Transaction { chip, count } => Progress::Transactions {
chip,
ended: place(session, chip)?.ended() + u64::from(count - 1),
},
Link::Execute {
code: Code::Address(address),
count,
} => watching(session, Watch::Instruction(address), count),
Link::Execute { .. } => {
return Err(LinkError::Misfit(
"names an instruction by what the target can't find".to_owned(),
));
}
Link::Write {
address,
length,
count,
} => watching(
session,
Watch::Writes {
address,
length: length.get(),
},
count,
),
})
}
fn watching<D: Board + ?Sized>(session: &mut Session<D>, watch: Watch, count: u32) -> Progress {
session.watch(watch);
Progress::Hits { watch, left: count }
}
fn check<D: Board + ?Sized>(armed: &Armed, session: &mut Session<D>) -> Result<(), LinkError> {
let misfit = |why: &str| Err(LinkError::Misfit(format!("armed fault {} {why}", armed.id)));
if armed.links.is_empty() {
return misfit("with nothing to fire it");
}
for link in &armed.links {
match *link {
Link::Access {
instance,
register,
mask,
value,
count,
..
} => {
span(session, instance, register)?;
if value & !mask != 0 {
return misfit("to match bits its mask leaves out, which nothing ever does");
}
if count == 0 {
return misfit("to count to zero");
}
}
Link::Transaction { chip, count } => {
place(session, chip)?;
if count == 0 {
return misfit("to count to zero");
}
}
Link::Execute { count, .. } | Link::Write { count, .. } if count == 0 => {
return misfit("to count to zero");
}
Link::Write {
address, length, ..
} => {
let in_memory = address.checked_add(length.get()).is_some_and(|end| {
session.mcu().bus().memory().iter().any(|region| {
region.kind.is_writable()
&& address >= region.base
&& end <= region.base + region.size
})
});
if !in_memory {
return misfit("to watch writes outside the part's RAM");
}
}
Link::Execute {
code: Code::Address(address),
..
} if !session
.mcu()
.bus()
.memory()
.iter()
.any(|region| region.contains(address)) =>
{
return misfit("to watch an instruction outside the part's memory");
}
Link::After(_) | Link::Execute { .. } => {}
}
}
match &armed.fault {
Fault::Conditions(sets) => {
let conditions = edge(session)?.conditions();
for set in sets {
let field = conditions.get(usize::from(set.index)).ok_or_else(|| {
LinkError::Misfit(format!(
"armed a fault setting condition {} and the board has {}",
set.index,
conditions.len()
))
})?;
if !fits(field, set.value)? {
return Err(LinkError::Misfit(format!(
"armed a fault setting {} to {}, which doesn't fit it",
field.name, set.value
)));
}
}
}
Fault::Flip { chip, .. } | Fault::Drop { chip } => {
place(session, *chip)?;
}
Fault::Interrupt { line } => {
let lines = session.mcu().cpu().interrupt_lines;
if *line >= lines {
return Err(LinkError::Misfit(format!(
"armed a fault raising line {line} and the part has {lines}"
)));
}
}
Fault::Reset => {}
Fault::BusError { instance, register } => {
span(session, *instance, *register)?;
}
}
Ok(())
}
fn fire<D: Board + ?Sized>(
fault: &Fault,
session: &mut Session<D>,
host: &mut dyn Host,
held: &mut Held,
point: Point,
) -> Result<Vec<super::Set>, LinkError> {
match fault {
Fault::Conditions(sets) => {
apply(&mut held.conditions, sets, "condition")?;
let edge = edge(session)?;
edge.condition_wire(&bytes_of(edge.conditions(), &held.conditions)?)?;
edge.draw(held.seed, held.advances.saturating_sub(1));
return Ok(sets.clone());
}
Fault::Flip { chip, mask } => place(session, *chip)?.flip(*mask),
Fault::Drop { chip } => place(session, *chip)?.drop_next(),
Fault::Interrupt { line } => {
let line = InterruptLine::new(*line).ok_or_else(|| {
LinkError::Misfit(format!(
"armed a fault raising line {line}, past any core's"
))
})?;
session.pulse(line, host);
}
Fault::Reset => session.reset_by_pin(point.at, point.instruction_count, host),
Fault::BusError { instance, register } => {
let (from, to) = span(session, *instance, *register)?;
session.fail_next(from, to);
}
}
Ok(Vec::new())
}
fn span<D: Board + ?Sized>(
session: &mut Session<D>,
instance: u16,
register: u16,
) -> Result<(u64, u64), LinkError> {
let bus = session.mcu().bus();
let slots = bus.slots();
let peripheral = slots
.get(usize::from(instance))
.ok_or_else(|| {
LinkError::Misfit(format!(
"names instance {instance} and the part has {}",
slots.len()
))
})?
.peripheral();
let spec = peripheral
.registers
.get(usize::from(register))
.ok_or_else(|| {
LinkError::Misfit(format!(
"names register {register} of {} and it has {}",
peripheral.name,
peripheral.registers.len()
))
})?;
Ok((
peripheral.base + u64::from(spec.offset),
peripheral.base + u64::from(spec.end()),
))
}
fn edge<D: Board + ?Sized>(
session: &mut Session<D>,
) -> Result<&mut dyn crate::edge::Coupled, LinkError> {
session
.board_mut()
.edge()
.ok_or_else(|| LinkError::Board("has no edge to name a chip or a condition on".to_owned()))
}
fn place<D: Board + ?Sized>(
session: &mut Session<D>,
chip: u16,
) -> Result<&mut dyn Placed, LinkError> {
let edge = edge(session)?;
let count = edge.chips().len();
edge.place(usize::from(chip))
.ok_or_else(|| LinkError::Misfit(format!("names chip {chip} and the board has {count}")))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::soc::Width;
#[test]
fn a_narrow_access_carries_its_bits_in_the_parts_lanes() {
const REGISTER: (u64, u64) = (0x4000_0010, 0x4000_0014);
let half = Access::new(AccessKind::Write, REGISTER.0, Width::W16, 0x8000);
let high_bit = (0x8000_0000, 0x8000_0000);
let low_half_bit = (0x8000, 0x8000);
assert!(carries(
&half,
REGISTER,
Endian::Little,
0x8000,
low_half_bit
));
assert!(!carries(&half, REGISTER, Endian::Little, 0x8000, high_bit));
assert!(carries(&half, REGISTER, Endian::Big, 0x8000, high_bit));
assert!(!carries(&half, REGISTER, Endian::Big, 0x8000, low_half_bit));
let last = Access::new(AccessKind::Write, REGISTER.0 + 3, Width::W8, 0x80);
assert!(carries(&last, REGISTER, Endian::Little, 0x80, high_bit));
assert!(carries(&last, REGISTER, Endian::Big, 0x80, (0x80, 0x80)));
let past = Access::new(AccessKind::Write, REGISTER.0 + 2, Width::W32, 0x8000);
assert!(!carries(&past, REGISTER, Endian::Little, 0x8000, high_bit));
assert!(carries(&past, REGISTER, Endian::Little, 0x8000, (0, 0)));
}
}