slang-netlist  0.10.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.

--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.

--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.

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

--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 /*