Skip to main content

DPI-C

info

The source code for this example can be found in examples/dpi-c

Description

This example shows how to do simulation using DPI with SoCMake. In this case, we will have a C++ function called in a System-Verilog testbench, using DPI-C.

Example

Directory structure

Lets take a look at the directory structure of the example first.

.
├── hello
│   ├── CMakeLists.txt
│   └── hello.cpp
├── CMakeLists.txt
└── tb.sv

We have a directory hello/ that contains the hello c++ function, it as its own CMakeLists.txt to make it easier to reuse in a larger project.

tip

For a design this simple it is not really necessary to have a separate CMakeLists.txt, but it is a good practice anyways.

hello/hello.cpp

Simple function that takes an integer and return the integer + 10.

hello/hello.cpp
#include <cstdint>

extern "C" uint32_t hello(uint32_t data);
uint32_t hello(uint32_t data)
{
return data + 10;
}

hello/CMakeLists.txt

The following CMakeLists.txt will create a library called hello_dpi and use the target_link_libraries function to declare it as a library with DPI.

hello/CMakeLists.txt
cmake_minimum_required(VERSION 3.27)
project(hello_dpi CXX)

set(CMAKE_CXX_STANDARD 11)

add_library(hello_dpi SHARED
./hello.cpp)

target_link_libraries(hello_dpi PRIVATE
SoCMake::DPI-C
)

tb.sv

This is a standard System-Verilator testbench. The DPI-C is imported and called.

tb.sv
module tb;

import "DPI-C" function int unsigned hello(input int unsigned data);

initial begin
$display("From DPI-C 5 + 10 is: %d", hello(5));
$finish();
end
endmodule

CMakeLists.txt

And finally we need a top CMakeLists.txt that will assemble the full project and create simulation targets.

CMakeLists.txt
include("../../SoCMakeConfig.cmake")

option_enum(SIMULATOR "Which simulator to use" "questa;vivado_sim;xcelium;vcs;verilator;all" "questa")

if(SIMULATOR STREQUAL "questa")
questasim_configure_cxx(LIBRARIES DPI-C)
elseif(SIMULATOR STREQUAL "xcelium")
xcelium_configure_cxx(LIBRARIES DPI-C)
elseif(SIMULATOR STREQUAL "vcs")
vcs_configure_cxx(LIBRARIES DPI-C)
elseif(SIMULATOR STREQUAL "vivado_sim")
vivado_sim_configure_cxx(LIBRARIES DPI-C)
elseif(SIMULATOR MATCHES "verilator")
verilator_configure_cxx(LIBRARIES DPI-C)
endif()

cmake_minimum_required(VERSION 3.27)
project(dpi_example NONE)

add_ip(tb
DESCRIPTION "Simple verilog testbench"
)

ip_sources(${IP} SYSTEMVERILOG
${PROJECT_SOURCE_DIR}/tb.sv
)

add_subdirectory(hello)

ip_link(${IP} hello_dpi)

if(SIMULATOR STREQUAL "questa")
questasim(${IP})
endif()

if(SIMULATOR STREQUAL "xcelium")
xcelium(${IP})
endif()

if(SIMULATOR STREQUAL "vivado_sim")
vivado_sim(${IP})
endif()

if(SIMULATOR STREQUAL "vcs")
vcs(${IP})
endif()

if(SIMULATOR STREQUAL "verilator")
enable_language(CXX)
verilator(${IP} MAIN VERILATOR_ARGS --timing)
endif()

help()

To give the right flags to use the DPI-C with the simulator used, the ${simulator}_configure_cxx() function can be used, in our case, we will use verilator_configure_cxx(LIBRAIRES DPI-C).

We can add the adder IP as a subdirectory with add_subdirectory() CMake function.

Then, we can just use the SoCMake function corresponding to the simulator we want to use, in our case verilator(), with the MAIN argument, which will take care of generating the different needed files to run the simulation.

Running the simulation

Simulation can be run the same way as always:

mkdir build
cd build
cmake ../ -DSIMULATOR=questa # Configure project
make run_tb_verilator -j$(nproc) # Build and run testbench