How to Call C-functions from SystemVerilog Using DPI-C

Recently I played a bit with SystemVerilog and DPI-C and I thought of sharing the experience with you.
This post shows data types mappings from SystemVerilog to C and how to call C-functions from SV. I also provide a simple SV/C application to facilitate understanding of data types mappings.

Data Mappings

When SystemVerilog interacts with the C environment, a data exchange occurs. Data needs to be interpreted in exactly the same way on both ends, otherwise the communication will fail. Data exchange between SystemVerilog and C is usually done using the DPI-C interface which standardizes the type correspondence and a basic API (see also svdpi.h in the simulator’s installation path).

Most of SystemVerilog data types have a straightforward correspondent in the C language, while others (e.g. 4-value types, arrays) require the DPI-C-defined types and API. Bellow, you can see a full set of data type correspondents in a table format:

SystemVerilog to C data type mapping correspondence

SV type to C equivalent SV type to DPI-defined equivalent
SystemVerilog C SystemVerilog C
byte char bit svBit
shortint short int bit[n:0] svBitVecVal
int int logic svLogic
longint long int reg svLogic
real double logic[n:0] svLogicVecVal*
string char* reg[n:0] svLogicVecVal*
string[n] char* int[] svOpenArrayHandle
chandle void* byte[] svOpenArrayHandle
shortint[] svOpenArrayHandle
longint[] svOpenArrayHandle
real[] svOpenArrayHandle

How to pass arguments to methods

There are two ways of passing arguments in both C and SV:

  • pass by value: the callee function will use a copy of the argument from the caller
  • pass by reference: the callee function will use a pointer/reference of the argument from the caller

If a function is changing the values of its arguments, the change is visible outside of the function only if the arguments are passed by reference. When arguments are passed by value, any change to the arguments done inside the function is NOT visible outside of it.

In SystemVerilog, passing by value or by reference is determined by the argument direction.
In C, passing by value or by reference is determined by whether the argument type is a pointer.
By default both SV and C are passing arguments by value.

Passing arguments by value

//SV - passing by value
function void f1(int a); // implicit direction of a is input
function void f2(input int a); // explicit direction mentioned as input
//C - passing by value
void f1(int a); // argument a is passed by value

Passing arguments by reference

//SV - passing by reference
function void f1(output int a); // direction of a is output, thus it is passing a reference 
//C - passing by reference
void f1(int* a); // argument a is passed by reference

The Application

The SV/C application provides usage examples for every data type in the table above. The source code of the application can be downloaded from here.

It also offers a self checking testbench that was successfully run on all 3 major simulators: QuestaSim, VCS, Xcelium.

Example

Let’s assume that we need to send out one bit from SystemVerilog towards a C implementation and return a result back to SystemVerilog. The SystemVerilog code could use two ways for receiving data from the C code:

  • via return value – get_bit() example
  • via argument – compute_bit() example

Since the library was developed with self-checking in mind, you will notice two assertions for checking the validity of data received from the C counterpart.

The following code snippets will show how to import a function definition and how to use it in SystemVerilog.

// First we must import the functions' declarations whose implementations are done in C
import "DPI-C" function void compute_bit(input bit i_value, output bit result);
import "DPI-C" function bit get_bit(input bit i_value);
//...
rand bit m_bit;
//...
function void test_bit();
  bit cres, ares;
  bit expected = transform_bit(m_bit);
  $display($sformatf("test.test_bit calls compute_bit with %b", m_bit));
  compute_bit(m_bit, cres);
  ares = get_bit(m_bit);
  COMPUTE_BIT_ERR: assert(cres == expected) else begin
    $display($sformatf("compute_bit error: expected %b received %b for input %b", expected, cres, m_bit));
    $finish();
  end
  GET_BIT_ERR: assert(ares == expected) else begin
    $display($sformatf("get_bit error: expected %b received %b for input %b", expected, ares, m_bit));
    $finish();
  end
endfunction

function bit transform_bit(bit in);
  return !in;
endfunction

This is the piece of code from C, showing the function definition:

//include the SystemVerilog DPI header file
#include "svdpi.h"

// Define the corresponding C functions to be imported in SystemVerilog

//compute function returns the result as argument to the function
void compute_bit(const svBit i_value, svBit* result) {
  log_info("dpi_c.compute_bit(): input %u", i_value);
  *result = transform_svBit(i_value);
  log_info("dpi_c.compute_bit(): result %u", *result);
}

//get function returns the result as return value of the function
svBit get_bit(const svBit i_value) {
  svBit result;
  log_info("dpi_c.get_bit(): input %u", i_value);
  result = transform_svBit(i_value);
  log_info("dpi_c.get_bit(): result %u", result);
  return result;
}

svBit transform_svBit(const svBit in) {
	return !in;
}

Data Types Mappings and The Corresponding API definitions

Next, we’ll list again the data type mappings, but this time together with examples of functions’ signatures from both SystemVerilog side and C side. In this manner you should be able to understand how data types can be used as function arguments or as return values for the functions.

SV byte maps to C char
// SV
import "DPI-C" function void compute_byte(input byte i_value, output byte result);
import "DPI-C" function byte get_byte(input byte i_value);
// C
void compute_byte(const char i_value, char* result);
char get_byte(const char i_value);
SV shortint maps to C short int
import "DPI-C" function void compute_shortint(input shortint i_value, output shortint result);
import "DPI-C" function shortint get_shortint(input shortint i_value);
void compute_shortint(const short int i_value, short int* result);
short int get_shortint(const short int i_value);
SV int maps to C int
// SV
import "DPI-C" function void compute_int(input int i_value, output int result);
import "DPI-C" function int get_int(input int i_value);
// C
void compute_int(const int i_value, int* result);
int get_int(const int i_value);
SV longint maps to C long int
// SV
import "DPI-C" function void compute_longint(input longint i_value, output longint result);
import "DPI-C" function longint get_longint(input longint i_value);
// C
void compute_longint(const long int i_value, long int* result);
long int get_longint(const long int i_value);
SV real maps to C double
// SV
import "DPI-C" function void compute_real(input real i_value, output real result);
import "DPI-C" function real get_real(input real i_value);
// C
void compute_real(const double i_value, double* result);
double get_real(const double i_value);
SV string maps to C char*
// SV
import "DPI-C" function void compute_string(input string i_value, output string result);
import "DPI-C" function string get_string(input string i_value);
// C
void compute_string(const char* i_value, char** result);
char* get_string(const char* i_value);
SV chandle maps to C void*
// SV
import "DPI-C" function void compute_chandle(output chandle result);
import "DPI-C" function chandle get_chandle();
import "DPI-C" function void call_chandle(input chandle i_value, output int result);
// C
void compute_chandle(void** result);
void** get_chandle();
void call_chandle(const void* i_value, int* o_value);
SV bit maps to C bit
// SV
import "DPI-C" function void compute_bit(input bit i_value, output bit result);
import "DPI-C" function bit get_bit(input bit i_value);
// C
void compute_bit(const svBit i_value, svBit* result);
svBit get_bit(const svBit i_value);
SV bit[n:0] maps to C svBitVecVal
// SV
import "DPI-C" function void compute_bit_vector(input bit[`BIT_ARRAY_SIZE - 1 : 0] i_val, output bit[`BIT_ARRAY_SIZE - 1 : 0] result);
import "DPI-C" function bit[`BIT_ARRAY_SIZE - 1 : 0] get_bit_vector(input bit[`BIT_ARRAY_SIZE - 1 : 0] i_val);
// C
void compute_bit_vector(const svBitVecVal* i_value, svBitVecVal* result);
svBitVecVal get_bit_vector(const svBitVecVal* i_value);
SV logic maps to C svLogic

// SV
import "DPI-C" function void compute_logic(input logic i_value, output logic result);
import "DPI-C" function logic get_logic(input logic i_value);
// C
void compute_logic(const svLogic i_value, svLogic* result);
svLogic get_logic(const svLogic i_value);
SV reg maps to C svLogic
// SV
import "DPI-C" function void compute_reg(input reg i_value, output reg result);
import "DPI-C" function reg  get_reg(input reg i_value);
// C
void compute_reg(const svLogic i_value, svLogic* result);
svLogic get_reg(const svLogic i_value);
SV logic[n:0] maps to C svLogicVecVal
// SV
import "DPI-C" function void compute_logic_vector(input logic[`LOGIC_ARRAY_SIZE - 1 : 0] i_val, output logic[`LOGIC_ARRAY_SIZE - 1 : 0] result, input int asize);
// C
svLogicVecVal*  get_logic_vector(const svLogicVecVal* i_value, int asize);
SV reg[n:0] maps to C svLogicVecVal
// SV
import "DPI-C" function void compute_reg_vector(input reg[`REG_ARRAY_SIZE - 1 : 0] i_val, output reg[`REG_ARRAY_SIZE - 1 : 0] result, input int asize);
// C
void compute_reg_vector(const svLogicVecVal* i_value, svLogicVecVal* result, int asize);
SV int[] maps to C svOpenArrayHandle
// SV
import "DPI-C" function void compute_unsized_int_array(input int i_value[], output int result[]);
// C
void compute_unsized_int_array(const svOpenArrayHandle i_value, svOpenArrayHandle result);
SV struct maps to C struct
// SV
`define BIT_ARRAY_SIZE 16
typedef struct {
	byte aByte;
	int anInt;
	bit aBit;
	longint aLongInt;
	bit[`BIT_ARRAY_SIZE-1:0] aBitVector;
} dpi_c_ex_s;
import "DPI-C" function void compute_struct(input dpi_c_ex_s i_value, output dpi_c_ex_s result);
// C
typedef struct dpi_c_ex_s {
	char aChar;
	int anInt;
	svBit aBit;
	long int aLongInt;
	svBitVecVal aBitVector;
} dpi_c_ex_s;
void compute_struct(const dpi_c_ex_s* i_value, dpi_c_ex_s* output);

All simulators support the examples above…similar to UVM the exceptions are guarded by macros( i.e. `ifdef).

References

If you are looking for more details about how DPI-C works, I recommend reading the following:

Enjoy!


Comments

andrew ming February 1st, 2019 02:16:19

” Passing arguments by reference

//SV – passing by reference
function void f1(output int a); // direction of a is output, thus it is passing a reference

I am not sure where i can find the output indicates “passing a reference”


andrew ming February 1st, 2019 02:32:29

never mind my previous question. i think you mean “passing reference” in C code if the argument in SV side is output.


    Aurelian Ionel Munteanu February 1st, 2019 13:14:18

    Hi, Andrew.

    Thank you for paying attention to details.
    Technically speaking you are correct. A SystemVerilog output argument is not passed by reference but, as per LRM IEEE-1800-2012, is passed by copy-out.
    Still, in my opinion, the effect is similar.

    As per LRM IEEE-1800-2012 :

    Chapter 35.6.1 Argument passing

    For the SystemVerilog side of the interface, the semantics of arguments passing is as if input arguments are
    passed by copy-in, output arguments are passed by copy-out, and inout arguments are passed by copy-in,
    copy-out. The terms copy-in and copy-out do not impose the actual implementation; they refer only to
    “hypothetical assignment.”
    The actual implementation of argument passing is transparent to the SystemVerilog side of the interface. In
    particular, it is transparent to SystemVerilog whether an argument is actually passed by value or by
    reference
    . The actual argument passing mechanism is defined in the foreign language layer. See Annex H
    for more details.


    Aurelian Ionel Munteanu February 1st, 2019 16:17:01

    By the way, here is an interesting question on stackOverflow: What does “ ref ” mean in systemverilog?
    The answer explains nicely, when to use ref and when to use output.


Carl Cavanagh February 1st, 2019 03:45:27

Great overview of the various mappings, especially how to handle output argument passing. I’ve struggled to find examples of getting values back from C into SystemVerilog via task arguments, and this page confirmed that I was on the right track. One suggestion to add to the material is how to deal with memory allocation. For example if one creates a string in C (char array) it needs to be allocated memory and then subsequently its needs to be freed. Currently I’ve settled on having SystemVerilog invoke a “freeStrMemPtr” C function to free up the given char* array in C memory space, once its assigned/copied the string in SystemVerilog memory space. I haven’t seen any examples of this online (or in the spec), so maybe I’m doing the wrong thing, so it would be a useful addition to this page.


    Aurelian Ionel Munteanu February 1st, 2019 16:11:48

    Hi, Carl.

    Thanks for the kind words and for suggestions.
    Memory management when using both SystemVerilog and C, is an interesting topic. It could make the subject of a different blog post.

    I also discussed this with a colleague of mine. Dragos, is developing a library for functional coverage in SystemC. He noticed some kind of a similar approach on Cadence’s website: Sharing memory between SV and DPI-C methods.


Kaushal Modi May 6th, 2019 19:27:52

Hello,

I am actively investigating the use of a foreign language called Nim[1] to interface with SV instead of using raw C or C++. In the process, I have done some research on using DPI-C with SystemVerilog and am always on the lookout for C examples in the wild used for interfacing with SV.

As I find such C examples (and time), I translate them to Nim and put them on my nim-systemverilog-dpic[2] GitHub repo for others to review and critique.

As my latest exercise, I converted all the C/H files in this blog post to a Nim file over here[3] (that compiles to both C and C++).

Further discussion at https://github.com/amiq-consulting/amiq_blog/issues/2 :). I am looking forward to feedback.

[1]: https://nim-lang.org
[2]: https://github.com/kaushalmodi/nim-systemverilog-dpic
[3]: https://github.com/kaushalmodi/nim-systemverilog-dpic/blob/master/amiq_dpi_c_examples/libdpi.nim


Udit Kumar June 11th, 2019 10:43:34

Hi Aurelian,

Could you please give any reference to fully working example of import “DPI-C” function void compute_unsized_int_array(input int i_value[], output int result[]);

I am trying to implement something similar and able to pass the dyanmic array from SV to C successfully but when passing dynamic array (result) from C to SV, SV side is not getting the correct value in dynamic array.

BR,
Udit


Leave a Comment:

Your comment will be visible after approval.

(will not be published)

This site uses Akismet to reduce spam. Learn how your comment data is processed.