Thanks to community member Tholin for originally writing this article.
To make absolutely sure that your hardware design is functional as you want it, it is possible to write Verilog unit tests. This guide will show you how to write a simple test for your hardware design, and create a GitHub actions pipeline to automatically run your tests every time you push to your repository.
This document refers to the example adder design included in the Verilog template.
Check the GitHub actions, the test should be green and passing. You can check the logs of a recent test to see what happens.
Even if you develop tests on your PC, it’s a good idea to keep the action enabled. If it ever fails you’ll receive an email.
There is a good chance you already have most of what you need to get started if you’ve developed in Verilog before (for an FPGA, for instance). But just in case you don’t:
The first thing you need is a Verilog simulator. You can either install the full OSS CAD Suite (recommended), or you can install just the required packages:
sudo apt install iverilog verilator
pip3 install cocotb pytest
You should install pytest even if using the full CAD Suite, as it enables cocotb to print more verbose error messages if a test fails, helping you track down the issue faster.
The testbench tb.v
file instantiates the example project and wires it up. You’ll want to change the instantiation so it matches the name of your module.
Replace tt_um_example user_project
with the actual name of your top level module.
We use a Makefile to run the tests. You need to edit the Makefile to include all your source files.
To do that, go to the line starting with VERILOG_SOURCES
, and expand it to list all your files. Separate entries by spaces. Paths are relative to the directory the makefile is in (which should be ‘src’). If you only have a single verilog file, you only need one additional entry: $(PWD)/my_custom_verilog.v
Now, you can actually get started writing tests. Have a look at the example.
It:
You can use the name of any of the wires defined in tb.v
as values to set or read. assert
is, in this case, a statement that will fail the test if the expression following it does not evaluate to True. A delay is also inserted in-between setting the input, and checking the output.
Print debug messages using dut._log.info("test")
After you’ve run make
, you should also have a tb.vcd
file. You can open this with GTKWave, which is included in the OSS CAD Suite:
gtkwave tb.vcd
This can be very helpful to debug your tests and see your design in operation.
If you get stuck, let us know in the #verification channel of the discord chat.
The simulations we’ve covered above are all pre synthesis. A simulator reads the HDL design and simulates it.
It’s well worth running the same test on the post synthesis netlist. This post synthesis netlist is called a Gate Level netlist, because it includes all the actual standard cells (gates) used by your design. Gate Level testing can expose some bugs or issues that weren’t exposed by HDL simulation.
You can have a look at yours by downloading the GDS.zip from the actions page of your design and then looking at the file: runs/wokwi/results/final/verilog/gl/<your design name>.v
This Gate Level netlist snippet just shows 2 of the ~240 standard cells of an example design:
sky130_fd_sc_hd__and4_1 _319_ (.A(\second_counter[7] ),
.B(\second_counter[9] ),
.C(\second_counter[10] ),
.D(\second_counter[12] ),
.VGND(VGND),
.VNB(VGND),
.VPB(VPWR),
.VPWR(VPWR),
.X(_145_));
sky130_fd_sc_hd__dfxtp_2 _320_ (.CLK(clknet_2_0__leaf_clk),
.D(_007_),
.VGND(VGND),
.VNB(VGND),
.VPB(VPWR),
.VPWR(VPWR),
.Q(\seg7.counter[0] ));
You can see the standard cells also have power ports, so one thing that has to change is the design must be powered. That happens automatically for you when the test is run as part of the GDS action.
Whenever the GDS action is triggered, your testbench will be run as a Gate Level test automatically!
If you get stuck, let us know in the #github-actions channel of the discord chat.