Complete Guide to Verilog & SystemVerilog Simulation and RTL Synthesis
Hardware Description Languages (HDLs) describe digital circuits structurally and behaviorally. Unlike sequential software programs that execute line by line on a CPU, Verilog modules describe hardware blocks that operate concurrently on every clock edge or input transition.
Combinational vs. Sequential Logic in RTL Design
Digital circuits fall into two foundational categories: combinational logic, where the output depends solely on the current inputs, and sequential logic, where the output depends on both current inputs and stored state (flip-flops or registers) synchronized to a clock signal.
In Verilog and SystemVerilog, combinational logic is modeled using continuous assignments (assign statements) or procedural blocks sensitive to all inputs (always @(*) or always_comb). Sequential logic is modeled using edge-sensitive procedural blocks (always @(posedge clk) or always_ff @(posedge clk)) that infer D-type flip-flops.
module sync_counter #(
parameter int WIDTH = 8
) (
input logic clk,
input logic rst_n,
input logic en,
output logic [WIDTH-1:0] count
);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= '0;
else if (en)
count <= count + 1'b1;
end
endmodule- Use blocking assignments (=) inside combinational blocks (always @(*) / always_comb).
- Use non-blocking assignments (<=) inside clocked sequential blocks (always @(posedge clk) / always_ff) to prevent simulation race conditions.
- Always assign a default value to every output in combinational blocks to avoid unintended transparent latches during synthesis.
Writing Effective Testbenches & Generating VCD Waveforms
A testbench is an unclocked top-level wrapper module that instantiates your Design Under Test (DUT), generates clock and reset stimuli, drives input vectors, and monitors output responses. To inspect signals in the Waveform Viewer, include the standard $dumpfile and $dumpvars system tasks inside an initial block.
Self-checking testbenches go beyond visual waveform inspection by automatically comparing the DUT output against a golden reference model and printing PASS or FAIL diagnostics via $display or $error.
module tb_counter;
logic clk = 0;
logic rst_n = 0;
logic en = 0;
logic [7:0] count;
// 10ns clock period (100 MHz)
always #5 clk = ~clk;
sync_counter #(.WIDTH(8)) dut (
.clk(clk), .rst_n(rst_n), .en(en), .count(count)
);
initial begin
$dumpfile("dump.vcd");
$dumpvars(0, tb_counter);
#12 rst_n = 1; en = 1;
#80 en = 0;
#20 $finish;
end
endmoduleUnderstanding Gate-Level Synthesis with Yosys
When you click Synthesize in the Verilog Lab, your behavioral RTL code is parsed and elaborated into a structural netlist of logic primitives—such as AND, OR, XOR, NOT, multiplexers ($mux), and D flip-flops ($dff). Reviewing the synthesized schematic helps you verify that your code maps cleanly to hardware without inferred latches or redundant combinational paths.
Verilog & SystemVerilog Design Rules Quick Reference
| Circuit Type | Procedural Block | Assignment Operator | Common Pitfall |
|---|---|---|---|
| Combinational Logic | always @(*) / always_comb | Blocking (=) | Missing else/default branch infers an unintended latch |
| Sequential Flip-Flops | always @(posedge clk) / always_ff | Non-blocking (<=) | Using blocking (=) causes simulation race conditions |
| Continuous Wire Assign | assign y = a & b; | Continuous (=) | Driving the same wire from multiple assign statements |
| Testbench Stimulus | initial begin ... end | Blocking (=) / Non-blocking (<=) | Forgetting $finish or $dumpvars(0, tb) |
Frequently Asked Questions
- Why does my simulation say no VCD waveform was generated?
- Ensure your testbench includes $dumpfile("dump.vcd"); and $dumpvars(0, <testbench_module_name>); inside an initial block, and that your simulation terminates cleanly with $finish; before timing out.
- What is the difference between Verilog-2005 and SystemVerilog-2012 in the lab?
- SystemVerilog extends IEEE 1364 Verilog with the unified logic data type, dedicated hardware blocks (always_ff, always_comb, always_latch), enums, structs, interfaces, and object-oriented verification constructs.
- Why did my combinational case statement create a latch during synthesis?
- If a case or if-else statement inside an always @(*) block does not assign a value to an output register in every possible branch, hardware must preserve the previous value—which requires inferring a level-sensitive latch. Add a default assignment at the top of the block.