Companion to the ASIC design flow, which places verification in the schedule, and to TLA+ for hardware, which covers the level above RTL. This article is about the methods themselves: what each one answers, and what it cannot.
Nobody verifies a design. Verification is not a thing you finish; it is a set of arguments you assemble until you are willing to spend the mask money. Every method below answers a narrower question than “is it correct”, and the skill is knowing which narrow question you have actually answered.
The most expensive misunderstanding in the field is treating these methods as competing answers to one question. They are not. They cover different shapes of the same state space, and the shapes are complementary — which is why every serious project runs several and why “we have 100% coverage” is a statement that needs four follow-up questions.
1. The two halves of a testbench
Before the methods, the distinction that organises all of them. A testbench does two things:
- Stimulus — it drives the design into states.
- Checking — it decides whether what came out was right.
These are independent, they fail independently, and almost all verification discussion is about the first one while almost all wasted effort is in the second.
The dangerous quadrant is strong stimulus with weak checking. It exercises everything, checks little, and reports success — and every metric you look at goes green. Coverage measures the horizontal axis only: a coverage report says where the design went, and nothing at all about whether anybody was watching when it got there.
This is not a hypothetical failure mode. In fault-injection work it has a name — an observation gap — and it is the reason a safety mechanism can appear to have high diagnostic coverage while detecting nothing: the fault was driven, the deviation happened, and the testbench never sampled the output it appeared on. The same defect in functional verification is quieter, because nothing computes a number that looks wrong.
The practical test: break the design on purpose and confirm the testbench fails. A testbench that has never caught a deliberately injected bug is not known to catch anything.
2. Directed tests
What it is. A human decides on a scenario and writes it: this sequence of transactions, this sequence of register writes, this error condition.
What it proves. That the scenarios you thought of work. That is more valuable than it sounds — bring-up, basic sanity, and the specific corner a customer reported are all directed work, and a directed test is the cheapest way to reproduce a known bug.
Blind to. Everything nobody thought of. Its coverage is exactly the union of its author’s imagination and the specification’s explicitness.
Cost. Cheap per test, expensive at scale. The effort grows linearly with the state space, which loses badly to a state space that grows exponentially with the design.
When it is right. Bring-up, smoke tests, regression pinning for fixed bugs, protocol compliance suites, and anything where the specific case matters more than the space around it. Also: when the design is small enough that the space is genuinely enumerable.
3. Constrained-random, and the coverage that makes it accountable
What it is. The testbench generates randomised but legal stimulus — constraints describe what a valid transaction looks like, and the generator explores inside them. Instead of writing a thousand tests, you write one generator and a thousand seeds.
What it proves. That the design survives a large, unplanned, uneven sample of its legal input space. It finds the cases nobody thought of, which is precisely where the bugs that reach silicon live.
The catch that defines the method: you no longer know what you tested. That is why constrained-random and coverage are inseparable — not two techniques but one, because coverage is the only thing converting “we ran a lot of random stimulus” into a defensible claim.
The two kinds of coverage, and the gap between them
Code coverage is automatic and structural:
| Metric | Asks |
|---|---|
| Line / statement | did this line execute? |
| Branch | did each direction of each decision get taken? |
| Condition / expression | did each sub-term independently affect the result? |
| Toggle | did each bit go both 0→1 and 1→0? |
| FSM state / transition | was each state entered, each arc taken? |
It is free, and it measures the stimulus only. 100% line coverage says every line ran. It says nothing about whether the result was checked, and nothing about whether the line did the right thing.
Functional coverage is hand-written and semantic: covergroups and cross-coverage that say “a read collided with a refresh while the FIFO was full at maximum burst length.” It is the only coverage that knows what your design is for.
The gap between them is the interesting part, and it cuts both ways:
- High code coverage with low functional coverage means the stimulus touched everything and combined nothing. Each feature works alone; the interactions are untested, and interactions are where the bugs are.
- High functional coverage with low code coverage means your coverage model is missing something the design does — dead code, an unimplemented feature, or a covergroup written against the spec while the RTL does something else.
Coverage closure is a necessary condition and never a sufficient one. It proves you reached the states you thought to enumerate. It cannot prove you enumerated the right ones, and it says nothing at all about the checking axis.
4. Assertion-based verification
What it is. Properties written in SVA, embedded in or bound to the design, checked continuously during simulation. “If req rises, ack must follow within 1 to 8 cycles.” “These two states are mutually exclusive.” “This FIFO never overflows.”
What it proves. That the stated property held on the stimulus you ran. In simulation an assertion is a checker, not a proof.
Why they are disproportionately valuable. Three reasons, and the third is the one people miss:
- They check continuously, everywhere, rather than at the end of a test. A protocol violation is caught at the cycle it happens rather than three thousand cycles later when a scoreboard notices a mismatch.
- They localise. An assertion failure names the module, the property and the cycle. A scoreboard mismatch names neither.
- The same properties are what formal proves. Writing them well pays twice, which makes assertions the natural bridge between simulation and formal.
Blind to. Anything you did not state. And an assertion that never fires because its antecedent is never satisfied is vacuous — it passes without checking anything. Vacuity analysis exists precisely because the failure mode of assertion-based verification is assertions that are green and meaningless.
5. Formal property checking
What it is. Mathematically prove a property over all reachable states of the design, with no stimulus at all.
What it proves. Completeness in one dimension: for the property stated, under the constraints stated, there is no input sequence that violates it. This is a categorically stronger claim than any amount of simulation.
Blind to. Everything you did not state, again — and the constraints matter enormously, because an over-constrained formal run proves a property about a design that cannot occur.
The limitation people misreport. A bounded proof to depth says the property holds for the first cycles from reset. An unbounded proof says it holds for ever. These are different claims, conflating them is the standard formal mistake, and a tool reporting “proven” after a bounded run is not saying what an unbounded one says.
Where it wins. Control logic, arbiters, FIFOs, protocol compliance, register maps, clock-domain-crossing structures, security properties, and — importantly — proving the absence of something: no deadlock, no overflow, no illegal state. It is also the right tool for exhaustive equivalence checking after an ECO.
Where it does not. Wide datapaths and arithmetic, where the state space defeats it, and anything needing a long sequence of real activity. A multiplier is a simulation problem.
6. Emulation and prototyping
What it is. Map the design onto purpose-built hardware or an FPGA and run it several orders of magnitude faster than simulation.
What it proves. That the design survives depth — billions of cycles, real firmware, a booting operating system, a real driver stack. There are bugs that simply cannot be reached in any feasible number of simulated cycles, and this is the only pre-silicon way to find them.
Blind to. Breadth. One very long trace is still one trace. Debug visibility is far worse than simulation, compile times are hours, and the cost is substantial.
The strategic value is usually not verification at all: it is having firmware that runs on day one of silicon bring-up. That schedule benefit is often the real justification, and it is worth saying out loud when the capital request is written.
7. The methods that get left off the list
Software-driven tests. The embedded CPU runs C code that exercises the rest of the chip. Closest to how the product will actually be used, excellent at finding integration and register-map bugs, and poor at corner cases — the software does what software does.
Reference-model comparison. Run a golden model — C, Python, SystemC, or a spreadsheet — alongside the RTL and compare. This is the strongest form of checking available, and the question it raises is where the model came from: an independently written model catches specification misreadings, while a model written from the same document by the same person catches typos only.
Portable Stimulus (PSS). Describe test intent once and generate implementations for simulation, emulation and post-silicon. Real value on large SoCs where the same scenario is needed at three levels; overhead that is hard to justify on a block.
Fault injection. Deliberately corrupt nodes and measure how many faults the safety mechanism detects. This is the FMEDA workhorse rather than a functional-verification tool, and it has the sharpest version of the checking problem in the whole field: a fault the workload never activates scores safe because nothing reached it, and a fault the testbench never observes scores safe because nobody was watching. Both flatter the result, and both look identical to success.
Protocol verification IP. Bought agents and compliance suites for AXI, PCIe, USB, MIPI, Ethernet. Almost always cheaper than writing your own, and the compliance suite is evidence you can hand to somebody else — which is half of why it is worth the money.
8. Choosing, in one table
| Question you are asking | Reach for |
|---|---|
| Does the basic path work? | directed tests |
| Does it survive inputs nobody imagined? | constrained-random + functional coverage |
| Does this protocol rule always hold? | SVA, then formal |
| Can this deadlock? Can this overflow? | formal — it proves absence, simulation cannot |
| Does it work with real firmware? | emulation, software-driven tests |
| Does it match the specification’s arithmetic? | reference-model comparison |
| Is the architecture right, before RTL? | TLA+ |
| Does the safety mechanism actually detect faults? | fault injection, two-sided |
| Does the netlist still match after an ECO? | formal equivalence checking |
The pattern: use formal to prove absence, simulation to explore presence, emulation for depth, and a reference model to decide what “right” means. Those are four different jobs.
9. The verification plan, and when you are done
A verification plan is the document connecting the specification to the evidence:
feature → what could go wrong → how it will be checked → what coverage proves it was → who owns it
Written before the testbench, it is the thing that makes coverage meaningful, because the covergroups come from features rather than from whatever was easy to instrument. Written afterwards, it is a description of what you happened to build.
When are you done? There is no honest threshold, and anyone offering one is selling a tool. What a mature team actually uses:
- Coverage closed against a plan that was reviewed, with every exclusion justified in writing rather than waived.
- The bug discovery rate flattened — and flattened while effort was still being applied, not because everyone moved to another project. A curve that drops because the team dispersed tells you about the team.
- Every bug found late analysed for why it was found late: what would have caught it earlier, and has that gap been closed for the next block? This is the single practice that most distinguishes teams that improve from teams that repeat.
- Assertions fail when the design is deliberately broken — the two-sided check.
- No open high-severity issues, and the low-severity ones documented rather than forgotten.
10. Regression, briefly
Everything above assumes the tests run repeatedly. Three properties decide whether that is sustainable:
Runtime. A regression that takes longer than a night does not gate a commit, so bugs live in the tree for days. This is worth real engineering — test grading, seed selection, and running the long tail nightly rather than per-commit.
Triage cost. A hundred failures that are all one bug is a good morning. A hundred failures that are ten bugs and ninety flakes is a lost week. Automatic clustering of failure signatures is the highest-value regression investment after runtime.
Flakiness. A test that fails one run in fifty destroys trust in the whole suite, because the team learns to re-run rather than investigate — and the one real intermittent bug hides perfectly behind that habit. Chase flakes as though they were bugs. Often they are.
11. The pitfalls
- Reporting coverage without saying whether anything was checked.
- Constrained-random with no functional coverage: expensive noise.
- A coverage model written after the testbench, describing what was built rather than what matters.
- Assertions that never fire, passing vacuously, counted as evidence.
- A bounded formal proof reported as a proof.
- An over-constrained formal environment proving a property about an impossible design.
- A reference model written by the same person from the same document — it catches typos, not misunderstandings.
- Treating emulation depth as breadth.
- Excluding coverage to hit a number, rather than to record a justified decision.
- Declaring done because the bug rate fell, when what fell was the effort.
- Never breaking the design on purpose, and therefore never establishing that the testbench can fail.
Glossary
Assertion (SVA) — A property checked continuously during simulation, embedded in or bound to the design. Its value is threefold: it checks every cycle rather than at test end, it localises a failure to a module and a cycle, and the same property is what formal proves — so writing it well pays twice.
Code coverage — Structural, automatic metrics of what the stimulus reached: line, branch, condition, toggle, FSM. Free, and it measures only the stimulus axis — it cannot know whether anything was checked.
Constrained-random — Generating randomised but legal stimulus from a constraint model rather than hand-writing cases. Finds what nobody imagined, at the cost of no longer knowing what was tested — which is why it is inseparable from coverage.
Coverage closure — Reaching the coverage goals in the plan. A necessary condition, never a sufficient one: it proves you reached the states you thought to enumerate, not that you enumerated the right ones.
Cross coverage — Coverage of combinations of values rather than values individually. Where the interesting bugs are, and where the coverage space explodes, so it needs deliberate pruning rather than a full cross.
Directed test — A hand-written scenario. Cheap per test, linear in effort against an exponential state space, and the right tool for bring-up, regression pinning and reproducing a reported bug.
Emulation — Running the design on purpose-built hardware, orders of magnitude faster than simulation. Buys depth — real firmware, billions of cycles — not breadth, and its strategic value is often the bring-up schedule rather than the bugs.
Formal property checking — Proving a property over all reachable states, with no stimulus. Categorically stronger than simulation for what it covers, and only as sound as its constraints.
Functional coverage — Hand-written, semantic coverage: covergroups and crosses describing scenarios that matter. The only coverage that knows what the design is for, and therefore the only one whose absence is a planning failure rather than a tooling one.
Observation gap — A deviation that occurred and was never sampled, so it scored as a pass. The failure mode of the strong-stimulus/weak-checking quadrant, and the reason a testbench should be proved able to fail.
Reference model — An independent implementation used to decide what the right answer is. The strongest form of checking available, and worth only as much as its independence.
Regression — The repeated run of the whole suite. Governed in practice by runtime, triage cost and flakiness, all three of which are engineering problems in their own right.
Scoreboard — The testbench component comparing what came out against what should have. Where “should have” comes from is the design decision that decides how much the whole testbench is worth.
Vacuity — An assertion that passes because its precondition never occurred. Green, and checking nothing. Vacuity analysis exists because this is the normal failure mode of assertion-based verification rather than an exotic one.
Verification plan — The document mapping features to risks to checks to coverage. Written first it makes coverage meaningful; written afterwards it describes what you happened to build.
Related reading
- The ASIC design flow — where each method sits in the schedule, and what it costs
- TLA+ for hardware — the level above RTL, before any of these methods can start
- The FMEDA work package — fault injection, diagnostic coverage, and the two-sided discipline
- Silicon power usage — where the same stimulus you built for verification gets reused, and why it is usually the wrong stimulus for power
Further reading
Book links below are Amazon affiliate links: if you buy through one we earn a small commission at no extra cost to you. It does not influence what is recommended — the same books were on this list before the links were.
- Chris Spear and Greg Tumbush, SystemVerilog for Verification — the standard practical text, and the right first book.
- Janick Bergeron, Writing Testbenches: Functional Verification of HDL Models — older, and still the clearest thinking about why testbenches are built the way they are.
- Erik Seligman, Tom Schubert and M V Achutha Kiran Kumar, Formal Verification: An Essential Toolkit for Modern VLSI Design — practical formal, including the bounded-versus-unbounded distinction done properly.
- Ashok Mehta, SystemVerilog Assertions and Functional Coverage — assertion and covergroup syntax with worked examples.
- Harry Foster, Adam Krolnik and David Lacey, Assertion-Based Design — the case for assertions as a design activity rather than a verification one.
Have a verification plan that closes coverage without closing risk, or a testbench nobody has proved can fail? That’s what we do. Reach us at circuit-design.space.
circuit-design.space · +1-971-357-1400 · anovickis@circuit-design.space
