simulacion-fpga-testbench-gtkwave

FPGA Simulation with Testbench and GTKWave

  • 6 min

An HDL simulation is the virtual execution of a design to verify its signals and temporal behavior before loading it onto the FPGA.

So far we’ve been brave: we write code, synthesize it, and load it onto the board hoping it works. We call this technique “Compile & Pray”.

In the professional world and with complex designs, this is unfeasible.

  1. Synthesis can take hours.
  2. If something fails in the hardware, you don’t have a printf or an easy-to-use debugger to see what’s happening inside.

To avoid this, we use simulation. Today we will create a virtual lab to test our circuits before they touch the silicon.

The healthy habit of a hardware designer. If you haven’t simulated it, it doesn’t work.

What is a Testbench?

A Testbench (Test Bench) is a Verilog file that is not loaded onto the FPGA. It is a program that runs on your computer to test your design.

Imagine you are designing a car engine (your module). The Testbench is not part of the engine; it is the test bench where you mount the engine, connect fuel hoses, press the accelerator, and measure the RPMs.

The Testbench has two missions:

  1. Generate Stimuli: Create fake signals like a clock, buttons being pressed, incoming data…
  2. Observe Responses: Check if your module’s outputs behave as expected.

The module we are going to test (DUT)

For this example, we will use a simple circuit: an AND gate combined with a Flip-Flop. We’ll call it mi_circuito. In the jargon, this is called the DUT (Device Under Test) or UUT (Unit Under Test).

// mi_circuito.v
module mi_circuito (
    input wire clk,
    input wire a,
    input wire b,
    output reg q
);

    always @(posedge clk) begin
        q <= a & b; // Registered AND
    end

endmodule
Copied!

How to write the Testbench

Now we create a new file, for example mi_circuito_tb.v.

A Testbench has a peculiarity: It has no inputs or outputs. It is a closed world.

// mi_circuito_tb.v
`timescale 1ns / 1ps  // Define the time scale (Unit / Precision)

module testbench();

    // 1. Internal signals to connect to the DUT
    // We use 'reg' for inputs because we will control them
    reg clk;
    reg a;
    reg b;
    
    // We use 'wire' for outputs because we only read them
    wire q;

    // 2. DUT (Device Under Test) Instance
    // Connect our signals to the real module
    mi_circuito uut (
        .clk(clk),
        .a(a),
        .b(b),
        .q(q)
    );

    // 3. Clock Generator
    // This creates an infinite clock that toggles every 1 time unit
    initial begin
        clk = 0;
        forever #1 clk = ~clk; 
    end

    // 4. Stimulus Block (The movie script)
    initial begin
        // Setup for saving the waves (required for GTKWave)
        $dumpfile("waves.vcd"); // Output file name
        $dumpvars(0, testbench); // Save all variables in this module

        // Initialize values
        a = 0;
        b = 0;

        // Wait and change values
        #10 a = 1; b = 0;  // At time 10ns
        #10 a = 1; b = 1;  // At time 20ns (We expect q to become 1 after the edge)
        #10 a = 0; b = 1;  // At time 30ns
        #10 a = 1; b = 1;
        
        #20; // Wait a bit more
        
        $finish; // End the simulation
    end

endmodule
Copied!

Simulation Commands

  • initial: Unlike always, this block runs only once at the beginning. It’s perfect for writing the test script.
  • #10: Means “wait 10 time units”.
  • $dumpfile and $dumpvars: These are system commands (starting with $) that tell the simulator to record everything that happens in a .vcd (Value Change Dump) file.
  • $finish: Stops the simulation. If you don’t put it, the infinite clock (forever) will make the simulation run forever.

How to run the simulation

To simulate, we need an engine. The standard in the Open Source world is Icarus Verilog (iverilog).

If you are using Apio and VSCode, it’s trivial:

  1. Make sure your testbench file (_tb.v) is in the folder.
  2. Open the command palette or use the Apio buttons.
  3. Press “Apio: Sim”.

Under the hood, Apio compiles the testbench and design, runs the simulation, and generates the waves.vcd file.

Visualization with GTKWave

Once the simulation finishes, Apio will automatically open GTKWave for you. If not, you can open it manually and load the .vcd file.

GTKWave is our digital oscilloscope.

How to read the interface

  1. Left Panel (SST): Here you see the hierarchy of your design. Click on testbench and then uut. You’ll see the signals clk, a, b, q.
  2. Drag signals: Select the signals and drag them to the black area on the right (or double-click).
  3. Magnifying Glass: Use the Zoom buttons (the “Fit” square magnifier is very useful) to view the full timeline.

Interpreting the waves

You will see green lines (or red ones if there’s an error) going up and down.

  • Line Up: Logic 1 (High).
  • Line Down: Logic 0 (Low).
  • Shaded/Red Areas (X): Unknown value. This often happens at the beginning of the simulation before giving an initial value to the registers.

In our example, if a and b are set to 1, the output q does not change instantly. It will change right after the next rising edge of the clk clock. That’s the synchronous behavior we were looking for!

Testbenches with automatic checking

Looking at waves is fine for a start, but with large designs, your eyes will get tired. A professional Testbench checks itself. We can use if to verify results automatically:

    // Inside the initial block...
    #10 a = 1; b = 1;
    #2; // Wait a bit after the clock edge
    
    if (q !== 1'b1) begin
        $display("ERROR: 1 AND 1 should be 1, but I have %b", q);
    end else begin
        $display("TEST PASSED: The AND gate works.");
    end
Copied!

This way, when you run the simulation, the console will tell you if your circuit passed the quality check.