Why GVim for VLSI?
EDA tools — Synopsys Design Compiler, Cadence Genus, Mentor Questasim, Vivado — all run in Unix/Linux environments where Vim is the dominant editor. Many EDA GUIs embed a Vim-compatible editor. Being fluent in Vim means the same muscle memory works everywhere: on a remote server via SSH, inside the Synopsys shell, or editing a 50,000-line synthesized netlist.
- Available everywhere: Every EDA Linux server has Vim. No installation needed, no license.
- Handles large files: Vim opens a 200 MB gate-level netlist without hesitation. Most IDEs choke.
- Regex is native: Renaming 200 signal connections across a module instantiation takes one command, not 200 clicks.
- Macros replace scripts: Repetitive port formatting, comment insertion, and signal alignment tasks that would need a Python script can be done as a Vim macro in 20 seconds.
- GVim vs Vim: GVim is Vim with a GUI window — you get the same commands plus a menu bar, clipboard integration, and better font rendering. All commands below work in both.
Modal Editing — The Core Concept
Vim has distinct modes. Every key does something different depending on which mode you are in. This is what beginners find confusing and experts find powerful.
Navigate, delete, copy, paste, run commands. This is the default state.
Type text as you would in any editor.
Select text by character, line, or block for bulk operations.
Run Ex commands: save, quit, search/replace, run shell commands.
Essential Navigation
All navigation happens in Normal mode. Never use arrow keys — the hjkl keys keep your hands on the home row.
Basic Movement
| Key | Action |
|---|---|
| h j k l | Left / Down / Up / Right (one character/line) |
| w / b | Next / previous word start — jumps by identifier boundaries. In Verilog: skips over always_ff word by word |
| W / B | Next / previous WORD — jumps by whitespace only. Faster for long signal names |
| e / ge | End of next / previous word |
| 0 / ^ | Start of line / first non-blank character |
| $ | End of line |
| gg / G | First line / last line of file |
| 42G or :42 | Go to line 42 — essential for jumping to a synthesis error line number |
| { / } | Previous / next empty line — jumps between always blocks in Verilog |
| % | Jump to matching bracket/parenthesis — great for checking module port lists |
Screen Navigation
| Key | Action |
|---|---|
| Ctrl+f / Ctrl+b | Page forward / backward |
| Ctrl+d / Ctrl+u | Half-page down / up — smoother scrolling through long modules |
| zz / zt / zb | Center / top / bottom the current line on screen — use after jumping to an error |
| H / M / L | Jump cursor to top / middle / bottom of visible screen |
| Ctrl+o / Ctrl+i | Jump back / forward in jump history — returns after a gd definition jump |
Search Navigation
| Key | Action |
|---|---|
| /pattern | Search forward for pattern |
| ?pattern | Search backward |
| n / N | Next / previous match |
| * | Search for the word under the cursor forward — fastest way to find all uses of a signal |
| # | Same, backward |
| gd | Go to local declaration of the identifier under the cursor |
Editing Commands
Entering Insert Mode
| Key | Action |
|---|---|
| i / a | Insert before / after cursor |
| I / A | Insert at start / end of line — A is the fastest way to append a semicolon |
| o / O | Open new line below / above and enter Insert mode |
| s | Delete character and enter Insert mode (substitute) |
| cw / ciw | Change word / change inner word — replaces the word under cursor |
| C | Change from cursor to end of line |
| cc | Change entire line |
Delete, Copy, Paste
| Key | Action |
|---|---|
| x / X | Delete character under / before cursor |
| dw / diw | Delete word / inner word |
| dd | Delete (cut) entire line |
| D | Delete from cursor to end of line |
| 5dd | Delete 5 lines — prefix any command with a count |
| yy | Yank (copy) line |
| yw / yiw | Yank word / inner word |
| p / P | Paste after / before cursor position |
| u / Ctrl+r | Undo / Redo |
| . | Repeat last change — the most powerful single key in Vim. Add a port connection, then . down the list |
Visual Mode Selection
| Key | Action |
|---|---|
| v then motion | Character-wise selection |
| V | Line-wise selection — select whole lines |
| Ctrl+v | Block (column) selection — select a rectangle. Essential for aligning port widths |
| Ctrl+v then I | Insert text at the start of every selected line simultaneously — add // to comment a block |
| gv | Re-select last visual selection |
.clk(clk), style to every input port simultaneously — Ctrl+v to select the column, I to insert at beginning of each line, type your text, then Esc. All lines update at once.Search & Replace
Vim's substitute command is one of its most powerful features for RTL work — renaming signals, fixing port connections, and reformatting code across an entire file.
Basic Substitute
" Replace first occurrence on current line
:s/old_sig/new_sig/
" Replace ALL occurrences on current line
:s/old_sig/new_sig/g
" Replace in entire file (% = all lines)
:%s/old_sig/new_sig/g
" Replace with confirmation prompt (c flag)
:%s/old_sig/new_sig/gc
" Replace only in selected lines (Visual mode → :)
:'<,'>s/old_sig/new_sig/g
Regex Patterns Useful in Verilog
" Rename a specific wire (exact word boundary match)
:%s/\bdata_in\b/payload_in/g
" Delete all single-line comments
:%s/\/\/.*$//g
" Add .rst_n(rst_n), after every .clk(clk), line
:%s/\.clk(clk),/\.clk(clk),\r .rst_n(rst_n),/g
" Convert 'reg' declarations to 'logic' (SV migration)
:%s/\breg\b/logic/g
" Remove trailing whitespace (keep your diffs clean)
:%s/\s\+$//e
" Add wire width — insert [7:0] before every signal named data*
:%s/wire data/wire [7:0] data/g
\c anywhere in the pattern — :%s/\cAlways/always/g matches regardless of case. Useful when cleaning up auto-generated netlists with inconsistent casing.Global Command — :g
The :g command runs a command on every line that matches a pattern. More powerful than substitute for structural operations.
" Delete all lines containing 'timescale'
:g/timescale/d
" Print all lines containing 'always_ff' (like grep)
:g/always_ff/p
" Move all 'input' port lines to end of file
:g/^\s*input/m$
" Delete all blank lines
:g/^$/d
" Add a comment above every 'module' declaration
:g/^module/normal O// Module definition
VLSI-Specific Tricks
Syntax Highlighting for Verilog / SystemVerilog
Modern Vim (8.x+) and GVim include built-in Verilog syntax highlighting. For SystemVerilog, you need a plugin or an updated syntax file.
" In .vimrc — enable filetype detection and syntax
filetype plugin indent on
syntax on
" Associate .sv and .svh files with SystemVerilog
autocmd BufRead,BufNewFile *.sv,*.svh set filetype=systemverilog
autocmd BufRead,BufNewFile *.v,*.vh set filetype=verilog
Folding — Collapse always Blocks
Folding lets you collapse sections of a large RTL file to see just the module skeleton.
" Enable syntax-based folding
set foldmethod=syntax
set foldlevel=1 " open 1 level deep by default
" Fold commands (Normal mode)
zo " open fold under cursor
zc " close fold under cursor
zR " open ALL folds in file
zM " close ALL folds in file
za " toggle fold under cursor
Aligning Port Connections
When writing module instantiations, aligning the .port(signal) column makes code readable. Use the :! shell escape to pipe through a formatter, or use visual block mode:
" Use column alignment via external tool (if installed)
:'<,'>!column -t
" Manually align with Visual Block — select width column
" Ctrl+v → select column → r (replace with spaces)
Navigating Synthesis Error Line Numbers
When DC or Genus reports Error at line 847, jump directly from the terminal:
# From shell — open file at a specific line
gvim +847 top.v
vim +847 top.v
" From inside Vim — jump to line
:847
" or in Normal mode
847G
Comparing Two Files (vimdiff)
Compare two versions of an RTL file side by side — essential for reviewing synthesis-modified netlists or tracking down what changed between runs.
# From shell
vimdiff rtl_v1.sv rtl_v2.sv
gvimdiff rtl_v1.sv rtl_v2.sv
" Navigate differences
]c " jump to next difference
[c " jump to previous difference
do " diff obtain — pull change from other window
dp " diff put — push change to other window
Reading Shell Output Into the Buffer
" Insert output of a shell command at cursor
:r !grep "always_ff" ../rtl/top.sv
" Replace current line with command output
:.!date
" Run synthesis and read errors into a scratch buffer
:r !dc_shell -f run.tcl 2>&1 | grep Error
Macros & Power Commands
A Vim macro records a sequence of keystrokes and replays them. For repetitive RTL tasks — formatting 30 port connections identically, adding a suffix to 20 signal names — a macro beats writing a script.
Recording and Playing a Macro
" Record macro into register 'a'
qa " start recording into register a
... " perform your keystrokes
q " stop recording
@a " play macro 'a' once
10@a " play macro 'a' 10 times
@@ " repeat last macro
Practical RTL Macro: Format a Port Connection
Task: convert bare signal names to .sig_name(sig_name), format across 20 lines.
" Assume cursor is on a line containing: " data_in"
" Macro recorded in register 'p':
qp " start recording
^ " go to first non-blank char
i. " insert '.' before signal name
<Esc>
e " jump to end of signal name
a( " append '('
<Esc>
yiw " yank the word (signal name)
ea " go to end, enter insert
), " close port connection
<Esc>
j " move to next line
q " stop recording
19@p " repeat for remaining 19 lines
Abbreviations — Instant Code Templates
" In .vimrc — type 'aff' in insert mode → expands to always_ff template
iabbrev aff always_ff @(posedge clk or negedge rst_n) begin<CR>end
iabbrev acb always_comb begin<CR>end
iabbrev mod module ();<CR><CR>endmodule
Marks — Bookmark Locations in a File
" Set mark 'a' at current line
ma
" Jump back to mark 'a'
`a " exact position
'a " beginning of the marked line
" Use case: mark the top of an always block (ma),
" scroll to find the signal, fix it, then 'a to return
Split Windows & Buffers
Open your DUT and testbench side by side, or keep a synthesis report open while editing RTL.
Splitting Windows
| Command | Action |
|---|---|
| :sp file.sv | Horizontal split — open file.sv above current |
| :vsp file.sv | Vertical split — open file.sv to the right |
| Ctrl+w h/j/k/l | Move focus between split windows |
| Ctrl+w = | Equalize all window sizes |
| Ctrl+w > / < | Increase / decrease vertical split width |
| Ctrl+w _ | Maximize current horizontal window |
| :q | Close current split |
| :only | Close all splits except current |
Buffers — Managing Multiple Files
" Open multiple files into buffers
vim *.sv
:ls " list all open buffers
:b2 " switch to buffer 2
:bn / :bp " next / previous buffer
:b top.sv " switch to buffer by filename
:bd " delete (close) current buffer
Tabs
:tabnew file.sv " open file in a new tab
gt / gT " next / previous tab
:tabclose " close current tab
:vsp tb_top.sv. Use Ctrl+wl and Ctrl+wh to switch. Set scrollbind if you want them to scroll together during review.ctags — Navigate Module Hierarchy
ctags builds an index of all module, function, and task definitions in your RTL. From any file, you can jump instantly to where a module is defined — without grep.
Setup
# Install ctags (Universal Ctags recommended)
sudo apt install universal-ctags # Ubuntu/Debian
brew install universal-ctags # macOS
# Generate tags for all Verilog/SV files in project
ctags -R --languages=Verilog,SystemVerilog *.v *.sv src/
# Or — generate tags for a whole project recursively
ctags -R --exclude=sim --exclude=work .
Using Tags in Vim
| Command | Action |
|---|---|
| Ctrl+] | Jump to definition of the word under cursor — e.g., jump from module instantiation to module definition |
| Ctrl+t | Jump back to where you came from (tag stack) |
| :tag module_name | Jump to definition of a specific module by name |
| :ts module_name | Show all matching tags (if multiple modules share a name) |
| :tn / :tp | Next / previous match in tag list |
| Ctrl+w Ctrl+] | Open tag definition in a new split window |
.vimrc to regenerate the tag file every time you save a .sv file: autocmd BufWritePost *.sv,*.v silent! !ctags -R &. The & runs it in the background so Vim doesn't freeze.The VLSI Engineer's .vimrc
Drop this into ~/.vimrc (Linux/macOS) or $HOME/_vimrc (Windows). It configures Vim for productive RTL and HDL work.
" ─── Core Settings ─────────────────────────────────────────
set nocompatible " use Vim improvements, not vi compatibility
syntax on " syntax highlighting
filetype plugin indent on " filetype-aware indenting
" ─── Display ────────────────────────────────────────────────
set number " show line numbers
set relativenumber " relative line numbers (faster navigation)
set cursorline " highlight current line
set colorcolumn=100 " show column guide at 100 chars
set scrolloff=6 " keep 6 lines visible above/below cursor
set laststatus=2 " always show status bar
set ruler " show line/column in status bar
set showcmd " show partial commands in bottom right
set wildmenu " enhanced command completion
" ─── Indentation (RTL standard: 2-space indent) ─────────────
set expandtab " spaces, not tabs
set tabstop=2
set shiftwidth=2
set softtabstop=2
set autoindent
set smartindent
" ─── Search ─────────────────────────────────────────────────
set hlsearch " highlight search results
set incsearch " search as you type
set ignorecase " case-insensitive search
set smartcase " ...unless pattern has uppercase
" ─── File Handling ──────────────────────────────────────────
set hidden " allow switching buffers without saving
set autoread " auto-reload files changed outside Vim
set noswapfile " no swap files (cleaner EDA directories)
set nobackup
set undofile " persistent undo across sessions
set undodir=~/.vim/undo// " store undo files here (create dir first)
" ─── Clipboard ──────────────────────────────────────────────
set clipboard=unnamed " yank goes to system clipboard (GVim/macOS)
" ─── Filetypes ──────────────────────────────────────────────
autocmd BufRead,BufNewFile *.sv,*.svh set filetype=systemverilog
autocmd BufRead,BufNewFile *.v,*.vh set filetype=verilog
autocmd BufRead,BufNewFile *.tcl set filetype=tcl
" ─── Folding ────────────────────────────────────────────────
set foldmethod=syntax
set foldlevelstart=2
nnoremap <space> za " toggle fold with spacebar
" ─── Key Mappings ───────────────────────────────────────────
let mapleader = ","
" Clear search highlight
nnoremap <leader>/ :nohlsearch<CR>
" Save with ,w
nnoremap <leader>w :w<CR>
" Toggle line numbers
nnoremap <leader>n :set relativenumber!<CR>
" Open tag in vertical split
nnoremap <leader>] :vsp <CR>:exec("tag ".expand("<cword>"))<CR>
" Quick buffer navigation
nnoremap <leader>b :ls<CR>:b<space>
" Strip trailing whitespace on save
autocmd BufWritePre *.v,*.sv,*.svh :%s/\s\+$//e
" Auto-regenerate ctags on save
autocmd BufWritePost *.v,*.sv silent! !ctags -R &
" ─── RTL Abbreviations ──────────────────────────────────────
iabbrev aff always_ff @(posedge clk or negedge rst_n) begin
iabbrev acb always_comb begin
iabbrev alb always_latch begin
iabbrev gen generate
iabbrev endg endgenerate
" ─── GVim-specific ──────────────────────────────────────────
if has('gui_running')
set guifont=JetBrains\ Mono:h13
set lines=45 columns=160
set guioptions-=T " hide toolbar
set guioptions-=m " hide menu bar
colorscheme desert
endif
mkdir -p ~/.vim/undo before using this config, or Vim will show an error on startup.Q&A
Press Esc first to make sure you're in Normal mode, then press u repeatedly to undo. Vim has unlimited undo by default. If you want to undo all changes and return to the saved version, use :e! — this reloads the file from disk, discarding all unsaved changes. With undofile set in your .vimrc, you can even undo changes from previous sessions.
Escape the special characters with a backslash: /data\[7:0\] to search for data[7:0]. Alternatively, use \V (very no-magic mode) which treats everything literally except \: /\Vdata[7:0] searches for the exact string with no regex interpretation. For the * under-cursor search, Vim automatically escapes the word boundary — but it won't work for non-word characters, so use / with escaping for those.
Two approaches: (1) Open and search — vim file.sv then /module fifo_ctrl. (2) With ctags set up, use :tag fifo_ctrl from inside Vim or vim -t fifo_ctrl from the shell — Vim opens the correct file at the exact line of the module definition. This is the fastest way once your tags file is generated.
Use Visual Block mode: position cursor at the first line, press Ctrl+v, then 20j to select 20 lines down. Press I (capital i) to insert at the beginning of all selected lines, type //, then press Esc. All 20 lines get // prepended simultaneously. To uncomment, select the same block with Ctrl+v, use 2l to extend the block 2 characters wide (covering //), then press d to delete.
Select the port declaration block with V and motion keys, then y to yank, move to destination, p to paste. Then use a macro or :%s to transform input logic [7:0] data_in → .data_in(data_in). The substitute: :'<,'>s/\s*\(input\|output\|inout\)\s\+\(logic\|wire\|reg\)\s\+\(\[.*\]\s\+\)\?\(\w\+\)/ .\4(\4)/g strips the direction/type and wraps in dot-port notation. Keep this regex in your notes — it's one of the most useful RTL substitutions.
Use :! to run a shell command: :!dc_shell -f synth.tcl 2>&1 | tee synth.log. The output appears in a terminal pane and you press Enter to return to Vim. To read the output directly into a new buffer for easier navigation: :new | r !vlog -sv top.sv 2>&1. This opens a split with the compiler output, letting you use Vim's search to find errors. Map this to a leader key for a one-key compile-and-check workflow.
Vim may not recognize .sv as SystemVerilog by default, especially on older installations. Add this to your .vimrc: autocmd BufRead,BufNewFile *.sv,*.svh set filetype=systemverilog. Also ensure filetype plugin indent on appears before any filetype-specific settings. Run :filetype inside Vim to check what filetype is detected for the current file. If it shows Off, your filetype plugin indent on line is missing or commented out.
Use :vimgrep (Vim's built-in grep) to search across files and populate the quickfix list: :vimgrep /\bfifo_ctrl\b/ **/*.sv. Then navigate results with :cn (next), :cp (previous), and :copen to see the full list in a split. Alternatively, use :grep which calls the system grep for speed on large codebases: :grep -rn "fifo_ctrl" src/. Map <leader>g to :grep -rn <cword> src/<CR>:copen<CR> for a one-key project-wide signal search.