SystemVerilog DPI (Direct Programming Interface) lets you interface between SystemVerilog and foreign programming languages such as C, C++, and SystemC. It's an easy and efficient way to connect existing code — usually written in C or C++ — without needing to learn PLI or VPI at all.

9 min read | Intermediate Level

What You'll Learn

  • What DPI is, and the problem it solves compared to PLI/VPI
  • The import declaration syntax, for calling a C function from SystemVerilog
  • The export declaration syntax, for calling a SystemVerilog function from C
  • What it actually takes to compile and link C code into a simulation, beyond just the DPI declaration
  • How basic SystemVerilog and C data types map to each other across the DPI boundary

Import Declarations

Methods implemented in a foreign language can be called from SystemVerilog once declared with an import declaration. Imported subroutines can have zero or more input, output, or inout arguments, and can return void or a result:


import "DPI-C" function void init();
import "DPI-C" function int sum(int a, b);

Export Declarations

Methods implemented in SystemVerilog can be called from foreign code once specified in an export declaration:


export "DPI-C" function int randomize(int range_a, range_b);

A Complete Example

The SystemVerilog side declares and calls the imported C function:


module tb_top;

	// Import the function called "system_init" implemented in C code
	import "DPI-C" function void system_init();

	initial begin
		$display ("[%0t] Call C method", $time);
		system_init();
	end
endmodule

And the C side implements it:


#include "stdio.h"
#include "stdlib.h"
  
extern "C" void system_init() {
	printf("Start system initialization ...\n");
}

extern "C" is a C++-only linkage specifier — a genuine C compiler doesn't recognize this syntax at all and would fail on this exact line. This file is only valid as written if it's actually compiled with a C++ compiler (like g++), despite using plain C headers and looking like a C file. If you're compiling this with a real C compiler instead, drop extern "C" entirely — a plain C function already has the linkage DPI expects, with no special keyword needed.

Declaring the import in SystemVerilog isn't enough on its own — the C (or C++) source still has to be compiled and linked into the simulation. Most simulators expect this as a separate compile step alongside the SystemVerilog compile, for example:


# Example (tool and flag names vary by simulator):
gcc -c system_init.c -o system_init.o
xrun testbench.sv system_init.o

Once both pieces are compiled and linked together, running the simulation produces output from both languages, in the order they actually execute:


[0] Call C method
Start system initialization ...

The first line is SystemVerilog's own $display; the second is the C function's printf, executing the moment system_init() is called from the initial block.

Quick Check

Q: If the C source file were never compiled and linked into the simulation at all, what would happen when system_init() is called?

Show Answer

A link-time (or elaboration-time) error — the SystemVerilog side declares that system_init() exists and what its signature looks like, but the actual implementation has to come from somewhere. Without compiling and linking the C code, there's nothing behind that declaration.

Passing Arguments Between SystemVerilog and C

Extending the sum import declared earlier into a full working example — the SystemVerilog side:


import "DPI-C" function int sum(int a, int b);

module tb;
  initial begin
    $display ("sum(3,4) = %0d", sum(3,4));
  end
endmodule

And the matching C implementation:


int sum(int a, int b) {
  return a + b;
}

Calling sum(3,4) from the initial block crosses into the C function, computes 3 + 4, and returns 7 back into SystemVerilog — from the testbench's point of view, this is no different from calling any other function, even though the actual computation happened in compiled C code.

Common SystemVerilog-to-C Type Mapping

DPI defines a standard mapping between simple SystemVerilog and C types:

SystemVerilog TypeC Type
bytechar
shortintshort int
intint
longintlong long
realdouble
shortrealfloat
stringconst char*

Multi-bit bit and logic vectors don't map onto ordinary C integer types this simply — they use DPI's own special vector types (svBitVecVal, svLogicVecVal) on the C side to correctly represent packed 4-state or 2-state vectors of arbitrary width. Stick to the simple scalar types in the table above until you specifically need to pass a wide vector across the boundary.

Where Is DPI Used?

DPI is commonly used to call into an existing C or C++ reference model — a bit-accurate golden model of an algorithm, for instance — directly from a scoreboard, instead of reimplementing that same logic in SystemVerilog. It's also a practical way to reuse legacy C testbench utility code without a full rewrite.

Common Beginner Mistakes

Mistake #1: Mismatching extern "C" with the actual compiler used

As flagged above, extern "C" only means something to a C++ compiler. Why? Using it in a file compiled as plain C is a syntax error; omitting it in a file compiled as C++ can cause a link-time "undefined reference" error instead, since C++ name-mangles function names by default unless told not to.

Mistake #2: Forgetting the C code still needs to be compiled and linked

An import "DPI-C" declaration only tells SystemVerilog that a function exists somewhere — it doesn't make the implementation appear. Why? The actual C or C++ source still has to be compiled separately and linked into the simulation, using whatever mechanism your specific simulator provides for that.

Mistake #3: Mismatching argument types between the two languages

The SystemVerilog import declaration's argument types have to correspond correctly to the actual C function's parameter types. Why? A mismatch isn't always caught cleanly at compile time, and can silently produce corrupted or misinterpreted data crossing the language boundary instead of a clear error.

Mistake #4: Assuming an imported DPI function can consume simulation time

A plain import "DPI-C" function follows the same rule as any other SystemVerilog function — no time-consuming behavior. Why? If the C side genuinely needs to model something that takes simulation time, DPI separately supports importing a task instead of a function for that purpose — a different declaration with different rules, not something a plain function import can do.

Practice Exercise

Challenge: Add a subtract() Function

Following the same pattern as sum, declare and implement a DPI-imported subtract(int a, int b) function that returns a - b.

Hint

You need two matching pieces, just like sum: the import "DPI-C" declaration on the SystemVerilog side, and the actual function body on the C side.

Solution

import "DPI-C" function int subtract(int a, int b);

module tb;
  initial begin
    $display ("subtract(10,4) = %0d", subtract(10,4));
  end
endmodule

int subtract(int a, int b) {
  return a - b;
}

subtract(10,4) returns 6, computed entirely on the C side and handed back to SystemVerilog — exactly the same calling pattern as sum, just with different arithmetic inside the C function.

Summary

In this tutorial, you learned:

  • DPI lets SystemVerilog call into (and be called from) C, C++, or SystemC code, without needing PLI or VPI
  • An import "DPI-C" declaration makes a foreign function callable from SystemVerilog; an export "DPI-C" declaration makes a SystemVerilog function callable from foreign code
  • Declaring an import isn't enough on its own — the foreign source still has to be compiled and linked into the simulation separately
  • extern "C" only means something in C++ code, and using or omitting it incorrectly for the actual compiler in use causes a compile or link error
  • Simple scalar types map directly between SystemVerilog and C (like int to int, byte to char); wide vectors need DPI's own special vector types instead
Did you find what you were looking for?

Quiz

Test Your Knowledge

1 of 5 Score: 0 / 5