Expand description
ANALYSIS (vj-sim::analysis) — the WOCB static-analysis pass’s
hand-off surface (netlist-analysis.md §Key Data Structures —
partitioning, CDC edges, DPI/VPI tagging, and the ForceObserved
runtime-invalidation hook ARCHITECTURE §4/§9 requires), plus (as of
Task 8) the real, sv-lang-Analysis-backed driver graph, WOCB
partitioning and clock-domain/CDC-edge inference: crate::analysis:: analyze(). See docs/superpowers/plans/2026-09-23-wave-A-netlist.md
Task 8.
Structs§
- Analysis
Options - Task 8 (and harness Task 18 / VF-116, whose duplicate definition was folded into
this one at the harness merge): the knobs
analyze()takes.Defaultis the fail-closed configuration ARCHITECTURE §6.5 requires verbatim: a CDC classification belowcdc_confidence_floormust degrade to a partition merge rather than stay a directed edge, and an unclassifiable DPI/VPI call defaults to the most conservative tag (Barrier), neverPure. - Call
Site Id - CdcEdge
- Clock
Domain - Task 8: a real, physical clock signal —
edge-triggered — and the denseClockDomainIdevery process whoseStaticSensitivity::Explicitresolves to the SAMEclock_signal(byInstantiatedSignalIdequality, NL-076) shares. - Clock
Domain Id - DpiTag
Stats - One aggregate metric per test corpus: the regression-monitored
Pure/DeferredSafe vs Barrier(/SpeculationUnsafe) ratio NL-103 requires.
Not part of
super::StaticAnalysisView(that trait is per-call-sitedpi_tag); this is a harness-facing aggregation a corpus runner (out of this wave — theharnesstrack’s own job, per the plan) calls once per test corpus. - Driver
Graph - The
{net,process}driver-dependency graph (NL-063):write_edges()is(process) -write-> (net)for every real (non-input-port-boundary)DriverInfo;read_edges()is(net) -read-> (process)for everyAnalyzedProcedure::read_set()entry whose net has at least one internal (non-input-port) driver somewhere in this graph — a pure top-level input has no driver of its own to synchronize with, so a read of it is never a partition-membership-relevant dependency (seebuild_driver_graph’s own doc comment for the fixture that pins this exact filter). - Partition
- One WOCB partition: a set of
{net,process}graph nodes that must be evaluated together (netlist-analysis.md’sPartition).clock_domainisNonefor a purely combinational/mixed region. - Region
Id - Static
Analysis Result - Task 8: the real
analyze()hand-off value —partitions/clock_domains/cdc_edgespopulated by the driver graph + WOCB partitioning + clock-domain/CDC-edge inference this task implements;on_force_observedstays on its safe placeholder body (out of this wave). Frontend-only (CONTRACTS D4):partitions: Vec<Partition>names a type that only exists under this crate’sfrontendfeature (Partitionlives in thesv_lang::Analysis-consuminggraphmodule).
Enums§
- Analysis
Error - Task 8: every
analyze()failure. Kept even though this task’s own implementation never constructs one (every fallible step it performs — driver/read-set resolution — degrades to a documented skip rather than an error, peranalysis::graph’s own doc comments): the type is part of the plan’s binding public signature (analyze() -> Result<.., AnalysisError>) and a future wave’s DPI/VPI classification pass (Task 9) or dataflow-dependent Implicit-sensitivity derivation is the natural place anErrfirst becomes reachable. - CdcClassification
- A CROSS-region edge, never a merge (netlist-analysis.md §C / ARCHITECTURE §4
item 4; ledger NL-059, NL-069, NL-077, NL-078, NL-084, NL-085): a net multiply
driven across provisional partitions forces a union instead — only a directed
read dependency on an already-resolved value from another partition is
eligible to stay a
CdcEdge. - CdcConfidence
- Fail-closed by default:
Unprovenbelowcdc_confidence_floordegrades to a partition merge, never stays an edge (netlist-analysis.md §C). - DpiVpi
Tag - Fail-closed DPI/VPI call tagging (netlist-analysis.md Decision 6/7; ledger
CG-105, ST-149, NL-092, NL-095, NL-100, DT-045): unprovable always defaults to
Barrier, neverPure. - Force
Observed Action - The
on_force_observedruntime-checked escape hatch (ARCHITECTURE §4/§9, netlist-analysis.md §G / DECISION_RECORD_CONFLICTS #1; ledger ST-121, ST-122, NL-106, NL-108, NL-110, DT-047, RT-159):force/release/vpi_put_valueon a region-crossing net must call this so the engine can detect and fall back from an invalidated independence assumption instead of trusting a stale one. Not a static proof — a runtime-checked escape hatch, because any net is in general VPI-addressable regardless of what elaboration could prove.
Traits§
- Static
Analysis View - The
analyze()hand-off surface (netlist-analysis.md’simpl StaticAnalysisResult; ledger NL-005, NL-006, NL-009, NL-053, NL-085, NL-106), reframed as a trait for the same reason ascrate::netlist::NetlistView.
Functions§
- analyze
- Task 8 (NL-007, NL-063, NL-076, NL-082): the real static-analysis pass —
backfills
SignalDecl.driver_setontonetlist(graph:: populate_driver_kinds, NL-063’s classification clause — the one mutation this whole pipeline performs on an already-elaboratedNetlist, sincesv_lang::Analysisdoes not exist until afterelaborate()returns), builds the{net,process}driver graph (graph::build_driver_graph), partitions it (graph::partition), classifies every un-unioned cross-domain read edge as aCdcEdge(clock::classify_cdc_edges), and (Task 9, NL-103) fail-closed- classifies every DPI/VPI call site (dpi::collect_call_tags). - build_
driver_ graph - Second load-bearing finding, TDD’d against real
sv-langoutput (verified this task):sv_lang::Analysis(slang::analysis:: AnalysisManager) tracks driver/procedure data per CANONICAL instance only — confirmed empirically against a fixture with two instances (i0,i1) of one shared module:slang_analysis.procedures(i0_body) .count() == 3(i0 happened to be canonical) whileslang_analysis.procedures(i1_body).count() == 0, even though Task 2’s ownDisableInstanceCachingfinding already establishedi0’s andi1’s own AST bodies are genuinely distinctSymbols (never pointer- shared). This isAnalysisManager.h’s own documented behavior (“driver state tracked PER CANONICAL INSTANCE”) — a SEPARATE, analysis-layer-only canonicalization fromDisableInstanceCaching‘s elaboration-layer pointer-identity guarantee. Consequence:Analysis::drivers/Analysis::procedurescan only ever be queried against ONE representative instance per dedupedTemplate— never a genuinely per-instance call — so this function computes the write/read STRUCTURAL PATTERN (as(SymbolKey, SymbolKey)pairs) once perTemplate, against its own canonical instance (found by [representative_instance_of], which — unlikepopulate_driver_kinds’ “any instance is correct, Decision 4 guarantees identical shape” reasoning — must specifically locate the one instanceAnalysisactually populated data for, not merely the lowestInstanceId), then FANS that pattern OUT onto every instance sharing theTemplate, using each instance’s ownInstanceId. Clock-domain seeding stays genuinely per-instance regardless (NL-076: it never callsAnalysisat all — it readsTemplate.processes, already derived directly from rawsv_langinelaborate(), soi0’s andi1’s own distinctInstantiatedSignalIds for theirclkport already differ correctly with no canonicalization pitfall). - dpi_
tag_ stats - NL-103’s own ACC, made concrete: the regression-monitored ratio metric
over every call site
analyze()tagged. - infer_
procedural_ clock - The procedure’s effective inferred clock: LRM 16.14.6 (a)/(b)/(c) when
those three conditions are jointly satisfied, otherwise LRM 16.15’s
default-clocking fallback when a default clocking block applies to
proc’s scope, otherwiseOk(None). See the module doc’s “Correction” section for why these two sources cannot be told apart from this return value, and why this module cannot change that given sv-lang 0.1.9’s exposed surface. - infer_
structural_ clock - LRM 16.14.6 (a)/(b)/(c) for a procedure that slang’s analysis does not list:
one inside a procedural checker instance’s body (17.3). slang analyzes such
a body as a side effect of analyzing the enclosing procedure and files no scope
result for it, so
infer_procedural_clockhas noAnalyzedProcedureto ask (and anAnalyzedAssertion::procedurehandle into that scope is not safe to follow: slang keeps the procedures in astd::vectorand the assertions point into it before it has finished growing). This is therefore the one place the three conditions are decided here rather than by slang, mirroringDFAResults::inferClockfor the shapes a checker’s procedure can take: - partition
- The WOCB partitioning pass (Decision-record union-find): every write
edge always unions process with net (LRM multi-driver atomicity falls
out for free — two processes that both write one net both union with
it, hence transitively with each other, with no special-casing). A read
edge unions net with reader UNLESS both sides already carry a resolved,
DIFFERING clock domain — a genuine cross-domain register-to-register
read, which [
crate::analysis::clock::classify_cdc_edges] turns into aCdcEdgeinstead (never a merge, per NL-082). Two same-domain clocked nodes (NL-076’s fixture) also do NOT union on a read alone — finer partition granularity, still correct (each register independently reads old values within one clock domain) and not required to merge by anything this pass’s tests assert. Iterated to a fixed point (a merge can turn a group’s domain fromNonetoSome, unblocking a previously-skipped union later in the same or a later pass), bounded bymax_passes.