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

22 Responses

  1. ” 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”

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

    1. 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.

  3. 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.

  4. 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

  5. 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

    1. Hi, Udit.

      Here is the C function definition:
      void compute_unsized_int_array(const svOpenArrayHandle i_value, svOpenArrayHandle result)

      Here is the DPI-C function signature:
      import “DPI-C” function void compute_unsized_int_array(input int i_value[], output int result[]);

      And here is the SystemVerilog code using the DPI-C:
      test_unsized_int_array();

      NOTE: Depending on your vendor’s simulator some data types might not be transferred as expected.

      Best regards,
      Aurelian

    1. Hello, Ravi.

      For that case, you need a function to convert the struct to svLogicVecVal *. After your conversion, you can send the svLogicVecVal * to a packed struct on the SystemVerilog side using DPI-C.

  6. Hello, how can we run these two files file.sv and file.c means with which command, can you please suggest command if I use VCS simulator? Because I tried but it is giving me an error !

    1. Hello, Dustin!

      A string from C can be passed to a SystemVerilog task using DPI-C.
      You can find an example below:

      SystemVerilog code:

      /*
       * Import the C function
       */
      import "DPI-C" function string string_c2sv();
      
      /*
       * A simple task that displays the C string 
       */
      task display_string();
      	string str;
      	
      	$display("Entering a SystemVerilog task..");
      	// Calls the C function that returns a string
      	str = string_c2sv();
      	// Display the C string
      	$display("The string is: %s", str);
      endtask
      

      C code:

      /*
       * The data type mapping table shows that "string" type in SystemVerilog is equivalent to "char*" in C.
       * The following section assigns the string "A string created in C." to a created char* and returns it.
       * The return of value of this function is then used in a SystemVerilog file, where the function "string_c2sv" is called.
       */
      char* string_c2sv(void) {
      	char *str;
      	str = "A string created in C.";
      	return str;
      }
      

      When calling the display_string() task, the output is:

      Entering a SystemVerilog task..
      The string is: A string created in C.
      
  7. My compilation with DPI-C calls pass but I got this run-time error below, any clue why?
    — log file —

    Error-[DPI-DIFNF] DPI import function not found
    tb/hvl_top.sv, 66
      The definition of DPI import function/task 'Basic_Test' does not exist.
      Please check the stated DPI import function/task is defined, and its 
      definition is either passed in a source file at compile-time, or provided in
      a shared library specified using the LRM Annex-J options at run-time.
    

    My source code is below:
    —– C code —–

    
    extern "C" void Basic_Test (char* testDir) { // testDir passed from command line    
        IO_PRINTF ("Enter %s in C environment ...\n", __FUNCTION__);
        sprintf (TestDirName, "%s/config/", testDir);
    
        IO_PRINTF ("Exit %s in C environment\n", __FUNCTION__);
    }
    

    — SV code —

    
        import "DPI-C" context task Basic_Test (input string testdir);
        initial begin
           Basic_Test ("string");
           $finish ();
        end
    
    1. The error indicates that the c file is not being compiled, thus the SV code does not know the implementation of the Basic_Test DPI-C function.

      It is interesting to note your comment: “testDir passed from command line”. I assume you mean from the simulator command line.

      Have a look at how we add compilation options to the simulator:
      https://github.com/amiq-consulting/amiq_blog/tree/master/amiq_sv_dpi_c_how_to_map_systemverilog_data_types_to_c_using_dpi_c/sim

  8. How to pass an unpacked struct from SV to C using an import call in which struct of SV is inout and bring data from C after filling the struct?

  9. Hi Aurelian,
    Nice informative article!
    Have you tried DPI tasks to access and wait on some signal to achieve certain value using VPI functions?
    I have a requirement to wait on a hierarchical DUT signal to attain certain value, but I have the path available only as string (from %m of SV).
    1. Do you know if I could add a DPI task which takes the string input, access handle using VPI and wait for the value inside the DPI itself?
    2. Or should I use a combination of functions: one to get the VPI handle out of DPI, possibly to a chandle type and use another DPI function to which chandle is passed and get the value back to SV. And add wait statement in SV itself. Can this slow down the simulation, compared to #1 method, if I have to get this done on, say a million different instances in a design?
    Please let me know your thoughts, or if you have any such example handy, please share. Thanks

  10. Hi
    I am new to DPI
    I have a .so file , how can I get golden values from c model whereas I do not have c code

  11. I believe you have an error in the section labeled “SV logic[n:0] maps to C svLogicVecVal”. The SV code imports a function named “compute_logic_vector” but the C code declares a function by a different name with a different function prototype (namely, the “result” parameter is missing and it returns a svLogicVecVal pointer instead of void).

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Subscribe to our newsletter

Do you want to be up to date with our latest articles?