ossschem — RTL schematic tracer
Opens an interactive schematic of a (System)Verilog design inside VS Code:
process and instance structure first, gates and expressions when you ask for
them, signals traced forward and backward through the hierarchy, and the
declaration, drivers and loads of whatever is selected in a source pane
beneath it.
With vaporview
installed it also works with a waveform dump: signals go from the schematic to
the waveform, a signal in the waveform is revealed in the schematic, and every
wire can be labelled with its value at the cursor.
1. Build a schematic from your design
The extension opens a schematic-ir.json, which the ossschem command writes
from your RTL. It runs Verilator's --json-only elaboration, so the design has
to elaborate with Verilator 5.046 or newer — nothing is simulated and no
C++ is generated. The version is detected, recorded in the IR, and reported
when it is older than that.
ossschem build --top my_top --out-dir build/schematic rtl/*.sv
That writes schematic-ir.json, sources.json and a standalone index.html
into build/schematic. Useful flags:
--sources path/to/rtl — add source text that is not on the Verilator
command line; repeatable. Without it, a file that was only included shows no
source in the pane.
--verilator /path/to/verilator — when it is not on PATH.
--open — open the standalone page in a browser instead of, or as well as,
using VS Code.
-- <verilator args> — everything after -- goes to Verilator unchanged:
parameter overrides (-pvalue+NAME=value), +define+, include paths, -f
file lists, and sources. A --top-module there replaces --top, so a
simulation's flag set can be reused as it is; --binary and --build, which
Verilator refuses alongside --json-only, are left out with a note.
--json <tree.json> [meta.json] — build from the JSON a verilator --json-only run in your own flow already wrote, instead of running
Verilator. Without a meta file, Vtop.tree.json looks for
Vtop.tree.meta.json next to it.
In a Makefile, next to the simulation that produces the dump:
TOP := my_top
RTL := $(wildcard rtl/*.sv) $(wildcard tb/*.sv)
SCHEMATIC_DIR := build/schematic
.PHONY: schematic
schematic: $(SCHEMATIC_DIR)/schematic-ir.json
$(SCHEMATIC_DIR)/schematic-ir.json: $(RTL)
ossschem build --top $(TOP) --out-dir $(SCHEMATIC_DIR) $(RTL)
Getting the ossschem command, and the rest of the install:
INSTALL.md.
2. Open it
Open schematic-ir.json — clicking it in the explorer is enough. The extension
is the default editor for it, and for any *.ir.json, so the schematic is what
you get rather than the JSON text.
When the JSON itself is the question, Reopen Editor With… → Text Editor on
the tab shows the file; ossschem: Open schematic (or the title bar button)
switches back.
3. Read it
Click to select; Fit restores the whole view. Double-click a process or
instance (or press E) to expand its structure in place, L to expand its
logic into gates and flip-flops, I to isolate one component, C to collapse
one, and Backspace to go back. Select a wire or pin and press F or B to
trace its loads or drivers, repeatedly to advance one step at a time.
Press H for the full list of shortcuts and operations.
Unpacked arrays are drawn as one wire marked 4×8b into a splitter, with one
branch per element labelled [0], [1], and so on.
Requires vaporview, with a dump open.
Schematic → waveform. Select a signal and press W, or use the
Waveform button. The name is checked against the dump before it is added:
vaporview accepts a name it cannot resolve without complaining, so an
unverified spelling would look exactly like success. Selecting a whole unpacked
array asks which elements to add — all of them for a small one, otherwise the
first, or type an index, a range (0-7) or a list (0,2,5). Selecting one
branch off a splitter adds just that element.
Waveform → schematic. Right-click a signal in the waveform or in its
netlist tree and choose Reveal in schematic. The instances between the
current scope and that signal open in place, the signal is highlighted and
framed, and its declaration, drivers and loads appear in the source pane. The
scope on screen does not change, so the answer arrives in the context the
question was asked in.
Both directions are deliberate rather than automatic: scrubbing the cursor
moves the waveform selection around, and a schematic that followed it would not
stay still long enough to read.
Values at the cursor. Toggle Values in the toolbar to label every wire
on screen with its value at the waveform cursor; the labels follow the cursor
as it moves. Values read as Verilog writes them (1'b1, 8'h0f), with unknown
bits in binary (4'b010x), and a signal that changes at the cursor shows the
step across it (8'h0f→a3). An unpacked array has no single value, so the wire
into its splitter carries none, but each branch off it does. The value of the
selected signal lights up with it and is written out on the heading of the
source pane.
Which dump. The Waveform picker at the top of the sidebar names the
dump everything reads and writes, and switches it when several are open. It is
settled once — from the one on screen — and then left alone, so the target does
not move between one operation and the next.
Where the design is in the dump. A dump names signals from the
simulation's top, which is often not the design's: a SystemC or C++ harness, a
cocotb wrapper or a SystemVerilog testbench puts it one or more levels down,
and possibly under another instance name (TOP.tb.u_dut for a design whose
top is my_top). The Scope field under the picker holds the dump's name
for the design's top; every name crossing between the two is translated
through it. It is usually found without asking:
- when a dump comes into use, ossschem checks whether it names the design as
the schematic does, or as Verilator's own traces do (
TOP.my_top);
- otherwise, the first signal of the design you select or add in vaporview, or
reveal in the schematic, tells it where the design is.
A scope is only adopted once the dump confirms it: a few of the top's ports
and one port of an instance inside it must all be there under it, so a
testbench signal named like one of the design's ports is not mistaken for the
design. When detection cannot place it, right-click the design's scope in
vaporview's netlist tree and choose Use as ossschem design root, or type it
into the field (TOP.tb.u_dut). A scope given by hand is never replaced;
emptying the field hands it back to detection. The field warns when the
design's ports are not found under the scope in use. Scopes are remembered per
dump file and design.
Commands
| Command |
What it does |
ossschem: Open schematic |
Show a schematic-ir.json as a schematic, from the text editor |
ossschem: Reveal in schematic |
Show the waveform's selected signal (context menus) |
ossschem: Use as ossschem design root |
Take a scope in vaporview's netlist tree as the design's top (context menu) |
ossschem: Link selection to waveform viewer (toggle) |
Stop or resume sending signals to vaporview |
ossschem: Show log |
Every cross-probing step, in an output channel |
ossschem: Diagnose waveform probing |
What vaporview knows about the last signal picked |
If a signal does not appear in the waveform, the diagnostic is the first thing
to check: it prints the spellings vaporview recognises for it.