Home / Resources / Empower simulation with C++ and SystemC using Verilator

Introduction

SystemC is a set of C++ classes and macros which provide an event-driven simulation interface. These facilities enable a designer to simulate concurrent processes, each described using plain C++ syntax. SystemC deliberately mimics the hardware description languages VHDL and Verilog, but is more aptly described as a system-level modeling language.

Verilator is a free and open-source software tool which converts Verilog HDL to a cycle-accurate behavioral model in C++ or SystemC. The generated models are cycle-accurate and 2-state; as a consequence, the models typically offer higher performance than more widely used event-driven simulators.

Icarus Verilog is an implementation of the Verilog hardware description language Its compiler translates Verilog source code into executable programs for simulation, or other netlist formats for further processing. It supports the 1995, 2001 and 2005 versions of the standard, portions of SystemVerilog, and some extensions.

Here is shown how to simulate the same Verilog module with SystemC, Verilator and Icarus Verilog.

Verilog

First, we write our Verilog model. For instance, a Block-RAM with parameterized address and data size.

module bram
#(
  parameter ADDR_WIDTH = 10,  //!< address bus width
  parameter DATA_WIDTH = 32   //!< data bus width
) (
  input  clk,
  input  [ADDR_WIDTH-1:0] addr,
  input  we,
  input  [DATA_WIDTH-1:0] wrdata,
  output [DATA_WIDTH-1:0] rddata
);
  localparam N = 2**ADDR_WIDTH;
  reg [DATA_WIDTH-1:0] memory_array[N-1:0];

  reg [DATA_WIDTH-1:0] r_rddata;
  always @( posedge(clk) ) begin
    if ( we == 1'b1 ) begin
      memory_array[addr] <= wrdata;
    end
    else begin
      r_rddata <= memory_array[addr];
    end
  end
  assign rddata = r_rddata;
endmodule

And connexions to switches, buttons and LEDs to test its behaviour on a FPGA.

module synthesis(
  input  clk,
  input  btn0,
  input  [3:0] sw,
  output [3:0] led
);
  // always reading and writting at the same address:
  wire [10:0] addr;
  assign addr[10:4] = 7'b1111111;
  assign addr[ 3:0] = 4'b0101;

  // write enable is a button:
  wire we;
  assign we = btn0;

  // write what is selected by the switches:
  wire [31:0] wrdata;
  assign wrdata[31:4] = 28'hFFFFFFF;
  assign wrdata[ 3:0] = sw;

  // show on the leds the 4 LSB of what is read:
  wire [31:0] rddata;
  assign led = rddata[3:0];

  wire [31:3] unused;
  assign unused = rddata[31:3];

  bram #(
    .ADDR_WIDTH( 11 ),
    .DATA_WIDTH( 32 )
  ) i_bram (
    .clk(    clk    ),
    .addr(   addr   ),
    .we(     we     ),
    .wrdata( wrdata ),
    .rddata( rddata )
  );
endmodule

C++

Then, we write our testbench, either in SystemC, C++ or Verilog (depending of the simulation solution we chose). Here is an example with C++ that relies on the Verilated library.

int main(
  int argc,
  char** argv
) {
  uint64_t main_time = 0;     // current simulation time

  // instantiation of the model:
  Vsynthesis*    theDut = new Vsynthesis;

  // to trace in a vcd:
  Verilated::traceEverOn(true);
  VerilatedVcdC* theVcd = new VerilatedVcdC;
  theDut->trace(theVcd, 99);  // trace 99 levels of hierarchy
  theVcd->open("output.vcd");

  // set some inputs:
  theDut->btn0 = 0;
  theDut->sw   = 0;
  while ( !Verilated::gotFinish() ) { // triggered by a $finish in the verilog
    // toggle clock
    if ( (main_time % 10) == 1 ) { theDut->clk = 1; }
    if ( (main_time % 10) == 6 ) { theDut->clk = 0; }

    // toggle inputs:
    if ( main_time > 45 && main_time < 125 ) { theDut->btn0 = 1; }
                                        else { theDut->btn0 = 0; }
    if ( main_time < 85 ) { theDut->sw = 0x0; }
                     else { theDut->sw = 0xD; }

    theDut->eval();           // evaluate model
    theVcd->dump(main_time);  // dump in the vcd file
    main_time++;              // time passes...

    // finish:
    if ( main_time > 265 ) {
      break;
    }
  }

  theVcd->close(); // done tracing
  theDut->final(); // done simulating
  delete theDut;
  return 0;
}

The C++ testbench can also rely on SystemC libraries.

Makefile

Now, we must call Verilator to convert our Verilog to C++ and then compile the results along with the testbench.

Here is a Makefile that provides several recipes (make sure to indent with tabulations).

We also can use the --sc option to convert our Verilog to SystemC and load a different testbench.

HDL  = bram.v synthesis.v
TOP  = synthesis
TEST = testbench.cc     # for Verilator
HW_TEST = testbench.v   # for Icarus

VERILATOR_MK  = $(addprefix obj_dir/V, $(addsuffix .mk, ${TOP}))
VERILATOR_BIN = $(addprefix obj_dir/V, ${TOP})

all: ${VERILATOR_BIN}
    ./${VERILATOR_BIN}

${VERILATOR_BIN} : ${VERILATOR_MK} ${TEST}
    make --directory=$(dir ${VERILATOR_MK}) --file=$(notdir ${VERILATOR_MK})

${VERILATOR_MK} : ${HDL} ${TEST}
    verilator -Wall -O0 --cc ${HDL} --top-module ${TOP} --exe ${TEST} --trace

icarus : ${HDL} ${HW_TEST}
    iverilog -o run.vvp $^
    vvp run.vvp

clean :
    $(RM) -r obj_dir/
    $(RM) run.sc
    $(RM) run.vvp
    $(RM) output.vcd

Associated resources

Here is an archive with Verilog models, C++/SystemC testbenches and a Makefile.



no cookie, no javascript, no external resource, KISS!