Skip to content

Verilog · Chapter 8.4 · System Tasks & Functions

Conditional Simulation in Verilog — Compile-Time vs Run-Time Control

Conditional simulation is the discipline of controlling what the simulator builds and runs, such as debug instrumentation, testbench-only stimulus, and fast versus full regression modes, without changing the hardware the source describes. It rides on the same ifdef and ifndef directives that the conditional-compilation page drills, but the skill here is not the syntax; it is knowing which of three conditional layers a problem actually wants: a compile-time macro that pulls text in or out before compilation, an elaboration-time parameter that is per-instance and still real hardware, or a run-time if that is evaluated every cycle and always present in the netlist. Reach for the wrong layer and you get real RTL hidden behind a simulator flag, or an undefined macro that silently deletes a check so a regression reports green while testing nothing. This page is the judgement layer: what conditional simulation is, when each layer is correct, and the failure modes that ship sim-only bugs to silicon.

Foundation18 min readVerilogConditional SimulationifdefSimulation ControlVerificationBuild Targets

Chapter 8 · Section 8.4 · System Tasks & Functions

1. The Engineering Problem

A design passes every simulation. It is taped out. The silicon is dead on arrival — a FIFO overflows under back-pressure that the testbench exercised and the simulator never flagged a problem. The post-mortem finds one line:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifdef SIMULATION
    // "temporary" full-flag fix while the real one is reviewed
    assign full = (count >= DEPTH - 1);
`endif

A junior had a real RTL bug — an off-by-one in the FIFO full flag — and wrote the fix inside a `ifdef SIMULATION guard so they could test it before the review landed. The review stalled. The guard stayed. In every simulation build the macro SIMULATION was defined, the fix existed, and the FIFO behaved. In the synthesis build the macro was not defined, the preprocessor stripped the assignment, and the netlist taped out with the original broken flag. Simulation and silicon ran different hardware.

This is the defining hazard of conditional simulation, and it is the mirror image of the Chapter 7.4 hazard. There, the danger was debug junk leaking into synthesis. Here, the danger is real logic vanishing from synthesis. Both come from the same root confusion: treating a compile-time text switch as if it controlled behaviour rather than which source the tool sees.

`ifdef does not mean "do this in simulation." It means "let this text exist in builds where the macro is defined." If that text is hardware, then a build without the macro is missing hardware. The skill this page teaches is recognising — every time you reach for a conditional — which layer you actually want, so simulation and synthesis describe the same chip.

2. What "Conditional Simulation" Actually Means

Conditional simulation is build and simulator control — not a design construct. It answers questions like:

  • "How do I keep debug instrumentation in my sim runs but out of the netlist?"
  • "How do I run the same testbench in a 5-minute smoke mode and an 8-hour soak mode?"
  • "How do I compile in a behavioural reference model that exists only to check the DUT?"

Every one of those is about what the tool does with the source, not about what the chip does. That distinction is the whole topic. A clean mental test:

If a conditional changes the manufactured hardware, it does not belong in a `ifdef SIMULATION. If it only changes what the simulator observes, controls, or compares, it does.

Conditional simulation is therefore primarily a verification and build-flow skill. It lives in testbenches, in shared debug headers, and in the thin instrumentation layer wrapped around RTL — never in the synthesizable description of the design itself. Chapter 7.4 taught the directive family as a build-target mechanism; this page narrows to the simulation-control application and the judgement that keeps it from corrupting the design.

3. Mental Model — The Three Conditional Layers

4. The Simulation View — Two Tools, Two Different Source Files

The reason conditional simulation works at all is that the simulator and the synthesizer read the same file through different macro sets, and therefore effectively compile different source.

ConstructSimulator build (+define+SIMULATION)Synthesis build (no SIMULATION)
$display / $monitor / $strobepresent, runsmust be absent (gate it)
Behavioural reference modelpresent, checks the DUTabsent — not part of the chip
Testbench stimulus / clock genpresentabsent
Synthesizable RTLpresentmust also be present ← never gate this
Assertions / coveragepresentabsent (or tool-handled)

The first three rows should differ between the two builds — that is the point of conditional simulation. The fourth row must never differ: if the synthesizable design is not byte-for-byte identical (in behaviour) across both builds, simulation is verifying a chip you are not manufacturing. Everything in this chapter is about keeping rows 1–3 conditional and row 4 unconditional.

5. Syntax — Assumed, Not Re-Derived

The five directives — `ifdef, `ifndef, `else, `elsif, `endif — and the +define+ command-line mechanism are drilled in Conditional Compilation (7.4). This page assumes them. The only syntactic point that matters here is the shape of a well-formed simulation guard:

sim-guard.v — the canonical conditional-simulation idiom
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Observation and checking — present in sim, gone in synth.
`ifdef SIMULATION
    always @(posedge clk)
        if (rd_en)
            $display("[t=%0t] read addr=%h data=%h", $time, addr, rdata);
`endif
 
// The design itself stays OUTSIDE every simulation guard.
always @(posedge clk)
    rdata <= mem[addr];          // real hardware — unconditional

Two rules carry the whole idiom: the guard wraps only the instrumentation, and the design sits outside it. Every pitfall in §12 is a violation of one of those two rules.

6. Compile-Time vs Run-Time — The Decision Framework

The single most common interview question on this topic is "when do you use `ifdef versus a normal if?" The answer is a two-question test.

Question 1 — Should this code exist in the manufactured chip?

  • No (it is observation, stimulus, or a reference model) → it must be removable, so use the compile-time layer: `ifdef SIMULATION.
  • Yes (it is part of the design's function) → it must always be present, so it cannot live behind `ifdef. Use a run-time if or an elaboration-time parameter.

Question 2 — If it stays in the chip, does the choice vary per instance at build time, or per cycle at run time?

  • Per instance, fixed at build → elaboration-time parameter / generate (e.g. WIDTH, HAS_PARITY).
  • Per cycle, while running → run-time if / case (e.g. if (mode == FAST)).
What you are gatingLayerConstruct
Debug prints, assertions, coverageCompile-time`ifdef SIMULATION
Testbench-only stimulus / clockCompile-time`ifdef SIMULATION
Behavioural reference vs vendor primitiveCompile-time`ifdef SYNTHESIS / `else
Per-instance design configurationElaboration-timeparameter / generate
Per-cycle operating-mode behaviourRun-timeif / case

The trap is answering Question 1 wrong: deciding "this is for testing, so `ifdef it" when the code is actually part of the design. The §1 FIFO fix failed exactly there — a real full-flag computation answered "yes, it's hardware" but was gated as if the answer were "no."

7. Macros vs Parameters — Stop Reaching for `ifdef

A second, quieter failure is using compile-time macros for things that elaboration-time parameters do better. They look similar — both configure a build — but parameters are almost always the right tool for design configuration.

macro-vs-parameter.v — same goal, two layers
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// ANTI-PATTERN — a macro per width variant.
`ifdef WIDTH_8
    reg [7:0]  data;
`elsif WIDTH_16
    reg [15:0] data;
`elsif WIDTH_32
    reg [31:0] data;
`endif
// Needs a recompile per variant; only ONE width per compilation;
// two instances of different widths are impossible in one build.
 
// CORRECT — a parameter.
module buffer #(parameter integer WIDTH = 8) (
    input  wire [WIDTH-1:0] d,
    output reg  [WIDTH-1:0] q
);
    always @(posedge clk) q <= d;
endmodule
// buffer #(.WIDTH(8)) and buffer #(.WIDTH(32)) coexist in ONE build,
// no recompile, no macro, fully synthesizable.

The decision rule:

  • Configuration of the design (widths, depths, feature toggles that ship in silicon) → parameter (parameter, 6.1). Per-instance, no recompile, two variants coexist.
  • Presence of simulation-only code (does this debug block exist at all) → `ifdef.

Reaching for `ifdef to configure hardware produces macro sprawl: a combinatorial blow-up of WIDTH_8 × HAS_PARITY × FAST_MODE macros, one recompile per combination, and the impossibility of two differently-configured instances in a single build. parameter exists precisely to avoid that. Conditional simulation uses macros; conditional hardware uses parameters.

8. Visual Explanation

Three figures anchor the model.

8.1 Visual A — the three conditional layers on a timeline

Three conditional layers — when each resolves and what it touches

data flow
Three conditional layers — when each resolves and what it touchescompile-time ·`ifdeftext in/out · before compile · per buildelaboration-time· parameterhardware shape · at build · per instancerun-time · if /casebehaviour · every cycle · always in netlistmanufacturednetlistwhat silicon runs
Left to right is earlier to later. `ifdef decides which text the later stages even see; parameter fixes per-instance hardware shape; if/case is the only layer that survives as selectable logic inside the chip. Conditional simulation lives entirely in the leftmost stage — and only for code that should NOT reach the netlist.

8.2 Visual B — the simulation/synthesis view split

Simulation versus synthesis view of one source fileone source fileDUT + guardedinstrumentationsim build+define+SIMULATIONsynth buildno SIMULATION macroDUT + $display +checkswhat the simulator runsDUT onlywhat silicon becomes12
The same source file is read by two tools through two macro sets. Sim defines SIMULATION and sees instrumentation + DUT; synth omits it and sees only the DUT. The DUT half MUST be identical across both reads — the only legal difference is the instrumentation half. Hiding RTL in the instrumentation half is the §1 mismatch.

8.3 Visual C — the decision matrix

Conditional layer decision matrixin silicon?Question 1NO — testing onlymust be removableYES — real hardwaremust always exist`ifdef SIMULATIONcompile-timeparameter / generateelab-time · per instanceif / caserun-time · per cycle12
Two questions pick the layer. Does the code belong in silicon? If no, it must be removable -> compile-time `ifdef. If yes, does the choice vary per instance at build or per cycle at run? -> parameter or run-time if. The only cell conditional simulation occupies is the 'no, not in silicon' column.

9. Worked Examples

Three patterns cover almost all legitimate conditional simulation.

9.1 Tiered debug verbosity

A single source with three observation levels — silent, normal, verbose — selected at compile time so production synthesis sees none of it.

verbosity-tiers.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifdef SIMULATION
    always @(posedge clk) begin
        `ifdef DEBUG_VERBOSE
            $display("[t=%0t] state=%0d a=%h b=%h result=%h",
                     $time, state, a, b, result);
        `elsif DEBUG
            $display("[t=%0t] state=%0d result=%h", $time, state, result);
        `endif
    end
`endif

Three build targets from one file: silent (+define+SIMULATION), normal (+SIMULATION +DEBUG), verbose (+SIMULATION +DEBUG_VERBOSE). The synthesis build defines none of them and the whole block disappears. Note the nested guard — DEBUG_VERBOSE inside SIMULATION — so verbose logging can never accidentally exist in a non-simulation build.

9.2 Testbench-only conditional stimulus

Stimulus that should run only in certain test modes, gated so it never reaches any design build.

conditional-stimulus.v — testbench fragment
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
initial begin
    apply_reset();
    `ifdef INJECT_ERRORS
        // Fault-injection stimulus — only in the error-handling regression.
        force dut.parity_bit = ~dut.parity_bit;
        #100 release dut.parity_bit;
    `endif
    run_main_sequence();
end

force / release are simulation-only constructs to begin with; gating them on INJECT_ERRORS means the default regression runs clean stimulus and a dedicated job (+define+INJECT_ERRORS) runs the fault campaign — same testbench source.

9.3 Reference model vs DUT primitive

The behavioural model exists only to check the DUT; the synthesizable path uses the vendor primitive.

reference-model.v
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifdef SYNTHESIS
    // Silicon path — vendor hard macro.
    spram_2kx32 u_mem (.clk(clk), .addr(addr), .din(din), .dout(dout), .we(we));
`else
    // Simulation path — portable behavioural model + golden check.
    reg [31:0] mem [0:2047];
    always @(posedge clk) begin
        if (we) mem[addr] <= din;
        dout <= mem[addr];
    end
`endif

Here the conditional does swap part of the design — but legitimately, because both branches implement the same function and the choice is which implementation compiles, not whether a piece of logic exists. The discipline: when a `ifdef straddles real hardware, both branches must be functionally equivalent and synthesizable-or-modelled in parity. That is the one safe case of gating near the design, and it is exactly the case the §1 bug violated (its `ifdef had a real branch and an empty one).

10. Trace — Same Testbench, Two Builds

Conditional simulation has no waveform of its own; its observable effect is that the same run produces different logs under different defines. The trace below is one testbench compiled two ways.

two builds, one source
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
$ vlog tb.v dut.v +define+SIMULATION +define+DEBUG_VERBOSE && vsim -c tb -do "run -all"
[t=10]  state=0 a=0005 b=0003 result=0008
[t=20]  state=1 a=0008 b=0002 result=000a
[t=30]  state=2 a=000a b=00ff result=0109
TEST PASSED
 
$ vlog tb.v dut.v +define+SIMULATION && vsim -c tb -do "run -all"
TEST PASSED                      # DEBUG_VERBOSE undefined -> per-cycle lines gone
 
$ vlog tb.v dut.v && vsim -c tb -do "run -all"
# ** Error: tb.v: hierarchical reference into 'dut' — testbench stimulus
#    was gated on SIMULATION and is now absent; nothing drives the clock.

The third invocation is the teaching point: dropping SIMULATION removed the testbench's own driving code, so the run does nothing. That is correct for the design (a synthesis tool reading dut.v alone is exactly what you want) but a mistake if you expected to simulate without the macro. The macro set is part of the experiment; a result is only reproducible if the defines are pinned with it (§12.4).

11. Industry Usage

  • Regression tiers. CI farms compile one testbench into smoke (+SIMULATION), nightly (+SIMULATION +DEBUG), and soak (+SIMULATION +DEBUG +LONG_RUN) jobs. One source, three schedules.
  • Fault and corner campaigns. +define+INJECT_ERRORS, +define+CLOCK_JITTER enable dedicated stimulus that the default run never sees.
  • Bring-up vs sign-off. Early bring-up builds carry heavy $display / assertion instrumentation; sign-off regressions strip verbosity for speed by dropping the DEBUG defines — never by editing source.
  • Reference-model gating. Golden C/behavioural models compiled in under `ifndef SYNTHESIS to score the DUT, excluded from any build headed for a netlist.
  • Emulation / FPGA prototyping. `ifdef FPGA swaps simulation-only constructs ($display, real-number models) for synthesizable equivalents so the same RTL runs on an emulator.

In every case the design RTL is untouched by the macros — only the surrounding observation, stimulus, and modelling layer is conditional.

12. Common Mistakes

Five mistakes, each a confusion of conditional layers.

12.1 Hiding synthesizable RTL behind a simulator flag

The §1 catastrophe. Any logic the chip needs — a flag computation, a state update, a corner-case branch — placed inside `ifdef SIMULATION exists in sim and vanishes in synth. Simulation verifies a chip you are not building. Rule: a `ifdef SIMULATION may contain observation, stimulus, or a reference model — never a line the design's function depends on.

12.2 Confusing compile-time `ifdef with run-time if

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// WRONG — author expects to flip mode while the sim runs.
`ifdef FAST_MODE
    assign latency = 1;
`else
    assign latency = 4;
`endif

`ifdef is resolved once, at compile time, for the whole build. You cannot change FAST_MODE mid-simulation, and you cannot have one instance fast and another slow. If the choice must vary per instance use a parameter; if it must vary at run time use if (fast_mode). `ifdef is the wrong layer for anything that is supposed to change while running.

12.3 Undefined macro silently deletes a check (false green)

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifdef ENABLE_SCOREBOARD
    if (dut_result !== expected) begin
        $error("MISMATCH exp=%h got=%h", expected, dut_result);
        fail = 1;
    end
`endif

If the regression script forgets +define+ENABLE_SCOREBOARD, the entire comparison is stripped and the test passes by testing nothing — the most dangerous outcome in verification, because green is mistaken for correct. `ifndef makes the failure mode worse (typo the macro and the guard inverts). Defensive pattern: make the absence of a critical define loud —

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
`ifndef ENABLE_SCOREBOARD
    initial $display("WARNING: scoreboard disabled — this run checks nothing");
`endif

12.4 Inconsistent command-line defines across a regression farm

The same source gives different pass/fail results because two jobs were compiled with different +define+ sets — one machine had +define+RELAXED_TIMING, another did not, and a flaky test "passed here, failed there." Defines are part of the build's identity. Rule: pin the full +define+ set in the build script / filelist, store it with the results, and never pass defines ad hoc on an interactive command line for anything whose result you intend to trust.

12.5 Macros where parameters belong

Using `ifdef WIDTH_8 / WIDTH_16 / WIDTH_32 (or feature macros) to configure hardware instead of parameters. Symptoms: a recompile per variant, an explosion of macro combinations, and the impossibility of instantiating two configurations in one build. If the thing being configured ships in silicon, it is design configuration — use a parameter (§7), not a simulation macro.

13. Debugging Lab

Three conditional-simulation debug post-mortems

14. Coding Guidelines

  1. Never gate design logic. A `ifdef SIMULATION (or any macro) may wrap observation, stimulus, or a parity reference model — never a line the chip's function depends on. If you would not delete it from the netlist, do not put it behind a simulation macro.
  2. Answer "does this ship in silicon?" before choosing a conditional. No → compile-time `ifdef. Yes → parameter (per instance) or run-time if (per cycle). Never use `ifdef for hardware that must always exist.
  3. Configure hardware with parameters, not macros. Widths, depths, feature toggles that reach silicon are parameters (6.1) — they avoid recompile-per-variant and let configurations coexist.
  4. Make critical checks default-ON. Gate the disabling of a scoreboard or assertion, not its enabling, and print a loud warning when one is disabled — so a green run can never be mistaken for a checked run.
  5. Pin the +define+ set with the source. Defines belong in a committed filelist / build config consumed by every environment, never passed ad hoc — otherwise results are not reproducible.
  6. Nest narrowly. Put DEBUG_VERBOSE inside SIMULATION, so simulation-only verbosity can never exist in a synthesis build by accident.

15. Interview Q&A

16. Exercises

Exercise 1 — Pick the conditional layer

For each requirement, name the correct layer (compile-time `ifdef, elaboration-time parameter, or run-time if) and justify in one line.

#Requirement
1Print a transaction log line every cycle in nightly sim runs, nothing in synthesis
2Build the same module as 8-bit in one instance and 32-bit in another, in one design
3Switch a datapath between low-power and high-performance mode while the chip runs
4Compile in a behavioural DRAM model that scores the DUT, excluded from the netlist
5Select a single-cycle vs iterative multiplier, fixed per chip variant at build time
6Inject a parity fault as stimulus only in the error-handling regression

Exercise 2 — Find the layer-confusion bug

Each snippet uses the wrong conditional layer. Name the pitfall (from §12) and give the fix.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Snippet A
`ifdef LOW_POWER
    assign clk_en = sleep ? 1'b0 : 1'b1;   // author wants to toggle per cycle
`else
    assign clk_en = 1'b1;
`endif
 
// Snippet B
`ifdef ASSERT_ON
    always @(posedge clk)
        if (state == ILLEGAL) $error("illegal state");
`endif
// regression compile line never defines ASSERT_ON
 
// Snippet C — a real output's reset value
`ifdef SIMULATION
    initial data_out = 8'h00;     // "so sim starts clean"
`endif
// synth build leaves data_out powering up as X in gate sim / unknown in silicon

Exercise 3 — Design a regression-tier define scheme

A block has three regression tiers — smoke (5 min, basic checks), nightly (1 hr, full scoreboard + assertions), soak (8 hr, fault injection + long sequences) — and must also build clean for synthesis (no simulation code at all).

Produce: (a) the macro set and which tier defines which, (b) the guard structure in the testbench (which checks/stimulus sit behind which macro, and which are default-on), and (c) the committed defines.f filelist contents for each of the four builds. State explicitly which constructs must sit outside every guard and why.

17. Summary

Conditional simulation is build and simulator control, not RTL design. Its one job is to make simulation-only code — observation, stimulus, reference models, checks — present in the builds that need it and absent everywhere else, without ever altering the hardware the source describes.

The three conditional layers, and the rule for each:

  • Compile-time `ifdef / `ifndef — gates existence of source text, resolved before compilation, identical per instance. The only layer conditional simulation uses, and only for code that should not reach the netlist.
  • Elaboration-time parameter / generate — gates hardware shape per instance at build. The right tool for design configuration (widths, depths, feature toggles).
  • Run-time if / case — gates behaviour every cycle; always present in the netlist. The right tool for per-cycle operating modes.

The decision framework: Does this code ship in silicon? No → compile-time `ifdef. Yes → parameter (per instance) or run-time if (per cycle).

The failure modes, all layer confusions:

  • Hiding synthesizable RTL behind a sim flag → sim passes, silicon fails (different hardware).
  • Compile-time `ifdef used for run-time intent → cannot change while running; cannot vary per instance.
  • Undefined macro deletes a check → false-green regression that verifies nothing.
  • Drifting +define+ sets → non-reproducible, "flaky" pass/fail on the same commit.
  • Macros where parameters belong → recompile-per-variant macro sprawl.

The discipline in one sentence: conditional simulation gates the testbench, never the design — keep observation, stimulus, and checking conditional, keep the synthesizable RTL unconditional, and pin the macro set with the source.

The next section, 8.5 Simulation Control, closes Chapter 8 with $finish, $stop, and the run-control tasks that decide when a simulation ends. After 8.5, the curriculum turns to the RTL-core sequence — operators, continuous assignment, and procedural always blocks — where the conditional layers above become the everyday tools of design rather than of simulation control.

  • Conditional Compilation — Chapter 7.4; the `ifdef / `ifndef / `elsif directive mechanics this page assumes and applies.
  • System Tasks & Functions — Chapter 8; the parent overview, including the $display family that conditional simulation most often gates.
  • $display vs $monitor vs $strobe vs $write — Chapter 8.1; the observation tasks that live inside simulation guards.
  • parameter — Chapter 6.1; the elaboration-time layer that should replace macros for hardware configuration.