One of the nice things about working with FPGAs and Verilog is the array of open source tools and development boards that are available.
For this article I'll be using the iCE40-feather development board by Josh Johnson which is based on the Lattice iCE40 UltraPlus Breakout Board, but the simulation set up should be the same for many boards.
If you are using an iCE40-feather, you can follow the set up instructions to get the icestorm toolchain installed, and get drivers working for this FTDI based board.
Our aim is to take a verilog file, a testbench and simulate it using Icarus Verilog / GTKWave.
We'll start with a simple standard blinker in one file.
Create a folder called:
simple-blink
Create a verilog file inside the folder and call it blink.v
:
`default_nettype none module top #(parameter integer DELAY = 2500000) ( input wire clk, output wire nLED_GRN ); reg [31:0] r_MAX = DELAY; reg [31:0] r_COUNT = 0; reg r_led = 0; always @ (posedge clk) begin if (r_COUNT < r_MAX) begin r_COUNT <= r_COUNT + 1; end else begin r_COUNT <= 0; r_led <= !r_led; end end assign nLED_GRN = r_led; endmodule
Nice and simple, blink the green LED on the iCE40-feather board after several thousand clock cycles.
Now a testbench file, create a blink_tb.v
file in our project folder:
`timescale 1ns/1ns `include "blink.v" module blink_tb; reg tClk = 1'b0; wire tLed; top #( .DELAY(10) ) UUT ( .clk(tClk), .nLED_GRN(tLed) ); always #2 tClk <= !tClk; // Dump wave initial begin $dumpfile("blink_tb.lxt"); $dumpvars(0,blink_tb); end initial begin #100; $finish; end endmodule
I'll explain a couple of things from the testbench. Firstly, an ns timescale is being used. So each time unit is a nanosecond. This will keep the timescale nice and compressed to view in the simulation. In the real module, the timescale has too much data to comfortably view.
There is a dump file generated using the testbench. Icarus Verilog / GTKWave uses these files for its simulation.
Now to download and install Icarus Verilog / GTKWave from: http://bleyer.org/icarus
Open a commandline to the project folder. We'll execute all the steps manually. And later, we'll move them to a Makefile to make life much easier.
Create a sim
folder with:
mkdir sim
Next…
iverilog -Wall -o sim/blink_tb blink_tb.v
This will compile the testbench into code suitable for simulation by the vvp
runtime.
vvp sim/blink_tb -lxt2
vvp takes the compiled blink_tb and executes a simulation with it. The -lxt2 flag means to output an lxt2 dump file that the GTKWave is able to nicely display for us.
mv blink_tb.lxt sim/blink_tb.lxt
Simply move the resulting blink_tb.lxt file into the sim
folder.
gtkwave sim/blink_tb.lxt
Here's the exciting bit. GTKWave will open up and load the simulation dump. But as yet, no signals have been chosen to visualise:
Drag the clk[0]
and nLED_GRN[0]
signals into the Waves
pane and your waveforms will display!
The wave setup is lost if you exit GTKWave. But you can save a config so that these signals are chosen next time you recompile the code. Select File
> Write Save File As
.
Save the file into the sim
folder as: gtkwaveConfig.gtkw
Now close GTKWave. If we change the gtkwave
command next time to:
gtkwave sim/blink_tb.lxt sim/gtkwaveConfig.gtkw
…the signals you set previously will be shown again.
It would be cumbersome to type all those commands each time you made a change to your verilog code to display your simulation. Thankfully a makefile can make like so much easier.
You can pinch a really nice one from Josh Johnson's version of the WTFpga course.
The main piece of interest from the makefile is this:
simulate: iverilog -Wall -o sim/$(PROJ)_tb $(PROJ)_tb.v vvp sim/$(PROJ)_tb -lxt2 mv $(PROJ)_tb.lxt sim/$(PROJ)_tb.lxt gtkwave sim/$(PROJ)_tb.lxt sim/gtkwaveConfig.gtkw
Copy the Makefile into the root of your project folder.
Now you can execute everything and see your simulation in one step with:
make simulate
And that's it. Enjoy!