The racetrack needs cars
In rung 5 you built the static skeleton of a UVM environment: a driver that wiggles pins, a monitor that watches them, a scoreboard that checks results, all wrapped in an agent. Power it on and… nothing happens. The driver is idling, waiting to be handed something to send. That something is a transaction, and the thing that produces a stream of transactions is a sequence. The skeleton is the racetrack; sequences are the cars you actually race on it.
A sequence's job is to decide *what scenario* to throw at the DUT. A trivial sequence sends one random read. A good sequence captures something a human verification engineer actually worries about: "hammer the FIFO with writes until it's full, then read it dry," or "start a burst and yank reset out from under it." Each of these is a named, reusable object you can hand to the sequencer, which arbitrates between competing sequences and feeds the driver one transaction at a time.
class write_read_seq extends uvm_sequence #(bus_txn);
`uvm_object_utils(write_read_seq)
rand int unsigned num;
constraint c_num { num inside {[8:16]}; }
task body();
bus_txn t;
// Phase 1: fill — back-to-back writes, no idle gaps
repeat (num) begin
t = bus_txn::type_id::create("t");
start_item(t);
assert(t.randomize() with { kind == WRITE; });
finish_item(t);
end
// Phase 2: drain — read everything back
repeat (num) begin
t = bus_txn::type_id::create("t");
start_item(t);
assert(t.randomize() with { kind == READ; });
finish_item(t);
end
endtask
endclassSequences that make designers sweat
Bugs don't hide in the easy middle of the input space; they lurk at the edges. Three families of sequence are worth mastering because they reach those edges deliberately. Back-to-back sequences remove every idle cycle so transactions collide and pipelines stay perpetually full — this is where handshake and stall logic breaks. Error-injection sequences deliberately send malformed traffic — a parity error, an unaligned address, a protocol-illegal command — to check the DUT *responds* correctly rather than wedging. Corner-case sequences target the arithmetic extremes: the very first transaction after reset, a FIFO at exactly full minus one, a counter rolling over at its maximum.
class b2b_error_seq extends uvm_sequence #(bus_txn);
`uvm_object_utils(b2b_error_seq)
task body();
bus_txn t;
repeat (200) begin
t = bus_txn::type_id::create("t");
start_item(t);
// 5% of traffic carries a bad-parity corner case,
// and inter_gap==0 forces back-to-back timing.
assert(t.randomize() with {
inter_gap == 0;
bad_parity dist { 0 := 95, 1 := 5 };
});
finish_item(t);
end
endtask
endclassSequences also *compose*. A virtual sequence orchestrates several sub-sequences across multiple agents — say, traffic on an AXI port while a config sequence reprograms registers on an APB port — to recreate the messy concurrency of a real SoC. This layering is the payoff of rung 5's structure: because each agent is independent, you can mix and match stimulus without rewiring the bench.
Coverage: the scoreboard for your test plan
Constrained-random stimulus is powerful but blind: it will happily send a million transactions and never once fill the FIFO completely, and you'd never know. You need a measuring stick. Functional coverage is that stick — it turns the prose of your verification plan into executable, countable goals. Where the plan says "verify every burst length on every channel," a covergroup with coverpoints records which of those combinations actually occurred during simulation.
covergroup bus_cg with function sample(bus_txn t);
cp_kind : coverpoint t.kind { bins rd = {READ}; bins wr = {WRITE}; }
cp_len : coverpoint t.burst_len {
bins single = {1};
bins small = {[2:7]};
bins max = {8}; // the corner we care about
}
// Cross: did we see a MAX-length READ *and* a MAX-length WRITE?
x_kind_len : cross cp_kind, cp_len;
endgroupEach bin is a bucket the simulator increments when a matching transaction is sampled; coverage is the fraction of bins that got at least one hit. The cross is where real intent lives — bugs love combinations (a max-length read *immediately after* a reset) far more than isolated values. Sample your covergroup from the monitor, not the driver: you want to measure what the DUT *actually saw on the wire*, including back-pressure and reordering, not what you *intended* to send.
Two kinds of coverage, two questions
Functional coverage answers "did I exercise everything I *meant* to?" — but it can only measure goals you remembered to write. Code coverage answers the complementary question "did I exercise every line, branch, expression and state the designer *actually wrote*?" The simulator instruments the RTL automatically, so it catches dead corners you never thought to put in your plan: an `else` branch that never executed, an FSM state never entered, a case item never selected.
Coverage type What it measures Typical signoff --------------- ----------------------------- --------------- Line / statement every RTL line executed ~100% Branch both arms of every if/case ~100% Toggle every bit toggled 0->1 and 1->0 ~90-100% FSM every state + legal transition 100% states Expression/cond each sub-condition's true/false ~95-100%+ --------------- ----------------------------- --------------- Functional coverpoints/bins/crosses hit 100% of plan Assertion each cover-property triggered 100%
The two are complementary in a precise way. 100% code coverage with low functional coverage means you ran a lot of code but never set up the scenarios that matter — you toggled every wire but never filled the FIFO. 100% functional coverage with code coverage holes means a chunk of RTL exists that your plan never thought about — often dead code, a forgotten mode, or a missing item in the plan itself. Each hole is a question you must answer before you can sign off; coverage doesn't tell you the design is correct, it tells you *what you haven't looked at yet.*
Closing coverage: the signoff loop
Coverage closure is the iterative grind that takes you from "the tests run" to "we're confident enough to tape out." It is rarely a straight line. You launch a regression of hundreds of seeds, merge their coverage databases into one picture, study the holes, and decide — for each hole — whether to add stimulus, fix the model, or formally waive it. Then you regress again. The curve rises fast at first, then flattens into a long, stubborn tail where the last few percent of bins cost more effort than the first ninety.
- Regress. Launch the test suite across many random seeds — different seeds drive constrained-random stimulus down different paths, so each run hits different bins.
- Merge. Combine every run's coverage database into one unified report. A bin counts as hit if *any* seed hit it.
- Triage the holes. Sort uncovered bins by importance. Group them: is this one hole, or fifty symptoms of one missing scenario?
- Act on each hole — write a targeted sequence, tighten or loosen a constraint, fix a buggy bin definition, or waive an unreachable bin with a documented justification.
- Re-regress and re-merge. Confirm the holes closed and that you opened no new ones. Repeat until you reach the plan's signoff goals.
Two moves rescue you from the stubborn tail. First, targeted sequences — when random stimulus keeps missing a bin, stop hoping and write a directed sequence that forces exactly that condition (or tighten a constraint so the solver biases toward it). Second, waivers — some bins are genuinely unreachable: a redundant-but-illegal state, a defensive `default` that a correct design can never hit. Don't chase them forever; exclude them with a written reason so the next reviewer trusts the green number. And for properties you can prove rather than hit, formal property verification can close coverage that random simulation never will.
Putting it together
Stand back and the whole flow is a single feedback loop. The verification plan declares intent; coverpoints turn that intent into a measurable target; sequences driven through the sequencer generate stimulus; the monitor samples both the scoreboard checks *and* the coverage. The merged coverage report points at the holes, and the holes tell you which sequences to write next. Around and around until the plan is green and defended.
This is why modern verification can swallow more than half a chip's total engineering effort. The DUT is finite, but its *behaviours* are astronomically many, and you are betting the mask cost on having explored the ones that matter. Sequences are how you explore; coverage is how you prove you explored; closure is the discipline that turns a pile of passing tests into a signed, defensible claim that the design is ready for tape-out.