Global training solutions for engineers creating the world's electronics

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:
  1. Applications that do not use SystemVerilog packed types.
  2. Applications that do not mix SystemVerilog packed and unpacked types in the same data type.
  3. 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*

SystemVerilog and C types

 

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:
  1. 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.
  2. 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
  1. The imported task must be declared context.
  2. The C function must return an int, with 1 indicating a disable.
  3. The C function must check for a disable every time it calls an exported DPI task.

Prev