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 Type | C Type |
|---|---|
| byte | char |
| shortint | short int |
| int | int |
| longint | long long |
| real | double |
| shortreal | float |
| string | const 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; anexport "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
inttoint,bytetochar); wide vectors need DPI's own special vector types instead