Skip to main content

SensitivityTable

Struct SensitivityTable 

Source
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

Source

pub fn new() -> Self

Source

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).

Source

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.

Source

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.

Source

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.

Source

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 into matches is already gated, by its callers, on a real change having happened, so the incoming edge classification (which only speaks to bit 0) cannot be used to veto an any-change waiter;
  • an entry whose edge is EdgeKind::BothEdges (SV’s edge keyword, @(edge sig)) matches any real committed transition, i.e. an incoming Posedge or Negedge — infer_edge (sched/notify.rs) is documented and proved to never itself produce BothEdges, 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 incoming EdgeKind::BothEdges — kept for forward-compatibility even though no production caller passes BothEdges as 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.

Source

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.

Source

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.

Source

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).

Source

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.

Trait Implementations§

Source§

impl Default for SensitivityTable

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V