use vj_abi::{Region, SimTime};
use super::{EdgeKind, ListenerRole, StateCellId, ValueChangeSink};
pub struct Dispatch {
scheduler: Option<Box<dyn ValueChangeSink + Send>>,
waveform: Option<Box<dyn ValueChangeSink + Send>>,
vpi: Option<Box<dyn ValueChangeSink + Send>>,
}
impl Dispatch {
pub fn new() -> Self {
Dispatch {
scheduler: None,
waveform: None,
vpi: None,
}
}
pub fn register(&mut self, role: ListenerRole, sink: Box<dyn ValueChangeSink + Send>) {
match role {
ListenerRole::Scheduler => self.scheduler = Some(sink),
ListenerRole::Waveform => self.waveform = Some(sink),
ListenerRole::Vpi => self.vpi = Some(sink),
}
}
pub fn dispatch(&mut self, cell: StateCellId, edge: EdgeKind, at: SimTime, region: Region) {
if let Some(sink) = &mut self.scheduler {
sink.notify_change(cell, edge, at, region);
}
if let Some(sink) = &mut self.waveform {
sink.notify_change(cell, edge, at, region);
}
if let Some(sink) = &mut self.vpi {
sink.notify_change(cell, edge, at, region);
}
}
pub fn notify_scheduler(
&mut self,
cell: StateCellId,
edge: EdgeKind,
at: SimTime,
region: Region,
) {
if let Some(sink) = &mut self.scheduler {
sink.notify_change(cell, edge, at, region);
}
}
pub fn notify_waveform_and_vpi(
&mut self,
cell: StateCellId,
edge: EdgeKind,
at: SimTime,
region: Region,
) {
if let Some(sink) = &mut self.waveform {
sink.notify_change(cell, edge, at, region);
}
if let Some(sink) = &mut self.vpi {
sink.notify_change(cell, edge, at, region);
}
}
}
impl Default for Dispatch {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
fn t(units: i64) -> SimTime {
SimTime {
sim_units: units,
delta: 0,
}
}
type RecordedCall = (StateCellId, EdgeKind, SimTime, Region);
struct SpySink {
label: &'static str,
log: Arc<Mutex<Vec<&'static str>>>,
calls: Arc<Mutex<Vec<RecordedCall>>>,
}
impl SpySink {
fn new(label: &'static str, log: Arc<Mutex<Vec<&'static str>>>) -> Self {
SpySink {
label,
log,
calls: Arc::new(Mutex::new(Vec::new())),
}
}
}
impl ValueChangeSink for SpySink {
fn notify_change(
&mut self,
cell: StateCellId,
edge: EdgeKind,
at: SimTime,
region: Region,
) {
self.log.lock().unwrap().push(self.label);
self.calls.lock().unwrap().push((cell, edge, at, region));
}
}
#[test]
fn register_scheduler_then_dispatch_calls_only_scheduler() {
let log = Arc::new(Mutex::new(Vec::new()));
let scheduler_spy = SpySink::new("scheduler", log.clone());
let scheduler_calls = scheduler_spy.calls.clone();
let waveform_spy = SpySink::new("waveform", log.clone());
let waveform_calls = waveform_spy.calls.clone();
let mut dispatch = Dispatch::new();
dispatch.register(ListenerRole::Scheduler, Box::new(scheduler_spy));
dispatch.register(ListenerRole::Waveform, Box::new(waveform_spy));
let cell = StateCellId(7);
let at = t(100);
dispatch.dispatch(cell, EdgeKind::Posedge, at, Region::Active);
assert_eq!(*log.lock().unwrap(), vec!["scheduler", "waveform"]);
assert_eq!(scheduler_calls.lock().unwrap().len(), 1);
assert_eq!(
scheduler_calls.lock().unwrap()[0],
(cell, EdgeKind::Posedge, at, Region::Active)
);
assert_eq!(waveform_calls.lock().unwrap().len(), 1);
}
#[test]
fn all_three_roles_registered_all_three_fire_in_fixed_order() {
let log = Arc::new(Mutex::new(Vec::new()));
let scheduler_spy = SpySink::new("scheduler", log.clone());
let waveform_spy = SpySink::new("waveform", log.clone());
let vpi_spy = SpySink::new("vpi", log.clone());
let mut dispatch = Dispatch::new();
dispatch.register(ListenerRole::Vpi, Box::new(vpi_spy));
dispatch.register(ListenerRole::Waveform, Box::new(waveform_spy));
dispatch.register(ListenerRole::Scheduler, Box::new(scheduler_spy));
dispatch.dispatch(StateCellId(1), EdgeKind::None, t(0), Region::Nba);
assert_eq!(*log.lock().unwrap(), vec!["scheduler", "waveform", "vpi"]);
}
#[test]
fn dispatch_table_has_no_heap_growth_across_many_events() {
let log = Arc::new(Mutex::new(Vec::new()));
let scheduler_spy = SpySink::new("scheduler", log.clone());
let scheduler_calls = scheduler_spy.calls.clone();
let waveform_spy = SpySink::new("waveform", log.clone());
let waveform_calls = waveform_spy.calls.clone();
let vpi_spy = SpySink::new("vpi", log.clone());
let vpi_calls = vpi_spy.calls.clone();
let mut dispatch = Dispatch::new();
dispatch.register(ListenerRole::Scheduler, Box::new(scheduler_spy));
dispatch.register(ListenerRole::Waveform, Box::new(waveform_spy));
dispatch.register(ListenerRole::Vpi, Box::new(vpi_spy));
const N: usize = 10_000;
for i in 0..N {
dispatch.dispatch(
StateCellId(i as u64),
EdgeKind::Posedge,
t(i as i64),
Region::Active,
);
}
assert_eq!(scheduler_calls.lock().unwrap().len(), N);
assert_eq!(waveform_calls.lock().unwrap().len(), N);
assert_eq!(vpi_calls.lock().unwrap().len(), N);
assert_eq!(log.lock().unwrap().len(), 3 * N);
let Dispatch {
scheduler,
waveform,
vpi,
} = dispatch;
assert!(scheduler.is_some());
assert!(waveform.is_some());
assert!(vpi.is_some());
}
#[test]
fn notify_scheduler_fires_only_scheduler_never_waveform_or_vpi() {
let log = Arc::new(Mutex::new(Vec::new()));
let scheduler_spy = SpySink::new("scheduler", log.clone());
let waveform_spy = SpySink::new("waveform", log.clone());
let waveform_calls = waveform_spy.calls.clone();
let vpi_spy = SpySink::new("vpi", log.clone());
let vpi_calls = vpi_spy.calls.clone();
let mut dispatch = Dispatch::new();
dispatch.register(ListenerRole::Scheduler, Box::new(scheduler_spy));
dispatch.register(ListenerRole::Waveform, Box::new(waveform_spy));
dispatch.register(ListenerRole::Vpi, Box::new(vpi_spy));
dispatch.notify_scheduler(StateCellId(4), EdgeKind::Posedge, t(60), Region::Active);
assert_eq!(*log.lock().unwrap(), vec!["scheduler"]);
assert_eq!(waveform_calls.lock().unwrap().len(), 0);
assert_eq!(vpi_calls.lock().unwrap().len(), 0);
}
#[test]
fn notify_waveform_and_vpi_fires_waveform_then_vpi_never_scheduler() {
let log = Arc::new(Mutex::new(Vec::new()));
let scheduler_spy = SpySink::new("scheduler", log.clone());
let scheduler_calls = scheduler_spy.calls.clone();
let vpi_spy = SpySink::new("vpi", log.clone());
let waveform_spy = SpySink::new("waveform", log.clone());
let mut dispatch = Dispatch::new();
dispatch.register(ListenerRole::Scheduler, Box::new(scheduler_spy));
dispatch.register(ListenerRole::Vpi, Box::new(vpi_spy));
dispatch.register(ListenerRole::Waveform, Box::new(waveform_spy));
dispatch.notify_waveform_and_vpi(StateCellId(3), EdgeKind::None, t(50), Region::Nba);
assert_eq!(*log.lock().unwrap(), vec!["waveform", "vpi"]);
assert_eq!(
scheduler_calls.lock().unwrap().len(),
0,
"notify_waveform_and_vpi must never reach Scheduler"
);
}
#[test]
fn attempting_a_fourth_role_does_not_compile() {
fn describe(role: ListenerRole) -> &'static str {
match role {
ListenerRole::Scheduler => "scheduler",
ListenerRole::Waveform => "waveform",
ListenerRole::Vpi => "vpi",
}
}
assert_eq!(describe(ListenerRole::Scheduler), "scheduler");
assert_eq!(describe(ListenerRole::Waveform), "waveform");
assert_eq!(describe(ListenerRole::Vpi), "vpi");
}
}