Skip to content
VLSI Mentor

C++ Part I · Module 1 · Why C++ Matters in Semiconductor Engineering

C++ in a Verification Flow

Chapter 1.5 listed verification as one of five places C++ appears. It earns a chapter of its own for two reasons: it is where most readers of this curriculum will meet C++ professionally, and its architecture is the least obvious of the five. A self-checking testbench is not a SystemVerilog program with a model bolted onto the side. It is a system with two genuinely different halves — one that deals in signals, clock edges and concurrency, and one that deals in data and algorithms — joined by a seam that somebody has to design. This chapter draws that structure, then does something the literature is usually vague about: it states precisely what a reference model, a predictor, a checker and a data generator each are, because those four words get used interchangeably and they are not the same thing. It ends with a working CRC-32 reference model that passes all four published check values, and with the reason that matters more than the code.

Foundation17 min readC++Part IDVReference Models

Part I · Module 1 · Chapter 1.6 · C++ in a Verification Flow

1. The Problem a Testbench Actually Has

A testbench drives inputs into a design and watches what comes out. That part is uncontroversial. The hard part is the next sentence: how does it know whether what came out was right?

There are only three answers, and two of them do not scale.

A person looks at the waveform. This works for the first hour of a new block and stops working immediately after. It does not survive a regression of ten thousand tests run overnight, and it cannot be trusted at three in the morning.

The expected answers are written down in advance. For each stimulus, a file records the expected output. This is real and it is used — Module 23.8 covers reading such files. But the expected values have to come from somewhere, they go stale every time the specification moves, and they do not work at all with randomised stimulus, because nobody wrote down the answer for a transaction the generator invented at run time.

Something computes the expected answer independently. Give the same stimulus to the design and to an independent implementation of what the design is supposed to do, then compare. This is the only one of the three that survives randomisation, scales to a full regression, and keeps working when the stimulus is generated rather than authored.

That third answer is called self-checking, and the independent implementation is where C++ comes in.

2. Two Halves, One Seam

Before the structure, the division that explains it.

A testbench does two kinds of work, and they have almost nothing in common:

Signal-level work. Driving a bus according to a protocol. Waiting for a clock edge. Holding a signal for the right number of cycles. Watching several interfaces at once. Randomising a transaction under constraints. Recording functional coverage. All of this is about time and concurrency, and it is connected to the design by wires.

Algorithmic work. Given this input, what should the output be? Computing a checksum, decoding a header, deciding which cache line a replacement policy would evict, working out what a register's value becomes after a write to a field with side effects. All of this is about data, and none of it has anything to do with clocks.

SystemVerilog is built for the first kind. It has a timing model, a concurrency model, constrained randomisation, functional coverage, and a direct connection to the design's ports. Nothing else in the toolchain can do that job and C++ cannot come close.

C++ is good at the second kind — and, per Chapter 1.5, specifically good when the algorithm is substantial, already exists in C, or has to run fast enough not to become the bottleneck.

So a serious testbench has a seam in it. Where that seam falls is an architectural decision, and the rest of this chapter is about making it deliberately rather than by accident.

3. The Structure

Here is the canonical shape of a self-checking testbench, reduced to the four parts that matter. Everything else — agents, drivers, sequencers, the whole UVM vocabulary if your project uses it — is elaboration on this.

A self-checking testbench in four blocks. In the left column, top to bottom: stimulus, written in SystemVerilog; the design under test, which is RTL; and compare, also SystemVerilog. An arrow runs down from stimulus to the design, labelled drives, and another down from the design to compare, labelled actual. Beside the design, in the right column, sits the reference model, written in C plus plus. An arrow runs from stimulus across to the reference model, labelled same input, and another from the reference model down to compare, labelled expected. The stimulus therefore reaches both the design and the model, and compare receives one answer from each.StimulusSystemVerilog ·drives the busDesign undertestRTL — the thingbeing judgedCompareSystemVerilog ·expected vs actualReference modelC++ · the expectedanswerdrivesactualsame inputexpected12
Figure 1 — the one idea behind every self-checking testbench: the same stimulus goes to two places, and the two answers are compared. The design (grey) is RTL. The reference model (lower) is the independent implementation of what the design should do, and is where C++ typically sits. Stimulus and comparison stay in SystemVerilog — one drives real signals in time, the other lives where transactions are collected. A disagreement means exactly one of three things is wrong: the design, the model, or your reading of the specification.

Two things about that picture are worth saying out loud.

The model is not in the path. Data does not flow through the reference model on its way to the design. The model is a parallel computation fed by the same stimulus, and if you deleted it the design would behave identically. A model that the design depends on is not a model; it is part of the design.

A mismatch does not mean the design is broken. It means one of three things is wrong: the design, the model, or your reading of the specification. Treating a mismatch as automatically a design bug is how teams waste days, and Module 27.9 is a whole chapter on resolving it quickly. The honest framing is that the comparison found a disagreement, and disagreement is information about the pair.

4. Four Roles That Are Not the Same Thing

Here is the part the literature is vague about. These four words are used interchangeably in conversation and they describe different components with different obligations.

Reference model. Computes what the output should be, from the input, independently of the design. Its authority is the specification. Its defining property is independence: it must be possible for it to disagree with the design, which means it must not be derived from the design's source, its author's reading of the design's source, or — most insidiously — a shared wrong assumption.

Predictor. A reference model that also carries state, so that it can say what should happen next given what has happened so far. A stateless CRC function is a reference model; something that tracks a register block's contents across a sequence of writes so it can predict the result of the next read is a predictor. The distinction matters because state means order matters, and order is where predictors go wrong.

Checker. Decides whether something is legal, not what it should be. A protocol checker asserts that a handshake never drops mid-transfer, that a response arrives within a bound, that two conflicting requests are not outstanding at once. A checker can fire without any reference model existing, because it is validating rules rather than comparing values.

Data generator. Produces stimulus or payload — a packet with valid field combinations, a traffic pattern, a test vector sequence. It is on the input side, and it has a property the other three do not: it must be reproducible, because a failure you cannot replay is a failure you cannot debug.

RoleAnswers the questionCarries state?Authority
Reference modelWhat should the output be?NoThe specification
PredictorWhat should happen next, given history?YesThe specification, plus observed history
CheckerIs this legal?SometimesThe protocol's rules
Data generatorWhat should we try?Usually (a seed)The space of valid inputs

5. Why Reach Outside the HDL at All?

A reasonable question, and it deserves a reasoned answer rather than "C++ is faster". There are four distinct motivations, and they apply independently.

The algorithm already exists. Compression, encryption, error correction, floating-point arithmetic, video and audio codecs, signal processing — these are frequently specified by a C reference implementation, or accompanied by one in the standard. Re-implementing it in SystemVerilog creates a second opportunity to misread the specification, in the one component whose entire job is to be right about the specification. Using the existing implementation removes that risk rather than adding to it.

Independence is easier to achieve. This one is subtle and it is the strongest argument. If the same engineer writes the RTL and the reference model in the same language at the same time, the model tends to inherit the RTL's assumptions — including the wrong ones. A model written in a different language, from the specification, often by a different person, is structurally more likely to disagree where disagreement matters. Module 27.4 is devoted to this, because a model that reproduces the design's own mistake makes a whole regression worthless while reporting success.

Speed. A reference model runs at least once per transaction, and a regression can push hundreds of millions of transactions. A model slower than the design it checks becomes the bottleneck of the campaign.

Reuse. The same C++ model can serve a block-level testbench, a subsystem testbench, an architectural model, and — unchanged — a post-silicon validation program. A model written inside one SystemVerilog environment usually serves only that environment.

Notice that none of those four is "SystemVerilog cannot express algorithms". It can. The arguments are about where the algorithm is most clearly written, most likely to be independently correct, fast enough, and reusable.

6. What Must Not Move

The counterpart, and it is just as important, because the failure mode here is an engineer who finds C++ congenial and tries to pull the whole testbench into it.

ResponsibilityStays in SystemVerilogWhy it cannot move
Driving and sampling the design's portsYesIt is connected by wires and governed by clock edges
Simulation time, waiting, cycle countingYesC++ has no notion of simulation time at all
Concurrency across interfacesYesThe simulator's scheduler owns it
Constrained randomisationYesBuilt into the language and its solver
Functional coverageYesBuilt in, and integrated with the coverage database
Assertions on signal behaviourYesThey are written against signals over time
Computing expected valuesEitherThis is the seam

The row that engineers try hardest to move is the second one, and it is worth being blunt about it: a function called from SystemVerilog across the boundary cannot consume simulation time. It runs, it returns, and no time has passed. A model that needs to wait for something is not a function; it is a process, and processes live on the SystemVerilog side. Module 28.4 and 28.11 cover the consequences precisely.

7. What a Reference Model Looks Like

Enough architecture. Here is one, complete and real.

CRC-32 is a good first example because it is genuinely used — Ethernet frames, many protocol payloads — and because it has published check values, which turns out to be the more important property.

Azvya Education Pvt. Ltd.VLSI Mentor
ch16_crc.cpp — complete, compiles clean at -std=c++17 -Wall -Wextra -Wpedantic
#include <cstddef>
#include <cstdint>

/* A reference model in its purest form: inputs in, expected result out,
   no state, no simulator, nothing to mock. */
std::uint32_t crc32(const std::uint8_t* data, std::size_t len)
{
    std::uint32_t crc = 0xFFFFFFFFu;

    for (std::size_t i = 0; i < len; ++i) {
        crc ^= data[i];
        for (int bit = 0; bit < 8; ++bit) {
            const bool lsb = (crc & 1u) != 0u;
            crc >>= 1;
            if (lsb) {
                crc ^= 0xEDB88320u;          // reversed CRC-32 polynomial
            }
        }
    }

    return crc ^ 0xFFFFFFFFu;
}

Almost everything in that function is Module 3 and Module 4 material — fixed-width integer types, bitwise ^ and &, a right shift, two loops. You will be able to write it yourself after Module 5. Three details are worth pointing out now, because each one is a decision rather than a formality:

  • std::uint32_t, not unsigned. A CRC-32 is thirty-two bits by definition. Chapter 1.3 §5 showed that unsigned and long are implementation-defined widths; a value whose width is part of the specification should be declared with a type that says the width. Module 3.4.
  • const bool lsb is captured before the shift. The bit being tested is destroyed by crc >>= 1, so the order is load-bearing. This is the sort of detail a reference model lives and dies by.
  • The parameters are a pointer and a length. The length travels separately because, as Module 9.2 explains, an array in C++ decays to a pointer and carries no size. Nothing about that is C++-specific; it is the interface every byte-oriented model has.

Now the part that matters more than the function.

8. A Model You Have Not Checked Is Not an Oracle

A reference model is the thing a regression trusts. If it is wrong, every test that passes is meaningless and every test that fails sends someone to debug a design that was correct. So the question "is the model right?" cannot be answered by the model's author being careful.

CRC-32 has published check values. Use them:

Azvya Education Pvt. Ltd.VLSI Mentor
ch16_crc.cpp — the self-check, same program
const Vector vectors[] = {
    { "",          0x00000000u },
    { "a",         0xE8B7BE43u },
    { "123456789", 0xCBF43926u },
    { "The quick brown fox jumps over the lazy dog", 0x414FA339u },
};

int failures = 0;

for (const Vector& v : vectors) {
    std::size_t len = 0;
    while (v.text[len] != '\0') { ++len; }

    const std::uint32_t actual =
        crc32(reinterpret_cast<const std::uint8_t*>(v.text), len);

    if (actual != v.expected) { ++failures; }
    /* ... reporting omitted here; see the output below ... */
}

Expected output:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
pass  expected=0x00000000  actual=0x00000000  len=0
pass  expected=0xe8b7be43  actual=0xe8b7be43  len=1
pass  expected=0xcbf43926  actual=0xcbf43926  len=9
pass  expected=0x414fa339  actual=0x414fa339  len=43
all vectors pass

Four vectors, all passing, on the machine used to write this chapter.

Three properties of that exercise are the real content of this section:

The check is independent of the design. Those four expected values did not come from the RTL, from simulation, or from the model's author. They are published check values for the algorithm, which means passing them is evidence about the specification, not evidence that two implementations agree with each other.

The check needs no simulator. This program compiles and runs in under a second on a laptop. No licence, no testbench, no design. A model with this property can be debugged in a loop measured in seconds rather than minutes, which changes how thoroughly it gets debugged.

The empty-input case is in the list on purpose. len == 0 exercises the path where neither loop body executes, and the expected result — 0x00000000, from the initial value XORed with the final value — is easy to get wrong and easy to never test. Boundary cases in a reference model deserve the same attention as boundary cases in a design.

9. The Seam Has a Cost

One honest note, since every diagram in this chapter shows an arrow crossing between a SystemVerilog box and a C++ box. That arrow is not free, and knowing its cost is part of deciding where to put the seam.

Data has to be converted. A SystemVerilog type and a C++ type are two descriptions of storage, and moving a value between them is a defined translation, not a reinterpretation. Widths, signedness and array layout all have rules. Module 28.7 and 28.8.

Four-state information is lost. SystemVerilog values can be X or Z; C++ has no such states. A value crossing the boundary is reduced to two-state, and what your model does about that is a decision you must make deliberately rather than discover. Module 28.7.

Somebody owns the memory. When a pointer crosses, the question "whose storage is this, and how long does it stay valid?" has an answer, and getting it wrong produces corruption rather than an error message. This is the chapter-1.4 ownership problem in its sharpest form. Module 28.10.

Crossing has a per-call cost. It is small, and it is multiplied by the number of transactions in a regression. A model called once per byte rather than once per packet can measurably slow a campaign.

None of that is a reason to avoid the boundary — the four motivations in §5 routinely outweigh it. It is a reason to cross it at a coarse granularity: hand the model a whole transaction, not a byte at a time. That principle is worth carrying for seventeen modules until Module 28 gives you the mechanics.

10. What You Should Be Able to Say Now

  1. A testbench's hard problem is knowing whether the output was right. Only self-checking — computing the expected answer independently — survives randomised stimulus and a full regression.
  2. A testbench does two unrelated kinds of work: signal-level (time, concurrency, the interface) and algorithmic (what the answer should be). The seam between them is an architectural decision.
  3. The canonical structure: one stimulus reaches both the design and the reference model, and the two answers are compared.
  4. The model is not in the data path. Delete it and the design behaves identically.
  5. A mismatch means the design, the model, or your reading of the specification is wrong — three candidates, not one.
  6. Reference model: what should the output be, stateless, authority is the specification. Predictor: a reference model with state, so order matters. Checker: is this legal, authority is the protocol's rules. Data generator: what should we try, must be reproducible.
  7. Each role has its own failure mode: the model agreeing while both are wrong; the predictor losing synchronisation; the checker never firing; the generator missing cases or being unreplayable.
  8. Four reasons to reach outside the HDL: the algorithm already exists, independence is easier to achieve, speed, and reuse. "SystemVerilog cannot do algorithms" is not among them.
  9. What cannot move: ports, simulation time, concurrency, randomisation, coverage, signal assertions. A function called across the boundary cannot consume simulation time.
  10. A reference model is independently testable with no simulator. CRC-32 passed all four published check values in under a second.
  11. A model checked only against the design can share the design's mistake, and then the regression reports success on a broken chip. External vectors are the cheapest defence.
  12. The seam costs data conversion, four-state information, an ownership decision and a per-call overhead — so cross it per transaction, not per byte.

11. Exercises

Exercise 1 — name the role. For each, say which of §4's four roles it is, and name its characteristic failure mode: (a) a component that asserts a response always arrives within 16 cycles; (b) a function returning the expected ECC syndrome for a 64-bit word; (c) a component tracking a register block's contents so it can predict the next read; (d) a component producing random but legal packet headers.

Exercise 2 — place the seam. A team is verifying a block that receives Ethernet frames, strips the header, decompresses the payload with an algorithm specified by a published C implementation, and emits the result. Decide what belongs on each side of the seam, and justify each placement with one of §5's four motivations or §6's table.

Exercise 3 — explain the shared-mistake failure. In your own words, describe how a reference model and a design can agree on every transaction while both being wrong, and state what the scoreboard reports while that is happening. Then give the defence from §8.

Exercise 4 — justify the empty case. §8 includes "" in the vector list. Explain what path it exercises, why 0x00000000 is the right expectation, and why this is the vector most likely to be missing from a hand-written suite.

Exercise 5 — argue against a proposal. A colleague proposes moving the bus driver into C++ "so the whole testbench is in one language". Using §6, give the single decisive technical objection — one sentence — and then one secondary one.

Exercise 6 — reason about granularity. §9 recommends crossing the seam once per transaction rather than once per byte. For a model processing 1,500-byte Ethernet frames, explain what changes between the two choices, and name one thing other than speed that the coarse choice also improves.

Exercise 7 — read the order dependence. In §7's crc32, const bool lsb is captured before crc >>= 1. Explain what would happen if those two lines were swapped, and say whether the compiler would warn you. Then say how the vector suite in §8 would behave.

12. Summary

  • Self-checking is the only answer that scales. Eyeballing waveforms and pre-written answer files both break under randomised stimulus and regression volume.
  • Two halves, one seam. Signal-level work — time, concurrency, the interface — is SystemVerilog's. Algorithmic work is where C++ belongs. Placing the seam is a design decision.
  • The canonical structure sends one stimulus to both the design and the model and compares two answers. The model is parallel to the data path, never in it.
  • A mismatch has three suspects: design, model, specification reading.
  • Four roles, precisely: reference model (stateless, what should the output be), predictor (stateful, what happens next), checker (is this legal), data generator (what should we try). Each fails differently.
  • Four reasons to use C++: the algorithm already exists, independence is structurally easier, speed, reuse across environments and projects.
  • What cannot move: ports, simulation time, concurrency, randomisation, coverage, signal assertions. An imported function cannot consume simulation time.
  • A reference model is independently testable. CRC-32, four published vectors, no simulator, under a second — including the empty-input boundary case.
  • A model validated only against the design can share its bug, and then a passing regression means nothing. External check values are the cheapest defence.
  • The seam costs conversion, four-state fidelity, an ownership decision and per-call overhead. Cross per transaction.

Every chapter in this module has now used the word "compile" without accounting for it, and §7's example quietly depended on it: a header was included, a function was defined in one file, and a program came out the other end. That is the last orientation question left, and it is the one every later module leans on — Module 2 opens the toolchain, Module 7 needs the idea of a translation unit, Module 24 is about organising them, and Module 28's linkage rules are unreadable without it. Chapter 1.7 builds that model: how many source files become many translation units, each becomes an object file, and a linker resolves the names between them into one program.

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.