Real-time block-diagram visualisation for SystemVerilog and Verilog source
files, inside VS Code. Open any .sv / .svh / .svi / .v / .vh file
and a side panel renders the structure as a clean schematic that updates
as you type. VSV understands both halves of a typical SystemVerilog project:
- Design view — RTL synthesis, module instances, generate-for grids, regfile / memory inference. Three named sub-views: Ports / Schematic / Logic — see below.
- Verification view — UVM class hierarchy via containment, full UVM
component topology (env → agent → driver/monitor/sequencer), TLM
.connect()wiring, virtual-interface binding, and the DUT module the testbench drives. (Opt-in viaVSV: Show Verification View.)
The two views are entirely separate panels of the same diagram — pick whichever matches what you're reading.
Features
VSV cycles between three views with VSV: Switch View (or run VSV: Show Diagram to open the panel):
-
Ports view. The module as a single labelled rectangle with every input / output port as a diamond on the boundary.
-
Schematic view. Sub-module instances laid out left-to-right, with wires derived from named port connections and
assign-alias resolution.generate-forblocks are expanded into an N-D grid — inner loop left-to-right, outer loop top-to-bottom — so a 4×4 systolic array shows up as a 16-cell grid. -
Logic view. The behavioural body of a single module is synthesised into primitive shapes:
Shape Meaning Notes rounded square + bold symbol arithmetic / logic / comparison / shift operator ( +,-,*,&,|,==,<<,[],{}, …)Symbol is the literal operator from the source right-pointing triangle + bubble logical or bitwise NOT ( !,~)Bubble marks the inversion tip vertical trapezoid 2:1 ?:MUX or N:1caseMUXWest side = data inputs; top = sel; legs are labelled with the literal case values (2'b00,default, …)dashed ellipse + literal constant / numeric literal ( 8'h2A,1'b0, …)Drives whatever consumes it rectangle "FF" + signal caption edge-triggered flip-flop D west, Q east, clk + rst on top; warning-coloured border + dashed stroke when the FF was inferred from an incomplete always_combrectangle "latch" + signal caption level-sensitive latch Always warning-coloured (latches are easy to write by accident) rectangle + horizontal dividers + [W × D]regfile / memory (1-D or N-D unpacked array) Write group on west top, read group below, we/clkon toprectangle + two-line label instance / (module)sub-module instantiation Ports labelled with the resolved module's port names rectangle + top title + faint inside wash code-structure block (fallback) One per always*/assignwhen the full Logic view would be too densediamond module-level input or output port Inputs on the west boundary, outputs on the east small filled circle on a wire junction fan-out solder point Drawn only where a single signal branches in 3+ directions (IEEE 315 convention) if/elsechains expand to MUX cascades; incompletealways_combbranches trigger an inferred-latch warning in the status bar.When a module has too many primitives for the detailed Logic view (default threshold: 1500 nodes — picorv32's main core is ~3000), the Logic view falls back to a code-structure summary: one rectangle per
always*/assignsource-code construct, labelled with the literal source keyword + line number. Click any block to jump to the source.
Wire colours follow schematic convention:
- Yellow — wire flowing from a module input port
- Green — wire flowing into a module output port
- Blue — internal wire
Edge endpoints are snapped to their port anchors, so wires meet the
shape edge cleanly with no visible stub. Edge labels carry the literal
signal name (↑clk, acc, 2'b00, …) and are placed at the
arc-length midpoint of the polyline.
Clock and reset signals always enter their target boxes from the top; their source ports float to the upper-left corner of the diagram.
Click to navigate
Click any node, edge, or generate-for box in the diagram to jump to the corresponding line in the source file. The diagram works as a navigable map of the module.
Verification view (UVM / class hierarchy + topology)
The design view (Ports / Schematic / Logic) deliberately hides non-synthesisable SystemVerilog — classes, programs, UVM macros, fork/join, etc. — so the schematic stays focused on the hardware. A separate opt-in panel renders the verification view with two sub-modes (a toolbar dropdown switches between them):
- Inheritance — class hierarchy rendered as containment: the
parent class is the outer rounded rectangle, every derived class
is nested inside it. UVM hierarchies are shallow (3–5 levels at
most), so nesting reads instantly without the arrow-soup of a
traditional UML class diagram. Library bases (
uvm_test,uvm_env,uvm_driver, …) are the outermost containers, with workspace classes inside them; deeper user chains (base_test → smoke_test) nest further.implements(SV2009 interface-class realisation) remains a dashed hollow-triangle edge — it's the only relationship that crosses inheritance chains. - Topology (auto-default when a
uvm_test-derived class is detected) — UVM component containment tree. Eachxxx::type_id::create("inst", this)call inside abuild_phasebecomes a nested compound box under its parent. TLM.connect()calls inconnect_phaseresolve to green arrows. Drivers / monitors that hold avirtual <iface_name> vif;field bind to an interface bus node, and that interface bus binds to the DUT module instance found in the testbench top — so the full path from a UVM transaction down to the design's pins is on one canvas.
Classes are coloured by UVM role family (test / env / agent / driver /
monitor / sequencer / scoreboard / subscriber / sequence /
sequence_item / object / reg / component), with the role written as a
UML stereotype subtitle (<<uvm_driver>>, <<uvm_env>>, …). Hovering a
node shows the type, family, parameter overrides, every
`uvm_*_utils macro the body declared, and the file:line.
Clicking jumps to the class … declaration.
A Legend button on the toolbar opens a popover that explains what every arrow style and family colour means.
Open the panel with VSV: Show Verification View (Ctrl/Cmd-Shift-P). Open a file with classes but no module and the design view will offer a one-click switch.
Trying it on a real UVM testbench
Three self-contained example testbenches ship under test/fixtures/:
| Fixture | What it shows |
|---|---|
alu_uvm_tb.sv |
Verifies the existing alu.sv (8-bit ALU). Full UVM stack + alu_if interface + tb_top instantiating the DUT. |
uvm_minimal.sv |
Self-contained counter testbench — simple_if + simple_counter DUT + minimal_tb_top. Smallest end-to-end example. |
uvm_two_agents.sv |
Multi-agent / multi-interface example — apb_if + axi_if bound to a single dual_bus_dut. Also exercises a 2-level test inheritance (smoke_test extends base_test). |
Open any of them and run VSV: Show Verification View to see:
- The full UVM component tree (test → env → agent → driver / monitor / sequencer + scoreboard)
- TLM
.connect()edges (driver ↔ sequencer, monitor.ap → scoreboard.imp) - The interface bus(es) bridging the agents to the DUT
- The DUT module rendered as a hardware-style box with its actual ports listed (inputs on the left, outputs on the right)
Multi-module files
Files that declare two or more modules expose a Module dropdown in the diagram toolbar. The selection is sticky across re-renders, so a save / edit doesn't snap the view back to the first module.
Live updates
The diagram re-renders 300 ms after every keystroke. External changes (git checkout, save from another editor) refresh the active diagram too. Layout results are cached per file content + module + stage, so re-opening a previously-rendered file is near-instant.
Layout engine
Layout is done by ELKjs with a custom
post-processor that pins clk/rst to the upper-left column, rebuilds
edge bends as 90° orthogonal routes, and resolves FF feedback cycles
so MUXes feeding FF.D always sit to the left of the flip-flop.
Commands
| Command | Default behaviour |
|---|---|
VSV: Show Diagram |
Opens / reveals the diagram panel for the active file |
VSV: Show Verification View |
Opens the UVM / class hierarchy view |
VSV: Switch View |
Cycles Ports → Schematic → Logic → Ports (design view only) |
VSV: Hide Diagram |
Closes the panel and stops background re-render |
VSV: LVS Check |
Compare every declared port against what the diagram drew (writes a per-port report to the "VSV LVS" output channel) |
VSV: Reset Macro Overrides |
Clear all manual \define` overrides set via the toolbar |
Configuration
| Setting | Description |
|---|---|
vsv.includePaths |
Additional search directories for `include lookups (absolute or workspace-relative). The directory of the file containing the `include is always tried first. |
Supported SystemVerilog / Verilog subset
- Module declarations with
#(parameter …)blocks (Verilog-2001 ANSI and SV) - ANSI port lists, packed and unpacked array dimensions
assign,always_ff,always_comb,always_latch,always @(posedge …),always @*and barealways @(*)if / else,casewithdefault,unique/priorityqualifiers- Sub-module instantiations with named (
.port(expr)) port connections generate-for/generate-ifblocks (1-D and N-D nested), with compile-time parameter folding ($clog2,$bits,$pow, etc.)- Unsized fill literals (
'0,'1,'x,'z), sized base literals (8'b…,16'h…,32'h ABCD_EF01with intra-literal whitespace) - Verilog-2001 indexed part-selects
[base +: width]/[base -: width] wire foo = expr;declaration-with-initializer (treated asassign)(* attr_name *)synthesis attribute pragmas (silently ignored)`include/`define/`ifdef/`ifndefdirectives, including nested-paren macro arguments- Sim-only constructs (
initial/final,$display/$readmemh/assert/ etc.,fork/join,function/task,class,package/import) are silently skipped so synthesisable code still renders
Memory / regfile inference
reg [W-1:0] mem [D-1:0]; declarations (1-D or N-D) accessed with at
least one dynamic index collapse into a single regfile node:
- One
(waddrN, wdataN, weN, clk)port group per write —clkis taken from the enclosingalways_ffsensitivity list andweNis the AND of all enclosingif (cond)chain conditions. - One
(raddrN, rdataN)port group per distinct read address. - Multi-dimensional addresses (
mem[way][idx]) get one sub-port per dimension (waddr0_d0,waddr0_d1, …).
Reads with fully-constant indices fall through to the per-slot driver path so generate-for replicated arrays continue to behave as N distinct cells.
Known limitations
Design view (Ports / Schematic / Logic)
- The Logic view falls back to the Schematic view when a module is purely structural (no behavioural body to synthesise).
- Direction of sub-module ports is inferred from the port name
(
*_in/*_out/*_i/*_opatterns) and from undriven-wire context. Expressions passed to a port whose direction can't be determined heuristically may render with the wrong arrow direction. always_comb-inferred latches are detected but not yet highlighted inline in the source editor.- Very large modules (>1500 primitive nodes) render as a coarse code-structure summary; the per-gate Logic view is not available for them.
Verification view (UVM)
The verification view is static-analysis only — it reads the source as written, not as it would execute under a UVM simulator. Things it deliberately doesn't reflect:
- Factory overrides (
set_type_override_by_type,set_inst_override_by_name) are NOT applied. A class registered via`uvm_component_utilsis rendered with its declared type; runtime type swaps don't change the diagram. - config_db lookups are recognised only as the declared
virtual <iface_name> vif;field on a driver / monitor. Arbitraryuvm_config_db#(T)::get(...)paths and non-interface context items are not traced. - TLM resolution walks dot-paths against the instance tree
(
agent.mon.ap → sb.imp). The last segment is assumed to be a TLM port; resolution stops at the deepest matched instance. Expressions involving array indexing or arbitrary method calls aren't resolved into a graph edge. - DUT detection requires the testbench to instantiate the
interface inside a Verilog module (the
tb_toppattern with a<iface_name> vif(...)instance). When no such module is in the workspace, the interface renders as a ghost with no pin binding. - Sequences and phases —
start_item/finish_item/phase.raise_objectionare not visualised. Sequence relationships are captured purely through class inheritance. - UVM register model —
uvm_reg/uvm_reg_block/uvm_reg_fieldare tagged with the reg family and rendered as ordinary classes; field-to-register containment is not auto-traced. - Parameterized class types —
class my_drv #(type T) extends uvm_driver #(T)renders with the parameter list as an annotation next to the class name but the type parameter isn't tracked across type bindings. - Mixed workspaces — when several unrelated testbenches are
open at once, every leaf-test renders by default. Use the
toolbar's
Test:dropdown to focus on one when the canvas gets busy. - SV2009
interface classdeclarations are parsed and anyimplementsclause appears as a dashed edge, but interface-class inheritance is not yet collapsed into containment the way regular class extension is.
Release notes
See CHANGELOG.