Verilog RTL Connector
A VS Code extension that lets you visually interconnect Verilog / SystemVerilog modules and
auto-generate a top-level wrapper with correct port lists, internal wire declarations and
instance port-maps. Think of it as a lightweight, open-source alternative to the (closed-source)
verilogCCN connector — built so you can read, extend and own the code.
New here? Read the user manual (中文使用手册) in MANUAL.md — quick start, every command
in detail, the full rule tables of the RTL Code Check, a command cheat-sheet and the FAQ.
Features
Editor-title menu (every feature in three categories) — a circuit-board chip icon sits in the
editor's top-right action bar, right next to Split Editor Right; clicking it opens a drop-down with
three submenus: RTL Design (canvas/connect, instantiate, auto-wire, wrapper, port table, OMIT,
comment banner, code templates, FSM diagram, signal path, hierarchy, lint), Simulation (testbench, simulation
environment, filelist) and General (vivid colours). It is a native submenu
(contributes.submenus + menus["editor/title"]), shown in every editor — each command validates the
current file itself.
Workspace scan — parses every *.v / *.sv file and extracts module name, parameters,
port names, directions (input/output/inout) and bus widths ([7:0]).
RTL MODULE HIERARCHY view — under the Explorer it shows the instantiation hierarchy of the
active file (u_a (module a) → u_core (module core)); click a node to jump to the instantiation
line, (?) marks a module that is not defined in the workspace. Only real instantiations count:
a <module> [#(params)] <instance> ( whose instance name sits right next to the module name
(spaces / newlines allowed). Compiler directives and macro names never become modules — an
`ifdef RW_WLAN_COEX_EN followed by a ( in a continued condition, a `define body, text
inside a string ($display("… expected to FAIL (…)")) and a call right after fork are all ignored.
Instantiations written inside `ifdef / `else are still found.
Explorer-style module tree — the Modules panel is an Explorer-style tree (folder > file >
module), with chevrons to expand/collapse and a + button on each module to add it to the canvas.
Working panel — a second sidebar to the right of Modules that lists every module currently
on the canvas, with a × button to remove it. Both sidebars are independently resizable.
Visual canvas — click a module's + to drop it on the canvas as a draggable card with a
header showing the module name and its source file path, and port dots on its left (inputs) /
right (outputs). Drag the card header to move a module.
Drag-to-connect — drag from one port dot onto another to draw an orthogonal wire. Direction
compatibility is checked (output → input; inout / unknown connect freely).
Auto Connect — wires ports across all modules by matching signal name (stripping _i / _o
suffixes and matching parameters by name). Rule: only real signal flow is wired
(output drives input; inout / unknown connect freely). All modules are treated as peers —
there is no special TOP node.
No TOP designation needed — every canvas node is just an instantiated submodule. Any port
you leave unconnected automatically becomes a top-level port of the wrapper; ports that
share the same name and direction (e.g. clk on every submodule) are merged into a single
top-level port, so clocks/resets surface as one clk/rst_n driving all instances.
Import Top Module — the button left of Fit on the zoom bar runs the whole thing
backwards: pick a .v / .sv file and its top module is drawn on the canvas — one card for
the top itself (highlighted, TOP badge), one card per instantiated sub-module, and a wire for
every net connecting them. The top is the module no other module in the file instantiates (if a
file holds several unrelated modules you are asked which one to import); sub-module ports come
from the workspace scan, assign a = b; counts as one net, and directions that are not declared
are deduced from the wiring. Connections are drawn driver → load, so a top-level input drives
sub-module inputs and a sub-module output drives the top-level outputs.
- It round-trips: the
TOP card is not instantiated when you press Generate — its ports
(names, directions, widths) become the generated module's own ports, so
import → tweak the wiring → Generate re-generates that very module instead of wrapping it
again. With no TOP card the generator behaves exactly as before.
- Only one level of hierarchy is expanded, constant / concatenation connections cannot be drawn
(those ports stay unconnected), and the top's own
#(...) parameters are not rebuilt — every
such note is logged to the Output channel as import top note:. Importing replaces the current
diagram, so it asks for confirmation first when the canvas is not empty.
Spec-compliant RTL — generated code follows a standard RTL coding style: standardized file
header, `default_nettype none / `default_nettype wire, 2-space indentation, grouped
input wire / output wire port declarations, aligned internal wire declarations, and
u_<module> instantiations with aligned port maps and // input / // output comments.
Internal connection wires are named from the port base without _i / _o suffixes.
Generate — asks where to save first (Save As dialog, default <workspace>/<wrapperName>.v)
and only writes the file once you confirm — cancel and nothing is written; the file is then
opened, and your own edits are yours to save with Ctrl+S. Every submodule is instantiated with
all of its ports; connected ports share an internal wire, unconnected ports become the
wrapper's top-level ports.
- Sub-module parameters become
localparams: <module abbreviation>_<PARAM>
(rw_ble_event_cntl.DATA_WIDTH → BEC_DATA_WIDTH) — the abbreviation drops vendor prefixes
(rw / act / hdt / ip) and takes the initials of the remaining words (fifo_sync → FS,
ahb2em → AHB2EM); one localparam per module (shared by all of its instances) and the
instantiations reference it (#(.DATA_WIDTH (BEC_DATA_WIDTH))).
- A module placed several times gets numbered nets: instances
u_<module>, u_<module>_1, …
and the wires they drive carry the same suffix — data, data_1, data_2 — so the two
instances can never be shorted onto one net by name.
- The canvas shows these rules (plus the auto-connect
_i / _o matching rule and examples) in
red text right above the canvas.
Create Testbench… — Command Palette → Verilog RTL Connector: Create Testbench…, or
right-click a .v / .sv file in the Explorer (also in the editor context menu). You pick the
folder (nothing is written before you confirm) and the extension writes a six-file split
testbench there — in a tb_<module>/ subfolder it creates inside it (picking that subfolder
itself does not add another level), all SystemVerilog:
| File |
Role |
<module>_package.sv |
a simulation package <module>_pkg: shared constants, clog2 / to_bin / to_hex / time_tag functions and print_rule / print_info / print_warn tasks (no class is generated — add your own stimulus class there if you want one) — included at file scope and imported by the top |
<module>_init.sv |
interface signals (all reg, names = port names), testbench parameters (TB_CLK_PERIOD / TB_RESET_CYCLES / TB_TIMEOUT_TIME / TB_SEED), clock generation, reset sequence + default drive, the VCD / FSDB dump selected by a compile-time macro, the absolute-time safety timeout and the tb_check / tb_fail helpers that drive chk_count / err_count |
<module>_inst.sv |
the DUT instantiation (u_<module>, named connections, #(.P (P)) when the module has parameters) |
<module>_bus_connect.sv |
commented templates for tie-offs, force / release pokes, pullup and internal-node monitors |
<module>_test_case.sv |
one task per test case plus the run_test() dispatcher that TB_TEST_CASE selects |
<module>_tb.sv |
the top / test-framework shell: module <module>_tb; includes the package before the module and the four parts inside it, prints the banner, calls run_test() and prints the [RESULT] ALL TESTS PASSED! / [RESULT] TEST FAILED with %0d errors. verdict |
Because the `include directives of the four parts sit inside the module, they share the signals
declared in *_init.sv; the package is file scope and is pulled in with import <module>_pkg::*;.
Dumping is opt-in: +define+DUMP_WAVE_VCD writes <module>_tb.vcd ($dumpvars),
+define+DUMP_WAVE_FSDB writes <module>_tb.fsdb ($fsdbDumpfile / $fsdbDumpvars / $fsdbDumpMDA,
needs the Verdi FSDB runtime), +define+DUMP_WAVE is still accepted as an alias of the VCD one, and
without any define nothing is dumped. Only the top has `timescale / `default_nettype /
endmodule, and every file carries the spec file header. A clockless module falls back to
#(TB_CLK_PERIOD) delays (no @(posedge …) anywhere), and an existing file is never overwritten
silently.
Insert Comment Banner… — Command Palette → Verilog RTL Connector: Insert Comment Banner…,
or the editor right-click menu of a .v / .sv file. Inserts the project's section banner
above the current line, with the current line's indentation:
//==========================================================================
// xxxxxxxxxx
//==========================================================================
The picker offers the banner titles already used in the file (first entry: Custom title…),
so a file keeps a consistent set of section names. The // + 74 = format is identical to the
banners in the RTL spec and in the generated code.
Create Spec-Compliant Wrapper (Ctrl+Shift+P → Create Spec-Compliant Wrapper…) — finds
modules whose ports break the interface rules of the project RTL spec (missing _i / _o /
_b suffix, ascending bit ranges such as [0:7]) and generates a wrapper that exposes a
compliant pin list while instantiating the original module 1:1 inside. The wrapper gets the
standard file header, `default_nettype guards, the source's own //$port_g interface
groups, a 44-column aligned pin list (or keyword groups when the source has no markers),
a u_<module> instantiation with // input / // output comments, and keeps port-level
`ifdef blocks. Clock / reset / interrupt / bus ports are exempt from the suffix rule.
Create Filelist… (<folder>_filelist.f) — two ways in, one command, with different output:
- right-click a folder in the Explorer (or a
.v / .sv / .vh / .svh, which uses its directory)
→ Verilog RTL Connector: Create Filelist… → the folder is scanned and a save dialog opens,
defaulting to <folder>_filelist.f (next to the folder, or inside it when its parent is
outside the workspace). The file holds the absolute path of every .v / .sv below the
folder (recursive, sorted, quoted when they contain spaces) and one
+incdir+<absolute directory> line per directory holding a .vh / .svh (deduplicated, listed
first). Header files are never listed as file entries — they are only `included.
Hidden folders and node_modules are skipped; cancelling writes nothing.
Ctrl+Shift+P → Create Filelist… → pick the folder you want the filelist for.
Nothing is written to disk: the same content is inserted at the cursor of the active editor
(handy to edit by hand or to paste into an existing .f). The block uses the editor's line
endings and only adds a line break where the cursor line really holds code — on an empty line
you get exactly the paths. Ctrl+Z undoes it.
Create Simulation Environment… — Ctrl+Shift+P → Verilog RTL Connector: Create Simulation
Environment…, or right-click a .v / .sv file in the Explorer (also in the editor context
menu, next to Create Testbench…): the right-click route uses the file you clicked instead of
the active editor (with the in-editor content when it is the same file). With the module's file
known (pick the module when the file has several), choose a folder and the extension creates
sim_<module>/ inside it, filled with the bundled VCS + Verdi + SpyGlass simulation
template (sim_test/): compile.sh,
simulate.sh, coverage.sh, cov_analyze.py, run_verdi.sh, sim_lib.sh, lint_extra.tcl,
sim.cfg, both example lists and the template READMEs — 12 files, all copied byte for byte
except the three that depend on the design:
sim.cfg → TOP / TB_TOP (rest untouched),
rtl_filelist.f → the module's own source, relative to the environment folder
(../rtl/<module>.v, forward slashes, absolute when it lives on another drive),
tb_filelist.f → the testbenches next to the module (tb_*.v|sv, *_tb.v|sv) including the
ones in its one-level tb_* subfolders — where Create Testbench… puts them; its own
<module>_tb.v first), or a TODO comment when there is none.
Paths are never quoted (the template's list parser matches whole lines), so a path with a space
gets a WARN comment instead; the generated lists always use LF. The environment is created
next to the module by default, an existing sim_<module>/ asks before overwriting (files you
added there survive) and sim.cfg is opened afterwards — the notification reminds you to
chmod +x *.sh once on Linux and to review the template's remaining example values
(TB_DEFINES / MACRO_ALIASES / COV_TREES).
OMIT Module… — Ctrl+Shift+P → Verilog RTL Connector: OMIT Module…, or right-click a
.v / .sv file (Explorer and editor context menus; the right-click route uses the file you
clicked). The module's main logic is wrapped unchanged in `ifndef <MODULE>_OMIT (the module
name upper-cased: backoff.v → `ifndef BACKOFF_OMIT) and the `else branch ties the
outputs off, so +define+BACKOFF_OMIT turns the block into an empty shell:
- wire outputs →
assign; reg outputs → one always @(*) begin … end with blocking
assignments (no latch, no N09/N14 finding);
- a 0 tie-off is written as
'd0 — no width, it zero-extends on its own
(assign data_o = 'd0;);
- ready-like outputs are tied to 1 (
ready / rdy as its own word, plus the bus
channels hready, hreadyout, awready, wready, arready, rready, bready, pready) so
an upstream handshake never hangs on the omitted block;
- inout ports are tied off like outputs (
assign sda_b = 'd0;) — a bidirectional port may be
read too, so it gets a defined value; inputs are never driven (driving an input from inside
the module is illegal/flagged);
- ports declared inside
`ifdef / `ifndef / `elsif / `else in the port list
keep their guard: the tie-off is wrapped in the same directives (nesting, elsif and else
branches included), so when the macro is off the generated code never references a port that does
not exist. Macro widths ([`AW-1:0]) are parsed as widths, not as directives;
- names are aligned like the spec's §8.2
`else pattern and the inserted lines use the editor's
own line endings.
The newly inserted lines are highlighted (`ifndef plus the whole `else … `endif
block) with a soft yellow background, and the highlight disappears as soon as you save.
Running it again on a module that is already OMIT-ed is safe: the command compares the
guard's `else branch with the current port list and
- if every output / inout is already tied off, it just says so (“already OMIT-ed and every
output / inout port is tied off — nothing new to OMIT”) and changes nothing;
- if the port list grew (a new output / inout was added later), it only appends the missing
tie-offs before
`endif — the existing ones are never duplicated.
Non-ANSI port lists (directions declared in the body) are refused. The edit is applied to the
editor buffer only (one Ctrl+Z undoes it) and you are asked Save / Keep Unsaved — if you
keep it unsaved the file stays modified and the dot next to its name in the Explorer marks it,
ready for Ctrl+S.
Remove OMIT Module… — the undo of the above: `ifndef <MODULE>_OMIT and the whole
`else … `endif tie-off branch are deleted, so the module body is back to its original
self (byte-for-byte — blank lines around the body included). Use it when the block no longer needs
to be stubbed out: Ctrl+Shift+P → Verilog RTL Connector: Remove OMIT Module…, or
right-click the .v / .sv file (in a multi-module file only the modules that really carry the
guard are offered). A file without the guard is reported (“not OMIT-ed”) and left untouched; the
same Save / Keep Unsaved prompt follows, and the yellow highlight is dropped.
Every edit the extension makes is highlighted — whenever a command writes into a document
(OMIT / Remove OMIT, Auto Instantiate, Insert Comment Banner, Insert Code Template, Create
Filelist at the cursor, Wire Modules), the lines it actually changed get a soft yellow
background plus a mark on the overview ruler. The highlight stays until you save the file —
scrolling, switching files, undoing or waiting do not clear it, and files that were not visible
at edit time get their highlight when you open them. Newly created files (wrapper, testbench,
sim env, port-table skeleton, exported diagrams) are not highlighted.
Show Schematic Diagram — select a piece of Verilog (an assign, one always block, a case, an
instantiation — anything from one line to a whole module) and right-click → Verilog RTL Connector:
Show Schematic Diagram. The selection is checked first: an unbalanced begin … end, case … endcase, module … endmodule, an unclosed (, a dangling `ifdef or a selection without any
drawable statement is reported (naming exactly what is missing) and nothing is drawn; a complete
selection opens a circuit schematic beside the editor, drawn in the hand-drawn textbook style:
data flows left → right, signal names are plain monospace text (inputs on the left, the output name
in red bold), and every cell is a big block with a title plus an italic type line —
comparators in purple (== IDLE / comparator), MUX in orange as a plain rectangle with 0 / 1
on its inputs and SEL at the bottom (MUX / 2:1), DFFs in teal with D / CLK / CLR_N / Q
pins, a filled clock triangle, a low-active bubble and the register name as subtitle, and combinational
logic as real gate symbols (AND / OR / NOT / XOR with the classic &, ≥1, =1 glyphs; + - ==
as rounded blocks). Wires are 2 px with junction dots, routed to cross as little as possible and
never cut through a cell (they go along a free row, over the top or around the bottom), net names are
printed on them, and the clock / reset nets of a DFF always get port cells so CLK / CLR_N really
connect. A green box at the bottom prints the next-state formula of every register
(D(q) = en ? d : Q(q)), the title is centred above a thin rule, boxes carry the source line in their
tooltip, and Refresh re-reads the current selection. The diagram is drawn with HTML + CSS
(divs, border-radius, clip-path), so the text stays crisp at any zoom; Save as SVG /
Save as PNG export it as an image. Typical uses: read a mux chain, check what an always block
really builds, or paste a schematic into a review.
Configure Macro — Ctrl+Shift+P → Verilog RTL Connector: Configure Macro (or the editor-title
menu under RTL Design ▸ Check). It scans every macro of the project (.v / .sv / .vh /
.svh) and lists them in a panel where:
- switches (
`define X — no value) get a checkbox, value macros
(`define X 16) get a checkbox + a value box;
- commented-out defines (
//define RW_DM_XBAR_BUS`) are found too and show up as off, ready to
be ticked — that is how RivieraWaves-style projects switch features on and off;
- master switches are highlighted with ★ and sorted first (a macro referenced by the
`ifdef of
at least 5 % of the files, minimum 2 files — 17 of 177 in the bundled 251-file test project), with a
master switches only filter;
- every row shows how often it is used in
`ifdef / `ifndef / `elsif, in how many files,
and a clickable file.v:line of its definition; a filter box, Apply, Reset, Refresh and
Copy defines (one `define line per enabled macro → clipboard) sit above the list.
Code that is not compiled is greyed out: with the configured set, every line inside an
`ifdef / `ifndef branch whose condition is false — and the losing branch of an
`elsif / `else chain — is drawn in grey, nesting included, following a file's own
`define / `undef in order and updating as you type. It is display only (no file is ever
changed) and can be switched off with verilog-rtl-connector.macroHighlight.enable. Your choices are
saved to .vscode/verilog-rtl-connector.macros.json, a plain diff-able JSON you can commit.
Show Signal Path — put the cursor on a signal (port, wire / reg / logic) and
right-click → Verilog RTL Connector: Show Signal Path (or the editor-title menu under
RTL Design): a diagram opens beside the editor with the driver module on the left and
every load module on the right, arrows always driver → load. Each box is the
instance name (u_rw_ble_event_cntl_scanini) over its clk: / rst: lines and the ports that
carry the signal (in .gnt_i / out .gnt_o) — one field per line, so text never overlaps (long
names are clipped to …, and a long clk: / rst: list shows the first name plus +N with the
full list in the tooltip). Boxes are sized from their own content per column (clamped to
150–360 px, side padding 10 px), so every load box is the same size while a short (outside)
box does not waste the width of the widest box; the type is large enough for screenshots
(instance name 17 px, clk/rst 14 px). The driver and the load modules are looked up in the whole
project (every .v / .sv in the workspace, not just the folder of the current file) — a driver
in the parent IP or a load in a sibling folder is found instead of showing up as a dashed
placeholder, and the header states how many files were scanned. Every box ends with a role line
saying what it does with the signal: real load / real driver (it reads / drives it in its own
logic) versus path (the signal only passes through it on the way to a child instance), spelled out
as real load + path when both apply — so a relay module is never mistaken for a real consumer.
Only modules that really use the signal are drawn: a module that merely routes the signal on gets
no box at all, and its instance chain is printed inside the real-load box as a small blue-grey
path: line — every hop joined with ->, wrapped continuation lines keep the arrow and line up,
and a name that cannot fit is clipped to … (the full chain is always in the tooltip):
path: u_rw_ble_core -> u_rw_ble_core_cntl -> u_rw_ble_event_cntl -> u_rw_ble_event_cntl_pti
(that is et_pti_prio in rw_ble_event_cntl.v; the notes list the modules that were left out). The
search follows the hierarchy up to 6 levels (which is what makes a driver three levels above the
cursor show up instead of a dashed placeholder) and also follows the signal down through instantiations
up to 4 levels, so a real load hidden inside a wrapper is found and reported as real load while the
wrapper stays path. The header (and the panel line) names them in words, e.g.
real load: u_rw_ble_event_cntl_pti · path only: u_rw_ble_event_cntl, u_rw_ble_core_cntl, u_rw_ble_core.
Hover a box to see that module's
full .v file path and every clock / reset it has. Colours follow the
role, never the other way round: drivers (including a box that both drives and loads) are
purple, loads are orange, and the module the cursor is in uses a darker shade of its own
role colour and has every line of its box in bold (no current module badge) — so a driver box can never
look like a load box. Unresolved modules are dashed grey, and the arrows (including the arrow head)
are drawn in the colour of the driver they leave.
Every box shows its clock and reset as clk: macCoreClk / rst: macCoreClkHardRst_n — and a
clock is only a signal that an always sensitivity list is actually clocked by
(@(posedge …)): a port that merely has clk in its name (clkncnt) is not a clock, and neither
is an assert property (@(posedge …)) edge. When a module has dozens of @(posedge clk …) plus
one stray @(posedge clkncnt), the dominant clock wins; a purely combinational module honestly
shows clk: -. Resets are name-checked and used in the body, so master_nrst +
master_soft_rst and hreset_n all appear correctly.
An output port of an instantiated module drives the net, an input port loads it,
assign s = … / s <= … makes the enclosing module itself the driver, and a signal on a
right-hand side or in a condition means the module reads it. When the signal is an
input / output of the enclosing module the parent instantiation is followed too (2 levels up),
so the driver of an input port shows up as the module above it — first searching the current
directory, then the whole workspace (cached, ~300 ms for 250 files). Unresolved drivers /
loads become dashed (outside) / (no load) boxes with the reason printed under the diagram, and
a fan-out cap (16 boxes) with a note keeps a clock net readable. Branches leaving a driver share
one trunk and each branch is labelled just above the line with the load port it enters
(.cnt_i), so every label sits on its own wire. Refresh re-reads the cursor,
Save as SVG / PNG exports the picture.
Wire Modules… (Auto-Wire) — from the Command Palette: pick the driver and the
receiver module with searchable, mouse-wheel-scrollable drop-downs, then add one row per
signal (name + width + optional driver/receiver port). All the controls of a row sit on one
line with a uniform height; there is no Chinese label anywhere in the panel.
- Show Hierarchy draws the path
driver … common parent … receiver as an SVG (the signal
turns around at the common parent; instance names and the wire label are shown);
- Preview Code lists every file/module that will change, with the exact lines and
// auto-wire: <start> -> <end> : <sig> comments;
- Generate Code inserts the internal
wire, the missing ports (auto-created as xxx_o /
xxx_i when no port matches) and the .port (signal) connections into the editor buffers of
the original files — it does not save them: the modified files are opened for you, you review
the changes and save them yourself (Ctrl+K S, or the Save These Files button in the
notification); Ctrl+Z undoes everything. The inserted code follows each file's own
formatting — indentation (tabs or the file's space step), the columns the neighbouring port
entries / .port (net) entries use (the most common column in that file wins), and the
// auto-wire: … comment is written in the right margin, aligned with the file's existing
trailing comments (port / wire lines get // auto-wire: …, connection lines get
// output [auto-wire: …]).
Verilog / SystemVerilog syntax highlighting — the extension ships its own TextMate grammars
(.v + .vh → source.verilog, .sv + .svh → source.systemverilog) plus a language
configuration, so all four extensions are tokenised even without any other Verilog extension
(.vh / .svh header files were previously opened as plain text). Run
Verilog RTL Connector: Apply Vivid Verilog Colors (offered once on first activation) to paint
keywords, types, directions, numbers, strings, comments, $tasks, macros and module/function
names with a vivid, easily distinguishable palette — written into
editor.tokenColorCustomizations for your current theme only (your own rules are kept;
… Remove Vivid Verilog Colors reverts it). Dark and light variants are chosen automatically.
Header files (.vh / .svh) are first-class citizens — they open as Verilog / SystemVerilog
with highlighting, comment toggling, block matching, completion, Ctrl+Click (including
`include "defs.vh" → the included file), // TODO highlighting and the OUTLINE view; they
are also scanned across the whole project together with the sources, so macros / typedefs /
parameters / functions declared in a header take part in the module index, the hierarchy scan, the
signal-path diagram and the instantiation port-direction lookup. Because a header is not a
compilation unit, Create Filelist… emits a +incdir+ line for its directory instead of a file
entry, Check RTL Code… still lints .v only, and module-level commands (wrapper / testbench /
simulation env / OMIT) stay on .v / .sv; Insert Code Template… and Insert Comment Banner…
are available in headers too. A workspace holding only header files activates the extension
(workspaceContains:**/*.vh / *.svh).
Verilog completion (templates + name hints) — no command needed, it works while you type in
.v / .sv / .vh / .svh files (the extension activates via onLanguage:verilog /
workspaceContains:**/*.v / *.vh / *.svh):
type always and get the clocked block in the project format
(always @(posedge clk or negedge rst_n) → if (!rst_n) reset branch → else main branch, §6.1);
type case, function or task for their templates, or type fsm for the
three-stage state machine (state register / next-state logic / output logic, §7.1 — localparam
ST_* states, <fsm>_cs / <fsm>_ns, a default assignment plus a default: branch so it is
latch-free). The FSM template is already recognised by Generate FSM Diagram…. Every template
follows xxx项目_数字设计RTL规范.md, is inserted at the cursor with tab stops and is unit-tested to
be complete (end / endcase / endfunction / endtask always present, block keywords
balanced).
On top of that, names
already written above the cursor (ports, wires, regs, parameters, modules, instances) are
offered as completions, and similar but different spellings are flagged with the reason
(prefix, the xxx_o ↔ xxx_i direction pair §10.3, letter case, or 1–2 characters off).
Toggle it with verilog-rtl-connector.completion.enable / .templates / .names.
Generate Ports from Table… (Port Table) — Ctrl+Shift+P → Generate Ports from Table….
Type the module name and one row per port (Port name, Direction input/output/inout,
Type wire (default) or reg, Signed signed (default) or unsigned, Width as the
number of bits — 8 becomes [7:0], 1 / empty is a single bit — and an optional Comment)
and use the panel buttons:
- Type is the net / variable keyword of the declaration: leave it on
wire (the default) for
most ports, pick reg for an output driven from an always block — the generated line becomes
output reg [3:0] cnt_o,;
- Signed is off by default: an
unsigned port writes nothing at all, so the keyword only
shows up on the rows where you actually pick signed (wire signed / reg signed). The
columns stay aligned (unsigned rows keep the spaces so widths line up) and when no row is
signed the column disappears from the output completely;
- Check — reports every problem with a rule number, the row and a suggested value: Verilog
itself (
V01 illegal identifier, V02 reserved keyword, V03 duplicate port, V04 width
syntax, V05 module name, V06 port named like the module, V07 case-only duplicate, V08
empty table, V09 comment with a line break, V10 unknown type, V11 unknown SIGNED value,
V12 bad macro line, V13 unknown directive name, V14 unmatched `ifdef / `endif)
plus the project RTL spec (missing _i / _o /
_b suffix §10.1, ascending range §3.2, 31-character names §11.1, clock/reset first §3.1).
One click on Apply fixes the field;
- Suggest Names — a compliant name per row from
xxx项目_数字设计RTL规范.md §10 / §11
(clock→clk, err→error, address→addr, enable→en, camelCase→lower_case, the
direction suffix with the §10.2 exemptions…), each with its reason and an Apply / Apply all
button;
- Generate Code — asks where to save first (default
<module>.v next to the active
editor, or in the workspace root), then writes the full spec skeleton (the port list, aligned
columns, comments + §1 file header + §2 `default_nettype guards) and opens it. An existing
file is only overwritten after a
modal confirmation; cancelling writes nothing. The notification offers Copy Port List for
pasting the list into an existing file. Nothing is generated while Check still reports
Verilog errors;
- + Add Port / + Add Comment / + Add Macro — a new row is inserted below the row you
last clicked. + Add Macro inserts a conditional-compilation row: type the directive
(
`ifdef SIMPLE, `ifndef SIM_DBG, `else, `endif // SIMPLE, `define …)
and it is printed exactly as typed (indentation included) at that position, so the ports after
it are compiled only when the macro matches. Macro rows are violet, Check reports unmatched
`ifdef / `endif (V14) and a line without the leading backtick (V12), and a port name
that appears in two different branches is not reported as a duplicate. + Add Comment inserts a
section banner: you type the title, the tool puts the two
//=== rules around it (//===== / // title / //=====), skips it entirely when the title is
empty and inserts a blank line before it. The input of that row is green (the same green
as the Excel buttons), so a banner row is easy to tell apart from a port row. The
Clock and Reset banner is the only one added
for you — every other section is yours to place, and the ports otherwise keep exactly the
order of the table (only clock / reset ports are moved to the top, and only while the
Clock and reset ports first (§3.1) checkbox is ticked);
- Import From Excel / Export To Excel (green) — round-trip the table through the
port_table_template.xlsx layout (信号名 / 位宽 / I/O / 类型 / SIGNED / From / To / 默认值 / 信号描述; a row
with only the first column is a section title, a //-prefixed one is a banner, the width column
holds the number of bits). Columns are recognised by their header, so an older 7-column
sheet (and a sheet whose columns were reordered) still imports — a missing 类型 / SIGNED cell just
means wire + unsigned. Import From Excel reads a .xlsx / .xlsm and replaces the table
(with a modal confirmation when the table already has rows), mapping I/O/IO to the
direction dropdown and flagging unknown I/O and unusable names instead of dropping rows
silently. Export To Excel saves the current table in the same layout (default
<module>_ports.xlsx) and offers Open in Excel.
From / To / default value / group are not shown in the panel — they stay attached to
each row (hover a row to see them) and only take part in the Excel round-trip, never in code
generation. Both directions are implemented in a hand-written, dependency-free
src/xlsx-core.ts (own ZIP writer/reader, own CRC32, zlib.deflateRawSync). The shipped
port_table_template.xlsx is a small, ready-to-use example: clk / rst_n, two // section
banners and both width spellings (a bit count and the parameter AA_WIDTH=10);
- New Port Table (pink, top-right corner) — resets the panel so you can start a new
table: module name, all rows, the Check/Suggest results and the code preview are cleared, the
§3.1 checkbox goes back to its default and the two starter rows (
clk, rst_n) are back.
No file is touched, and when the table is not empty a modal confirmation says exactly what is
about to be cleared.
Width can be a custom macro. An all-upper-case name becomes a module parameter:
WIDTH → [WIDTH-1:0] plus parameter WIDTH = 8, and WIDTH with an explicit default —
AAAA=5 — becomes parameter AAAA = 5 (a name used more than once takes the default from the
occurrence that has one). Macros inside a range ([DATA_WIDTH-1:0]) are collected as well; the
parameter list is de-duplicated in order of appearance with the = signs aligned:
module rw_ble_core_cntl #(
parameter DATA_WIDTH = 32,
parameter CFG_W = 8
) (
//======================================================================
// Clock and Reset
//======================================================================
input wire [CFG_W-1:0] cfg_i, // configuration
//======================================================================
// AXI write channel
//======================================================================
input wire [DATA_WIDTH-1:0] axi_data_i, // write data
input wire valid_i // write valid
);
Check RTL Code… (Lint) — static review of a .v file with fix suggestions (.sv files
are not checked). Open it from the Command Palette (Shift+Ctrl+P, then pick a .v file — the
file of the current editor comes first) or by right-clicking a .v file in the Explorer.
It reports, with line numbers and a suggested fix for every item:
- syntax errors (unbalanced
()/[], module/endmodule, unbalanced begin/end,
case/endcase, function/task/generate/fork pairing — reported per broken block with
the exact line range L12–L40 (where the opener sits and where the closer has to be added),
plus a stray closer as its own single line,
unterminated comments and `ifdef,
missing ;, duplicate modules/ports/signals, case without default, if without
parentheses, = in an if condition, assign with <=, unterminated strings, module header
without ;, nested modules, else without if, comma errors / a port connected twice in a
port map, a case item without :, duplicate default:, a signal that is used but never
declared);
- port-map checks against the workspace (the module index is built once and cached): an
instantiated module that cannot be found, a port that does not exist in that module, a width
mismatch on a connection and ports that are left unconnected;
- not-synthesizable code (
initial, # delays — only real ones: #10, #(1), assign #2;
the #(...) parameter list of module m #(...) and of an instantiation is not reported,
$display/$finish, fork/join,
force/release, wait/while, disable, x/z, casex, clock without edge, always
without an event control, <= in combinational logic, = in clocked logic, if without
else (latch), mixed =/<=, assignment to an input port or to a wire in a procedural
block, a signal driven from two blocks, real/time);
- optimisation suggestions from the project RTL spec (
xxx项目_数字设计RTL规范.md) — port
suffixes _i/_o/_b (§10.1), ascending ranges (§3.2), named port connections (§9.1),
internal signals without _i/_o (§10.4), port groups / clock first (§3.1), the file
header (§1) and the `default_nettype guard (§2).
The report opens in a panel beside the editor (click a line number to jump to it, press
Re-check after editing) and every finding is also published to the Problems view.
The top of the report shows N error(s) / N warning(s) / N suggestion(s) and the number of rule
classes; the findings are then split into three sections — Syntax / usage, Not
synthesizable and RTL spec suggestions — and each section title prints its own severity mix,
so the section numbers always add up to the chips above.
Findings of the same rule are grouped into one collapsible block (group header = rule,
count, severity and one generic fix hint; the line numbers of identical findings are listed
side by side, each with the offending code and its specific fix in the tooltip), so a file with
dozens of findings stays short and readable. If one rule has findings of different severities
the header shows mixed and every row is tagged with its own error / warning /
suggestion badge. A finding that spans several lines (a begin that is never closed) is shown
as L12–L40, hovering it explains the range, clicking it selects exactly those lines, and the
Problems view underlines the whole range.
Block keyword pairing highlight — put the cursor on a begin (or end, case,
endcase, function, endfunction, task, endtask, generate, fork, join, …) and the
keyword you are on and the one it pairs with are both boxed in yellow, so the other half of a
long block is visible at a glance (an overview-ruler mark shows where it is when it is off
screen). Nested blocks pair up level by level, clicking the end finds its begin too, and a
keyword without a partner is boxed in orange as a hint. No command needed; switch it off with
verilog-rtl-connector.blockMatch.enable.
Go to Definition (Ctrl+Click) — no command, no activation: open a .v / .sv file, hover a
name and Ctrl+Click (⌘+Click on macOS) to jump to
`include "file.sv" → the included file (resolved relative to the current file, then a
workspace-wide search by file name);
- a
task / function call or declaration → the declaration (the return type is skipped, so
function automatic int clog2(…) jumps to clog2);
- a
class usage (Foo x = new();, Foo::bar(), function Foo copy();) → class Foo;;
- a port name, a signal (
wire / reg / logic / bit / genvar / integer …,
multi-line declarations included), a parameter / localparam (also the ones in the module
header's #(...)) → its declaration;
- an instance name (
u_fifo, e.g. in a hierarchical reference u_fifo.busy_o) → the
module it instantiates (and an instantiation's module name → that module's declaration);
- a
module / interface / program / package / interface name → its declaration;
- a macro —
`RW_BLE_SUPPORT (also when the cursor sits on the backtick), the bare name in
`ifdef / `ifndef / `elsif / `undef, and a value macro used without a
backtick in an expression → its `define line (current file → include chain → whole
workspace, so macros that only live in a defines.v / xxx.vh are found);
- several same-named declarations → a peek list with the definition from the cursor's scope
first.
Cross-file works out of the box: the `include chain is followed (up to 5 levels) and the
sibling parts of a split testbench (<stem>_package / _init / _inst / _bus_connect / _test_case / _tb.sv) are indexed, so run_test() in xxx_tb.sv jumps into xxx_test_case.sv and
print_info() in xxx_test_case.sv jumps into xxx_package.sv. When none of those has the name,
the whole workspace is searched (index built on the first such click, reused for 60 s,
invalidated on save), so an instantiated module that lives in another file — backoffACSel in
backoff.v — jumps to module backoffACSel in backoffACSel.v; definitions from the same
directory are preferred. Comments and strings are masked before scanning (a commented-out
// task foo(); or a //`define X is never a definition). If nothing happens, run
Verilog RTL Connector: Show Go to Definition Status to see the setting, the word under the
cursor, the include-chain symbols, the workspace index and the candidates it found. Switch the
feature off with verilog-rtl-connector.gotoDefinition.enable.
Hover: what is this name? — no command needed. Rest the mouse on a name and a popup shows
- a signal / port / parameter: the declaration line as written (its trailing comment
included), a type summary such as
output [7:0], the owning module and file · line;
- a macro (
`NAME or `ifdef NAME): its `define line with the value
(`define RW_DM_ADDRESS_WIDTH 16), the comment above the definition and where it lives
(defines.v · line 37);
- an instance name →
instance of <module> plus the declaration line, and a module /
task / function / class → its declaration.
It uses exactly the same resolution as Ctrl+Click, so everything you can jump to is also shown,
and the definition from another file is read (and cached) on the fly. Hover never triggers a
workspace scan of its own — it reuses the index the Ctrl+Click already built, so moving the mouse
stays fast. Switch it off with verilog-rtl-connector.hover.enable.
Explorer OUTLINE: all tasks and all functions — the OUTLINE view under the Explorer lists
the routines of the active file in two groups, Tasks (n) and Functions (n), in source
order; the grey text on the right is the owning module / package / class, and clicking an
entry selects the whole body (declaration line → endtask / endfunction). It is on by default and
needs no command — open a .v / .sv file and it is there (the same symbols also feed
Go to Symbol, Ctrl+Shift+O, and the breadcrumbs). Comments and strings are ignored, so a
commented-out // task foo(); never appears. Switch it off with
verilog-rtl-connector.outline.enable and the OUTLINE goes back to whatever else you have.
// TODO highlight — switched on by Apply Vivid Verilog Colors — the shipped TextMate grammar
tags TODO / FIXME / XXX inside line and block comments with the scope
keyword.other.todo.verilog, but nothing is coloured until you run
Verilog RTL Connector: Apply Vivid Verilog Colors. That one command (a) writes the palette — where the
TODO row is a bright yellow #FFEB3B bold + underlined text colour for dark themes (#A67C00 for
light ones) — into your editor.tokenColorCustomizations, and (b) sets
verilog-rtl-connector.todoHighlight.enable to true. Remove Vivid Verilog Colors takes both away
again. While it is on, an editor decoration adds the highlighter chip: #FFEB3B background
(configurable, opaque by default), #3B2E00 text (verilog-rtl-connector.todoHighlight.foregroundColor,
empty = keep the theme colour), a dark amber outline — plus an overview-ruler mark and a
status-bar counter
($(checklist) TODO: 3) while the active RTL file has hits —
click it (or run Show TODO Highlight Status from the palette) for the setting values, the active
editor and the hit lines. Only comments are scanned (a "// TODO" string literal is ignored), matching
is case-insensitive and whole-word, and //TODO without a space, tabs and CRLF all work. You can also
flip verilog-rtl-connector.todoHighlight.enable yourself, and tune .keywords / .backgroundColor /
.foregroundColor.
Usage
Open your Verilog project as a folder in VS Code.
Open the command palette (Ctrl+Shift+P) → Verilog RTL Connector: Open Top Connector.
In the Modules tree on the left, click a module's + to add it to the canvas.
The Working panel on the right shows what you've added.
Drag a card header to move a module; drag a port dot onto another port to connect.
Or press Auto Connect to match by name (with _i/_o suffix stripping).
Set a Wrapper name and click Generate (ports you leave unwired become top-level I/O).
To make a legacy module comply with the interface spec there are two entry points:
- Command Palette (
Ctrl+Shift+P) → Verilog RTL Connector: Create Spec-Compliant Wrapper… — scans the whole workspace and lists every module whose ports break the spec.
- Right-click a
.v / .sv file — in the Explorer file list or inside the editor —
→ Verilog RTL Connector: Create Spec-Compliant Wrapper… (only that file's modules;
when the file has exactly one module to fix the picker is skipped).
Either way the extension creates <module>_wrapper.v next to the module it wraps and
opens it. That file contains module <module>_wrapper (spec-compliant port list) plus one
instantiation u_<module> of the original module — the wrapped .v file itself is never
modified (if <module>_wrapper.v already exists you are asked to overwrite or save as).
To build a compile filelist: right-click a folder in the Explorer →
Verilog RTL Connector: Create Filelist… → choose where to save it (the dialog defaults to
<folder>_filelist.f). From the Command Palette the same command asks which folder the
filelist is for and then inserts the content at the cursor instead of writing a file — use
that when you want to assemble or edit a .f by hand.
To start simulating a module: open its .v / .sv file (or right-click it in the Explorer —
the file you click wins), then Ctrl+Shift+P →
Verilog RTL Connector: Create Simulation Environment… → pick the folder that should
contain the environment. sim_<module>/ is created there with the bundled VCS / Verdi /
SpyGlass template (compile, simulate, coverage, waveform and lint scripts) and the three
design-dependent files (sim.cfg, rtl_filelist.f, tb_filelist.f) are pointed at the module you
ran it on. sim.cfg opens right after; on Linux run chmod +x *.sh once inside the new folder.
To disable a block for a while: open its file (or right-click it in the Explorer) →
Ctrl+Shift+P → Verilog RTL Connector: OMIT Module…. The whole module body is wrapped in
`ifndef <MODULE>_OMIT (module name upper-cased) and the `else branch ties the outputs
off ('d0, ready-like signals to 1); the inserted lines stay highlighted until you save. You are
then asked whether to save. Run it again on the same module and only new output / inout ports
get a tie-off (or you are told there is nothing left to do); Verilog RTL Connector: Remove OMIT Module… puts the original body back.
To wire two modules together: Ctrl+Shift+P →
Verilog RTL Connector: Wire Modules… (Auto-Wire) → choose the Driver module and the
Receiver module (type to search, or scroll the list with the mouse wheel) → add a row per
signal with its Signal name, Width (8, [7:0], W-1:0, empty = 1 bit) and the
Driver port / Receiver port ((auto match) by default, (auto create new port) to
force a new port, or pick any existing port). Then:
- Show Hierarchy — visualize the hierarchy path and the connection from start to end
(the common parent is the turning point; every edge shows its instance name and its port
mapping);
- Preview Code — see the files/modules/comments that will be modified;
- Generate Code — modify the original files: the connection is created and marked as
added by the auto-wiring tool. New ports are inserted at the head of the port list, and
a level that only receives (or only drives) the signal is connected port-to-port (no
extra internal wire /
assign); the internal wire is only created at the common parent.
Running it twice changes nothing (already wired = skipped).
- To create a new module's port list:
Ctrl+Shift+P →
Verilog RTL Connector: Generate Ports from Table… → fill the module name and one row per
port (name / direction / type wire or reg / signed on or off / width in bits, e.g. 8 /
optional comment) → Check to validate the naming (Verilog + the project spec, with
one-click fixes) → Suggest Names for spec-conform names → Generate Code, which asks for
a file name/location and then writes the spec skeleton (port list + §1 header + §2 guards) and
opens it.
Already have the ports in a spreadsheet? Import From Excel loads a .xlsx written in the
port_table_template.xlsx layout, and Export To Excel saves whatever is in the table back to the
same layout.
Example
Given the sample in sample/example.v (u_src → u_proc, plus top_iface), with only
u_src.data → u_proc.data and u_src.valid → u_proc.valid connected (everything else left
unconnected), Generate produces a spec-styled wrapper:
`default_nettype none
module top_wrapper (
//======================================================================
// Clock and Reset
//======================================================================
input wire clk,
input wire rst_n,
//======================================================================
// External Interfaces
//======================================================================
output wire done,
output wire [7:0] result
);
//==========================================================================
// Internal Wires declarations
//==========================================================================
wire [7:0] data;
wire valid;
//==========================================================================
// Sub-Module Instantiation
//==========================================================================
// Module : top_iface
// Instance : u_top_iface
u_top_iface u_top_iface (
.clk (clk ), // input
.rst_n (rst_n ), // input
.result (result), // output
.done (done ) // output
);
// Module : u_src
// Instance : u_src
u_src u_src (
.clk (clk ), // input
.rst_n (rst_n), // input
.data (data ), // output
.valid (valid) // output
);
// Module : u_proc
// Instance : u_proc
u_proc u_proc (
.clk (clk ), // input
.rst_n (rst_n ), // input
.data (data ), // input
.valid (valid ), // input
.result (result), // output
.done (done ) // output
);
endmodule
`default_nettype wire
Develop
npm install
npm run compile # tsc -> out/
# Press F5 in VS Code with this folder open to launch the Extension Development Host.
Run the logic tests (parser + code generator) without VS Code:
npm run compile
node test/run-tests.js
Package (build a local .vsix)
npm install -g @vscode/vsce # one-time
vsce package --allow-missing-repository
# On Windows PowerShell the .ps1 launcher may be blocked; use the .cmd:
vsce.cmd package --allow-missing-repository
This runs vscode:prepublish (tsc) then produces verilog-rtl-connector-<version>.vsix.
test/, sample/ and the source artwork are excluded via .vscodeignore. Install it in VS Code
via Extensions → ⋯ → Install from VSIX….
Limitations (intentional MVP scope)
- Port parsing targets the common ANSI style (
module m (input wire clk, ...)). Non-ANSI
separated declarations are partially supported; very unusual port constructs may need manual wiring.
- The RTL Code Check is a static, heuristic checker (no compiler / no full parser): it catches
the common mistakes listed above, it is not a replacement for a real simulator or a linter such as
Verilator. It checks
.v files only. There is no syntax highlighting of our own — pair this with
mshr-h.veriloghdl or AndrewNolte.vscode-system-verilog for that.
- Auto Connect only wires real signal flow (
output → input); it does not auto-drive a net
from one input to another. That is intentional — input↔input pairs are simply left
unconnected and therefore surface as top-level wrapper ports (e.g. a shared clk becomes one
top-level clk driving every instance).
License
MIT — do whatever you like, just keep the header.
| |