Skip to content
VLSI Mentor

C Part II · Module 18 · C in SoC Verification

Choosing Between SystemVerilog and C for a Job

The decision is usually made by habit — the testbench is SystemVerilog so everything is SystemVerilog, or someone prefers C and writes the model in C. This chapter makes it a decision you can defend, using four ordered questions whose answers are evidence rather than preference, and most of that evidence was measured earlier in this module. It is also explicit about the limit of what could be demonstrated here: the three things SystemVerilog has that C has no equivalent for could not be run on this machine, so they are argued from the language definition and labelled as such.

Advanced21 min readCPart IIVerificationArchitecture

Part II · Module 18 · Chapter 18.6 · Choosing Between SystemVerilog and C for a Job

1. How the Decision Is Usually Made

Two defaults, both habits rather than reasoning.

"The testbench is SystemVerilog, so everything goes in SystemVerilog." It keeps one language and one build, which are real benefits. It also means a reference model is written in the same language, in the same repository, often by the same person who wrote the RTL — and Chapter 18.2 measured what happens to a model's value when its independence erodes.

"This is a model, and models go in C." Equally unexamined, and it imports an interface, a build integration and a debugging story that Chapter 18.5 §3 showed is not free.

The chapter's purpose asks for a decision made on evidence, so the four questions below each have an answer that is either a measurement from this module or a property of the two languages. Where a criterion is neither — where it is a judgement call — the chapter says so rather than dressing it up.

2. Question One: Does It Already Exist?

The cheapest decision and the one most often skipped.

If the work is implementing a published standard — a checksum, a cipher, a compression format, a floating-point operation — an implementation almost certainly exists in C, has been exercised by people outside your project, and owes nothing to your team's reading of the specification.

Chapter 18.3 established the value and the obligations. The evidence that it is a decision rather than a convenience is Chapter 18.2's measurement: a model derived from the design found 0 mismatches in 4096 inputs against a broken design, while an independently-derived one found 3072. An adopted implementation is independent by construction.

And Chapter 18.3 measured the cost of adopting carelessly — four implementations all called CRC-8 producing 0x52, 0x34, 0xE3 and 0x07 for the same three bytes, and two of them agreeing on the published check value while disagreeing on everything else.

So the answer to question one is not "use a library". It is:

If a trustworthy implementation of the same specification exists, adopting it buys independence you cannot otherwise afford — provided you pin the variant with more than one vector.

If nothing exists, the question has no bearing and you move on.

3. Question Two: Will the Same Code Ship?

Chapter 18.4's subject, and the criterion with the sharpest answer.

If the code will run on the product — firmware, a driver, a bootloader — then writing it in C and exercising it against the RTL verifies the thing that will ship. A SystemVerilog reimplementation verifies something nobody will ever run, and the difference between them is a class of bug nobody will find until bring-up.

The evidence is 18.4's measurement. Two directed tests set a baud divisor to 1000 and to 2400 from reset and both passed, with the field fully covered. Firmware that opened the port at 1000 and re-tuned to 2400 left it at 1000 — a defect reachable only by a sequence that real software performs.

This criterion is close to decisive when it applies. If the answer is yes, the work is C and the only remaining question is how it reaches the simulation, which is Chapter 18.5's.

4. Question Three: Does It Need What Only SystemVerilog Has?

Three capabilities have no C equivalent, and if the job needs one of them the job is SystemVerilog.

Constrained randomisation. Declaring a class with random fields and constraints, and having the solver produce legal stimulus. C has no solver; writing one is not a reasonable response to this question.

Functional coverage. Covergroups, coverpoints, crosses, and the reporting around them. The tooling that answers "what have we actually exercised?" is a language feature, not a library.

Concurrent assertions. Temporal properties over clocked behaviour — "a request is followed by a grant within three cycles" — checked continuously by the simulator.

C can approximate parts of all three badly. It has random number generators, it can count things, and it can check conditions. What it does not have is the solver, the coverage database and the temporal-property engine, and reimplementing any of them is a project rather than a task.

5. Question Four: What Does the Coupling Cost?

The question that is usually skipped and that Chapter 18.5's figure was built to answer.

If the work goes in C, it has to reach the simulation, and that costs something: a build integration, a type mapping, C that can only be debugged inside a simulator process, and model output that does not outlive the run. Chapter 18.5's third column is that cost, route by route.

The cost is zero for two of the six routes. A generator or post-processor that exchanges a file has no coupling at all; firmware on a core model couples only through a memory image. For those, question four does not argue against C.

For the in-process routes it is a genuine cost, and it is the one that performance arguments usually ignore — §7's subject.

So the practical form is:

Work out which of Chapter 18.5's six routes this job needs, before deciding the language. If the route is a file or firmware, C costs nothing to integrate. If it needs an in-process interface, that is a real cost and belongs in the comparison.

6. Putting the Four Together

In order, because the early ones are more decisive:

  1. Does a trustworthy implementation already exist? → adopt it (C), pinned by several vectors.
  2. Will the same code ship? → C, and exercise the real thing.
  3. Does it need constrained randomisation, functional coverage or concurrent assertions? → SystemVerilog.
  4. What does the coupling cost? → if a file or firmware will do, C is free to integrate; if an in-process interface is required, weigh it.

If none of the four settles it, the work is genuinely a toss-up, and then the tie-breakers are ordinary engineering ones that this chapter will not pretend are evidence: which language the team maintains better, which one the reviewers read fluently, and what the rest of the environment is written in. Defaulting to the testbench's language is a perfectly good answer once the four questions have failed to produce one — what is not good is reaching it first.

One addition worth making explicit, because it cuts across all four. Chapter 18.2's central finding was that a model's worth is proportional to its independence, and language is one of several things that can differ between a model and a design. A C model written by the design's author in the same week is less independent than a SystemVerilog model written by someone else from the specification. The language is a weak proxy for independence, and treating it as a strong one is how teams end up with model B in a different file extension.

7. The Misconception: C Is Faster, So Models Go in C

"C is compiled and fast; SystemVerilog is interpreted and slow. Performance-critical models belong in C."

Performance is the least reliable of the criteria, and it is usually not the real reason even when it is the stated one.

The arithmetic is rarely the cost. A reference model computes a checksum or decodes a field — work measured in nanoseconds. The simulation around it is doing far more.

The coupling can cost more than the computation. Chapter 18.5's third column is the real expense of an in-process model: a call across the boundary, argument marshalling, and a type conversion at each crossing. A model called once per transaction may spend more time crossing than computing — and a model moved into C for speed can end up slower.

Where C genuinely wins on performance, it wins big and for a specific reason: a bulk computation over a large data set, done once, outside the per-cycle path. Compressing a megabyte, running a reference codec over a frame, computing an expected image. That is a real category, and note that it is also the category where Chapter 18.5's file route applies — so the win is available without any interface at all.

Chapter 18.1's callout listed the four honest reasons for reaching outside SystemVerilog, and three of them were about provenance and audience rather than the language's properties: the code already exists, the same code ships, independence is the point, the software team is the audience. Performance was not on the list, which was deliberate.

The corrected position:

Decide on provenance, on what ships, on what the job needs, and on the coupling. If performance is the argument, measure it — including the crossings — before believing it.

8. Module 18 in One Page

The module's description was "the architecture before the syntax", and its job was to make the reader able to decide what C is for in a verification flow before learning any mechanism for connecting it.

ChapterThe measured result
18.1Five places C appears, and only two are called by the testbench. Firmware against the Chapter 16.1 model: CTRL = 0x00203e8b, read "Hi", W1C ack left pending 0x00000004, 6 reads + 2 writes
18.2A design-derived model found 0 mismatches in 4096 inputs against a broken design; a spec-derived one found 3072. And a model encoding pipeline depth failed a correct design when the depth went 3 → 4
18.3Four implementations all called CRC-8 gave 0x52 / 0x34 / 0xE3 / 0x07 for the same bytes — and MAXIM-DOW and ITU both give 0xA1 on the published check vector while differing elsewhere
18.4Two directed tests passed with full field coverage; firmware's 1000-then-2400 left 1000
18.5C executed inside a running simulation via VPI — $c_crc8(65,66,67) = 82, matching the standalone run and the published 0xF4 check value
18.6Four ordered questions, and an explicit statement of what could not be demonstrated

Three threads run through all six.

Independence is the property that matters, and it is invisible. A model that agrees with the design looks identical whether the agreement is a vindication or a tautology — measured, 0 mismatches against a broken design. Every criterion in this chapter is downstream of that.

The two failure modes are opposite and both are common. Too close to the design catches nothing; reproducing too much of it reports failures against correct hardware. A chapter, or a team, that knows only one of them produces the other.

What could not be measured was labelled. Hardware reordering in Chapter 17.6, the read-modify-write window in Chapter 16.5, and SystemVerilog's three distinctive capabilities in §4 here. In each case the mechanism was stated and the transcript was not invented — and in §4's case the tool's own refusal is the evidence that they are language features.

9. What Comes Next

Chapter 18.5 §6 deferred one thing deliberately: the syntax of the interface. The map showed six routes and expanded five of them; the DPI row was left as a row.

Module 19, DPI-C Foundations, is that row. Its own description is "the SystemVerilog/C boundary explained before any declaration is written: what crosses it, who calls whom, how the build is put together, and where the C code actually runs — syntax arrives only after the architecture that makes it readable", which is the same ordering Module 18 applied one level up.

It begins with why DPI-C exists at all (19.1) and the boundary in both directions (19.2) — because C calling into SystemVerilog is as real as the reverse and is usually taught second or not at all. Then imported functions and tasks (19.3), a first DPI-C function written end to end (19.4), the distinction between pure and context imports (19.5), how a DPI build is actually put together (19.6), and where the C code runs inside the simulator's execution context (19.7).

That last chapter is the one Module 17 makes you ready for. C running inside a simulator's process is C running in an environment with its own ideas about threading, re-entrancy and lifetime — and Chapter 17.5's three non-guarantees stop being a register topic the moment your function can be called from two places.

10. What You Should Be Able to Say Now

  1. Two habits replace the decision: everything in the testbench's language, or models reflexively in C. Both skip the question.
  2. Question one — does it already exist? An adopted implementation is independent by construction, which 18.2 measured as the difference between 3072 and 0 mismatches.
  3. Pin the variant with several vectors — 18.3 measured two variants colliding on the usual one.
  4. Question two — will the same code ship? If yes, C, and exercise the real thing: 18.4 measured a sequence bug that two passing directed tests could not reach.
  5. Question three — does it need constrained randomisation, functional coverage or concurrent assertions? Those are SystemVerilog language features with no C equivalent.
  6. Those three could not be demonstrated here — Icarus reports "Constraint declarations not supported" and a syntax error on a covergroup. They are argued from the language definition, and the chapter says so.
  7. Question four — what does the coupling cost? Zero for a file or firmware; real for an in-process interface (18.5's third column).
  8. Ask them in order, and if none settles it, defaulting to the testbench's language is a fine answer — reached last, not first.
  9. Language is a weak proxy for independence. A C model by the design's author is less independent than a SystemVerilog model by someone else from the spec.
  10. Performance is the least reliable criterion. The crossings can cost more than the computation, and the case where C genuinely wins is bulk work that the file route handles with no interface at all.

11. Exercises

  1. Take three pieces of verification work you have done and run each through §6's four questions. Record where each was actually written and whether the questions agree.
  2. For a block you know, identify one piece of work that question three settles immediately. State which of the three capabilities it needs and why C cannot supply it.
  3. §2 says an adopted implementation is independent by construction. Give one way that can be false, using Chapter 18.3 §4.
  4. Write the three-sentence case for putting a reference model in C, for a block where question two's answer is no. Then write the case against.
  5. A team proposes moving a per-transaction model from SystemVerilog to C for speed. List what you would measure before agreeing, including the thing §7 says is usually ignored.
  6. §6 claims language is a weak proxy for independence. Construct the two examples that make the point — a C model that is not independent, and a SystemVerilog model that is.
  7. Pick one of the three capabilities in §4 and describe, concretely, what reimplementing it in C would involve. Then say whether you would.

12. Summary

  • Two habits replace the decision; the purpose asks for evidence instead.
  • Q1 — does it exist? Adoption buys independence — 3072 vs 0 mismatches. Pin the variant with several vectors (two collided at 0xA1).
  • Q2 — will the same code ship? Then C, and exercise it — two directed tests passed where firmware's sequence failed.
  • Q3 — constrained randomisation, functional coverage, concurrent assertions? SystemVerilog. These could not be demonstrated here and are argued from the language definition.
  • Q4 — what does the coupling cost? Zero for a file or firmware; real for an in-process interface.
  • Ask in order; default to the testbench's language last, not first.
  • Language is a weak proxy for independence.
  • Performance is the least reliable criterion — measure the crossings; the bulk-work case is served by a file anyway.

Where this fits

Part of the C Programming curriculum.