Directed verification is the practice of writing test cases that target a specific, known scenario — with every input value, timing relationship, and expected output explicitly defined by the engineer. Understanding what directed tests are good for, and where they fall short, helps clarify why the industry has largely moved toward constrained random environments for functional verification.

12 min read | Beginner Level

What You'll Learn

  • What directed verification is and how it differs from constrained random testing
  • Who writes directed tests and why — designers vs verification engineers
  • When directed tests are genuinely useful and when they fall short
  • How to structure a directed testbench in SystemVerilog with proper output checking

What is Directed Verification?

In directed verification, the engineer writes test cases where every input value, every timing decision, and every expected output is hardcoded. There is no randomness. You know the exact scenario you want to exercise, and you write precisely that.

This is the opposite of constrained random verification, where a constraint solver generates legal stimulus automatically across thousands of seeds, and functional coverage measures how much of the design's behavior has been exercised. Directed tests skip all of that — the author already has a specific scenario in mind and writes it directly.

The source for directed tests is always the design specification. A spec describes required behaviors: "when reset is deasserted, the FIFO must be empty," "a read after a write to the same address must return the written value," "if a request arrives while the arbiter is busy, it must be queued and served within 8 cycles." Each of these is a scenario that can be written as a directed test.

Who Writes Directed Tests

Both RTL designers and verification engineers write directed tests, but for different reasons at different points in the project.

RTL designers write directed tests as a quick sanity check before releasing their block to the verification team. These are typically a handful of tests that confirm the most obvious behaviors work: does reset clear the registers, does a basic read/write round-trip produce the right value, does the output flag assert in the obvious case? They are not intended to be comprehensive — they are a confidence check that the RTL is worth handing over. Because these tests are written by the same person who wrote the RTL, they share the same mental model of the design, which means they also share the same blind spots.

Verification engineers write directed tests deliberately and strategically throughout the project — often for scenarios that are difficult or impossible to reach reliably with a randomized environment:

  • Specific protocol sequences that require an exact chain of events to set up, such as AXI exclusive access or a multi-phase handshake, where constraining a random generator to produce the sequence reliably would be complex and fragile.
  • Coverage hole closure — after thousands of random seeds, specific functional coverage bins remain unhit. Rather than running more seeds with diminishing returns, the VE writes a directed test that forces exactly that scenario.
  • Bug regression tests — when a bug is found (by random testing or otherwise), a directed test is written to reproduce the exact failing conditions. This stays in the regression suite permanently.
  • Timing corner cases where two events must coincide in the same clock cycle — such as a reset arriving mid-burst, or a FIFO transitioning to full in the same cycle a simultaneous read and write are applied. These are hard to hit naturally under randomization.

A common pattern in industry: the RTL designer releases a block with a few passing directed tests. The verification team's random environment then finds bugs the directed tests missed — not because the directed tests were wrong, but because randomization explores a fundamentally different space. The VE then adds their own directed tests on top, targeting the corner cases randomization still struggles to reach.

Directed Tests from Both Sides

On a DMA controller project, the RTL designer releases the block with 10 directed tests covering single-transfer operation — all passing. The verification team's random environment runs for two hours and finds a bug where concurrent requests to adjacent addresses corrupt the write data pointer — a scenario the designer's directed tests never targeted. The VE then adds a directed test that exactly reproduces this condition, which joins the permanent regression suite. Later in coverage closure, the VE also writes directed tests to force a channel-priority inversion scenario that the random environment — despite thousands of seeds — had never naturally triggered.

When Directed Tests Are the Right Tool

The common thread across all good uses of directed tests is that the scenario is known in advance and either difficult to reach with randomization or important enough to guarantee explicitly:

  • Deeply sequenced protocol corner cases: Some behaviors require an exact chain of events to set up. Testing AXI exclusive access (lock → read → lock → write → check EXOKAY response) requires a precise protocol sequence that may take millions of random seeds to hit naturally — if reachable at all under the active constraints. A directed test forces it in one run.
  • Coverage closure for stubborn gaps: After thousands of random seeds, specific functional coverage bins often remain unhit. Rather than running more seeds with diminishing returns, write a directed test that targets exactly that scenario.
  • Bug regression tests: When a bug is found — by random testing, by a designer, or in silicon — a directed test is written that exactly reproduces the failing conditions. This test stays in the regression suite permanently to ensure the bug never silently reappears.
  • Timing corner cases requiring exact coincidence: Scenarios like a reset arriving mid-burst, a FIFO going full in the same cycle as a simultaneous read/write, or a grant arriving exactly at a timeout boundary are hard to constrain a random generator to hit reliably. Write them directly.
  • Designer pre-release sanity checks: Before handing off RTL to the verification team, designers write a small number of directed tests to confirm the most basic behaviors work. These are not comprehensive — they are a quick confidence check that the block is worth starting verification on.

A useful rule of thumb: if you find yourself adding complex constraints to a random environment just to force one specific scenario, that scenario is a better candidate for a directed test.

Structure of a Directed Testbench

A directed testbench in SystemVerilog has a straightforward structure:

  1. Instantiate the DUT
  2. Generate the clock
  3. Apply stimulus in sequence using an initial block
  4. Check outputs explicitly after each stimulus using $error or assertions
  5. Call $finish when complete

The key discipline is that every test case must check its output automatically. A directed test that only applies stimulus and prints values without verifying correctness is an observation, not a test. In a regression suite that runs overnight, no one reads individual log files — the testbench must flag failures itself.

Example: Directed Tests for a Synchronous FIFO

The two scenarios below illustrate the two different reasons directed tests get written: a basic reset check a designer would write before releasing RTL, and a full-flag overflow check a verification engineer might add to close a coverage hole that random testing failed to hit reliably.


module tb_fifo;

    parameter DATA_W = 8;
    parameter DEPTH  = 4;

    logic              clk, rst_n;
    logic              wr_en, rd_en;
    logic [DATA_W-1:0] wr_data, rd_data;
    logic              full, empty;

    sync_fifo #(.DATA_W(DATA_W), .DEPTH(DEPTH)) u_fifo (
        .clk(clk), .rst_n(rst_n),
        .wr_en(wr_en), .wr_data(wr_data),
        .rd_en(rd_en), .rd_data(rd_data),
        .full(full), .empty(empty)
    );

    initial clk = 0;
    always #5 clk = ~clk;

    initial begin
        wr_en = 0; rd_en = 0; wr_data = 0;

        // -- TEST 1: Reset (designer sanity check) --
        // After reset deasserts, FIFO must report empty and not full.
        rst_n = 0;
        repeat(2) @(posedge clk);
        rst_n = 1;
        @(posedge clk);

        if (!empty) $error("TEST 1 FAIL: expected empty after reset");
        if ( full)  $error("TEST 1 FAIL: full must not assert after reset");
        $display("TEST 1 PASS: reset behaviour correct");

        // -- TEST 2: Overflow blocked (VE corner-case directed test) --
        // Fill the FIFO to capacity, then attempt one extra write.
        // The DUT must discard it; draining must yield only DEPTH entries.
        repeat(DEPTH) begin
            @(posedge clk); wr_en <= 1; wr_data <= wr_data + 1;
            @(posedge clk); wr_en <= 0;
        end

        if (!full) $error("TEST 2 FAIL: full flag did not assert after %0d writes", DEPTH);

        @(posedge clk); wr_en <= 1; wr_data <= 8'hFF;  // overflow attempt
        @(posedge clk); wr_en <= 0;

        repeat(DEPTH) begin
            @(posedge clk); rd_en <= 1;
            @(posedge clk); rd_en <= 0;
            if (rd_data === 8'hFF)
                $error("TEST 2 FAIL: overflow data 0xFF entered the FIFO");
        end

        if (!empty) $error("TEST 2 FAIL: FIFO not empty after draining %0d entries", DEPTH);
        $display("TEST 2 PASS: overflow correctly blocked");

        $finish;
    end

endmodule

Test 1 is the kind of sanity check a designer writes before releasing RTL. Test 2 is the kind of corner-case directed test a verification engineer adds when a coverage hole for the full-flag overflow path has not closed after many random seeds.

Common Mistakes in Directed Testbenches

Mistake #1: Checking outputs too early

Wrong: Sampling rd_data on the same clock edge as asserting rd_en
Right: Waiting one clock cycle after asserting rd_en before sampling rd_data

Why? Synchronous designs register outputs on the clock edge. Sampling on the same edge as the triggering control signal reads the previous value, not the updated one. Always allow at least one clock cycle for the DUT to respond before checking.

Mistake #2: No automated output checking

Wrong: Rely on visual inspection.


$display("rd_data = %0h", rd_data);

Right: Add self checks instead

 
if (rd_data !== expected) 
  $error("FAIL: got %0h", rd_data);

Why? In a regression suite running hundreds of tests overnight, no one reads individual log files. The testbench must flag failures automatically. $error increments the simulator's error count, which is what regression infrastructure checks.

Mistake #3: Testing only the happy path

Wrong: Only testing valid writes to a non-full FIFO and reads from a non-empty FIFO
Right: Also testing boundary conditions — overflow attempts, reset mid-operation, and simultaneous read/write

Why? The happy path is the easiest path for a designer to get right and the least likely to contain bugs. Boundary conditions are where RTL bugs hide. If corner cases are left only to randomization, they may not be hit within the project's simulation budget.

Quick Check

Q: A designer writes 8 directed tests for a new block, all passing. The verification team's random environment then immediately finds a bug. Why does this happen?

Show Answer

Directed tests written by the designer are derived from the same mental model that produced the RTL — so they test the scenarios the designer thought about. The random environment explores input combinations the designer did not anticipate. Bugs caused by unexpected interactions between features, back-pressure scenarios, or multi-cycle sequences that were never explicitly considered in the directed tests are exactly what randomization finds. This is the expected and healthy outcome — directed and random testing cover different spaces.

Limitations of Directed Verification

  • It can only find bugs you already know to look for. Every directed test encodes an assumption about which scenarios matter. Bugs arising from unexpected feature interactions — two things that work correctly in isolation but fail when combined — are rarely caught by directed tests because no one thought to write that specific scenario.
  • It does not scale. A FIFO with 256 entries, 32-bit data, and multiple port configurations has a state space that no realistic set of directed tests can meaningfully cover. The number of test cases required grows faster than anyone can write them.
  • It is biased by the author's understanding. If the designer misunderstood a corner case in the specification, their directed test will encode and validate that misunderstanding. The test passes, the bug survives. Constrained random testing has no such bias — the constraint solver does not share the engineer's assumptions.
  • Maintenance cost grows with RTL changes. Directed tests encode specific values and timing. When RTL changes, directed tests can break and require manual updates. Random tests adapt automatically within their constraints.

A regression suite built entirely from directed tests gives a false sense of security. High pass rates mean the specific scenarios the designer thought to test all work — not that the design is correct across its full input space.

Practice Exercise

Challenge: Identify the Right Test Type

For each scenario below, decide whether a directed test or a constrained random test is more appropriate, and explain why.

  1. Verifying that a counter resets to zero when rst_n is asserted
  2. Verifying that a memory controller handles any combination of read and write requests arriving in random order without corrupting data
  3. Verifying that an AXI master correctly handles a specific sequence: issue a locked read, receive the data, issue a locked write to the same address, and confirm the EXOKAY response
  4. Verifying that a round-robin arbiter fairly serves all requesters across 10,000 randomized request patterns
  5. Closing a coverage hole where the functional coverage model requires observing a FIFO transitioning from full to exactly one slot remaining in the same cycle a new write arrives
Show Answers

1. Reset to zero: Directed test. This is an exact, known scenario with a specific expected output. It is the classic designer sanity check — write it once, keep it in regression forever.

2. Random read/write ordering: Constrained random. "Any combination" and "random order" are exactly what a random environment is built for. A directed test cannot enumerate the relevant combinations.

3. AXI locked access sequence: Directed test. This is a specific multi-step protocol sequence with an exact expected response. Constraining a random generator to produce this exact sequence reliably is complex and fragile — write it directly.

4. Round-robin fairness across 10,000 patterns: Constrained random. The goal is to exercise many patterns — randomization is ideal. A scoreboard or assertion checks fairness across all of them.

5. Coverage hole closure: Directed test. The scenario is specific and known (full → one slot, simultaneous write). Write a directed test to force it exactly rather than running more random seeds hoping it appears.

Summary

In this tutorial, you learned:

  • Directed verification means writing test cases with explicitly defined stimulus and expected outputs, derived from the design specification
  • RTL designers write directed tests for quick sanity checks before releasing RTL; verification engineers prefer constrained random environments that find unexpected bugs
  • Directed tests are most valuable for corner cases that are hard to reach with randomization, specific protocol sequences, bug regression guards, and coverage closure
  • Every directed test must check outputs automatically — manual log inspection does not scale in regression
  • Directed testing cannot cover the full state space and is biased by the author's understanding; it must be combined with constrained random verification for complete functional coverage
Did you find what you were looking for?

Quiz

No quiz questions available for this article.