16.8 Simulation vs. Synthesis: Two Different Meanings of "Running" HDL
Explain the distinct roles of simulation and synthesis in the HDL design flow, and identify which Verilog constructs are simulation-only versus synthesizable.
Every lesson in this module has quietly relied on a distinction that this final lesson makes explicit: writing Verilog code does not, by itself, produce hardware — that same text is processed by two fundamentally different tools for two fundamentally different purposes. Simulation checks whether a design behaves the way it's supposed to, entirely inside software, with no real hardware involved at all. Synthesis converts a synthesizable subset of that same HDL into an actual hardware implementation — gates on an ASIC, or configured look-up tables on an FPGA. Understanding which constructs belong to which world (and that some constructs, like the #10 delay and initial block from the previous lesson's testbench, belong only to simulation) is what separates a design that merely 'compiles' from one that is actually correct, both on paper and in silicon.
Simulation: Checking Behavior in Software
A simulator is a software program that reads Verilog/SystemVerilog code and executes it against a model of simulated time, computing what every signal's value would be at every point in that timeline, exactly as the testbench in 16.7 demonstrated. Nothing about simulation requires any of the code to be realizable as physical hardware — a simulator is happy to run #10 delays, initial blocks, $display print statements, and any other construct useful for describing test stimulus or debugging output, because its entire job is behavioral verification, not hardware realization.
This is precisely why testbenches (16.7) can use constructs RTL modules should not: a testbench never needs to become a physical circuit, so it is free to use the full, unrestricted Verilog language, including timing controls and I/O system tasks that have no meaningful hardware equivalent.
Synthesis: Converting HDL into Hardware
A synthesis tool reads a restricted subset of Verilog/SystemVerilog — the synthesizable subset — and converts it into an actual hardware implementation: a gate-level netlist targeting an ASIC standard-cell library, or a configuration for an FPGA's look-up tables, flip-flops, and routing fabric. Every RTL construct covered earlier in this module (module/port declarations in 16.1, bitwise operators in 16.2, gate primitives in 16.3, always_comb in 16.4, always_ff in 16.5, and the blocking/nonblocking discipline in 16.6) was deliberately restricted to exactly this synthesizable subset, which is why those examples can move from 'simulated and verified' to 'built into real hardware' without modification.
The module's own flow diagram summarizes the complete pipeline directly: HDL is written, then simulated to check behavior, then synthesized into a hardware representation, and finally implemented as an actual gate netlist, FPGA bitstream, or ASIC layout. Simulation and synthesis are not alternatives to each other — a real design flow always does both, in that order, precisely because simulation is fast and cheap to iterate on while synthesis (and especially fabrication) is slow and expensive, so catching a bug in simulation before synthesis saves enormous time and cost.
Not Every Construct Is Synthesizable
The module's tricky-problem statement for this final topic is the single most important caution in the entire module: not every HDL construct is synthesizable, and testbench delays and simulation-only constructs may not represent physical hardware. #10 (a fixed time delay) has no meaning in real silicon — a physical AND gate does not know how to 'wait 10 nanoseconds' the way a simulator's delay statement does; real timing in hardware comes entirely from the physical propagation delay of actual gates and wires, something the synthesis and place-and-route tools calculate afterward, not something an RTL author specifies with a delay statement.
The initial block itself is fundamentally a simulation-only construct in almost all practical RTL contexts: it describes 'run this once, starting at time 0,' which has no equivalent in a piece of hardware that, from the moment it's powered on, has no built-in notion of 'the beginning of the simulation.' (Some FPGA tools permit initial blocks solely to specify a register's power-up value for simulation/bitstream-initialization purposes — but using initial to sequence general logic, the way a testbench does, is not synthesizable.) $display, $finish, $monitor, and the other system tasks used for printing and controlling simulation likewise have no hardware meaning at all — they exist purely to give the engineer visibility during simulation.
• #<N> delay statements — simulation timing only; real hardware timing comes from physical propagation delay, not a delay statement. • initial blocks used to sequence stimulus — a simulation-only way to describe 'run once, from time 0'; hardware has no such starting point built in. • $display, $monitor, $finish, and other system tasks — exist purely for simulation visibility and control, with no hardware equivalent. • By contrast, always_comb, always_ff, assign, gate primitives, and properly structured case/if logic (as used throughout 16.1–16.6) are all synthesizable.
Why This Distinction Matters in Practice
Every mistake covered earlier in this module — the &/&& mix-up (16.1), an incomplete case inferring a latch (16.4), blocking assignment inside sequential logic (16.6) — matters precisely because these are all synthesizable-subset issues: the resulting hardware genuinely differs from what was intended, and no amount of simulation alone guarantees that mismatch is caught, especially the latch-inference and race-condition bugs, which can simulate 'correctly' while still synthesizing into unwanted or incorrect hardware. This is why real design flows always run a synthesis pass (and review its reports for inferred latches, unexpected structures, and timing violations) rather than trusting simulation results alone.
The practical discipline this leaves a beginner with is simple to state: while learning, write RTL modules (16.1–16.6) using only synthesizable constructs, and reserve simulation-only constructs like #10, initial (for stimulus), and $display strictly for testbenches (16.7), never for the design itself. Keeping that boundary sharp from the very start avoids an entire category of 'it worked in simulation but the real chip didn't do that' bugs.
The HDL Design Flow
This is the module's own stated flow, in order: write HDL, simulate it to check behavior, synthesize it into hardware, and implement it as a real gate netlist, FPGA bitstream, or ASIC layout. Simulation-only constructs (delays, initial-block stimulus, $display/$finish) never proceed past the simulation stage — they are stripped out or rejected by synthesis. A design should always pass through simulation before synthesis, since bugs are far cheaper to find and fix at the simulation stage.
HDL
↓
Simulation (behavior check — software only, full language allowed)
↓
Synthesis (HDL -> hardware representation — restricted, synthesizable subset only)
↓
Gate / FPGA / ASIC implementationCode Examples
dff_simple uses only synthesizable constructs — always_ff with nonblocking assignment and no timing or I/O system tasks — so it can be simulated to verify correctness and then synthesized directly into real flip-flop hardware, unchanged. tb_dff, by contrast, generates its own clock with always #5 clk = ~clk; (toggle every 5 time units forever — meaningless as a hardware description, since real hardware doesn't 'toggle a wire every 5 nanoseconds' as an instruction, it just responds to a physically oscillating clock source) and drives stimulus with #-delays, $display, and $finish — none of which synthesis tools accept as hardware description, and none of which are meant to be. Expected result: dff_simple synthesizes to one physical flip-flop; tb_dff never synthesizes to anything — it only ever exists to drive and check dff_simple in simulation.
always #5 clk = ~clk; // clock generator: simulation-only
// SYNTHESIZABLE: this module can become real hardware
module dff_simple (
input wire clk,
input wire d,
output logic q
);
always_ff @(posedge clk) begin
q <= d; // no delays, no $display — pure hardware description
end
endmodule
// SIMULATION-ONLY: this module can never become hardware
module tb_dff;
logic clk = 0, d;
logic q;
dff_simple dut (.clk(clk), .d(d), .q(q));
always #5 clk = ~clk; // clock generator: simulation-only
initial begin
d = 0;
#12 d = 1;
#20 $display("q = %b at time %0t", q, $time);
#10 $finish;
end
endmoduleHDL design flow: from text to silicon
The same HDL text feeds both stages, but simulation accepts the entire language while synthesis accepts only the synthesizable subset.
┌─────┐ ┌────────────┐ ┌───────────┐ ┌────────────────────┐
│ HDL │───►│ Simulation │───►│ Synthesis │───►│ Gate / FPGA / ASIC │
└─────┘ └────────────┘ └───────────┘ └────────────────────┘
(text) (behavior check, (restricted (real hardware:
full language) synthesizable standard cells,
subset only) LUTs, or a chip)Important Rules
Simulation checks behavior in software and accepts the full HDL language, including timing controls and I/O system tasks.
Synthesis converts a restricted, synthesizable subset of HDL into a real hardware implementation (gate netlist, FPGA configuration, or ASIC layout).
Delay statements (#<N>), stimulus-sequencing initial blocks, and system tasks like $display/$finish are simulation-only and have no synthesizable hardware equivalent.
always_comb, always_ff, assign, gate primitives, and properly complete case/if logic are synthesizable when written according to the rules from 16.1–16.6.
A real design flow always simulates before synthesizing, because bugs are dramatically cheaper to catch and fix in simulation than after synthesis or fabrication.
Why the Synthesizable Subset Exists
Chip and FPGA design teams enforce the synthesizable-subset discipline with linting tools that scan RTL for non-synthesizable constructs before a design is even sent to synthesis, precisely because a mismatch between simulated and synthesized behavior discovered late in a project (or worse, after fabrication) is enormously expensive to fix. Keeping simulation-only code strictly confined to testbenches — never letting a #10 or an initial-block stimulus sequence creep into an RTL module — is one of the most basic and most enforced coding-standard rules in professional digital design.
always_ff @(posedge clk) begin #5 q <= d; // delay inside an RTL module intended for synthesis end always_ff @(posedge clk) begin q <= d; // no delay — real timing comes from physical propagation delay end
Placing a #5 delay inside a module meant to be synthesized is a direct violation of the synthesizable subset: synthesis tools either reject it outright or silently ignore the delay, meaning the simulated timing behavior (which does honor the delay) will not match the real hardware's timing behavior (which is governed entirely by physical gate and wire propagation delay, calculated later by the synthesis/timing tools, not specified by the RTL author).
[tricky-problem] A testbench module uses #10 delays and an initial block to sequence stimulus into a DUT. Can these same constructs be used inside the DUT's own RTL and still expect the design to synthesize correctly? A. No — delay statements and stimulus-sequencing initial blocks are simulation-only constructs with no physical hardware equivalent; real hardware timing comes from physical propagation delay, not a delay statement B. Yes — any construct valid in a testbench is automatically valid in synthesizable RTL as well C. Only the initial block is synthesizable; #10 delays are always rejected D. Only #10 delays are synthesizable; initial blocks are always rejected
A. No — delay statements and stimulus-sequencing initial blocks are simulation-only constructs with no physical hardware equivalent; real hardware timing comes from physical propagation delay, not a delay statement — This is the module's central tricky problem for this lesson: not every HDL construct is synthesizable, and testbench delays and simulation-only constructs may not represent physical hardware. #10 has no hardware meaning because real gates don't 'wait' on command — their timing comes from physical propagation delay computed after synthesis. An initial block used to sequence logic similarly has no equivalent in hardware that has no built-in notion of 'simulation time zero.'
[interview] What is the fundamental difference between what a simulator does with Verilog code and what a synthesis tool does with it? A. A simulator executes the code against a model of simulated time to check behavior in software; a synthesis tool converts a restricted, synthesizable subset of the code into an actual hardware implementation B. A simulator and a synthesis tool do exactly the same thing, just at different points in the design flow C. A simulator only works on testbenches, while a synthesis tool only works on gate-primitive modules D. A synthesis tool checks for syntax errors, while a simulator checks for timing violations
A. A simulator executes the code against a model of simulated time to check behavior in software; a synthesis tool converts a restricted, synthesizable subset of the code into an actual hardware implementation — Simulation is a behavioral check performed entirely in software, using the full HDL language including simulation-only timing and I/O constructs. Synthesis is a translation step that maps a restricted subset of that same language onto real gates, look-up tables, or standard cells — a fundamentally different kind of processing with a fundamentally different, and much more limited, accepted input.
Which of the following is a synthesizable construct, usable directly inside an RTL module intended for real hardware? A. always_ff @(posedge clk) q <= d; B. #10 q <= d; C. initial begin q = 0; #5 q = 1; end D. $display("q=%b", q);
A. always_ff @(posedge clk) q <= d; — always_ff @(posedge clk) q <= d; is a pure, delay-free edge-triggered assignment — exactly the synthesizable pattern covered in 16.5 and 16.6. The other three options all rely on simulation-only constructs (a delay statement, an initial-block stimulus sequence, and a print system task) that have no hardware equivalent and are rejected or ignored by synthesis tools.
Given the module below, identify every construct that would prevent it from being synthesized, and rewrite it as valid, synthesizable RTL describing the same intended AND-gate-with-registered-output behavior. module bad( input wire clk, a, b, output logic y); initial y = 0; always_ff @(posedge clk) begin #3 y <= a & b; end endmodule module fixed ( input wire clk, input wire rst_n, input wire a, input wire b, output logic y); always_ff @(posedge clk or negedge rst_n) begin if (!rst_n) y <= 1'b0; else y <= a & b; end endmodule
Two constructs are non-synthesizable here: the initial y = 0; statement used to set a starting value procedurally, and the #3 delay inside the always_ff block. Synthesizable RTL sets an initial/reset value through an actual reset signal, not an initial statement, and never uses delay controls.