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
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>
impl<'d> SuspendPoint<'d>
Sourcepub fn kind(&self) -> SuspendPointKind
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.