Verilator is a Verilog simulator that translates synthesizable Verilog to C++ or SystemC. That translated code can then be compiled with a C++/SystemC testbench, and run in lieu of a commercial simulator.

I’ve used Verilator in the past to compile a design into a DLL file. That library was called by Excel, and used to draw waveforms of what the design would do with various register settings. It turned out to be a great tool for generating accurate waveforms for both the user guide, and for the firmware team wishing to find the right settings.

In this example, we’ll simulate a simple 4-bit counter. This counter resets to 0, and increments on each clock edge out of reset.

module test (
  input wire clk,
  input wire rst_n,
  output reg [3:0] cnt
);

always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    cnt <= 4'h0;
  end
  else begin
    cnt <= cnt + 4'h1;
  end
end

endmodule

The Verilator testbench file looks very similar to a traditional Verilog testbench – instantiate the design under test, enable waveform dumping, initialize the variables, and run the simulation.

#include "Vtest.h"
#include "verilated.h"
#include "verilated_vcd_c.h"

int main(int argc, char **argv, char **env) {
  Verilated::commandArgs(argc, argv);

  // init top verilog instance
  Vtest* top = new Vtest;

  // init trace dump
  Verilated::traceEverOn(true);
  VerilatedVcdC* tfp = new VerilatedVcdC;
  top->trace(tfp, 99);
  tfp->open("counter.vcd");

  // initialize simulation inputs
  top->clk   = 1;
  top->rst_n = 0;

  // run simulation
  for (int i = 0; i < 20; i++) {
    top->rst_n = !(i < 2);
    // dump variables into VCD file and toggle clock
    for (int clk = 0; clk < 2; clk++) {
      tfp->dump(2*i+clk);
      top->clk = !top->clk;
      top->eval();
    }
    printf("count: $%0X\n", top->cnt);
    if (Verilated::gotFinish()) exit(0);
  }

  tfp->close();
  exit(0);
}

To compile into C++, first call verilator with the Verilog module and the C++ testbench. The -Wall option enables all warnings. The --cc option creates a C++ output, rather than SystemC. The --trace option enables waveforms. The --exe option enables creating an executable. The test.v input is the Verilog module, and the test_tb.cpp input is the C++ testbench. The output of verilator will be a set of files, including a Makefile to compile the whole kit and caboodle into an executable (obj_dir/Vtest.mk).

Vtest:
	verilator -Wall --cc --trace test.v --exe test_tb.cpp
	make -j -C obj_dir/ -f Vtest.mk Vtest

The executable can then be run like any traditional C++ program. Print statements will be displayed to the terminal, and a waveform will be created (counter.vcd).

% obj_dir/Vtest
count: $0
count: $0
count: $1
count: $2
count: $3
count: $4
count: $5
count: $6
count: $7
count: $8
count: $9
count: $A
count: $B
count: $C
count: $D
count: $E
count: $F
count: $0
count: $1
count: $2

This example can be extended to do many things. We could add some self-checking like a real testbench. We could capture the I/O and display them in a fancy GUI. We could even wire in keyboard inputs and create a GUI to create a game.

Verilator is an amazing tool with unlimited possibilities. I look forward to using it on my next project.

This example can be found on GitHub.