use super::*;
use crate::sva::ir::{Automaton, Transition};
use crate::sva::property::{PropId, SeqRef};
use std::cell::{Cell, RefCell};
struct Mock {
tick: Cell<bool>,
live: RefCell<Vec<bool>>,
}
impl Host for Mock {
fn clock_ticked(&self, _: ClockId, _: &[u128]) -> bool {
self.tick.get()
}
fn leaf(&self, leaf: LeafId, row: &[u128], _: &[i64]) -> i64 {
i64::from(row[leaf.0 as usize] != 0)
}
fn case_pair(&self, _: LeafId, _: LeafId, _: &[u128], _: &[i64]) -> bool {
false
}
fn live(&self, leaf: LeafId, _: &[u128]) -> bool {
self.live.borrow()[leaf.0 as usize]
}
}
fn seq2(au: &mut Automaton, a: u32, b: u32, min: u32, max: Option<u32>) -> SeqRef {
let entry = au.new_state();
let s1 = au.new_state();
au.add_edge(entry, Some(LeafId(a)), Transition::Epsilon, s1);
let s2 = au.new_state();
au.add_edge(s1, None, Transition::Delay { min, max }, s2);
let s3 = au.new_state();
au.add_edge(s2, Some(LeafId(b)), Transition::Epsilon, s3);
au.add_edge(
s3,
None,
Transition::Match {
actions: Vec::new(),
},
s3,
);
SeqRef::single(entry, false)
}
fn leaf_seq(au: &mut Automaton, l: u32) -> SeqRef {
let entry = au.new_state();
let s1 = au.new_state();
au.add_edge(entry, Some(LeafId(l)), Transition::Epsilon, s1);
au.add_edge(
s1,
None,
Transition::Match {
actions: Vec::new(),
},
s1,
);
SeqRef::single(entry, false)
}
fn implication(disable: Option<u32>) -> PropertyIr {
let mut au = Automaton::empty();
let ante = leaf_seq(&mut au, 0);
let cons_seq = leaf_seq(&mut au, 1);
let props = vec![
Prop::Seq {
seq: cons_seq,
strength: None,
},
Prop::Implication {
ante,
cons: PropId(0),
overlapped: false,
},
];
PropertyIr {
automaton: au,
props,
decls: Vec::new(),
root: PropId(1),
disable: disable.map(LeafId),
nv_rules: Vec::new(),
}
}
fn cfg(ir: PropertyIr, kind: AssertKind, leaves: usize) -> EngineConfig {
EngineConfig {
ir,
kind,
n_clocks: 1,
leaf_reads: vec![Vec::new(); leaves],
leaf_writes: vec![None; leaves],
activated: false,
captured: Vec::new(),
disable_mixed: false,
history_depth: 0,
hist_clocks: 0,
future: false,
future_clock: 0,
live_history: false,
tick_history: false,
}
}
type Tick = Vec<(EventKind, u64)>;
fn drive(e: &mut Engine, data: &[(Vec<u128>, Vec<bool>)]) -> Vec<Tick> {
let host = Mock {
tick: Cell::new(false),
live: RefCell::new(vec![false; 8]),
};
let mut now = 0;
let mut out = Vec::new();
for (row, live) in data {
now += 5;
host.tick.set(false);
*host.live.borrow_mut() = vec![false; 8];
let ev = e.fire(&host, now).unwrap();
assert!(ev.is_empty());
e.end_of_step(row.clone());
now += 5;
host.tick.set(true);
let mut l = vec![false; 8];
l[..live.len()].copy_from_slice(live);
*host.live.borrow_mut() = l;
let ev = e.fire(&host, now).unwrap();
out.push(ev.iter().map(|x| (x.kind, x.attempt)).collect());
e.end_of_step(row.clone());
}
out
}
fn row(a: u128, b: u128, c: u128) -> Vec<u128> {
vec![a, b, c]
}
#[test]
fn implication_reports_pass_and_fail_per_attempt_in_start_order() {
let mut e = Engine::new(cfg(implication(None), AssertKind::Assert, 2), row(0, 0, 0)).unwrap();
let data: Vec<_> = [(0, 0), (1, 0), (0, 0), (1, 1), (0, 1), (0, 0)]
.iter()
.map(|&(a, b)| (row(a, b, 0), vec![]))
.collect();
use EventKind::{Fail, Pass};
assert_eq!(
drive(&mut e, &data),
vec![
vec![(Pass, 0)],
vec![],
vec![(Fail, 1), (Pass, 2)],
vec![],
vec![(Pass, 3), (Pass, 4)],
vec![(Pass, 5)],
]
);
assert_eq!(e.stats().attempted, 6);
assert_eq!(e.stats().failed, 1);
}
#[test]
fn cover_property_counts_attempts_successes_and_vacuous_successes() {
let mut e = Engine::new(
cfg(implication(None), AssertKind::CoverProperty, 2),
row(0, 0, 0),
)
.unwrap();
let data: Vec<_> = [(0, 0), (1, 0), (0, 0), (1, 1), (0, 1), (0, 0)]
.iter()
.map(|&(a, b)| (row(a, b, 0), vec![]))
.collect();
drive(&mut e, &data);
let s = e.stats();
assert_eq!(
(s.attempted, s.passed, s.vacuous, s.failed, s.disabled),
(6, 5, 4, 1, 0)
);
}
#[test]
fn window_is_trimmed_when_attempts_decide() {
let mut e = Engine::new(cfg(implication(None), AssertKind::Assert, 2), row(0, 0, 0)).unwrap();
let data: Vec<_> = (0..50).map(|_| (row(0, 0, 0), vec![])).collect();
drive(&mut e, &data);
assert!(e.positions.len() <= 1, "window kept {}", e.positions.len());
}
#[test]
fn disable_true_while_pending_discards_the_attempt() {
let mut e = Engine::new(
cfg(implication(Some(2)), AssertKind::Assert, 3),
row(0, 0, 0),
)
.unwrap();
let data = vec![
(row(1, 0, 0), vec![false, false, false]),
(row(0, 0, 0), vec![false, false, true]),
(row(0, 0, 0), vec![false, false, false]),
];
use EventKind::Pass;
assert_eq!(drive(&mut e, &data), vec![vec![], vec![], vec![(Pass, 2)]]);
assert_eq!(e.stats().disabled, 2);
assert_eq!(e.stats().failed, 0);
}
#[test]
fn restrict_is_never_checked() {
let mut e = Engine::new(
cfg(implication(None), AssertKind::Restrict, 2),
row(0, 0, 0),
)
.unwrap();
let data = vec![(row(1, 0, 0), vec![]), (row(0, 0, 0), vec![])];
assert_eq!(drive(&mut e, &data), vec![Tick::new(), Tick::new()]);
assert_eq!(e.stats().attempted, 0);
}
fn range_seq() -> PropertyIr {
let mut au = Automaton::empty();
let seq = seq2(&mut au, 0, 1, 1, Some(2));
PropertyIr {
automaton: au,
props: vec![Prop::Seq {
seq,
strength: None,
}],
decls: Vec::new(),
root: PropId(0),
disable: None,
nv_rules: Vec::new(),
}
}
#[test]
fn cover_sequence_counts_every_match_cover_property_once() {
let data = vec![
(row(1, 0, 0), vec![]),
(row(0, 1, 0), vec![]),
(row(0, 1, 0), vec![]),
];
let mut s = Engine::new(cfg(range_seq(), AssertKind::CoverSequence, 2), row(0, 0, 0)).unwrap();
use EventKind::Match;
assert_eq!(
drive(&mut s, &data),
vec![vec![], vec![(Match, 0)], vec![(Match, 0)]]
);
assert_eq!(s.stats().matched, 2);
let mut p = Engine::new(cfg(range_seq(), AssertKind::CoverProperty, 2), row(0, 0, 0)).unwrap();
use EventKind::Pass;
assert_eq!(drive(&mut p, &data), vec![vec![], vec![(Pass, 0)], vec![]]);
assert_eq!(p.stats().passed, 1);
assert_eq!(p.stats().attempted, 3);
}
#[test]
fn cover_sequence_attempts_retire_after_the_span() {
let mut s = Engine::new(cfg(range_seq(), AssertKind::CoverSequence, 2), row(0, 0, 0)).unwrap();
let data: Vec<_> = (0..20).map(|_| (row(1, 0, 0), vec![])).collect();
drive(&mut s, &data);
assert!(
s.attempts.len() <= 4,
"{} attempts pending",
s.attempts.len()
);
assert!(
s.positions.len() <= 5,
"{} positions kept",
s.positions.len()
);
}
struct TwoClocks {
ticks: RefCell<[bool; 2]>,
}
impl Host for TwoClocks {
fn clock_ticked(&self, clock: ClockId, _: &[u128]) -> bool {
self.ticks.borrow()[clock.0 as usize]
}
fn leaf(&self, leaf: LeafId, row: &[u128], _: &[i64]) -> i64 {
i64::from(row[0] >> leaf.0 & 1 != 0)
}
fn case_pair(&self, _: LeafId, _: LeafId, _: &[u128], _: &[i64]) -> bool {
false
}
fn live(&self, _: LeafId, _: &[u128]) -> bool {
false
}
}
fn two_clock_seq(strict: bool, tail: bool) -> PropertyIr {
let mut au = Automaton::empty();
let entry = au.new_state();
let s1 = au.new_state();
au.add_edge(entry, Some(LeafId(0)), Transition::Epsilon, s1);
let s2 = au.new_state();
au.add_edge(
s1,
None,
Transition::ClockSync {
to: ClockId(1),
strict,
},
s2,
);
let s3 = au.new_state();
au.add_edge(s2, Some(LeafId(1)), Transition::Epsilon, s3);
let end = if tail {
let s4 = au.new_state();
au.add_edge(
s3,
None,
Transition::Delay {
min: 1,
max: Some(1),
},
s4,
);
let s5 = au.new_state();
au.add_edge(s4, Some(LeafId(1)), Transition::Epsilon, s5);
s5
} else {
s3
};
au.add_edge(
end,
None,
Transition::Match {
actions: Vec::new(),
},
end,
);
PropertyIr {
automaton: au,
props: vec![Prop::Seq {
seq: SeqRef {
entry,
admits_empty: false,
clock: ClockId(0),
end_clock: ClockId(1),
},
strength: None,
}],
decls: Vec::new(),
root: PropId(0),
disable: None,
nv_rules: Vec::new(),
}
}
fn two_clock_run(e: &mut Engine, steps: &[([bool; 2], u128)]) -> Vec<Tick> {
let host = TwoClocks {
ticks: RefCell::new([false; 2]),
};
let mut out = Vec::new();
for (n, (ticks, end)) in steps.iter().enumerate() {
*host.ticks.borrow_mut() = *ticks;
let ev = e.fire(&host, 10 * (n as u64 + 1)).unwrap();
out.push(ev.iter().map(|x| (x.kind, x.attempt)).collect());
e.end_of_step(vec![*end]);
}
out
}
#[test]
fn multiclock_cover_sequence_counts_matches_across_the_clock_change() {
use EventKind::Match;
let mut cfg = cfg(two_clock_seq(true, false), AssertKind::CoverSequence, 2);
cfg.n_clocks = 2;
let mut e = Engine::new(cfg, vec![0]).unwrap();
let r = two_clock_run(
&mut e,
&[
([false, false], 0b00),
([true, false], 0b01), ([false, true], 0b10), ([true, false], 0b01), ([false, true], 0b11), ([true, false], 0b10), ([false, false], 0b10),
([false, true], 0b00), ([true, true], 0b00),
([true, true], 0b00),
],
);
let mut expect = vec![Tick::new(); 10];
expect[7] = vec![(Match, 2)];
assert_eq!(r, expect, "{r:?}");
assert_eq!(e.stats().matched, 1);
}
#[test]
fn a_clock_change_is_strict_for_one_and_weak_for_zero() {
use EventKind::Match;
for (strict, at_same_instant) in [(true, None), (false, Some(1))] {
let mut cfg = cfg(two_clock_seq(strict, false), AssertKind::CoverSequence, 2);
cfg.n_clocks = 2;
let mut e = Engine::new(cfg, vec![0]).unwrap();
let r = two_clock_run(&mut e, &[([false, false], 0b11), ([true, true], 0b00)]);
let expect = match at_same_instant {
Some(i) => {
let mut v = vec![Tick::new(); 2];
v[i] = vec![(Match, 0)];
v
}
None => vec![Tick::new(); 2],
};
assert_eq!(r, expect, "strict={strict}: {r:?}");
}
}
#[test]
fn multiclock_cover_sequence_attempts_retire() {
let mut cfg = cfg(two_clock_seq(true, true), AssertKind::CoverSequence, 2);
cfg.n_clocks = 2;
let mut e = Engine::new(cfg, vec![0]).unwrap();
let mut steps = Vec::new();
for _ in 0..40 {
steps.push(([true, false], 0b11));
steps.push(([false, true], 0b11));
}
two_clock_run(&mut e, &steps);
assert!(
e.attempts.len() <= 4,
"{} attempts pending",
e.attempts.len()
);
assert!(
e.positions.len() <= 8,
"{} positions kept",
e.positions.len()
);
}
fn expect_leaf0() -> PropertyIr {
let mut au = Automaton::empty();
let seq = leaf_seq(&mut au, 0);
PropertyIr {
automaton: au,
props: vec![Prop::Seq {
seq,
strength: None,
}],
decls: Vec::new(),
root: PropId(0),
disable: None,
nv_rules: Vec::new(),
}
}
fn expect_cfg() -> EngineConfig {
EngineConfig {
activated: true,
..cfg(expect_leaf0(), AssertKind::Expect, 1)
}
}
fn tick_host(tick: bool) -> Mock {
Mock {
tick: Cell::new(tick),
live: RefCell::new(vec![false; 8]),
}
}
#[test]
fn expect_first_evaluation_is_the_clocking_event_after_the_one_of_its_step() {
let host = tick_host(true);
let mut e = Engine::new(expect_cfg(), vec![0]).unwrap();
e.activate_tagged(10, Vec::new(), 7);
let ev = e.fire(&host, 10).unwrap();
assert!(
ev.is_empty(),
"no attempt starts at the activation step's tick"
);
assert_eq!(e.stats().attempted, 0);
e.end_of_step(vec![1]);
let ev = e.fire(&host, 20).unwrap();
assert_eq!(ev.len(), 1);
assert_eq!(ev[0].kind, EventKind::Pass);
assert_eq!(ev[0].tag, 7, "the event carries the activation's tag");
assert_eq!(e.stats().attempted, 1);
}
#[test]
fn expect_is_a_single_evaluation_thread_and_reports_failure() {
let host = tick_host(true);
let mut e = Engine::new(expect_cfg(), vec![0]).unwrap();
e.activate_tagged(10, Vec::new(), 1);
assert!(e.fire(&host, 10).unwrap().is_empty());
e.end_of_step(vec![0]);
let ev = e.fire(&host, 20).unwrap();
assert_eq!(ev.len(), 1);
assert_eq!(ev[0].kind, EventKind::Fail);
e.end_of_step(vec![1]);
assert!(e.fire(&host, 30).unwrap().is_empty());
e.end_of_step(vec![1]);
assert!(e.fire(&host, 40).unwrap().is_empty());
assert_eq!(e.stats().attempted, 1);
}
#[test]
fn expect_activated_in_a_step_without_a_tick_waits_for_the_next_tick() {
let host = tick_host(false);
let mut e = Engine::new(expect_cfg(), vec![1]).unwrap();
e.activate_tagged(10, Vec::new(), 3);
assert!(e.fire(&host, 10).unwrap().is_empty());
e.end_of_step(vec![1]);
host.tick.set(true);
let ev = e.fire(&host, 20).unwrap();
assert_eq!(ev.len(), 1);
assert_eq!((ev[0].kind, ev[0].tag), (EventKind::Pass, 3));
}
#[test]
fn expect_activated_after_the_tick_of_its_step_was_evaluated_still_waits() {
let host = tick_host(true);
let mut e = Engine::new(expect_cfg(), vec![1]).unwrap();
assert!(e.fire(&host, 10).unwrap().is_empty());
e.activate_tagged(10, Vec::new(), 5);
assert!(e.fire(&host, 10).unwrap().is_empty());
assert_eq!(e.stats().attempted, 0);
e.end_of_step(vec![1]);
let ev = e.fire(&host, 20).unwrap();
assert_eq!((ev[0].kind, ev[0].tag), (EventKind::Pass, 5));
}
use crate::sched::assert_control::{Q_ALL, Q_CHECKING, Q_FAIL, Q_PASS_NONVACUOUS};
fn tick_at(e: &mut Engine, now: u64, row: Vec<u128>) -> Vec<Event> {
let host = tick_host(true);
e.end_of_step(row);
let ev = e.fire(&host, now).unwrap();
e.end_of_step(vec![0, 0, 0]);
ev
}
#[test]
fn checking_off_starts_no_attempts_but_leaves_executing_ones_alone() {
let mut e = Engine::new(cfg(implication(None), AssertKind::Assert, 2), row(0, 0, 0)).unwrap();
assert!(tick_at(&mut e, 10, row(1, 0, 0)).is_empty(), "a=1: pending");
assert_eq!(e.stats().attempted, 1);
e.set_checking(false);
let ev = tick_at(&mut e, 20, row(0, 0, 0));
assert_eq!(
ev.iter().map(|x| (x.kind, x.attempt)).collect::<Vec<_>>(),
vec![(EventKind::Fail, 0)],
"the executing attempt fails (b is 0) although checking is off"
);
assert_eq!(e.stats().attempted, 1, "no attempt started at tick 20");
assert!(tick_at(&mut e, 30, row(0, 0, 0)).is_empty());
assert_eq!(e.stats().attempted, 1);
e.set_checking(true);
let ev = tick_at(&mut e, 40, row(0, 0, 0));
assert_eq!(ev.len(), 1, "a = 0: the new attempt passes vacuously");
assert_eq!(e.stats().attempted, 2);
}
#[test]
fn kill_aborts_executing_attempts_without_an_outcome() {
let mut e = Engine::new(cfg(implication(None), AssertKind::Assert, 2), row(0, 0, 0)).unwrap();
assert!(tick_at(&mut e, 10, row(1, 0, 0)).is_empty());
e.kill();
e.set_checking(false);
assert!(
tick_at(&mut e, 20, row(0, 0, 0)).is_empty(),
"the aborted attempt does not fail"
);
let s = e.stats();
assert_eq!((s.attempted, s.failed, s.passed), (1, 0, 0));
assert!(e.positions.len() <= 1, "the window is released");
e.set_checking(true);
let ev = tick_at(&mut e, 30, row(1, 0, 0));
assert!(ev.is_empty());
let ev = tick_at(&mut e, 40, row(0, 0, 0));
assert_eq!(ev.iter().filter(|x| x.kind == EventKind::Fail).count(), 1);
}
#[test]
fn kill_drops_matured_instances_that_have_not_started() {
let host = tick_host(false);
let mut e = Engine::new(
EngineConfig {
activated: true,
..cfg(implication(None), AssertKind::Assert, 2)
},
row(0, 0, 0),
)
.unwrap();
e.activate_tagged(10, Vec::new(), 1);
assert!(e.fire(&host, 10).unwrap().is_empty());
e.kill();
host.tick.set(true);
e.end_of_step(row(1, 0, 0));
assert!(e.fire(&host, 20).unwrap().is_empty());
assert_eq!(e.stats().attempted, 0, "the instance never started");
}
#[test]
fn events_carry_the_action_flags_of_the_attempt_start() {
let mut e = Engine::new(cfg(implication(None), AssertKind::Assert, 2), row(0, 0, 0)).unwrap();
assert!(tick_at(&mut e, 10, row(1, 0, 0)).is_empty());
e.set_action_flags(Q_CHECKING | Q_FAIL);
let ev = tick_at(&mut e, 20, row(0, 0, 0));
let by_attempt: Vec<(u64, u32)> = ev.iter().map(|x| (x.attempt, x.flags)).collect();
assert_eq!(by_attempt, vec![(0, Q_ALL), (1, Q_CHECKING | Q_FAIL)]);
assert_eq!(by_attempt[0].1 & Q_PASS_NONVACUOUS, Q_PASS_NONVACUOUS);
}
use crate::sva::ir::{CallArg, CallId};
fn call_seq(au: &mut Automaton) -> SeqRef {
let entry = au.new_state();
let s1 = au.new_state();
let s2 = au.new_state();
let s3 = au.new_state();
let s4 = au.new_state();
let s5 = au.new_state();
au.add_edge(entry, Some(LeafId(0)), Transition::Epsilon, s1);
au.add_edge(
s1,
None,
Transition::SubroutineCall {
call: CallId(0),
args: vec![CallArg::Value(LeafId(1))],
},
s2,
);
au.add_edge(
s2,
None,
Transition::Delay {
min: 1,
max: Some(1),
},
s3,
);
au.add_edge(s3, Some(LeafId(1)), Transition::Epsilon, s4);
au.add_edge(
s4,
None,
Transition::SubroutineCall {
call: CallId(1),
args: vec![],
},
s5,
);
au.add_edge(
s5,
None,
Transition::Match {
actions: Vec::new(),
},
s5,
);
SeqRef::single(entry, false)
}
fn call_implication(disable: Option<u32>) -> PropertyIr {
let mut au = Automaton::empty();
let ante = call_seq(&mut au);
let cons = leaf_seq(&mut au, 2);
PropertyIr {
automaton: au,
props: vec![
Prop::Seq {
seq: cons,
strength: None,
},
Prop::Implication {
ante,
cons: PropId(0),
overlapped: true,
},
],
decls: Vec::new(),
root: PropId(1),
disable: disable.map(LeafId),
nv_rules: Vec::new(),
}
}
fn drive_events(e: &mut Engine, data: &[(Vec<u128>, Vec<bool>)]) -> Vec<Vec<Event>> {
let host = tick_host(false);
let mut now = 0;
let mut out = Vec::new();
for (row, live) in data {
now += 5;
host.tick.set(false);
*host.live.borrow_mut() = vec![false; 8];
assert!(e.fire(&host, now).unwrap().is_empty());
e.end_of_step(row.clone());
now += 5;
host.tick.set(true);
let mut l = vec![false; 8];
l[..live.len()].copy_from_slice(live);
*host.live.borrow_mut() = l;
out.push(e.fire(&host, now).unwrap());
e.end_of_step(row.clone());
}
out
}
type Shown = Vec<(EventKind, u64, Option<(u32, Vec<Option<i64>>)>)>;
fn shown(events: &[Event]) -> Shown {
events
.iter()
.map(|e| {
(
e.kind,
e.attempt,
e.call.as_ref().map(|c| (c.call.0, c.args.clone())),
)
})
.collect()
}
fn calls_only(ticks: &[Vec<Event>]) -> Vec<Shown> {
ticks
.iter()
.map(|t| {
shown(t)
.into_iter()
.filter(|(k, _, _)| *k == EventKind::Call)
.collect()
})
.collect()
}
#[test]
fn a_call_is_reported_at_its_end_point_before_the_outcome() {
let mut e = Engine::new(
cfg(call_implication(None), AssertKind::Assert, 3),
row(0, 0, 0),
)
.unwrap();
let data = vec![(row(1, 0, 1), vec![]), (row(0, 1, 1), vec![])];
let ticks = drive_events(&mut e, &data);
use EventKind::{Call, Pass};
assert_eq!(shown(&ticks[0]), vec![(Call, 0, Some((0, vec![Some(0)])))]);
assert_eq!(
shown(&ticks[1]),
vec![
(Call, 0, Some((1, vec![]))),
(Pass, 0, None),
(Pass, 1, None)
]
);
}
#[test]
fn a_failing_attempt_still_executes_its_calls() {
let mut e = Engine::new(
cfg(call_implication(None), AssertKind::Assert, 3),
row(0, 0, 0),
)
.unwrap();
let data = vec![(row(1, 0, 1), vec![]), (row(0, 1, 0), vec![])];
let ticks = drive_events(&mut e, &data);
use EventKind::{Call, Fail, Pass};
assert_eq!(
shown(&ticks[1]),
vec![
(Call, 0, Some((1, vec![]))),
(Fail, 0, None),
(Pass, 1, None)
]
);
assert_eq!(e.stats().failed, 1);
}
#[test]
fn a_pending_attempt_reports_each_call_once_however_often_it_is_re_run() {
let mut au = Automaton::empty();
let entry = au.new_state();
let s1 = au.new_state();
let s2 = au.new_state();
let s3 = au.new_state();
let s4 = au.new_state();
au.add_edge(entry, Some(LeafId(0)), Transition::Epsilon, s1);
au.add_edge(
s1,
None,
Transition::Delay {
min: 1,
max: Some(3),
},
s2,
);
au.add_edge(s2, Some(LeafId(1)), Transition::Epsilon, s3);
au.add_edge(
s3,
None,
Transition::SubroutineCall {
call: CallId(0),
args: vec![CallArg::Value(LeafId(1))],
},
s4,
);
au.add_edge(
s4,
None,
Transition::Match {
actions: Vec::new(),
},
s4,
);
let ante = SeqRef::single(entry, false);
let cons = leaf_seq(&mut au, 2);
let ir = PropertyIr {
automaton: au,
props: vec![
Prop::Seq {
seq: cons,
strength: None,
},
Prop::Implication {
ante,
cons: PropId(0),
overlapped: true,
},
],
decls: Vec::new(),
root: PropId(1),
disable: None,
nv_rules: Vec::new(),
};
let mut e = Engine::new(cfg(ir, AssertKind::Assert, 3), row(0, 0, 0)).unwrap();
let data = vec![
(row(1, 0, 1), vec![]),
(row(0, 1, 1), vec![]),
(row(0, 1, 1), vec![]),
(row(0, 0, 1), vec![]),
];
let ticks = drive_events(&mut e, &data);
let one = vec![(EventKind::Call, 0, Some((0, vec![Some(1)])))];
assert_eq!(
calls_only(&ticks),
vec![vec![], one.clone(), one, vec![]],
"attempt 0 ends at ticks 1 and 2, each reported at its own tick, once"
);
assert_eq!(shown(&ticks[3]).first(), Some(&(EventKind::Pass, 0, None)));
}
#[test]
fn a_disabled_attempt_executes_only_the_calls_before_the_disabling() {
let mut e = Engine::new(
cfg(call_implication(Some(3)), AssertKind::Assert, 4),
row(0, 0, 0),
)
.unwrap();
let data = vec![
(row(1, 0, 1), vec![false, false, false, false]),
(row(0, 1, 1), vec![false, false, false, true]),
];
let ticks = drive_events(&mut e, &data);
use EventKind::Call;
assert_eq!(shown(&ticks[0]), vec![(Call, 0, Some((0, vec![Some(0)])))]);
assert_eq!(shown(&ticks[1]), vec![], "call #1 is at the disabling tick");
assert_eq!(e.stats().disabled, 2);
}
#[test]
fn cover_sequence_executes_calls_at_every_end_point() {
let mut au = Automaton::empty();
let entry = au.new_state();
let s1 = au.new_state();
let s2 = au.new_state();
let s3 = au.new_state();
let s4 = au.new_state();
au.add_edge(entry, Some(LeafId(0)), Transition::Epsilon, s1);
au.add_edge(
s1,
None,
Transition::Delay {
min: 1,
max: Some(2),
},
s2,
);
au.add_edge(s2, Some(LeafId(1)), Transition::Epsilon, s3);
au.add_edge(
s3,
None,
Transition::SubroutineCall {
call: CallId(7),
args: vec![],
},
s4,
);
au.add_edge(
s4,
None,
Transition::Match {
actions: Vec::new(),
},
s4,
);
let seq = SeqRef::single(entry, false);
let ir = PropertyIr {
automaton: au,
props: vec![Prop::Seq {
seq,
strength: None,
}],
decls: Vec::new(),
root: PropId(0),
disable: None,
nv_rules: Vec::new(),
};
let mut e = Engine::new(cfg(ir, AssertKind::CoverSequence, 2), row(0, 0, 0)).unwrap();
let data = vec![
(row(1, 0, 0), vec![]),
(row(0, 1, 0), vec![]),
(row(0, 1, 0), vec![]),
];
let ticks = drive_events(&mut e, &data);
use EventKind::{Call, Match};
assert_eq!(shown(&ticks[0]), vec![]);
assert_eq!(
shown(&ticks[1]),
vec![(Call, 0, Some((7, vec![]))), (Match, 0, None)]
);
assert_eq!(
shown(&ticks[2]),
vec![(Call, 0, Some((7, vec![]))), (Match, 0, None)]
);
}
#[test]
fn unsupported_call_placements_are_refused_with_the_reason() {
let with_abort = {
let mut au = Automaton::empty();
let seq = call_seq(&mut au);
PropertyIr {
automaton: au,
props: vec![
Prop::Seq {
seq,
strength: None,
},
Prop::Abort {
cond: LeafId(2),
p: PropId(0),
mode: AbortMode::Accept,
},
],
decls: Vec::new(),
root: PropId(1),
disable: None,
nv_rules: Vec::new(),
}
};
let err = Engine::new(cfg(with_abort, AssertKind::Assert, 3), row(0, 0, 0))
.err()
.expect("refused");
assert!(err.0.contains("accept_on"), "{err}");
let expect = EngineConfig {
activated: true,
..cfg(call_implication(None), AssertKind::Expect, 3)
};
let err = Engine::new(expect, row(0, 0, 0)).err().expect("refused");
assert!(err.0.contains("expect"), "{err}");
let mut ir = expect_leaf0();
let mut inner = Automaton::empty();
call_seq(&mut inner);
ir.automaton.new_end_point(crate::sva::ir::EndPoint {
method: crate::sva::ir::EndMethod::Triggered,
site: 0,
automaton: inner,
flow_out: Vec::new(),
});
let err = Engine::new(cfg(ir, AssertKind::Assert, 1), vec![0])
.err()
.expect("refused");
assert!(err.0.contains(".triggered"), "{err}");
assert!(
Engine::new(
cfg(call_implication(None), AssertKind::Assert, 3),
row(0, 0, 0)
)
.is_ok()
);
}
struct HistHost {
back: usize,
tick: Cell<bool>,
}
impl Host for HistHost {
fn clock_ticked(&self, _: ClockId, prev: &[u128]) -> bool {
assert_eq!(prev.len(), 2, "the clock test gets the plain row");
self.tick.get()
}
fn leaf(&self, leaf: LeafId, row: &[u128], _: &[i64]) -> i64 {
assert_eq!(row.len(), 2 * (1 + self.back), "row + history");
match leaf.0 {
0 => i64::from(row[0] != 0 && row[2 * self.back] == 0),
_ => i64::from(row[1] != 0),
}
}
fn case_pair(&self, _: LeafId, _: LeafId, _: &[u128], _: &[i64]) -> bool {
false
}
fn live(&self, _: LeafId, _: &[u128]) -> bool {
false
}
}
fn hist_run(back: usize, initial: [u128; 2], ticks: &[[u128; 2]]) -> Vec<Tick> {
let mut e = Engine::new(
EngineConfig {
history_depth: back,
..cfg(implication(None), AssertKind::Assert, 2)
},
initial.to_vec(),
)
.unwrap();
let host = HistHost {
back,
tick: Cell::new(false),
};
let mut now = 0;
let mut out = Vec::new();
for row in ticks {
now += 5;
host.tick.set(false);
assert!(e.fire(&host, now).unwrap().is_empty());
e.end_of_step(row.to_vec());
now += 5;
host.tick.set(true);
let ev = e.fire(&host, now).unwrap();
out.push(ev.iter().map(|x| (x.kind, x.attempt)).collect());
e.end_of_step(row.to_vec());
}
out
}
#[test]
fn a_position_carries_the_previous_ticks_rows_for_the_sampled_value_functions() {
use EventKind::{Fail, Pass};
let ticks = [[1, 0], [1, 1], [0, 0], [1, 0], [1, 0]];
assert_eq!(
hist_run(1, [0, 0], &ticks),
vec![
vec![],
vec![(Pass, 0), (Pass, 1)],
vec![(Pass, 2)],
vec![],
vec![(Fail, 3), (Pass, 4)],
]
);
let r = hist_run(1, [1, 0], &ticks);
assert_eq!(r[0], vec![(Pass, 0)], "{r:?}");
}
#[test]
fn history_depth_two_reaches_the_second_previous_tick() {
use EventKind::Pass;
let r = hist_run(2, [0, 0], &[[1, 1], [1, 1], [1, 1], [0, 0]]);
assert_eq!(
r,
vec![
vec![],
vec![(Pass, 0)],
vec![(Pass, 1), (Pass, 2)],
vec![(Pass, 3)],
],
"{r:?}"
);
}
#[test]
fn a_sampled_value_function_in_a_multiclock_assertion_is_accepted() {
assert!(
Engine::new(
EngineConfig {
history_depth: 1,
n_clocks: 2,
..cfg(implication(None), AssertKind::Assert, 2)
},
vec![0, 0],
)
.is_ok()
);
}
struct Multi {
ticks: RefCell<Vec<bool>>,
l0: fn(&[u128]) -> bool,
live_rows: RefCell<Vec<Vec<u128>>>,
live_fn: fn(&[u128]) -> bool,
}
impl Multi {
fn new(l0: fn(&[u128]) -> bool) -> Self {
Multi {
ticks: RefCell::new(Vec::new()),
l0,
live_rows: RefCell::new(Vec::new()),
live_fn: |_| false,
}
}
}
impl Host for Multi {
fn clock_ticked(&self, clock: ClockId, _: &[u128]) -> bool {
self.ticks.borrow()[clock.0 as usize]
}
fn leaf(&self, _: LeafId, row: &[u128], _: &[i64]) -> i64 {
i64::from((self.l0)(row))
}
fn case_pair(&self, _: LeafId, _: LeafId, _: &[u128], _: &[i64]) -> bool {
false
}
fn live(&self, _: LeafId, sampled: &[u128]) -> bool {
self.live_rows.borrow_mut().push(sampled.to_vec());
(self.live_fn)(sampled)
}
}
fn multi_run(e: &mut Engine, host: &Multi, steps: &[(&[bool], u128)]) -> Vec<Tick> {
let mut out = Vec::new();
for (n, (ticks, end)) in steps.iter().enumerate() {
*host.ticks.borrow_mut() = ticks.to_vec();
let ev = e.fire(host, 10 * (n as u64 + 1)).unwrap();
out.push(ev.iter().map(|x| (x.kind, x.attempt)).collect());
e.end_of_step(vec![*end]);
}
out
}
#[test]
fn a_history_only_clock_keeps_its_own_history_and_makes_no_position() {
use EventKind::{Fail, Pass};
let mut e = Engine::new(
EngineConfig {
n_clocks: 1,
hist_clocks: 1,
history_depth: 1,
..cfg(expect_leaf0(), AssertKind::Assert, 1)
},
vec![0],
)
.unwrap();
let host = Multi::new(|row| {
assert_eq!(row.len(), 3, "own row, then one history row per clock");
row[0] != 0 && row[2] == 0
});
let r = multi_run(
&mut e,
&host,
&[
(&[false, false], 0),
(&[false, true], 1),
(&[true, false], 1),
(&[true, true], 0),
(&[true, false], 1),
(&[true, false], 1),
],
);
assert_eq!(
r,
vec![
vec![],
vec![],
vec![(Pass, 0)],
vec![(Pass, 1)],
vec![(Fail, 2)],
vec![(Fail, 3)],
],
"{r:?}"
);
}
#[test]
fn a_future_function_holds_positions_for_the_next_global_tick() {
use EventKind::{Fail, Pass};
let mut e = Engine::new(
EngineConfig {
n_clocks: 1,
hist_clocks: 1,
future: true,
future_clock: 1,
..cfg(expect_leaf0(), AssertKind::Assert, 1)
},
vec![0],
)
.unwrap();
let host = Multi::new(|row| {
assert_eq!(row.len(), 2, "own row, then the next global tick's");
row[1] != 0
});
let r = multi_run(
&mut e,
&host,
&[
(&[false, false], 0),
(&[true, false], 0),
(&[true, false], 1),
(&[false, true], 0),
(&[false, true], 0),
(&[true, false], 0),
(&[false, false], 0),
(&[false, true], 0),
],
);
assert_eq!(
r,
vec![
vec![],
vec![],
vec![],
vec![(Pass, 0), (Pass, 1)],
vec![],
vec![],
vec![],
vec![(Fail, 2)],
],
"{r:?}"
);
}
#[test]
fn live_leaves_are_handed_the_position_shaped_row() {
let mut ir = expect_leaf0();
ir.disable = Some(LeafId(1));
let mut e = Engine::new(
EngineConfig {
n_clocks: 1,
history_depth: 1,
live_history: true,
..cfg(ir, AssertKind::Assert, 2)
},
vec![0],
)
.unwrap();
let host = Multi::new(|_| true);
let r = multi_run(&mut e, &host, &[(&[false], 5), (&[true], 7), (&[true], 9)]);
assert_eq!(r.len(), 3);
assert_eq!(
host.live_rows.borrow().as_slice(),
&[vec![5, 0], vec![7, 5]]
);
}
fn mixed_cfg() -> EngineConfig {
let mut ir = implication(Some(2));
let v = ir.automaton.new_local_var(32, false);
let mut c = cfg(ir, AssertKind::Assert, 3);
c.activated = true;
c.captured = vec![v];
c.disable_mixed = true;
c.leaf_reads[2] = vec![v];
c
}
#[test]
fn mixed_disable_tuples_are_pruned_once_their_attempts_completed() {
let host = tick_host(true);
let mut e = Engine::new(mixed_cfg(), vec![0, 0, 0]).unwrap();
let mut most = 0;
for n in 0..300u64 {
let now = 10 * (n + 1);
e.activate(now, vec![n as i64]);
let ev = e.fire(&host, now).unwrap();
assert!(ev.iter().all(|x| x.kind != EventKind::Fail));
most = most.max(e.live_tuples());
e.end_of_step(vec![0, 0, 0]);
}
assert_eq!(e.stats().attempted, 300);
assert!(most <= 2, "{most} tuples live at once");
}
#[test]
fn mixed_disable_tuple_activated_after_the_steps_fire_is_recorded_by_the_next_fire() {
let host = tick_host(true);
let mut e = Engine::new(mixed_cfg(), vec![0, 0, 0]).unwrap();
assert!(e.fire(&host, 10).unwrap().is_empty());
e.activate(10, vec![7]);
assert_eq!(e.live_tuples(), 1);
host.live.borrow_mut()[2] = true;
e.fire(&host, 10).unwrap();
e.end_of_step(vec![1, 0, 0]);
host.live.borrow_mut()[2] = false;
let ev = e.fire(&host, 20).unwrap();
assert!(ev.is_empty(), "the attempt was disabled at its start step");
assert_eq!(e.stats().failed, 0);
}