This document captures planned features that go beyond the existing connectivity / CDC / metrics surface area. Each item is sized as an independent PR (or PR series) and includes the data flow, API surface, and tests required to ship. Items are ordered by expected impact on real-world adoption.
Status legend: proposed (no work started), scoped (interface fixed, work
ready to begin), in-progress, landed.
Cumulative improvements to the structural CDC analyzer that close false-negative classes observed in production OSS RTL (OpenTitan, CVA6/pulp, ZipCPU, BlackParrot). Each item is sized as a discrete finding with paired pos/neg golden fixtures.
{wq2,wq1} <= {wq1,rgray}; was previously over-broadcast, hiding the
real 2-FF synchronizer. Bit-aware positional matching in
src/cdc/ast_utils.cpp::splitAssignmentByLHS recognises the
positional pairing when LHS and RHS concats have matching arity and
operand widths. Fixtures 36-37, validates upstream ZipCPU afifo.v
(0V/0C/2 INFO two_ff).
sync_verifier.cpp::findNextFF and detectPulseSyncPattern no longer
require source_leaf == fanin[0] strict name match (would fail on
port-renamed cross-instance chains). Replaced with FFEdge-trust +
single-source predicate isSingleSourceFF. Fixture 33 transitions
from VIOLATION to INFO, paired neg fixture 38 keeps single-stage
case as VIOLATION.
-D defines and -I include paths flow through the slang
preprocessor (CompilationSession.cpp::expandFilelists). Fixtures
39-40 with .flags sidecars exercise \SYNC_2FF/`SYNC_DIRECT`
macro patterns from VeeR-EH1 / BlackParrot.
OpenTitan prim_flop uses q_o <= ResetValue (parameter) which was
inflating fanin_signals.size() to 2 and silently disabling 2-FF
sync detection. collectReferencedSignals now filters
SymbolKind::Parameter / TypeParameter / EnumValue. OpenTitan
prim_fifo_async: was 2 VIOLATIONS, now 2 INFO two_ff.
When parent ports use unconventional names (ca, cb, tck,
phy_clock) that don't match the *clk* / *clock* heuristic, the
auto-detector previously produced phantom domains for submodule's
clk_i. clock_tree.cpp::isPortUsedAsClock walks generate blocks and
lazily seeds parent ClockSources; autoDetectClockPorts rejects
reset-named ports. Fixture 43-44 paired pos/neg.
u_sub.q reads from a parent module across clock domains were
silently dropped because extractExprSignalName returned only the
leaf name. Now returns getHierarchicalPath(); findFFByName does a
direct dotted-path lookup. Fixtures 45-46.
u_sub.gen_blk[1].q_inner (getHierarchicalPath syntax) vs
genblk1.q_inner (getExternalName flattened form) format
mismatch. findFFByName now tries dual-variant lookup
(no-brackets + flattened) plus a parent-prefix + idx + leaf-suffix
scan with dot-count guard for digit-medial-underscore labels.
Fixtures 47-49 (positive coverage) plus 52 (paired negative for
the digit-medial-underscore label corner).
splitConcatPair recursive helper handles arbitrary-depth nested
concat (was 2-level inline). detectPulseSyncPattern walks bounded
BFS (MAX_DEPTH=6) for delay taps multiple hops downstream of
last_sync. Fixture 50 combines 5 patterns in one design.
--strict, --sync-stages, --ignore-gated all locked with
Catch2 integration tests (test_cdc_runner.cpp). Fixture 51 with
SDC create_generated_clock produces the Severity::Low gated entry
needed for --ignore-gated verification.
After Round 12's O(D*N) → O(D) hash lookup in
ff_classifier.cpp (clock net resolution) and the suffix-scan
length pre-check in connectivity.cpp::findFFByName, analysis
times: ZipCPU afifo <50ms, OpenTitan prim_fifo_async ~11ms,
CVA6 cdc_fifo_gray ~22ms. See tests/cdc/README.md for full
external coverage table.
Why: today CDC analysis emits cdc_constraints.sdc (timing helpers).
Adding cdc_assertions.sva lets simulation verify the same crossings the
static check identified, closing the loop between structural CDC and
dynamic verification.
Output schema: a new artifact cdc_assertions.sva written next to
cdc_report.json. Each crossing produces:
// CDC-001: src_clk -> dst_clk via top.u_sync.q1/q2
property p_cdc_001_2ff_sync;
@(posedge dst_clk) disable iff (!rst_n)
$stable(top.u_sync.q1) || top.u_sync.q1 == $past(top.u_sync.d);
endproperty
a_cdc_001: assert property (p_cdc_001_2ff_sync);For each synchronizer style svlens recognizes, generate the corresponding
property template. Unrecognized crossings emit cover property only.
API:
- New CLI flag
--svawritescdc_assertions.sva. --sva-style {2ff,3ff,handshake,fifo}overrides per-crossing detection.- New JSON field
cdc_report.json:crossings[].sva_assertion_idcross-links the report to the generated assertion identifier.
Files to add / modify:
src/cdc/sva_generator.{h,cpp}-- new module, takesCdcReportand emits SVA file content.src/CdcRunner.cpp-- wire--svainto the writer.tests/test_sva_generator.cpp-- snapshot tests against fixtures intests/cdc/sva/.docs/schema/cdc_report.md-- document the new optional field.
Risk: SVA hierarchical references depend on the full instance path; we
already capture this in the report. Risk is in the synchronizer-pattern
detection getting confused by mixed-style code; the --sva-style override
is the escape hatch.
Why: README currently flags interface/modport handling as "partial"
under the Current implementation limits / Connectivity mode section
of README.md. Real SoC designs lean heavily on bus interfaces, so this
is the largest gap blocking adoption.
Approach: extend ConnectionExtractor to walk
InterfaceInstanceSymbol and ModportSymbol from slang's AST, producing
per-modport-signal edges instead of opaque interface ports.
Files to modify:
src/ConnectionExtractor.cpp-- new visitor for interface symbols.src/InterfaceGrouper.cpp-- already exists; deepen to consume the new per-signal edges and group them back at report time.tests/sv/conn/interfaces/*.sv-- new fixtures: AXI-lite-style modport, multi-modport interface, parameterized interface array.
Risk: parameterized interfaces (parametric interface arrays) need careful handling; start with non-parametric, gate parametric behind a flag.
Three sub-features, each independently shippable:
Add fanout field per FF-D-rooted cone in metrics_report.json. Already
trivial given TransformExtractor tracks reverse edges; needs a forward
edge map per signal node.
Sum the operator complexity (multipliers, comparators, muxes weighted)
into a single estimated_gate_count field. Calibration against a small
synthesis run should land alongside.
Codec / video designs have specific structural patterns (line buffers,
DPB-style FF arrays, fixed-point arithmetic chains). Add detection
heuristics that classify a cone as lineBuffer, dpbAccess, or
fixedPointArith and surface counts per top-level module.
Risk: D3c is heuristic-heavy; gate behind --metrics-domain video
to avoid false positives in non-video designs.
Why: every team wants different convention rules. Today
ConventionChecker is hard-coded; allow YAML-defined rules so the
toolchain can be customized without forking.
Schema sketch (checkers.yaml):
checkers:
- id: USR-001
description: "Module names must end with _ip"
target: module
pattern: ".*_ip$"
severity: warning
- id: USR-002
description: "Signals named tmp_* are forbidden in production"
target: signal
forbidden: "tmp_.*"
severity: errorFiles to add:
src/UserCheckerLoader.{h,cpp}-- parse YAML, buildstd::vector<CustomChecker>.src/CheckerRunner.cpp-- run user checkers in the standard pipeline.tests/test_user_checker.cpp-- golden YAML + violation fixtures.
Risk: pattern matching needs a clear glob vs. regex contract; the
schema explicitly names pattern (regex) vs. existing pattern (glob)
fields used elsewhere -- pick one and document loudly.
Why: EDA scripting is overwhelmingly Python. A pip install svlens
that exposes the analysis modes as functions is the highest-leverage
adoption move.
Surface:
import svlens
result = svlens.conn(["rtl/top.sv"], top="my_top",
check_protocol=True, format="json")
# result["edges"], result["issues"], etc.
cdc = svlens.cdc(filelist="rtl/filelist.f", top="soc_top",
sdc="syn/clocks.sdc")
metrics = svlens.metrics(filelist="rtl/filelist.f", top="soc_top",
topk=5)Each function returns the same JSON dict the CLI emits to disk, so users can introspect without re-parsing files.
Files to add:
python/CMakeLists.txt-- pybind11 module target.python/src/svlens_module.cpp-- bindings calling into the existing*Runnerclasses.python/svlens/__init__.py-- thin Python surface and type hints.pyproject.toml-- scikit-build-core driven build.python/tests/test_bindings.py-- pytest covering each mode..github/workflows/python.yml-- build wheels via cibuildwheel and publish to PyPI on tag.
Risk: ABI compatibility between Python's libstdc++ and the local slang build. Solve by linking slang statically into the Python module.
Why: surfacing svlens results inline in editor is a strong onboarding moment. slang already has an LSP, so we can wrap that and layer svlens diagnostics on top.
Approach:
- VSCode extension in
vscode/that:- Registers a new "Run svlens" command.
- Spawns
svlens conn|cdc|metrics --format jsonagainst the active workspace'sfilelist.f. - Surfaces JSON issues as VSCode diagnostics.
- Adds a tree view for the connectivity / CDC reports.
Files to add:
vscode/package.json,vscode/src/extension.ts,vscode/src/diagnostics.ts,vscode/README.md..github/workflows/vscode.yml-- vsce package + marketplace publish on tag.
Risk: VSCode marketplace publishing requires a publisher account managed outside this repo; the workflow has to run against a token.
Why: today connect_report.html is a static table. A dynamic D3.js
viewer (collapsible hierarchy, signal trace highlighting) makes large-SoC
results actually navigable.
Approach:
- Migrate
src/HtmlReport.cppto emit a single self-contained HTML file with:- D3.js bundle inlined (vendored under
assets/d3.v7.min.js). - Force-directed graph for the connectivity matrix.
- Click-to-trace: clicking a node opens the trace path on the right pane.
- Filter-by-module dropdown driven by hierarchy data.
- D3.js bundle inlined (vendored under
- Existing schema reused; the HTML is purely presentation.
Files to modify / add:
src/HtmlReport.cpp-- emit new template.assets/d3.v7.min.js,assets/dashboard.js,assets/dashboard.css-- inlined at build time (CMakeconfigure_fileor a small generator script).tests/test_html_report.cpp-- snapshot tests covering deterministic parts of the output (data sections, not D3 internals).
Risk: keeping the HTML self-contained (no CDN) means embedding ~100KB of D3 in every report. Acceptable for offline / on-prem CI.
A reasonable PR order, each independently mergeable:
- D1 (SVA generation) -- highest impact, smallest surface change.
- F1 (Python bindings) -- biggest adoption multiplier; can land alongside D1.
- D2 (interface/modport deepening) -- closes the largest stated gap.
- D3a-c (metrics extensions) -- ship sub-features individually.
- D4 (custom YAML checkers) -- needs schema review with users first.
- F3 (HTML dashboard) -- nice-to-have, low blocker risk.
- F2 (VSCode extension) -- requires marketplace setup; defer.
Each PR should land its design notes inline (this document is intentionally high-level) and update CONTRIBUTING.md if the build / test workflow shifts.