Skip to content
VLSI Mentor

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.

A verification loop drawn as two columns of three. The right column, top to bottom, carries the timed SystemVerilog path: Driver, labelled SystemVerilog; D U T, labelled R T L; and Monitor, labelled SystemVerilog. The left column carries the C plus plus path: Stimulus, labelled C plus plus generator, at the top; Model, labelled C plus plus reference via V P I, in the middle; and Scoreboard, labelled C plus plus comparison, at the bottom. An arrow runs from Stimulus across to Driver carrying transactions, labelled t x n, then down from Driver to D U T carrying pin activity, and down again from D U T to Monitor carrying pin activity. An arrow runs down from Stimulus to the Model carrying the same transactions, also labelled t x n, and down from the Model to the Scoreboard carrying expected results. A final arrow runs from Monitor across to the Scoreboard carrying actual results. The Stimulus, Model and Scoreboard blocks are highlighted because those are the three places C plus plus normally does the work, while the Driver and Monitor stay in SystemVerilog because their job is timed.StimulusC++ generatorDriverSystemVerilogDUTRTLMonitorSystemVerilogModelC++ ref, via VPIScoreboardC++ comparisontxnpinspinstxnexpectedactual12
Figure 1 — the map this module works from. The two blocks that touch pins stay in SystemVerilog because their job is timed; the three that compute — stimulus, reference model and scoreboard — are where C++ is the usual answer. The same transactions reach both the driver and the model, and the scoreboard compares what the model expected against what the monitor observed.

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

wherewhat it is responsible forchapter
reference modelwhat the answer should be, from the specification27.3
golden modelthe same, with independence from the RTL as an explicit property27.4
predictorthe expected state after a transaction, not just the output27.7, 27.8
checker / scoreboardcomparing expected against actual, and reporting usefully27.6
stimulus generatorproducing vectors reproducibly, seed recorded27.5
post-processinglogs, coverage, waveform summaries, regression triageModule 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.

mechanismwhat it ishere
DPI-Cthe standard SystemVerilog↔C interface, import "DPI-C"rejected
VPIthe older procedural interface, C functions registered as system tasksworks
no couplingthe simulator writes files; C++ reads them afterwardsalways works

DPI-C is not available in Icarus Verilog 13.0. Not slow, not partial — a syntax error:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  == 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 produced

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  byte  C++ model returns
     0  0
    17  17
    34  0
    51  51
    68  34
    85  17
    [C++ model] distinct byte values seen: 6

Those 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  == 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.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.o

Three 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
vvp -M <dir-containing-the-vpi> -m <module-name> design.vvp

Getting -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 modelledconsequence for the comparison
timing and latencythe model cannot catch a protocol timing violation — that is SVA's job
arbitration between requestersthe model cannot predict which requester wins, only what each would get
ECC and error injection pathsa corrupted-data case compares against an undefined expectation
clock-domain crossing behaviourthe model has one notion of order; the hardware has two
power states and retentionafter a power event the model's state is stale, not wrong
X and ZC++ 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 questionthe answermeasured
what work belongs in C++algorithmic, untimed, reusable§1
what stays in SystemVerilogpins, time, concurrency, constraints, temporal assertions§3
the touch pointsmodel, golden model, predictor, checker, stimulus, post-processing§2
reference against goldengolden adds independence from the RTL§2, 27.4
is DPI-C available hereno — syntax error, exit 2, no artefact§4
is VPI available hereyes, and it carries a C++ class with STL state§4
what standard does iverilog-vpi useC++11 (Show<201103>)§5
how do you get C++20 anywaycompile the .o yourself, hand it to the wrapper§5
what does the third mechanism costno in-simulation reaction; everything else is easier§4
what can a C++ model never modelX and Z, timing, arbitration, CDC, power§6
why keep the model simulator-freethree 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 -fPIC and 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 .vvp produced — while VPI works and carries a real C++ class with an std::map holding state across calls.
  • That iverilog-vpi compiles at C++11 (Show<201103>), and that the fix is to compile the object yourself with -std=c++20 -fPIC and 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, iverilog exits 2, and no .vvp is 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::map member, 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-vpi compiles 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.