C Part II · Module 19 · DPI-C Foundations
The SystemVerilog/C Boundary in Both Directions
Almost everything written about DPI covers SystemVerilog calling C, which is half the boundary. A C model that has to sample a signal, raise an event, or report through the testbench's own mechanism cannot do any of it by returning a value — it has to call back. This chapter gives both directions equal weight, and establishes the property that makes the C half of either one developable without a simulator at all: in both directions the C is ordinary C, with no header, no marking and no awareness that a simulator exists.
Advanced21 min readCPart IIVerificationDPI-C
Part II · Module 19 · Chapter 19.2 · The SystemVerilog/C Boundary in Both Directions
1. Two Directions
The boundary carries traffic both ways and the two have names.
An import is SystemVerilog calling C. The SystemVerilog side declares that a C function exists and what its arguments look like; calls to it then look like calls to any other function.
An export is C calling SystemVerilog. The SystemVerilog side declares that one of its own tasks or functions may be called from C; the C side declares an ordinary prototype and calls it.
Imports are what gets taught. Exports are taught last or omitted, and the consequence is a widespread belief that the traffic only runs one way — which makes a whole class of design look impossible.
2. Why Exports Exist
A C reference model computes an expected value and returns it. That covers a great many cases and it does not cover these:
The model needs to sample something. Its expected output depends on a signal the testbench has, and which was not an argument — a mode bit, a configuration register, the current state of an arbiter.
The model needs to raise an event. It has detected something and wants the testbench's own mechanism to react — a scoreboard entry, a semaphore, a completion.
The model needs to report. Printing from C writes to a stream the testbench does not control. The testbench has a reporting system with severity levels, filtering and a summary, and a model's message belongs in it.
The model drives rather than checks. A C stimulus generator that decides what the next transaction should be has to hand it to something that can drive it.
None of those can be done by returning a value, and all of them are ordinary requirements. That is what exports are for.
3. In Both Directions, the C Is Ordinary C
This is the chapter's most useful observation and it is measurable.
An import's C side is a function definition. Nothing more:
int c_add(int a, int b) { return a + b; }Measured. That compiles with -Wall -Wextra -Wpedantic -Wconversion -Wsign-conversion, runs clean under AddressSanitizer and UndefinedBehaviorSanitizer, gives c_add(40, 2) = 42, and includes no DPI header. There is no attribute marking it as callable from SystemVerilog, no registration, and no reference to a simulator.
An export's C side is a prototype and a call. Also nothing more:
/* the SystemVerilog side exported this; to C it is an ordinary function */
extern void sv_report(int severity, int code);
void c_model_step(int value) {
if (value > 1000) sv_report(2, value); /* call back into SystemVerilog */
}From the LRM: the export "DPI-C" declaration lives on the SystemVerilog side, in the scope that defines the task or function. Measured: the C above compiles against that prototype with no DPI header, and links once something provides sv_report.
The asymmetry worth noticing is in Figure 1's fourth row: both declarations are on the SystemVerilog side, including for the direction where C is the caller. A C file on its own never reveals that an export is involved — sv_report looks like any other external function. That is Chapter 11.4's linkage material arriving in a new setting: an undefined symbol that something else must supply at link time, and the C has no way to know what.
4. What the Simulator Supplies
For an import, the simulator finds your C function by name. Chapter 19.6 is the build, and Chapter 19.1 §5 already measured the hazard — the same source compiled as C++ exports __Z5c_addii instead of _c_add, and no simulator will find it.
For an export, the simulator provides the function your C calls. sv_report has no definition in your C at all; the simulator supplies it, and calling it runs SystemVerilog.
Both are ordinary linkage questions from Chapter 11.4's point of view — a name that one translation unit defines and another uses — with the unusual property that one side of the link is not a C translation unit.
5. Developing the C Half Without a Simulator
§3's property has a practical consequence that is worth more than it sounds, and it is the reason to establish it early.
The entire C half of a DPI interface can be written, compiled, tested and sanitised with no simulator present — by supplying the export side yourself.
/* ---- the C model under test: unchanged, exactly as it will ship ---- */
extern void sv_report(int severity, int code);
void c_model_step(int value) {
if (value > 1000) sv_report(2, value);
}
/* ---- a stub, only in the test build, standing in for the simulator ---- */
#include <stdio.h>
static int last_sev = -1, last_code = -1;
void sv_report(int severity, int code) { last_sev = severity; last_code = code; }
int main(void) {
c_model_step(500);
printf("no report expected : sev=%d code=%d\n", last_sev, last_code);
c_model_step(2000);
printf("report expected : sev=%d code=%d\n", last_sev, last_code);
return 0;
}The model's source is unchanged between the test build and the simulation build. Only what provides sv_report differs — your stub, or the simulator.
What that buys is everything Part I's correctness tooling offers, on code that would otherwise only run inside a simulator process: Chapter 13.10's sanitizers, Chapter 13.9's debugger, Chapter 13.7's warning flags, and a test loop that runs in milliseconds instead of minutes.
6. What Crosses, and What Does Not
A preview of Chapter 19.3's subject, because the two directions differ here in a way that matters.
Values cross. Arguments and return values, converted between the two type systems. The simple scalars are direct — Chapter 19.1 §3 measured sizeof(int) as 4, which is what SystemVerilog's int requires.
Time does not cross in an import that is a function. A SystemVerilog function cannot consume simulation time, and an imported function inherits that. Something that must wait is an imported task, which is Chapter 19.3's distinction and is the main reason that chapter exists.
Simulation context does not cross by default. An imported function does not know which scope called it, which instance it belongs to, or what time it is. Chapter 19.5's context qualifier is what grants the minimum needed to call back — and an export requires it, because reaching a SystemVerilog task means reaching it somewhere.
State does not cross, and this one surprises people. A C function's static variables belong to the shared object, not to any SystemVerilog instance. Measured — a counter in a shared object, called from two places, gave 1 2 3 and then 4:
three calls from 'instance A': 1 2 3
one call from 'instance B' : 4 <- SAME counter, not a fresh oneFour SystemVerilog instances calling the same imported function share one copy of its statics. Chapter 19.7 is that subject properly.
7. The Misconception: DPI Means SystemVerilog Calling C
"DPI is how SystemVerilog calls into C."
Half the boundary, and the missing half is the one that makes a class of design possible.
A C model that must sample a signal, raise an event, report through the testbench's mechanism, or drive stimulus cannot do any of it by returning a value. Returning a value is a result; those are actions, and they happen at a moment of the C's choosing rather than at the moment the C happens to return.
The reason the belief is so common is ordinary: the import direction is enough for the first and most-taught example — a reference model that takes inputs and gives an expected output — and material that stops there is not wrong, merely incomplete.
The corrected position:
An import is for computing something. An export is for doing something. A model that only computes needs one direction; a model that participates needs both.
And one consequence to carry into Chapter 19.5: an export is the reason context exists. A C function that calls back into SystemVerilog must reach a particular scope, and a function that was never told which scope called it has nowhere to go.
8. What You Should Be Able to Say Now
- Two directions: an import is SystemVerilog calling C; an export is C calling SystemVerilog.
- Exports exist for things a return value cannot do — sample a signal, raise an event, report through the testbench's mechanism, drive stimulus.
- In both directions the C is ordinary C. An import is a function you define; an export is a prototype you declare and call. Measured:
c_add(40,2) = 42, no DPI header, clean under-Wconversion -Wsign-conversionand ASan/UBSan. - Both declarations live on the SystemVerilog side — including for the direction where C is the caller. A C file alone never reveals that an export is involved.
- Both are ordinary linkage questions (11.4): a name one side defines and the other uses, where one side is not a C translation unit.
- The C half can be developed with no simulator, by stubbing the exported functions. The model's source is unchanged between builds.
- That is an independence argument, not just convenience (18.2) — a model you can only observe through the testbench is a model you cannot interrogate.
- Time does not cross an imported function — that is 19.3's task distinction.
- State does not cross. Measured: one counter across callers —
1 2 3then4. - An import computes; an export acts. And an export is why
contextexists (19.5).
9. Exercises
- Write
c_addand confirm it compiles and runs clean under the full flag set with no DPI header. State what that tells you about where the C half can be developed. - Write §5's model and stub, run both cases, and confirm the reported values. Then change only the stub — not the model — to count calls instead.
- List four things a C reference model might need that a return value cannot provide. For each, say whether an export is the only option.
- §3 says a C file never reveals that an export is involved. Given only the C, how would you discover that
sv_reportis a SystemVerilog task? - Reproduce §6's static-counter measurement. Then predict what four SystemVerilog instances calling that function would observe.
- Using Chapter 11.4's vocabulary, describe what the simulator does at link time for an import and for an export. Which direction has the undefined symbol in your C?
- A colleague says their C model cannot report through the testbench's messaging because DPI only goes one way. Write the two-sentence correction.
10. Summary
- Import = SystemVerilog calls C. Export = C calls SystemVerilog. Both are real; the second is under-taught.
- Exports do what a return value cannot — sample, signal, report, drive.
- In both directions the C is ordinary C — measured
42, no DPI header, sanitiser-clean. - Both declarations are on the SystemVerilog side; the C never shows that an export is involved.
- Ordinary linkage (11.4), with one side not a C translation unit.
- Stub the exports and develop the C half with no simulator — same source, different provider.
- That preserves independence (18.2), not just time.
- Time does not cross a function (19.3); state does not cross at all — measured
1 2 3then4. - An import computes; an export acts. Exports are why
contextexists.
An import that must wait for something cannot be a function at all, because a SystemVerilog function may not consume time. That restriction produces the first real branch in the interface — two kinds of import with different rules — and it is the distinction that decides what a model is allowed to be.
Standards & specifications
- Governing standard
- IEEE Std 1800 (SystemVerilog)(opens IEEE in a new tab)
Defines SystemVerilog language semantics — syntax, data types, scheduling and the behaviour a conforming simulator must produce. It does not define tool-specific synthesis support or vendor methodology.
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.
