SystemVerilog DPI TutorialThe 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 ExampleHere 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); C: #include "svdpi.h" Note the following points:
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 FunctionsHere 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 CodeAll 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:
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 CompatibilityBinary 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:
Return Value and Argument Data TypesResult types of both imported and exported functions are restricted to small values. Small values include:
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 passingThere 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:
C vs SystemVerilog Data TypesA 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:
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 filesThe C-layer of the DPI provides two include files:
Argument Passing Example 1This 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 { C: #include "svdpi.h" Argument Passing Example 2This 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 C: #include "svdpi.h" C Global Name SpaceBy 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 FunctionsIt 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:
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 TasksC 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); The C code will look like this: extern int Delay(int *t); 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) 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
|
|