A verification plan is only as good as the feature list it is built from. If you miss a feature during analysis, no amount of clever testbench engineering will catch bugs in it — you simply will not be testing it. Extracting a complete, well-categorized feature list from a design specification, and then identifying the corner cases within each feature, is one of the most important skills a verification engineer develops.
12 min read | Intermediate Level
What You'll Learn
- How to read a DUT specification and extract verifiable features by category
- Techniques for identifying corner cases: boundary conditions, simultaneous events, and back-to-back transactions
- How to use decision tables and state transition diagrams to find gaps
- How to prioritize features by risk so verification effort is allocated correctly
Reading a Specification to Extract Features
A DUT specification describes what a block is supposed to do. Your job as a verification engineer is to convert that prose description into a concrete list of things that can be tested and pass or fail. The key is to read the specification as a skeptic — not accepting what the design intends to do, but asking what could go wrong and how you would detect it.
A practical starting point is to treat every behavioral statement in the specification as a potential feature entry. Words like "shall," "must," "will," and "is expected to" almost always map to something verifiable. Passive statements like "the block resets on power-on" or "the output is valid two cycles after the request" are explicit timing and functional commitments that must appear in your feature list.
Read the specification at least twice before writing a single feature entry. The first pass builds context. The second pass is where you annotate and extract — marking every behavioral requirement, timing constraint, mode, and error condition.
Categorizing Features
Organizing features into categories prevents gaps and makes it easier to assign priorities and reviewers. A standard set of categories for block-level verification covers four areas:
Functional Behavior
The core operation of the block under normal conditions. For a FIFO, this is: data written in the correct order is read out in the correct order. For an ALU, this is: each arithmetic and logical operation produces the correct result for valid inputs. These are usually the first features written and the easiest to test, but they are also where the most coverage is needed because the space of valid inputs is large.
Interface Protocols
How the block communicates with its neighbors — handshake sequences, bus protocols, flow control, and valid/ready signaling. A block may compute the correct result internally but drive its output interface incorrectly. Protocol behavior must be verified independently of functional correctness. This category also includes reset behavior on the interface: does the block drive its outputs to defined states during and after reset?
Error Handling
How the block responds when something goes wrong: illegal inputs, protocol violations, timeout conditions, parity errors, or overflow. Error handling is frequently undertested because it only activates in fault conditions that normal stimulus does not generate. Every error flag, error interrupt, and recovery sequence described in the specification belongs in this category.
Configuration and Modes
Register-controlled operating modes, enable bits, programmable thresholds, and any behavior that changes based on a configuration register value. A block with four operating modes effectively has four sub-designs that each need their own functional and protocol verification. Features in this category often interact — a configuration change mid-operation is a corner case that belongs here.
Quick Check
Q: A UART receiver spec states: "If a framing error is detected, the rx_error flag shall assert for one clock cycle and the received byte shall be discarded." Which category does this belong to, and name two test scenarios it implies.
Show Answer
Category: Error Handling. Scenarios: (1) Inject a framing error and verify rx_error asserts for exactly one cycle — not zero, not two. (2) Verify the corrupted byte does not appear in the output data stream while the following valid byte does.
Identifying Corner Cases
Normal-path stimulus finds normal-path bugs. Corner cases are the conditions where designs are most likely to have implementation errors — and where random stimulus is least likely to land without guidance. Corner cases generally fall into three types:
Boundary Conditions
Any design that counts, buffers, or computes has boundary values where behavior transitions. A FIFO transitions from "not full" to "full" at a specific count. A counter wraps from its maximum value back to zero. An adder produces a carry-out at a specific input combination. These transition points are where off-by-one errors live.
For every counter, buffer depth, threshold, or range in the specification, write explicit test scenarios for the value just below the boundary, the boundary value itself, and the value just above it. This is called boundary value analysis.
Back-to-Back Transactions
Many bugs only appear when transactions arrive with zero idle cycles between them, exposing pipeline timing errors, missing ready-de-assertion, or incorrect state machine transitions. A block may handle a single transaction correctly but fail when a second transaction arrives before the first has fully completed. Back-to-back testing should be a default scenario for every interface, not an afterthought.
Simultaneous Events
Hardware is parallel — multiple signals can change in the same clock cycle. A FIFO can receive a write request and a read request simultaneously. A reset can arrive while a transaction is in progress. An interrupt can fire at the exact cycle an acknowledgment is sent. These simultaneous conditions are hard to hit with random stimulus and must be planned explicitly in the feature list.
Simultaneous events are a common source of late-project bugs. Because they require precise timing to reproduce, they often go undiscovered in simulation and only surface during silicon bring-up or system-level integration when traffic patterns are more realistic.
Techniques for Finding What You Missed
Decision Tables
A decision table maps combinations of input conditions to expected outputs. For a block with N control inputs, a full decision table has 2N rows. Working through the table — even partially — reveals combinations that were not in the specification's worked examples and forces you to define expected behavior for each one.
For example, a bus arbiter with two request signals req_a and req_b and a priority input priority has eight distinct input combinations. Listing them as a table quickly shows whether the specification defines behavior for the case where both requests arrive simultaneously with each priority setting — a common omission.
State Transition Diagrams
If the DUT contains a finite state machine, drawing its state transition diagram (or locating the one in the specification) and systematically listing every transition — including transitions back to the same state — generates a natural set of test scenarios. Every arc in the diagram is a scenario. Every state that can be reached should also be reachable from reset, and that path should be a test scenario.
State diagrams also reveal unreachable transitions: arcs that the specification never describes but the implementation might inadvertently allow. These are candidates for directed tests and assertion-based checks.
Protocol State Machines
For blocks with external bus interfaces, the protocol specification defines a state machine of its own — legal sequences of valid/ready, request/grant, or command/response. Listing all legal sequences and, importantly, all illegal sequences that the block must tolerate without hanging, gives you both the positive test cases and the error injection test cases.
Prioritizing by Risk
Not all features carry equal risk, and verification effort should not be distributed equally. A useful risk model combines two factors: probability of a bug existing (based on implementation complexity and designer experience) and impact of a bug escaping (based on how visible the failure would be and how hard it would be to work around in silicon).
Features that score high on both axes get the most coverage — multiple directed tests, constrained random scenarios covering many combinations, and dedicated assertions. Features that score low on both axes may need only a basic smoke test scenario. The Vplan should record the priority assigned to each feature and the reasoning behind it, so the team can defend coverage decisions during sign-off reviews.
New or significantly modified RTL is higher risk than reused IP. If a block was carried forward from a previous project with minimal changes, its well-tested core functions can be lower priority — but any modified paths must be treated as new and prioritized accordingly.
Common Mistakes
Mistake #1: Only writing feature entries for the "happy path" scenarios described in the specification's worked examples.
Wrong: The spec shows a three-step write transaction with valid data. The feature list contains one entry: "verify write transaction completes correctly."
Right: The feature list also contains: write at full capacity, write during reset assertion, back-to-back writes, write with de-asserted enable mid-transaction, and write followed immediately by a read.
Why? Specifications illustrate typical usage, not exhaustive usage. The verification engineer's job is to go beyond the examples and systematically ask what else can happen on every input at every state.
Mistake #2: Treating corner cases as optional extras to add if time permits.
Wrong: The test plan addresses all normal-path scenarios first, with a note that corner cases will be added "in a later regression pass" if schedule allows.
Right: Corner case scenarios are written into the Vplan alongside normal-path scenarios at the start, with their own priority ratings and coverage goals.
Why? Schedule pressure always arrives before the "later regression pass." Corner cases that are not planned from the start are the ones that escape to silicon.
Mistake #3: Leaving simultaneous events out of the feature list because they seem unlikely.
Wrong: The feature list has separate entries for "write when full" and "reset during operation" but no entry for "reset arriving while a write-when-full condition is active."
Right: Simultaneous combinations of independent corner cases are explicitly listed as their own test scenarios, especially for any signals that share the same clock domain.
Why? "Unlikely" in simulation often means "common in real system traffic." Simultaneous events are not rare in silicon — they are routine. The verification environment must be designed to generate them on purpose.
Practice Exercise
Challenge: Extract and Categorize Features from a Spec Excerpt
The following is a short specification excerpt for a synchronous counter block:
"The counter is a 4-bit up-counter. It increments on every rising clock edge when enable is high. When the count reaches 15, it wraps to 0 on the next enabled clock edge. The max_reached output asserts for one clock cycle when the counter transitions from 15 to 0. An active-high synchronous reset forces the counter to 0 on the next rising clock edge regardless of enable."
List at least six verifiable features, assign each a category (Functional, Protocol/Interface, Error Handling, or Configuration), and identify two corner cases not explicitly stated in the spec text.
Hint
Think carefully about the relationship between reset and enable — the spec says reset works "regardless of enable," which implies a specific priority. Also consider what max_reached does when reset is applied at count 15.
Sample Solution
Features:
- Counter increments by 1 each enabled clock edge — Functional
- Counter does not increment when
enableis low — Functional - Counter wraps from 15 to 0 on the next enabled edge after reaching 15 — Functional
max_reachedasserts for exactly one cycle on the 15→0 transition — Functionalmax_reacheddoes not assert on any other transition — Functional- Synchronous reset forces counter to 0 on the next rising edge — Functional
- Reset takes effect even when
enableis low — Functional (priority behavior)
Corner cases not stated explicitly:
- Reset applied when the counter is at 15: does
max_reachedassert on that cycle, or is it suppressed because the 15→0 transition was caused by reset rather than wrap? - Back-to-back enable toggles:
enablegoes low then high in adjacent cycles — does the counter correctly pause and resume without missing or double-counting an increment?
Summary
In this tutorial, you learned:
- Feature extraction starts with reading the specification as a skeptic — every "shall" statement is a verifiable requirement
- Features fall into four categories: functional behavior, interface protocols, error handling, and configuration modes
- Corner cases come in three types: boundary conditions, back-to-back transactions, and simultaneous events — all must be planned, not discovered by accident
- Decision tables and state transition diagrams are systematic tools for finding feature combinations the specification does not explicitly cover
- Features should be prioritized by risk — complexity of implementation combined with impact of an escaping bug — so verification effort is concentrated where it matters most