Skip to content

Configuration ​

Everything is set in lazyverilog.toml. The server uses the nearest one above each file you open, so two projects open side by side each use their own.

Without a lazyverilog.toml ​

A file with no lazyverilog.toml above it still gets cross-file features. The server indexes the folder the file belongs to as an implicit project:

  • your editor's workspace folders, when it sends them: the folder VS Code opened, or the git checkout of each file the Neovim plugin opens (never the directory nvim was started in), or
  • the nearest version-control checkout above the file (a .git, .hg or .svn directory).

Every .sv and .v file under that folder is indexed, and every directory that holds a .svh, .vh or .svi header is an include directory. Directories that hold tool output are skipped: dot-directories, names starting with build, out, work, sim_build, obj_dir, xcelium.d, INCA_libs, csrc, node_modules, and names ending in .runs, .cache, .sim or .gen. Directory names and *.ext patterns in .gitignore are skipped too. A subtree with its own lazyverilog.toml belongs to that project instead.

An implicit project is a guess, so it does less than a filelist:

  • No defines. `ifdef blocks that need one stay inactive.
  • No whole-design compilation, so no elaboration diagnostics.
  • Two modules with the same name are both indexed. Go to definition ranks the nearer one first. Rename refuses the name until a lazyverilog.toml says which files belong together.
  • Nothing is written into your tree. Index shards go to the user cache: %LOCALAPPDATA%\lazyverilog\i on Windows, ~/Library/Caches/lazyverilog/i on macOS, and $XDG_CACHE_HOME/lazyverilog/i (default ~/.cache/lazyverilog/i) elsewhere.

The home directory, the folders above it, and a drive or filesystem root are never indexed this way. A folder is not indexed at all when it holds more than 5000 source files or 300000 entries, or takes longer than 5 seconds to list. The server then shows one message and works on open files only. Add a lazyverilog.toml, or exclude directories (below).

When you save a new lazyverilog.toml in the folder, the folder becomes an ordinary project. The server drops the implicit project and its cache.

Turning it off or narrowing it ​

The client sends these in initializationOptions:

json
{ "lazyverilog": { "implicitProject": "auto", "implicitProjectExclude": ["vendor/**", "sim"] } }
KeyValueEffect
implicitProject"auto" (default)Workspace folders, else the version-control checkout
"workspace"Workspace folders only
"off"No implicit projects: a file with no lazyverilog.toml is handled on its own
implicitProjectExcludestring[]Globs relative to the folder. A matching directory or file is skipped

The server also takes --implicit-project=off|workspace|auto on its command line, for a site-wide wrapper. initializationOptions override the flag. In Neovim, pass the flag with cmd_args = { "--implicit-project=off" } in setup().

After you add or remove many files, run the lazyverilog.rescan command to list the folder again.

Minimal example ​

toml
[design]
vcode = "demo/vcode.f"
define = ["RTL_SIM"]

[format]
enable_format_on_save = true
indent_size = 4

[lint.statement]
enable = true
case_missing_default = true

Lint rules are all off until you turn them on: [lint].enable defaults to true and only switches everything off, while each category ([lint.statement], [lint.naming], ...) needs its own enable = true plus the rules you want. See Linter options.

Complete example ​

The lazyverilog.toml from the repository root, imported here so it cannot drift from the file the project tests against.

toml
[design]
vcode = "demo/vcode.f"
define = ["RTL_SIM"]   # preprocessor defines passed to slang for parsing

[compilation]
background_compilation = false # run semantic compilation in background workers (slower, richer diagnostics)

[lint]
enable = true                  # global kill-switch; false disables all lint rules


[lint.naming]
enable = true
severity = "hint"          # "warning" | "error" | "hint"
module_pattern = "^m_.*$" # regex; empty = skip
input_port_pattern = "^i_.*$"  # regex; empty = skip
output_port_pattern = "^o_.*$" # regex; empty = skip
signal_pattern = "^(?!q_).*"           # regex for internal logic/wire/var; empty = skip
interface_pattern = ".*_intf$"        # regex for interface names; empty = skip
struct_pattern = ".*_t$"           # regex for struct names; empty = skip
union_pattern = ".*_u$"            # regex for union names; empty = skip
enum_pattern = ""             # regex for enum names; empty = skip
parameter_pattern = "^W_.*$"        # regex for parameter names; empty = skip
localparam_pattern = "^LP_.*$"       # regex for localparam names; empty = skip
register_pattern = "^r_.*$"
check_module_filename = true # check that module name matches filename
check_package_filename = true # check that package name matches filename

[lint.module]
enable = true
severity = "warning"          # "warning" | "error" | "hint"
one_module_per_file = true   # check that at most one module is declared per file

[lint.instance]
enable = true
severity = "warning"          # "warning" | "error" | "hint"
module_instantiation_style = "named"  # "positional", "named", or "both"
stale_instance_diagnostic = true

[lint.statement]
enable = true
severity = "warning"          # "warning" | "error" | "hint"
no_raw_always = true         # check that there are no occurrences of raw always
blocking_nonblocking_assignments = true  # check blocking vs non-blocking assignments
latch_inference_detection = true # check for latch inference
case_missing_default = true              # check that default case-item is always defined
explicit_begin = true # check that Verilog begin directive follows all applicable statements

[lint.function]
enable = true
severity = "warning"          # "warning" | "error" | "hint"
functions_automatic = false   # functions should be automatic type
function_call_style = "both"      # "positional", "named", or "both"
explicit_function_lifetime = true  # check that functions have explicit lifetime (static/automatic)
explicit_task_lifetime = true    # check that tasks have explicit lifetime (static/automatic)

[lint.style]
trailing_whitespace = true

[rtltree]
show_instance_name = true   # show "cpu (u_cpu)" instead of just "cpu"
show_file = true             # show "[rtl/cpu.sv]" after module name

[format]
indent_size = 4
blank_lines_between_items = 1
default_indent_level_inside_outmost_block = 0
tab_align = false
enable_format_on_save = true
format_off_comment_pattern = '//\s*verilog[-_]format\s*:\s*off\b'
format_on_comment_pattern = '//\s*verilog[-_]format\s*:\s*on\b'
log_path = "" # optional directory for formatter per-pass debug logs

[format.spacing]
control_keyword_space = true
space_inside_parens = false
space_inside_dimension_brackets = false
binary_operator_spacing = "both"                    # none | before | after | both
dimension_binary_operator_spacing = "none"          # none | before | after | both
semicolon_spacing = "both"                         # none | before | after | both; for-loop headers
range_colon_spacing = "none"                        # none | before | after | both; ranges inside []
indexed_part_select_spacing = "none"                # none | before | after | both; +: and -:
procedural_event_control_at_spacing = "both"      # none | before | after | both; always @(...)
space_inside_event_control_parens = true
assignment_operator_spacing = "both"                  # none | before | after | both; = and <= operators

[format.statement]
align = true              # align = and <= operators in consecutive assignments
align_adaptive = true
lhs_min_width = 10           # minimum width of the LHS column before the operator
begin_newline = false       # true: place begin / constraint { on the next line
wrap_end_else_clauses = true

[format.port_declaration]
align = true
align_adaptive = true
section1_min_width = 10
section2_min_width = 11
section3_min_width = 12
section4_min_width = 13
section5_min_width = 14

[format.var_declaration]
align = true
align_adaptive = true
section1_min_width = 20
section2_min_width = 20
section3_min_width = 20
section4_min_width = 16

[format.function_call]
break_policy = "auto"      # never | always | auto
line_length = 100
arg_count = 3 # when auto: break if arg count >= this value; -1 disables arg-count breaking
layout = "hanging"           # hanging | block
space_before_paren = false
space_inside_paren = false

[format.function_declaration]
layout = "block"           # hanging | block — applies to function and task declarations
line_length = 13          # declarations shorter than this stay single-line
space_before_paren = false # insert a space between function/task name and '('

[format.macros]
object_like_expr = []      # expression token, for example: `MY_WIDTH
function_like_expr = [
    "dv_fatal"
]    # expression-valued call, for example: `CLAMP(a, b)
statement_like = []        # complete procedural statement, for example: `DV_CHECK_EQ(a, b)
declaration_like = [
    "DV_ALERT_IF_CONNECT", # module/class item helper; often used without a trailing semicolon
]
control_flow_like = []     # control-like macro, for example: `ASSERT_IF(en)
block_begin_like = []      # opens logical indent scope, for example: `MY_BLOCK_BEGIN()
block_end_like = []        # closes logical indent scope, for example: `MY_BLOCK_END
whitespace_sensitive = []  # preserve invocation argument spelling/line breaks

[format.module]
parameter_layout = "block" # block | hanging
non_ansi_port_per_line_enabled = true
non_ansi_port_per_line = 3
non_ansi_port_max_line_length_enabled = false
non_ansi_port_max_line_length = 18

[format.enum_declaration]
align = true
align_adaptive = true
enum_name_min_width = 10
enum_value_min_width = 12

[format.modport]
align = true
align_adaptive = true
direction_min_width = 15
signal_min_width = 10

[format.instance]
align = true
port_indent_level = 1                   # indent levels for port lines relative to instantiation
instance_port_name_width = 20           # total field width from '.' to '(' (port name + gap)
instance_port_between_paren_width = 30  # total field width from '(' to ')' (signal + trailing gap)
align_adaptive = true                  # if true, per-line gap; if false, all ports in instance align to common column


[inlay_hint]
enable = true  # set false to disable all inlay hints

[folding]
enable = true  # set false to stop the editor requesting folds on every edit

[autoff]
register_pattern = "^r_"  # regex — signal matching this is the register (dst); the other is the source

[autofunc]
indent_size = 4
use_named_arguments = true

[autoarg]
autoarg_on_save = true

[autowire]
group_by_instance = false
sort_by_name = true

Index cache ​

The index of each project file is cached on disk, so a restart does not parse the project again:

  • In a project with a lazyverilog.toml: .cache/lazyverilog/index next to that file. Add .cache/ to your .gitignore.
  • Files under no project: lazyverilog/index in the user cache directory (listed above).
  • Implicit projects: lazyverilog/i in the user cache directory.

An entry is reused only when the file, its includes, its defines and include directories are unchanged, compared by content, not by timestamp. You can delete the directory at any time; the next start indexes the files again.

CPU use ​

Indexing uses up to 8 worker threads, never more than the CPUs this process may use: its CPU affinity, a container CPU quota (cgroup), and the batch scheduler's allocation. The scheduler variables read are SLURM_CPUS_PER_TASK (Slurm), LSB_DJOB_NUMPROC (LSF), NCPUS (PBS Pro) and PBS_NUM_PPN (Torque); the smallest one set wins. OMP_NUM_THREADS is not read.

Option reference ​

TopicPage
Filelist and definesDesign & filelist
FormatterOptions, macro policy
LinterLinter options
Semantic diagnosticsBackground compilation
Folding and inlay hintsLSP features
RTL treeRTL tree
Code generationAutoInst, AutoWire, AutoArg, AutoFunc, AutoFF

Mistakes in the file ​

A value of the wrong type or out of range, an unknown key, and an unknown section such as a misspelled [fromat] are reported, all in one message. The server shows it as a warning (window/showMessage) and as a diagnostic on lazyverilog.toml, and the command-line tools print it to stderr. The rest of the file still applies: a bad value keeps its default, and an unknown key or section is ignored. A TOML syntax error is different: then the whole file is ignored and every option keeps its default.

Released under the MIT License. Themed with Catppuccin.