Getting Started
A Basic Verilog testbench
Before introducing Verilator let`s first describe a simple testbench with Verilog.
In a new file src/hello_tb.v enter the following:
module hello_tb;
reg clk;
parameter N_CYCLES = 20;
parameter PULSE_WIDTH = 5;
integer j;
initial begin
clk <= 0;
for (j=0; j<N_CYCLES; j = j + 1)
#PULSE_WIDTH clk <= ~clk;
end
always @(posedge clk) begin
$display("tick");
end
endmodule
This module only drives a clock signal which can be used to test a digital system. You can compile and run this module with iverilog for example:
$ iverilog -o hello_tb src/hello_tb.v
$ vvp hello_tb
You should see the following output:
tick
tick
...
The system under test is very simple as our main focus at this stage is to study how parts of this setup can be written with C++.
Building Systems with Verilator
The example above uses the square symbol (#) to implement pulses. Verilog code that shall be translated to C++ (or "verilated" code) would not be able to process these delays in Verilog. Therefore let's remove the clock generator from Verilog. It will be addressed later in C++.
To make the system under test a bit more interesting, we can add a counting action: To track the variables with digital waveforms, the Verilog dumpfile and dumpvar tasks are inserted. The resulting module is:
module hello(input clk, output [7:0] y);
reg [7:0] n;
initial begin
$dumpfile("waves.vcd");
$dumpvars();
n = 8'h0;
end
always @(posedge clk) begin
$display("tick");
n <= n + 1;
end
assign y = n;
endmodule
The next step is to setup the C++ code that drives this device under test (or DUT).
In Verilator, clocks can be generated with C++ or with SystemC. One possiblity to drive the module is as follows:
#include "verilated.h"
#include "Vhello.h"
void wait_n_cycles(int n);
// Keep track of simulation time (64-bit unsigned)
vluint64_t main_time = 0;
// Called by $time in Verilog an needed to trace waveforms
double sc_time_stamp() {
return main_time; // Note does conversion to real, to match SystemC
}
int main(int argc, char ** argv) {
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true);
Vhello* top = new Vhello();
// Simulate 20 clock cycles
for (int n = 0; n < 20; n++) {
top->clk = 1;
wait_n_cycles(1000);
top->eval();
top->clk = 0;
wait_n_cycles(1000);
top->eval();
main_time++;
}
delete top;
exit(0);
}
void wait_n_cycles(int n) {
for (int i = 0; i < n; i++) {
main_time++;
}
}
To compile with SystemC and C++, we can use CMake with this CMakeLists.txt build definition:
cmake_minimum_required(VERSION 3.15)
project(counter_tb)
find_package(verilator HINTS $ENV{VERILATOR_ROOT})
add_executable(Vhello hello.cc)
verilate(Vhello SOURCES src/hello.v)
Now compile all with:
$ mkdir build
$ cd build
$ cmake -G Ninja ..
$ ninja all
You should be able to run the example and see:
> ./Vhello
tick
tick
...
tick
- src/hello.v:14: Verilog $finish
Besides the console output of clock ticks, you should have a waveform file which can be loaded with gitkwave:
$ gtkwave waves.vcd
You should be able to see 20 clock cycles and the resulting count value.
A counter varation
We can make the system under test a bit more interesting. First instead of having all structures defined in one module (which is unlikely in a real project), you can add a top module as follows:
../src/hello.v
module hello(input clk,
input resetn,
output [7:0] y);
initial begin
$dumpfile("waves.vcd");
$dumpvars();
end
// device under test
counter u0(clk, resetn, y);
always @ (posedge clk) begin
if (y == 8'h10)
$finish();
end
endmodule
You see already that we added a new signal to control the module reset. Also the top module can be a good place to define conditiions on when to finish the simulation.
module counter(input clk,
input resetn,
output [7:0] y);
reg [7:0] n;
always @(posedge clk) begin
if (resetn == 1'b0)
n <= 0;
else
n <= n + 1;
end
assign y = n;
endmodule
This reset could be driven from the testbench as follows
#include "verilated.h"
#include "Vhello.h"
void wait_n_cycles(int n);
// Current simulation time (64-bit unsigned)
vluint64_t main_time = 0;
// Called by $time in Verilog
double sc_time_stamp() {
return main_time; // Note does conversion to real, to match SystemC
}
int main(int argc, char ** argv) {
Verilated::commandArgs(argc, argv);
Verilated::traceEverOn(true);
Vhello* top = new Vhello();
top->resetn = 0;
while (!Verilated::gotFinish()) {
// release reset
if (main_time > 15) {
top->resetn = 1;
}
top->clk = 1;
wait_n_cycles(1000);
top->eval();
top->clk = 0;
wait_n_cycles(1000);
top->eval();
}
delete top;
exit(0);
}
void wait_n_cycles(int n) {
for (int i = 0; i < n; i++) {
main_time++;
}
}
Last updated
Was this helpful?