use std::collections::BTreeSet;
use std::fmt;
use crate::emulator::{Access, AccessKind};
use crate::mcu::Mcu;
use crate::soc::Peripheral;
#[derive(Debug)]
struct Instance {
peripheral: Peripheral,
modeled: bool,
reached: Vec<bool>,
window: Option<u64>,
}
#[derive(Debug)]
pub struct Coverage {
instances: Vec<Instance>,
reads: u64,
writes: u64,
outside: BTreeSet<u64>,
last: Option<(u64, usize)>,
}
impl Coverage {
#[must_use]
pub fn of(mcu: &mut dyn Mcu) -> Self {
let modeled: Vec<bool> = (0..mcu.bus().slots().len())
.map(|slot| mcu.split(slot).1.is_some())
.collect();
let mut instances: Vec<Instance> = mcu
.bus()
.slots()
.iter()
.enumerate()
.map(|(slot, carried)| Instance {
peripheral: *carried.peripheral(),
modeled: modeled[slot],
reached: vec![false; carried.peripheral().registers.len()],
window: carried.is_window().then_some(0),
})
.collect();
instances.sort_by_key(|instance| instance.peripheral.base);
Self {
instances,
reads: 0,
writes: 0,
outside: BTreeSet::new(),
last: None,
}
}
pub fn saw(&mut self, access: &Access) {
match access.kind {
AccessKind::Read => self.reads += 1,
AccessKind::Write => self.writes += 1,
}
let repeated = self.last.filter(|(address, _)| *address == access.address);
let index = if let Some((_, index)) = repeated {
index
} else {
let at = self
.instances
.partition_point(|instance| instance.peripheral.base <= access.address);
let Some(index) = at
.checked_sub(1)
.filter(|&index| self.instances[index].peripheral.contains(access.address))
else {
self.outside.insert(access.address);
return;
};
self.last = Some((access.address, index));
index
};
let instance = &mut self.instances[index];
if let Some(accesses) = &mut instance.window {
*accesses += 1;
return;
}
if repeated.is_some() {
return;
}
let offset = access.address - instance.peripheral.base;
for (row, register) in instance.peripheral.registers.iter().enumerate() {
if (u64::from(register.offset)..u64::from(register.end())).contains(&offset) {
instance.reached[row] = true;
}
}
}
pub fn untouched(&self) -> impl Iterator<Item = (&'static str, &'static str)> {
self.instances
.iter()
.filter(|instance| instance.modeled)
.flat_map(|instance| {
instance
.peripheral
.registers
.iter()
.zip(&instance.reached)
.filter(|(_, reached)| !**reached)
.map(|(register, _)| (instance.peripheral.name, register.name))
})
}
}
impl fmt::Display for Coverage {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(
formatter,
"{} reads and {} writes over {} of {} instances",
self.reads,
self.writes,
self.instances.iter().filter(|i| i.modeled).count(),
self.instances.len()
)?;
for instance in self.instances.iter().filter(|instance| instance.modeled) {
if let Some(accesses) = instance.window {
writeln!(
formatter,
" {:10} window, {accesses} accesses",
instance.peripheral.name
)?;
continue;
}
let reached = instance.reached.iter().filter(|&&hit| hit).count();
writeln!(
formatter,
" {:10} {reached:3} of {:3} registers",
instance.peripheral.name,
instance.peripheral.registers.len()
)?;
}
for (instance, register) in self.untouched() {
writeln!(formatter, "untouched {instance}.{register}")?;
}
for instance in self
.instances
.iter()
.filter(|instance| !instance.modeled && instance.reached.iter().any(|&hit| hit))
{
let reached = instance.reached.iter().filter(|&&hit| hit).count();
writeln!(
formatter,
"unimplemented {} {reached} of {} registers reached, with no model behind them",
instance.peripheral.name,
instance.peripheral.registers.len()
)?;
}
for address in &self.outside {
writeln!(formatter, "outside every instance {address:#010x}")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::soc::{Access as Rule, ReadAction, RegisterSpec, Width, WriteModify};
use crate::time::VirtualNanos;
const fn register(name: &'static str, offset: u32) -> RegisterSpec {
RegisterSpec {
name,
offset,
width: Width::W32,
reset: 0,
access: Rule::ReadWrite,
read: ReadAction::None,
write: WriteModify::None,
fields: &[],
}
}
static REGISTERS: [RegisterSpec; 3] = [
register("FIRST", 0x0),
register("ALSO_FIRST", 0x0),
register("SECOND", 0x4),
];
static INSTANCE: Peripheral = Peripheral {
name: "ONE",
base: 0x4000_0000,
size: 0x400,
interrupts: &[],
registers: ®ISTERS,
};
fn read_at(address: u64) -> Access {
Access {
kind: AccessKind::Read,
address,
width: Width::W8,
value: 0,
instruction_count: 0,
virtual_time: VirtualNanos(0),
program_counter: Some(0),
}
}
fn coverage() -> Coverage {
Coverage {
instances: vec![Instance {
peripheral: INSTANCE,
modeled: true,
reached: vec![false; REGISTERS.len()],
window: None,
}],
reads: 0,
writes: 0,
outside: BTreeSet::new(),
last: None,
}
}
fn untouched(coverage: &Coverage) -> Vec<&'static str> {
coverage.untouched().map(|(_, register)| register).collect()
}
#[test]
fn a_register_nothing_addressed_is_untouched() {
assert_eq!(
untouched(&coverage()),
["FIRST", "ALSO_FIRST", "SECOND"],
"the gate is worth nothing if it cannot report a miss"
);
}
#[test]
fn a_narrow_access_reaches_the_register_whose_lane_it_is() {
let mut coverage = coverage();
coverage.saw(&read_at(INSTANCE.base + 0x7));
assert_eq!(untouched(&coverage), ["FIRST", "ALSO_FIRST"]);
}
#[test]
fn two_registers_at_one_offset_are_reached_together() {
let mut coverage = coverage();
coverage.saw(&read_at(INSTANCE.base));
assert_eq!(untouched(&coverage), ["SECOND"]);
}
#[test]
fn a_register_is_reached_whatever_row_it_sits_in() {
static BACKWARDS: [RegisterSpec; 2] = [register("SECOND", 0x4), register("FIRST", 0x0)];
let mut coverage = coverage();
coverage.instances[0].peripheral.registers = &BACKWARDS;
coverage.instances[0].reached = vec![false; BACKWARDS.len()];
coverage.saw(&read_at(INSTANCE.base));
assert_eq!(untouched(&coverage), ["SECOND"]);
}
#[test]
fn a_repeated_access_counts_and_reaches_what_the_first_did() {
let mut coverage = coverage();
coverage.saw(&read_at(INSTANCE.base));
coverage.saw(&read_at(INSTANCE.base));
assert_eq!(coverage.reads, 2);
assert_eq!(untouched(&coverage), ["SECOND"]);
coverage.saw(&read_at(INSTANCE.base + 0x4));
assert!(untouched(&coverage).is_empty());
}
#[test]
fn a_window_counts_every_access_repeated_or_not() {
let mut coverage = coverage();
coverage.instances[0].window = Some(0);
coverage.saw(&read_at(INSTANCE.base));
coverage.saw(&read_at(INSTANCE.base));
coverage.saw(&read_at(INSTANCE.base + 0x4));
assert_eq!(coverage.instances[0].window, Some(3));
}
#[test]
fn an_address_inside_no_instance_reaches_nothing() {
let mut coverage = coverage();
coverage.saw(&read_at(0x9000_0100));
assert_eq!(
untouched(&coverage),
["FIRST", "ALSO_FIRST", "SECOND"],
"an address the bus never decoded cannot have reached a register"
);
assert!(coverage.to_string().contains("outside every instance"));
}
#[test]
fn an_instance_with_no_model_is_not_gated_on() {
let mut coverage = coverage();
coverage.instances[0].modeled = false;
assert!(coverage.untouched().next().is_none());
}
}