Design Flow · Stage 4 of 13 · Front end

Verification

Before anything is built, teams test the design on a computer to find mistakes. A mistake found after the chip is made can cost millions of dollars.

Engineers run the design in simulation under automated test programs, measure which situations the tests have reached, prove the trickiest rules mathematically, and run long software tests on special hardware. It often takes as much effort as writing the design, or more.

Coverage-driven constrained-random verification, formal property checking, emulation and FPGA prototyping for software bring-up, power-aware and gate-level simulation, and the coverage criteria that define done.

Builds Confidence

A chip design starts as a long description, written in a special language for hardware. Before anyone pays a factory to build it, the team checks that it does what the plan says. That checking is called verification. On many teams, more people check the design than write it.

Mistakes found late are expensive. In 1994, Intel’s Pentium chip got some division sums slightly wrong. A few numbers were missing from a table inside the chip. Replacing the faulty chips cost Intel $475 million. Caught early, the fix would have been a quick edit.

The hard part is that there are far too many cases to try them all. Adding two numbers up to 255 has about 65,000 possible pairs. That is easy for a computer. But a modern chip adds much bigger numbers. All the world’s computers together could not try every pair in a billion years. So verification is about choosing smart tests, and knowing when you have done enough.

A digital chip starts life as code. Engineers describe it in a hardware description language, which looks like a programming language but describes circuits rather than steps for a computer to follow. At this stage the description is called (register-transfer level). It says which values the chip stores in , tiny one-bit memories, and how it computes their next values. All of this is paced by the clock, a signal that ticks millions or billions of times a second; each tick starts a new clock cycle.

Functional verification checks that the RTL does what the specification, the written description of what the chip must do, says. It starts as soon as the first RTL exists, runs alongside the design work, and continues until tapeout, when the finished design goes to the factory. Two other kinds of checking come later and answer different questions. Manufacturing test looks for physical defects in each chip that comes off the line. Implementation checks confirm that each later step, such as turning the RTL into logic gates, did not change what the design does. Both assume the RTL itself is right. Verification is what makes that assumption safe.

The main tool is : running the design as a program on an ordinary computer. Engineers write a , a test program that feeds inputs to the , watches what comes out, and checks it. No simulation can try every possible input, so teams also keep count of which situations their tests have reached. For the trickiest parts they use , which proves with mathematics that a rule holds for every input. And they run the design on and FPGA prototypes, special hardware fast enough to run real software before the chip exists.

Verification takes a large share of a chip project. Academic studies call it the most resource-consuming part of design, and note that on many teams the people doing verification outnumber the designers. The reason is cost. A bug that reaches manufactured chips means new photomasks (the costly stencils used to print a chip), months of delay, or a recall. In 1994, entries missing from a lookup table in Intel’s Pentium made some divisions slightly wrong, and replacing the chips cost Intel $475 million.

Two different questions get checked between the first RTL and the finished layout. The first is whether the RTL does what the specification intends. Nothing upstream can answer that automatically, because the specification is mostly prose; that is functional verification, the subject of this chapter. The second is whether each later transformation (synthesis into gates, insertion of test logic, layout) preserved the RTL’s behavior. answers the second question formally. A 1998 study already named the first as the hardest verification problem for most designs, and simulation as its workhorse.

Production flows combine several techniques, each covered below:

  • Constrained-random simulation: the computer generates legal random inputs, and checkers compare the design’s behavior with a model of what it should do.
  • Assertions and coverage: rules checked on every cycle, and counters that record which planned situations the tests actually reached.
  • Formal property verification: mathematical proof that a rule holds for all inputs, on blocks small enough to analyze.
  • Emulation and FPGA prototyping: running the design in hardware, fast enough for operating systems and long software tests.
  • Gate-level and power-aware simulation: targeted reruns on the synthesized gates, and on the design with its power switching modeled.

Progress is planned, measured and reviewed against written criteria. OpenTitan, an open-source security chip project, publishes its own. A block reaches its V1 stage with a reviewed testplan (the list of features to test and how). V2 requires 90% code and 90% functional coverage, with above 90% of tests passing in nightly runs over many random seeds. V3, the final stage, requires 100% code and functional coverage (with waivers for any items left out) and every seed passing. Each of its random tests runs with 100 different seeds in the nightly regression.

Code and functional coverage at OpenTitan V2
90%
Coverage at OpenTitan V3, with waivers
100%
Seeds per random test, OpenTitan nightly
100
Cost of the 1994 Pentium FDIV replacement
$475M

The staffing follows from the costs. A bug found in simulation costs an edit and a rerun. A bug found in silicon costs a new mask set and months of schedule, and one that ships can cost a recall, as Intel’s Pentium division bug did. So teams accept spending more engineering effort on verification than on writing the design.

input a →input b →each input0–255input pairs2¹⁶ = 65,536tested in 1 day100%exhaustive66 µs
Input width

2¹⁶ = 65,536 input pairs, all tried in 1 day. At this width, exhaustive testing works.

Every input pair of an adder with two n-bit inputs, drawn as one square; the filled area is what one computer tests in the chosen time. Illustrative rate: 10⁹ tests per second. Each extra input bit doubles the square.Share freely with credit: ‘Figure from chipfieldguide.com’

Verification starts with the plan for what the chip should do, plus the design itself. It ends with a pile of tests, a record of what has been tested, and a list of bugs found. Last comes a sign-off: the team agrees the design is ready to build.

DirectionWhatFormat
InSpecification and verification plan (testplan and coverage plan)Documents; machine-readable testplans such as Hjson, a relaxed form of JSON
InRTL designHardware description languages: SystemVerilog, Verilog or VHDL
InReference models that predict the right answersC, C++, Python or SystemVerilog; an instruction-set simulator for processors
InPower intent: which parts of the chip can be switched off, and howUPF, the Unified Power Format (IEEE 1801)
InGate-level netlist and gate delays, for gate-level simulationVerilog netlist; SDF (Standard Delay Format) file from the timing tools
OutTestbench, tests and software testsSystemVerilog with UVM, Python with cocotb, C for programs that run on the chip’s own processor
OutAssertions and formal setupsSVA (SystemVerilog Assertions); formal tool scripts such as a SymbiYosys .sby file
OutRegression results and waveforms (recorded signal values over time)Logs; VCD or FST waveform files, or a simulator’s own database
OutMerged coverage and exclusionsCoverage database; reviewed exclusion files
OutBug reports and the sign-off checklistIssue tracker, review records

The most important input is the , which works as a contract between the designers and the verification team. In OpenTitan it has two parts. The testplan lists the tests planned for every feature in the specification. The functional coverage plan lists the situations, and combinations of situations, that tests must be seen to reach before each feature counts as exercised. It is kept in Hjson so that a program can read it and attach each night’s test results to the right line.

Most of the outputs are evidence. The tests and checkers are the work itself; the results, coverage and bug list show how far that work has got; and the sign-off checklist records that reviewers agreed it is enough.

Two inputs often arrive late and cause trouble. The first is the UPF power intent, which says which regions of the chip can be powered down and which special cells guard them. If it joins the simulations only near the end, bugs in the logic that sequences power-down and power-up surface late and are slow to trace; one Samsung team built checkers generated from the UPF to find them earlier. The second is the gate-level netlist and its SDF delays, which exist only after synthesis and layout, so the list of gate-level tests has to be planned before they arrive.

Among the outputs, sign-off reviewers look hardest at the merged coverage database and its exclusions. An exclusion removes a coverage item from the total, so each one needs a reason. OpenTitan sorts them into categories: unreachable code (UNR), simulation-only code that never becomes hardware (NON_RTL), features of reused IP that this chip doesn’t use (UNSUPPORTED), items already covered in another testbench (EXTERNAL), and items too hard to hit and judged low-risk (LOW_RISK). It reviews them.

VerificationinputsoutputsSpec + planRTL designReference modelPower intentNetlist + SDFTestbenchAssertionsResults + wavesCoverageBugs + sign-off
Project phase

At the end, every input has arrived, and the outputs are the evidence reviewers sign off on. Tap any box.

Verification’s inputs (left) and outputs (right). Most of the outputs are evidence: they show how far the work has got.Share freely with credit: ‘Figure from chipfieldguide.com’

The test setup. Engineers build a around the design inside a computer. It feeds the design inputs and checks what comes out. The right answers come from a separate, much simpler program, written from the plan.

Hand-written and random tests. Some tests are written by hand to check one tricky case an engineer thought of. Others are made up by the computer. It rolls dice, within sensible rules, to invent thousands more. Random tests find surprises that nobody imagined.

Alarms. Engineers also write rules into the design, such as “an answer always comes back within four steps.” If a rule is ever broken, an alarm goes off right away.

The checklist. is a checklist of the cases the plan says to test. Each one is ticked off when a test reaches it. An empty box means nobody has tested that case yet, no matter how many tests have passed.

Proof. For small, tricky parts, engineers can use math instead of tests. A proof tool checks a rule for every possible input at once. If the rule can break, it shows exactly how.

Anatomy of a testbench

A design talks to the outside world through wires called signals, each carrying a 0 or a 1, and the values change once per clock cycle. Working at that level is tedious, so a modern thinks in whole operations, called transactions, such as “write 4 bytes to memory address 64,” and splits the work into parts.

  • A stimulus generator decides which transactions to send.
  • A driver turns each transaction into the right pattern of 0s and 1s on the design’s input signals, cycle by cycle.
  • A monitor watches the output signals and reassembles what it sees into transactions.
  • A predicts the correct output for each input.
  • A compares what the design produced with what was predicted, and reports mismatches.

The checking side should not depend on how the inputs were chosen, so the same checks work for every test.

Directed and random tests

A sets specific inputs and checks specific outputs. It is quick to write for known cases and easy to debug. A test instead states rules for what counts as a legal input and lets the computer choose values within them. Each run starts from a seed, a number that fixes the whole random sequence, so the same seed always reproduces the same run. Constrained-random verification is widely used in industry. OpenTitan runs each random test with 100 different seeds every night, as part of its , the full suite rerun to catch anything a new change broke.

UVM and cocotb

Most testbenches in industry are written in SystemVerilog, a hardware description language with extra features for testing, using , a standard library of testbench parts. Accellera, the industry standards group, maintains UVM’s reference code, aligned with the IEEE 1800.2 standard, so that test components can be reused across projects and tools. UVM bundles a driver, a monitor and a sequencer (which feeds transactions to the driver) into an “agent” for each of the design’s interfaces, and adds scoreboards and a “factory” for swapping one component for another. OpenTitan’s block-level testbenches are built on it.

is an open-source alternative that lets engineers write the testbench in Python, a widely used general-purpose language. It works with open-source simulators such as Icarus Verilog and Verilator and with commercial ones such as Synopsys VCS, Cadence Xcelium and Siemens Questa. The project’s own adder example shows the typical pattern: set the inputs, wait a moment, and compare the design’s output with the answer from a Python model.

Assertions

SystemVerilog has assertions, coverage and constrained random generation built into the language (IEEE 1800). An is a rule the simulator checks as the design runs. A simple one checks a condition at one moment, such as “this counter is never above 9.” Others check behavior over several clock cycles. For example, req |-> ##[1:4] ack reads: whenever the request signal req is 1, the acknowledge signal ack must become 1 between one and four cycles later. Assertions placed inside the design catch a bug where it happens, often long before a wrong value reaches an output, and formal tools can prove the same assertions mathematically.

Coverage

Coverage answers “how much have the tests actually exercised?” There are two kinds, and both matter. is measured automatically from the design’s code: which lines ran, which branches were taken, which signals switched between 0 and 1. is written by engineers as a list of situations from the plan, and the simulator ticks each one off when a test reaches it.

OpenTitan’s documentation gives a neat example of why code coverage alone isn’t enough. Take a two-input AND gate, whose output is 1 only when both inputs are 1. Two tests, inputs 00 and 11, make every signal take both values and give 100% code coverage. Yet they can’t tell an AND gate from an OR gate, which gives the same answers for those two inputs. Functional coverage that requires all four input combinations would expose the gap. High code coverage shows progress, but it is not enough to call a design verified. is the work of examining every item no test has reached and either writing a test for it, adjusting the random rules, or excluding it with a reviewed reason.

Formal verification

treats the design as a machine that moves from state to state, one step per clock cycle, where a state is the set of values held in all its flip-flops. The tool checks a rule against every state the design can reach, and if the rule can be broken it returns a : the exact input sequence that breaks it. The open-source tool SymbiYosys can check a fixed number of cycles, attempt a complete proof, or find the shortest input sequence that reaches a situation of interest. Commercial formal tools include Cadence JasperGold and Synopsys VC Formal. For an 8-bit adder, a formal tool considers all 65,536 input pairs at once, so a wrong answer for 255 + 255 can’t hide.

compares two versions of a design, such as the RTL and the gate-level version that synthesis produced, and proves that they compute the same thing. Yosys’s EQY is an open-source tool for it. Formal tools also come in packaged “apps” for common jobs. One is connectivity checking: proving that every connection between blocks listed in a spreadsheet really exists in the chip’s RTL.

Testbench architecture

A production is layered, so that each layer can be reused and replaced on its own. From the bottom up:

  1. Sequences produce transactions, whole operations such as “burst-write 16 words to this address.”
  2. Drivers convert each transaction into pin activity, obeying the interface’s timing protocol cycle by cycle.
  3. Monitors sit passively on every interface and turn pin activity back into transactions.
  4. A transaction-level predicts the expected result of each input transaction, usually without modeling exact cycle timing.
  5. Scoreboards and coverage collectors consume the monitors’ output: the compares observed with predicted, and the coverage collector counts which situations occurred.

The checking has to stay independent of how the stimulus was generated, either through the scoreboard or through assertions; otherwise a new test needs new checks. In , an agent bundles the sequencer, driver and monitor for one interface. The factory is a registry that builds every component, so a test can tell it “build my error-injecting driver wherever the normal driver is requested” without editing the environment. The library follows IEEE 1800.2, and its point is verification IP (ready-made agents for standard interfaces) that works across tools and projects.

Simulators and unknown values

Python-based flows are a serious option. embeds a Python interpreter in the simulator and runs each test as a coroutine, a function that pauses until a given time or signal change. It reaches Icarus Verilog, Verilator, GHDL, NVC and the commercial simulators through their standard C programming interfaces (VPI, VHPI or FLI).

Simulators differ in how they treat unknown values. Hardware languages have a value X, meaning “could be 0 or 1,” for example in a flip-flop that was never reset. Verilator compiles the design into a multithreaded C++ program and is mostly two-state: an X becomes a constant chosen by its --x-assign option, and storage starts at random values, so rerunning with different settings exposes reset bugs. Its support for assertions and functional coverage is partial. Four-state simulators keep X, but standard RTL semantics are “X-optimistic”: an if-statement whose condition is X simply takes the else branch, so an uninitialized value can pass quietly in RTL and fail only at gate level. That is why OpenTitan turns on pessimistic , which lets the X spread instead, for all its RTL simulations by default.

Stimulus quality

stimulus is only as good as its distribution. A constraint solver that picks uniformly among all legal input combinations can still starve the control logic. Take an ALU (the arithmetic unit of a processor) with several operations. If one operation allows only a few legal operand values while the others allow millions, a uniform pick over all legal combinations almost never chooses that operation, when the useful target is each operation equally often.

Engineers bend the distribution with the test language’s controls: weights that make some values more likely (dist in SystemVerilog), ordering hints that make the solver pick the operation before the operands (solve … before), and randomizing whole scenarios first and details second. Coverage reports tell them where to aim. Automatic coverage-directed test generation has been researched for two decades, but published approaches either suit only narrow classes of designs or need a good deal of expert time to set up.

Coverage and closure

(line, branch, condition, toggle, and state-machine states and transitions) costs nothing to write: the simulator derives it from the code. costs engineering time and is only as good as its model. It is written as covergroups, in which coverpoints sort a signal’s values into bins and crosses count combinations of bins, and as SVA cover properties, which count sequences of events.

Code coverage measures activation, not detection. A line holding a bug counts as covered the moment it executes, even if the wrong value it computes is overwritten before reaching an output or a checker. That is why closing code coverage by writing tests that merely reach lines, without checking anything, is a classic way to look done without being done. means sorting every hole into one of four kinds and acting on it: missing stimulus (write or retune a test), missing checker (add a check), unreachable (prove or argue it, then exclude it), or unused feature (exclude it). Exclusions are recorded in reviewed categories.

Formal in a production flow

Formal property verification proves SVA under assumptions, written with assume, that describe the legal inputs. Each property ends in one of three states: proven for all time, failed with a , or bounded (no failure up to some number of cycles, but no full proof). Assumptions are the danger. If they are too strong, they rule out the very inputs that would break a property, and it “passes” without proving anything; tools report such properties as vacuous. So engineers pair each important assertion with cover properties showing that the interesting scenarios can still happen.

SymbiYosys runs bounded checks, full proofs, cover searches and liveness checks (“something good eventually happens”), using k-induction in its smtbmc engine and PDR through the ABC tool; Under the hood explains both. OpenTitan sets formal targets alongside its simulation targets. At V1, every input and output of a block must appear in at least one assertion. At V2, 90% code coverage, 75% cone-of-influence coverage (the share of logic that can affect some checked property) and 90% of properties proven in nightly runs. At V3, 100% of properties proven with reviewed assumptions. One useful trick is the symbolic variable. Instead of writing one assertion per interrupt line, an engineer declares an index that the tool may choose freely, constrained by two assumptions (it is in range, and it never changes after reset). A single property over that index then covers every line.

Formal apps package recurring problems. Connectivity checking turns a spreadsheet of required chip-level connections into proofs. is the other everyday formal job: it proves that a changed design, such as a synthesized netlist or a refactored block, still behaves exactly like the original. Yosys’s EQY is an open-source tool for it.

same inputsGenerator250 + 250Driver Design (DUT)sat. adderplanted bugMonitor Ref. model Scoreboard Coverage
1 / 7
Inputs a + b
Design

The stimulus generator picks a transaction.

A layered testbench around the saturating adder from the simulation. The checking side never looks at how the inputs were chosen, so the same checks serve every test.Share freely with credit: ‘Figure from chipfieldguide.com’
OR (bug)abya bwantgot0 000✓0 10–·1 00–·1 111✓Coveragecode (toggle)100%functional50%00011011holes: 01, 10all tests pass
Design under test
Tests

Code coverage 100%, every test passes, yet this is an OR gate. Functional coverage shows 2 of 4 cases reached.

OpenTitan’s example: an AND gate tested with inputs 00 and 11. Every signal toggles (100% code coverage), but an OR gate passes the same tests. Functional coverage of all four combinations exposes the gap.Share freely with credit: ‘Figure from chipfieldguide.com’

There are three main ways to run a chip design before the chip exists. The faster ones show you less.

  • Simulator. A program on an ordinary computer. It shows everything happening inside the design, which is great for tracking down a bug. But it runs thousands of times slower than the real chip, or worse.
  • Emulator. A big, expensive machine built just to run chip designs. It is fast enough to start up an operating system, like Windows or Android, though still much slower than the finished chip.
  • FPGA board. An FPGA is a chip that can be rewired by software. Loaded onto a few of them, the design runs fast enough to plug into real devices. But it is harder to see inside. The FPGA chapter shows what is inside one.

FPGAs have many other jobs too, from stock trading to satellites; see Where FPGAs win.

A design can be run in three ways before the chip exists. Each trades three things: compile time (how long it takes to prepare the design to run), run speed (how many clock cycles it gets through per second), and visibility (how many internal signals an engineer can see when something goes wrong).

PlatformCompileRun speedVisibilityTypical use
on ordinary computers (Icarus, Verilator, VCS, Xcelium, Questa)FastSlow to mediumEvery signalTesting single blocks and groups of blocks, debugging, coverage closure
on a dedicated machineFast to slow, depending on the machineFast (up to millions of cycles per second)HighWhole-chip runs, booting an operating system, long software tests
Slow (full synthesis and layout for the FPGA)FastLimited; watching a new signal needs a recompileSoftware development, connecting real devices, long soak tests

Verilator, a popular open-source simulator, gets its speed by compiling the design into a C++ program. Emulators are built either from many FPGAs or from arrays of custom processors. The FPGA-based kind takes long to compile but runs fast. How an FPGA’s lookup tables and programmable wiring work is covered in Lookup tables and the FPGA fabric. One catch applies to all hardware platforms: they can only run code that describes real circuits (“synthesizable” code). Testbench parts written as ordinary software, as many stimulus generators and scoreboards are, can’t move onto an FPGA, so the testbench has to be reworked or split between the hardware and a host computer. Why an FPGA compile takes so long, step by step, is covered in From hardware code to bitstream.

What prototyping costs, and how it fits the wider case for FPGAs, is covered in Where FPGAs win.

Gate-level simulation

After synthesis turns the RTL into a , a list of logic gates and the wires between them, teams rerun some tests on it. can ignore timing, or it can include each gate’s real delay, read from an SDF file produced by the timing tools. One reason to do it is unknown values. Simulators mark a signal whose value isn’t known, for example a storage bit that was never reset, with X. Standard RTL simulation sometimes quietly treats an X as a definite 0 or 1, so a missing reset can pass at RTL and show up only at gate level. Gate-level simulation is slow, so teams run a short list: reset and start-up, the chip’s manufacturing-test modes, clock gating, and a sample of functional tests.

Power-aware simulation

Many chips save power by switching off whole regions, called power domains, when they aren’t needed. The plan for this is written in a file. reads that file and models what happens when a domain turns off. Everything inside it becomes unknown. An on each of its outputs must hold a safe, fixed value so the unknowns don’t leak into blocks that are still on. Any must save their values before power-off and restore them after power-on. If the signal that switches the isolation cells on arrives late, unknown values leak into live logic, and power-aware simulation is where that shows up.

Choose the platform by the total time it takes to reach the cycle where the bug lives: compile time plus run time. Software simulation compiles fast and runs slowly, so it wins for short runs; FPGA-based platforms compile slowly and run fast, so they win for very long ones. Bugs also change character over a project. Early bugs show up within a few cycles, and what matters is fast turnaround and full visibility. Later, subtler bugs may need billions of cycles to appear, and on the fast platforms changing which signals are recorded usually means another long compile. Commercial emulators (Cadence Palladium, Siemens Veloce, Synopsys ZeBu) sit between the two extremes; Under the hood compares their architectures. The economics of FPGA prototypes, including rented cloud FPGAs, are in Where FPGAs win.

Moving onto hardware raises a testbench problem. A typical UVM or cocotb testbench is object-oriented software that no synthesis tool can turn into gates, so it can’t run on an FPGA. Teams either keep it on a host computer and connect it to the design in the emulator, or rewrite stimulus and checking as synthesizable hardware. Software-driven tests fit hardware platforms well: the chip’s own processor runs C programs that exercise the design, and the testbench mostly watches.

Gate-level and power-aware runs

(GLS) is kept for what RTL simulation and static checks miss. Because RTL simulation can be X-optimistic, an uninitialized flip-flop, a missing reset or an if-statement on an unknown condition can pass in RTL and fail at gate level. Zero-delay GLS also catches cases where synthesis read the code differently from the simulator, and exercises the test modes that later stages add. Runs annotated with SDF delays target what static timing analysis models poorly, such as asynchronous interfaces and reset release; they are not how timing is signed off. Expect noise in the other direction too: at gate level, an X that splits and rejoins through different gates can flag failures real silicon would never have, and each one needs analysis.

has to exercise the full power-down and power-up sequence in order: enable isolation, save retention state, switch power off, switch power on, restore retention state, release isolation. Static checkers find a missing , but not control logic that enables it a cycle late; that needs simulation. A single unknown value from a powered-off domain can spread to thousands of signals within a few cycles, which makes tracing a failure back to its cause slow. Simulation with UPF also takes significantly longer than plain RTL simulation, so teams aim it at tests that switch power modes.

1 min1 h1 day1 yr100 yrSimulationevery signal12 mincompile 10 min · 1 kHzEmulationhigh visibility1.0 hcompile 1.0 h · 1 MHzFPGA prototypefew signals8.0 hcompile 8.0 h · 10 MHz

Bug at 10⁵ cycles: Simulation 12 min, Emulation 1.0 h, FPGA prototype 8.0 h. Simulation wins.

Time to reach a bug = compile time (pale) + cycles ÷ run speed. Illustrative values for a large chip: simulation 10 min and 1 kHz, emulation 1 h and 1 MHz, FPGA prototype 8 h and 10 MHz. Log time axis.Share freely with credit: ‘Figure from chipfieldguide.com’
VDDswitch onswitchable domain1flop1retention–saved copypassisolation1always-on block11live flops1
1 / 7
Isolation enable

Running: both domains powered.

The power-down and power-up sequence that power-aware simulation checks. Isolation must clamp the domain’s outputs before the power goes off, and retention flip-flops save and restore their state.Share freely with credit: ‘Figure from chipfieldguide.com’

A big chip is checked in layers. Each small part is tested alone first, where it is easy to poke at odd cases. Then groups of parts are tested together. Finally the whole chip runs real programs, to check that everything is connected correctly.

Every night, computers rerun thousands of tests with fresh random choices. Each failure gets looked into. When is testing done? Never perfectly. The team stops when the checklist is complete, every test passes, and new bugs have become rare. Then reviewers sign off.

Three levels

A modern chip, often called a system on chip (SoC), is assembled from many blocks: processors, memory controllers, interfaces to the outside world. Each block is often called an IP (from “intellectual property”), because it may be bought or reused from another project.

  • Block level. Most random testing and formal proof happens here, because a single block’s inputs are easy to control and its outputs easy to check.
  • Subsystem level. Several blocks together, with the on-chip network that connects them and any shared memory. Tests target traffic between blocks, how blocks take turns on shared resources, and performance.
  • Full chip. The goal is integration: correct connections, clocks, resets, interrupts (signals that tell the processor something needs attention) and start-up. OpenTitan’s chip-level tests are C programs, compiled for and run on the chip’s own Ibex processor. Formal connectivity checks prove the chip-level wiring. Testbench parts written for a block get reused here in a watch-only mode, so they keep checking that block while its real neighbors drive it.

Regressions and bug tracking

A reruns the whole test suite with many seeds, usually every night and over weekends. OpenTitan publishes a nightly dashboard that shows pass rates and coverage against each item of its testplans, and files bugs in the design, in the test code and in the tools on its public issue tracker. Each failing run is triaged, which means sorted out: rerun the seed, find the first error, decide whether the design or the testbench is wrong, then file a bug or fix the test.

Signs of done

Verification can never prove a chip free of bugs, so teams sign off against written criteria. OpenTitan’s final stage requires 100% code and functional coverage (with waivers for anything left out), every test passing with every seed, and a final sign-off review of the checklist; the plan itself is reviewed face to face by designers, verification engineers, software engineers and architects at the start. Teams also watch how many new bugs turn up each week. It should fall and stay low even as new tests and seeds are added.

Vertical reuse is the economic case for the methodology. A UVM agent can run in active mode, where its driver generates traffic, or passive mode, where only its monitor runs. Agents written for a block run active at block level. At subsystem and chip level they run passive: their monitors, assertions and coverage keep checking that block while its real neighbors drive the traffic, and a whole block-level environment becomes a sub-environment of the larger one.

At chip level the stimulus shifts to software. Tests written in C and compiled for the on-chip processor exercise the integration, while the testbench supplies models of external devices and watches the pins. Connectivity moves to formal, where a spreadsheet of required connections becomes a set of proofs.

Sign-off is a staged checklist reviewed by people. OpenTitan’s simulation stages run as follows.

  • V1: testplan written and reviewed; the block instantiated in a testbench with its major interfaces connected; a sanity test passing; sanity and nightly regressions set up.
  • V2: every planned test written and passing in nightly multi-seed regressions above 90%; 90% code and functional coverage.
  • V2S: every security countermeasure tested.
  • V3: 100% coverage with waivers, and no failing seeds.

Teams add trend metrics on top: open bugs by severity, the rates at which bugs are found and closed, failure signatures (failures grouped by their first error message) with no owner, and how many weeks the regression has stayed clean. After silicon, every escaped bug gets a root-cause review that asks which plan item, checker or coverage point should have caught it.

chipon-chip networkCPUMemory ctrlDMAagent: activeUARTTestbenchdrives traffic
Level

Block level: the DMA alone. Its agent is active: the driver creates all its traffic. Most random tests and formal proofs run here.

Block, subsystem and full-chip verification. A block’s testbench parts are reused at higher levels in a watch-only mode, so they keep checking that block.Share freely with credit: ‘Figure from chipfieldguide.com’

The circuit below adds two numbers from 0 to 255. If the total would go over 255, it should stop at 255, the way a volume knob stops at max. But it has a hidden bug. When both numbers are 240 or more, the answer wraps around to a small number instead.

Press Run 100 to throw random tests at it. Watch the grid fill in, and count how many tests it takes before “Bug found” appears. Then press Reset, switch on Weight toward corners, and try again.

The design under test is an 8-bit saturating adder: it adds two numbers aa and bb, each 0–255, and if the true sum is above 255 it outputs 255 instead (it “saturates”). In short, it should compute min⁡(255,a+b)\min(255, a + b). A bug has been planted: when aa and bb are both 240 or more, the output wraps around to a small number instead of stopping at 255. An assertion compares every result with min⁡(255,a+b)\min(255, a + b).

The grid is the functional coverage. Each input is sorted into four ranges (0, 1–127, 128–239, 240–255), and the grid crosses the ranges of aa with those of bb to make 16 bins. The bug lives in exactly one bin. With plain random inputs, each test lands in that bin with probability (16/256)2=1/256(16/256)^2 = 1/256. With Weight toward corners on, the generator first picks one of the four ranges for each input, evenly, and then a value inside it, so the probability becomes 1/16. The directed tests (0 + 0, 100 + 100, 200 + 100 and 255 + 255) check chosen cases, and 255 + 255 fails at once. Press New seed to see how much luck the random runs need.

The adder computes min⁡(255,a+b)\min(255, a + b), except that it wraps when both aa and bb are 240–255. An assertion checks every result, and a 4 × 4 cross of input ranges (0, 1–127, 128–239, 240–255) gives 16 coverage bins, one of which holds the bug. Uniform random stimulus hits it with probability (16/256)2=1/256(16/256)^2 = 1/256 per test. Weight toward corners picks a range uniformly for each input, then a value inside it, raising that to 1/16. That is the difference between uniform over legal values and uniform over the cases that matter. The directed 255 + 255 test fails at once, and in this view the sim also shows the covergroup and assertion that express the same check. It is coverage-driven debugging in miniature: the empty bin in the grid tells you where to aim before any failure does. A formal tool would find the same counterexample in one query, because it considers all 65,536 input pairs at once.

Loading simulation…

Each morning, a verification engineer asks four questions. Did last night’s tests pass? Is anything new? Where are the gaps? Are bugs getting rarer? Even 99% passing can leave a hundred failures to look into.

Below is an illustrative cocotb testbench, in Python, for the saturating adder from the simulation. It follows the pattern of cocotb’s own adder example: a Python reference model, a directed test, and random tests. A few Python details help in reading it. dut is the design under test, and dut.a.value = a sets its input wire a. await Timer(1, unit="ns") lets one nanosecond of simulated time pass so the output can settle. assert stops the test with a message if the condition is false. The third test shows the corner-weighting idea from the simulation.

Below are a cocotb test, the matching SystemVerilog checker and covergroup, a SymbiYosys setup that proves the property, and a nightly regression summary. All are illustrative. The regression log is modeled on typical dashboards and is not the output of any one tool.

test_sat_add.py (illustrative, cocotb 2.x style)text
# test_sat_add.py: illustrative cocotb tests for an 8-bit saturating adder
import random

import cocotb
from cocotb.triggers import Timer


def model(a, b):
    """Reference model, written from the spec."""
    return min(255, a + b)


async def check(dut, a, b):
    dut.a.value = a
    dut.b.value = b
    await Timer(1, unit="ns")
    expected = model(a, b)
    assert dut.y.value == expected, f"{a} + {b}: got {dut.y.value}, expected {expected}"


@cocotb.test()
async def directed_cases(dut):
    for a, b in [(0, 0), (100, 100), (200, 100), (255, 255)]:
        await check(dut, a, b)


@cocotb.test()
async def random_uniform(dut):
    for _ in range(1000):
        await check(dut, random.randint(0, 255), random.randint(0, 255))


@cocotb.test()
async def random_by_bin(dut):
    ranges = [(0, 0), (1, 127), (128, 239), (240, 255)]
    for _ in range(1000):
        lo_a, hi_a = random.choice(ranges)
        lo_b, hi_b = random.choice(ranges)
        await check(dut, random.randint(lo_a, hi_a), random.randint(lo_b, hi_b))
  1. 1L8The reference model is one line of Python. It must come from the spec, never from reading the RTL, or it will copy the RTL’s mistakes.
  2. 2L16The adder has no clock or memory (it is combinational), so a short wait lets the output settle before checking.
  3. 3L18The check is the same for every test, independent of how the inputs were chosen.
  4. 4L23Directed test: hand-picked cases. 255 + 255 lands in the buggy corner and fails at once.
  5. 5L30Uniform random: each test hits the both-inputs-240-or-more corner with probability 1/256.
  6. 6L37Pick a range first, then a value inside it. Each of the 16 range pairs, including the corner, now comes up 1 time in 16.

The same checks in SystemVerilog, the language most industrial testbenches use. The checker module sits next to the design and samples its signals on every tick of a testbench clock. It holds three things: a concurrent assertion that compares the output with the specification, a covergroup that builds the 4 × 4 grid from the simulation, and a cover property that records whether the corner was ever reached.

sat_add_checks.sv (illustrative)systemverilog
// sat_add_checks.sv: checker instantiated next to the DUT in the testbench
module sat_add_checks (
  input logic       clk,
  input logic [7:0] a, b, y
);
  logic [8:0] full_sum;
  assign full_sum = a + b;

  // The output must equal min(255, a + b), sampled on every testbench clock
  property p_saturate;
    @(posedge clk) y == ((full_sum > 9'd255) ? 8'd255 : full_sum[7:0]);
  endproperty
  a_saturate: assert property (p_saturate)
    else $error("a=%0d b=%0d y=%0d", a, b, y);

  // Functional coverage: 4 ranges of a crossed with 4 ranges of b = 16 bins
  covergroup cg_inputs @(posedge clk);
    cp_a: coverpoint a { bins zero = {0};         bins low = {[1:127]};
                         bins high = {[128:239]}; bins top = {[240:255]}; }
    cp_b: coverpoint b { bins zero = {0};         bins low = {[1:127]};
                         bins high = {[128:239]}; bins top = {[240:255]}; }
    ab: cross cp_a, cp_b;
  endgroup
  cg_inputs cg = new();

  // Shows the corner was actually exercised, whatever the tests intended
  c_both_top: cover property (@(posedge clk) a >= 8'd240 && b >= 8'd240);
endmodule
  1. 1L6Nine bits hold the true sum, so the checker can see the overflow that the 8-bit output hides.
  2. 2L11The property restates the spec: on each rising clock edge, y must equal the saturated sum. When the bug fires (a, b ≥ 240) y wraps and this fails with the inputs printed.
  3. 3L18A coverpoint sorts a signal’s values into named bins. These mirror the simulation’s four ranges; the top bin is where the bug lives.
  4. 4L22The cross combines the two coverpoints into 16 bins. A coverage report with top × top unhit means the bug can’t have been found yet.
  5. 5L27A cover property counts how often the corner occurred. In formal, it must be reachable, or the proof above is suspect.

For formal, the same property becomes an immediate assertion in a small wrapper module, and SymbiYosys runs it as three tasks. The bounded task searches a fixed number of cycles for a counterexample, the prove task asks the PDR engine for a proof with no depth limit, and the cover task finds a trace that reaches each cover statement. On the buggy adder, bmc and prove both report a counterexample in the corner. After the fix, prove passes and cover still produces a trace, which shows the proof is not vacuous.

sat_add.sby (illustrative)text
[tasks]
bmc
prove
cover

[options]
bmc: mode bmc
bmc: depth 10
prove: mode prove
cover: mode cover

[engines]
bmc: smtbmc
prove: abc pdr
cover: smtbmc

[script]
read -formal sat_add.sv sat_add_props.sv
prep -top sat_add_props

[files]
sat_add.sv
sat_add_props.sv
  1. 1L1Three tasks share one setup. Each runs separately and reports PASS, FAIL with a trace, or an error.
  2. 2L8Bounded check to 10 cycles. Enough for an adder with no internal state; a pipelined block needs at least its pipeline depth plus the reset sequence.
  3. 3L10Cover mode generates the shortest trace that reaches each cover() statement, the standard guard against vacuous proofs.
  4. 4L14Unbounded proof with PDR through the ABC tool. With the smtbmc engine, prove mode would use k-induction instead.
  5. 5L18read -formal enables the assert, assume and cover statements in the property wrapper.

Finally, a nightly regression summary for a whole chip. Each row is a block: uart and dma are a serial port and a memory-copy engine, pcie_ss is the PCI Express subsystem that talks to a host computer, and soc_chip is the full chip. Read the pass rate per block first, then the failure signatures (failures grouped by their first error message), then the coverage holes. Every failure group should end up with an owner: a bug number, a testbench fix, or a ticket for the computing infrastructure.

nightly_regression.log (illustrative)log
=== Nightly regression: soc_top  build rc4 ===
Block       Tests  Seeds   Runs   Pass  Fail  Pass%  Line  Toggle   FSM   Func
uart           18    100   1800   1800     0  100.0  98.7    96.1  100.0  100.0
dma            42    100   4200   4163    37   99.1  95.2    91.8   97.5   93.4
pcie_ss        67     50   3350   3301    49   98.5  91.4    87.0   94.2   88.9
soc_chip       25      5    125    119     6   95.2     -       -      -   81.3
--------------------------------------------------------------------------------
Total         152          9475   9383    92   99.0

Failure signatures (bucketed by first error):
  31  dma: scoreboard data mismatch ch=3 desc_wrap=1       -> BUG-1182 (open, P1)
   6  dma: SVA a_no_overflow failed in fifo_ctl            -> BUG-1190 (new)
  44  pcie_ss: TIMEOUT waiting for link_up                 -> infra? rerun 3 seeds
   5  pcie_ss: UVM_ERROR reg model mismatch cfg_space      -> BUG-1171 (fixed in rc5)
   6  soc_chip: sw test dma_mem2mem returned FAIL 0x3      -> likely dup of BUG-1182

Functional coverage holes:
  dma.cg_desc.len_x_wrap            3/12 bins unhit (len=MAX with wrap)
  pcie_ss.cg_ltssm.trans            L1 -> Recovery never seen
Exclusions reviewed: 412 (UNR 377, NON_RTL 21, UNSUPPORTED 14)

Bugs per week, last 4 weeks:  opened 23, 19, 11, 6   closed 18, 21, 15, 12
  1. 1L1rc4 is release candidate 4: the fourth tagged snapshot of the RTL that this run tested.
  2. 2L2Line, Toggle and FSM are code coverage (lines run, signals switched, state-machine states visited); Func is functional coverage. Seeds per test vary by level: many at block level, where runs are cheap, few at chip level, where each run takes hours.
  3. 3L499.1% pass still means 37 failing runs. They collapse into two signatures below.
  4. 4L6Chip-level tests run software on the on-chip processor. Only functional coverage is tracked here; code coverage is closed at block level.
  5. 5L11A scoreboard mismatch with the channel and descriptor state printed: enough to reproduce with the failing seed. P1 is the highest bug priority.
  6. 6L12An assertion inside the design fired. Assertions point to a bug far faster than a mismatch seen only at the outputs.
  7. 7L13Timeouts are often testbench or infrastructure problems. Rerunning the seeds decides before anyone blames the RTL.
  8. 8L14The testbench’s model of the configuration registers disagreed with the design. Already fixed in the next snapshot, rc5.
  9. 9L15The chip-level software failure matches the block-level DMA bug. Recognizing duplicates saves a second investigation.
  10. 10L18A cross with unhit bins is a stimulus problem: the random rules rarely combine maximum length with address wrap-around.
  11. 11L19The PCI Express link’s state machine has never moved from its L1 low-power state to Recovery, so that path is untested.
  12. 12L20Every exclusion has a category and a reviewer. UNR means proven or argued unreachable; NON_RTL is simulation-only code; UNSUPPORTED is a feature this chip doesn’t use.
  13. 13L22Opened per week is falling, while closed has stayed ahead for the last three weeks: the trend reviewers want before sign-off.
1. Pass rate per blockuart1,800 runs0 fail100.0%dma4,200 runs37 fail99.1%pcie_ss3,350 runs49 fail98.5%soc_chip125 runs6 fail95.2%
1 / 4

9,383 of 9,475 runs passed (99.0%). The 92 failures sit in three blocks.

Reading a nightly regression: pass rate, failure signatures, coverage holes, bug trend. The numbers are the illustrative log in this section.Share freely with credit: ‘Figure from chipfieldguide.com’
  • Copying the answer from the design. If the “right answer” is worked out the same way the design works, the test agrees with every mistake. The right answer has to come from the plan.
  • Tests that can’t fail. Some tests run but never check anything, so they always pass. Teams plant bugs on purpose to make sure the alarms really go off.
  • Missing the rare case. Random tests mostly pick ordinary inputs. A bug hiding in a rare case can survive thousands of runs.
  • Stopping too early. Deadlines tempt teams to stop with gaps left. Reviewing a written checklist keeps them honest.
  • Shared misunderstanding. If the designer also writes the reference model, both carry the same misreading of the specification, and the test agrees with the mistake. Having a different person write the model, and reviewing the spec together, catches this.
  • Silent checkers. A scoreboard that compared nothing, or an assertion whose triggering condition never occurred, passes every time. Teams check that every checker saw traffic, and add coverage for each assertion’s trigger.
  • Thin coverage lists. Reaching 100% of a weak functional coverage list means little. If the plan leaves out a feature, so does the coverage, and nothing flags the gap.
  • Over-tight random rules. A rule that accidentally excludes a legal case makes it impossible to reach, however many seeds run. A coverage hole that never shrinks usually points here.
  • Hidden unknowns. Simulation that quietly turns unknown (X) values into 0s or 1s can hide a missing reset that only gate-level simulation exposes.
  • Noisy regressions. Flaky tests and infrastructure timeouts train people to ignore red results. Every failure group needs an owner.
  • Vacuous formal proofs. An assumption that is too strong, or an assertion whose triggering condition can never occur, proves nothing, yet it reports as passing. Formal tools flag such properties as vacuous, and OpenTitan’s formal checklist requires the assumptions to be specified and reviewed. Pair assertions with cover properties, and review assumptions as carefully as RTL.
  • Bounded results treated as proofs. A bounded pass to depth kk says nothing beyond cycle kk. Record the depth, compare it with how many cycles the design needs to reach its deepest states, and push for full proofs on critical properties; OpenTitan’s final formal stage requires every property proven.
  • Coverage without observation. Tests written only to reach uncovered lines raise code coverage without checking anything. A line being run (activation) is not the same as its error being seen at an output (observation).
  • Exclusion creep. Waivers added under schedule pressure turn real holes into “unreachable.” Require a category, a reason and a reviewer for each.
  • Platform divergence. FPGA prototypes change clocks, memories and how the design is split across chips, so a pass there is not a pass on the real RTL, and a failure there may be an artifact of the prototype.
  • Late power intent. Power sequences tested only late, at full-chip level, leave the timing of isolation and retention control exposed. Static tools catch missing cells, not wrong sequencing.
  • Spec drift. A spec change that never reaches the testplan and the coverage model leaves the regression green while it tests the old behavior.
250 + 250Designy = 255Reference modelexpects 255written from the specScoreboard255 = 255PASS
Reference answer
Planted bug

Both 255: pass. Plant a bug to prove this check can fail.

Two common traps: a reference model copied from the design, and a checker that never compares. Planting a known bug shows whether a check can fail at all.Share freely with credit: ‘Figure from chipfieldguide.com’

This part goes deeper, into the math, models and algorithms behind the chapter. It’s written for the Expert level.

Constraint solving for random stimulus

Every call that generates a random transaction (randomize() in SystemVerilog) hides two steps. First, solve a constraint-satisfaction problem: find values for the random fields that satisfy every rule. Second, sample: choose one solution among the many that exist, ideally without bias. Two families of solver dominate.

A (binary decision diagram) represents the set of all solutions at once, as a graph of yes/no decisions on each bit with two end nodes, “legal” and “illegal.” Weight each node by how many legal solutions lie below it, walk from the root choosing branches in proportion to those weights, and you land on every legal solution with equal probability, even when some fields are fixed in advance. The cost is size: a BDD for multiplication grows exponentially with the number of input bits whatever order the variables are in, and arithmetic is common in constraints.

A or SMT solver (SAT extended with arithmetic on fixed-width numbers) handles arithmetic far better, but it returns one solution per call, and which one depends on its search heuristics, so the results cluster. Generators spread them out by adding blocking constraints that forbid solutions already seen, or by combining the solver with random-walk samplers. A further family adds random hash constraints (XOR formulas over the variables) that slice the solution space into small, roughly equal cells, then picks a cell and a solution inside it. Hashing-based samplers have produced near-uniform stimuli from constraints with more than 100,000 variables. UniGen2 keeps the theoretical guarantees, runs about 20 times faster than the previous state of the art on one core, and speeds up almost linearly with more cores.

Uniformity is not the real goal, though. Uniform over solutions starves control decisions that have few solutions behind them, such as a rare operation or the top range of an operand. Weighted distributions, solve-before ordering and scenario layers bend the distribution toward the coverage model, which is exactly what the simulation’s corner weighting does.

Coverage metrics, and what they can’t tell you

Manufacturing test has a simple model of what goes wrong (a wire stuck at 0 or at 1), so it can measure what fraction of possible faults a test set detects. Design errors have no such good model, so nobody can prove that a coverage metric tracks the bugs that remain. Every metric is a stand-in, and a way to decide when to stop.

Code metrics came from software testing and measure controllability: was the statement activated? A bug is detected only if its effect also travels to an output that something checks. OCCOM added that observability. Conceptually, it places a tag on the value assigned in each statement to stand for “this value might be wrong,” then asks whether the tag reaches an output. The share of tags that do is the coverage. Instead of simulating the tags directly, it runs a normal simulation of a slightly rewritten design and then traces tags through a flow graph of the code, which is far cheaper than earlier tag simulators. The result judges a test set more accurately than line coverage.

Structural metrics also make automated test generation possible. RFUZZ defines mux-control coverage: every two-input multiplexer (a switch that passes one of two signals) counts as covered when its select signal takes both values within one test. Small extra circuits collect this on an FPGA, and coverage-guided mutational fuzzing, borrowed from software security testing, mutates inputs that reached new coverage to find more. Functional coverage remains the engineer’s statement of intent, and code coverage is a check that the intent missed nothing structural. Neither replaces the other.

Model checking: from BDDs to SAT

For a formal tool, a design is a transition system. Write xx for the values of all its flip-flops (its state) and ii for its inputs in one cycle. Then I(x)I(x) is a formula that is true for the initial states (after reset), T(x,i,x′)T(x, i, x') is true when the design can move from state xx to state x′x' under input ii in one clock cycle, and P(x)P(x) is the safety property, true in every good state. asks whether any state where PP is false can be reached from II.

Symbolic model checking with BDDs computes the set of reachable states directly. Start with the initial set, add every state reachable in one more step, and repeat until the set stops growing. It can handle more than 102010^{20} states, but its BDDs become too large beyond a few hundred state variables, and the result depends on a variable order that is costly to find and for many designs doesn’t exist in a compact form.

replaces BDDs with SAT. It copies TT once per cycle, kk times, and asks whether I(x0)∧T(x0,x1)∧⋯∧T(xk−1,xk)∧¬P(xk)I(x_0) \land T(x_0, x_1) \land \dots \land T(x_{k-1}, x_k) \land \lnot P(x_k) can be satisfied: is there a start state, and a sequence of inputs, that leads to a bad state at step kk? A satisfying assignment is a counterexample. Trying k=0,1,2,…k = 0, 1, 2, \dots in turn yields the shortest one. BMC finds bugs fast thanks to the depth-first way SAT solvers search, uses much less memory than BDDs, and needs no variable order. Its weakness is completeness: “no violation up to kk” says nothing about step k+1k + 1.

closes the gap in two parts. The base case is BMC to depth kk. The step case asks whether any path of k+1k + 1 states, where PP holds in the first kk, can violate PP in the last. Crucially, the path may start from any state at all, not only from reset. If no such path exists, PP holds forever. Because the step ignores reachability, it can fail on a path that starts in a state the design never reaches. Requiring all states on the path to differ (loop-free paths) makes the method complete for finite designs, though kk may then need to grow as long as the longest such path. SymbiYosys’s smtbmc engine runs k-induction in prove mode, with a default depth of 20. In practice, engineers add helper assertions that rule out the unreachable states, so a small kk is enough.

reset01234567P falsereachable from resetnever reached from resetPASS (bounded, k = 2)explored in ≤ 2 steps
Engine

BMC, k = 2: no path of 2 steps or fewer from reset reaches 7, so “PASS (bounded)”. It says nothing about step 3.

A 3-bit counter that wraps at 3, with property P: x ≠ 7. States 4–7 can’t be reached from reset, but the induction step doesn’t know that. Compare BMC with k-induction, then add the helper assertion x < 4.Share freely with credit: ‘Figure from chipfieldguide.com’

avoids unrolling altogether. It maintains a series of frames F0=I,F1,…,FkF_0 = I, F_1, \dots, F_k. Each FiF_i is a formula true in at least every state reachable within ii steps (an over-approximation), each frame contains the one before it, and PP holds in all of them. Step by step:

  1. Ask SAT whether some state ss in the last frame FkF_k can step into a state that violates PP.
  2. If so, try to block ss: find the largest frame FiF_i from which ss can’t be reached in one step, shrink “not ss” to a small clause that still holds, and add that clause to frames F1F_1 to Fi+1F_{i+1}.
  3. If ss can be reached from the previous frame, its predecessor must be blocked first, so recurse. If a chain of predecessors leads back to the initial states, that chain is a real counterexample.
  4. When no bad state remains in FkF_k, add a new empty frame and push each clause forward to later frames wherever it still holds.
  5. If two neighboring frames end up with the same clauses, that frame is an inductive invariant: a fact that holds initially, is preserved by every step, and implies PP. PP is proven.

A typical run makes many tens of thousands of cheap, incremental SAT queries, and IC3 placed third in the 2010 hardware model checking competition. Een, Mishchenko and Brayton named the method property directed reachability (PDR). They found it stronger than interpolation, the previous leading method, on industrial problems, and able to find deep counterexamples, though BMC finds counterexamples better on average.

Production formal tools therefore run portfolios: BMC to find bugs, k-induction and PDR for proofs, and simplifications in front, such as cutting away all logic that can’t influence the property (cone-of-influence reduction). SymbiYosys exposes the same idea through its list of engines: smtbmc, abc pdr, and AIGER back-ends for other model checkers.

Emulation architectures

The first purpose-built engines, IBM’s Yorktown Simulation Engine and Engineering Verification Engine, were arrays of simple 4-bit processors joined by a crossbar switch. Processor-based commercial emulators descend from them. They break the gate-level design into instructions and schedule those across many simple processors, so compiling is quick and every signal can be recorded without recompiling, at up to a few MHz. The price is very expensive hardware, in the millions of dollars.

FPGA-based emulators and prototypes instead map the design onto commercial FPGAs. Splitting the design across many devices, then running synthesis and place-and-route for each, makes compiles slow, and observing different signals usually means re-synthesizing. In return, run speed is high and the hardware is cheaper. Research platforms explore the space between. Cyclist maps RTL operators (adders, multiplexers, registers) rather than individual gates onto a mesh of small RISC-like processors, aiming for near-FPGA speed with software-like compile times and full visibility. Every hardware platform shares one limit: the design and anything mapped with it must be synthesizable, so stimulus and checking either become synthesizable hardware or stay on a host computer.

Novice · 0 of 5 correct
  1. Q1In a testbench, what does the scoreboard do?

  2. Q2In the testbench simulation, the inputs aa and bb are each picked evenly from 0–255, and the bug fires only when both are 240 or more. What is the chance one random test hits the bug?

  3. Q3A block’s tests have run every line of its code (100% line coverage), but the functional coverage report shows several planned combinations never reached. What does that mean?

  4. Q4Why does every random test record its seed?

  5. Q5How does formal verification differ from simulation?

Sources

Show Hide 27 sources
  1. OCCOM: Efficient Computation of Observability-Based Code Coverage Metrics for Functional VerificationFarzan Fallah, Srinivas Devadas, Kurt Keutzer · DAC 1998 (author-hosted, MIT) · 1998Verification as the major bottleneck; verification staff often larger than implementation staff; initial RTL the hardest problem; simulation as the workhorse; line coverage ignores observability; tag-based observability metric.
  2. Functional Test Generation for Behaviorally Sequential ModelsF. Ferrandi, G. Ferrara, D. Sciuto, A. Fin, F. Fummi · DATE 2001 (open proceedings archive) · 2001Functional verification as the most resource-consuming part of design; exhaustive simulation infeasible; no good models for design errors, so coverage metrics are approximations and stopping criteria.
  3. Intel 1994 Revenue, Earnings Per Share Set Records (earnings release, SEC exhibit)Intel Corporation · Intel investor relations (intc.com) · 1995A one-time $475 million pretax charge in Q4 1994 covered replacement and other costs of a divide problem in the Pentium floating-point unit.
  4. Intel’s $475 million error: the silicon behind the Pentium division bugKen Shirriff · Ken Shirriff’s blog (righto.com) · 2024Kept for the silicon-level cause (the cost figure now also cites Intel’s own release). Pentium division bug caused by entries missing from a lookup table; reported by Thomas Nicely in 1994; Intel replaced the chips at a cost of $475 million.
  5. Design Verification Methodology within OpenTitanlowRISC and OpenTitan contributors · OpenTitan documentationUVM-based constrained-random DV, Hjson testplans and coverage plans, checks independent of stimulus, 100 seeds per test, nightly regressions, code and functional coverage (the AND-gate example), exclusion categories, pessimistic X-propagation by default, chip-level C tests on Ibex, face-to-face plan reviews, issue tracking.
  6. OpenTitan Development Stages: Hardware Verification StageslowRISC and OpenTitan contributors · OpenTitan documentationV1/V2/V2S/V3 checklists: 90% code and functional coverage at V2, 100% with waivers at V3; FPV criteria including COI coverage, proven properties and reviewed assumptions.
  7. OpenTitan Assertions and Formal VerificationlowRISC and OpenTitan contributors · OpenTitan documentationFormal property verification with JasperGold or VC Formal; reports of proven, vacuous, covered and failing assertions; symbolic variables with two assumptions; formal connectivity checks from a CSV specification.
  8. Universal Verification Methodology (UVM)Accellera Systems Initiative · AccelleraUVM reference library for IEEE 1800.2; improves interoperability and makes verification components easier to reuse.
  9. Universal Verification MethodologyWikipedia contributors · WikipediaUVM components: agent, driver, sequencer, monitor, scoreboard, factory; IEEE 1800.2.
  10. Universal Verification Methodology (UVM) 1.2 User’s GuideAccellera Systems Initiative · Accellera · 2015An agent groups sequencer, driver and monitor for one DUT interface and runs active or passive; use a passive agent for every RTL device to be verified; factory type overrides change a component without modifying its parent; vertical reuse turns a top-level environment into a sub-environment.
  11. IEEE 1800-2023: IEEE Standard for SystemVerilogIEEE Standards Association · IEEE · 2023Scope: SystemVerilog is a unified hardware design, specification and verification language that supports test benches using coverage, assertions, object-oriented programming and constrained random verification.
  12. cocotb documentationcocotb contributors · cocotb projectA coroutine-based cosimulation testbench environment for verifying HDL designs in Python, connected through VPI, VHPI or FLI.
  13. Simulator Supportcocotb contributors · cocotb projectIcarus Verilog, Verilator, GHDL, NVC and commercial simulators (VCS, Xcelium, Questa, Riviera-PRO) through VPI, VHPI or FLI.
  14. cocotb adder example: test_adder.pycocotb contributors · GitHub (cocotb/cocotb)A directed test and a randomized test, each comparing the DUT output with a Python reference model.
  15. Verilator OverviewWilson Snyder and Verilator contributors · Verilator documentationVerilator is a compiler, not a traditional simulator: it turns SystemVerilog into a multithreaded C++ or SystemC model.
  16. Input LanguagesWilson Snyder and Verilator contributors · Verilator documentationMostly two-state; X assignments become constants chosen by --x-assign; state starts at random values; partial support for assertions and covergroups.
  17. SymbiYosys ReferenceYosysHQ · SymbiYosys documentationModes bmc, prove, cover and live; cover generates the shortest traces reaching each cover statement; smtbmc does k-induction in prove mode (default depth 20); abc pdr; AIGER back-ends.
  18. Getting started (EQY documentation)YosysHQ · YosysHQ EQY documentationEQY formally proves two designs equivalent, for example that a synthesis tool introduced no functional change or that a refactor preserved correctness.
  19. Symbolic Model Checking without BDDsArmin Biere, Alessandro Cimatti, Edmund Clarke, Yunshan Zhu · TACAS 1999 (author copy, co-author Edmund Clarke’s Carnegie Mellon course page) · 1999BDD-based symbolic model checking handles more than 10^20 states but only hundreds of state variables, and depends on variable ordering; bounded model checking with SAT finds counterexamples fast, of minimal length, in less space and without a variable order.
  20. SAT-based verification (BMC, temporal induction)Mary Sheeran · Chalmers University of Technology, course TDA956 lecture slides · 2012Temporal (k-)induction with a SAT solver: base case, step case, loop-free paths via the unique-states condition; sound and complete.
  21. SAT-Based Model Checking without UnrollingAaron R. Bradley · VMCAI 2011 (author-hosted, Stanford) · 2011IC3: relatively inductive clauses over stepwise over-approximations F0..Fk; converges when adjacent frames match; many tens of thousands of small SAT queries; third at HWMCC’10.
  22. Efficient Implementation of Property Directed ReachabilityNiklas Een, Alan Mishchenko, Robert Brayton · FMCAD 2011 (proceedings copy, UT Austin) · 2011Names IC3’s method PDR; stronger than interpolation on industrial problems; finds deep counterexamples, though BMC does better on average.
  23. SMT-based Stimuli Generation in the SystemC Verification LibraryRobert Wille, Daniel Große, Finn Haedicke, Rolf Drechsler · FDL 2009 (author-hosted, University of Bremen) · 2009BDD constraint solvers sample weighted 1-paths for uniform stimuli but blow up on multipliers whatever the order; SMT returns one solution, so distribution needs blocking constraints and other strategies; for an ALU, uniform over solutions is not uniform over operations.
  24. On Parallel Scalable Uniform SAT Witness GenerationSupratik Chakraborty, Daniel J. Fremont, Kuldeep S. Meel, Sanjit A. Seshia, Moshe Y. Vardi · TACAS 2015 (author-hosted) · 2015Constrained-random verification is widely used in industry; hashing-based near-uniform stimuli from 100,000+ variables; UniGen2 about 20× faster on one core, near-linear speedup with more cores.
  25. RFUZZ: Coverage-Directed Fuzz Testing of RTL on FPGAsKevin Laeufer, Jack Koenig, Donggyu Kim, Jonathan Bachrach, Koushik Sen · ICCAD 2018; author copy archived by the Wayback Machine (DOI 10.1145/3240765.3240842) · 2018High code coverage indicates progress but is not sufficient; coverage-directed generation approaches are narrow or need expert time; mux-control coverage; non-synthesizable stimulus and scoreboards can’t easily move onto FPGAs.
  26. Cyclist: Accelerating Hardware DevelopmentJonathan Bachrach, Albert Magyar, Palmer Dabbelt, Patrick Li, Richard Lin, Krste Asanović · ICCAD 2017; author copy archived by the Wayback Machine (DOI 10.1109/ICCAD.2017.8203892) · 2017Compile-time vs run-time trade-off of software simulation, FPGA emulation and processor-based emulators; FPGA visibility needs re-synthesis; early vs late bugs; YSE/EVE lineage; processor-based emulators up to 4 MHz and priced in the millions.
  27. Automation for Early Detection of X-propagation in Power-Aware Simulation Verification using UPF IEEE 1801Tony Gladvin George, Ramesh Kumar, Kyuho Shim, Karan K, Wooseong Cheong, ByungChul Yoo · DVCon proceedings (Samsung Electronics authors)Isolation and retention in power domains, the power-down/up sequence, one X spreading to thousands of signals within a few cycles, slower UPF simulation, and static checks that find missing isolation cells but not control sequencing.