Skip to content

AMBA APB · Module 15

APB Functional Coverage

The APB functional-coverage model — a covergroup sampled per transaction across direction, address region, response, wait-count, and PSTRB, with the crosses (direction × response, region × response) that prove every interesting scenario was actually exercised. Coverage answers 'did we test it?'; assertions answer 'did it pass?' — and the wait-count coverpoint is the classic hole.

You have built an environment that checks APB — now you must prove it actually exercised the cases worth checking. Assertions fire when something is wrong; a scoreboard catches wrong data. But neither tells you whether the test ever drove a write that errored at the reserved region, or a transfer the responder held with two wait states. A passing, assertion-clean regression can be a regression that never reached the interesting corner at all. The single idea to carry: functional coverage is a covergroup, sampled once per transaction from the monitor's analysis port, that records which scenarios were exercised — direction, address region, response, wait-count, PSTRB pattern, and their crosses — so that closing it guarantees no meaningful case (especially wait-states and error-on-each-region) went un-tested. Coverage measures exercised; assertions and the scoreboard measure correct. Both are required, and they answer different questions.

1. Problem statement

The problem is measuring, per transaction, which interesting APB scenarios the verification actually exercised — so that "we ran a lot of tests and nothing failed" can be upgraded to "we provably hit every direction, region, response, and wait-state combination that matters."

A green regression is a weak claim on its own. It says nothing observed was wrong; it does not say everything worth observing was observed. A constrained-random test that, by luck of its seeds, only ever issued zero-wait reads to the CTRL register can pass forever while the held-bus paths, the error responses, and the partial-byte writes go completely un-driven. The bug lives in a path the stimulus never reached, so no assertion ever had a chance to fire on it. Functional coverage is the instrument that closes this gap, and it has three demands:

  • It must be sampled from real observed transactions, not from the stimulus. Coverage is collected from the monitor's reconstructed transaction — what actually appeared on the bus — not from what the sequence intended. Sampling the intent measures your generator; sampling the observed transfer measures the DUT's exercised behaviour, which is the thing that matters.
  • It must enumerate the scenarios that matter, with bins fine enough to distinguish them. Direction (read vs write), address region (each mapped register block), response (OKAY vs PSLVERR), wait-count (0, 1, 2..N — including the tail), and PSTRB byte-lane pattern. A coverpoint with too few bins reports a high percentage that means nothing — the classic failure where a single 0-wait bin reads 100% while every held-bus path is un-tested.
  • It must capture the combinations, not just the marginals. Hitting "write" and "PSLVERR" separately does not prove you ever saw a write that errored. The cross — direction × response, region × response — is what forces the per-region error path and the per-direction wait path to be exercised.

So the job is not "did the tests pass?" — assertions and the scoreboard answer that. It is "did the tests go everywhere interesting?" — and only an explicit, per-transaction coverage model can answer it.

2. Why previous knowledge is insufficient

Everything earlier in this module checks correctness; none of it measures exercise. That is precisely the gap coverage fills.

  • The monitor gives you the transaction; it does not tell you which transactions you saw. Module 15.3 built the monitor that reconstructs each APB transfer and broadcasts it on an analysis port. Coverage is the subscriber to that port — it consumes the same stream the scoreboard does, but where the scoreboard asks "is this transfer correct?", coverage asks "which kind of transfer is this, and have we seen it before?" Without coverage, the analysis port's transaction stream is checked but never catalogued.
  • Assertions prove the bus never broke a rule — not that the rule's scenario ever occurred. Module 15.2 wrote SVA that fires on illegal behaviour. But an assertion that never fires proves only that nothing illegal happened — it cannot distinguish "the PSLVERR rule held because errors were handled correctly" from "the PSLVERR rule held because no error was ever driven." A passing error-response assertion on a test that never triggered an error is a green light over an un-tested path. Coverage is what demands the error actually be driven.
  • Wait-state behaviour is the canonical un-exercised path. Wait-state verification is hard precisely because responders default to zero waits and random stimulus rarely stretches the tail. Knowing how wait states work does not make them occur in your regression — only a wait-count coverpoint with 0/1/N bins, backed by a wait-injecting responder, forces them. This is the single most common APB coverage hole.

Coverage is also what makes the later UVM APB agent self-reporting: the agent's coverage collector, subscribed to the monitor, is how a reusable agent proves its own thoroughness in any environment it is dropped into. So the model to add is the exercise ledger: a per-transaction record of which scenarios the regression actually reached, sitting beside — and distinct from — the correctness checks.

3. Mental model

The model: coverage is the attendance sheet of your verification; assertions and the scoreboard are the exams. The exam tells you whether each student who showed up answered correctly. The attendance sheet tells you which students showed up at all. A class where everyone present scored 100% looks perfect — until the attendance sheet reveals that half the students never came to class. A green regression is the perfect exam; coverage is the attendance check that asks "did the interesting scenarios even show up?"

Three refinements make it precise:

  • One covergroup, sampled once per observed transaction. The covergroup instance lives in a coverage collector subscribed to the monitor's analysis port. Each time a transaction arrives, you call cg.sample(item) — and the coverpoints (direction, region, response, wait-count, PSTRB) each drop the transaction into a bin. Coverage accumulates across the whole regression; closure is "every meaningful bin and cross-cell got at least one transaction."
  • Crosses are where the real scenarios live. A coverpoint hits its bins independently — you can reach 100% on cp_dir and 100% on cp_resp while never having seen a single write that errored. The cross direction × response has a cell for exactly that, and region × response has a cell for "did we see an error at each region?" — the per-region error path that marginal coverage silently misses.
  • A hole is an un-tested path, not a bug. An empty bin means "this scenario was never exercised" — it is a gap in what you tested, not evidence of a defect. You close it by driving stimulus until the bin fills (or by declaring it illegal_bins/ignore_bins if it genuinely cannot occur). The discipline is: every meaningful empty bin must be explained — exercised, or formally excluded — before sign-off.
The APB coverage model: five coverpoints on the left (direction, region, response, wait-count, PSTRB) with covered bins in green and holes in red, and a region-by-response cross matrix on the right where green cells were exercised and red cells are un-tested error-per-region holes, illustrating that coverage measures exercise and crosses reveal the missing combinations.
Figure 1 — the APB functional-coverage model, sampled once per transaction from the monitor's analysis port. On the left, five coverpoints with their bins: direction (read/write), region (CTRL/STATUS/DATA/RESERVED — one per mapped register block), response (OKAY/PSLVERR), wait-count (0, 1, 2..N — the tail, the classic hole), and PSTRB byte-lane pattern. Green bins are covered (exercised), amber are partial, red are holes (un-exercised paths). On the right, the cross matrix region × response shows one cell per region-and-response pair: green cells were hit, red cells are holes — here a PSLVERR was never seen at the STATUS, DATA, or RESERVED regions, so an error-handling path per region went un-tested, and the RESERVED region was never accessed at all. The figure teaches that coverage answers 'did we exercise it?' (not 'did it pass?', which is checking), that the cross is what reveals the untested error-per-region case the marginals hide, and that closure means driving stimulus until every meaningful bin and cross-cell is hit or declared illegal/ignore.

4. Real SoC implementation

In a real APB environment the covergroup lives in a coverage collector — a UVM subscriber on the monitor's analysis port — and is sampled with the reconstructed transaction. The wait-count and region fields are derived by the monitor (cycles PREADY was low; the address decoded against the register map) and carried on the transaction item. A realistic model:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// APB functional-coverage collector — subscribes to the monitor analysis port
// and samples ONE covergroup per observed transaction. Measures EXERCISE,
// not correctness (assertions + scoreboard own correctness).
class apb_coverage extends uvm_subscriber #(apb_seq_item);
  `uvm_component_utils(apb_coverage)
 
  apb_seq_item item;   // current transaction, set in write()
 
  covergroup apb_cg;
    option.per_instance = 1;
 
    // --- direction: read vs write ---
    cp_dir : coverpoint item.pwrite {
      bins rd = {0};
      bins wr = {1};
    }
 
    // --- address region: one bin per mapped register block ---
    cp_region : coverpoint item.region {           // region enum from the monitor's decode
      bins ctrl     = {CTRL};
      bins status   = {STATUS};
      bins data     = {DATA};
      bins reserved = {RESERVED};                    // accesses to a hole must be exercised too
    }
 
    // --- response: OKAY vs slave error ---
    cp_resp : coverpoint item.pslverr {
      bins okay   = {0};
      bins err    = {1};
    }
 
    // --- wait-state count: 0 / 1 / the tail. THE classic APB hole. ---
    cp_wait : coverpoint item.wait_cycles {          // PREADY-low cycles, counted by the monitor
      bins zero = {0};
      bins one  = {1};
      bins many = {[2:$]};                            // 2..N — must NOT be left as a single 0 bin
    }
 
    // --- PSTRB byte-lane pattern (writes): all-lanes vs partial ---
    cp_strb : coverpoint item.pstrb iff (item.pwrite) {
      bins full    = {'hF};                           // every byte lane enabled
      bins partial = default;                         // sparse / sub-word writes
    }
 
    // --- CROSSES: where the real scenarios live ---
    // "did we see a write that errored?" and "an error at EACH region?"
    x_dir_resp    : cross cp_dir, cp_resp;
    x_region_resp : cross cp_region, cp_resp;         // forces error-per-region to be exercised
    x_dir_wait    : cross cp_dir, cp_wait;            // forces held-bus reads AND writes
  endgroup
 
  function new(string name, uvm_component parent);
    super.new(name, parent);
    apb_cg = new();                                   // construct the covergroup
  endfunction
 
  // Called by the analysis port for every observed transaction.
  function void write(apb_seq_item t);
    item = t;
    apb_cg.sample();                                  // one sample per transfer on the bus
  endfunction
endclass

Two facts make this the right shape. First, it samples the observed transaction, not the stimuluswrite() is invoked by the monitor's analysis port, so every bin reflects what genuinely appeared on the bus, including DUT-driven responses and wait counts the sequence never explicitly requested. Second, the crosses carry the model's real value: x_region_resp is what turns "we saw some errors" into "we saw an error at the STATUS register, at the DATA register, and at the RESERVED region" — the per-region error path that marginal cp_resp coverage would report as 100% while leaving most of those cells empty. (Accesses to the reserved/unmapped space have their own verification concerns that this region bin makes visible.) Correctness for all of these is decided by the scoreboard and the assertions — the covergroup only records that the scenario happened.

5. Engineering tradeoffs

A coverage model is a sequence of judgement calls about bin granularity, what to cross, and what to formally exclude. Each cell below names the coverpoint, its bins, why it matters, and the bug a missing bin lets hide.

CoverpointBinsWhy it mattersBug an un-covered bin hides
Directionrd, wrRead and write exercise different DUT paths (data drive vs capture)A write-only or read-only test that never touches the other datapath
Regionone per mapped block (CTRL, STATUS, DATA, RESERVED)Each register block decodes and responds differentlyAn un-accessed register whose decode/access logic is never tested
Responseokay, errOKAY and PSLVERR drive different completion handlingError-handling logic (and the PSLVERR assertion) never actually triggered
Wait-countzero, one, many ([2:$])Held-bus paths only run when PREADY is held low — the classic holeA wait-state bug: signals drift or the manager mis-handles a stretched ACCESS
PSTRB patternfull, partialPartial-byte writes exercise byte-lane merge/mask logicA sub-word write corrupting adjacent bytes, never exercised
dir × resprd-okay, rd-err, wr-okay, wr-errProves a write that errored was seen, not just the marginalsAn error path reachable only on writes, never driven
region × resperr at each regionProves an error at every region was exercisedA region whose error response is broken but never tested
dir × waitheld-bus read AND writeProves both directions were stretched, not just oneA wait-state bug that only manifests on the un-stretched direction

The throughline: bins must be fine enough to distinguish the scenarios that fail differently, and the crosses must cover the combinations that hide in the marginals. Over-binning is real cost too — a coverpoint with thousands of bins you never close becomes noise that masks the holes that matter, so cross only the axes whose combination is a distinct DUT path, and use ignore_bins for combinations that are legal-but-uninteresting and illegal_bins for ones that must never occur. A good model is small, every bin is justified, and every empty bin is a question someone must answer before sign-off.

6. Common RTL mistakes

7. Debugging scenario

The signature coverage failure is a false closure: a regression reports "coverage closed, all green," yet a bug ships down a path the coverage summary implied was tested but a too-coarse coverpoint never actually exercised.

  • Observed symptom: the wait-state handling block reports a field-return defect — a peripheral occasionally commits stale data when the bus is held — yet the sign-off record shows the APB agent's functional coverage at 98% and the regression fully green. The summary number said the wait paths were covered.
  • Waveform clue: pulling the regression traces, every observed transaction completes in a single ACCESS cycle — PREADY is high on the first sample, every time. No transaction in the entire regression was ever held. The held-bus path the field bug lives on was never on any waveform, despite "98% coverage."
  • Root cause: the cp_wait coverpoint defined only a single bins zero = {0};. Because the responder model never injected wait states, that one bin hit 100%, and a coverpoint that is 100% of one bin contributes a perfect score to the summary. The 1-wait and N-wait scenarios had no bin to report them as missing, so the hole was invisible — the report read 98% while the entire wait-state path read 0% exercise. The coverage model was complete relative to its bins, and the bins had a hole.
  • Correct RTL: the fix is in the coverage model and the stimulus together. Split the coverpoint into meaningful bins — bins zero = {0}; bins one = {1}; bins many = {[2:$]}; — so the un-driven tail now shows as 0%, and configure the responder to inject 0, 1, and N waits across the regression so closure forces the held-bus scenarios to run. Add the x_dir_wait cross so both directions get stretched. The DUT bug is then exercised by construction, and the assertions/scoreboard catch the stale-data commit.
  • Verification assertion: beyond the wait coverpoint, add a sign-off gate that no meaningful bin or cross-cell is empty without an explicit ignore/illegal justification — a coverage review, not a runtime SVA: every cp_wait bin and every x_region_resp cell must be hit or formally excluded before "closed" can be declared. Pair it with a wait-injection assertion in the responder (assert (waits_injected_this_regression > 0)) so a responder that silently stops stretching the bus fails loudly.
  • Debug habit: when a "coverage-closed" design ships a bug, never trust the summary percentage — open the bin-level report for the coverpoint on the failing path. A high overall number almost always hides a coverpoint with too few bins, where one trivially-hit bin masks an un-exercised tail. The question is not "what's the coverage number?" but "which bins are empty, and is the empty one explained?" Functional-coverage completeness is a bin property, not a percentage property.
Two cases: on the left a buggy coverage report where the wait-count coverpoint has only a 0-wait bin that reads 100% so the 98% summary looks closed while the held-bus paths are never exercised and a bug ships; on the right the corrected model with 0/1/N bins plus a wait-injecting responder so the wait paths honestly show as uncovered until driven and closure forces them to run, illustrating that too-few bins hide a coverage hole behind a high percentage.
Figure 2 — a coverage report that looks 'closed' but hides an un-exercised wait-state path. Left (bug, red): the wait-count coverpoint defines only a single 0-wait bin; the responder never injects waits, so that bin reads 100% and the summary reads 98% 'closed' — but the held-bus paths (PREADY held low for 1..N cycles) were never exercised, and with no bin to report them missing, the hole is invisible and a wait-state bug ships. The waveform shows PREADY always high, zero waits, every transaction. Right (correct, green): the wait-count coverpoint is split into 0, 1, and 2..N bins and the responder injects waits, so the 1 and N bins start at 0% and honestly show the wait paths as un-covered until stimulus drives them; closure now forces the held-bus scenarios to run and the bug is caught. The waveform shows PREADY held low for N cycles, hitting the 'many' bin. The figure teaches that a high summary percentage can hide a hole when a coverpoint has too few bins, and the fix is meaningful 0/1/N bins plus stimulus that actually drives them.

8. Verification perspective

Functional coverage is the verification deliverable that turns "we ran tests" into a sign-off claim — and using it correctly means keeping it sharply distinct from the correctness checks.

  • Coverage is a sign-off gate, but it gates exercise, not pass/fail. Closure — every meaningful bin and cross-cell hit or formally excluded — is a required gate before tape-out, alongside (never instead of) all-assertions-passing and a clean scoreboard. The two gates answer different questions: coverage says "we drove every interesting scenario," the checkers say "every scenario we drove behaved correctly." A design ships only when both are satisfied; either alone is a false sign-off. Reviewing coverage means reading the bin-level report, not the headline percentage, because the percentage hides exactly the holes that ship bugs.
  • Distinguish exercised from passing, deliberately, in the model's structure. Keep coverpoints describing what happened on the bus (direction, region, response, wait, strobe) and let the scoreboard and assertions decide whether it was right. Crucially, the err bin filling means "an error response was observed" — not "the error was handled correctly"; that the error was handled correctly is the scoreboard's job. Conflating the two (e.g. only sampling coverage when the scoreboard passes) corrupts the exercise ledger and hides un-tested paths.
  • Close the known holes explicitly — wait-states and error-per-region. These two are the APB coverage holes that ship bugs. The wait-count coverpoint needs 0/1/[2:$] bins backed by a wait-injecting responder; the region × response cross needs every region's error cell driven, which means stimulus that targets errors at each mapped block, not just one. Treat both as named closure items reviewed individually, because both are the kind of path random stimulus reaches last, if ever.
  • Use illegal and ignore bins to make exclusions honest. A scenario that must never occur (e.g. a response combination the protocol forbids) goes in illegal_bins so that if it ever is observed, the simulation flags it — turning coverage into a lightweight checker for "this should be impossible." A scenario that legally cannot occur in this DUT (an unimplemented feature) goes in ignore_bins so it doesn't sit as a permanent un-closeable hole. The rule: every empty meaningful bin is either driven to closure or formally excluded with a reason — there are no silently-ignored holes.

The point: coverage is the gate that proves thoroughness of exercise, it is structurally separate from the correctness gate, and its value at sign-off is in the bin-level review that surfaces the wait-state and error-per-region holes before they reach silicon.

9. Interview discussion

"How do you know your APB verification actually tested everything that matters?" is a senior-DV question, and the answer that signals real experience is "functional coverage — and I keep it strictly separate from the checkers."

Lead with the distinction: coverage measures whether a scenario was exercised; assertions and the scoreboard measure whether it was correct — a green, coverage-closed regression needs both, because each answers a different question, and a passing assertion on an un-driven path proves nothing. Then describe the model concretely: one covergroup, sampled once per transaction from the monitor's analysis port, with coverpoints for direction, address region (per mapped register), response, wait-count, and PSTRB pattern — sampled from the observed transaction, not the stimulus, so it reflects the bus and not the generator's intent. The depth that distinguishes a strong candidate is the crosses: region × response and direction × wait are where the real scenarios live — marginal closure on each coverpoint does not prove you ever saw a write that errored or an error at the reserved region, and the cross is what forces those combinations. Land the closing on the known holes: wait-count is the classic APB coverage hole — responders default to zero waits, so a single {0} bin reads 100% while every held-bus path is un-tested; you fix it with 0/1/[2:$] bins and a wait-injecting responder. Finishing with "I never trust the coverage percentage — I read the bin-level report, because a high number almost always hides a too-coarse coverpoint" shows you understand that coverage completeness is a bin property, not a percentage.

10. Practice

  1. Build the covergroup. Write an APB covergroup with coverpoints for direction, region (four mapped blocks), response, and wait-count (0/1/[2:$]), sampled from a transaction item. State which port it subscribes to and why.
  2. Add the crosses that matter. Add direction × response, region × response, and direction × wait. For each, name one specific scenario the cross forces that the marginals would miss.
  3. Diagnose a false closure. Given a report showing 97% overall coverage but a field bug on a held bus, describe what you'd look at first and the most likely bin-level cause.
  4. Exercised vs correct. Explain what it means for the err bin to be hit, and what it does not mean. Which component decides whether the error was handled correctly?
  5. Illegal vs ignore. Give one APB scenario you'd put in illegal_bins and one you'd put in ignore_bins, and explain the difference in intent.

11. Q&A

12. Key takeaways

  • Coverage measures exercised; assertions and the scoreboard measure correct. They answer different questions, a green regression needs both, and a passing check over an un-driven path proves nothing — exercised ≠ correct.
  • The model is one covergroup, sampled once per transaction from the monitor's analysis port, with coverpoints for direction, address region (per mapped register), response, wait-count, and PSTRB pattern — sampled from the observed transaction, not the stimulus.
  • Crosses are where the real scenarios live. region × response forces an error at each region; direction × wait forces both directions to be held — combinations the marginals report as "covered" while the cells stay empty.
  • Wait-count is the classic APB coverage hole. A single {0} bin reads 100% while every held-bus path is un-tested; close it with 0/1/[2:$] bins and a wait-injecting responder.
  • A hole is an un-exercised path, not a bug. Every meaningful empty bin must be driven to closure or formally excluded with illegal_bins/ignore_bins and a reason — there are no silently-ignored holes.
  • Trust bins, not percentages. Coverage completeness is a bin property; a high summary number almost always hides a too-coarse coverpoint, so sign-off reviews the bin-level report, not the headline figure.