C++ Part II · Module 27 · C++ for Semiconductor Verification
Where C++ Appears in a Verification Stack
Part II is about the job rather than the language, and it opens with a map because the commonest mistake here is not writing bad C++ — it is writing C++ for a problem that belonged in SystemVerilog, or the reverse. There are five or six places where C++ genuinely earns its position in a verification flow, every one of them characterised by the same two properties: the work is algorithmic rather than timed, and it needs to be fast or portable or reusable outside the simulator. This chapter names those places, says what each one is actually responsible for, and then does the thing a map chapter usually skips: it finds out which integration mechanisms work on the toolchain in front of us. That part produced a clear answer rather than a survey. The standard route, DPI-C, is rejected by this simulator with a syntax error and produces no executable at all; the older route, VPI, not only works but carries an ordinary C++ class with an STL container holding state across calls. Both results are measured, and the second one is what makes the rest of this module demonstrable rather than hypothetical.
Advanced20 min readC++Part IIVerificationModeling
Part II · Module 27 · Chapter 27.1 · Where C++ Appears in a Verification Stack
1. The Shape of the Problem
A verification environment has to do two completely different kinds of work, and almost every sensible decision in this module follows from keeping them apart.
Timed work. Driving pins on a clock edge, sampling a bus with the right skew, waiting for a handshake, running four agents concurrently, randomising with constraints inside a scenario. This is what SystemVerilog and UVM exist for, and the language has dedicated machinery — clocking blocks, fork/join, assertions, constraint solving — that C++ does not have and should not try to imitate.
Algorithmic work. Given this input, what should the answer be? Compute a checksum, model a register map's side effects, maintain a sparse memory, decode a frame, predict the state after a transaction, compare two streams, post-process ten gigabytes of log. None of this involves time at all.
C++ belongs on the second list. Not because it is a better language, but because that work is a function from inputs to outputs, and a compiled general-purpose language is good at functions: it is fast, it has containers and algorithms, it can be unit-tested without a simulator, and the same code can run inside the regression, inside a standalone validation harness, and inside the firmware team's build.
The diagram below is the map. The blocks marked in brand colour are the ones where C++ is the usual answer.
Read the loop and the division is visible: the two blocks that touch pins stay in SystemVerilog, and the three that compute stay in C++. Everything else in this module is detail on those three.
2. The Six Touch Points
| where | what it is responsible for | chapter |
|---|---|---|
| reference model | what the answer should be, from the specification | 27.3 |
| golden model | the same, with independence from the RTL as an explicit property | 27.4 |
| predictor | the expected state after a transaction, not just the output | 27.7, 27.8 |
| checker / scoreboard | comparing expected against actual, and reporting usefully | 27.6 |
| stimulus generator | producing vectors reproducibly, seed recorded | 27.5 |
| post-processing | logs, coverage, waveform summaries, regression triage | Module 22, 23 |
Three of those deserve a sentence on what distinguishes them, because the words get used interchangeably and they are not the same thing.
A reference model says what the answer is. Its authority is the specification. It may be slow, it may not model timing, and it must be deterministic where the specification is deterministic — Chapter 27.3 is the full list of obligations.
A golden model is a reference model with one extra property: independence. It was written without reading the RTL. That sounds like a process detail and it is the difference between a regression that can fail and one that cannot — Chapter 27.4 measures exactly that, and the measurement is the strongest in this module.
A predictor models state rather than output. A register map after a write, a memory after a burst, a FIFO's occupancy. The comparison is then against the DUT's internal or software-visible state, which is why Chapters 27.7 and 27.8 are about modelling registers and memories rather than functions.
And the last row is not a lesser one. Regression triage, log parsing and coverage merging are where a lot of real C++ in this industry lives, and Modules 22 and 23 already built those tools.
3. What C++ Is Not For Here
Equally important, and the source of more wasted effort than anything else in Part II.
Not for driving pins. You can reach signals through VPI, and people do, and it is almost always worse than a few lines of SystemVerilog: you lose the clocking-block semantics that solve the sample-versus-drive race, and you take on timing code in a language with no notion of time.
Not for concurrency inside the testbench. Module 26 was nine chapters on how hard threads are. A SystemVerilog fork/join is cooperative, deterministic within a time step, and free of every bug in that module. Using std::thread to model concurrent agents is choosing the hardest available tool.
Not for constrained randomisation. SystemVerilog has a constraint solver. C++ has std::uniform_int_distribution and your own rejection loop — fine for the independent-field case in Chapter 27.5, hopeless for "these nine fields must satisfy these five relations".
Not for assertions about time. a must rise within three cycles of b is a temporal property, and SVA (or PSL, which Module 22 of the I²C track found actually runs on nvc) expresses it directly.
The rule that falls out: if the requirement contains the word "cycle", "clock", "concurrently" or "within", it is not a C++ job. Chapter 27.10 turns that instinct into a decision procedure with measurements behind it.
4. The Mechanisms, and Which Ones Work Here
Now the part that decides whether any of this is demonstrable. There are three ways to get C++ and a simulator into the same process, and on this machine they do not all work.
| mechanism | what it is | here |
|---|---|---|
| DPI-C | the standard SystemVerilog↔C interface, import "DPI-C" | rejected |
| VPI | the older procedural interface, C functions registered as system tasks | works |
| no coupling | the simulator writes files; C++ reads them afterwards | always works |
DPI-C is not available in Icarus Verilog 13.0. Not slow, not partial — a syntax error:
== 27.1 DPI-C is not available in Icarus Verilog 13.0 ==
iverilog exit=2
dpi_tb.sv:2: syntax error
dpi_tb.sv:2: error: Invalid module item.
no artefact was producedThe exit status is 2 and no .vvp file is produced at all. One detail about how that was measured, because it matters for your own gates: the status was read without a pipe. Through | head it reads 0 — the Icarus trap recorded repeatedly in the Linux and Bash track, where a build that failed looks like a build that passed.
This is a property of this simulator rather than of DPI. On a commercial simulator DPI-C is the normal route and is what you should reach for first; everything in this module about what to model transfers unchanged, and only the glue differs.
VPI works, and it carries real C++. This is the result that makes Part II demonstrable. A VPI module registers a system function, and the body is an ordinary class:
class CrcModel { // an ordinary C++ class, not a C shim
public:
unsigned step(unsigned byteIn)
{
crc_ = ((crc_ << 1) ^ byteIn) & 0xFFu;
++counts_[byteIn];
return crc_;
}
std::size_t distinctBytes() const { return counts_.size(); }
private:
unsigned crc_ = 0;
std::map<unsigned, long> counts_; // STL state across calls
};Registered as $crc_step and $crc_distinct and called from a plain Verilog loop:
byte C++ model returns
0 0
17 17
34 0
51 51
68 34
85 17
[C++ model] distinct byte values seen: 6Those are six calls into a C++ object that kept its std::map between them, and the sixth line is the model reporting on its own accumulated state. I checked all six returned values by hand against crc = ((crc << 1) ^ byte) & 0xFF before believing the transcript — 0, 17, 0, 51, 34, 17 are correct in order.
And the third mechanism is the one people forget. The simulator dumps transactions to a file; a standalone C++ program reads them and produces the verdict. No glue, no co-simulation, the model is trivially unit-testable, and the whole thing runs in CI without a simulator licence. It costs you the ability to react within the simulation — so it cannot drive stimulus or stop on first error — and for a great many checkers that is not a real loss. Chapter 23.7 built exactly this shape for simulation logs.
5. Building the C++ Side, Which Is Not Quite Obvious
The wrapper that builds a VPI module has a constraint worth knowing before it costs you an afternoon. iverilog-vpi compiles at C++11. Probed by making the compiler print the value in an error message:
== 27.1 what C++ standard iverilog-vpi compiles at ==
Show<201103>201103 is C++11. So a model written in the C++ this track has spent twenty-six modules on — std::optional, structured bindings, if constexpr, designated initialisers, concepts — does not build through the wrapper. I hit it immediately and for real: an aggregate initialiser with a default member initialiser was rejected as "no matching constructor for initialization", which is a confusing diagnostic until you realise the standard is the problem.
The fix is a two-step build: compile the object yourself with whatever standard you want, then let the wrapper do only the linking.
clang++ -std=c++20 -O2 -fPIC -I/opt/homebrew/Cellar/icarus-verilog/13.0/include/iverilog \
-c model.cpp -o model.o
iverilog-vpi --name=model model.oThree details in that command line are load-bearing. -fPIC, because the result is a shared object. The include path to the simulator's own vpi_user.h, which lives inside the Cellar prefix rather than on the default search path. And handing iverilog-vpi a .o rather than a .cpp, which is what stops it choosing the standard for you. Every measured result in this module's co-simulation chapters was built that way.
Then it is loaded at run time rather than linked into the simulation:
vvp -M <dir-containing-the-vpi> -m <module-name> design.vvpGetting -M wrong produces Unable to find a 'model.vpi' module on the search path followed by "System task/function $x() is not defined by any module" and Program not runnable — which looks like a Verilog problem and is a path problem.
6. What the Model Does Not Model
A reference model is always a partial description, and the places it stops are the places a comparison silently passes. So the obligation is to write the exclusions down next to the model, not to try to eliminate them.
Typical exclusions, each of which is legitimate and each of which must be explicit:
| not modelled | consequence for the comparison |
|---|---|
| timing and latency | the model cannot catch a protocol timing violation — that is SVA's job |
| arbitration between requesters | the model cannot predict which requester wins, only what each would get |
| ECC and error injection paths | a corrupted-data case compares against an undefined expectation |
| clock-domain crossing behaviour | the model has one notion of order; the hardware has two |
| power states and retention | after a power event the model's state is stale, not wrong |
| X and Z | C++ has no third value; an unknown becomes a definite 0 or 1 |
The last row is worth dwelling on because it is the one that surprises people. A C++ model has no way to represent an unknown. The C track's Module 25 measured the consequence directly: reading a signal that is X through an integer-valued interface discards the X and yields a number. So a DUT that drives X and a model that computes 0 can agree, and the comparison passes on a bug that a SystemVerilog checker would have caught immediately. If X-propagation matters in your design, that check does not belong in the C++ model.
7. One Model, Three Consumers
The argument for C++ that survives contact with a real project is reuse, and it is worth being concrete about who the consumers are.
The regression, through whatever glue §4 settled on.
A standalone validation harness that runs the model against known vectors with no simulator at all — Chapter 27.11's first step, and the thing that proves the model before it is trusted. This is the consumer that justifies keeping the model free of simulator types: a model whose interface is std::uint8_t and std::vector can be unit-tested in milliseconds, while one whose interface is s_vpi_value cannot be tested without a simulation.
Software and firmware, which often need the same register map, the same packing code and the same checksum. A model that is a plain library with no simulator dependency can be handed over; one that is a VPI module cannot.
That gives a structural rule used throughout this module: the model is a library, and the glue is a thin separate layer that translates. In the experiments here the entire VPI layer is the argument-fetching and value-returning code, and the model proper never mentions VPI at all. Chapter 24.9's project organisation is the same separation applied to a build.
8. The Table
| the question | the answer | measured |
|---|---|---|
| what work belongs in C++ | algorithmic, untimed, reusable | §1 |
| what stays in SystemVerilog | pins, time, concurrency, constraints, temporal assertions | §3 |
| the touch points | model, golden model, predictor, checker, stimulus, post-processing | §2 |
| reference against golden | golden adds independence from the RTL | §2, 27.4 |
| is DPI-C available here | no — syntax error, exit 2, no artefact | §4 |
| is VPI available here | yes, and it carries a C++ class with STL state | §4 |
what standard does iverilog-vpi use | C++11 (Show<201103>) | §5 |
| how do you get C++20 anyway | compile the .o yourself, hand it to the wrapper | §5 |
| what does the third mechanism cost | no in-simulation reaction; everything else is easier | §4 |
| what can a C++ model never model | X and Z, timing, arbitration, CDC, power | §6 |
| why keep the model simulator-free | three consumers: regression, harness, firmware | §7 |
9. What to Actually Do
- Split the work by whether it involves time. Timed work in SystemVerilog, functions in C++.
- Name which of the six touch points you are building before choosing a mechanism — the roles have different obligations.
- Try DPI-C first on a commercial simulator, and check what your simulator actually supports by compiling a two-line test rather than reading a release note.
- Read a build's exit status directly, never through a pipe. Icarus reports success through
| head. - On Icarus, use VPI, and build the model object yourself with
-std=c++20 -fPICand the simulator's include path, handing the wrapper only a.o. - Consider the file-based flow seriously. No glue, unit-testable, no licence needed in CI.
- Keep the model a plain library whose interface uses standard types, with the simulator glue in a separate thin layer.
- Write down what the model does not model, especially X/Z, timing and arbitration — those are the comparisons that pass for the wrong reason.
- Do not model pins, concurrency, constraints or temporal properties in C++.
10. Mistakes Worth Making Once
Choosing the mechanism before the model. The glue is a day; the model's authority is the whole regression.
Assuming DPI-C is available. Here it is a syntax error with exit 2 and no artefact.
Reading iverilog's status through a pipe. A failed build reports success.
Writing the model against C++11 because the wrapper demanded it. Compile the object yourself.
Forgetting -fPIC or the simulator's own include path for vpi_user.h.
Getting vvp -M wrong and debugging a Verilog error that is really a path error.
Driving pins from C++. You have reimplemented a clocking block, worse.
Modelling concurrent agents with std::thread. Module 26 is what that costs.
Letting the model depend on simulator types. You have just made it untestable without a simulation.
Comparing a value that may be X. The X is discarded on the way in and the comparison passes.
11. Interview Relevance
The frozen curriculum marks this High, and the question is usually open: where would you use C++ in a verification environment? A list of nouns — reference model, scoreboard, stimulus — is the expected answer. What distinguishes a strong one is the criterion behind the list: C++ takes the untimed algorithmic work, because that work is a function from inputs to outputs, and SystemVerilog keeps anything involving clocks, concurrency, constraints or temporal properties, because it has dedicated machinery for those and C++ does not.
The follow-up worth preparing is the difference between a reference model and a golden model, since the words are often used loosely. The answer is independence: a golden model was written from the specification without reading the RTL, and the reason that matters is that a model derived from the design reproduces the design's mistakes, so the comparison passes. Chapter 27.4 has the number to quote.
If the conversation turns to mechanisms, DPI-C is the right first answer, with the caveat that support varies — on the open-source simulator used here it is a syntax error and VPI is the working route — and that a file-based flow, where the simulation dumps transactions and a standalone C++ program judges them, is often the best engineering choice because the model stays unit-testable and needs no licence in CI. Mentioning that a C++ model cannot represent X or Z, so X-propagation checks must stay in SystemVerilog, is the kind of limit that shows you have built one.
12. What You Should Be Able to Say Now
- That the split is timed work in SystemVerilog, untimed algorithmic work in C++, and why.
- The six touch points: reference model, golden model, predictor, checker/scoreboard, stimulus generator, post-processing.
- That a golden model is a reference model plus independence from the RTL, and that the distinction decides whether a regression can fail.
- That a predictor models state, not just output.
- That DPI-C is unavailable on Icarus Verilog 13.0 —
syntax error, exit 2, no.vvpproduced — while VPI works and carries a real C++ class with anstd::mapholding state across calls. - That
iverilog-vpicompiles at C++11 (Show<201103>), and that the fix is to compile the object yourself with-std=c++20 -fPICand the simulator's include path. - That an Icarus build's exit status must not be read through a pipe.
- That the file-based flow costs you in-simulation reaction and buys unit-testability and no licence dependency.
- That a C++ model cannot represent X or Z, cannot model timing, arbitration, CDC or power — and that these exclusions must be written down.
- That the model should be a plain library with the simulator glue in a thin separate layer, because it has three consumers.
13. Exercises
Exercise 1 — probe your own simulator. Write a two-line import "DPI-C" file and compile it. Record the exit status without a pipe, and whether an artefact appeared.
Exercise 2 — the C++ standard your wrapper uses. Make the compiler print __cplusplus through a template error, then build the same model at C++20 using the two-step route.
Exercise 3 — a stateful model across the boundary. Register a system function whose body is a class with an STL member, call it six times from a Verilog loop, and verify every returned value by hand before believing the transcript.
Exercise 4 — the path error. Deliberately get vvp -M wrong and read the three messages it produces, so you recognise them later.
Exercise 5 — the file-based flow. Dump transactions from a simulation to a file and write a standalone C++ program that judges them. Note what you can no longer do.
Exercise 6 — the exclusion list. For a block you know, write the list of things a C++ model of it would not model, and mark which checks therefore have to live in SystemVerilog.
14. Summary
- A verification environment does timed work and algorithmic work, and C++ belongs to the second. Pins, clocks, concurrency, constraints and temporal assertions stay in SystemVerilog, which has machinery for them.
- The six touch points are the reference model, the golden model, the predictor, the checker or scoreboard, the stimulus generator, and log and coverage post-processing.
- A golden model is a reference model plus independence from the RTL — a process property that decides whether the regression is capable of failing at all.
- Measured here: DPI-C is not available on Icarus Verilog 13.0.
import "DPI-C"is a syntax error,iverilogexits 2, and no.vvpis produced. The status has to be read without a pipe, or the failure reads as success. - VPI works, and it carries real C++: a class with an
std::mapmember, registered as a system function, returned the six hand-checked values 0/17/0/51/34/17 and reported its own accumulated state from inside the simulation. iverilog-vpicompiles at C++11 (Show<201103>), so build the object yourself —-std=c++20 -O2 -fPIC -I<simulator include> -c— and hand the wrapper only the.o.- The file-based flow is a real third option: no glue, no licence in CI, a unit-testable model, at the cost of not being able to react inside the simulation.
- The mechanism is the least important decision in the module. It is replaceable in a day; a model that derives its answers from the RTL invalidates every result it ever produced.
- Write down what the model does not model — X and Z above all, since C++ has no third value and the unknown is silently converted on the way in.
Chapter 27.2 starts on the modelling proper, with the smallest unit: one transaction, and an invariant that cannot be violated.
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.
