FPGA design is ruthless, so we need to check it using any software we can get our hands on. Verilator is a Verilog simulator that also supports linting: static analysis of problems in design. Verilator not only finds problems that synthesis tools might miss, but it also runs quickly. Verilator is also well suited for graphics simulation using SDL.
Install Verilator
Linux
Verilator is available in most Linux distribution repositories and is suitable for running on Windows guest Linux systems.
For Debian and Ubuntu-based distributions, Verilator can be installed using apt:
apt update
apt install verilator
Apple system
On macOS, the latest version of Verilator can be installed via the Homebrew package manager:
brew install verilator
To install Verilator for other platforms (such as FreeBSD), see the official Verilator installation guide.
Basic Linting
For standalone modules, linting itself is simple:
verilator --lint-only -Wall foo.v
--lint-only- 告诉 Verilator 进行 lint 但不生成任何仿真输出
-Wall- 打开额外的风格检查
If all goes well, you will not see messages from Verilator.
If Verilator detects a potential problem, it provides clear recommendations, including how to disable warnings. The Verilator manual contains a list of possible warnings.
Let's create a simple "add" module and set up a few issues, then lint it:
`default_nettype none
module add (
input wire clk,
input wire [3:0] x,
input wire [3:0] y,
output reg z,
output reg c
);
always @(posedge clk) begin
z <= x + y;
end
endmodule
Perform lint check
$ verilator --lint-only -Wall add.v
%Warning-WIDTH: add.v:12:11: Operator ASSIGNDLY expects 1 bits on the Assign RHS, but Assign RHS's ADD generates 4 bits.
: ... In instance add
12 | z <= x + y;
| ^~
... Use "/* verilator lint_off WIDTH */" and lint_on around source to disable this message.
%Warning-UNDRIVEN: add.v:8:16: Signal is not driven: 'c'
: ... In instance add
8 | output reg c
| ^
%Error: Exiting due to 2 warning(s)
The first problem is width: x and y are 4 bits wide, but z has no explicit width, so it's only 1 bit wide.
We can ignore the width warning by doing this:
always @(posedge clk) begin
/* verilator lint_off WIDTH */
z <= x + y;
/* verilator lint_on WIDTH */
end
This simply hides the problem without doing anything about it.
Instead, we can fix the problem by setting the width of z to 4:
output reg [3:0] z,
Although this eliminates the Verilator warning, it may not completely resolve the problem.
For example, what happens if x and y are both 4'b1000? Our addition overflows, calculating the z value of 4'b0000. This example illustrates one of the limitations of linting: it can look at the width of different signals, but it can't account for all the logic applied to them.
So instead of fixing the width of z, we can also use it as the c carry signal, which also solves the "signal not driven" warning:
always @(posedge clk) begin
{c,z} <= x + y;
end
Dependencies and paths
What happens if one module depends on another module? Verilator will search the current path for a matching module. If we want to add additional directories to the module search path, we can use -I. For example, if top.v depends on modules in the ../maths directory:
verilator --lint-only -Wall -I../maths top.v
Multiple -I parameters can be used to include multiple directories.
Black boxes and empty modules
Most designs rely on vendor primitives or IP cores without sources, such as using a PLL to generate the clock. When trying to lint a module that references a vendor primitive, you will receive an error like this:
%Error: clock_pix.sv:29:5: Cannot find file containing module: 'MMCME2_BASE'
29 | MMCME2_BASE #(
| ^~~~~~~~~~~
%Error: clock_pix.sv:29:5: This may be because there's no search path specified with -I<dir>.
29 | MMCME2_BASE #(
| ^~~~~~~~~~~
... Looked in:
MMCME2_BASE
MMCME2_BASE.v
MMCME2_BASE.sv
obj_dir/MMCME2_BASE
obj_dir/MMCME2_BASE.v
obj_dir/MMCME2_BASE.sv
The first idea might be to find a way to exclude MMCME2_BASElint. Alas, Verilog "cannot be checked without careful design, requiring the entire design". We can solve this problem by creating an empty module for the primitive. Empty modules contain IO but no logic.
For example, an empty module is created for Xilinx's BUFG primitive:
module BUFG (
input wire logic I,
output logic O
);
// NULL MODULE
endmodule
Creating an empty module is a bit tedious, but allows you to check the entire design. To use the null module, make sure it is in Verilator's search path (see the previous section).
Linting Waivers
If you need to eliminate linter warnings for larger designs or designs using third-party sources, the /* verilator lint_off */ comment may not work. Starting with Verilator 4.028, it is possible to create Waivers to handle warnings without touching the source code. To learn more, see Stefan Wallentowitz's post Verilator Waivers (https://wallento.cs.hm.edu/post/20200612-verilator-waivers/).
Linting Shell Script
If there are many top-level modules and/or include directories, this can be checked automatically using a Makefile or a simple shell script.
The following shell script lints all top-level modules in the same directory as this script:
#!/bin/sh
DIR=`dirname $0`
echo "## Linting top modules in ${DIR}"
for f in ${DIR}/top_*\.*v; do
echo "## Checking ${f}";
verilator --lint-only -Wall -I${DIR} -I${DIR}/../common $f;
done
You can adjust the -I parameter to suit your own setup. top_*.*v captures files with extensions .v and .sv.
Summarize
Today’s introduction to Verilog Lint about Verilator is like this. This is a powerful tool. I hope those who are interested can try it~