Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>VSV — Visualizing SystemVerilogNew to Visual Studio Code? Get it now.
VSV — Visualizing SystemVerilog

VSV — Visualizing SystemVerilog

rrroooyyywang

|
70 installs
| (0) | Free
Real-time block-diagram visualisation for SystemVerilog files — module ports, internal structure, and RTL synthesis.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

VSV — Visualizing SystemVerilog & Verilog

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 via VSV: 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-for blocks 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:1 case MUX West 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_comb
    rectangle "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 / clk on top
    rectangle + 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* / assign when the full Logic view would be too dense
    diamond 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/else chains expand to MUX cascades; incomplete always_comb branches 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* / assign source-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. Each xxx::type_id::create("inst", this) call inside a build_phase becomes a nested compound box under its parent. TLM .connect() calls in connect_phase resolve to green arrows. Drivers / monitors that hold a virtual <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 bare always @(*)
  • if / else, case with default, unique / priority qualifiers
  • Sub-module instantiations with named (.port(expr)) port connections
  • generate-for / generate-if blocks (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_EF01 with intra-literal whitespace)
  • Verilog-2001 indexed part-selects [base +: width] / [base -: width]
  • wire foo = expr; declaration-with-initializer (treated as assign)
  • (* attr_name *) synthesis attribute pragmas (silently ignored)
  • `include / `define / `ifdef / `ifndef directives, 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 — clk is taken from the enclosing always_ff sensitivity list and weN is the AND of all enclosing if (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 / *_o patterns) 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_utils is 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. Arbitrary uvm_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_top pattern 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_objection are not visualised. Sequence relationships are captured purely through class inheritance.
  • UVM register model — uvm_reg / uvm_reg_block / uvm_reg_field are 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 class declarations are parsed and any implements clause 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.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft