Developer guide

Environment

To setup a local build configuration, for example if you are using VSCode's CMake integration, create a CMake user preset that in CMakeUserPresets.json in the root directory of the project. This user preset can inherit from one of the standard ones in CMakePresets.json and add its own variables. For example:

{
  "version": 3,
  "configurePresets": [
    {
      "name": "macos-local",
      "displayName": "macOS local",
      "generator": "Ninja",
      "inherits": [
        "macos-debug"
      ],
      "cacheVariables": {
        "CMAKE_PREFIX_PATH":  "/Users/jamie/llvm-project/install/bin",
        "CMAKE_BUILD_TYPE": "Debug",
        "ENABLE_PY_BINDINGS": ON,
        "CODE_COVERAGE": "OFF",
        "BUILD_DOCS": "OFF"
      }
    }
]
}

Then to use this preset:

cmake --preset macos-local
cmake --build build/macos-local --target install
ctest --test-dir build/macos-local --output-on-failure --no-tests=error

Report code coverage:

# Compile with -DCODE_COVERAGE=ON, then:
make ccov-run-netlist_unittests
make ccov-report

Profile performance using cachegrind:

valgrind --tool=cachegrind netlist_unittests
# Analyse results in speedscope.

To build the documentation install the Python dependenices:

virtualenv venv
source venv/bin/activate
pip install requirements.txt
# Compile with -DBUILD_DOCS=ON

External tests

The test suite includes tests using code from external projects, enabled with -DENABLE_EXTERNAL_TESTS=ON (this is the default).

RTLmeter** is a collection of large reference designs (BlackParrot, Caliptra, NVDLA, OpenPiton, OpenTitan, VeeR, Vortex, XiangShan, XuanTie) sourced from verilator/rtlmeter. It is fetched via CPM at configure time and each design is elaborated through the full netlist construction pipeline. To run only the external tests:

ctest --test-dir build/macos-local -R rtlmeter-tests --output-on-failure

The RTLmeter suite runs one unittest per design; failures print the slang-netlist stderr so the root cause is visible directly in the test output. Each design has a 5-minute timeout and the overall test has a 1-hour timeout. Because these are large, real-world designs, some may fail due to slang-netlist limitations and are useful for tracking coverage progress.

To skip the external tests entirely (e.g. for faster iteration):

cmake --preset macos-local -DINCLUDE_EXTERNAL_TESTS=OFF

Architecture

The library builds a directed dependency graph (the "netlist") over an elaborated SystemVerilog AST provided by slang. The graph captures source-level static connectivity at bit-level granularity. The main components are described below.

Graph data structures

The graph is built on a generic DirectedGraph<NodeType,EdgeType> template that stores nodes in an adjacency list and edges as shared pointers on each node. The netlist specialises this as NetlistGraph, holding NetlistNode and NetlistEdge objects.

NetlistNode is a polymorphic base with a NodeKind discriminator. Concrete subtypes are:

  • Port — an input or output port of a module instance.
  • Variable — a net or variable declaration.
  • State — a register (variable driven inside a clocked procedural block).
  • Assignment — a continuous or procedural assignment expression.
  • Conditional / Case — an if or case branch in a procedural block.
  • Merge — a synthetic join point that merges two branches back together.

NetlistEdge carries annotations for the driven symbol, its bit range, and an ast::EdgeKind that records clock sensitivity (used to distinguish combinational from sequential edges).

Graph construction

NetlistBuilder is the main AST visitor (slang::ast::ASTVisitor). Construction is a two-phase process:

  1. Elaboration: A VisitAll pass forces full lazy construction of the AST. The compilation is then frozen (Compilation::freeze()), making the AST safe for concurrent reads.
  2. Netlist population: NetlistBuilder traverses the AST and creates graph nodes for ports, variables, and instances. For each procedural block (always, initial) and continuous assignment it runs a local DataFlowAnalysis, which extends slang's AbstractFlowAnalysis to model if/case branching, static loop unrolling, and non-blocking assignments. The analysis produces per-symbol driver maps that are merged into the central ValueTracker.

After the traversal, NetlistBuilder::finalize() resolves deferred R-value connections (operands that depend on all drivers being present first).

Key supporting types:

  • ValueTracker / VariableTracker — interval-map-based structures that track which netlist nodes drive which bit ranges of each symbol.
  • DriverMap — a thin wrapper around slang's IntervalMap, mapping bit ranges to driver lists.
  • ExternalManager<T> — a handle-based allocator used because IntervalMap values must be trivially copyable.

Multithreading

Phase 2 of netlist construction is parallelised using a BS::thread_pool (bundled with slang). During Phase 1 (the sequential collecting pass), procedural blocks and continuous assignments are not executed immediately but are instead pushed onto a deferredBlocks work list. In Phase 2 each deferred block is dispatched as an independent task to the thread pool, where it runs its own DataFlowAnalysis and merges results back into the shared graph.

Thread safety is achieved through several mechanisms:

  • The slang AST is frozen (Compilation::freeze()) before Phase 2, making it safe for concurrent reads.
  • NetlistGraph::addNode() and NetlistGraph::addEdge() are protected by a mutex so that graph mutations from different tasks do not race.
  • The pending R-value list is guarded by pendingRValuesMutex.
  • NetlistNode IDs are allocated with an atomic counter.

Analysis and queries

  • PathFinder — finds a path between two nodes using depth-first search; returns a NetlistPath.
  • CombLoops / CycleDetector — detects combinational loops by only traversing edges without clock sensitivity (EdgeKind::None).
  • DepthFirstSearch — generic DFS template parameterised on a visitor and an edge predicate; used by both PathFinder and CycleDetector.

Tooling

  • tools/driver/driver.cpp — the slang-netlist CLI binary. It links against the netlist library and exposes commands such as --report-registers, --comb-loops, --from/--to path queries, and --netlist-dot graph export.
  • bindings/python/pyslang_netlist.cpp — pybind11 Python module (pyslang_netlist); enabled with -DENABLE_PY_BINDINGS=ON.

Dependencies

All fetched automatically via CPM at configure time:

  • slang — SystemVerilog compiler/AST/analysis (pinned to a specific git hash).
  • fmt — string formatting.
  • Catch2 — unit testing framework.
  • pybind11 — Python bindings.

Pre commit

Pre-commit runs a set of lint checks on files when creating git commits, with the config in .pre-commit.

You can install the hooks explicity with:

pre-commit install

And you can run pre-commit checks on all files with:

pre-commit run -a

To update the verisons of the hooks used in the pre-commit configuration file:

pre-commit autoupdate

Ubuntu Docker environment

You can use Docker to create an Ubuntu development environment, eg:

docker build -t slang-netlist-dev docker/ubuntu
docker run -it slang-netlist-dev

This is useful to debug issues seen in CI, especially when developing on MacOS.