13 KiB
src/ -- Verilator compiler sources
Covers all C++ under src/, including astgen inputs and the parser/lexer
(verilog.y, verilog.l). Read the repository-root AGENTS.md
first. This file has two parts: Orientation explains the AST and pass model;
Before you open a PR is the style and correctness checklist.
Orientation: the AST and the visitor model
- Everything is an
AstNode. Each construct is anAst*subclass (AstAdd,AstVar,AstIf). The design under analysis is one tree, with statement lists threaded by siblingnextp()links. - Children sit in numbered slots
op1p()..op4p(), accessed by name. Use the named accessors (lhsp(),condp(),thensp()), not the raw slots -- the numbering is an implementation detail. astgengenerates node boilerplate. Declare children and cross-node pointers with@astgen op/@astgen ptrannotations in theV3AstNode*.hheaders; it emits accessors, clone, and broken-check code. Do not hand-write what astgen can generate.- A pass is a visitor. Convention: a class with a private constructor and a
static
apply()entry point, named after its file (TimingSuspendableVisitorinV3Timing.cpp). It walks the tree throughvisit(AstFoo*)handlers anditerateChildren(). To understand a pass, read its top-of-file comment first -- every.cppopens with one describing the algorithm. - Scratch state lives on nodes. Passes stash data in
user1p()..user5p()(anduser1()..user5()), claimed for the pass lifetime with aVNUser*InUseguard. Save and restore visitor members across recursion withVL_RESTORER. - Three downcasts, three null behaviors:
VN_ISreturns bool,VN_CASTreturns nullptr on mismatch,VN_ASasserts the type.V3Brokenre-validates tree invariants between passes, so trust resolved pointers (dtypep(),varp()) instead of adding defensive null checks for impossible cases. docs/internals.rstis the authoritative reference for the AST, the pass list, and node lifetime.
Before you open a PR
Code style
- Mark every variable, parameter, pointer, and member function
constwhere possible. - Pointer variables take a
psuffix and pointer locals are doubly const where possible:AstVar* const varp; non-pointers never use thepsuffix. - Do not use
autoexcept for iterators or genuinely unwieldy types. - Use pre-increment (
++i) unless you specifically need post-increment's old value. - Brace-initialize node and struct construction:
new AstIf{fl, condp, thenp, elsep}. - Never use C-style casts; instead use
static_cast<T>for non-AST types andVN_AS/VN_CASTfor AST downcasts. static constexprfor compile-time constants, not#defineor file-scope const.- Mark every
class/structfinalorVL_NOT_FINAL-- a distribution test scans all definitions. - Keep functions under roughly 100-150 lines; thread shared state through a context struct rather than long parameter lists.
- Keep headers lean: move implementation to
.cpp; convert large lambdas into named member functions -- lambdas are opaque in stack traces and block reuse. - Start every new
.cppwith a top-of-file comment explaining the algorithm. - Comments are capitalized sentences written for an unknown future reader, without "I"/"we"/"our"; remove commented-out code -- version control preserves history.
- No
using namespace; prefix non-namespaced symbols withVL/Vl. - Prefer semantic predicates over enum comparisons:
varp->isClassMember(), notvarp->varType() == VVarType::MEMBER. new*functions return anewobject;make*functions do something more complex -- pick the prefix accordingly.- Name compiler-created temporaries with a
__Vprefix plus a context suffix (__VInside,__VCase); runtime utility functions use avl_prefix. - Use
VL_*bit/word macros fromverilatedos.h(VL_WORDS_I,VL_MASK_I); do not include<cstdint>directly. - Replace magic numbers with named
static constexprconstants.
AST construction and manipulation
-
Build logic as AST nodes, never as raw C text in
AstCStmt-- later passes (V3Name,--protect) rename AST identifiers but cannot see into raw strings. -
Know the cast forms (above) and never pair
VN_ISwithVN_ASon the same node -- use a singleVN_CAST:// BAD: redundant double check if (VN_IS(nodep, VarRef)) { AstVarRef* const refp = VN_AS(nodep, VarRef); } // GOOD: single conditional cast if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { ... } -
Use
UASSERT_OBJ(cond, nodep, "...")overUASSERTwhen a node is in scope; usev3fatalSrc("...")for unreachable paths, never a silentif (!ptr) return;. -
Use
VL_DO_DANGLING(pushDeletep(nodep), nodep)instead ofdeleteTree()in visitors -- deferred deletion is safe against re-entry and unlinking order.deleteTree()is only for fresh nodes that never entered the tree. -
Every new AST member needs both
dump()anddumpJson()support -- never wrap these inLCOV_EXCL; cover them by adding a construct tot_debug_emitv.v. OverrideisSame()to include any new semantically meaningful field. -
Pointers to nodes outside op1p-op4p require a
broken()override andcloneRelink()support -- avoid storing out-of-tree node pointers when possible. -
Never allocate AstNode objects on the stack or by value -- always pointers.
-
Prefer a new
visit()in an existing visitor overnodep->foreach(...)-- better for runtime, and handles diverse node types better. Prefer named accessors overop1p()..op4p(), and verify traversal order is preserved when converting manual iteration. -
Prefer
AstForeachover generating unrolled loop bodies -- constant-size code instead of O(N); wrap the body inAstBeginfor scope isolation. -
Always
skipRefp()when comparing or resolving dtypes -- missing it breaks typedef support; prove the paths with typedef tests. -
Use
num().isOpaque()rather thanisDouble() || isString()when guarding V3Number comparisons against non-integer types. -
Use
FileLine::operatorComparefor source-position ordering -- never hand-roll filename/lineno comparisons. -
Identify compiler-generated constructs by an attribute flag on the node (with dump support), never by name-pattern matching -- magic names break with escaped identifiers.
-
Use
V3Numberarithmetic forAstConstvalues wider than 32 bits --1 << isilently overflows ati >= 32. -
Use
VMemberMap/findMember()for name lookups in structs, classes, modules, and packages -- O(1) versus quadratic linear scans. -
Never emit raw source filenames or identifiers in generated code -- pass them through
protect()/putsQuotedso--protectdoes not leak source.
Visitors and passes
VL_RESTORERon every visitor member avisit()modifies before iterating children -- even when nesting "cannot happen" today.- Every pass using
userNp()needs aVNUserNInUseguard, and the header documents which user fields it uses. - Use
iterateAndNextNull()rather thaniterate()-- the null-safe form prevents copy-paste errors during refactors. - Derive read-only visitors from
VNVisitorConstwithiterateChildrenConst. - Reset per-module visitor state in
visit(AstNodeModule*), including numeric ID counters, to keep generated names stable. - Capture first-occurrence module state inside the node's own
visit()handler, not via aforeachpre-scan fromvisit(AstNodeModule)-- source order already matches IEEE declaration-before-use. - Avoid
backp()-- it returns parent or prior sibling depending on position and causes O(n^2) hunts; build maps or capture context during forward traversal. - When raw node pointers key a map or set, erase entries when the node is deleted -- allocators reuse addresses, so stale entries alias new nodes.
- Derive graph-shaped passes from V3Graph (
V3GraphVertex/V3GraphEdge) -- it gives dump, color, rank, topological sort, and reachability for free. - When a change outgrows local rewrites, create a dedicated pass instead of growing an existing one.
- State explicitly how side effects are preserved in optimizations involving purity, expression lifting, or simplification.
Errors and warnings
(See the root checklist for choosing the diagnostic API and its required test.)
- Append
nodep->prettyNameQ()for user-facing names; usename()only in debug/UINFO output. Enclose specific values in single quotes:'value'. - End messages with periods, never exclamation marks; do not write "Error:" in the text -- the macro prints the prefix.
- State what was attempted and what was found: "Instance attempts to connect to
'PARAM' as a parameter, but it is a variable". Add a
warnMore()suggestion where possible. - Name warning codes object-first and short (
ASCRANGE, notRANGEASC); rename viarenamedTo()so old suppressions keep working. - Set warning suppression on
AstVar, notAstVarRef-- VarRefs get recreated and losewarnIsOff. - "Unsupported:" messages describe the specific unsupported context, not just the construct name -- each message must be distinct.
- When replacing or refactoring a pass, keep existing user-facing error strings --
.outgoldens and documentation depend on the wording.
Performance and memory
- O(n^2) is never acceptable -- build maps for batch lookups; any quadratic loop needs explicit justification in a comment.
- Prefer
std::mapfor per-module structures (many small instances); useunordered_maponly for one-per-netlist data, and never letunordered_*iteration order reach generated output. - Prefer
emplaceoverinsertand check the returned.secondinstead of a separatefind().reserve()strings and vectors when the size is estimable. - Add no new static or global mutable data -- statics are being eliminated for future parallelism.
- Use Verilator's fixed-width data types for model data (
CData/SData/IData/QData/VlWide), notsize_t. Process wide data word-by-word (VL_ZERO_W,VL_MEMCPY_W), never bit-by-bit. - No exceptions in verilated runtime code; do string parsing at verilation time, never during simulation.
- Wrap unlikely hot-path branches in
VL_UNLIKELY/VL_LIKELY. - Count what every new pass does via V3Stats -- stats become deterministic regression anchors.
Thread safety
- Annotate with the hierarchy
VL_PURE>VL_MT_SAFE>VL_MT_STABLE: PURE has no side effects and calls only PURE; MT_SAFE is safe under locks; MT_STABLE is safe only while tree topology is stable. Annotations must match the implementation. - Never include
verilated.hin the compiler itself -- useverilatedos.h. - Annotate mutex-protected members with
VL_GUARDED_BYand document acquisition ordering.++on shared state and containerempty()are not thread-safe.
Parser and lexer (verilog.y, verilog.l)
- Preserve IEEE Appendix A BNF comments (
// IEEE: {rule}); comment explicitly when accepting syntax beyond IEEE as an extension. - The parser only builds AST nodes -- defer semantic validation,
VN_ISchecks, and context-dependent logic to V3LinkParse/V3Width and later passes. - Represent hierarchical paths as structured nodes (
AstDot/parse-ref chains viaidDotted), never concatenated strings -- preserves per-segment FileLine. - Prefer tightening a grammar rule's operand type over a runtime cast-chain guard in a later visitor -- illegal operands then fail with a clean syntax error.
- Solve ambiguities with token-pipeline look-ahead (
tokenPipeScan*) rather than limiting grammar rules; mark unsupported rules with//UNSUP. - Sort token declarations alphabetically by string literal; sort
yD_*productions by token name. - Add a test for every
|alternative and optional clause of a new or changed grammar rule -- untested alternatives are where parse regressions hide.
File-specific rules
| File | Rule |
|---|---|
src/V3Options.cpp |
Chain .notForRerun() onto DECL_OPTION() for options that do not affect semantic output |
src/V3Ast.cpp |
For composite types (queues, dynamic arrays) use computeCastableImp() on subtypes -- shallow width()/similarDType() checks miss nested incompatibility |
src/V3AstNode*.h |
Every node class gets a what-construct comment and every member a semantic-purpose comment; put enum type definitions in V3AstAttr.h |
src/V3AstNodeExpr.h |
CCast is only for basic C types (char/short/int/QData) -- never 4-state logic or structs |
src/V3AstNodeOther.h |
cloneRelink must propagate all stateful flags (e.g. maybePointedTo) and update internal references |
src/V3Const.cpp |
Check keepIfEmpty before removing empty functions -- flagged functions must survive for codegen/side effects |
src/V3Coverage.cpp |
Instrumentation contexts are opt-in (allowlist), never blocklist -- blocklists silently break when new contexts appear |
src/V3Inline.cpp |
Preserve VarXRef::varp() during passes -- pin-reconnection needs it before V3LinkDot re-resolves |
src/V3Sched*.cpp |
Every change needs a test proving necessity; isolate unrelated scheduler changes into separate PRs -- high-risk area |