Skip to content

UVM

Directed Testing

Directed testing — the arrange-act-expect-check pattern, when hand-written tests are the right tool, why they don't scale, and how they silently lie.

Verification Foundations · Module 1 · Page 1.6

The Engineering Problem

The previous lesson called directed testing the scalpel — precise, narrow, the tool you reach for when you know exactly what you want to check. Now we go deep on it, because it is both the oldest verification methodology and the one every engineer reaches for first — and understanding precisely why it works and exactly where it breaks is the foundation for everything that follows.

Directed testing means you hand-write a test for a specific scenario you already have in mind. You construct the situation deliberately, apply a known stimulus, and check a known response. Its great virtue is total control: the test does exactly what you wrote, every time, deterministically.

That same virtue is its ceiling. Your effort grows with the number of scenarios you can think of and type; the design's corner space grows combinatorially. So the engineering problem of this lesson is two-sided:

When is hand-construction exactly the right tool — and where, precisely, is the wall it cannot climb?

Motivation — why directed testing exists and never goes away

Even in a modern constrained-random shop, directed tests never disappear. Four properties keep them essential:

  • Total control. You decide every input, every cycle. When a scenario is precise — "release reset, write 0xA5, read it back" — directed testing constructs it directly instead of hoping random stimulus stumbles into it.
  • Determinism. A directed test does the same thing on every run. That makes it the ideal regression net: reproduce a bug once as a directed test, and it guards that corner forever.
  • Readability. A directed test is its scenario, written out in order. A new engineer reads it top to bottom and knows exactly what is being checked — invaluable for spec-mandated sequences and for documentation-by-test.
  • Spec mandates and errata. Some scenarios must be checked exactly: a protocol's required power-on handshake, a published erratum, a safety requirement. For these, "construct it precisely" beats "generate it randomly" every time.

The motivation, then, is not that directed testing is the whole answer — the previous lesson showed it is blind to the unimagined — but that for the slice of work that is known and specific, nothing is more direct, more readable, or more reliable.

Mental Model

Hold this picture:

A directed test is a script you wrote for a play whose plot you already know. It has four beats, always in the same order: Arrange (get the DUT into a known starting state), Act (apply the one exact stimulus this test exists to check), Expect (compute the spec-defined correct response), and Check (compare actual against expected, and fail loudly if they differ). Every good directed test is these four beats and nothing else — self-contained, deterministic, and readable as the scenario it represents.

The discipline is keeping the four beats clean: a known precondition (not "whatever the last test left behind"), a single deliberate stimulus (not a tangle), a spec-derived expected value (not one you eyeballed from the waveform), and a check that actually executes and actually fails on mismatch.

Visual Explanation — the anatomy of a directed test

Every directed test, in any language or framework, is the same four-beat data flow: drive the DUT to a known state, apply the stimulus, and compare its output against an independently-computed golden response.

Directed test anatomy — arrange, act, DUT, expected golden from spec, check compareknown preconditionexactstimulusactual outputgolden (from spec)Arrangereset / set up a knownstateActapply the one deliberatestimulusDUTExpected (spec)independently-computedgoldenCheckcompare actual vs golden; failon mismatch12
Figure 1 — the anatomy of a directed test. Arrange establishes a known precondition; Act applies the one deliberate stimulus; the spec gives an independently-computed Expected (golden) value; Check compares the DUT's actual output against that golden and fails loudly on mismatch. The golden value must come from the spec, not from the DUT — a check against the design's own output proves nothing.

The non-negotiable detail is the Expected node sitting beside the DUT, fed from the spec — not from the design. The most common way a directed test lies is checking the DUT against a value derived from the DUT's own behaviour: such a test passes whether the design is right or wrong. The golden value has to come from an independent source — the specification, a reference model, or a hand-calculation traceable to the spec.

RTL / Simulation Perspective — a directed test, beat by beat

Take a synchronous FIFO (depth 4) and verify one precise scenario: write a single known value and read it straight back. Watch the four beats map directly onto the code.

a directed test — arrange, act, expect, check
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// DUT: synchronous FIFO, depth 4. Scenario: single-element write then read.
initial begin
  // ── ARRANGE ── drive the DUT into a known precondition (empty FIFO)
  rst_n = 0; wr_en = 0; rd_en = 0; din = '0;
  @(posedge clk); @(posedge clk);
  rst_n = 1;                       // reset released → FIFO empty, fully known state
  @(posedge clk);
 
  // ── ACT ── apply the one deliberate stimulus this test exists to check
  din = 8'hA5; wr_en = 1;          // write a single known value
  @(posedge clk);
  wr_en = 0;
  rd_en = 1;                       // read it straight back out
  @(posedge clk);
 
  // ── EXPECT + CHECK ── compare against the spec-defined golden response
  rd_en = 0;
  if (dout !== 8'hA5)              // golden 0xA5 comes from the scenario, not the DUT
    $fatal(1, "single-element R/W failed: got %h, expected A5", dout);
  $display("PASS: single-element write-then-read");
  $finish;
end

Read it as the four beats: Arrange holds reset to guarantee an empty FIFO; Act writes 0xA5 and then reads; Expect is the literal 8'hA5 (derived from the scenario, independent of the DUT); Check is the !== comparison with a loud $fatal on mismatch. Notice how concrete and readable it is — and notice that it verifies exactly one path. The full FIFO is correct only if you also check empty-read, full-write, wrap-around, simultaneous read+write, and reset-while-busy. Each is another hand-written test. That multiplication is the wall.

Verification Perspective — the scaling wall

Directed testing's cost is linear in the scenarios you write; the design's behaviour space is the product of its independent features. Those two curves diverge fast.

Directed test count grows linearly while the design corner space grows combinatoriallyWhy directed testing stops scaling — addition vs multiplicationWhy directed testing stops scaling — addition vs multiplication11 feature × 3 corners≈ 3 hand-written tests — easy, readable, done in an afternoon.2+ a 2nd independent featurenow 3 × 3 = 9 crosses to cover the interaction, not 3 + 3 = 6.3+ reset / error / width modeseach mode multiplies again — hundreds of crosses, still each onetyped by hand.4+ concurrency & orderinginterleavings explode the product past any team's test-writingbudget.
Figure 2 — the directed-testing scaling wall. Each independent feature multiplies the corner space; directed effort only adds one hand-written test at a time. A couple of features with a few corners each is tractable by hand; cross a handful of features with modes, widths, and ordering and the product runs past any team's test-writing budget. Directed effort grows by addition; the corner space grows by multiplication.

This is the structural limit, and it is arithmetic, not a matter of effort or skill. Directed tests add coverage one scenario at a time; real designs multiply their corner space with every independent feature, mode, and timing relationship. Long before you exhaust the corners, you exhaust the engineer-hours — and the corners you never reached are exactly where the escaping bugs live. (This is the precise problem the next lesson, random testing, exists to attack.)

Runtime / Execution Flow — directed tests as the regression net

The place where directed testing is uniquely irreplaceable is regression. Every bug you find — in review, in simulation, or in silicon — should become a directed test that reproduces it, so the corner can never silently come back.

Directed regression loop — reproduce bug, add to suite, rerun on every change, fix failures, corner stays closednorerunyesBug found(review, sim, orsilicon)Write a directedtest that reproducesitAdd it to theregression suiteRerun regression onevery RTL changeAll directedtests pass?Fix the regressionCorner staysclosed — nosilentregression
Figure 3 — directed testing as the regression net. A found bug is reproduced as a deterministic directed test and added to the suite; the suite reruns on every RTL change. If a directed test fails, the regression is fixed and the suite reruns (the dashed loop); when all pass, that corner stays permanently closed. Determinism is what makes this work — a flaky test cannot guard a corner.

This is the role directed testing keeps forever, even in a fully constrained-random environment: the deterministic, readable record of every corner that must never break again. Random stimulus is excellent at finding the bug the first time; a directed regression test is what guarantees it stays found. The two methodologies are complementary in exactly this way — discovery versus permanence.

Waveform Perspective — the deterministic, hand-crafted stimulus

A directed test's signature is on the waveform: the stimulus is deliberate and repeatable, with every transition placed by the engineer. Here is the FIFO test above, cycle by cycle.

A directed test drives a precise, repeatable sequence — every edge placed by hand

10 cycles
A directed test drives a precise, repeatable sequence — every edge placed by handArrange: reset released — FIFO empty, a fully known preconditionArrange: reset release…Act: write the one known value 0xA5Act: write the one kno…Check: dout == 0xA5, exactly the spec-defined goldenCheck: dout == 0xA5, e…clkrst_nwr_endin000000A5000000000000rd_endout000000000000A5A50000emptyt0t1t2t3t4t5t6t7t8t9
Figure 4 — reset is held low for two cycles then released to guarantee an empty FIFO (the known precondition); at cycle 3 the test writes the known value 0xA5 (empty falls); at cycle 5 it reads, and at cycle 6 dout presents exactly 0xA5 — the golden value the check compares against. Nothing here is random: the same edges occur on every run, which is what makes the test a reliable regression guard.

Compare this to the random waveform from the previous lesson, where overlaps emerged unpredictably. Here every edge is intentional and identical run to run. That determinism is directed testing's superpower for regression — and, simultaneously, its limitation: it only ever exercises the one path you drew. Breadth has to come from somewhere else.

DebugLab — the directed test that passed for the wrong reason

Green for two years — then the corner it 'guarded' shipped a bug

Symptom

A directed test named test_fifo_overflow_drop had been passing in regression for two years. It was supposed to verify that writing to a full FIFO drops the new word and never corrupts the stored data. A silicon bug then showed exactly that corruption — on the corner this test was named for. The test was green on the very RTL that was broken.

Root cause

The Arrange beat had silently rotted. A refactor a year earlier had retimed the full flag by one cycle. The test's setup loop wrote four words to fill the depth-4 FIFO — but after the retime, the fourth write now landed one cycle later, so at the moment the test performed its "overflow" write, the FIFO was not yet full. The write was accepted normally, dout later read back correctly, and the check passed — but it was checking an ordinary write, not the overflow corner. The precondition the whole test depended on had quietly stopped being true:

what the test believed vs what actually happened
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
intended precondition:  FIFO full (4/4)  →  5th write must be DROPPED
actual precondition:    FIFO 3/4 (retimed full flag)  →  5th write ACCEPTED normally
the check (dout == expected):  PASS  ← but it never exercised the overflow path
overflow corner actually tested:  NONE for ~12 months

The check was fine. The stimulus no longer created the scenario the check assumed.

Diagnosis

The tell is a passing test with no evidence it reached its own corner. Diagnose directed-test rot by making each beat prove itself:

  1. Assert the precondition. Before the overflow write, assert full === 1'b1. Had this existed, the test would have failed the day the retime broke it — pointing straight at the real change.
  2. Check the side-effect, not just the survivor. The test only checked that stored data was intact; it never checked that the dropped word's write was actually rejected (e.g., a dropped count or that wr_ack deasserted). A directed test must observe the behaviour it is named for, not a bystander signal.
  3. Cover the corner. A single functional-coverage bin on "write while full" would have read zero hits for a year — exposing that the named corner was never being exercised, regardless of the green result.
Prevention

A directed test is only as good as its weakest silent beat. Make rot impossible to hide:

  1. Assert your preconditions. Every Arrange beat that the test depends on should be checked, not assumed — a precondition that silently breaks turns a real test into a tautology.
  2. Derive the golden independently. Expected values must trace to the spec or a reference model, never to the DUT's own output — a self-referential check passes on broken silicon.
  3. Back directed tests with a coverage bin. Let coverage confirm the named corner is actually being hit; a zero-hit bin on a "passing" test is the loudest possible warning.
  4. Check the effect you claim to verify. Observe the specific behaviour (the drop, the flag, the count) — not a nearby signal that happens to look right.

The one-sentence lesson: a directed test that does not verify it reached its own corner is not a test — it is a tautology waiting to go green on a bug.

Common Mistakes

  • Checking the DUT against itself. Deriving the expected value from the design's own output (or a model that shares the design's bug) makes the test pass regardless of correctness. The golden value must come from the spec or an independent reference.
  • Assuming the precondition instead of asserting it. If the Arrange beat silently stops creating the intended state (after a refactor, a retime, a port rename), the test keeps passing while testing nothing. Assert the precondition you depend on.
  • Relying on leftover state between tests. A directed test that depends on what the previous test left behind is non-deterministic and order-sensitive — the opposite of directed testing's whole value. Each test must establish its own known precondition.
  • Treating directed testing as the primary strategy on a large block. Hand-writing one test per corner cannot keep up with a combinatorial space. Directed testing is for the specific, known, high-value scenarios and for regression — breadth must come from constrained-random.
  • A check that never executes. A $finish before the comparison, a branch that skips it, or a stimulus that ends early can leave the check unreached — and an unreached check always "passes." Make the check unconditional and loud.

Senior Design Review Notes

Interview Insights

Directed testing is hand-writing a test for a specific scenario you already have in mind: you arrange a known precondition, apply an exact stimulus, compute the spec-defined expected response, and check the DUT against it. Its strengths are total control, determinism, and readability — the test does exactly what you wrote, identically every run, and reads as the scenario it represents, which makes it ideal for spec-mandated sequences and for regression. Its weakness is scaling: effort grows linearly with the scenarios you can write, while the design's corner space grows combinatorially, so directed testing cannot, by itself, cover a large design — and it is blind to the corners you never thought to write.

Exercises

  1. Label the beats. Take the FIFO code in this lesson and annotate exactly which lines are Arrange, Act, Expect, and Check. Then identify the single line you would add to make the test assert its precondition, and explain what failure that line would have caught.
  2. Find the self-referential check. Sketch a directed test for an ALU's ADD that would pass on a broken adder, and then rewrite the Expect beat so it cannot. State in one sentence the rule your rewrite enforces.
  3. Estimate the wall. A block has 4 independent features, each with 4 corners, plus 3 reset/error modes that each interact with all of them. Estimate the number of crosses a thorough directed suite would need, and argue whether hand-writing them is realistic — and what you would do instead.
  4. Design a regression test. A silicon bug corrupts stored data when a FIFO write and read land on the same cycle while full. Write the four beats (in prose) of a deterministic directed test that reproduces it, including how you would prove the test actually reaches the full-and-simultaneous corner.

Summary

  • A directed test is a hand-written, four-beat scenario — Arrange a known precondition, Act with one deliberate stimulus, Expect a spec-derived golden, Check loudly — that trades breadth for total control, determinism, and readability.
  • It is the right tool for the specific, known, high-value slice: spec-mandated sequences, reproduced bugs, named corners, and especially regression, where a deterministic test locks a found bug closed forever.
  • It does not scale as a primary strategy: directed effort grows by addition while the corner space grows by multiplication, so you exhaust engineer-hours long before the corners — and remain blind to the scenarios you never wrote.
  • A directed test can silently lie — a self-referential golden, a rotted precondition, or an unreached check all stay green on a bug. Make each beat prove itself: derive the golden independently, assert the precondition, back it with a coverage bin, and check the effect you actually claim to verify.
  • The durable rule of thumb: construct what is known and specific with directed tests; explore what is broad and unknown with something else — and never let a directed test pass without proving it reached its own corner.

Next — Random Testing: directed testing's wall is the unimagined corner. The next lesson takes up the methodology built to climb it — generating legal-but-unpredictable stimulus that reaches the states no one would ever hand-write.