C Part II · Module 23 · Calling SystemVerilog from C
Why a C Model Would Call Back into SystemVerilog
The usual reason given for exporting is that the testbench can then react to things as the model finds them. Measured, it cannot: a C function that wrote three findings into a simulator variable during one call left a watching SystemVerilog process waking exactly once, having seen only the last. Nothing in the simulation runs while the call is in progress, so the intermediate values never existed. The clumsy-looking design — record them in C and let the testbench fetch each one — turns out to be the only one of the three that let the testbench act on every event.
Advanced22 min readCPart IIVerificationDPI-C
Part II · Module 23 · Chapter 23.1 · Why a C Model Would Call Back into SystemVerilog
1. The Direction Reverses
Module 22 had the testbench asking and the C answering, eight chapters of it. This module turns the arrow around: from the LRM, SystemVerilog can export a subroutine, and C can call it.
The first question is not how. It is when, because the reverse direction costs things the import direction does not — a scope that has to be chosen (Chapter 23.3), a control-flow path that has to be reasoned about (23.4), and limits on what can happen to simulation time (23.5). Exporting by reflex buys all of that for a problem that often has a simpler answer.
So this chapter measures the alternatives before the module teaches the mechanism.
The labelling rule from Chapter 22.1 §2 applies unchanged: the export "DPI-C" declaration and its rules are from the LRM; everything measured here is a real C function in a running simulation, through VPI, which is the binding available on this machine.
2. One Job, Three Designs
A C model scans a buffer and finds faults. Measured on sixteen bytes with faults planted at indices 3, 9 and 14.
(a) Return a count. One import, Chapter 22.2's shape.
[SV] count = 3 -- and the positions are gone
calls into C: 1Correct, cheap, and it answers a different question than the one that was asked. The testbench knows there were three faults and nothing about where.
(b) Record in C, fetch each one. Two imports, Chapter 22.7's state.
[SV] positions: 3 9 14
calls into C: 4 (1 scan + 3 fetches)Everything recovered. It costs three extra calls and it looks clumsy — a loop in the testbench pulling results out one at a time.
(c) Report each fault as it is found. The C writes into a SystemVerilog variable from inside the scan — the reverse direction, in its simplest form.
[C] fault 1 at index 3 -- reported from inside the scan
[C] fault 2 at index 9 -- reported from inside the scan
[C] fault 3 at index 14 -- reported from inside the scan
[SV] last reported: fault 3 at index 14
calls into C: 1One call, three reports, and the testbench apparently learns about each fault at the moment it is found. That is the design everybody wants, and §3 is what it actually does.
3. The Finding: Three Writes, One Wake-Up
Design (c) only earns its place if the testbench can see the three reports. So put a process on that variable and count.
always @(fault_hit) begin
seen = seen + 1;
$display(" [SV watcher] woke: fault_hit=%0d fault_pos=%0d", fault_hit, fault_pos);
endMeasured:
[C] fault 1 at index 3 -- reported from inside the scan
[C] fault 2 at index 9 -- reported from inside the scan
[C] fault 3 at index 14 -- reported from inside the scan
[SV] the call returned 3
[SV] fault_hit now reads 3, fault_pos 14
[SV watcher] woke: fault_hit=3 fault_pos=14
[SV] after letting time advance, the watcher woke 1 time(s)
[SV] -- three writes happened; the watcher saw 1 of themOne. The process woke once, and what it saw was fault_hit = 3, fault_pos = 14 — the final state. Faults 1 and 2 were written, and as far as the simulation is concerned they never existed.
The reason is the single most important fact in this module, and Chapter 23.5 measures it directly: nothing in SystemVerilog runs while the C call is in progress. The call is one instant. Three writes landed inside that instant, each overwriting the last, and the first moment any SystemVerilog process got to look, only the third was there.
So design (c) delivers one event out of three — and it delivers it looking exactly like success, since fault_hit = 3 is a plausible count and fault_pos = 14 is a real position.
4. What the Reverse Direction Is Actually For
§3 removes the most common reason people give. Four reasons survive it, and they have a shape in common: each is about running SystemVerilog code, not about letting SystemVerilog processes observe intermediate state.
Invoking testbench infrastructure the C has no other access to. A scoreboard's compare method, a coverage sample, an error issued through the testbench's own reporting so it is counted and filtered like every other error. The C does not want to communicate a value; it wants a piece of SystemVerilog to run.
Reporting through the one mechanism that already exists. A model that prints to stdout has invented a second error channel that nothing filters, counts or fails the test on. Calling the testbench's reporting function puts the model's findings where the rest of the environment already looks.
Letting the environment decide what an event means. The C knows it found a fault at index 9. Whether that is fatal, expected, or interesting depends on the test — and the test is SystemVerilog. Exporting a report_fault(index) moves the policy to where the policy lives.
And genuine callbacks from a model that is driving. When the C side holds the loop — a bus functional model stepping through a transaction — the testbench's hooks have to be callable from inside it.
What is not on that list is anything of the form "so the testbench can watch the value change". §3 measured that.
5. The Misconception: Export So the Testbench Can React as It Happens
"If the model exports a callback, the testbench sees each event as the model reaches it, instead of waiting for the whole scan to finish."
Measured false, in the sharpest form the experiment allows: three writes, one wake-up, and only the final value visible.
Two things make the belief durable.
It is true of ordinary software. A callback in a C program does let the caller act at the moment the callee reaches it, because there is one thread of control and the callback is a real call. The intuition transfers, and the part that does not transfer is that a simulator has many processes and the one calling into C is the only one running.
The measurement looks like success. fault_hit = 3 and fault_pos = 14 are a plausible count and a real index. Nothing is corrupt, nothing is out of range, and a testbench that prints the final state reports something entirely reasonable — which is Module 21 and Module 22's recurring shape, now in the reverse direction.
The accurate position:
A call into C is one instant with the simulator suspended, so no SystemVerilog process can observe anything the C does part-way through it. What a callback into SystemVerilog buys is that SystemVerilog code runs at that point — a scoreboard, a coverage sample, a report. What it does not buy is interleaving, and if the testbench must act on each of several events, the events have to be delivered as several calls.
6. What You Should Be Able to Say Now
- Decide whether before learning how — the reverse direction costs a scope (23.3), a control-flow path (23.4) and time limits (23.5).
- Three designs measured on the same scan: return a count (1 call, positions lost) · record and fetch (4 calls, all recovered) · write back per fault (1 call, 3 writes).
- The finding: a process watching the variable woke once out of three writes and saw only
fault_hit = 3, fault_pos = 14. - Nothing in SystemVerilog runs while a C call is in progress — the call is one instant, so intermediate values never existed for the simulation.
- The fetch loop's extra calls are the feature: each is a separate call, so SystemVerilog gets to run between them.
- What the reverse direction is for: invoking testbench infrastructure · reporting through the existing channel · letting the environment decide what an event means · hooks in a model the C is driving.
- What it is not for: letting a process watch a value change part-way through a call.
- Three questions before exporting: can a return value carry it · can the testbench fetch it · does SystemVerilog code need to run at that point.
- The failure looks like success — a plausible count and a real index.
7. Exercises
- Build the three designs and count the calls into C for each. Which answers the question that was actually asked?
- Put a process on the variable version (c) writes and count its wake-ups against the number of writes. Explain the difference.
- Why are the fetch loop's three extra calls the thing that makes it work? Answer in terms of when SystemVerilog gets to run.
- Version (c) left
fault_hit = 3andfault_pos = 14. Why is that pair particularly hard to notice as wrong? - For an export you have seen in a real environment, work through §4's checklist. Which question did it reach, and by elimination or by reflex?
- Name a piece of testbench infrastructure a C model cannot reach any other way. What would the model have to do instead if it could not call it?
- A model prints its findings to standard output. List three things the environment's own reporting does that the print does not.
- §5 says the intuition transfers from ordinary software. Describe precisely which part does not, and why a simulator is different.
8. Summary
- Whether comes before how — the reverse direction brings scope, control flow and time constraints with it.
- Measured, three designs: count (1 call, positions lost) · record and fetch (4 calls, all recovered) · write-back (1 call, 3 writes).
- The finding: three writes, one wake-up, and only the last value survived.
- A C call is one instant with the simulator suspended — nothing can observe intermediate state.
- The fetch loop's extra calls are what let SystemVerilog run between events.
- The reverse direction is for running SystemVerilog code: scoreboards, coverage, the environment's reporting, hooks in a C-driven model.
- Not for watching a value change inside a call.
- Three questions first: return value · fetch · does SystemVerilog need to run here.
- The failure presents as a plausible number and a real index.
The cases that survive all have the same shape: something in SystemVerilog has to execute at a point the C chooses. That means naming a subroutine on the SystemVerilog side and making it callable — and the first thing to settle is that the two kinds of subroutine it could be are not interchangeable.
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.
