DPI-C

Call C functions from SystemVerilog and SystemVerilog functions from C with RyuSim's DPI-C support.

The Direct Programming Interface (IEEE 1800-2023 clause 35 and Annex H) connects SystemVerilog to C. An import "DPI-C" declaration lets SystemVerilog call a C function. An export "DPI-C" declaration lets C call a SystemVerilog function. RyuSim compiles your C and C++ sources with Clang and links them into the simulation executable.

This page describes RyuSim 2.1.16. Each supported row below was checked by compiling and running a small design. Several parts of Annex H do not work yet. The limits section lists them, and --unsupported does not catch them.

Import a C function

Declare the C function in SystemVerilog with import "DPI-C", then call it like any other function.

module dpi_import; import "DPI-C" function int gcd(input int a, input int b); initial begin $display("gcd(84, 36) = %0d", gcd(84, 36)); $finish; end endmodule

Write the C side in its own directory. The C and C++ sources section explains why.

int gcd(int a, int b) { while (b != 0) { int t = a % b; a = b; b = t; } return a; }

Pass the C file on the compile line next to the SystemVerilog file, then run the simulation:

ryusim -q compile dpi_import.sv c/gcd.c --top dpi_import ./obj_dir/build/dpi_import_sim
RyuSim - Simulation of dpi_import RyuSim: effective root seed = 142078604353849677 (source: randomly generated) gcd(84, 36) = 12 Simulation complete. Time: 0

Export a SystemVerilog function

export "DPI-C" gives a SystemVerilog function a C symbol. C code declares that symbol and calls it. In this example SystemVerilog calls the C function sum_squares, and it calls square back in SystemVerilog once per term.

module dpi_export; import "DPI-C" context function int sum_squares(input int n); export "DPI-C" function square; function int square(input int x); return x * x; endfunction initial begin $display("sum_squares(4) = %0d", sum_squares(4)); $finish; end endmodule
#include "svdpi.h" int square(int x); /* the SystemVerilog function exported above */ int sum_squares(int n) { int total = 0; for (int i = 1; i <= n; i++) total += square(i); return total; }

The import is declared context because the C code calls an exported function. Annex H requires that. -o puts this build in its own directory:

ryusim -q compile dpi_export.sv c/sum_squares.c --top dpi_export -o export_dir ./export_dir/build/dpi_export_sim
RyuSim - Simulation of dpi_export RyuSim: effective root seed = 12126635280260929235 (source: randomly generated) sum_squares(4) = 30 Simulation complete. Time: 0

An exported function works from C when it meets all of these conditions:

RyuSim writes no C entry point for an export that breaks one of these rules. The link then fails with undefined reference to '<name>'. An exported task works when it does not wait. A task that contains a delay or an event control cannot be exported, and it fails the same way.

C and C++ sources

ryusim compile treats a source with one of these extensions as a DPI source: .c, .cc, .cpp, .cxx. Put them on the command line or in a -f file list, next to the SystemVerilog sources. Any other file goes to the SystemVerilog parser.

RyuSim also looks for DPI sources you did not name. When the design has an import "DPI-C", every .c file in a directory that holds a SystemVerilog source is compiled and linked. It reports these files only with -v. This search is kept for older projects and is deprecated. Name every DPI source on the command line and keep C files out of your SystemVerilog directories.

In 2.1.16 the two mechanisms can pick up the same file twice. Take a SystemVerilog file given by a bare name (top.sv) and a C file in the same directory. Name both on the command line, and the link fails with multiple definition of '<function>'. That is why the examples above keep the C files in c/.

Prebuilt libraries

Not supported. ryusim compile does not link object files (.o), static libraries (.a) or shared libraries (.so), and it has no -L or -l option. A .o, .a or .so on the command line is parsed as SystemVerilog and rejected. Pass the library's C or C++ sources instead.

svdpi.h

RyuSim ships the IEEE 1800-2023 Annex I svdpi.h without changes. The installer puts it in <prefix>/include/: /opt/ryusim/include/svdpi.h for a root install, or ~/.ryusim/include/svdpi.h otherwise. The directory that holds this header is on the include path of every DPI source, so #include "svdpi.h" needs no -I.

Annex H places the scalar encodings in this header: sv_0, sv_1, sv_z and sv_x. It also declares the canonical vector types svBitVecVal and svLogicVecVal, the open-array handle, and the scope functions.

Type mapping

The C column is the type to use in your C code. Directions are those that were tested. An output or inout argument is passed as a pointer to the C type.

SystemVerilogCStatus
bytechar Supported input, output, result
shortintshort Supported input, result
intint Supported input, output, inout, result
int unsignedunsigned int Supported input, result
longintlong long Supported input, output, result
chandlevoid* Supported input, result
stringconst char* Partial input and result work. An output string fails to compile.
bitsvBit Supported input, output, result
logicsvLogic Partial input and result carry sv_0, sv_1, sv_z and sv_x. An output that C sets to sv_x reads back as 1.
bit [N-1:0], N ≤ 64char, short, int or long long, by width Partial passed by value as the smallest of those types that holds N bits. Annex H passes these as const svBitVecVal*. C code written that way reads the value as a pointer and crashes.
logic [N-1:0], N ≤ 64as for bit Partial passed the same way as bit. X and Z bits reach C as 0.
bit [N-1:0], N > 64const svBitVecVal* (input), svBitVecVal* (output) Supported input, output
logic [N-1:0], N > 64const svLogicVecVal* (input), svLogicVecVal* (output) Supported input, output, with X and Z
open array T name[]const svOpenArrayHandle Supported input, output (see open arrays)
realdouble Not yet In an import, the value crosses as an 8-bit integer and the result is wrong. Do not use it.
shortrealfloat Not yet same as real
fixed-size unpacked array, structpointer to the C layout Not yet the generated code fails to compile

The frontend enforces the Annex H limit on function results. A result type outside it, such as bit [15:0], is a compile error:

top.sv:3:38: error: 'bit[15:0]' is not a valid return type for a DPI subroutine

Open arrays

An argument declared with an unsized dimension, such as input int a[], is an open array. C receives an svOpenArrayHandle and reads it with the svdpi.h functions:

#include "svdpi.h" int sum_open(const svOpenArrayHandle h) { /* SV: input int a[] */ int s = 0; for (int i = svLow(h, 1); i <= svHigh(h, 1); i++) s += *(int *)svGetArrElemPtr1(h, i); return s; }

RyuSim implements every non-deprecated function in its svdpi.h except three:

Not implemented: svGetTime, svGetTimeUnit and svGetTimePrecision, and every function in the deprecated part of the header (the …Vec32, svGetSelect…, svGetPartSelect…, svGetBits, svGet32Bits, svGet64Bits and svSizeOf…PackedArr functions). Calling one fails at link time with undefined reference to 'svGetTime' (or the function's name).

Import forms

A missing C definition

RyuSim adds a default definition for every import, so the link never fails for lack of one. If no DPI source defines the function, calls to it return 0 (an empty string for a string result, and nothing for void). The simulation exits 0 and prints no warning. Leaving c/gcd.c out of the first example gives:

ryusim -q compile dpi_import.sv --top dpi_import -o nodef ./nodef/build/dpi_import_sim
RyuSim - Simulation of dpi_import RyuSim: effective root seed = 11211161386169724967 (source: randomly generated) gcd(84, 36) = 0 Simulation complete. Time: 0

Check the compile line when an imported function returns 0. ryusim -v compile lists each DPI source it compiles.

Clang

RyuSim compiles the DPI sources with the same Clang it uses for the generated simulation code. It searches PATH for clang++-19, then clang++-18, then clang++, and takes the first it finds. .c files need the C compiler with the same suffix: clang-18 beside clang++-18, or clang beside clang++.

With no clang++ on PATH, the compile stops with:

Warning: build.ninja not generated (clang++ not found on PATH (clang++-19, clang++-18, clang++) — RyuSim generated code requires Clang); the CMake driver will build this design Warning: build driver: cmake (no build.ninja in obj_dir (the ninja build file was not generated)) … CMake Error at CMakeLists.txt:5 (message): clang++ not found; RyuSim generated code requires Clang -- Configuring incomplete, errors occurred! Error: CMake configuration failed

With clang++-18 present but no clang-18, a design with a .c source stops with the output below. The first line names the full path of the clang++-18 it found.

Warning: build.ninja not generated (clang not found on PATH beside <path>/clang++-18 — RyuSim generated code requires Clang for the DPI C sources); the CMake driver will build this design … CMake Error at CMakeLists.txt:15 (message): clang not found; RyuSim generated code requires Clang

Install the matching clang package, or rename the C files to .cpp with extern "C" declarations so they build with clang++. Getting Started lists the Clang packages for each distribution.

Limits in 2.1.16

Each item below compiles without a diagnostic from the --unsupported gate. Some then fail in Clang or the linker. Others run and give a wrong result.

The clause 35 row of the compliance matrix gives the overall status.