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 can run
make
to run with the C++ testbench - We can run
make icarus
if our testbench is written in Verilog
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.