Passing and receiving different SystemVerilog datatypes with C/C++ using DPI

SystemVerilog makes interaction with C/C++ a piece of cake. Now, you can directly call C/C++ functions as functions or tasks in SystemVerilog(aka. Importing) and call SystemVerilog functions or tasks from C/C++(aka. Exporting). In this article, we will see how we can pass and receive different SystemVerilog datatypes through DPI.

Return type of imported C/C++ functions

First thing to note is that imported functions can only return single bit types and 2-state int or double types that are not vectors.

This rule rules out all 4-state types except single bit types

  • logic packed arrays, logic unpacked arrays
  • wire, wire packed arrays, wire unpacked arrays
  • reg, reg packed arrays, reg unpacked arrays

and all vector types

  • bit packed arrays, bit unpacked arrays
  • logic packed arrays, logic unpacked arrays
  • wire packed arrays, wire unpacked arrays
  • reg packed arrays, reg unpacked arrays

So what we are left with are fixed width int and double types:

  • bit
  • byte
  • shortint
  • int
  • shortreal
  • real
  • logic

You can pass these types as arguments and receive these types as return from an imported functions. Here is an example of passing and receiving single bit bit and logic.

import "DPI-C" context function bit and_bit(input bit a, b);
import "DPI-C" context function logic and_logic(input logic a, b);
svBit
and_bit(
    svBit a,
    svBit b) {
	return a && b;
}

svLogic
and_logic(
    svLogic a,
    svLogic b) {
	return a && b;
}

You should have noticed two new types on C/C++ side: svBit and svLogic. These are usually 8 bit unsigned char types but only the lsb bit is used.

Passing and receiving packed arrays

Passing and receiving packed arrays is easier than dealing with unpacked arrays. On C side, packed bit vectors are called svBitVecVal and packed logic vectors are called svLogicVecVal. Bit vectors are plain int types. If the vector size exceeds the size that can be stored inside an integer, the bit vectors are passed as array of svBitVecVal.
Logic vectors are little bit more complicated because they represent 4-state values. svLogicVecVal is a structure with two members aval and bval. aval stores the plain bit representation of logic vector while bval stores whether the corresponding bit is ‘0’, ‘1’ or ‘x’, ‘z’. If the vector size exceeds the size that can be stored inside an integer, the logic vectors are passed as array of svLogicVecVal.

[ada]
import “DPI-C” context function void add_bpv(input bit [3:0] a, b, output bit [3:0] c);
import “DPI-C” context function void add_lpv(input logic [3:0] a, b, output logic [3:0] c);
[/ada]

void
add_bpv(
    const svBitVecVal* a,
    const svBitVecVal* b,
    svBitVecVal* c) {
	*c = *a + *b;
}

void
add_lpv(
    const svLogicVecVal* a,
    const svLogicVecVal* b,
    svLogicVecVal* c) {
	c->aval = a->aval + b->aval;
	c->bval = 0;
}

As you can see, we are using output arguments to receive vector types because imported functions cannot return vector types.

Passing and receiving unpacked vectors

Unpacked arrays are sent as arrays of svBit or scLogic to C. There is no way to directly access the integer represented by putting all the individual bits together without calculating it.

import "DPI-C" context function void and_buv(input bit a[3:0], b [3:0], output bit c [3:0]);
import "DPI-C" context function void or_luv(input logic a[3:0], b [3:0], output logic c [3:0]);
void
and_buv(
    const svBit* a,
    const svBit* b,
    svBit* c) {
	for(int i = 0; i < 4; i++) {
		c[i] = a[i] & b[i];
	}
}
void
or_luv(
    const svLogic* a,
    const svLogic* b,
    svLogic* c) {
	for(int i = 0; i < 4; i++) {
		c[i] = a[i] | b[i];
	}
}

Passing and receiving mixed packed and unpacked arrays

As expected, mixed packed and unpacked arrays are represented as arrays of svBitVecVal or svLogicVecVal. If the size of the packed dimension is greater than what can fit inside an integer, mixed packed and unpacked arrays are represented as arrays of arrays of svBitVecVal or svLogicVecVal.

import "DPI-C" context function void add_bpuv(input bit [0:3]a[0:3], b [0:3], output bit [0:3]c [3:0]);
void
add_bpuv(
    const svBitVecVal* a,
    const svBitVecVal* b,
    svBitVecVal* c) {
	for(int i = 0; i < 4; i++) {
		c[i] = a[i] + b[i];
	}
}

Passing and receiving structs

Structs are passed can be passed and received directly. Individual members are representing based on their data type as discussed above.

typedef struct {
shortint mem1;
logic [7:0] mem2;
bit [7:0] mem3;
} struct_t;

import "DPI-C" context function void add_struct(input struct_t a, b, output struct_t c);
void
add_struct(
    const struct_t* a,
    const struct_t* b,
    struct_t* c) {
	c->mem1 = a->mem1 + b->mem1;
	c->mem2[0].aval = a->mem2[0].aval + b->mem2[0].aval;
	c->mem2[0].bval = 0;
	c->mem3[0] = a->mem3[0] + b->mem3[0];
}

Here is the full verilog and c code.


Crack and install Modelsim on linux

Note: Piracy is crime. Please don’t use or encourage pirated software. If you would like to evaluate Modelsim, try the evaluation version. If you still haven’t changed your mind, read on :P.

1) Install the required dependencies

2) Download modelsim 10.1c for linux and its corresponding crack.

3) Mount the downloaded modelsim iso and install modelsim.

mkdir /tmp/modelsim/
mount -o loop path-to-iso /tmp/modelsim
cd /tmp/modelsim
./install.linux

Note:Install modelsim under ~/modelsim/ directory. If you choose another location, please make sure you exchange the default location with your preferred location in the steps below.

4) Execute this script to crack modelsim

Comment the first two lines in the generated license.dat

#SERVER xxxx xxxxxxxxxxxx 27001
#VENDOR mgcld D:FEATURE mgc_s mgcld 2020.00 1-jan-2021 999 0 TS_OK

5) Fix libfreetype problem (Only required if you face this problem)
Try to start vsim

cd ~/modelsim/modeltech/linux_x86_64/
./vsim

If vsim reports the following error, your distro’s freetype library doesn’t play well with modelsim.

** Fatal: Read failure in vlm process (0,0)
Segmentation fault (core dumped)

Use the following script to compile custom freetype

6) Modelsim is ready to use! One last step, add these commands to ~/.bashrc to avoid executing every time you start a new terminal

Linux and Modelsim!!!! Ain’t it fun?