slang-netlist  0.11.0
Loading...
Searching...
No Matches
User guide

Getting started

Currently there are no pre-built binaries, so you will need to build from source.

Make sure you have the following prerequisites:

  • CMake >= 3.20
  • Python 3
  • C++20 compiler

Then:

git clone https://github.com/jameshanlon/slang-netlist.git
cd slang-netlist
cmake -B build \
-DCMAKE_BUILD_TYPE=Release \
-DENABLE_PY_BINDINGS=ON \
-DCMAKE_INSTALL_PREFIX=$PWD/install
cmake --build build -j --target install
ctest --test-dir build

When using the Python bindings, it is recommended to use the pyslang shared object file that is produced as part of the build. Different versions of the upstream slang Python bindings may not work.

CLI usage

There are two command-line tools available. slang-netlist exposes the netlist-graph building and querying functions of the library and answers structural questions about a design (fan-in/fan-out, path queries, combinational loops, register inventory). slang-report is its companion tool: while you are exploring a design with slang-netlist it surfaces the underlying AST-level information that the netlist abstracts away — port declarations, variable / net declarations with type and width, the full driver set for each value symbol, and the elaborated AST itself as JSON. The two tools share the same source-elaboration front end, so a question that turns out to be about source-level data rather than connectivity can be answered without re-compiling. Both tools accept SystemVerilog source files and the standard slang compilation options. Run either with --help for the full option list.

General options (slang-netlist)

  • -h, --help — display available options.
  • --version — print version information and exit.
  • --no-colours — disable coloured output.
  • -q, --quiet — suppress non-essential output.
  • -d, --debug — enable debug logging (debug builds only).
  • -j, --threads <N> — set the number of threads (0 = hardware concurrency, 1 = sequential).
  • --stats — print phase timings and peak memory to stderr.
  • --stats-json — print phase timings and peak memory in JSON format to stdout.
  • --no-resolve-assign-bits — disable bit-aligned dependency resolution of concatenations, replications, conversions, and equal-width conditional operators in assignments and port connections; see Bit dependency resolution.
  • --no-prop-cuts-across-ports — disable propagation of concat-induced cut points across module port boundaries. With this set, port nodes and module-internal assignments stay whole-word at port boundaries and paths through concatenated ports become bit-imprecise; see Bit dependency resolution.
  • --black-box <pattern> — treat instances whose definition name or hierarchical path matches the glob pattern as black boxes (see Glob pattern syntax for the full pattern syntax). Black-boxed instances get port nodes and external wiring, but their body is not traversed, so paths terminate at the boundary. The option may be repeated to supply multiple patterns.

Query commands

--from <name> --to <name> — find a path between two named points. Names are hierarchical paths such as top.sub.signal. The path is printed with source locations when available.

slang-netlist design.sv --from top.a --to top.b

A path query prints an alternating sequence of nodes and edges from the start signal to the end signal. Each step shows a source location and a description:

  • "input port" / "output port" — a module port boundary.
  • "assignment" — a continuous or procedural assignment.
  • "conditional statement" / "case statement" — a branch in a procedural block.
  • "value" — the edge between two nodes, annotated with the driven signal name and bit range.

When the design has been compiled with the slang AST available, source locations are available and the output includes annotated source lines; otherwise after loading a serialised netlist, a plain file:line:col format is used.

Either endpoint may be given on its own. --from <name> alone reports the combinational fan-out cone from the node (equivalent to --fan-out), and --to <name> alone reports the combinational fan-in cone to it (equivalent to --fan-in). An explicit --fan-out / --fan-in takes precedence over the aliased endpoint.

--fan-out <name> — report the combinational fan-out cone from a named node (all nodes reachable via combinational edges, stopping at registers).

--fan-in <name> — report the combinational fan-in cone to a named node.

--sensitivity <name> — report the clocks and resets gating a named node. For a register the node's own clocking edges are listed; for a combinational node the union of the clocking edges over every register in its combinational fan-out. Each entry shows the source signal, its edge kind, and source location.

--constant-drivers <name> — report the constant values driving a named node when its combinational fan-in bottoms out only at literals (i.e. the node is tied off). Prints nothing if any register or external input reaches the node. Each entry shows the constant value and source location.

--drivers <name[bit-range]> — report, per bit, which node drives a named signal. Each row shows a contiguous bit range, the driving node (assignment, register, port, constant, ...) and its source location. An optional bit-range suffix narrows and clips the query, e.g. m.sig[3:0] or m.sig[2]; without one the whole signal is reported. This exposes the graph's bit-level connectivity directly.

--find <pattern> — find named nodes matching a glob pattern. See Glob pattern syntax for the full pattern syntax.

--find-regex <pattern> — find named nodes matching a regex pattern.

Filtering by scope or name

The node-listing query commands (--report-registers, --find, --find-regex, --fan-out, --fan-in, and --sensitivity) accept --scope <path> and --name <pattern> to constrain their output. --scope keeps only nodes within a hierarchical subtree: a node passes if its path is the scope or a descendant of it, so top.cpu matches top.cpu.alu.x but not top.cpu2. --name keeps only nodes whose hierarchical path matches a glob pattern (see Glob pattern syntax). Both options may be repeated; a node passes if it lies in one of the scopes and matches one of the names. Scopes are literal paths — use --name for glob matching across a subtree.

slang-netlist design.sv --report-registers --scope top.cpu --name "**.q_reg"
Output format

The tabular query and report commands (--report-registers, --find, --find-regex, --fan-out, --fan-in, --sensitivity, --constant-drivers, and --drivers) accept --format <table|json>. The default table renders an aligned text table; json emits an array of objects keyed by the lower-cased column headers (name, location, edge, value, bits, driver), which is convenient for scripting and CI. Pass -o / --output <file> to write the output to a file instead of stdout (- for stdout).

slang-netlist design.sv --report-registers --format json -o registers.json

Reports

--report-registers — list all registers (State nodes) in the design. A State node is created for any variable that is assigned inside a clocked procedural block (always_ff or an always block with an edge-sensitive event list). Each entry shows the hierarchical path and source location.

--comb-loops — detect and report combinational loops. Reports cycles in the graph that consist entirely of combinational (non-clocked) edges. Each loop is printed as a path from a node back to itself. A reported loop means the tool found a structural cycle in the source-level dependency graph; see Bit dependency resolution for caveats on precision.

For AST-level reports (variables, ports, drivers, AST JSON dump) use slang-report — see slang-report below.

Export and serialization

--netlist-dot <file> — write the netlist graph in DOT format. Use - for stdout. The output contains one graph node per NetlistNode and one graph edge per NetlistEdge. Node labels show the node kind and hierarchical path where applicable; edge labels show the driven signal and bit range. The DOT output can be rendered with Graphviz tools such as dot, neato, or fdp:

slang-netlist design.sv --netlist-dot - | dot -Tsvg -o netlist.svg

By default the whole graph is rendered. Combine --netlist-dot with a --fan-out, --fan-in, or --from/--to selector to render only the relevant cone or path — the induced subgraph over those nodes — instead of the entire design:

slang-netlist design.sv --netlist-dot - --fan-out top.sub.sig | dot -Tsvg -o cone.svg

--save-netlist <file> — serialize the netlist graph to a JSON file for later reloading.

--load-netlist <file> — load a previously saved netlist (skips compilation). Analysis commands such as --from/--to, --comb-loops, and --report-registers work on loaded netlists.

slang-report

slang-report is the companion tool to slang-netlist: it surfaces AST-level information so that while you are exploring a design with slang-netlist you can pivot to a question about the underlying source — "what type is this variable", "how wide is this port", "who drives this signal" — without re-compiling. It runs reports against the elaborated AST and accepts the standard slang compilation options plus exactly one of the following actions:

  • --ports — list every port (direction, hierarchical path, width, net type, source location). The net type is the underlying nettype name (wire, wand, ...) for net ports and var for variable ports.
  • --variables — list every variable and net declaration (name, declared type, width, kind, driver count, source location). Kind is var for variable symbols and the nettype name for nets. The driver count is taken from slang's analysis pass, so this action triggers analysis.
  • --drivers — for every value symbol, list each known driver with its bit range, driving expression, kind (proc for procedural, cont for continuous), and source location. Triggers analysis.
  • --ast-json <file> — dump the compiled AST as JSON to the given file (- for stdout). Table output is not meaningful for this mode.
Output format
Use --format table (the default) or --format json on the three tabular actions to choose human-readable columns or machine-readable JSON. --ast-json is always JSON. The JSON drivers report nests the per-value drivers under each value entry.
Output destination
By default the tabular actions print to stdout; pass -o / --output <file> to redirect to a file (use - for stdout). --ast-json takes its file path directly on the flag.
Filtering
All four actions accept --scope <path> to restrict reporting to one or more named hierarchical scopes. <path> may be a literal hierarchical name (resolved via slang's name lookup) or a glob pattern expanded by walking the elaborated tree. slang-report errors if a literal scope does not resolve or if a glob matches nothing.

The three tabular actions additionally accept --name <pattern> to restrict by hierarchical path. Multiple --name patterns combine with OR semantics; an empty match returns an empty result rather than an error (the flag is a filter, not a selector).

Both --scope and --name use the glob syntax described in Glob pattern syntax (*, ?, **, ...).

# Inspect a single instance's ports.
slang-report design.sv --ports --scope top.cpu
# All variables in the design whose name ends in '_q', as JSON.
slang-report design.sv --variables --name '**.*_q' --format json
# Drivers of a single net, written to a file.
slang-report design.sv --drivers --name top.bus.cmd -o cmd-drivers.txt
# AST dump of just one scope.
slang-report design.sv --ast-json - --scope top.cpu

Python bindings

The pyslang_netlist module (enabled with -DENABLE_PY_BINDINGS=ON) exposes the graph data structures and analysis routines to Python. It depends on pyslang for the compilation and analysis types.

Building a graph

The build pipeline has four steps: force lazy AST construction with VisitAll, freeze the compilation, run AnalysisManager, then unfreeze the compilation so NetlistGraph.build can keep elaborating.

import pyslang
import pyslang_netlist
# Compile.
tree = pyslang.syntax.SyntaxTree.fromText("""
module m(input logic a, output logic b);
assign b = a;
endmodule
""")
compilation = pyslang.ast.Compilation()
compilation.addSyntaxTree(tree)
# Force construction of the whole AST before freezing, since AST
# construction is lazy and visiting a previously-unvisited node can
# mutate the compilation (which is not thread-safe).
pyslang_netlist.VisitAll().run(compilation)
compilation.freeze()
# Run analysis.
am = pyslang.analysis.AnalysisManager()
am.analyze(compilation)
# Unfreeze before building: the netlist builder needs to keep
# elaborating the AST.
compilation.unfreeze()
# Build the netlist.
graph = pyslang_netlist.NetlistGraph()
graph.build(compilation, am)

NetlistGraph.build accepts these keyword arguments:

  • parallel (default True) — dispatch DFA work and R-value resolution across a thread pool.
  • num_threads (default 0) — thread pool size; 0 uses hardware concurrency.
  • resolve_assign_bits (default True) — bit-aligned dependency resolution; see Bit dependency resolution.
  • prop_cuts_across_ports (default True) — propagate concat-induced cut points across port boundaries; see Bit dependency resolution.
  • black_boxes (default []) — list of glob patterns matched against each instance's definition name and hierarchical path (see Glob pattern syntax). Matched instances skip body traversal and record only port-boundary connectivity.

Querying the graph

# Graph size.
print(graph.num_nodes(), graph.num_edges())
# Lookup a node by hierarchical name.
node = graph.lookup("m.a") # Returns NetlistNode or None
# Lookup nodes by name and bit range overlap.
nodes = graph.lookup_by_range("m.a", 0, 7)
# Find nodes by wildcard or regex.
nodes = graph.find_nodes("m.*")
nodes = graph.find_nodes_regex(r"m\.[ab]")
# Get drivers of a signal over a bit range.
drivers = graph.get_drivers("m.b", 0, 0)
# Combinational fan-out / fan-in (stops at State nodes).
fan_out = graph.get_comb_fan_out(node)
fan_in = graph.get_comb_fan_in(node)

Path finding

finder = pyslang_netlist.PathFinder()
# Find any path.
path = finder.find(start_node, end_node)
# Find a combinational-only path (skips State nodes).
path = finder.find_comb(start_node, end_node)
if not path.empty():
for node in path:
print(node.kind, getattr(node, "path", ""))

Iterating over nodes

# Iterate all nodes.
for node in graph:
print(node.ID, node.kind)
# Access typed properties on specific node kinds.
if node.kind == pyslang_netlist.NodeKind.Port:
port = node # Port subclass
print(port.name, port.path, port.bounds, port.is_input())

Available node types and their properties:

  • Portname, path, direction, bounds, is_input(), is_output(), is_driven()
  • Variablename, path, bounds
  • Statename, path, bounds
  • Constantwidth, value (string form of the constant value). Created for literal RHSs, constant-foldable expressions, zero-extension padding bits, literal port connections, and constant arms of conditional operators.
  • Assignment, Conditional, Case, Merge — no additional properties

The bounds property on nodes and edges is a DriverBitRange with lower, upper, and width accessors; it also unpacks as a (lower, upper) tuple.

Edge properties: symbol_name, symbol_path, bounds, disabled.

Performance tuning

Threading

Use -j <N> to set the number of threads. The default (-j 0) uses hardware concurrency. For small designs the overhead of thread management can outweigh the benefit; -j 1 forces sequential execution.

The parallel phases of construction are Phase 2 (DFA dispatch) and Phase 4 (R-value resolution). Phase 2 dispatches one task per procedural block and continuous assignment, so designs with many independent blocks benefit most from additional threads. Phase 4 only parallelises when the number of pending R-values exceeds an internal threshold (1000 by default).

Profiling

Use --stats to print a human-readable timing breakdown to stderr:

slang-netlist design.sv --report-registers --stats

The output shows per-phase timings (collect, parallel DFA, drain, resolve R-values), per-task statistics (min/max/mean/median), and peak RSS. Use --stats-json for machine-readable JSON output.

Considerations

There are various factors that affect performance and how much multithreading improves this. Here are some to consider; note that the execution phases of the netlist construction are detailed in Graph construction.

  • Design size. More ports, variables and instances means more nodes and edges to create in an sequential traversal of the AST (Phase 1).
  • Procedural block count. Each always / initial block is a separate DFA task in Phase 2; designs with many small blocks parallelise well, while designs dominated by a few large blocks may be limited by the longest task.
  • R-value count. Complex expressions with many operands generate more pending R-values for Phase 4 to resolve.
  • Hierarchy depth: deeply nested module instantiations increase the AST traversal time and the number of port connections to process.

Analysis model

Slang Netlist builds its dependency graph by analysing the SystemVerilog source directly, without performing logic synthesis. This makes it fast, portable across target technologies, and able to operate on partial or non-synthesisable designs. However, because the tool does not flatten the design down to a gate- or LUT-level netlist, the analysis is necessarily more conservative than what a synthesis tool could provide. The key differences are described below.

No-synthesis elaboration

A synthesis tool resolves every operator, generate construct, and hierarchical reference into a flat network of primitive gates or technology cells. Slang Netlist instead works at the SystemVerilog source level: it preserves the original module hierarchy, treats each assignment as an atomic operation and records data dependencies between the variables that appear on each side. This means the graph is a faithful representation of the source structure, but it does not always capture the Boolean-level logic that a synthesised netlist would expose.

Bit dependency resolution

When an assignment connects symbols on its left- and right-hand sides, the graph records dependencies bit-by-bit wherever the source structure makes the per-bit correspondence clear. Concatenations, replications, and width-changing conversions are tracked precisely, so the bits of one symbol are connected only to the bits they actually drive on the other side:

assign {a, b} = {x, y}; // x connects to a, y connects to b
assign w = {2{p}}; // p connects to both halves of w
assign z[31:0] = q[15:0]; // only the lower 16 bits of z have a driver;
// the upper 16 are zero-extended

This holds across the structural rearrangements you can write in SystemVerilog: nesting concatenations, replicating with a constant count, widening to a larger type (the extra bits are recorded as zero- or sign-extension and have no driver), and using a conditional operator ?: where both arms have the same width as the result (the condition itself becomes an extra dependency on every bit).

The same rules apply to port connections, which are treated as assignments. A hookup such as .in({x,y}) on a two-bit input port connects x and y to the matching individual bits of the port.

Bit precision also carries through the port itself. When a concat appears on a port connection, the cut points are propagated inward so that the formal port nodes and the module-internal assignments are split at the same bit boundaries. For example, given:

module sub(input logic [1:0] x, output logic [1:0] y);
assign y = x;
endmodule
module top(input logic a, b, output logic c, d);
sub u (.x({b, a}), .y({d, c}));
endmodule

The graph reports a→c and b→d but no cross-bit paths.

Known limitations
  • Operators are not analysed bit-by-bit. As soon as an expression contains arithmetic (+, -, *, …), a bitwise or relational operator, a reduction, a function call, a streaming concatenation, a non-constant select, a narrowing conversion, or a pattern-bearing conditional, every symbol read inside that expression is treated as contributing to every bit the expression produces. So in y = a & b the graph still records that every bit of y depends on every bit of a and b, even though & is bitwise in nature.
  • Bit-aligned resolution only applies to integral types whose widths agree on both sides. Strings, unpacked aggregates, and dynamically-sized types, along with assignments where the two sides have different selectable widths (for example an enum coerced to a logic at a port boundary), fall back to a coarser per-symbol connection.
Fallback options

Bit-aligned resolution can be turned off with --no-resolve-assign-bits on the slang-netlist CLI, or by passing resolve_assign_bits=False to NetlistGraph.build in the Python bindings. With it off, every symbol on one side of an assignment is connected to every symbol on the other side, matching the behaviour of releases before bit-aligned resolution landed.

Cross-port cut propagation can be turned off with --no-prop-cuts-across-ports on the slang-netlist CLI or by passing prop_cuts_across_ports=False to NetlistGraph.build in the Python bindings. With it off, port nodes and module-internal assignments stay whole-word at port boundaries even when the actual side of the connection is a concat.

In every fallback case the resulting graph is still sound (no real connection is missed) but path and dependency queries over the affected symbols may report paths that a deeper Boolean analysis could rule out.

Black-boxed instances

Instances can be black-boxed to stop the netlist builder from descending into their bodies. A black-boxed instance still receives port nodes and external port-connection wiring, but its internal assignments and sub-instances are not traversed, so fan-in/fan-out paths terminate at the port boundary. This is useful for excluding vendor IP, technology cells, or any sub-tree whose internals are irrelevant to the analysis at hand.

Patterns are glob expressions (see Glob pattern syntax) and are matched against either an instance's definition name or its hierarchical instance path, so the same pattern can target a single instance or every instantiation of a module:

slang-netlist design.sv --black-box ram_* --black-box top.cpu.fpu

The Python equivalent passes a list of patterns to NetlistGraph.build:

graph.build(compilation, am, black_boxes=["ram_*", "top.cpu.fpu"])

Glob pattern syntax

Glob patterns are used to match hierarchical names by the --black-box and --find CLI flags, by the black_boxes argument to NetlistGraph.build, and by NetlistGraph::findNodes. Names are treated as dot-separated path segments (for example top.sub.signal is three segments).

The supported wildcards are:

PatternDescription
* Matches zero or more characters within a single path segment. Does not cross .
** or ...Matches zero or more characters across path boundaries (recursive). ... is the LRM-native spelling used elsewhere in slang; ** is also accepted for consistency with widespread tooling convention.
?Matches exactly one character within a single path segment. Does not match .

Any other character is matched literally.

When a recursive wildcard is written adjacent to a literal . in the pattern (forms such as a.**.b, a.**, or **.b), the adjacent . is treated as an optional segment boundary. This mirrors gitignore-style recursive globbing and slang's LRM-style .../ handling and lets the recursive wildcard stand in for zero or more intermediate path segments.

Examples:

PatternMatchesDoes not match
top.*top.a, top.foo top.a.b (crosses .), top (no segment)
top.**top, top.a, top.a.b.c topX
top....leaftop.leaf, top.mid.leaf, top.a.b.leaf topXleaf
top.u_?top.u_a, top.u_b top.u_ab, top.u_a.x
**.leafleaf, mid.leaf, top.mid.leaf xleaf

SystemVerilog support

The following SystemVerilog constructs are handled during netlist construction:

  • Module and interface hierarchies, including generate blocks and arrays of instances.
  • Input, output, and inout ports; port connections (named and positional).
  • Interface modport ports and their connection expressions.
  • Continuous assignments (assign).
  • Procedural blocks: always, always_ff, always_comb, initial.
  • Procedural assignments (blocking = and non-blocking <=).
  • Conditional statements (if/else) and case statements within procedural blocks, including static constant folding of unreachable branches.
  • Bit and range selects on the left-hand and right-hand sides of assignments.
  • Multi-dimensional arrays and packed/unpacked struct member access.

SystemVerilog constructs that are not currently supported or have limited handling:

  • Tasks and functions — calls are not followed; dependencies through function arguments are not tracked.
  • Assertions and coverage constructs are ignored.
  • System tasks and functions ($display, etc.) are ignored.
  • force/release and deassign are not modelled.
  • Dynamic types, classes, and constraint blocks are not modelled.
  • Cross-module references (XMRs) are partially supported through slang's hierarchical reference resolution.

Potential use cases

Here are some ideas for ways in which Slang Netlist can be used in digital design.

  • Connectivity checks. Testing for the existence of paths can be used to implement lightweight lint checks for design structure. For example, checking that a control signal fans out correctly to all memories in a design. Equally, the absence of paths can be also be checked, for example to assert that design components are correctly isolated.
  • Static timing / critical-path estimation. By annotating edges with a simple unit-delay or gate-type-based delay model, the longest combinational path between any two register stages could be reported. Even without real timing analysis, logic depth is a useful early-stage proxy for critical paths and helps flag modules that will struggle to meet frequency targets.
  • Clock domain crossing checks (CDC). This requires tracing connectivity across register boundaries and correlating with clock edges. Slang knows about EdgeKind but doesn't do cross-domain path analysis.
  • Reset domain crossing checks (RDC). Tracing which registers share reset paths and flagging cross-domain reset issues. Similar to CDC but for resets.
  • Cone-of-influence extraction. Given a target signal, extract the minimal sub-design (all transitive fan-in) needed to compute it. This could output a reduced SV module or a sub-graph DOT file, useful for debugging or feeding a smaller design into formal verification tools.
  • Netlist diffing. Compare netlists from two revisions of the same design and report structural differences: added/removed signals, changed connectivity, altered fan-in/fan-out. This would be valuable to catch unintended RTL changes, as a lightweight netlist equivalence check.
  • Hierarchical complexity metrics. Report per-module statistics such as node count, edge count, combinational depth, fan-out distribution, register count. These metrics could help identify overly complex modules that should be refactored and can track design complexity over time.