Part 7 · Advanced & Integration · Intermediate
Compilation, Elaboration & Optimization Flow
Compile, elaborate, optimize, simulate stages — which errors appear where, incremental compile, libraries, and debug-vs-speed trade-offs.
The four stages
Every simulator runs your code through the same conceptual pipeline: compile (parse each file into a library), elaborate (build the design hierarchy, bind instances, resolve parameters), optimize (flatten, inline, and strip for speed), then simulate (execute the event scheduler). Knowing which stage produced an error tells you immediately where to look — a syntax error and an unresolved-instance error need completely different responses.
COMPILE → ELABORATE → OPTIMIZE → SIMULATE
source files (.sv)
│ preprocessor: `define / `ifdef / `include resolved here
▼
┌──────────┐ per-file: parse, type-check, store in library
│ COMPILE │ errors: syntax, unknown types, bad port widths
└────┬─────┘
▼
┌──────────┐ whole-design: build hierarchy from top module,
│ELABORATE │ bind instances, resolve parameters & defparams
└────┬─────┘ errors: missing module, port mismatch, bad param
▼
┌──────────┐ flatten hierarchy, inline always blocks, dead-code
│ OPTIMIZE │ strip — may REMOVE signals you wanted to see
└────┬─────┘ knobs: debug-access options re-preserve visibility
▼
┌──────────┐ event-driven execution of the elaborated design
│ SIMULATE │ errors: null handle, X-propagation, assertion
└──────────┘ failures, $fatal — anything needing live valuesWhich errors appear at which stage
Stage-to-error map
Compile errors — missing semicolon, undeclared identifier, unknown class, macro expansion failures: fixed file-by-file, fast iteration.
Elaboration errors — module not found in any library, port connection width/direction mismatch, parameter override of nonexistent parameter, hierarchical reference to a missing scope: these need the whole design, so they appear only after every file compiles.
Optimization surprises — not errors but missing visibility: a signal optimized away cannot be waved or forced until you rebuild with debug access enabled.
Run-time errors — null object dereference, out-of-bounds dynamic array index, constraint solver failure, assertion fail, $fatal: only reproducible by running, often seed-dependent.
// COMPILE-stage error: visible from this file alone
logic [7:0] data
logic [7:0] mask; // ERROR: syntax error near 'logic' (missing ;)
// ELABORATION-stage error: legal file, broken hierarchy
module tb_top;
// ERROR at elaboration: module 'fifo_wrap' not found —
// the file defining it was never compiled into the library
fifo_wrap #(.DEPTH(32)) u_fifo (.clk(clk), .rst_n(rst_n));
endmodule
// RUN-time error: compiles and elaborates clean, dies at t=0
initial begin
my_txn t; // never constructed
t.addr = 32'h100; // FATAL: null object access at run time
endLibraries, incremental compile, and speed knobs
Compiled units land in a library (traditionally named work). Large projects map RTL, testbench, and vendor IP to separate libraries so each can be cleaned and rebuilt independently. Incremental compile then recompiles only files whose text — or whose included headers — changed, which is why a one-line edit in a widely included .svh file can trigger a near-full rebuild.
# Three-step flow (Questa-style; VCS/Xcelium have equivalents)
vlib work_rtl && vmap rtl work_rtl
vlib work_tb && vmap tb work_tb
vlog -work rtl rtl/*.sv # compile RTL into its library
vlog -work tb +incdir+tb tb/my_pkg.sv tb/tb_top.sv
# Elaborate + optimize: -debug keeps signal visibility (slower),
# omitting it lets the optimizer strip internals (faster)
vopt tb.tb_top -o tb_top_opt -debug
vsim tb_top_opt -do "run -all"
# Incremental: re-run vlog only for changed files; elaboration
# re-runs whenever any compiled unit it depends on changed.Debug visibility vs simulation speed
Full debug access preserves every signal for waves, forces, and interactive stepping — at a significant run-time cost (often 2x or more).
Optimized builds inline and strip aggressively — fastest regressions, but a failure may need a rebuild with debug enabled to wave the relevant signals.
Common compromise: regressions run optimized; the failure-reproduction flow rebuilds with debug access and reruns the failing seed.
Per-instance or per-module debug scoping (where the tool supports it) buys visibility on the suspect block without paying the full-design penalty.
Key takeaways
Compile is per-file, elaboration is whole-design — 'module not found' is a library/compile-order problem, not a syntax problem.
Run-time errors (null handles, constraint failures) cannot be caught earlier — they need live execution.
Editing a widely included .svh invalidates every file that includes it — header hygiene controls incremental-build speed.
Regressions run optimized; debug reruns rebuild with visibility enabled for the failing seed only.
Common pitfalls
Hunting a 'module not found' elaboration error inside the module's source file — the file simply was not compiled, or went to the wrong library.
Forcing or waving a signal that optimization removed — silently ineffective or unfindable until rebuilt with debug access.
Stale incremental builds after switching branches — when behavior makes no sense, a clean rebuild is the first sanity check.
Assuming elaboration order equals execution order — initial blocks across modules have no defined relative ordering.
Interview angle
Standard questions: walk through compile vs elaboration vs run time and give one error from each; why does 'module not found' appear only at elaboration (hierarchy binding needs all units); what trade-off do optimization switches make (speed vs signal visibility) and how does your regression flow handle a failure that needs waves.