pub struct SensitivityTable { /* private fields */ }Expand description
The inverted index itself: StateCellId -> SmallVec<SensitivityEntry>
(scheduler.md §4). SmallVec<[_; 4]> per the plan’s literal type —
most cells are read by a small, fixed number of always-blocks, so this
avoids a heap allocation per row in the common case.
Implementations§
Source§impl SensitivityTable
impl SensitivityTable
pub fn new() -> Self
Sourcepub fn install_static(
&mut self,
sensitivity: &StaticSensitivity,
proc: ProcessId,
)
pub fn install_static( &mut self, sensitivity: &StaticSensitivity, proc: ProcessId, )
Installs one SensitivityEntry::Static per (cell, edge) pair in
sensitivity.edges — the exact codegen-supplied read-set -> table-row
translation (SC-045/SC-046). Called exactly once per process, at
registration time; the production call site (Engine::register_process,
gated on sensitivity.is_some()) is Task 18’s, not this module’s —
this method itself has no idempotence guard because nothing here can
tell “first call” from “second call” for the same process, so that
invariant is the caller’s obligation (proved by Task 18’s dedicated
production-call-site test, per this task’s own plan text).
Sourcepub fn narrow_static(
&mut self,
proc: ProcessId,
cell: StateCellId,
windows: &[(u32, u32)],
)
pub fn narrow_static( &mut self, proc: ProcessId, cell: StateCellId, windows: &[(u32, u32)], )
Narrows proc’s installed Static entries on cell to the bit
windows (lsb, width) it actually reads (LRM 9.2.2.2.1): each entry
becomes one per window, matching only an update that changed that
window. A no-op for an empty windows; the per-process dedup gate
makes several matching windows of one update wake the process once.
Sourcepub fn install_dynamic(&mut self, wait: &EventWaitSet, proc: ProcessId)
pub fn install_dynamic(&mut self, wait: &EventWaitSet, proc: ProcessId)
Installs one SensitivityEntry::Dynamic per (cell, term) pair a
suspended wait(...)/@(...) implies, keyed by that term’s index in
wait.terms (the same index ResumeArg::EventFired { term_index }
reports back on the firing wakeup). Recurses through
EventTerm::Repeat to its inner term, keeping the outer Repeat
term’s index — the repeat count is resume-orchestration state (Task
12/13’s concern: how many times the inner event must fire before the
process actually resumes), not something the sensitivity table itself
tracks or needs to.
Sourcepub fn remove_dynamic(&mut self, proc: ProcessId)
pub fn remove_dynamic(&mut self, proc: ProcessId)
Removes every Dynamic entry belonging to proc, from every cell —
“removed the instant the owning process resumes” (scheduler.md §4).
Static entries are untouched: they are installed once at
registration and must survive for the process’s entire lifetime.
Sourcepub fn matches(
&self,
cell: StateCellId,
edge: EdgeKind,
) -> impl Iterator<Item = (ProcessId, usize, Option<u64>, Option<PredicateFn>)> + '_
pub fn matches( &self, cell: StateCellId, edge: EdgeKind, ) -> impl Iterator<Item = (ProcessId, usize, Option<u64>, Option<PredicateFn>)> + '_
Yields every ProcessId whose sensitivity entry on cell matches the
just-committed transition classified as edge.
edge is only ever infer_edge’s classification of bit 0 of the
old/new value (sched/notify.rs’s LRM Table 9-1 scalar posedge/
negedge table) — it is not a “did the whole cell change” signal, and
a multi-bit cell can genuinely change (SignalAccess::write returns
true) while bit 0 itself stays fixed (e.g. Narrow(0b10) -> Narrow(0b110)), in which case infer_edge reports EdgeKind::None
for a real change. Both production callers of this table
(commit_nba/commit_blocking_write, via notify_change /
SensitivityLookupSink) call in only for a write
SignalAccess::write reported as an actual change — so any call
reaching matches at all, regardless of what edge it carries,
represents a real change on cell. A “any change” entry
(always_comb/always_latch’s LRM-implicit read-set sensitivity,
SC-046) must therefore fire on every such call, not just the ones
where infer_edge happened to classify bit 0 as non-None — that is
the only reading consistent with “wake on any read-set change” rather
than “wake only when bit 0 of the read-set changes”.
Matching rule (uniform across Static/Dynamic, unifying Static’s
Option<EdgeKind> to the same EdgeKind sentinel Dynamic already
uses via .unwrap_or(EdgeKind::None)):
- an entry whose edge is
EdgeKind::None(“any change”) matches unconditionally — every call intomatchesis already gated, by its callers, on a real change having happened, so the incomingedgeclassification (which only speaks to bit 0) cannot be used to veto an any-change waiter; - an entry whose edge is
EdgeKind::BothEdges(SV’sedgekeyword,@(edge sig)) matches any real committed transition, i.e. an incomingPosedgeorNegedge—infer_edge(sched/notify.rs) is documented and proved to never itself produceBothEdges, so this is the entry-side wildcard for that keyword, not the incoming-side one; - any other edge-qualified entry matches that exact incoming
edge, or an incomingEdgeKind::BothEdges— kept for forward-compatibility even though no production caller passesBothEdgesas the incoming classification today. An edge-qualified (always_ff) entry is correctly not woken by a bit0-unchanged multi-bit change it wasn’t watching for — only the any-change case needs the incoming-edge-is-imprecise fix above.
Returns, for every entry matching (cell, edge), the tuple (proc, term_index, repeat_total, iff) — term_index and repeat_total are
0/None respectively for a Static entry (which has no term-index
concept and is never Repeat-sourced); a Dynamic entry reports its
own installed term_index/repeat_total (Task 3, ledger SC-052 —
see SensitivityEntry::Dynamic’s own doc comment). Callers that
only need the ProcessId (every caller predating Task 3) destructure
and use only the first element.
Task 5 (ledger SC-068) addition: iff is the fourth element —
always None for a Static entry (that variant carries no iff
field: always_comb/always_latch’s implicit sensitivity has no LRM
iff clause) and a Dynamic entry’s own installed
iff: Option<PredicateFn> otherwise. This method deliberately does
NOT evaluate iff — it has no SimHandle/arena access to call
through — so a match here means only “this entry’s edge/cell
condition fired,” not “the process should wake.” The one production
caller with the access needed to actually invoke a Some(iff)
predicate before deciding to enqueue is sched::control’s
EngineNotifySink::notify_change.
Sourcepub fn waiting_on(&self, cell: StateCellId) -> Vec<ProcessId> ⓘ
pub fn waiting_on(&self, cell: StateCellId) -> Vec<ProcessId> ⓘ
The processes whose wait list holds cell now, first-registered
first, each once: the Dynamic rows of a suspended @/wait and the
Static rows of a procedure’s implicit sensitivity (LRM 1800-2023
37.27 detail 1, “all waiting processes, static or dynamic”). Read-only.
Sourcepub fn has_select(&self, cell: StateCellId) -> bool
pub fn has_select(&self, cell: StateCellId) -> bool
Whether any entry on cell waits on a bit/part select of it, i.e.
whether a committed update needs to carry a ChangeDelta.
Sourcepub fn matches_delta<'a>(
&'a self,
cell: StateCellId,
edge: EdgeKind,
delta: Option<&'a ChangeDelta>,
) -> impl Iterator<Item = (ProcessId, usize, Option<u64>, Option<PredicateFn>)> + 'a
pub fn matches_delta<'a>( &'a self, cell: StateCellId, edge: EdgeKind, delta: Option<&'a ChangeDelta>, ) -> impl Iterator<Item = (ProcessId, usize, Option<u64>, Option<PredicateFn>)> + 'a
Self::matches with the update’s old/new words: a select entry
(EventTerm::Select) matches only if the update changed its bits, and
takes its edge from bit lsb of the transition – not from the cell’s
own bit 0, which edge classifies. With no delta a select entry
falls back to the whole-cell rule (a superset: it can wake for a change
of a bit it does not select, never miss one).
Sourcepub fn entry_count(&self) -> usize
pub fn entry_count(&self) -> usize
NEW (Task 18, wA-sched plan) — the #[cfg(test)]-only production
backing for Engine::test_sensitivity_entry_count (SC-045’s “exactly
once, at registration” proof; see sched::control’s deviations note).
Total entry count across every cell’s row, Static and Dynamic
combined. sched::control (Task 18) is the same sched track that
owns this file, so this is an in-track, additive extension, not a
foreign-contract change; the method itself is not test-gated (a plain
pub fn, cheap and side-effect-free) so no #[cfg(test)] plumbing is
needed here, only at the Engine accessor’s own call site.