SystemVerilog DPI Tutorial
The SystemVerilog Direct
Programming Interface (DPI) is basically an interface between
SystemVerilog and a foreign programming language, in particular the C
language. It allows the designer to easily call C functions from
SystemVerilog and to export SystemVerilog functions, so that they can
be called from C.
The DPI has great advantages:
it allows the user to reuse existing C code and also does not require
the knowledge of Verilog Programming Language Interface (PLI) or
Verilog Procedural Interface (VPI) interfaces. It also provides an
alternative (easier) way of calling some, but not all, PLI or VPI
functions.
Functions implemented in C can
be called from SystemVerilog using import "DPI" declarations. We will
refer to these functions as imported tasks and functions. All imported
tasks and functions must be declared. Functions and tasks implemented
in SystemVerilog and specified in export "DPI" declarations can be
called from C. We will refer to these tasks and functions as exported
tasks and functions.
An Example
Here an example is presented. A
module called Bus contains two functions: write, which is a
SystemVerilog function that is also exported to C, and a function
called slave_write which is imported from C. Both functions return void.
SystemVerilog:
module Bus(input In1, output Out1);
import "DPI" function void slave_write(input int address,
input int data);
export "DPI" function write; // Note – not a function prototype
// This SystemVerilog function could be called from C
function void write(int address, int data);
// Call C function
slave_write(address, data); // Arguments passed by copy
endfunction
...
endmodule
C:
#include "svdpi.h"
extern void write(int, int); // Imported from SystemVerilog
void slave_write(const int I1, const int I2)
{
buff[I1] = I2;
...
}
Note the following points:
- The C function slave_write
is called inside the SystemVerilog function, the arguments being passed
by value (we will see more detail about this later in the tutorial).
- The function imported from C has two inputs, which in C are declared as const. This is because they shouldn’t be changed in the C function.
Both DPI imported and exported
functions can be declared in any place where normal SystemVerilog
functions can be (e.g. package, module, program, interface,
constructs). Also all functions used in DPI complete their execution
instantly (zero simulation time), just as normal SystemVerilog
functions.
Examples of Importing C Functions
Here are some more examples of imported functions:
// User-defined function import "DPI" function void AFunc(); // Standard C function import "DPI" function chandle malloc(int size); // Standard C function import "DPI" function void free(chandle ptr); // Open array of 8-bit import "DPI" function void OpenF(logic [7:0] Arg[]);
chandle is a special
SystemVerilog type that is used for passing C pointers as arguments to
imported DPI functions.
Including Foreign Language Code
All SystemVerilog applications
support integration of foreign language code in object code form.
Compiled object code can be specified by one of the following two
methods:
- by an entry in a bootstrap file; Its location is specified with one instance of the switch -sv_liblist pathname.
- by specifying the file with one instance of the switch -sv_lib pathname_without_extension (i.e. the filename without the platform specific extension).
Here is an example of a
bootstrap file:
#!SV_LIBRARIES myclibs/lib1 proj2/clibs/lib2
The first line must contain the
string: #!SV_LIBRARIES. Then the following lines hold one and only one
library location each. Comment lines can be inserted. A comment line
start with a # and ends with a newline.
Here is an example of a switch list:
-sv_lib myclibs/lib1 -sv_lib proj2/clibs/lib2
The two files above are
equivalent, if the pathname root has been set by the switch -sv_root to
/home/user and the following shared object libraries are included:
/home/user/myclibs/lib1.so /home/user/proj2/clibs/lib2.so
Binary and Source Compatibility
Binary compatibility means an
application compiled for a given platform will work with every
SystemVerilog simulator on that platform. Source-level compatibility
means an application needs to be re-compiled for each SystemVerilog
simulator and implementation-specific definitions will be required for
the compilation.
Depending on the data types
used for imported or exported functions, the C code can be binary-level
or source-level compatible. Binary compatible are:
- Applications that do not use SystemVerilog packed types.
- Applications that do not mix SystemVerilog packed and unpacked types in the same data type.
- Open arrays (see Argument Passing below) with both packed and unpacked parts.
Return Value and Argument Data Types
Result types of both imported
and exported functions are restricted to small values. Small
values include:
- void, byte, shortint, int, longint, real, shortreal, chandle, and string
- packed bit arrays up to 32 bits and all types that are eventually equivalent to packed bit arrays up to 32 bits.
- scalar values of type bit and logic
All SystemVerilog data types
are allowed for formal arguments of imported functions.
Imported functions can have
input, output and inout arguments. The formal input arguments cannot be
modified. In the C code, they must have a const qualifier. Also, the
initial values of output arguments are undetermined and
implementation-dependent as far as the C function is concerned.
Argument passing
There is no difference in
argument passing between calls from SystemVerilog to C and calls from C
to SystemVerilog, apart from the fact that functions exported from
SystemVerilog cannot have open arrays as arguments. Formal arguments in
SystemVerilog can be specified as open arrays only in import
declarations. This facilitates writing generalised C code that can
handle SystemVerilog arrays of different sizes.
An open array is an array with
the packed, unpacked or both dimensions left unspecified. This is
indicated using the symbol [ ] for the open array dimensions.
The imported and exported
functions’ arguments can be passed in several modes, with
certain limitations for each mode:
- Argument passing by value: here the following restrictions apply:
- Only small values of formal input arguments are passed by value.
- Function results restricted to small values are directly passed by value.
- The user needs to provide the C-type equivalent to the SystemVerilog type of a formal argument (see below).
- Argument passing by reference (i.e. pointer or handle):
- Formal arguments (input, output, inout), except for open arrays and small values of input arguments, are passed by direct reference (i.e. C pointer) and are directly accessible in C code.
- Formal arguments declared in SystemVerilog as open arrays are always passed by a handle (type svOpenArrayHandle) and are accessible via library functions. This is independent of the direction of the SystemVerilog formal argument. Arguments passed by handle must have a const qualifier (the user cannot modify the contents of a handle).
- If an argument of type T is passed by reference, the formal argument will be of type T*. Packed arrays can also be passed using generic pointers void* (typedef-ed accordingly to svBitPackedArrRef or svLogicPackedArrRef).
C vs SystemVerilog Data Types
A pair of matching type
definitions is required to pass a value through DPI: the SystemVerilog
definition and the C definition.
SystemVerilog types which are
directly compatible with C types are presented in the following table:
| SystemVerilog Type |
C Type |
|---|---|
| byte | char |
| int | int |
| longint | long long |
| shortint | short int |
| real | double |
| shortreal | float |
| chandle | void* |
| string | char* |
There are
SystemVerilog-specific types, including packed types (arrays,
structures, unions), 2-state or 4-state, which have no natural
correspondence in C. For these the designers can choose the layout and
representation that best suits their simulation performance. The
representation of data types such as packed bit and logic arrays
are implementation-dependent, therefore applications using them are not
binary-compatible (i.e. an application compiled for a given platform
will not work with every SystemVerilog simulator on that platform).
Packed arrays are treated as
one-dimensional, while the unpacked part
of an array can have an arbitrary number of dimensions. Normalised
ranges are used for accessing all arguments except open arrays.
(Normalized ranges mean [n-1:0] indexing for the packed part (packed
arrays are restricted to one dimension) and [0:n-1] indexing for a
dimension in the unpacked part of an array.) The ranges for a formal
argument specified as an open array, are those of the actual argument
for a particular call.
If a packed part of an array
has more than one dimension, it is
transformed to a one-dimensional one, as well as normalised (e.g.
packed array of range [L:R] is normalized as [abs(L-R):0], where index
min(L,R) becomes the index 0 and index max(L,R) becomes the index
abs(L-R)). For example:
logic [2:3][1:3][2:0] b [1:8] [63:0]
becomes
logic [17:0] b[0:7] [0:63]
after normalisation.
Enumerated names are not
available on the C side of the DPI. enum types
are represented as the types associated with them.
The C include files
The C-layer of the DPI provides
two include files:
- svdpi.h is implementation-independent and defines the canonical representation, all basic types, and all interface functions. Applications using only this include file are binary-compatible with all SystemVerilog simulators.
- svdpi_src.h defines only the actual representation of 2-state and 4-state SystemVerilog packed arrays and hence, its contents are implementation-dependent. Applications that need to include this file are not binary-level compatible, they are source-level compatible
Argument Passing Example 1
This example includes a struct,
a function imported from C and a
SystemVerilog function exported to C. The struct uses three different
types: byte, int (which are small values) and a packed 2-dimensional
array. The SystemVerilog struct has to be re-defined in C. Byte and int
are directly compatible with C, while the packed array is redefined
using the macro SV_BIT_PACKED_ARRAY(width, name).
SV_LOGIC_PACKED_ARRAY(width,name)
and SV_BIT_PACKED_ARRAY(width,name)
are C macros allowing variables to be declared to represent
SystemVerilog packed arrays of type bit or logic respectively They are
implementation specific, therefore source-compatible, and require
"svdpi_src.h" to be included.
The SystemVerilog function
exported to C has an input of a type int (a
small value), and a packed array as an output. The packed array will be
passed as a pointer to void. (SvLogicPackedArrRef is a typdef for void
*). The SystemVerilog function is called inside the C function, the
first argument being passed by value, and the second by reference.
SystemVerilog:
typedef struct {
byte A;
bit [4:1][0:7] B;
int C;
} ABC;
// Imported from C
import "DPI" function void C_Func(input ABC S);
// Exported to C
export "DPI" function SV_Func;
function void SV_Func(input int In,
output logic[15:0] Out);
...
endfunction
C:
#include "svdpi.h"
#include "svdpi_src.h"
typedef struct {
char A;
SV_BIT_PACKED_ARRAY(4*8, B); // Implementation specific
int C;
} ABC;
SV_LOGIC_PACKED_ARRAY(64, Arr); // Implementation specific
// Imported from SystemVerilog
extern void SV_Func(const int, svLogicPackedArrRef);
void C_Func(const ABC *S)
// A
struct is passed by reference
{
...
// First argument passed by value, second by reference
SV_Func(2, (svLogicPackedArrRef)&Arr);
}
Argument Passing Example 2
This is an example with a
function imported from C having a
3-dimensional array as argument. The argument is passed by a
svOpenArrayHandle handle and has a const qualifier. The function
described in C uses several access functions:
void *svGetArrElemPtr3(const svOpenArrayHandle, int indx1,int indx2, int indx3)
returns a pointer to the actual
representation of 3-dimensional array
of any type.
int svLow(const svOpenArrayHandle h, int d)
and
int svHigh(const svOpenArrayHandle h, int d)
are array querying functions,
where h= handle to open array and
d=dimension. If d = 0, then the query refers to the packed part (which
is one-dimensional) of an array, and d> 0 refers to the unpacked
part of an array.
SystemVerilog:
// 3-dimensional unsized unpacked array
import "DPI" function void MyFunc(input int i [][][]);
int Arr_8x4x16 [8:0][2:5][17:2];
int Arr_4x16x8 [3:0] [15:0][-1:-8];
MyFunc (Arr_8x4x16);
MyFunc (Arr_4x16x8);
C:
#include "svdpi.h"
void MyFunc(const svOpenArrayHandle h)
{
int Value;
int i, j, k;
int lou1 = svLow(h, 1);
int hiu1 = svHigh(h, 1);
int lou2 = svLow(h, 2);
int hiu2 = svHigh(h, 2);
int lou3 = svLow(h, 3);
int hiu3 = svHigh(h, 3);
for (i = lou1; i <= hiu1; i++) {
for (j = lou2; j <= hiu2; j++) {
for (k = lou3; k<= hiu3; k++) {
Value = *(int *)svGetArrElemPtr3(h, i, j, k);
...
}
...
}
}
}
C Global Name Space
By default, the C linkage name
of an imported or exported SystemVerilog
function is the same as the SystemVerilog name. For example the
following export declaration,
export "DPI" void function func;
Corresponds to a C function
called func.
It is possible for there to be
another SystemVerilog function with the
same name, func. For example, another func could be declared in a
separate module. To cater for this, and to provide a means to have
different SystemVerilog and C function names for a DPI function, an
optional C identifier can be defined in import "DPI" or export "DPI"
declarations:
export "DPI" Cfunc = function func;
The function is called func in
SystemVerilog and Cfunc in C.
Pure and Context Functions
It is possible to declare an
imported function as pure to allow for
more optimisations. This may result in improved simulation performance.
There are some restrictions related to this, though. A function can be
specified as pure only if:
- Its result depends only on the values of its inputs and has no side-effects.
- It is a non-void function with no output or inout arguments.
- It does not perform any file operations, read/write anything (including I/O, objects from the OS, from the program or other processes, etc.), access any persistent data (like global or static variables).
Here is an example of a pure
function from the standard C math library:
import "DPI" pure function real sin(real);
An imported function that is
intended to call exported functions or to
access SystemVerilog data objects other then its actual arguments (e.g.
via VPI or PLI calls) must be specified as context. If it is
not, it can lead to unpredictable behaviour, even crash. Calling
context functions will decrease simulation performance. All export
functions are always context functions.
If VPI or PLI functions are
called from within an imported function,
the imported function must be flagged with the context qualifier. Not
all VPI or PLI functions can be called in DPI context imported
functions, e.g. activities associated with system tasks.
Importing and Exporting Tasks
C functions would usually be
imported as SystemVerilog functions.
However they can also be imported as SystemVerilog tasks:
import "DPI" task MyCTask(input int i, output int j);
Similarly, SystemVerilog tasks
may be exported:
export "DPI" task MySVTask;
A SystemVerilog task does not
have a return value and is called as a
statement – in an initial or always block, for example. An
important feature of tasks is that, unlike functions, they may consume
simulation time, if they include one or more .timing controls. Now if
an imported DPI task calls an exported DPI task that consumes
simulation time, the imported task will consume time.
Only context imported tasks may
call exported tasks:
import "DPI" context task MayDelay();
In SystemVerilog, tasks may be
disabled, using a disable statement.
When this happens, the task exits with its outputs undefined. In order
to cater for an imported task (or the exported task it calls) being
disabled, the C code must handle this possibility.
Consider an imported DPI task
that calls an exported DPI task that does
delay:
task Delay (output int t);
#10 t = $stime;
endtask
export "DPI" task Delay;
import "DPI" context task DoesDelay(output int t);
The C code will look like this:
extern int Delay(int *t);
int DoesDelay (int *t)
{
...
Delay();
...
}
Notice that the C functions DoesDelay
and Delay return an int, even
though they correspond to SystemVerilog tasks. The return value of
Delay will be 0, unless the SystemVerilog task DoesDelay is disabled,
in which case the C function Delay returns 1. This must be checked in
DoesDelay, which must acknowledge that it has seen the disable and also
return 1:
int DoesDelay (int *t)
{
...
if ( Delay(t) ) { // Was the task DoesDelay disabled?
svAckDisabledState();
return 1;
}
...
return 0;
}
Note that if Delay is disabled whilst DoesDelay is executing, Delay
will return 0.
In summary, if a C function that implements an imported DPI task
itself
calls an exported DPI task, then
- The imported task must be declared context.
- The C function must return an int, with 1 indicating a disable.
- The C function must check for a disable every time it calls an exported DPI task.


