Coverage architecture and planning
Sample coverage where it proves what the design did, cross it with the register state that gives a requirement its meaning, and trace one requirement from the specification to the test that hits it.
- Lesson
- U15.1
- Time
- 30 min
- Runs on
- Verilator with UVM
- Check
- 5 questions
The burst test passed with no errors, and its coverage report listed REQ-7, a write burst is accepted while CTRL.burst = 1, as hit three times. The port never accepted a write burst. The test had written CTRL = 01, leaving the burst bit (bit 1) clear, so the port refused all three and the scoreboard, predicting from the register, agreed. The report came from a collector fed by the driver.
| Collector fed by | REQ-7 (burst accepted) | REQ-8 (burst refused) | Holes |
|---|---|---|---|
| The driver, before driving | 3 | 0 | REQ-8 |
| The monitor, at the observed answer | 0 | 3 | REQ-7 |
Measured: the example run, ten requests with three write bursts, CTRL = 01.
Three places a collector can listen
- 80 ns: intent
The driver takes item 4 and, before any pin moves, writes its intent. The configuration says
ctrl_value = 2'b11, sodrv_covcounts REQ-7: a write burst with bursts on.More context
The intent is counted from the item and the configuration alone. Nothing the port does later can change this count: it ends at REQ-7 3, REQ-8 0.
Follow the calls, values and waits
- data item 4: write, len 2: req_seq → Driver
- data intent: REQ-7: Driver → drv_cov
- 90 ns: valid rises
The driver raises valid at 90 ns with write and length 2 on the pins. The port does nothing until its next clock edge.
More context
The timing probe of the example bench puts the drive 10 ns after the intent: the driver waits for the negative edge before it changes a pin.
Follow the calls, values and waits
- data valid, wr = 1, len = 2: Driver → req_port
- Waiting: Driver — holding valid until the take edge
- 95 ns: taken and recorded
The port takes the request at the 95 ns edge, holding CTRL = 01: the test wrote bit 0 and left the burst bit clear. At the same edge the monitor records write, length 2 and
ctrl_burst = 0, the register state the request met.More context
The monitor reads
ctrl_burstfrom the pin at the take edge. Reading the configuration instead would record 1, the value the test meant to write.Follow the calls, values and waits
- data taken: write, len 2, ctrl_burst = 0: req_port → Monitor
- Waiting: Monitor — holding the request until the answer
- Released: Driver
- 105 ns: the answer is observed
The port registered
ok = 0at the 95 ns edge; the monitor samples it at the 105 ns edge. It writes one observation to both listeners:mon_covcounts REQ-8 (refused), and the scoreboard predicts a refusal fromctrl_burst = 0, so it matches.More context
Both listeners get the same handle in one
ap.write. Over the ten requests the monitor side ends at REQ-7 0, REQ-8 3, and the scoreboard at 10 answers checked, 0 mismatches.Follow the calls, values and waits
- data ok = 0: req_port → Monitor
- data obs: write, len 2, ctrl_burst 0, ok 0: Monitor → mon_cov
- data obs: write, len 2, ctrl_burst 0, ok 0: Monitor → Scoreboard
- Released: Monitor
Read the diagram as text
- req_seq: Item 4: write, len 2
- Driver: Knows the item and the configuration
- drv_cov: Counts what was asked for
- req_port: CTRL = 01: bursts off
- Monitor: Request at the take edge, ok at the answer
- mon_cov: Counts what the port did
- Scoreboard: Predicts from CTRL.burst
- req_seq → Driver: item
- Driver → drv_cov: write(intent)
- Driver → req_port: valid, wr, len
- req_port → Monitor: pins + ctrl_burst
- Monitor → mon_cov: write(obs)
- Monitor → Scoreboard: write(obs)
Both collectors run the same bins. Only the one fed by the monitor counts what the port did.
A coverage collector is usually a uvm_subscriber: an analysis export and a write() that samples each transaction it receives. The architecture question is which port feeds it, because that decides what a hit means.
| Sampling point | What a hit records | Use it for |
|---|---|---|
| Driver intent, before the pins move | The stimulus asked for this | Stimulus quality: did the sequences ask for bursts at all? |
| Monitor transaction, at completion | The design took it and answered | Requirements: what the design did |
| Register or configuration state | The mode the request met | Crosses that give a bin its meaning |
The third row is not a separate stream: it is a field the monitor adds to each transaction. In this bench a missing answer is also an error, so every planned request must reach the collector.
Sample at the event that establishes the requirement (here, the answer), and attach the state the request met at the event it qualifies (here, the take edge). REQ-7 and REQ-8 are about the answer, so their bins sample when the monitor observes it, one edge after the port took the request. The mode they cross with is the one sampled at the take edge. Other requirements need other events: an acceptance-rate or occupancy goal can sample at the take edge or every cycle.
Cross the transaction with the state that gives it meaning
REQ-7 and REQ-8 describe the same stimulus, a write burst. They differ only in the CTRL.burst value the request met and in the answer. So the bin crosses four things: direction, length, register state and response.
burst matches the counter's len > 1.covergroup req_cg with function sample(req_obs t);
dir: coverpoint t.wr { bins rd = {0}; bins wr = {1}; }
len: coverpoint t.len { bins single = {1}; bins burst = {[2:4]}; }
mode: coverpoint t.ctrl_burst;
resp: coverpoint t.ok;
rows: cross dir, len, mode, resp {
bins req7 = binsof(dir.wr) && binsof(len.burst) && binsof(mode) intersect {1} && binsof(resp) intersect {1};
bins req8 = binsof(dir.wr) && binsof(len.burst) && binsof(mode) intersect {0} && binsof(resp) intersect {0};
} // the other combinations get automatic cross bins
endgroup
// in req_coverage: function new ... req_cg = new(); ... write(t): req_cg.sample(t);With a covergroup, one more trap appears. drv_cov and mon_cov are two instances of one class, and by default their covergroups share one type coverage. The driver's REQ-7 hits would then hide the monitor's REQ-7 hole, the same false closure as the hook. Set option.per_instance = 1 and option.name = get_full_name() so the report keeps the two apart.
Which state goes into the cross matters as much as which port. The driver's collector took CTRL.burst from the configuration object, the value the test meant to program. The monitor's collector takes it from the design at the take edge. Only the second can reveal a register write that went wrong.
This port shows CTRL.burst on a pin. In a bench with a register model, the monitor copies the mirrored field into the transaction at the take edge instead (U13, U14.3). Reading the mirror later, when the answer arrives, could attach a mode written after the request. Two more conditions apply. A predictor must see every CTRL write. The snapshot must also follow the design's rule for a write and a request at the same edge. This port gives such a request the old value, and the bench must order its snapshot to match. Analysis listeners at one edge run one after another, in an order the bench must not rely on (U13.3).
One requirement, from specification to test
- A testable sentence
The specification sentence names the condition (CTRL.burst = 1), the stimulus (a write burst) and the result (accepted).
More context
A sentence without a condition or a result gives the row nothing to measure or judge. The row keeps the REQ-7 name, so every later artifact can cite it.
Follow the calls, values and waits
- data REQ-7 sentence: Specification → Test plan row
- A bin and a check
The row points to one coverage bin and one checker rule. The bin proves the case happened; the rule proves the result was right.
More context
The bin is on the monitor-side collector, because only the monitor sees the answer and the CTRL state the request met.
Follow the calls, values and waits
- data row REQ-7: bin: Test plan row → mon_cov bin REQ-7
- data row REQ-7: check: Test plan row → Scoreboard rule
- A test that owns it
The row names
burst_testas the test expected to hit REQ-7. In the example, its CTRL write was wrong, so the bin stayed at 0.More context
The example prints "port accepted: ... REQ-7 0, REQ-8 3; holes: REQ-7" while the driver side claims REQ-7 3.
Follow the calls, values and waits
- data REQ-7 hits = 0: mon_cov bin REQ-7 → burst_test (repaired)
- Waiting: burst_test (repaired) — REQ-7 not hit: CTRL written as 01
- Closed only when both agree
REQ-7 closes when a run of
burst_testhits the bin, answers and checks all ten planned requests, and reports no mismatch. A hit in a failing or incomplete run does not count.More context
This is the repaired test, writing
cfg.ctrl_value: the port side reports REQ-7 3, and the scoreboard still checks 10 answers with 0 mismatches.Follow the calls, values and waits
- data REQ-7 hits = 3: mon_cov bin REQ-7 → burst_test (repaired)
- data REQ-7 hits = 3: burst_test (repaired) → Closure
- data 10 checked, 0 mismatches: Scoreboard rule → Closure
- Released: burst_test (repaired)
Read the diagram as text
- Specification: REQ-7: with CTRL.burst = 1 a write burst is accepted
- Test plan row: REQ-7: bin, check, test, closure rule
- mon_cov bin REQ-7: write, len > 1, ctrl_burst = 1, ok = 1
- Scoreboard rule: ok must equal !(wr && len > 1 && !ctrl_burst)
- burst_test (repaired): Intended: CTRL = 11, then 3 write bursts
- Closure: Hit in a complete, checked run of burst_test
- Specification → Test plan row: becomes a row
- Test plan row → mon_cov bin REQ-7: measured by
- Test plan row → Scoreboard rule: judged by
- mon_cov bin REQ-7 → burst_test (repaired): hit by
- Scoreboard rule → Closure: no mismatch
- burst_test (repaired) → Closure: hits counted
A requirement is closed by a hit in a complete, checked run, both traceable to one plan row.
| Row | Requirement | Bin condition (at the answer) | Owning test |
|---|---|---|---|
| REQ-3 | A single read is accepted | rd, len 1, ok | burst_test |
| REQ-4 | A read burst is accepted | rd, len > 1, ok | burst_test |
| REQ-5 | A single write is accepted | wr, len 1, ok | burst_test |
| REQ-7 | With CTRL.burst = 1, a write burst is accepted | wr, len > 1, ctrl_burst 1, ok | burst_test |
| REQ-8 | With CTRL.burst = 0, a write burst is refused | wr, len > 1, ctrl_burst 0, !ok | not yet assigned |
The test plan for the port. REQ-8 has no owning test, which the next sections turn into a decision.
Each row needs both a bin and a check. A bin without a check proves the case happened but not that the result was right. A check without a bin may pass in every run because the case never occurred.
Explicit and implicit coverage
Every bin in this lesson is explicit coverage: an engineer wrote the goal, the values and the sampling point. Its weakness is that it measures only what someone thought of. Implicit coverage is generated from the design by the simulator: line, branch, toggle and state-machine coverage. It needs no model, but a missed line rarely names the missing feature.
Build the explicit model from the plan first, then use code coverage as a cross-check. Full functional coverage with low code coverage is a reason to inspect the unhit code: the model may miss behaviour, or the code may be unreachable or out of scope. Full code coverage with functional holes means the code ran, but never in the combination a requirement names.
Explicit coverage has two usual homes. A covergroup in a subscriber suits transaction-level goals like these rows. A cover property in the interface checker suits cycle-level sequences, such as a stall held for two edges (U7.4).
A switch that must say it is off
coverage_enable is a VIP convention, not a library field: the uvm-core 2020.3.1 source never defines it. The collector reads it in build_phase. So a test sets it in the config database before the build reaches the collector, or assigns the field directly after build and before the first sample.
A switched-off collector must report that it is off. If it printed its empty bins as holes, a run that skipped coverage for speed would look like a run that missed every requirement. The example's summary() returns disabled, no samples taken instead.
The lookup follows the ordinary config rules. A set in connect_phase comes after the read and changes nothing; a set with a different type, such as int for a bit field, never matches. U15.2 shows how to see both in a trace.
Holes that random stimulus cannot reach
Fix the CTRL write and REQ-7 is hit three times, but REQ-8 becomes the hole. REQ-8 needs a write burst that meets CTRL.burst = 0. This test writes CTRL once, before any traffic, so every request meets the same value. The example's sequence is a fixed list, but suppose it were extended to randomize direction and length: CTRL would still be written once, and no seed would reach REQ-8.
More random runs
// randomized extension, 50 seeds
// CTRL = 11 for every request
REQ-8 stays at 0 in all of them: the seeds vary the items, never the register written once at the start.
A directed step
// at a quiet point (no request in flight):
// write CTRL = 01, send one write burst,
// expect ok = 0, then restore CTRL = 11
One item hits REQ-8, with a checked refusal. The plan row names this step, or a second test, as its owner.
Separate a rare bin from an unreachable one. A rare bin gets more seeds or a weighted constraint. An unreachable bin needs a different configuration or a directed step, and U16.2 shows how to close it.
Try it
The scoreboard is clean because it predicts from the CTRL value the port held. The two coverage lines disagree on REQ-7 and REQ-8: the driver counted its intent, the monitor counted the answers.
Change env.vif.cfg_wdata <= 2'b01 to env.vif.cfg_wdata <= cfg.ctrl_value and rerun. Both collectors now report REQ-7 hit three times, and REQ-8 becomes a hole for both.
scoreboard: 10 answers checked, 0 mismatches driver intent: REQ-3 2, REQ-4 2, REQ-5 3, REQ-7 3, REQ-8 0; holes: REQ-8 port accepted: REQ-3 2, REQ-4 2, REQ-5 3, REQ-7 0, REQ-8 3; holes: REQ-7 UVM_ERROR : 0Check yourself
Remember
- Sample a requirement at the event that establishes it, here the monitor's observed answer; driver-side coverage measures what the stimulus asked for.
- Cross the transaction with the register state the design held when it took the request, not with the configuration the test intended.
- Trace each requirement to one plan row with a bin, a check and an owning test; a row closes when a complete, checked run hits its bin.
coverage_enableis a convention read in build; a collector disabled before sampling must say so rather than report holes.- A bin that needs a register state the test never creates is unreachable by more seeds; close it with a directed step or another test (U16.2).