Skip to main content

SuspendPoint

Enum SuspendPoint 

Source
pub enum SuspendPoint<'d> {
    Delay(Expression<'d>, Option<Symbol<'d>>),
    Edge {
        signal: Expression<'d>,
        edge: EdgeKind,
        iff: Option<Expression<'d>>,
    },
    EdgeList(Vec<(Expression<'d>, EdgeKind)>, Vec<Vec<Expression<'d>>>),
    Level(Expression<'d>),
    CycleDelay {
        count: u32,
        clock: Symbol<'d>,
        edge: EdgeKind,
        via: Option<Expression<'d>>,
    },
    ImplicitComb(Vec<Expression<'d>>),
    OneStep,
    WaitFork,
    ByRequest,
}
Expand description

The suspend kinds this file implements. Delay/Edge are PR-006’s slice-scope half (Task S3a); EdgeList/Level/CycleDelay are Task 1’s addition (PR-006’s Part 2 half). ImplicitComb remains unimplemented – see the module doc comment’s PR-006 accounting.

Variants§

§

Delay(Expression<'d>, Option<Symbol<'d>>)

#<delay>. The second field is the inlined subroutine whose body holds the delay, when one does: a delay’s time unit is the unit of the scope that DECLARES it (LRM 3.14.2.1), not the caller’s.

§

Edge

Fields

§signal: Expression<'d>
§iff: Option<Expression<'d>>

SignalEventControl.iffCondition (LRM @(posedge clk iff en), Task 3/CG-170): None when no iff qualifier is present. The kernel’s resume prologue for a suspend point carrying Some(_) ALWAYS re-evaluates it on wake and, if false, immediately re-returns Suspended with the identical WaitSpec unchanged – it does not advance the PC and does not re-run the segment body (docs/design/process.md §2’s own “iff re-check” paragraph; compile_iff_guard’s doc comment in vj-codegen:: process_lower is the codegen half of this same contract).

§

EdgeList(Vec<(Expression<'d>, EdgeKind)>, Vec<Vec<Expression<'d>>>)

EventListControl (@(a or b or c)): wake on any member. Deviation from docs/design/process.md §2’s literal Vec<{edge,signal,iff}> triple – Task 1’s own “Public names/types introduced” text specifies the narrower (Expression, EdgeKind) pair with no iff (matching how Level’s own iff/re-check contract is deferred to Task 3); this is the more specific, more recent spec and is followed exactly.

The second field holds each member’s iff guard (LRM 1800-2023 9.4.2.3: @(posedge a iff c or b)): always the same length as the member list, empty for an unguarded member. The list wakes on a member’s event only if every one of that member’s guards holds then (more than one when an iff meets an $inferred_clock whose own clocking event has an iff, a conjunction: LRM 17.4, 9.4.2.3).

§

Level(Expression<'d>)

wait(expr) (LRM 9.4.3, level-sensitive). Task 3: [CfgBuilder:: walk_wait] wires this as the else_blk of a Terminator::Branch on expr itself (never unconditionally) – “evaluate immediately, suspend only if false” (docs/design/process.md §2’s own “wait(expr) special case”, CG-169). The per-wake re-check (re-suspending unchanged on a spurious wake, CG-115) is entirely codegen’s job (vj-codegen::process_lower::compile_iff_guard’s resume prologue) – this crate never evaluates expr.

§

CycleDelay

##N (clocking-block-relative cycle delay) – Task 6 (CG-171) revision: no longer a bare cycle count. ARCHITECTURE §6.5 represents CycleDelayControl as “kept as an unresolved expression + ClockDomain for the scheduler to convert cycles->ticks at runtime”, i.e. codegen must never precompute an absolute tick offset (a clock’s period can change dynamically – see sched::mod::EventTerm::Repeat’s own SC-053 doc comment for the differentiating behavior this rules out). This variant carries count (the compile-time-constant cycle count – still restricted the same way ##N always was, see [CfgBuilder::walk_timed]’s CycleDelay arm) plus the syntactically inferred single clock this process suspends against, clock/ edge (or, with a default clocking block in scope, that block itself with BothEdges: ##N counts its events) – the SAME inferred_clock this file already tracked before this task (previously computed then silently discarded once ambiguity-checked; now threaded through as real payload). Resolving clock to a concrete StateCellId still happens at codegen time (vj-codegen::process_lower::resolve_suspend_point, exactly like Edge’s own signal) – what does NOT happen at codegen time is converting count cycles into an absolute tick number: that stays a live, per-edge EventTerm::Repeat{count, inner: Edge{cell, edge}} wait the SCHEDULER counts down at runtime (already built and tested, SC-052/SC-053), so a clock whose period later changes still produces the correct absolute timing by construction, with no cached tick offset to go stale.

Deviation from the plan’s literal analysis::ClockDomainId-carrying wire design, and a real, verified SCOPE LIMIT (both recorded in full in this task’s deviations – not silently narrowed): the plan’s own text describes resolving clock against a live sv-lang- Analysis-backed WOCB partition result (analysis:: StaticAnalysisResult::clock_domains()) threaded all the way from vj::run’s elaboration pipeline through to the scheduler’s Engine. That plumbing does not exist anywhere in this tree yet (analysis::analyze() is never called from run.rs/aot.rs/ pipeline.rs today), and building it is a genuinely separate, run.rs-touching undertaking this task’s own binding rules forbid risking (wave-B-runtime is concurrently landing in that exact file). This variant instead reuses the clock inference this crate ALREADY performed for every ##N before this task (the same inferred_clock ambiguity check, now threaded through as real payload instead of discarded) – which is genuinely narrower than “the process’s own inferred clock domain,” not merely a smaller-but- equivalent instance of it, and this narrowing is load-bearing, not cosmetic: it resolves ##N written inside a process that ALSO itself syntactically sights a posedge/negedge edge somewhere earlier (the common always_ff @(posedge clk) ... ##N ... idiom), but does NOT resolve a ##N relative to a default clocking/named clocking cb @(posedge clk); endclocking block referenced from a process with NO local edge sighting of its own – verified directly (a throwaway test compiling ARCHITECTURE §6.5’s own canonical clocking-block fixture, default clocking cb; ... initial ##3 x=1;, with no @(posedge clk) anywhere in that second initial block): walk_timed’s CycleDelay arm returns the SAME “no unambiguous single inferred clock in scope” LoweringError for it that a genuinely unclocked ##N gets, because default clocking/ ClockingBlock declarations are never read by this mechanism at all. Reading them is also a session.rs-owned, pre-existing, verified-separately-blocked gap regardless (vj-codegen:: session.rs’s own structural-symbol classification already places SymbolKind::ClockingBlock in its “no correct lowering exists yet” bucket – a design containing one fails upstream of this crate entirely), so closing it is not a smaller extension of this task’s own mechanism, it needs a different, cross-track-blocked one. CG-171 therefore closes for real, common ##N usage (a clock edge syntactically visible in the SAME process) but stays open for the default clocking-bound shape – see this task’s deviations for the full accounting, and crates/vj-harness/tests/ slice_vs_iverilog.rs::clocking_cycle_delay_matches_iverilog_bit_for_bit for the still-#[ignore]d e2e proof (updated by this task to name this exact, narrower residual, not the stale “CG-171 unimplemented” reason it carried before). See sched::reschedule_req_proposal:: KIND_REPEAT_EDGE’s own doc comment for the wire shape the resolved (local-edge) case resolves into – a single-cell, single-edge repeat wait that fits the frozen 24-byte budget inline (no out-of-line table needed, unlike EdgeList/ImplicitComb).

Verified, separately-recorded finding (not this task’s bug to fix, but load-bearing for why no NEW oracle-diffed e2e fixture proves this closure end-to-end): ##N outside a default clocking context is not accepted as valid SystemVerilog by either reference tool this crate uses – iverilog -g2012 rejects a bare ##2 inside an ordinary always_ff body with a flat parser “syntax error”, and verilator --binary -sv rejects the identical construct with “Usage of cycle delays requires default clocking (IEEE 1800-2023 14.11)” (both verified directly, by hand, against a throwaway fixture). This mechanism’s “syntactically inferred single clock” acceptance criterion PRE-DATES this task (an earlier task’s own inferred_clock field, which this task only threads through as real payload instead of discarding) – this task neither introduces nor revisits that acceptance decision, only documents, empirically, that no available reference oracle can differentially validate it end to end: the codegen-level JIT+run round trip (crates/vj-codegen/ tests/frame_roundtrip_matrix.rs) is therefore the strongest available proof of this specific closure, not an iverilog/Verilator diff.

Fields

§count: u32
§clock: Symbol<'d>

The clock: a signal (the process’s inferred clock, counted on edge), or the default clocking block (its event counter, counted on any change).

§via: Option<Expression<'d>>

A clocking drive THROUGH a virtual interface (v.sb.c <= ##1 1, LRM 25.9.1): the clock variable’s member access. clock is then the template clocking block, which has no signal; the run time counts the events of the clocking block of the instance v holds.

§

ImplicitComb(Vec<Expression<'d>>)

Task 6 (CG-081): ImplicitEventControl (@*/@(*)). Fieldless per docs/design/process.md §2’s own SuspendPoint sketch would suggest (WaitSpec::ImplicitComb(SubscriptionSetId) is resolved against a separate table, not embedded read-set data) – deviates by carrying the guarded body’s own AST-derived read-set directly (Vec<Expression<'d>>, computed by [implicit_comb_read_set]) rather than being truly fieldless, because this crate’s build_cfg has no live sv_lang::Analysis/AnalyzedProcedure handle to later re-derive it from at codegen time (see [implicit_comb_read_set]’s own doc comment for the full reasoning) – the read-set has to be captured HERE, while the guarded body is in hand, or nowhere in this crate.

§

OneStep

Task 6 (CG-081): OneStepDelayControl (the LRM’s built-in 1step delay, one global-precision tick, SC-123). Fieldless – the AST node itself carries no expression (confirmed against the vendored slang C++: OneStepDelayControl declares no field and no visitExprs override at all, so TimingControl::children() is always empty for this kind). Resolved at codegen time to Suspend::Delay(1) (LRM 3.14.3: a step is the global time precision) – see vj-codegen::process_lower::resolve_suspend_point’s OneStep arm.

§

WaitFork

wait fork; (LRM 9.6.1, SC-125): resume once every child this process forked has finished. Payload-free – the scheduler tracks the children.

§

ByRequest

Classes plan Task 13 (LRM 9.7): p.await(); / p.suspend(); as a statement. The call itself queues a process request; this suspend yields so the scheduler applies it before the next statement runs (Suspend::ByRequest). Payload-free.

Implementations§

Source§

impl<'d> SuspendPoint<'d>

Source

pub fn kind(&self) -> SuspendPointKind

Exhaustive, no-wildcard match (ARCHITECTURE §7 item 9): if a future task adds a new SuspendPoint variant without extending SuspendPointKind and this match, the crate fails to COMPILE, not merely under-tests – a stronger guarantee than suspending_statement_kinds’s own length alone could give.

Trait Implementations§

Source§

impl<'d> Clone for SuspendPoint<'d>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<'d> Debug for SuspendPoint<'d>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'d> !RefUnwindSafe for SuspendPoint<'d>

§

impl<'d> !UnwindSafe for SuspendPoint<'d>

§

impl<'d> Freeze for SuspendPoint<'d>

§

impl<'d> Send for SuspendPoint<'d>

§

impl<'d> Sync for SuspendPoint<'d>

§

impl<'d> Unpin for SuspendPoint<'d>

§

impl<'d> UnsafeUnpin for SuspendPoint<'d>

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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