C++ Part II · Module 28 · C/C++ and SystemVerilog DPI
Why DPI Exists
The last module of this track is about the seam between the two languages a verification engineer actually uses, and it starts with the problem rather than the syntax because the syntax is short and the problem is not. A SystemVerilog type and a C++ type are two different descriptions of storage; a simulator's execution model and a function call are two different ideas of control flow. DPI is the standardised translation between them, and before it existed teams either wrote their models twice or glued them together through an interface designed for something else. This chapter also sets out something unusual, and sets it out at the start rather than burying it: on the toolchain in front of us DPI cannot be compiled or run at all, which I established by probing every language generation this simulator supports, searching the filesystem for the standard header, and looking for any other simulator. So every DPI declaration in this module is published as reference syntax and marked as such, while the half of the subject that is ordinary C++ — the linkage, the type mapping, the lifetimes, and every failure that produces a wrong answer instead of an error — is measured for real.
Advanced20 min readC++Part IIVerificationDPI
Part II · Module 28 · Chapter 28.1 · Why DPI Exists
1. The Problem
A verification environment needs a model of what the design should do. Module 27 established where that model belongs: the algorithmic, untimed work goes in C++, because it is a function from inputs to outputs and benefits from a compiled general-purpose language with containers, a test framework and reuse outside the simulator.
That leaves one question, and it is the only thing DPI is for: the model is in one language and the testbench is in another, and they have to call each other.
Concretely, four things have to happen:
The testbench must call the model. expected = model_step(data) — a function call from SystemVerilog into C++.
The model must sometimes call back. To report a mismatch into the right scope, to fetch a configuration value, to log through the testbench's own mechanism.
Values must survive the crossing. A SystemVerilog int becoming a C int, a byte array becoming bytes, a string becoming characters — at the right width, the right signedness and the right layout.
And the model must keep state between calls. A CRC accumulator, a register map, a memory. The second call has to see what the first one did.
Each of those is easy to state and each has a specific failure mode, which is what the remaining twelve chapters are about.
2. What Teams Did Before It
Worth knowing, because every alternative is still in use somewhere and each explains a design choice in DPI.
Write the model twice. Once in C for the firmware team, once in SystemVerilog for verification. Two implementations, two sets of bugs, and — this is the part that matters — the two can agree with each other while both disagree with the specification, which is Chapter 27.4's failure with extra steps.
PLI/VPI. The older procedural interface: register a system task, fetch arguments through handles, convert values yourself. It works — Chapter 27.1 §4 measured it working here, and it is what this module uses as its live substitute — and the cost is that every call is twenty lines of handle manipulation instead of a function call, with the type conversion written by hand each time.
Files. The simulation writes transactions, a C++ program reads them and produces a verdict. Genuinely good for many checkers, as Chapter 27.1 §4's third mechanism argued, and useless when the model must answer during the simulation.
Sockets or shared memory to a separate process. Flexible, and it introduces a second process, a protocol, serialisation on both sides, and a new class of hang.
DPI replaced the first two for most purposes by making the crossing a declaration rather than a programming exercise. The C++ function is an ordinary function; the SystemVerilog side declares its signature; the simulator does the marshalling.
3. What DPI Actually Is
Three properties, and all three matter for how the rest of the module reads.
It is a function-call interface, not a data-sharing one. You do not get a pointer into the simulator's signals. You get arguments passed in and a value returned, which is why Chapter 28.2's architecture chapter draws the boundary as a contract rather than as a window.
It runs in the simulator's own process. The C++ is compiled into a shared object the simulator loads, and a call is a function call — no serialisation, no process boundary, nanoseconds rather than microseconds. It also means a crash in your C++ takes the simulator with it, and a memory error in your model corrupts the simulator's heap.
And it is a contract between two type systems, declared twice: once in SystemVerilog as an import, once in C++ as a definition. Nothing checks that the two declarations agree. That single fact generates most of this module — Chapter 28.12 measures a mismatch that links cleanly and returns a plausible wrong number.
The direction vocabulary is worth fixing now because it is easy to invert:
| term | direction | declared in |
|---|---|---|
| import | SystemVerilog calls C | SystemVerilog, as import "DPI-C" |
| export | C calls SystemVerilog | SystemVerilog, as export "DPI-C" |
Both are declared on the SystemVerilog side. An import brings a C function in; an export makes a SystemVerilog function available out. Chapter 28.4 is the export direction, which is the less used and more surprising half.
4. The Shape of a Binding
Reference syntax, for orientation — the full treatment is Chapter 28.3:
// SystemVerilog side: declare what the C function looks like
import "DPI-C" function int crc32(input int data);
// ... and then just call it
initial begin
automatic int result = crc32(32'h0000_00FF);
$display("crc32 = %0h", result);
end// C++ side: an ordinary function, exposed under C linkage
extern "C" int crc32(int data)
{
return someAlgorithm(data);
}That is the whole mechanism in eight lines, and it is genuinely that simple when the types are scalars and the model is stateless. Everything in Chapters 28.5 through 28.12 is what happens when one of those two conditions fails.
5. What Can Be Executed Here, and What Cannot
Now the part that shapes the rest of the module, and it is better stated at the start than discovered later.
DPI cannot be compiled or run on this machine. Not partially, not slowly — at all. The evidence, from a two-line import declaration compiled at every language generation the simulator supports:
== 28.1 DPI is unavailable on every generation this simulator supports ==
iverilog -g2005-sv exit=2 dpi_probe.sv:2: syntax error
iverilog -g2009 exit=2 dpi_probe.sv:2: syntax error
iverilog -g2012 exit=2 dpi_probe.sv:2: syntax error
svdpi.h on this system: 0 found
headers iverilog does ship:
_pli_types.h
acc_user.h
ivl_target.h
sv_vpi_user.h
veriuser.h
vpi_user.hFive separate findings in that transcript.
import "DPI-C" is a syntax error at every generation — 2005-SystemVerilog, 2009 and 2012 — each with exit status 2 and no .vvp artefact produced. The status was read directly rather than through a pipe, because Icarus reports success through | head, a trap recorded three times now across these tracks. (-g2023 is rejected outright as an unknown generation.)
svdpi.h does not exist anywhere on the system. Zero matches under the Homebrew prefix, /usr/local or /usr/include. That header defines svBitVecVal, svLogicVecVal, svOpenArrayHandle and the open-array accessors, so without it the C side of a DPI binding cannot even be compiled, let alone linked.
The headers the simulator does ship are the PLI/VPI family — vpi_user.h, sv_vpi_user.h, acc_user.h, veriuser.h — which is the older interface, and the one that does work here.
The simulator binaries contain no DPI support at all: zero occurrences of the string in the driver or in the compiler proper.
And no other SystemVerilog simulator is installed. Verilator, VCS, Questa, Xcelium, ModelSim, DSim and several front-ends were all checked and all absent.
So the module is written under a constraint, and the constraint is handled the same way the I²C track handled UVM and the Tcl track handled EDA tools: every DPI declaration here is reference syntax, marked as not executed, and no DPI transcript appears anywhere in this module — because there is no simulator present that could produce one.
What is executed, and it turns out to be most of the subject:
| what | chapters |
|---|---|
| linkage, name mangling, the undefined-symbol failure | 28.5, 28.6 |
| the C-side scalar, array, string and open-array semantics | 28.7–28.10 |
| what a purity claim licenses, and the cost of a false one | 28.11 |
| every boundary failure that yields a wrong answer, not an error | 28.12 |
| a static declaration-vs-definition cross-checker | 28.12 |
| the full model integration, from pure C and live inside the simulator | 28.13 |
The live route in that last row is VPI, not DPI — the one Chapter 27.1 §4 measured working — and it is labelled as such every time it appears.
6. When Not to Use DPI
The mechanism is good enough that the failure mode is over-use. Four cases where something else is better, each measured or argued earlier in the track.
Anything timed. Chapter 27.10 §2's capability list: a clock edge, a temporal property, a constraint over related fields. DPI gives you a function call, and a function call has no notion of time.
A checker that does not need to react during the simulation. Dump transactions and judge them afterwards — no glue, a unit-testable model, no simulator licence in CI.
Work so fine-grained that the crossing dominates. Chapter 27.10 §6's rule: cross per transaction or per frame, never per cycle. A DPI call is cheap, and a million of them per simulated millisecond is not.
And anything where a crash is unacceptable. The C++ runs in the simulator's process, so a null dereference in your model is a dead simulation with no waveform written. That argues for the defensive boundary in Chapter 28.13 §2, where every entry point validates its handle.
7. The Table
| the question | the answer | measured |
|---|---|---|
| what problem does DPI solve | a model in C++ and a testbench in SystemVerilog must call each other | §1 |
| what did teams do before | write the model twice, PLI/VPI, files, or a separate process | §2 |
| what is DPI, in one line | a function-call contract between two type systems | §3 |
| where does the C++ run | in the simulator's process — fast, and a crash is fatal | §3 |
| what checks the two declarations agree | nothing | §3 |
| import against export | import = SV calls C; export = C calls SV; both declared in SV | §3 |
| is DPI available here | no — syntax error at every generation, exit 2, no artefact | §5 |
is svdpi.h present | no — zero matches on the whole system | §5 |
| any other simulator | none of thirteen checked | §5 |
| what does the module execute instead | linkage, type mapping, lifetimes, failures, a checker, and VPI | §5 |
| where is DPI's real difficulty | the contract, which is ordinary C++ | §5 callout |
| when should you not use DPI | timed work, post-processing, per-cycle crossings, crash-sensitive flows | §6 |
8. What to Actually Do
- Find out what your simulator supports before planning the integration — compile a two-line import and read the exit status directly, not through a pipe.
- Reach for DPI when the model must answer during the simulation and the work is per transaction or coarser.
- Use a file-based flow when it need not answer live — it is simpler and needs no licence in CI.
- Keep timed work, constraints and temporal properties in SystemVerilog. DPI is a function call.
- Remember the C++ is inside the simulator's process, so validate at the boundary and treat a crash as a lost regression run.
- Expect nothing to check that your two declarations agree, and plan a check of your own — Chapter 28.12's cross-checker is one.
- Never publish a transcript your toolchain cannot produce. Mark reference syntax as reference syntax.
9. Mistakes Worth Making Once
Planning a DPI flow without checking the simulator supports it. Here it is a syntax error at every generation.
Reading iverilog's exit status through a pipe. A failed build reports success.
Assuming svdpi.h is part of the compiler. It ships with the simulator, and not with this one.
Writing the model twice, once per language. Two implementations that can agree and both be wrong.
Using DPI for a clock-edge check. It is a function call with no notion of time.
Crossing the boundary per cycle. The call is cheap; a million of them are not.
Assuming a signature typo will be caught. Chapter 28.12: it links, runs, and returns a wrong number.
Forgetting the C++ shares the simulator's process. Your null dereference is a lost regression run with no waveform.
10. Interview Relevance
The frozen curriculum marks this High, and it is usually an opening question: what is DPI and why does it exist?
The complete answer is three sentences. DPI is a standardised function-call interface between SystemVerilog and C, so a reference model written in C can be called from a testbench and can call back into it. It exists because the alternatives were writing the model twice — two implementations that can agree with each other and both be wrong — or hand-marshalling every argument through PLI/VPI. And the C code runs inside the simulator's process, so a call costs a function call rather than a context switch, with the corollary that a memory error in your model corrupts the simulator.
The follow-up worth having ready is the vocabulary, because it is easy to invert: an import brings a C function into SystemVerilog and an export makes a SystemVerilog function callable from C, and both are declared on the SystemVerilog side.
The point that distinguishes an experienced answer is that nothing checks the two declarations against each other. The SystemVerilog import says what the function looks like, the C definition says what it is, and under C linkage there is no mangling to catch a disagreement — so a wrong width or a swapped argument links cleanly and returns a plausible number. Saying that you would add a static cross-check, or at minimum a known-value test through the boundary on day one, is a concrete answer to a question most candidates treat as rhetorical.
11. What You Should Be Able to Say Now
- That DPI exists so a C or C++ reference model and a SystemVerilog testbench can call each other, and that the alternatives were duplicating the model or hand-marshalling through PLI/VPI.
- That the four requirements are: the testbench calls the model, the model calls back, values survive the crossing at the right width and layout, and the model keeps state between calls.
- That DPI is a function-call contract between two type systems, not a window into the simulator's data.
- That the C++ runs in the simulator's own process — cheap calls, and a crash or heap corruption takes the simulation with it.
- That nothing checks that the SystemVerilog declaration and the C definition agree.
- That import is SV calling C and export is C calling SV, and both are declared in SystemVerilog.
- That on this machine DPI is unavailable:
import "DPI-C"is a syntax error at 2005-SV, 2009 and 2012, exit 2, no artefact;svdpi.his absent; the simulator ships only the PLI/VPI headers; and none of thirteen other simulators is installed. - That the module therefore publishes DPI declarations as reference syntax, and executes the C++ half — linkage, type mapping, lifetimes, boundary failures, a cross-checker — plus the full model integration live via VPI.
- That DPI's real difficulty is the contract, which is ordinary C++, rather than the declaration, which a simulator checks for you on the first run.
- When not to use it: timed work, checks that can run after the simulation, per-cycle crossings, and flows where a crash is unacceptable.
12. Exercises
Exercise 1 — probe your own simulator. Compile a two-line import "DPI-C" file. Record the exit status without a pipe and whether an artefact appeared.
Exercise 2 — find the header. Search your system for svdpi.h. If it is there, note which tool shipped it; if not, note that the C side cannot compile.
Exercise 3 — the alternatives. For a checker you know, write down what it would take as a DPI import, as a VPI system task, and as a file-based post-processing step. Count the lines of glue in each.
Exercise 4 — the direction vocabulary. Write one import and one export declaration from memory and check which side each is declared on.
Exercise 5 — the unchecked contract. Before reading Chapter 28.12, predict what happens if the SystemVerilog declaration says int and the C definition says short. Write your prediction down.
13. Summary
- DPI exists because the model and the testbench are in different languages and must call each other — the testbench calling in, the model calling back, values crossing at the right width and layout, and state persisting between calls.
- The alternatives were worse: writing the model twice, which produces two implementations that can agree and both be wrong; hand-marshalling through PLI/VPI; files, which cannot answer during the simulation; or a separate process, which adds a protocol and a new class of hang.
- DPI is a function-call contract between two type systems, declared twice — once as a SystemVerilog
import, once as a C definition — and nothing checks that the two agree. - The C++ runs inside the simulator's process, so a call is a function call and a memory error in the model corrupts the simulator.
- Import is SystemVerilog calling C; export is C calling SystemVerilog. Both are declared on the SystemVerilog side.
- Measured: DPI is unavailable on this machine.
import "DPI-C"is a syntax error at every supported generation with exit 2 and no artefact,svdpi.his absent system-wide, the simulator ships only the PLI/VPI headers, its binaries contain no DPI support, and none of thirteen other SystemVerilog simulators is installed. - So every DPI declaration in this module is reference syntax, marked as such, and no DPI transcript appears anywhere in it.
- What is executed is most of the subject: linkage and mangling, the scalar, array, string and open-array contracts, what a purity claim licenses, every boundary failure that returns a wrong answer rather than an error, a static cross-checker, and the complete model integration driven from pure C and live inside the simulator through VPI.
- Because DPI's difficulty is the contract rather than the declaration, and the contract is ordinary C++.
- And the habit worth taking from this chapter is to establish what the environment can do before deciding what to build.
Chapter 28.2 draws that contract as a picture: two type systems, two execution models, one process.
Standards & specifications
- Governing standard
- ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)
Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.
This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.
Where this fits
Part of the C++ Programming curriculum.
