vj_sim/sva/property.rs
1//! SV-B (`docs/superpowers/plans/2026-10-03-sva.md` T19-T26): the property
2//! layer over T9-T18's sequence automata, and a reference evaluator for it --
3//! the oracle every property operator is tested against, as [`super::trace`] is
4//! for sequences. The runtime engine (T29) is a separate, incremental
5//! implementation of the same contract; this one evaluates the definitions of
6//! IEEE 1800-2023 Annex F.5 and F.7 as literally as it can, favouring being
7//! obviously right over being fast, and is never on a simulation path.
8//!
9//! A [`PropertyIr`] is a DAG of [`Prop`] nodes over ONE shared sequence
10//! [`Automaton`]: every sequence of the property (an antecedent, a `strong(...)`
11//! operand, a bare sequence property) is an entry state of that automaton and
12//! they share its local-variable slot table, so a frame carried out of an
13//! antecedent match is valid for the consequent's sequences. Like
14//! [`super::ir`] this module has no `sv-lang` dependency.
15//!
16//! # Semantics
17//!
18//! Satisfaction is over the infinite words of F.5.3: a finite trace of `len`
19//! samples completed by `⊤^ω` is the WEAK verdict (nothing has failed yet) and by
20//! `⊥^ω` the STRONG one (everything still owed has failed), see
21//! [`TailWord`]. [`Evaluator::verdict`] returns both. The relations are
22//! implemented as Annex F states them:
23//!
24//! - `strong(R)`: some NONEMPTY match of `R` from the start point.
25//! `weak(R)`: for every `j`, the first `j` letters followed by `⊤^ω` strongly
26//! satisfy `R` (no finite prefix witnesses the inability to match).
27//! - `not P` holds on `w` iff `P` does not hold on the dual word `w̄` (every
28//! `⊤` letter `⊥` and vice versa): `not` switches strength.
29//! - `P1 or P2`, `P1 and P2`: the Boolean combinations. `P1 implies P2` is
30//! `not P1 or P2` and `P1 iff P2` is `(P1 implies P2) and (P2 implies P1)`
31//! (F.3.4.3, applied by the builder).
32//! - `R |-> P`: for every match of `R` from the start, `P` holds from its end
33//! tick, with the frame the matching thread ended with. `R |=> P` is
34//! `R ##1 1 |-> P`.
35//! - `nexttime`, `always`, `eventually`, `until`: positions are `w^{i..}`;
36//! `s_nexttime p` is `not nexttime not p`, which on an infinite word is
37//! `nexttime p` again (nexttime is self-dual there), and likewise for the other
38//! `s_` operators of a bounded range -- the strong and weak forms differ only
39//! in the verdict they are judged at, so the evaluator keeps one rule for each
40//! pair and the builder keeps the strength for its legality checks.
41//! - `accept_on(b) P`: `P` holds and no letter satisfies `b`, or for some `i`,
42//! `w_i ⊨ b` and `w^{0,i-1}⊤^ω ⊨ P` (F.5.6.1). `reject_on(b) P` is
43//! `not accept_on(b) not P`.
44//! - Recursive properties (F.7): `w ⊨ π` iff `w ⊨ π[k]` for every `k`, where
45//! `π[0]` is `1'b1` and `π[k+1]` unfolds each recursive instance once. See
46//! [`Evaluator::recursion_limit`] for the bound `k` is run to.
47use std::cell::{Cell, RefCell};
48use std::collections::{BTreeSet, HashMap};
49use std::rc::Rc;
50
51use super::incremental::{Accel, automaton_span};
52use super::ir::{AbortMode, Automaton, ClockId, LeafId, LocalVarId, ThreadStateId, Transition};
53use super::trace::{CallEvent, Frame, Matches, TailWord, Timeline, TraceError, attempt_word_calls};
54
55/// Index of a [`Prop`] in [`PropertyIr::props`].
56#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
57pub struct PropId(pub u32);
58
59/// Index of a [`PropDecl`] in [`PropertyIr::decls`].
60#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub struct DeclId(pub u32);
62
63/// One sequence of the property: its entry state in the shared automaton and
64/// whether it admits the empty match (16.9.2.1), which is a flag of the
65/// sequence, not a path of the automaton.
66///
67/// `clock` is the clock the sequence STARTS on and `end_clock` the one it ends on
68/// (16.13: they differ for a multiclocked sequence, `a ##1 @(c1) b`). The
69/// builder stamps both from the clock flow; a single-clock property has
70/// `ClockId(0)` in both.
71#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct SeqRef {
73 pub entry: ThreadStateId,
74 pub admits_empty: bool,
75 pub clock: ClockId,
76 pub end_clock: ClockId,
77}
78
79impl SeqRef {
80 /// A single-clocked sequence on the assertion's clock (`ClockId(0)`).
81 pub fn single(entry: ThreadStateId, admits_empty: bool) -> Self {
82 SeqRef {
83 entry,
84 admits_empty,
85 clock: ClockId(0),
86 end_clock: ClockId(0),
87 }
88 }
89}
90
91/// The `strong`/`weak` operator written on a sequence property (16.12.2).
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
93pub enum Strength {
94 Strong,
95 Weak,
96}
97
98/// One arm of a `case` property: the labels that select it and its property.
99#[derive(Clone, Debug, PartialEq, Eq)]
100pub struct CaseArm {
101 pub labels: Vec<LeafId>,
102 pub body: PropId,
103}
104
105/// One property operator (16.12). Operands are [`PropId`]s of nodes built before
106/// this one, so the table is a DAG in topological order (a shared operand, as in
107/// the `iff` expansion, is one node referenced twice).
108#[derive(Clone, Debug, PartialEq, Eq)]
109pub enum Prop {
110 /// A sequence used as a property (16.12.2). `strength` is the explicit
111 /// `strong`/`weak` operator, or `None` for a bare sequence, which is weak
112 /// under `assert`/`assume` and strong under `cover` (the evaluator's
113 /// `implicit_strong`).
114 Seq {
115 seq: SeqRef,
116 strength: Option<Strength>,
117 },
118 /// `not p` (16.12.3).
119 Not(PropId),
120 /// `p1 and p2` (16.12.5).
121 And(PropId, PropId),
122 /// `p1 or p2` (16.12.4).
123 Or(PropId, PropId),
124 /// `if (cond) then [else els]` (16.12.6): `cond` is a Boolean leaf, and an
125 /// absent `else` is true.
126 If {
127 cond: LeafId,
128 then: PropId,
129 els: Option<PropId>,
130 },
131 /// `case (selector) labels: body ... [default: body] endcase` (16.12.16): the
132 /// first arm one of whose labels matches the selector is taken; no arm and no
133 /// `default` is vacuously true.
134 Case {
135 selector: LeafId,
136 arms: Vec<CaseArm>,
137 default: Option<PropId>,
138 },
139 /// `ante |-> cons` (`overlapped`) or `ante |=> cons` (16.12.7).
140 Implication {
141 ante: SeqRef,
142 cons: PropId,
143 overlapped: bool,
144 },
145 /// `nexttime [n] p` / `s_nexttime [n] p` (16.12.10).
146 Nexttime { p: PropId, n: u32, strong: bool },
147 /// `always [lo:hi] p` / `s_always [lo:hi] p` (16.12.11): `hi == None` is `$`.
148 Always {
149 p: PropId,
150 lo: u32,
151 hi: Option<u32>,
152 strong: bool,
153 },
154 /// `eventually [lo:hi] p` / `s_eventually [lo:hi] p` (16.12.13).
155 Eventually {
156 p: PropId,
157 lo: u32,
158 hi: Option<u32>,
159 strong: bool,
160 },
161 /// `p until q` / `p s_until q` (16.12.12); `until_with` is built as
162 /// `p until (p and q)`.
163 Until { p: PropId, q: PropId, strong: bool },
164 /// `accept_on`/`reject_on`/`sync_accept_on`/`sync_reject_on (cond) p`
165 /// (16.12.14).
166 Abort {
167 cond: LeafId,
168 p: PropId,
169 mode: AbortMode,
170 },
171 /// Writes local variables at the start of `body`'s evaluation, in order, each
172 /// value evaluated in the frame the earlier writes left (a property's local
173 /// formals and declared locals with initialisers, 16.12.19).
174 Let {
175 writes: Vec<(LocalVarId, LeafId)>,
176 body: PropId,
177 },
178 /// `@(clock) p` (16.13.2): `p` runs on `clock`, whatever clock the context
179 /// had. Every operand of `p` that counts time (`nexttime`, `always`, `until`,
180 /// a sequence's delays) counts ticks of `clock`.
181 Clocked { clock: ClockId, p: PropId },
182 /// An instance of a recursive property (16.12.17): evaluates `decl`'s body in
183 /// the caller's frame with the listed slots rebound to the arguments, every
184 /// one evaluated in the CALLER's frame first. Then `inits` (the instance's
185 /// declared locals with initialisers, in declaration order) are written: each
186 /// is evaluated in the caller's frame as the earlier `inits` left it, so that
187 /// it reads the formals as the instance's actuals (which is what slang
188 /// substitutes into an initialiser) and the declared locals it follows.
189 Call {
190 decl: DeclId,
191 args: Vec<(LocalVarId, LeafId)>,
192 inits: Vec<(LocalVarId, LeafId)>,
193 },
194}
195
196/// A recursive property's body, entered through [`Prop::Call`].
197#[derive(Clone, Debug, PartialEq, Eq)]
198pub struct PropDecl {
199 pub name: String,
200 pub body: PropId,
201}
202
203/// A built property: the shared sequence automaton, the operator DAG and the
204/// recursive declarations.
205#[derive(Clone, Debug, PartialEq, Eq)]
206pub struct PropertyIr {
207 pub automaton: Automaton,
208 pub props: Vec<Prop>,
209 pub decls: Vec<PropDecl>,
210 pub root: PropId,
211 /// The `disable iff (cond)` of the property spec (16.12, T27): `root` is the
212 /// property under it. `None` when the spec has no explicit clause. Only a
213 /// top-level clause exists -- the builder rejects a nested one -- and the
214 /// leaf is evaluated with CURRENT values (see [`Evaluator::attempt`]).
215 pub disable: Option<LeafId>,
216 /// Nodes whose 16.14.8 nonvacuity rule is not the one their shape says,
217 /// because the builder expressed an operator through others (see
218 /// [`NvRule`]). Empty for a hand-built property that has none.
219 pub nv_rules: Vec<(PropId, NvRule)>,
220}
221
222/// An operator built from others whose nonvacuity rule (16.14.8) differs from
223/// the derived shape's.
224#[derive(Clone, Copy, Debug, PartialEq, Eq)]
225pub enum NvRule {
226 /// The [`Prop::Or`] of `not p or q` standing for `p implies q` (rule z): `p`
227 /// must be true and nonvacuous, and `q` nonvacuous.
228 Implies,
229 /// The [`Prop::Until`] of `p until (p and q)` standing for `p until_with q`
230 /// (rules x, y): only `p` can make the attempt nonvacuous.
231 UntilWith,
232}
233
234impl PropertyIr {
235 /// The special nonvacuity rule of node `id`, if it has one.
236 pub fn nv_rule(&self, id: PropId) -> Option<NvRule> {
237 self.nv_rules
238 .iter()
239 .find(|(p, _)| *p == id)
240 .map(|(_, r)| *r)
241 }
242
243 /// Borrows `id`'s node (an id this IR's builder minted).
244 pub fn prop(&self, id: PropId) -> &Prop {
245 &self.props[id.0 as usize]
246 }
247
248 /// Whether the property contains a recursive instance ([`Prop::Call`]).
249 pub fn is_recursive(&self) -> bool {
250 self.props.iter().any(|p| matches!(p, Prop::Call { .. }))
251 }
252}
253
254/// What a property's leaves are on a trace.
255pub trait PropEnv {
256 /// The value of `leaf` at `tick` with the thread's local-variable `frame`; a
257 /// guard holds when nonzero. Called for real samples only.
258 fn value(&self, leaf: LeafId, tick: u32, frame: &[Option<i64>]) -> Result<i64, TraceError>;
259
260 /// Whether `label` selects the `case` selector at `tick`. The environment
261 /// owns the comparison: 16.12.16 uses the case equality of 12.5 (`===`), under
262 /// which an `x`/`z` selector matches no label that is not itself that value.
263 fn case_match(
264 &self,
265 selector: LeafId,
266 label: LeafId,
267 tick: u32,
268 frame: &[Option<i64>],
269 ) -> Result<bool, TraceError>;
270
271 /// The sampled value of the abort condition `leaf` at a time step STRICTLY
272 /// BETWEEN tick `after` and tick `after + 1` (16.12.14: `accept_on` and
273 /// `reject_on` are "evaluated at the granularity of the simulation time step",
274 /// unlike the `sync_` forms). A trace that has no such steps -- the default --
275 /// has none true.
276 fn between(&self, _leaf: LeafId, _after: u32) -> Result<bool, TraceError> {
277 Ok(false)
278 }
279
280 /// The tick lists of a multiclock property's clocks over the trace's
281 /// positions (16.13), or `None` -- the default -- when every position is a
282 /// tick of every clock, which is what a single-clocked property wants. With
283 /// a timeline the positions are the trace's global steps and `value` is read
284 /// at them; `between` is not consulted.
285 fn timeline(&self) -> Option<&Timeline> {
286 None
287 }
288
289 /// Local variables an attempt starts with already assigned: the values a
290 /// procedural concurrent assertion captured when it was reached (16.14.6).
291 fn initial_frame(&self) -> Vec<(LocalVarId, i64)> {
292 Vec::new()
293 }
294}
295
296/// The result of checking a property over a finite trace (F.5.3.2).
297#[derive(Clone, Copy, Debug, PartialEq, Eq)]
298pub enum Verdict {
299 /// `w ⊭- A`: the weak verdict fails -- the property has definitely failed.
300 Fails,
301 /// `w ⊨- A` but `w ⊭+ A`: nothing has failed but something is still owed
302 /// ("holds, but does not hold strongly", the evaluation is pending).
303 Holds,
304 /// `w ⊨+ A`: holds strongly.
305 HoldsStrongly,
306}
307
308/// What an evaluation attempt of a property spec came to (16.12 `disable iff`).
309#[derive(Clone, Copy, Debug, PartialEq, Eq)]
310pub enum Disposition {
311 /// The property failed (before any disabling).
312 Fails,
313 /// Nothing has failed and nothing is decided: the attempt is pending.
314 Pending,
315 /// The property holds strongly: it passed.
316 Passes,
317 /// The disable condition was true at some time step of the attempt while the
318 /// property was still undecided: "a disabled evaluation of a property does not
319 /// result in success or failure" (16.12).
320 Disabled,
321}
322
323impl From<Verdict> for Disposition {
324 fn from(v: Verdict) -> Self {
325 match v {
326 Verdict::Fails => Disposition::Fails,
327 Verdict::Holds => Disposition::Pending,
328 Verdict::HoldsStrongly => Disposition::Passes,
329 }
330 }
331}
332
333/// The evaluator: a property, the trace it is checked on and which strength a bare
334/// sequence takes.
335pub struct Evaluator<'a> {
336 ir: &'a PropertyIr,
337 env: &'a dyn PropEnv,
338 implicit_strong: bool,
339 /// Whether the trace is a prefix of a run that goes on (the live engine), see
340 /// [`Evaluator::open_future`].
341 open_future: bool,
342 /// An upper bound on how many ticks any simple path of the sequence automaton
343 /// can span, see [`Evaluator::horizon`].
344 span: u32,
345 /// The result of every [`Prop::Call`] evaluated so far, by what it depends on:
346 /// the declaration, the start tick, the word, the local-variable frame the
347 /// body starts with and the call depth (which decides where the unfolding is
348 /// cut off). A recursive property reaches the same configuration along many
349 /// paths (every match of an antecedent such as `x[->1]`, every abort point of
350 /// an `accept_on` re-runs the property after it), so without this the number
351 /// of evaluations grows exponentially with the depth of the unfolding.
352 calls: RefCell<HashMap<CallKey, bool>>,
353 /// The environment's [`Timeline`] cut to a word's real length, by that length
354 /// (see [`Self::timeline_for`]).
355 timelines: RefCell<HashMap<u32, Rc<Timeline>>>,
356 /// Set while [`Self::calls`] collects: every sequence search then unions the
357 /// subroutine calls its threads scheduled (16.11) into [`Self::call_sink`],
358 /// and the operators that would stop at the first failing operand
359 /// (`and`, `or`, `|->`) evaluate all of them, so the calls are those of every
360 /// thread the property starts, not of the ones a verdict happened to need.
361 exhaustive: Cell<bool>,
362 /// Set while [`Evaluator::verdict`] decides "holds strongly": a weak sequence
363 /// on the strong word then holds only once it has matched, see `weak_seq`.
364 pending_until_match: Cell<bool>,
365 call_sink: RefCell<Option<BTreeSet<CallEvent>>>,
366 /// The live engine's incremental sequence evaluator, see
367 /// [`Self::with_accel`]; `None` is the reference semantics, spelled out.
368 accel: Option<&'a RefCell<Accel>>,
369}
370
371/// What the settled ticks of an `until` say, see [`Evaluator::skip_settled_until`].
372enum Settled {
373 Verdict(bool),
374 Resume(u32),
375}
376
377/// What a [`Prop::Call`]'s result depends on, see the `calls` field of
378/// [`Evaluator`].
379type CallKey = (DeclId, u32, TailWord, Frame, u32, ClockId);
380
381impl<'a> Evaluator<'a> {
382 /// `implicit_strong` is whether a bare sequence property is strong (a `cover
383 /// property` directive) or weak (`assert`/`assume`), 16.12.2.
384 pub fn new(ir: &'a PropertyIr, env: &'a dyn PropEnv, implicit_strong: bool) -> Self {
385 Self::new_with_span(ir, env, implicit_strong, automaton_span(&ir.automaton))
386 }
387
388 /// [`Self::new`] with the automaton's [`automaton_span`] already known (an
389 /// engine builds one evaluator per evaluation).
390 pub fn new_with_span(
391 ir: &'a PropertyIr,
392 env: &'a dyn PropEnv,
393 implicit_strong: bool,
394 span: u32,
395 ) -> Self {
396 Evaluator {
397 ir,
398 env,
399 implicit_strong,
400 open_future: false,
401 span,
402 calls: RefCell::new(HashMap::new()),
403 timelines: RefCell::new(HashMap::new()),
404 exhaustive: Cell::new(false),
405 pending_until_match: Cell::new(false),
406 call_sink: RefCell::new(None),
407 accel: None,
408 }
409 }
410
411 /// Answers the sequence searches of words with no explicit letters (`strong`
412 /// and `weak` verdict words, which is what a live engine asks) from `accel`'s
413 /// incremental state instead of replaying them. The accelerator declines
414 /// what it does not model, and then the reference code below runs; the
415 /// results are the reference's, which the engine's differential mode and
416 /// this module's exhaustive test check.
417 pub fn with_accel(mut self, accel: Option<&'a RefCell<Accel>>) -> Self {
418 self.accel = accel;
419 self
420 }
421
422 /// Reads the trace as a PREFIX of a run that is not over, which is what a live
423 /// simulation has: the weak and strong verdicts then bracket every possible
424 /// continuation, so `Fails` and `HoldsStrongly` are final and `Holds` means
425 /// "undecided". Annex F's strong word `w⊥^ω` is the end of the run, where an
426 /// antecedent that still needs a letter has no match and the implication is
427 /// vacuously true (`a |=> b` holds strongly when `a` is the last letter). That is
428 /// the right verdict for a run that ended and the wrong one for a decision
429 /// taken in the middle of a run, so here an antecedent is matched on the DUAL
430 /// word (`⊤` letters in the strong word, `⊥` in the weak one): it matches as
431 /// eagerly as any continuation could make it in the strong word, as rarely in
432 /// the weak one, and the consequent is judged on the word itself.
433 pub fn open_future(mut self) -> Self {
434 self.open_future = true;
435 self
436 }
437
438 /// The clock the property starts on: the root context clock.
439 fn root_clock(&self) -> ClockId {
440 self.ir.automaton.entry_clock
441 }
442
443 /// The environment's timeline as `word` sees it: a position at or past
444 /// `word.real` is a `⊤`/`⊥` letter, which is a tick of EVERY clock (see
445 /// [`Timeline`]), so a word cut shorter than the trace needs the tick lists cut
446 /// with it. `None` when the property is single-clocked.
447 fn timeline_for(&self, word: &TailWord) -> Result<Option<Rc<Timeline>>, TraceError> {
448 let Some(full) = self.env.timeline() else {
449 return Ok(None);
450 };
451 if let Some(t) = self.timelines.borrow().get(&word.real) {
452 return Ok(Some(Rc::clone(t)));
453 }
454 let cut = Rc::new(full.truncated(word.real));
455 self.timelines
456 .borrow_mut()
457 .insert(word.real, Rc::clone(&cut));
458 Ok(Some(cut))
459 }
460
461 /// The `k`-th tick of `dom` strictly after `from` on `word` (`k == 0`: `from`).
462 fn after(&self, word: &TailWord, dom: ClockId, from: u32, k: u32) -> Result<u32, TraceError> {
463 Ok(match self.timeline_for(word)? {
464 Some(t) => t.kth_after(dom, from, k),
465 None => from + k,
466 })
467 }
468
469 /// The clock the property `id` starts on when that is evident from its own
470 /// operators (a clocked sequence, `@(c) p`, `not`, an `and`/`or` whose
471 /// operands agree); `fallback` otherwise.
472 fn lead_clock(&self, id: PropId, fallback: ClockId) -> ClockId {
473 match self.ir.prop(id) {
474 Prop::Seq { seq, .. } => seq.clock,
475 Prop::Clocked { clock, .. } => *clock,
476 Prop::Not(p) => self.lead_clock(*p, fallback),
477 Prop::Let { body, .. } => self.lead_clock(*body, fallback),
478 Prop::And(a, b) | Prop::Or(a, b) => {
479 let (x, y) = (self.lead_clock(*a, fallback), self.lead_clock(*b, fallback));
480 if x == y { x } else { fallback }
481 }
482 _ => fallback,
483 }
484 }
485
486 /// `pos` moved to the first tick of `dom` at or after it, folded into
487 /// `extent` (every position from there on is the same letter).
488 fn align(&self, word: &TailWord, dom: ClockId, pos: u32) -> Result<u32, TraceError> {
489 let at = match self.timeline_for(word)? {
490 Some(t) => t.next_tick(dom, pos),
491 None => pos,
492 };
493 Ok(at.min(word.extent()))
494 }
495
496 /// The verdict of an evaluation attempt started at tick `start` of a trace of
497 /// `len` samples.
498 pub fn verdict(&self, len: u32, start: u32) -> Result<Verdict, TraceError> {
499 let weak = self.holds(&TailWord::weak(len), start)?;
500 if !weak {
501 return Ok(Verdict::Fails);
502 }
503 self.pending_until_match.set(true);
504 let strong = self.holds(&TailWord::strong(len), start);
505 self.pending_until_match.set(false);
506 Ok(if strong? {
507 Verdict::HoldsStrongly
508 } else {
509 Verdict::Holds
510 })
511 }
512
513 /// [`Self::verdict`] for the whole property spec, `disable iff` included
514 /// (16.12): the attempt started at tick `start` of a trace of `len` samples.
515 ///
516 /// The disable condition is "true at anytime between the start of the attempt
517 /// in the Observed region, inclusive, and the end of the evaluation attempt,
518 /// inclusive". [`PropEnv::value`] of the disable leaf at tick `t` says whether
519 /// it was true in the Observed region of tick `t`, and [`PropEnv::between`]
520 /// whether it was true at some time step strictly between `t` and `t + 1`; both
521 /// are CURRENT values (16.12: "not sampled"). Let `p` be the first such moment
522 /// at or after the start; the attempt is judged on the letters before it -- the
523 /// ones that came before the disabling, `w^{0,i-1}` -- as the Annex F neutral
524 /// satisfaction of a top-level `disable iff` has it: failed if the weak completion of that prefix fails
525 /// (the failure was decided before the disabling), passed if its strong
526 /// completion holds, and DISABLED when it is neither (still pending).
527 ///
528 /// A condition true when the attempt starts disables it outright: Annex F
529 /// excludes letter 0 from `0 < i`, but 16.12's prose is explicit that the window
530 /// starts at the attempt's start, inclusive, and that is the clause that decides
531 /// what an evaluation attempt is. A decided verdict is the same on every longer
532 /// prefix (it does not depend on the continuation), so a disabling AFTER the
533 /// property was decided changes nothing.
534 pub fn attempt(&self, len: u32, start: u32) -> Result<Disposition, TraceError> {
535 let Some(prefix) = self.disabling_prefix(len, start)? else {
536 return Ok(self.verdict(len, start)?.into());
537 };
538 if prefix == start {
539 return Ok(Disposition::Disabled);
540 }
541 Ok(match self.verdict(prefix, start)? {
542 Verdict::Holds => Disposition::Disabled,
543 decided => decided.into(),
544 })
545 }
546
547 /// The number of letters (counted from tick 0) that precede the first moment at
548 /// or after `start` the disable condition is true, see [`Self::attempt`]; `None`
549 /// when there is no `disable iff` or it is never true on the `len` ticks.
550 pub fn disabling_prefix(&self, len: u32, start: u32) -> Result<Option<u32>, TraceError> {
551 let Some(cond) = self.ir.disable else {
552 return Ok(None);
553 };
554 // The attempt's own captured values (16.14.6.1) are what a condition over
555 // them reads; any other condition ignores the frame.
556 let frame = self.initial_frame();
557 for t in start..len {
558 if self.env.value(cond, t, &frame)? != 0 {
559 return Ok(Some(t));
560 }
561 if self.env.between(cond, t)? {
562 return Ok(Some(t + 1));
563 }
564 }
565 Ok(None)
566 }
567
568 /// The local-variable frame an attempt starts with: all unassigned, except
569 /// the slots the environment pre-assigns ([`PropEnv::initial_frame`]).
570 fn initial_frame(&self) -> Frame {
571 let mut frame = vec![None; self.ir.automaton.local_vars.len()];
572 for (var, value) in self.env.initial_frame() {
573 frame[var.0 as usize] = Some(value);
574 }
575 frame
576 }
577
578 /// Whether the property holds on `word` from tick `start` (an evaluation
579 /// attempt's start point), local variables unassigned save those the
580 /// environment pre-assigns.
581 pub fn holds(&self, word: &TailWord, start: u32) -> Result<bool, TraceError> {
582 // What the environment says may differ from one call to the next.
583 self.calls.borrow_mut().clear();
584 let frame = self.initial_frame();
585 self.sat(
586 self.ir.root,
587 word,
588 start,
589 &frame,
590 0,
591 self.root_clock(),
592 false,
593 )
594 }
595
596 /// Every nonempty match of the root sequence of a `cover sequence` (16.14.3)
597 /// attempt started at tick `start`, over the `len` real samples of the trace,
598 /// as `(end tick, the local-variable frame it matched with)`. Several matches
599 /// may end on one tick (with different frames, or the same frame reached along
600 /// different paths is one match): 16.14.3 counts every one. A match that would
601 /// need a tick past the trace is not listed -- it is reported when that tick
602 /// arrives. `NotASequence` for a root that is not a bare sequence.
603 pub fn root_sequence_matches(&self, len: u32, start: u32) -> Result<Matches, TraceError> {
604 let Prop::Seq { seq, .. } = self.ir.prop(self.ir.root) else {
605 return Err(TraceError::NotASequence);
606 };
607 self.calls.borrow_mut().clear();
608 let frame = self.initial_frame();
609 self.matches(*seq, &TailWord::strong(len), start, &frame)
610 }
611
612 /// How deep a [`Prop::Call`] chain is followed (`k` of F.7's `π[k]`); an
613 /// instance past it is `π[0]`, `1'b1`.
614 ///
615 /// Every recursive instance occurs after a positive time advance (16.12.17
616 /// restriction 3), so a chain of calls advances the start tick by at least one
617 /// each step, and past the trace (`extent`) every start tick is equivalent.
618 /// What can still differ between two calls is which recursive declaration
619 /// they enter and the local-variable arguments they pass. For a property with
620 /// `c` recursive declarations and no argument that grows, the configuration
621 /// `(declaration, tick clamped to extent)` repeats within `c * (extent + 1)`
622 /// calls, after which the unfolding is periodic and `π[k]` no longer changes
623 /// with `k` -- the limit below doubles that and adds slack. A recursion whose
624 /// local arguments grow forever (a counter) has no such fixpoint; for it the
625 /// verdict is that of this `k`, and the tests pin that `k` and `2k` agree on
626 /// every property of the LRM's recursive examples.
627 pub fn recursion_limit(&self, word: &TailWord) -> u32 {
628 2 * (self.ir.decls.len() as u32).max(1) * (word.extent() + 2) + 4
629 }
630
631 /// The tick the sequence search runs to for a start tick `pos` on `word`: past
632 /// the last tick that is not the constant tail, plus the longest a simple
633 /// path of the automaton can span, so every distinct way a match can end has
634 /// been tried.
635 fn horizon(&self, word: &TailWord, pos: u32) -> u32 {
636 word.extent().max(pos) + self.span + 1
637 }
638
639 /// Every nonempty match of `seq` started at `pos` with `frame`.
640 fn matches(
641 &self,
642 seq: SeqRef,
643 word: &TailWord,
644 pos: u32,
645 frame: &Frame,
646 ) -> Result<Matches, TraceError> {
647 if let Some(cell) = self.accel
648 && word.mid.is_empty()
649 && let Ok(mut accel) = cell.try_borrow_mut()
650 && let Some(found) = accel.matches(
651 &self.ir.automaton,
652 self.env,
653 seq.entry,
654 word.real,
655 word.tail_top,
656 pos,
657 frame,
658 )
659 {
660 return Ok(found);
661 }
662 let eval = |leaf: LeafId, tick: u32, f: &[Option<i64>]| self.env.value(leaf, tick, f);
663 let timeline = self.timeline_for(word)?;
664 let (found, calls) = attempt_word_calls(
665 &self.ir.automaton,
666 seq.entry,
667 frame,
668 pos,
669 word,
670 self.horizon(word, pos),
671 &eval,
672 timeline.as_deref(),
673 seq.clock,
674 )?;
675 if let Some(sink) = self.call_sink.borrow_mut().as_mut() {
676 sink.extend(calls);
677 }
678 Ok(found.into_iter().collect())
679 }
680
681 /// Every subroutine call (16.11) a thread of the evaluation attempt started at
682 /// tick `start` scheduled on the `len` samples of the trace, ascending by
683 /// `(tick, call, args)`: "all subroutine calls attached to a sequence are
684 /// executed at every end point of the sequence", whether or not the property
685 /// those sequences are part of later holds.
686 ///
687 /// The attempt is judged on the letters before its `disable iff` (16.12): a
688 /// call after the disabling is not the attempt's. The set depends only on
689 /// the samples up to each call's own tick (a thread reaches a call edge by
690 /// the letters before it), so an extension of the trace only ever ADDS
691 /// calls -- which is what lets the engine report each once.
692 ///
693 /// Both completions of the word are run: the verdict calls the second only
694 /// when the first holds, and the calls must not depend on the verdict.
695 ///
696 /// The calls are those the sequence searches find: they are exhaustive over
697 /// `and`/`or`/`|->` operands (see [`Self::exhaustive`]), and a property that
698 /// stops evaluating at a decision (`always`, `until`, ...) lists the calls of
699 /// the evaluations that ran.
700 pub fn scheduled_calls(&self, len: u32, start: u32) -> Result<Vec<CallEvent>, TraceError> {
701 let eff = self.disabling_prefix(len, start)?.unwrap_or(len);
702 *self.call_sink.borrow_mut() = Some(BTreeSet::new());
703 self.exhaustive.set(true);
704 let ran = self
705 .holds(&TailWord::weak(eff), start)
706 .and_then(|_| self.holds(&TailWord::strong(eff), start));
707 self.exhaustive.set(false);
708 let sink = self.call_sink.borrow_mut().take().unwrap_or_default();
709 ran?;
710 Ok(sink.into_iter().collect())
711 }
712
713 /// Whether a multiclock `cover sequence` attempt (16.13.1, 16.14.3) started at
714 /// position `start` of a trace of `len` positions has reported every match it
715 /// ever will: no thread of the root sequence can still be waiting for a
716 /// position past the trace.
717 ///
718 /// A single-clock attempt is retired by [`Automaton::max_span`], a count of
719 /// clock ticks. With several clocks the positions are the union of the clocks'
720 /// ticks, and how many positions a `##N` or a clock change takes depends on the
721 /// clock it waits for, so this follows the automaton on the timeline instead:
722 /// the latest position each state is reachable at, taking every `##[m:n]` to its
723 /// `n`-th tick and every clock change to its next tick of the new clock, guards
724 /// ignored (so it only ever over-estimates). A step that needs a tick not in the
725 /// trace yet, or an unbounded delay, leaves the attempt alive. `false` without
726 /// a timeline (a single clock: use `max_span`).
727 pub fn sequence_exhausted(&self, len: u32, start: u32) -> Result<bool, TraceError> {
728 let Prop::Seq { seq, .. } = self.ir.prop(self.ir.root) else {
729 return Err(TraceError::NotASequence);
730 };
731 let Some(tl) = self.env.timeline() else {
732 return Ok(false);
733 };
734 let au = &self.ir.automaton;
735 let first = tl.next_tick(seq.clock, start);
736 let mut best: HashMap<(u32, ClockId), u32> = HashMap::new();
737 let mut work = vec![((seq.entry.0, seq.clock), first)];
738 while let Some(((state, dom), pos)) = work.pop() {
739 if pos >= len {
740 return Ok(false);
741 }
742 if best.get(&(state, dom)).is_some_and(|&b| b >= pos) {
743 continue;
744 }
745 best.insert((state, dom), pos);
746 for edge in &au.state(ThreadStateId(state)).edges {
747 let target = edge.target.0;
748 match &edge.label {
749 Transition::Delay { min, max } => {
750 let Some(max) = max else {
751 return Ok(false);
752 };
753 work.push(((target, dom), tl.kth_after(dom, pos, (*max).max(*min))));
754 }
755 Transition::ClockSync { to, strict } => {
756 let at = if *strict {
757 tl.kth_after(*to, pos, 1)
758 } else {
759 tl.next_tick(*to, pos)
760 };
761 work.push(((target, *to), at));
762 }
763 Transition::Match { .. } => {}
764 Transition::Epsilon
765 | Transition::Abort { .. }
766 | Transition::LocalVarWrite { .. }
767 | Transition::LocalVarCopy { .. }
768 | Transition::FirstMatchBegin { .. }
769 | Transition::FirstMatchEnd { .. }
770 | Transition::LocalVarStep { .. }
771 | Transition::SubroutineCall { .. }
772 | Transition::EndPoint { .. } => work.push(((target, dom), pos)),
773 }
774 }
775 }
776 Ok(true)
777 }
778
779 /// The calls (16.11) of the threads of `cover sequence`'s root sequence
780 /// started at `start` (see [`Self::root_sequence_matches`]), over the `len`
781 /// real samples, before the attempt's `disable iff` (16.12).
782 pub fn sequence_calls(&self, len: u32, start: u32) -> Result<Vec<CallEvent>, TraceError> {
783 let eff = self.disabling_prefix(len, start)?.unwrap_or(len);
784 *self.call_sink.borrow_mut() = Some(BTreeSet::new());
785 let ran = self.root_sequence_matches(eff, start);
786 let sink = self.call_sink.borrow_mut().take().unwrap_or_default();
787 ran?;
788 Ok(sink.into_iter().collect())
789 }
790
791 /// A Boolean leaf at `tick`: a real sample's value, or the letter.
792 fn boolean(
793 &self,
794 leaf: LeafId,
795 word: &TailWord,
796 tick: u32,
797 frame: &Frame,
798 ) -> Result<bool, TraceError> {
799 match word.letter(tick) {
800 Some(top) => Ok(top),
801 None => Ok(self.env.value(leaf, tick, frame)? != 0),
802 }
803 }
804
805 /// `strong(seq)` from `pos`.
806 fn strong_seq(
807 &self,
808 seq: SeqRef,
809 word: &TailWord,
810 pos: u32,
811 frame: &Frame,
812 ) -> Result<bool, TraceError> {
813 if let Some(cell) = self.accel
814 && word.mid.is_empty()
815 && let Ok(mut accel) = cell.try_borrow_mut()
816 && let Some(found) = accel.strong(
817 &self.ir.automaton,
818 self.env,
819 seq.entry,
820 word.real,
821 word.tail_top,
822 pos,
823 frame,
824 )
825 {
826 return Ok(found);
827 }
828 Ok(!self.matches(seq, word, pos, frame)?.is_empty())
829 }
830
831 /// `weak(seq)` from `pos`: for every number of letters `n` past the start
832 /// point, `w^{pos..n-1}⊤^ω` strongly satisfies `seq` (F.5.3.1).
833 ///
834 /// On a `⊤` tail a cut past the real and explicit letters is the word itself,
835 /// so the cuts to try end there; on a `⊥` tail each longer cut adds a `⊥`
836 /// letter before the `⊤`s, and a cut longer than the longest path a match can
837 /// span from `pos` behaves like that one.
838 fn weak_seq(
839 &self,
840 seq: SeqRef,
841 word: &TailWord,
842 pos: u32,
843 frame: &Frame,
844 ) -> Result<bool, TraceError> {
845 // When deciding "holds strongly" (a PASS: `Evaluator::verdict`), a weak
846 // sequence on the strong word (`⊥` tail, no explicit letters) holds exactly
847 // when it has matched. Annex F's cuts are lenient
848 // on an unbounded range: `a ##[1:$] b` over `[a]⊥^ω` is matched by every
849 // cut `[a]⊥^k⊤^ω` (the `⊤` satisfies `b`), so it would hold strongly --
850 // the attempt would PASS at its first tick, before `b` is ever true. The
851 // LRM's own example (Table 40-3: `aundf: assert (on ##[1:$]
852 // off)` "will not pass or fail") says such an attempt stays pending until
853 // the sequence matches; a bounded sequence cannot tell the two apart (a
854 // match needs real letters, the `⊥`s kill every Boolean), so this only
855 // changes unbounded ranges, repetitions and goto/nonconsecutive forms.
856 if self.pending_until_match.get() && !word.tail_top && word.mid.is_empty() {
857 return self.strong_seq(seq, word, pos, frame);
858 }
859 if let Some(cell) = self.accel
860 && word.mid.is_empty()
861 && let Ok(mut accel) = cell.try_borrow_mut()
862 && let Some(found) = accel.weak(
863 &self.ir.automaton,
864 self.env,
865 seq.entry,
866 word.real,
867 word.tail_top,
868 pos,
869 frame,
870 )
871 {
872 return Ok(found);
873 }
874 let ext = word.extent();
875 let last = if word.tail_top {
876 ext.max(pos + 1)
877 } else {
878 ext.max(pos) + self.span + 1
879 };
880 for n in pos + 1..=last {
881 let cut = word.cut_then_top(n);
882 if !self.strong_seq(seq, &cut, pos, frame)? {
883 return Ok(false);
884 }
885 }
886 Ok(true)
887 }
888
889 /// The position `prop` stands on when asked at `pos`, and the pending
890 /// synchronization it hands on (see [`Self::sat`]'s `strict`).
891 ///
892 /// The pass-through operators hand the position on untouched; every other one
893 /// stands on a tick of the clock it starts on. A position past `extent` is the
894 /// one at it: every later tick carries the same letter.
895 fn stand(
896 &self,
897 prop: &Prop,
898 word: &TailWord,
899 pos: u32,
900 dom: ClockId,
901 strict: bool,
902 ) -> Result<(u32, bool), TraceError> {
903 let through = pass_through(prop);
904 let pos = if through {
905 pos.min(word.extent())
906 } else {
907 let lead = match prop {
908 Prop::Seq { seq, .. } => seq.clock,
909 Prop::Implication { ante, .. } => ante.clock,
910 // 16.13.7: a declaration assignment is performed at the earliest
911 // tick of the body's leading clock, not of the context clock.
912 Prop::Let { body, .. } => self.lead_clock(*body, dom),
913 _ => dom,
914 };
915 if strict {
916 self.after(word, lead, pos, 1)?.min(word.extent())
917 } else {
918 self.align(word, lead, pos)?
919 }
920 };
921 // Only a pass-through operator hands the pending synchronization on.
922 Ok((pos, strict && through))
923 }
924
925 /// The local-variable frame a [`Prop::Call`] body starts with: the caller's
926 /// frame with the formals' slots rebound (every actual is evaluated in the
927 /// caller's frame, before any slot is overwritten -- an actual may read the
928 /// slot its own formal is about to take, `fibonacci1(b, a+b, ...)`).
929 fn call_frame(
930 &self,
931 args: &[(LocalVarId, LeafId)],
932 inits: &[(LocalVarId, LeafId)],
933 word: &TailWord,
934 pos: u32,
935 frame: &Frame,
936 ) -> Result<Frame, TraceError> {
937 let mut f: Frame = frame.clone();
938 for (var, leaf) in args {
939 let raw = if word.letter(pos).is_some() {
940 0
941 } else {
942 self.env.value(*leaf, pos, frame)?
943 };
944 f[var.0 as usize] = Some(self.ir.automaton.local_var(*var).cast(raw));
945 }
946 let mut seen: Frame = frame.clone();
947 for (var, leaf) in inits {
948 let raw = if word.letter(pos).is_some() {
949 0
950 } else {
951 self.env.value(*leaf, pos, &seen)?
952 };
953 let v = Some(self.ir.automaton.local_var(*var).cast(raw));
954 f[var.0 as usize] = v;
955 seen[var.0 as usize] = v;
956 }
957 Ok(f)
958 }
959
960 /// `sat(prop, w, pos, frame)`: does `w^{pos..}` satisfy `prop` with the
961 /// local-variable `frame` as input context. `dom` is the clock the context is
962 /// running on (16.13.2): an evaluation point is a tick of it, so `pos` is moved
963 /// to the first tick of `dom` at or after it, and every operator that counts
964 /// time counts ticks of `dom`. A single-clocked property has every position a
965 /// tick and this is the identity.
966 ///
967 /// `strict` is the pending synchronization of a nonoverlapped implication's
968 /// consequent (16.13.2): "time is advanced from the end point of the match to
969 /// the nearest STRICTLY future occurrence" of the clock the consequent starts
970 /// on. It is carried down to wherever that clock is known -- through `not`,
971 /// `and`, `or` and `@(c)`, which add no time of their own, so that each branch
972 /// of `s0 |=> (@(c1) s1) and (@(c2) s2)` synchronizes to its own clock -- and
973 /// consumed by the first operator that stands on a position.
974 #[allow(clippy::too_many_arguments)]
975 fn sat(
976 &self,
977 id: PropId,
978 word: &TailWord,
979 pos: u32,
980 frame: &Frame,
981 depth: u32,
982 dom: ClockId,
983 strict: bool,
984 ) -> Result<bool, TraceError> {
985 let prop = self.ir.prop(id);
986 let (pos, strict) = self.stand(prop, word, pos, dom, strict)?;
987 match prop {
988 Prop::Seq { seq, strength } => {
989 let strong = match strength {
990 Some(Strength::Strong) => true,
991 Some(Strength::Weak) => false,
992 None => self.implicit_strong,
993 };
994 if strong {
995 self.strong_seq(*seq, word, pos, frame)
996 } else {
997 self.weak_seq(*seq, word, pos, frame)
998 }
999 }
1000 Prop::Not(p) => Ok(!self.sat(*p, &word.flip(), pos, frame, depth, dom, strict)?),
1001 Prop::And(a, b) => {
1002 let left = self.sat(*a, word, pos, frame, depth, dom, strict)?;
1003 if !left && !self.exhaustive.get() {
1004 return Ok(false);
1005 }
1006 let right = self.sat(*b, word, pos, frame, depth, dom, strict)?;
1007 Ok(left && right)
1008 }
1009 Prop::Or(a, b) => {
1010 let left = self.sat(*a, word, pos, frame, depth, dom, strict)?;
1011 if left && !self.exhaustive.get() {
1012 return Ok(true);
1013 }
1014 let right = self.sat(*b, word, pos, frame, depth, dom, strict)?;
1015 Ok(left || right)
1016 }
1017 Prop::If { cond, then, els } => {
1018 if self.boolean(*cond, word, pos, frame)? {
1019 self.sat(*then, word, pos, frame, depth, dom, false)
1020 } else if let Some(e) = els {
1021 self.sat(*e, word, pos, frame, depth, dom, false)
1022 } else {
1023 Ok(true)
1024 }
1025 }
1026 Prop::Case {
1027 selector,
1028 arms,
1029 default,
1030 } => {
1031 for arm in arms {
1032 if self.arm_selected(*selector, arm, word, pos, frame)? {
1033 return self.sat(arm.body, word, pos, frame, depth, dom, false);
1034 }
1035 }
1036 match default {
1037 Some(d) => self.sat(*d, word, pos, frame, depth, dom, false),
1038 None => Ok(true),
1039 }
1040 }
1041 Prop::Implication {
1042 ante,
1043 cons,
1044 overlapped,
1045 } => {
1046 // A consequent with a clock of its own is a `Clocked` node (or a
1047 // sequence stamped with one) and moves itself.
1048 //
1049 // `|->` evaluates the consequent at the end point, and the
1050 // consequent's own clock "awaits its nearest tick", at or after
1051 // it. `|=>` advances from the end point to the nearest STRICTLY
1052 // future tick of the clock the consequent starts on (16.13.2, the
1053 // `strict` flag of `sat`); with one clock that is the next tick,
1054 // `ante ##1 1` of F.3.4. Annex F's rewrite, read literally for a
1055 // multiclocked `|=>`, advances one tick of the ANTECEDENT's clock
1056 // and only then awaits the consequent clock: the two differ
1057 // exactly when a tick of the consequent's clock falls between the
1058 // end point and the next tick of the antecedent's. The prose of
1059 // 16.13.2, being specific to this construct, is what is
1060 // implemented.
1061 // A consequent that carries no clock of its own runs on the clock
1062 // of the context the implication stands in (`dom`), not on the one
1063 // the antecedent ended on: Annex F.5.1 rewrites `r |-> p` under `c`
1064 // to `T_s(r, c) |-> T_p(p, c)`, and 16.13.3's `@(c) w ##1 (x ##1
1065 // @(d) y) |=> z` clocks `z` at `c`.
1066 let on = ante.end_clock;
1067 let dual;
1068 let aw = if self.open_future {
1069 dual = word.flip();
1070 &dual
1071 } else {
1072 word
1073 };
1074 // A failing consequent decides the implication; collecting calls
1075 // goes on, as the consequent's threads (16.11) are evaluated too.
1076 let mut holds = true;
1077 for (end, f) in self.matches(*ante, aw, pos, frame)? {
1078 if !*overlapped {
1079 // The `1` of `ante ##1 1` is a Boolean on the next tick of
1080 // the antecedent's clock, which no `⊥` letter satisfies
1081 // (F.5.3). A consequent that starts on another clock is
1082 // synchronized to that clock's next tick instead (16.13.2),
1083 // so it is that tick that must exist: the verdict of a
1084 // failing consequent is then reported at ITS tick, not at
1085 // the antecedent clock's next one.
1086 let next = self.after(aw, self.lead_clock(*cons, on), end, 1)?;
1087 if aw.letter(next) == Some(false) {
1088 continue;
1089 }
1090 }
1091 if !self.sat(*cons, word, end, &f, depth, dom, !*overlapped)? {
1092 if !self.exhaustive.get() {
1093 return Ok(false);
1094 }
1095 holds = false;
1096 }
1097 }
1098 // An empty match of the antecedent triggers `|=>` at the start
1099 // point itself (16.12.7): `(empty ##1 1)` is `1` on that tick.
1100 if !overlapped
1101 && ante.admits_empty
1102 && aw.letter(pos) != Some(false)
1103 && !self.sat(*cons, word, pos, frame, depth, dom, false)?
1104 {
1105 holds = false;
1106 }
1107 Ok(holds)
1108 }
1109 Prop::Nexttime { p, n, .. } => {
1110 let to = self.after(word, dom, pos, *n)?;
1111 self.sat(*p, word, to, frame, depth, dom, false)
1112 }
1113 Prop::Always { p, lo, hi, .. } => {
1114 if let Some(found) =
1115 self.scan_range(*p, word, pos, *lo, *hi, frame, depth, dom, false)
1116 {
1117 return found.map(|failed| !failed);
1118 }
1119 for t in self.positions(word, dom, pos, *lo, *hi)? {
1120 if !self.sat(*p, word, t, frame, depth, dom, false)? {
1121 return Ok(false);
1122 }
1123 }
1124 Ok(true)
1125 }
1126 Prop::Eventually { p, lo, hi, .. } => {
1127 if let Some(found) =
1128 self.scan_range(*p, word, pos, *lo, *hi, frame, depth, dom, true)
1129 {
1130 return found;
1131 }
1132 for t in self.positions(word, dom, pos, *lo, *hi)? {
1133 if self.sat(*p, word, t, frame, depth, dom, false)? {
1134 return Ok(true);
1135 }
1136 }
1137 Ok(false)
1138 }
1139 Prop::Until { p, q, strong } => {
1140 // `∃j: q at j, p at every i < j` or `p at every i`; a start point
1141 // past `extent` is `extent`, so the scan ends there and, having
1142 // passed every `p`, a weak `until` holds forever on. The `i` and
1143 // `j` are ticks of `dom`.
1144 let mut t = pos;
1145 if let Some(decided) = self.skip_settled_until(*p, *q, word, pos, frame, depth, dom)
1146 {
1147 match decided {
1148 Settled::Verdict(v) => return Ok(v),
1149 Settled::Resume(from) => t = from,
1150 }
1151 }
1152 loop {
1153 if self.sat(*q, word, t, frame, depth, dom, false)? {
1154 return Ok(true);
1155 }
1156 if !self.sat(*p, word, t, frame, depth, dom, false)? {
1157 return Ok(false);
1158 }
1159 if t >= word.extent() {
1160 break;
1161 }
1162 t = self.align(word, dom, self.after(word, dom, t, 1)?)?;
1163 }
1164 Ok(!strong)
1165 }
1166 Prop::Clocked { clock, p } => self.sat(*p, word, pos, frame, depth, *clock, strict),
1167 Prop::Abort { cond, p, mode } => {
1168 self.abort(*cond, *p, *mode, word, pos, frame, depth, dom)
1169 }
1170 Prop::Let { writes, body } => {
1171 let mut f = frame.clone();
1172 for (var, leaf) in writes {
1173 let raw = if word.letter(pos).is_some() {
1174 0
1175 } else {
1176 self.env.value(*leaf, pos, &f)?
1177 };
1178 f[var.0 as usize] = Some(self.ir.automaton.local_var(*var).cast(raw));
1179 }
1180 self.sat(*body, word, pos, &f, depth, dom, false)
1181 }
1182 Prop::Call { decl, args, inits } => {
1183 if depth >= self.recursion_limit(word) {
1184 // `π[0]` is the Boolean `1'b1`: true on every letter but `⊥`.
1185 return Ok(word.letter(pos) != Some(false));
1186 }
1187 // The body runs in the caller's frame with the formals' slots
1188 // rebound: every actual is evaluated in the caller's frame, before
1189 // any slot is overwritten (an actual may read the slot its own
1190 // formal is about to take, `fibonacci1(b, a+b, ...)`).
1191 let f = self.call_frame(args, inits, word, pos, frame)?;
1192 let key = (*decl, pos, word.clone(), f, depth, dom);
1193 if let Some(&known) = self.calls.borrow().get(&key) {
1194 return Ok(known);
1195 }
1196 let body = self.ir.decls[decl.0 as usize].body;
1197 let holds = self.sat(body, word, pos, &key.3, depth + 1, dom, false)?;
1198 self.calls.borrow_mut().insert(key, holds);
1199 Ok(holds)
1200 }
1201 }
1202 }
1203
1204 /// Whether the evaluation attempt started at tick `start` of a trace of `len`
1205 /// samples is NONVACUOUS (16.14.8); together with a passing verdict it is a
1206 /// nonvacuous success.
1207 ///
1208 /// The attempt is judged on the letters it saw: those before the first moment
1209 /// the property spec's `disable iff` is true (rule ag: the condition holds in
1210 /// no time step of the attempt), all of them when it never is. The rules
1211 /// (a)-(af) are syntax-directed and live in [`Self::nv`], one arm per
1212 /// operator, next to [`Self::sat`]'s arm for the same operator.
1213 pub fn nonvacuous(&self, len: u32, start: u32) -> Result<bool, TraceError> {
1214 let end = self.disabling_prefix(len, start)?.unwrap_or(len);
1215 self.calls.borrow_mut().clear();
1216 let frame = self.initial_frame();
1217 self.nv(
1218 self.ir.root,
1219 &TailWord::strong(end),
1220 start,
1221 &frame,
1222 0,
1223 self.root_clock(),
1224 false,
1225 )
1226 }
1227
1228 /// 16.14.8: is the evaluation attempt of `id` from `pos` nonvacuous? The
1229 /// arguments are [`Self::sat`]'s. A "clock event" of the rules is a real tick
1230 /// of `dom` of the word, never one of its `⊤`/`⊥` letters.
1231 #[allow(clippy::too_many_arguments)]
1232 fn nv(
1233 &self,
1234 id: PropId,
1235 word: &TailWord,
1236 pos: u32,
1237 frame: &Frame,
1238 depth: u32,
1239 dom: ClockId,
1240 strict: bool,
1241 ) -> Result<bool, TraceError> {
1242 let prop = self.ir.prop(id);
1243 let (pos, strict) = self.stand(prop, word, pos, dom, strict)?;
1244 let real = |t: u32| word.letter(t).is_none();
1245 match prop {
1246 // (a), (b), (c): a sequence, `strong(seq)` and `weak(seq)`.
1247 Prop::Seq { .. } => Ok(true),
1248 // (d)
1249 Prop::Not(p) => self.nv(*p, &word.flip(), pos, frame, depth, dom, strict),
1250 // (e), (f), (aa)
1251 Prop::And(a, b) => Ok(self.nv(*a, word, pos, frame, depth, dom, strict)?
1252 || self.nv(*b, word, pos, frame, depth, dom, strict)?),
1253 Prop::Or(a, b) => {
1254 if self.ir.nv_rule(id) == Some(NvRule::Implies) {
1255 // (z): `not p or q` standing for `p implies q`.
1256 let Prop::Not(p) = self.ir.prop(*a) else {
1257 unreachable!("an `implies` node's left operand is the negation of `p`")
1258 };
1259 return Ok(self.sat(*p, word, pos, frame, depth, dom, strict)?
1260 && self.nv(*p, word, pos, frame, depth, dom, strict)?
1261 && self.nv(*b, word, pos, frame, depth, dom, strict)?);
1262 }
1263 Ok(self.nv(*a, word, pos, frame, depth, dom, strict)?
1264 || self.nv(*b, word, pos, frame, depth, dom, strict)?)
1265 }
1266 // (g): the branch taken, and nothing at all for a missing `else`.
1267 Prop::If { cond, then, els } => {
1268 if self.boolean(*cond, word, pos, frame)? {
1269 self.nv(*then, word, pos, frame, depth, dom, false)
1270 } else if let Some(e) = els {
1271 self.nv(*e, word, pos, frame, depth, dom, false)
1272 } else {
1273 Ok(false)
1274 }
1275 }
1276 // (af): the selected arm, the `default` when none is.
1277 Prop::Case {
1278 selector,
1279 arms,
1280 default,
1281 } => {
1282 for arm in arms {
1283 if self.arm_selected(*selector, arm, word, pos, frame)? {
1284 return self.nv(arm.body, word, pos, frame, depth, dom, false);
1285 }
1286 }
1287 match default {
1288 Some(d) => self.nv(*d, word, pos, frame, depth, dom, false),
1289 None => Ok(false),
1290 }
1291 }
1292 // (h), (j), (k): a match of the antecedent with a nonvacuous
1293 // consequent attempt from its end point (the next clock event for the
1294 // nonoverlapped forms).
1295 Prop::Implication {
1296 ante,
1297 cons,
1298 overlapped,
1299 } => {
1300 let on = ante.end_clock;
1301 let dual;
1302 let aw = if self.open_future {
1303 dual = word.flip();
1304 &dual
1305 } else {
1306 word
1307 };
1308 for (end, f) in self.matches(*ante, aw, pos, frame)? {
1309 if !*overlapped {
1310 let next = self.after(aw, self.lead_clock(*cons, on), end, 1)?;
1311 if aw.letter(next) == Some(false) {
1312 continue;
1313 }
1314 }
1315 if self.nv(*cons, word, end, &f, depth, dom, !*overlapped)? {
1316 return Ok(true);
1317 }
1318 }
1319 Ok(!overlapped
1320 && ante.admits_empty
1321 && aw.letter(pos) != Some(false)
1322 && self.nv(*cons, word, pos, frame, depth, dom, false)?)
1323 }
1324 // (l)-(o): the clock event `n` ticks on must exist.
1325 Prop::Nexttime { p, n, .. } => {
1326 let to = self.after(word, dom, pos, *n)?;
1327 Ok(real(to) && self.nv(*p, word, to, frame, depth, dom, false)?)
1328 }
1329 // (p)-(r): a clock event of the range where `p` is nonvacuous, `p`
1330 // not having failed before it.
1331 Prop::Always { p, lo, hi, .. } => {
1332 for t in self.positions(word, dom, pos, *lo, *hi)? {
1333 if !real(t) {
1334 break;
1335 }
1336 if self.nv(*p, word, t, frame, depth, dom, false)? {
1337 return Ok(true);
1338 }
1339 if !self.sat(*p, word, t, frame, depth, dom, false)? {
1340 return Ok(false);
1341 }
1342 }
1343 Ok(false)
1344 }
1345 // (s)-(u): the same with `p` not having held before.
1346 Prop::Eventually { p, lo, hi, .. } => {
1347 for t in self.positions(word, dom, pos, *lo, *hi)? {
1348 if !real(t) {
1349 break;
1350 }
1351 if self.nv(*p, word, t, frame, depth, dom, false)? {
1352 return Ok(true);
1353 }
1354 if self.sat(*p, word, t, frame, depth, dom, false)? {
1355 return Ok(false);
1356 }
1357 }
1358 Ok(false)
1359 }
1360 // (v), (w): a clock event where either operand is nonvacuous, `q` not
1361 // having held and `p` having held at every one before it; (x), (y)
1362 // (`until_with`): only `p` counts.
1363 Prop::Until { p, q, .. } => {
1364 let with = self.ir.nv_rule(id) == Some(NvRule::UntilWith);
1365 let mut t = pos;
1366 while real(t) {
1367 if self.nv(*p, word, t, frame, depth, dom, false)?
1368 || (!with && self.nv(*q, word, t, frame, depth, dom, false)?)
1369 {
1370 return Ok(true);
1371 }
1372 if self.sat(*q, word, t, frame, depth, dom, false)?
1373 || !self.sat(*p, word, t, frame, depth, dom, false)?
1374 {
1375 return Ok(false);
1376 }
1377 t = self.align(word, dom, self.after(word, dom, t, 1)?)?;
1378 }
1379 Ok(false)
1380 }
1381 Prop::Clocked { clock, p } => self.nv(*p, word, pos, frame, depth, *clock, strict),
1382 // (ab)-(ae): `p` nonvacuous and the condition true at no time step of
1383 // the attempt (at no clock event, for the `sync_` forms).
1384 Prop::Abort { cond, p, mode } => {
1385 if !self.nv(*p, word, pos, frame, depth, dom, false)? {
1386 return Ok(false);
1387 }
1388 let sync = mode.is_sync();
1389 let timeline = self.timeline_for(word)?;
1390 for t in pos..word.real {
1391 let looks = !sync || timeline.as_ref().is_none_or(|tl| tl.is_tick(dom, t));
1392 if looks && self.boolean(*cond, word, t, frame)? {
1393 return Ok(false);
1394 }
1395 if !sync && timeline.is_none() && self.env.between(*cond, t)? {
1396 return Ok(false);
1397 }
1398 }
1399 Ok(true)
1400 }
1401 Prop::Let { writes, body } => {
1402 let mut f = frame.clone();
1403 for (var, leaf) in writes {
1404 let raw = if word.letter(pos).is_some() {
1405 0
1406 } else {
1407 self.env.value(*leaf, pos, &f)?
1408 };
1409 f[var.0 as usize] = Some(self.ir.automaton.local_var(*var).cast(raw));
1410 }
1411 self.nv(*body, word, pos, &f, depth, dom, false)
1412 }
1413 // (i): the instance's body with the actuals substituted.
1414 Prop::Call { decl, args, inits } => {
1415 if depth >= self.recursion_limit(word) {
1416 return Ok(false);
1417 }
1418 let f = self.call_frame(args, inits, word, pos, frame)?;
1419 let body = self.ir.decls[decl.0 as usize].body;
1420 self.nv(body, word, pos, &f, depth + 1, dom, false)
1421 }
1422 }
1423 }
1424
1425 /// Whether the `case` arm is selected at `pos`: on a `⊤` letter every label
1426 /// holds, on `⊥` none does (the `if (selector === label)` chain of F.3.4.3
1427 /// evaluated at such a letter).
1428 fn arm_selected(
1429 &self,
1430 selector: LeafId,
1431 arm: &CaseArm,
1432 word: &TailWord,
1433 pos: u32,
1434 frame: &Frame,
1435 ) -> Result<bool, TraceError> {
1436 if let Some(top) = word.letter(pos) {
1437 return Ok(top);
1438 }
1439 for &label in &arm.labels {
1440 if self.env.case_match(selector, label, pos, frame)? {
1441 return Ok(true);
1442 }
1443 }
1444 Ok(false)
1445 }
1446
1447 /// The distinct start points `always`/`eventually [lo:hi]` ranges over from
1448 /// `pos`: `pos+lo ..= pos+hi` (to the end for `$`), with every tick past
1449 /// `extent` folded into it.
1450 ///
1451 /// The range counts ticks of `dom` (16.13.2: a bounded `always [1:2]` on a
1452 /// clock is the next one and two ticks of THAT clock).
1453 fn positions(
1454 &self,
1455 word: &TailWord,
1456 dom: ClockId,
1457 pos: u32,
1458 lo: u32,
1459 hi: Option<u32>,
1460 ) -> Result<Vec<u32>, TraceError> {
1461 let ext = word.extent();
1462 let mut out = Vec::new();
1463 let mut k = lo;
1464 loop {
1465 let at = self.after(word, dom, pos, k)?;
1466 if at >= ext {
1467 out.push(ext);
1468 break;
1469 }
1470 out.push(at);
1471 if hi.is_some_and(|h| k >= h) {
1472 break;
1473 }
1474 k += 1;
1475 }
1476 Ok(out)
1477 }
1478
1479 /// How far the verdicts of `id` are SETTLED on `word` (see [`Accel`]'s
1480 /// `Fin`): the window ticks `0..end` have a verdict no continuation of the
1481 /// trace and no choice of word can change, and they are in the accelerator's
1482 /// memo. `None` when the accelerator does not model `id` (several clocks, an
1483 /// unbounded lookahead, a local variable that is read, a word with explicit
1484 /// letters, ...), and the caller then runs the reference code.
1485 ///
1486 /// The memo is filled here, in tick order, with the verdicts the reference
1487 /// evaluation computes (`sat` of `id` at the tick, which is exactly what its
1488 /// loops ask), up to the last tick whose whole lookahead is real samples of
1489 /// `word`; one that is an error stops the filling, and the reference code
1490 /// then meets it where it would have.
1491 fn settled(
1492 &self,
1493 id: PropId,
1494 word: &TailWord,
1495 frame: &Frame,
1496 depth: u32,
1497 dom: ClockId,
1498 ) -> Option<u32> {
1499 let cell = self.accel?;
1500 if !word.mid.is_empty() || self.env.timeline().is_some() {
1501 return None;
1502 }
1503 let (reach, base) = {
1504 let accel = cell.try_borrow().ok()?;
1505 (accel.final_reach(id)?, accel.base())
1506 };
1507 // A tick `t` is settled when `t + reach` is a real tick.
1508 let limit = word.real.saturating_sub(reach);
1509 loop {
1510 let (next, blocked) = cell.try_borrow_mut().ok()?.final_next(id);
1511 let next_rel = u32::try_from(next - base).ok()?;
1512 if blocked || next_rel >= limit {
1513 return Some(next_rel.min(limit));
1514 }
1515 let verdict = self.sat(id, word, next_rel, frame, depth, dom, false);
1516 let mut accel = cell.try_borrow_mut().ok()?;
1517 accel.final_reach(id)?;
1518 accel.final_record(id, next, verdict.ok());
1519 }
1520 }
1521
1522 /// Whether some tick of the range `always`/`eventually [lo:hi]` ranges over
1523 /// from `pos` has the verdict `stop_on` for `p`, found in the order, and with
1524 /// the errors, the reference loop over [`Self::positions`] has: the settled
1525 /// run of the range is read from the memo, and the rest evaluated.
1526 /// `None` declines (see [`Self::settled`]).
1527 #[allow(clippy::too_many_arguments)]
1528 fn scan_range(
1529 &self,
1530 p: PropId,
1531 word: &TailWord,
1532 pos: u32,
1533 lo: u32,
1534 hi: Option<u32>,
1535 frame: &Frame,
1536 depth: u32,
1537 dom: ClockId,
1538 stop_on: bool,
1539 ) -> Option<Result<bool, TraceError>> {
1540 let end = self.settled(p, word, frame, depth, dom)?;
1541 let cell = self.accel?;
1542 let first = pos.saturating_add(lo);
1543 let upper = hi.map_or(end, |h| end.min(pos.saturating_add(h).saturating_add(1)));
1544 let mut k = lo;
1545 if first < upper {
1546 let accel = cell.try_borrow().ok()?;
1547 let base = accel.base();
1548 let hit = accel.final_first(p, stop_on, base + u64::from(first));
1549 if hit.is_some_and(|t| t < base + u64::from(upper)) {
1550 return Some(Ok(true));
1551 }
1552 k = upper - pos;
1553 }
1554 let ext = word.extent();
1555 loop {
1556 if hi.is_some_and(|h| k > h) {
1557 return Some(Ok(false));
1558 }
1559 let at = pos + k;
1560 match self.sat(p, word, at.min(ext), frame, depth, dom, false) {
1561 Err(e) => return Some(Err(e)),
1562 Ok(v) if v == stop_on => return Some(Ok(true)),
1563 Ok(_) => {}
1564 }
1565 if at >= ext || hi.is_some_and(|h| k >= h) {
1566 return Some(Ok(false));
1567 }
1568 k += 1;
1569 }
1570 }
1571
1572 /// The settled run of `p until q` from `pos`: the verdict when a settled tick
1573 /// decides it, else the tick the evaluation resumes at (every tick before
1574 /// it has `q` false and `p` true). `None` declines.
1575 #[allow(clippy::too_many_arguments)]
1576 fn skip_settled_until(
1577 &self,
1578 p: PropId,
1579 q: PropId,
1580 word: &TailWord,
1581 pos: u32,
1582 frame: &Frame,
1583 depth: u32,
1584 dom: ClockId,
1585 ) -> Option<Settled> {
1586 let ep = self.settled(p, word, frame, depth, dom)?;
1587 let eq = self.settled(q, word, frame, depth, dom)?;
1588 let end = ep.min(eq);
1589 if pos >= end {
1590 return None;
1591 }
1592 let accel = self.accel?.try_borrow().ok()?;
1593 let (from, to) = (accel.base() + u64::from(pos), accel.base() + u64::from(end));
1594 let q_true = accel.final_first(q, true, from).filter(|&t| t < to);
1595 let p_false = accel.final_first(p, false, from).filter(|&t| t < to);
1596 Some(match (q_true, p_false) {
1597 // `q` is tried before `p` on a tick.
1598 (Some(a), Some(b)) => Settled::Verdict(a <= b),
1599 (Some(_), None) => Settled::Verdict(true),
1600 (None, Some(_)) => Settled::Verdict(false),
1601 (None, None) => Settled::Resume(end),
1602 })
1603 }
1604
1605 /// `accept_on`/`reject_on`/`sync_*` (16.12.14, F.5.6.1).
1606 #[allow(clippy::too_many_arguments)]
1607 fn abort(
1608 &self,
1609 cond: LeafId,
1610 p: PropId,
1611 mode: AbortMode,
1612 word: &TailWord,
1613 pos: u32,
1614 frame: &Frame,
1615 depth: u32,
1616 dom: ClockId,
1617 ) -> Result<bool, TraceError> {
1618 if mode.accepts() {
1619 self.accept_on(cond, mode.is_sync(), p, false, word, pos, frame, depth, dom)
1620 } else {
1621 // `reject_on(b) P` is `not accept_on(b) not P`.
1622 Ok(!self.accept_on(
1623 cond,
1624 mode.is_sync(),
1625 p,
1626 true,
1627 &word.flip(),
1628 pos,
1629 frame,
1630 depth,
1631 dom,
1632 )?)
1633 }
1634 }
1635
1636 /// `accept_on(cond) p` (`not p` when `negated`) on `word` from `pos`: `p` holds
1637 /// on the whole word and the condition is never true, or for some abort
1638 /// point the word cut just before it and completed with `⊤` satisfies `p`.
1639 /// An abort point is a tick whose sampled condition is true and, unless
1640 /// `sync`, a time step between two ticks where it is.
1641 #[allow(clippy::too_many_arguments)]
1642 fn accept_on(
1643 &self,
1644 cond: LeafId,
1645 sync: bool,
1646 p: PropId,
1647 negated: bool,
1648 word: &TailWord,
1649 pos: u32,
1650 frame: &Frame,
1651 depth: u32,
1652 dom: ClockId,
1653 ) -> Result<bool, TraceError> {
1654 let inner = |w: &TailWord| -> Result<bool, TraceError> {
1655 if negated {
1656 Ok(!self.sat(p, &w.flip(), pos, frame, depth, dom, false)?)
1657 } else {
1658 self.sat(p, w, pos, frame, depth, dom, false)
1659 }
1660 };
1661 let timeline = self.timeline_for(word)?;
1662 let mut aborted = false;
1663 for t in pos..=word.extent() {
1664 // On a multiclock timeline the positions ARE the global steps: an
1665 // asynchronous condition is looked at on every one of them and a
1666 // synchronous one on the ticks of the property's clock. The `between`
1667 // steps do not exist there (every step is a position). When a
1668 // property spans several clocks this uses the clock the abort sits
1669 // on, not each operand's own -- see the module docs' approximation.
1670 let looks = !sync || timeline.as_ref().is_none_or(|tl| tl.is_tick(dom, t));
1671 if looks && self.boolean(cond, word, t, frame)? {
1672 aborted = true;
1673 if inner(&word.cut_then_top(t))? {
1674 return Ok(true);
1675 }
1676 }
1677 if !sync && timeline.is_none() && t < word.real && self.env.between(cond, t)? {
1678 aborted = true;
1679 if inner(&word.cut_then_top(t + 1))? {
1680 return Ok(true);
1681 }
1682 }
1683 }
1684 // A condition true on the constant tail is true at `extent` already.
1685 if aborted { Ok(false) } else { inner(word) }
1686 }
1687}
1688
1689/// Whether `p` adds no position of its own: it evaluates its operands at the
1690/// position it is given, so a pending `|=>` synchronization (see
1691/// [`Evaluator::sat`]'s `strict`) passes through it to the operands.
1692fn pass_through(p: &Prop) -> bool {
1693 match p {
1694 Prop::Not(_) | Prop::And(..) | Prop::Or(..) | Prop::Clocked { .. } => true,
1695 Prop::Seq { .. }
1696 | Prop::If { .. }
1697 | Prop::Case { .. }
1698 | Prop::Implication { .. }
1699 | Prop::Nexttime { .. }
1700 | Prop::Always { .. }
1701 | Prop::Eventually { .. }
1702 | Prop::Until { .. }
1703 | Prop::Abort { .. }
1704 | Prop::Let { .. }
1705 | Prop::Call { .. } => false,
1706 }
1707}
1708
1709/// What an automaton entry's sequence is, for the static legality checks of
1710/// 16.12.22.
1711#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1712pub enum SequenceClass {
1713 /// No match at all, empty or not: `(1'b1) intersect (1'b1 ##1 1'b1)`.
1714 Never,
1715 /// Only the empty match: `1'b1[*0]`.
1716 EmptyOnly,
1717 /// At least one nonempty match; `also_empty` says whether the empty match is
1718 /// admitted too (`a[*0:2]`).
1719 NonEmpty { also_empty: bool },
1720}
1721
1722/// Classifies the sequence entered at `seq` (16.12.22, F.5.5: nondegenerate iff
1723/// some nonempty word matches). A nonempty match is a path from the entry to a
1724/// state with a [`Transition::Match`] edge; the Boolean guards are treated as
1725/// satisfiable, which is what "some word" means for a Boolean expression, so
1726/// the result is a pure function of the automaton's graph.
1727///
1728/// `guard_never` names the leaves that can never hold (a constant-false
1729/// Boolean, judged on the leaf's own value -- `!e` for a negated leaf): an edge
1730/// guarded by one is not on any path.
1731pub fn classify_sequence(
1732 au: &Automaton,
1733 seq: SeqRef,
1734 guard_never: &dyn Fn(LeafId) -> bool,
1735) -> SequenceClass {
1736 let mut seen = vec![false; au.states.len()];
1737 let mut stack = vec![seq.entry];
1738 seen[seq.entry.0 as usize] = true;
1739 let mut accepting = false;
1740 while let Some(s) = stack.pop() {
1741 for e in &au.state(s).edges {
1742 if e.guard.is_some_and(guard_never) {
1743 continue;
1744 }
1745 if matches!(e.label, Transition::Match { .. }) {
1746 accepting = true;
1747 continue;
1748 }
1749 if !seen[e.target.0 as usize] {
1750 seen[e.target.0 as usize] = true;
1751 stack.push(e.target);
1752 }
1753 }
1754 }
1755 match (accepting, seq.admits_empty) {
1756 (true, also_empty) => SequenceClass::NonEmpty { also_empty },
1757 (false, true) => SequenceClass::EmptyOnly,
1758 (false, false) => SequenceClass::Never,
1759 }
1760}
1761
1762#[cfg(test)]
1763mod tests {
1764 use super::*;
1765 use crate::sva::ir::LeafId;
1766
1767 /// A property under construction over a hand-built automaton.
1768 struct B {
1769 au: Automaton,
1770 props: Vec<Prop>,
1771 decls: Vec<PropDecl>,
1772 }
1773
1774 impl B {
1775 fn new() -> Self {
1776 B {
1777 au: Automaton::empty(),
1778 props: Vec::new(),
1779 decls: Vec::new(),
1780 }
1781 }
1782
1783 fn prop(&mut self, p: Prop) -> PropId {
1784 self.props.push(p);
1785 PropId(self.props.len() as u32 - 1)
1786 }
1787
1788 /// The sequence `l0 ##d1 l1 ##d2 l2 ...` of Boolean leaves.
1789 fn seq(&mut self, leaves: &[u32], delays: &[u32]) -> SeqRef {
1790 let ranges: Vec<(u32, Option<u32>)> = delays.iter().map(|&d| (d, Some(d))).collect();
1791 self.seq_r(leaves, &ranges)
1792 }
1793
1794 /// The sequence `l0 ##[m1:n1] l1 ...` (`None` is `$`).
1795 fn seq_r(&mut self, leaves: &[u32], delays: &[(u32, Option<u32>)]) -> SeqRef {
1796 assert_eq!(leaves.len(), delays.len() + 1);
1797 let entry = self.au.new_state();
1798 let mut at = entry;
1799 for (i, l) in leaves.iter().enumerate() {
1800 let next = self.au.new_state();
1801 self.au
1802 .add_edge(at, Some(LeafId(*l)), Transition::Epsilon, next);
1803 at = next;
1804 if let Some(&(min, max)) = delays.get(i) {
1805 let next = self.au.new_state();
1806 self.au
1807 .add_edge(at, None, Transition::Delay { min, max }, next);
1808 at = next;
1809 }
1810 }
1811 self.au.add_edge(
1812 at,
1813 None,
1814 Transition::Match {
1815 actions: Vec::new(),
1816 },
1817 at,
1818 );
1819 SeqRef::single(entry, false)
1820 }
1821
1822 fn leaf(&mut self, l: u32) -> SeqRef {
1823 self.seq(&[l], &[])
1824 }
1825
1826 /// The Boolean leaf `l` as a property (a one-letter sequence).
1827 fn b(&mut self, l: u32, strength: Option<Strength>) -> PropId {
1828 let seq = self.leaf(l);
1829 self.prop(Prop::Seq { seq, strength })
1830 }
1831
1832 fn not(&mut self, p: PropId) -> PropId {
1833 self.prop(Prop::Not(p))
1834 }
1835
1836 fn finish(self, root: PropId) -> PropertyIr {
1837 PropertyIr {
1838 automaton: self.au,
1839 props: self.props,
1840 decls: self.decls,
1841 root,
1842 disable: None,
1843 nv_rules: Vec::new(),
1844 }
1845 }
1846 }
1847
1848 /// Leaves as per-leaf boolean rows.
1849 struct Rows(Vec<Vec<bool>>);
1850
1851 impl PropEnv for Rows {
1852 fn value(&self, leaf: LeafId, tick: u32, _: &[Option<i64>]) -> Result<i64, TraceError> {
1853 Ok(i64::from(self.0[leaf.0 as usize][tick as usize]))
1854 }
1855 fn case_match(
1856 &self,
1857 selector: LeafId,
1858 label: LeafId,
1859 tick: u32,
1860 f: &[Option<i64>],
1861 ) -> Result<bool, TraceError> {
1862 Ok(self.value(selector, tick, f)? == self.value(label, tick, f)?)
1863 }
1864 }
1865
1866 fn rows(r: &[&str]) -> Rows {
1867 Rows(
1868 r.iter()
1869 .map(|s| s.chars().map(|c| c == '1').collect())
1870 .collect(),
1871 )
1872 }
1873
1874 fn verdict(ir: &PropertyIr, env: &Rows, len: u32, start: u32, strong: bool) -> Verdict {
1875 Evaluator::new(ir, env, strong).verdict(len, start).unwrap()
1876 }
1877
1878 /// 16.12.2: `p3` is `b ##1 c`; `a1: assert property (a |-> p3)` reads `p3` as
1879 /// weak, `c1: cover property (a #-# p3)` as strong.
1880 #[test]
1881 fn lrm_sequence_property_is_weak_in_assert_and_strong_in_cover() {
1882 // leaves: 0 = a, 1 = b, 2 = c
1883 let mut b = B::new();
1884 let a = b.leaf(0);
1885 let p3 = b.seq(&[1, 2], &[1]);
1886 let p3p = b.prop(Prop::Seq {
1887 seq: p3,
1888 strength: None,
1889 });
1890 let a1 = b.prop(Prop::Implication {
1891 ante: a,
1892 cons: p3p,
1893 overlapped: true,
1894 });
1895 let ir = b.finish(a1);
1896 // a false: true either way. b true then the trace ends: weak holds,
1897 // strong does not (assert only needs the weak reading).
1898 let end_after_b = rows(&["1", "1", "0"]);
1899 assert_eq!(
1900 verdict(&ir, &end_after_b, 1, 0, false),
1901 Verdict::Holds,
1902 "weak: `b` holds and there is no next tick"
1903 );
1904 // b true and c true at the next tick: holds strongly.
1905 let full = rows(&["11", "10", "01"]);
1906 assert_eq!(verdict(&ir, &full, 2, 0, false), Verdict::HoldsStrongly);
1907 // b true but c false at the next tick: fails.
1908 let bad = rows(&["11", "10", "00"]);
1909 assert_eq!(verdict(&ir, &bad, 2, 0, false), Verdict::Fails);
1910 // a false at the start: the implication is vacuously true.
1911 let vac = rows(&["0", "0", "0"]);
1912 assert_eq!(verdict(&ir, &vac, 1, 0, false), Verdict::HoldsStrongly);
1913 // b false at the start: fails regardless of the trace length.
1914 let nob = rows(&["1", "0", "1"]);
1915 assert_eq!(verdict(&ir, &nob, 1, 0, false), Verdict::Fails);
1916 }
1917
1918 /// Every assignment of `leaves` boolean rows of `len` ticks.
1919 fn all_traces(leaves: usize, len: usize) -> Vec<Rows> {
1920 let bits = leaves * len;
1921 (0..1u32 << bits)
1922 .map(|m| {
1923 Rows(
1924 (0..leaves)
1925 .map(|l| (0..len).map(|t| m >> (l * len + t) & 1 == 1).collect())
1926 .collect(),
1927 )
1928 })
1929 .collect()
1930 }
1931
1932 /// The verdict of `root` over every trace of `leaves` signals of 0..=max_len ticks
1933 /// from every start tick, as a flat list.
1934 fn verdicts(ir: &PropertyIr, leaves: usize, max_len: usize, strong: bool) -> Vec<Verdict> {
1935 let mut out = Vec::new();
1936 for len in 1..=max_len {
1937 for env in all_traces(leaves, len) {
1938 for start in 0..len as u32 {
1939 out.push(verdict(ir, &env, len as u32, start, strong));
1940 }
1941 }
1942 }
1943 out
1944 }
1945
1946 /// 16.12.3: `not` switches strength. For `a ##[1:$] b` the weak sequence holds on
1947 /// the dual word (an unbounded delay is never refuted by a finite prefix),
1948 /// so `not (a ##[1:$] b)` fails on a trace that ends right after `a` -- the
1949 /// pitfall -- while `not strong(a ##[1:$] b)` only stays pending.
1950 #[test]
1951 fn lrm_not_of_a_weak_sequence_is_the_pitfall_and_strong_is_the_fix() {
1952 let mut b = B::new();
1953 let r = b.seq_r(&[0, 1], &[(1, None)]);
1954 let weak = b.prop(Prop::Seq {
1955 seq: r,
1956 strength: None,
1957 });
1958 let a1 = b.not(weak);
1959 let strong = b.prop(Prop::Seq {
1960 seq: r,
1961 strength: Some(Strength::Strong),
1962 });
1963 let a2 = b.not(strong);
1964 let mut ir1 = b.finish(a1);
1965 // a holds at the last tick of the trace, `b` never does.
1966 let env = rows(&["1", "0"]);
1967 assert_eq!(verdict(&ir1, &env, 1, 0, false), Verdict::Fails);
1968 ir1.root = a2;
1969 assert_eq!(verdict(&ir1, &env, 1, 0, false), Verdict::Holds);
1970 // With `b` seen after `a`, the strong sequence matched: `not` fails both ways.
1971 let env = rows(&["10", "01"]);
1972 assert_eq!(verdict(&ir1, &env, 2, 0, false), Verdict::Fails);
1973 }
1974
1975 /// Table 40-3 (`aundf`): the weak `a ##[1:$] b` never fails on a finite prefix
1976 /// and does not hold STRONGLY until it matched, so its attempt is pending
1977 /// (not passed) while `b` has not been seen, and passes once it has.
1978 #[test]
1979 fn weak_unbounded_sequence_is_pending_until_it_matches() {
1980 let mut b = B::new();
1981 let r = b.seq_r(&[0, 1], &[(1, None)]);
1982 let weak = b.prop(Prop::Seq {
1983 seq: r,
1984 strength: None,
1985 });
1986 let ir = b.finish(weak);
1987 // Leaves 0 = a, 1 = b, one string per leaf: `a` at tick 0, `b` never.
1988 for (a, bb) in [("1", "0"), ("10", "00"), ("100", "000")] {
1989 let env = rows(&[a, bb]);
1990 assert_eq!(verdict(&ir, &env, a.len() as u32, 0, false), Verdict::Holds);
1991 }
1992 // `b` two ticks after `a`.
1993 let env = rows(&["100", "001"]);
1994 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::HoldsStrongly);
1995 }
1996
1997 /// Strength is the only thing `not` changes: `strong(R)` implies `weak(R)` on
1998 /// every trace, and `weak` fails only where `strong` does.
1999 #[test]
2000 fn strong_sequence_implies_weak_sequence() {
2001 let mut b = B::new();
2002 let r = b.seq_r(&[0, 1], &[(1, Some(2))]);
2003 let s = b.prop(Prop::Seq {
2004 seq: r,
2005 strength: Some(Strength::Strong),
2006 });
2007 let w = b.prop(Prop::Seq {
2008 seq: r,
2009 strength: Some(Strength::Weak),
2010 });
2011 let mut ir = b.finish(s);
2012 let strong = verdicts(&ir, 2, 4, false);
2013 ir.root = w;
2014 let weak = verdicts(&ir, 2, 4, false);
2015 assert_eq!(strong.len(), weak.len());
2016 for (s, w) in strong.iter().zip(&weak) {
2017 // strong: Fails or HoldsStrongly... a strong sequence is `Holds`
2018 // (pending) on a trace that cut it short, weak is too.
2019 assert!(
2020 *s != Verdict::HoldsStrongly || *w == Verdict::HoldsStrongly,
2021 "strong match on a trace the weak reading does not hold strongly"
2022 );
2023 assert!(*s == Verdict::Fails || *w != Verdict::Fails);
2024 }
2025 }
2026
2027 /// 16.12.22 nondegeneracy, the LRM's three examples as automata.
2028 #[test]
2029 fn classify_sequence_lrm_examples() {
2030 let never = |_: LeafId| false;
2031 // (1'b1) intersect (1'b1 ##1 1'b1): no word matches -- built as an
2032 // entry whose only edges lead nowhere near a match.
2033 let mut au = Automaton::empty();
2034 let e = au.new_state();
2035 let dead = au.new_state();
2036 au.add_edge(e, Some(LeafId(0)), Transition::Epsilon, dead);
2037 let r = SeqRef::single(e, false);
2038 assert_eq!(classify_sequence(&au, r, &never), SequenceClass::Never);
2039 // 1'b1[*0]: only the empty match.
2040 let r = SeqRef::single(e, true);
2041 assert_eq!(classify_sequence(&au, r, &never), SequenceClass::EmptyOnly);
2042 // a[*0:2]: a nonempty match and the empty one.
2043 let mut b = B::new();
2044 let mut seq = b.leaf(0);
2045 seq.admits_empty = true;
2046 assert_eq!(
2047 classify_sequence(&b.au, seq, &never),
2048 SequenceClass::NonEmpty { also_empty: true }
2049 );
2050 // A match behind a leaf that can never hold is no match.
2051 let mut seq = b.leaf(1);
2052 assert_eq!(
2053 classify_sequence(&b.au, seq, &|l| l == LeafId(1)),
2054 SequenceClass::Never
2055 );
2056 seq.admits_empty = false;
2057 assert_eq!(
2058 classify_sequence(&b.au, seq, &never),
2059 SequenceClass::NonEmpty { also_empty: false }
2060 );
2061 }
2062
2063 /// `s_nexttime p` is `not nexttime not p` (F.3.4.3), for every trace, start and
2064 /// offset; `nexttime[0]` is the alignment operator (16.12.10).
2065 #[test]
2066 fn nexttime_duality_and_alignment() {
2067 for n in 0..=2u32 {
2068 for strength in [Some(Strength::Weak), Some(Strength::Strong)] {
2069 let mut b = B::new();
2070 let p = b.b(0, strength);
2071 let snt = b.prop(Prop::Nexttime { p, n, strong: true });
2072 let np = b.not(p);
2073 let nn = b.prop(Prop::Nexttime {
2074 p: np,
2075 n,
2076 strong: false,
2077 });
2078 let dual = b.not(nn);
2079 let mut ir = b.finish(snt);
2080 let a = verdicts(&ir, 1, 4, false);
2081 ir.root = dual;
2082 assert_eq!(a, verdicts(&ir, 1, 4, false), "n = {n}");
2083 }
2084 }
2085 // nexttime p needs the next tick: on a single-tick trace the weak
2086 // reading is satisfied, the strong one is only pending.
2087 let mut b = B::new();
2088 let p = b.b(0, Some(Strength::Strong));
2089 let nt = b.prop(Prop::Nexttime {
2090 p,
2091 n: 1,
2092 strong: false,
2093 });
2094 let ir = b.finish(nt);
2095 assert_eq!(verdict(&ir, &rows(&["0"]), 1, 0, false), Verdict::Holds);
2096 assert_eq!(
2097 verdict(&ir, &rows(&["01"]), 2, 0, false),
2098 Verdict::HoldsStrongly
2099 );
2100 assert_eq!(verdict(&ir, &rows(&["10"]), 2, 0, false), Verdict::Fails);
2101 }
2102
2103 /// `s_eventually p` is `not always not p` and `always p` is `p until 0`.
2104 #[test]
2105 fn always_eventually_until_dualities() {
2106 let mut b = B::new();
2107 let p = b.b(0, Some(Strength::Weak));
2108 let seventually = b.prop(Prop::Eventually {
2109 p,
2110 lo: 0,
2111 hi: None,
2112 strong: true,
2113 });
2114 let np = b.not(p);
2115 let al = b.prop(Prop::Always {
2116 p: np,
2117 lo: 0,
2118 hi: None,
2119 strong: false,
2120 });
2121 let dual = b.not(al);
2122 let always = b.prop(Prop::Always {
2123 p,
2124 lo: 0,
2125 hi: None,
2126 strong: false,
2127 });
2128 let zero = b.b(1, Some(Strength::Weak));
2129 let until = b.prop(Prop::Until {
2130 p,
2131 q: zero,
2132 strong: false,
2133 });
2134 let mut ir = b.finish(seventually);
2135 let a = verdicts(&ir, 2, 3, false);
2136 ir.root = dual;
2137 assert_eq!(a, verdicts(&ir, 2, 3, false));
2138 // `until` with q the constant-0 leaf 1 only when leaf 1 is all-zero; compare
2139 // `always p` with `p until q` on the traces where q never holds.
2140 ir.root = always;
2141 let al = verdicts_with(&ir, 3);
2142 ir.root = until;
2143 let un = verdicts_with(&ir, 3);
2144 assert_eq!(al, un);
2145 }
2146
2147 /// Verdicts over every `p` trace with `q` constant 0.
2148 fn verdicts_with(ir: &PropertyIr, max_len: usize) -> Vec<Verdict> {
2149 let mut out = Vec::new();
2150 for len in 1..=max_len {
2151 for env in all_traces(1, len) {
2152 let env = Rows(vec![env.0[0].clone(), vec![false; len]]);
2153 for start in 0..len as u32 {
2154 out.push(verdict(ir, &env, len as u32, start, false));
2155 }
2156 }
2157 }
2158 out
2159 }
2160
2161 /// 16.12.12: the four `until` forms on the LRM's `a`/`b` traces.
2162 #[test]
2163 fn until_family_truth_table() {
2164 // p until q: q at some tick j with p at all earlier ticks, or p always.
2165 let mut b = B::new();
2166 let p = b.b(0, Some(Strength::Weak));
2167 let q = b.b(1, Some(Strength::Weak));
2168 let u = b.prop(Prop::Until {
2169 p,
2170 q,
2171 strong: false,
2172 });
2173 let su = b.prop(Prop::Until { p, q, strong: true });
2174 let mut ir = b.finish(u);
2175 // a,a,a... never b: weak until holds (pending), strong until does not.
2176 let env = rows(&["111", "000"]);
2177 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Holds);
2178 ir.root = su;
2179 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Holds);
2180 // p fails before q: fails both.
2181 let env = rows(&["101", "001"]);
2182 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Fails);
2183 // q arrives while p still holds: holds strongly, even the strong form.
2184 let env = rows(&["110", "001"]);
2185 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::HoldsStrongly);
2186 // q at the same tick p drops: `until` does not need p at the q tick.
2187 let env = rows(&["100", "010"]);
2188 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::HoldsStrongly);
2189 }
2190
2191 /// 16.12.14: the abort cut. `accept_on(a) p` succeeds if `a` becomes true
2192 /// during the evaluation even when `p` would fail afterwards; and an abort
2193 /// that is not reached leaves `p`'s verdict.
2194 #[test]
2195 fn accept_on_cuts_the_word_at_the_abort() {
2196 // p = strong(x ##1 y), abort cond = leaf 2.
2197 let mut b = B::new();
2198 let r = b.seq(&[0, 1], &[1]);
2199 let p = b.prop(Prop::Seq {
2200 seq: r,
2201 strength: Some(Strength::Strong),
2202 });
2203 let acc = b.prop(Prop::Abort {
2204 cond: LeafId(2),
2205 p,
2206 mode: AbortMode::SyncAccept,
2207 });
2208 let ir = b.finish(acc);
2209 // x then y: holds strongly regardless.
2210 assert_eq!(
2211 verdict(&ir, &rows(&["10", "01", "00"]), 2, 0, false),
2212 Verdict::HoldsStrongly
2213 );
2214 // the sequence fails at tick 1 (y low) but the abort fires at tick 1: the
2215 // word cut before tick 1 and completed by T lets `y` be true, so true.
2216 let env = rows(&["10", "00", "01"]);
2217 assert_eq!(verdict(&ir, &env, 2, 0, false), Verdict::HoldsStrongly);
2218 // the abort fires only after the sequence failed: cut at tick 2 still sees the
2219 // failure at tick 1.
2220 let env = rows(&["100", "000", "001"]);
2221 assert_eq!(verdict(&ir, &env, 3, 0, false), Verdict::Fails);
2222 // sync_reject_on is `not sync_accept_on not`: on the same trace it is false when
2223 // the abort fires before the property is decided, true if it never fires.
2224 let rej = {
2225 let mut b = B::new();
2226 let r = b.seq(&[0, 1], &[1]);
2227 let p = b.prop(Prop::Seq {
2228 seq: r,
2229 strength: Some(Strength::Strong),
2230 });
2231 let rej = b.prop(Prop::Abort {
2232 cond: LeafId(2),
2233 p,
2234 mode: AbortMode::SyncReject,
2235 });
2236 b.finish(rej)
2237 };
2238 let env = rows(&["10", "01", "01"]);
2239 assert_eq!(verdict(&rej, &env, 2, 0, false), Verdict::Fails);
2240 let env = rows(&["10", "01", "00"]);
2241 assert_eq!(verdict(&rej, &env, 2, 0, false), Verdict::HoldsStrongly);
2242 }
2243
2244 /// 16.12.14: an abort and the end of the evaluation at the same tick: the abort
2245 /// wins; the outermost of two nested aborts wins a tie.
2246 #[test]
2247 fn abort_wins_a_tie_and_the_outermost_nested_abort_wins() {
2248 // p1 = strong(x), decided at tick 0. a (leaf 1) fires at tick 0 too.
2249 let mut b = B::new();
2250 let x = b.b(0, Some(Strength::Strong));
2251 let acc = b.prop(Prop::Abort {
2252 cond: LeafId(1),
2253 p: x,
2254 mode: AbortMode::SyncAccept,
2255 });
2256 let rej = b.prop(Prop::Abort {
2257 cond: LeafId(1),
2258 p: x,
2259 mode: AbortMode::SyncReject,
2260 });
2261 // x is false but the abort fires at tick 0 along with it: accept wins (true),
2262 // reject wins (false).
2263 let env = rows(&["0", "1"]);
2264 let mut ir = b.finish(acc);
2265 assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::HoldsStrongly);
2266 ir.root = rej;
2267 assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::Fails);
2268 // x true at tick 0 with the abort too: reject_on still fails (the abort takes
2269 // precedence), accept_on holds.
2270 let env = rows(&["1", "1"]);
2271 assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::Fails);
2272 // nested accept_on(a) reject_on(b) p1: a and b at the same tick -> succeeds;
2273 // b before a -> fails.
2274 let mut b = B::new();
2275 // p1 = strong(x ##5 x) is still being evaluated through all three ticks.
2276 let r = b.seq(&[0, 0], &[5]);
2277 let p1 = b.prop(Prop::Seq {
2278 seq: r,
2279 strength: Some(Strength::Strong),
2280 });
2281 let inner = b.prop(Prop::Abort {
2282 cond: LeafId(2),
2283 p: p1,
2284 mode: AbortMode::SyncReject,
2285 });
2286 let outer = b.prop(Prop::Abort {
2287 cond: LeafId(1),
2288 p: inner,
2289 mode: AbortMode::SyncAccept,
2290 });
2291 let ir = b.finish(outer);
2292 // leaves: 0 = x (always true), 1 = a, 2 = b
2293 let same = rows(&["111", "001", "001"]);
2294 assert_eq!(verdict(&ir, &same, 3, 0, false), Verdict::HoldsStrongly);
2295 let b_first = rows(&["111", "001", "010"]);
2296 assert_eq!(verdict(&ir, &b_first, 3, 0, false), Verdict::Fails);
2297 // a before b: the outer abort already accepted.
2298 let a_first = rows(&["111", "010", "001"]);
2299 assert_eq!(verdict(&ir, &a_first, 3, 0, false), Verdict::HoldsStrongly);
2300 }
2301
2302 /// 16.12.14: `async` aborts also see conditions between ticks; the sync forms do
2303 /// not. The three formulations of the LRM example agree on tick-aligned traces,
2304 /// and only the async form reacts to a pulse between ticks.
2305 #[test]
2306 fn async_abort_sees_between_ticks_and_sync_does_not() {
2307 struct Between(Rows, Vec<u32>);
2308 impl PropEnv for Between {
2309 fn value(&self, l: LeafId, t: u32, f: &[Option<i64>]) -> Result<i64, TraceError> {
2310 self.0.value(l, t, f)
2311 }
2312 fn case_match(
2313 &self,
2314 s: LeafId,
2315 l: LeafId,
2316 t: u32,
2317 f: &[Option<i64>],
2318 ) -> Result<bool, TraceError> {
2319 self.0.case_match(s, l, t, f)
2320 }
2321 fn between(&self, leaf: LeafId, after: u32) -> Result<bool, TraceError> {
2322 Ok(leaf == LeafId(1) && self.1.contains(&after))
2323 }
2324 }
2325 let build = |mode| {
2326 let mut b = B::new();
2327 let r = b.seq(&[0, 2], &[1]);
2328 let p = b.prop(Prop::Seq {
2329 seq: r,
2330 strength: Some(Strength::Strong),
2331 });
2332 let r = b.prop(Prop::Abort {
2333 cond: LeafId(1),
2334 p,
2335 mode,
2336 });
2337 b.finish(r)
2338 };
2339 // x ##1 y with y never true; the abort pulses between tick 0 and tick 1 only.
2340 let fails = Between(rows(&["10", "00", "00"]), vec![0]);
2341 let ev =
2342 |ir: &PropertyIr, env: &Between| Evaluator::new(ir, env, false).verdict(2, 0).unwrap();
2343 // accept_on takes the pulse (the cut before tick 1 leaves `y` to the T tail);
2344 // sync_accept_on never samples it and `y` fails.
2345 assert_eq!(
2346 ev(&build(AbortMode::Accept), &fails),
2347 Verdict::HoldsStrongly
2348 );
2349 assert_eq!(ev(&build(AbortMode::SyncAccept), &fails), Verdict::Fails);
2350 // x ##1 y holds; the same pulse rejects it asynchronously, not synchronously.
2351 let holds = Between(rows(&["10", "00", "01"]), vec![0]);
2352 assert_eq!(ev(&build(AbortMode::Reject), &holds), Verdict::Fails);
2353 assert_eq!(
2354 ev(&build(AbortMode::SyncReject), &holds),
2355 Verdict::HoldsStrongly
2356 );
2357 }
2358
2359 /// `if`/`case` on the two-valued letters: `⊤` takes the first arm, `⊥` the default.
2360 #[test]
2361 fn if_and_case_follow_the_condition() {
2362 let mut b = B::new();
2363 let t = b.b(1, Some(Strength::Strong));
2364 let e = b.b(2, Some(Strength::Strong));
2365 let i = b.prop(Prop::If {
2366 cond: LeafId(0),
2367 then: t,
2368 els: Some(e),
2369 });
2370 let ir = b.finish(i);
2371 assert_eq!(
2372 verdict(&ir, &rows(&["1", "1", "0"]), 1, 0, false),
2373 Verdict::HoldsStrongly
2374 );
2375 assert_eq!(
2376 verdict(&ir, &rows(&["0", "1", "0"]), 1, 0, false),
2377 Verdict::Fails
2378 );
2379 // case (l0) l1: p1; l2: p2; default: p3
2380 let mut b = B::new();
2381 let p1 = b.b(3, Some(Strength::Strong));
2382 let p2 = b.b(4, Some(Strength::Strong));
2383 let p3 = b.b(5, Some(Strength::Strong));
2384 let c = b.prop(Prop::Case {
2385 selector: LeafId(0),
2386 arms: vec![
2387 CaseArm {
2388 labels: vec![LeafId(1)],
2389 body: p1,
2390 },
2391 CaseArm {
2392 labels: vec![LeafId(2)],
2393 body: p2,
2394 },
2395 ],
2396 default: Some(p3),
2397 });
2398 let ir = b.finish(c);
2399 // selector equals label 2 only: p2.
2400 let env = rows(&["1", "0", "1", "0", "1", "0"]);
2401 assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::HoldsStrongly);
2402 // equals none: default p3 (selector 1, labels 0 and 0).
2403 let env = rows(&["1", "0", "0", "1", "1", "0"]);
2404 assert_eq!(verdict(&ir, &env, 1, 0, false), Verdict::Fails);
2405 }
2406
2407 // ---- multiclock (16.13; sva T28) -------------------------------------------
2408
2409 /// Leaf rows over the global steps of a trace, plus the tick lists of its
2410 /// clocks: `ClockId(c)` ticks at the steps where `ticks[c]` has a `1`.
2411 struct Multi {
2412 rows: Rows,
2413 timeline: Timeline,
2414 }
2415
2416 impl PropEnv for Multi {
2417 fn value(&self, leaf: LeafId, tick: u32, f: &[Option<i64>]) -> Result<i64, TraceError> {
2418 self.rows.value(leaf, tick, f)
2419 }
2420 fn case_match(
2421 &self,
2422 selector: LeafId,
2423 label: LeafId,
2424 tick: u32,
2425 f: &[Option<i64>],
2426 ) -> Result<bool, TraceError> {
2427 self.rows.case_match(selector, label, tick, f)
2428 }
2429 fn timeline(&self) -> Option<&Timeline> {
2430 Some(&self.timeline)
2431 }
2432 }
2433
2434 fn multi(leaves: &[&str], clocks: &[&str]) -> Multi {
2435 let bits: Vec<Vec<bool>> = clocks
2436 .iter()
2437 .map(|s| s.chars().map(|c| c == '1').collect())
2438 .collect();
2439 let refs: Vec<&[bool]> = bits.iter().map(Vec::as_slice).collect();
2440 Multi {
2441 rows: rows(leaves),
2442 timeline: Timeline::from_rows(&refs),
2443 }
2444 }
2445
2446 fn multi_verdict(ir: &PropertyIr, env: &Multi) -> Verdict {
2447 Evaluator::new(ir, env, false)
2448 .verdict(env.timeline.real(), 0)
2449 .unwrap()
2450 }
2451
2452 impl B {
2453 /// The one-letter sequence `leaf` on `clock`.
2454 fn leaf_on(&mut self, l: u32, clock: u32) -> SeqRef {
2455 let mut s = self.leaf(l);
2456 s.clock = ClockId(clock);
2457 s.end_clock = ClockId(clock);
2458 s
2459 }
2460
2461 /// `l0 ##d @(clock to) l1`: the cross-clock synchronisation of 16.13.1.
2462 fn seq_sync(&mut self, l0: u32, l1: u32, to: u32, strict: bool) -> SeqRef {
2463 let entry = self.au.new_state();
2464 let s1 = self.au.new_state();
2465 let s2 = self.au.new_state();
2466 let s3 = self.au.new_state();
2467 self.au
2468 .add_edge(entry, Some(LeafId(l0)), Transition::Epsilon, s1);
2469 self.au.add_edge(
2470 s1,
2471 None,
2472 Transition::ClockSync {
2473 to: ClockId(to),
2474 strict,
2475 },
2476 s2,
2477 );
2478 self.au
2479 .add_edge(s2, Some(LeafId(l1)), Transition::Epsilon, s3);
2480 self.au.add_edge(
2481 s3,
2482 None,
2483 Transition::Match {
2484 actions: Vec::new(),
2485 },
2486 s3,
2487 );
2488 SeqRef {
2489 entry,
2490 admits_empty: false,
2491 clock: ClockId(0),
2492 end_clock: ClockId(to),
2493 }
2494 }
2495 }
2496
2497 /// 16.13.2: `@(clk) a |-> @(clk1) b` (clk = ClockId 0, clk1 = ClockId 1). The
2498 /// consequent starts at the nearest tick of clk1 at or after the end of the
2499 /// antecedent.
2500 #[test]
2501 fn overlapped_implication_awaits_the_nearest_tick_of_the_consequent_clock() {
2502 let mut b = B::new();
2503 let a = b.leaf_on(0, 0);
2504 let cons = b.leaf_on(1, 1);
2505 let c = b.prop(Prop::Seq {
2506 seq: cons,
2507 strength: Some(Strength::Strong),
2508 });
2509 let c = b.prop(Prop::Clocked {
2510 clock: ClockId(1),
2511 p: c,
2512 });
2513 let root = b.prop(Prop::Implication {
2514 ante: a,
2515 cons: c,
2516 overlapped: true,
2517 });
2518 let ir = b.finish(root);
2519 // clk ticks at 0 and 2, clk1 at 1 and 3; `a` holds at 0.
2520 let clocks = ["1010", "0101"];
2521 // b at clk1's first tick (step 1): holds.
2522 let env = multi(&["1000", "0100"], &clocks);
2523 assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
2524 // b only at clk1's second tick (step 3): the consequent is checked at
2525 // step 1 and fails.
2526 let env = multi(&["1000", "0001"], &clocks);
2527 assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
2528 // b true at a step that is no tick of clk1 does not count.
2529 let env = multi(&["1000", "1010"], &clocks);
2530 assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
2531 }
2532
2533 /// 16.13.2: `|=>` moves to the nearest STRICTLY LATER tick of the consequent's
2534 /// clock. Annex F's rewrite (`ante ##1 1 |-> cons`) advances one tick of the
2535 /// ANTECEDENT's clock first; the two differ when a clk1 tick falls between the
2536 /// end of the antecedent and the next clk tick, and the prose of 16.13.2 is the
2537 /// reading pinned here: `b` false at step 1 (the strictly later clk1 tick) fails
2538 /// even though it holds at step 2, which the rewrite would have checked.
2539 #[test]
2540 fn non_overlapped_implication_moves_to_the_strictly_later_consequent_tick() {
2541 let mut b = B::new();
2542 let a = b.leaf_on(0, 0);
2543 let cons = b.leaf_on(1, 1);
2544 let c = b.prop(Prop::Seq {
2545 seq: cons,
2546 strength: Some(Strength::Strong),
2547 });
2548 let c = b.prop(Prop::Clocked {
2549 clock: ClockId(1),
2550 p: c,
2551 });
2552 let root = b.prop(Prop::Implication {
2553 ante: a,
2554 cons: c,
2555 overlapped: false,
2556 });
2557 let ir = b.finish(root);
2558 // clk ticks at 0 and 2, clk1 at every step.
2559 let clocks = ["1010", "1111"];
2560 let env = multi(&["1000", "0100"], &clocks);
2561 assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
2562 let env = multi(&["1000", "0010"], &clocks);
2563 assert_eq!(
2564 multi_verdict(&ir, &env),
2565 Verdict::Fails,
2566 "the consequent is checked at step 1, the next clk1 tick after the antecedent"
2567 );
2568 // clk1's tick at the end point itself (step 0) is not "strictly later":
2569 let env = multi(&["1000", "1000"], &clocks);
2570 assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
2571 }
2572
2573 /// 16.13.1: `a ##1 @(clk1) b` waits for the first tick of clk1 strictly after
2574 /// the tick `a` matched at; `##0` allows the same step.
2575 #[test]
2576 fn cross_clock_delay_synchronises_to_the_other_clock() {
2577 let clocks = ["10100", "01011"];
2578 let mut b = B::new();
2579 let s = b.seq_sync(0, 1, 1, true);
2580 let root = b.prop(Prop::Seq {
2581 seq: s,
2582 strength: Some(Strength::Strong),
2583 });
2584 let ir = b.finish(root);
2585 // `a` at step 0; clk1's first tick strictly after is step 1.
2586 let env = multi(&["10000", "01000"], &clocks);
2587 assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
2588 // `b` at step 3 (a later clk1 tick) does not match: the sync is to the
2589 // NEAREST tick.
2590 let env = multi(&["10000", "00010"], &clocks);
2591 assert_eq!(multi_verdict(&ir, &env), Verdict::Fails);
2592 // A non-strict (`##0`) sync with both clocks ticking at step 0 stays there.
2593 let clocks = ["1010", "1101"];
2594 let mut b = B::new();
2595 let s = b.seq_sync(0, 1, 1, false);
2596 let root = b.prop(Prop::Seq {
2597 seq: s,
2598 strength: Some(Strength::Strong),
2599 });
2600 let ir = b.finish(root);
2601 let env = multi(&["1000", "1000"], &clocks);
2602 assert_eq!(multi_verdict(&ir, &env), Verdict::HoldsStrongly);
2603 }
2604
2605 mod calls;
2606 mod incremental;
2607 mod nonvacuity;
2608}