Basic operations of on-site programmable gate array simulation tools
FPGA Simulation Tools: Basic Operations Every RTL Designer Needs to Know
Writing RTL code is only half the job. The other half is proving that the code actually does what you think it does. That is where simulation tools come in. They let you run your design in software before you ever touch hardware. Catch a bug in simulation and it costs you five minutes. Catch that same bug on the board and it costs you a respin.
Every FPGA project needs a simulation flow. The tools vary, but the operations are the same across the board. This guide covers the fundamentals you need to get started, regardless of which simulator you end up using.
Setting Up Your First Simulation Project
Before you write a single testbench, you need to understand the three files that make up any simulation.
The design under test (DUT). This is your RTL module, the thing you actually want to verify. It could be a state machine, a FIFO, a DSP block, whatever you are building.
The testbench. This is a separate module that instantiates the DUT, drives inputs with known patterns, and checks outputs against expected results. The testbench never gets synthesized. It exists only for simulation.
The simulation script or project file. This tells the tool which files to compile, in what order, and what the top-level module is. Without this, the tool does not know where to start.
A minimal Verilog testbench looks like this:
verilog1`timescale 1ns / 1ps
2
3module tb_adder;
4 reg [7:0] a, b;
5 wire [7:0] sum;
6
7 // Instantiate the design under test
8 adder #(.WIDTH(8)) dut (
9 .a(a),
10 .b(b),
11 .sum(sum)
12 );
13
14 initial begin
15 // Test case 1
16 a = 8'd10; b = 8'd20;
17 #10;
18 if (sum !== 8'd30) $display("FAIL: 10+20 != 30");
19
20 // Test case 2
21 a = 8'd255; b = 8'd1;
22 #10;
23 if (sum !== 8'd0) $display("FAIL: overflow not handled");
24
25 $finish;
26 end
27endmodule
28
That is the entire flow. Instantiate, stimulate, observe, report. Everything else is just variations on this theme.
Running Simulation: The Step-by-Step Workflow
Most simulation tools follow the same four-step process. Learn this once and it applies everywhere.
Step 1: Compile the Design Files
The tool reads your RTL files and builds an internal database of modules, signals, and connections. This is called elaboration. Errors here are usually syntax mistakes, missing semicolons, undeclared signals, or port mismatches between the DUT and the testbench.
If you get a “module not found” error, check your include paths. The tool needs to know where to look for your files.
Step 2: Load the Simulation
Once compilation succeeds, the tool loads the design into memory. At this point, no time has passed yet. The simulation is frozen at time zero, waiting for you to tell it to start.
Step 3: Run the Simulation
You tell the tool to advance time. It executes every always block, every initial block, every assignment, in the order the scheduler determines. Time moves forward based on your timescale directive.
You can run the simulation in different modes.
Run all. The tool runs until it hits a $finish statement or runs out of events. This is what you use for most testbenches.
Run for N nanoseconds. Useful when your testbench does not have a $finish and you just want to see what happens over a specific time window.
Run continuously. The tool keeps going until you manually stop it. Good for debugging interactions that happen over long periods, like a UART transmission or a memory write sequence.
Step 4: Inspect the Waveforms
This is where you actually see what your design is doing. The waveform viewer shows every signal in your design plotted against time. You zoom in, you measure delays, you check setup and hold times visually.
The golden rule: if your waveform does not look right, the design is wrong. Do not trust a simulation that passed but you never looked at. Always open the waveform viewer and actually watch the signals toggle.
Writing a Testbench That Actually Catches Bugs
A testbench that only checks one happy-path scenario is worse than no testbench at all. It gives you false confidence. Here is how to write one that finds real problems.
Stimulate Every Input Combination That Matters
Do not just test normal cases. Test boundary conditions. Test overflow. Test underflow. Test reset behavior. Test what happens when two inputs change at the exact same time.
For a simple adder, that means testing:
Zero plus zero. Maximum plus zero. Maximum plus maximum. Negative values if signed. Random values in a loop that runs a thousand times.
verilog1integer i;
2initial begin
3 for (i = 0; i < 1000; i = i + 1) begin
4 a = $random;
5 b = $random;
6 #5;
7 if (sum !== a + b) $display("MISMATCH at time %0t", $time);
8 end
9end
10
This catches corner cases that your hand-written test vectors would miss.
Use Self-Checking Testbenches
Manual waveform inspection is slow. A self-checking testbench compares the DUT output against a reference model and prints pass or fail automatically.
verilog1reg [7:0] expected;
2always @(*) begin
3 expected = a + b;
4 if (sum !== expected) begin
5 $display("ERROR: %0d + %0d = %0d, expected %0d", a, b, sum, expected);
6 error_count = error_count + 1;
7 end
8end
9
At the end of simulation, you print the error count. Zero errors means the design passed. Non-zero means something is broken. No waveform viewer needed.
Check Clock Domain Crossing Carefully
Most bugs in real FPGA designs happen at clock domain boundaries. Your testbench must model this. If your DUT has two clock domains, your testbench needs two clock generators with a known phase relationship.
verilog1reg clk_a, clk_b;
2initial begin
3 clk_a = 0;
4 forever #5 clk_a = ~clk_a;
5end
6
7initial begin
8 clk_b = 0;
9 forever #7 clk_b = ~clk_b;
10end
11
Different frequencies. Different phases. This is what your design will see in hardware, so this is what your simulation must see.
Common Simulation Mistakes That Waste Hours
Forgetting the timescale directive. Without `timescale, the simulator does not know what #10 means. Is it 10 nanoseconds? 10 picoseconds? 10 seconds? The default varies by tool, and it will not be what you expect. Always put `timescale at the top of every file.
Using $display instead of $monitor. The $display statement prints once when it executes. The $monitor statement prints every time any signal in its list changes. For debugging, $monitor saves you from adding print statements everywhere.
Not resetting the design. If your testbench does not assert reset at time zero, your DUT starts in an unknown state. Every simulation run gives different results. Always add a reset sequence at the beginning of your testbench.
verilog1initial begin
2 rst_n = 0;
3 #20;
4 rst_n = 1;
5 #10;
6 // now start applying test vectors
7end
8
Simulating at the wrong abstraction level. Behavioral simulation is fast but does not show timing. Post-synthesis simulation includes gate delays but takes forever. Post-place-and-route simulation is accurate but painfully slow. Know which level you need for what you are verifying. For most functional bugs, behavioral simulation is enough.
A Practical Simulation Checklist
Before you say your design is verified, run through this list.
Did you simulate with reset asserted at the start? Yes or no.
Did you test all boundary conditions, not just the middle of the range? Yes or no.
Did you check clock domain crossings with realistic clock relationships? Yes or no.
Did you look at the actual waveforms, not just the pass/fail log? Yes or no.
Did you run the simulation long enough to catch slow interactions? Yes or no.
If any answer is no, go back and fix it. Simulation is not optional. It is the cheapest debug tool you will ever use. Skip it and you pay for it later on the board.
ChipApex is a global distributor of electronic components: ICs, semiconductors, passives & interconnects. Source active & obsolete parts with wholesale pricing, fast RFQ response, and worldwide delivery.Official website address:chipapex.com