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.
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 "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:
input.byte, shortint,
int, longint, real,
shortreal, string, or a scalar
bit or logic.void, byte, shortint,
int, longint, real, or a scalar
bit or logic. A scalar result that is X or Z
reaches C as 0.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.
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.
.c files compile with clang. The others
compile as C++ with clang++, so declare the DPI functions in
them extern "C".--dpi-define NAME or --dpi-define NAME=VALUE
defines a macro for the DPI sources only. -D defines a
macro for the SystemVerilog sources and does not reach the C files.-I adds a directory to the SystemVerilog include path
only. A DPI source finds headers in its own directory and in the
directories RyuSim adds for svdpi.h.
There is no option that adds an include directory for DPI sources.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/.
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.hRyuSim 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.
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.
| SystemVerilog | C | Status |
|---|---|---|
byte | char |
Supported input, output, result |
shortint | short |
Supported input, result |
int | int |
Supported input, output, inout, result |
int unsigned | unsigned int |
Supported input, result |
longint | long long |
Supported input, output, result |
chandle | void* |
Supported input, result |
string | const char* |
Partial input and result work. An output string fails to compile. |
bit | svBit |
Supported input, output, result |
logic | svLogic |
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 ≤ 64 | char, 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 ≤ 64 | as for bit |
Partial passed the same way
as bit. X and Z bits reach C as 0. |
bit [N-1:0], N > 64 | const svBitVecVal* (input), svBitVecVal* (output) |
Supported input, output |
logic [N-1:0], N > 64 | const svLogicVecVal* (input), svLogicVecVal* (output) |
Supported input, output, with X and Z |
open array T name[] | const svOpenArrayHandle |
Supported input, output (see open arrays) |
real | double |
Not yet In an import, the value crosses as an 8-bit integer and the result is wrong. Do not use it. |
shortreal | float |
Not yet same as real |
fixed-size unpacked array, struct | pointer 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
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:
svLeft, svRight,
svLow, svHigh, svIncrement,
svSize, svDimensions,
svSizeOfArray.svGetArrayPtr,
svGetArrElemPtr and svGetArrElemPtr1 to
3, and the svGet/svPut
BitArrElem, LogicArrElem and
…VecVal families for one to three dimensions.svGetBitselBit,
svGetBitselLogic, svGetPartselBit,
svGetPartselLogic and their svPut forms.svGetScope, svSetScope,
svGetNameFromScope, svGetScopeFromName,
svPutUserData, svGetUserData,
svGetCallerInfo, svIsDisabledState,
svAckDisabledState, svDpiVersion.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 "DPI-C" function
… and import "DPI-C" task … both work. The C side of
an imported task returns int.pure. Accepted. RyuSim does not fold
calls to a DPI import at compile time, pure or not.context. Accepted, and it lets the C
code call exported functions. The scope functions do not work yet in
2.1.16. svGetScope() returns NULL inside a
context import. svGetScopeFromName() returns
NULL for an instance path. A function exported from a
module with more than one instance always runs in the last instance.import "DPI-C" c_name = function
… sv_name(…) does not work yet. The call goes to
sv_name, not c_name, and the
missing-definition rule below applies.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.
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.
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.
real and shortreal
in imports; packed logic vectors of 64 bits or fewer lose X
and Z; an output logic set to sv_x reads as 1;
a C name alias calls the default definition; an import with no C
definition returns 0.NULL, and an export in a module with several instances
runs in the last one.svBitVecVal* or svLogicVecVal*, as Annex H
specifies.struct
arguments, output string, exported tasks that wait,
exports with output or inout arguments,
svGetTime and the deprecated svdpi.h
functions, prebuilt libraries.The clause 35 row of the compliance matrix gives the overall status.