Skip to content

UVM RAL · Chapter 10 · RAL Coverage

User-Defined Coverage

User-defined coverage is the covergroups you write yourself to require the specific values and crosses that built-in RAL coverage cannot know about, such as reserved encodings, boundary modes, and one field crossed with another. Writing the bins that matter is only half the job. The covergroup does nothing until its sample is called, so you must wire that sample to the right event with the right data, usually through a callback like post-predict, post-read, or post-write. This lesson shows how to write meaningful bins and, just as importantly, how to wire the sample so they actually fill. It then breaks the classic bug where a perfect covergroup never fills because its sample is never called or fires on the wrong event, leaving every critical bin stuck at zero.

Foundation12 min readUVM RALcoveragecovergroupsamplecross

Chapter 10 · Section 10.3 · RAL Coverage

1. Why Should I Learn This?

User-defined coverage is how you require the values that actually hide bugs — reserved encodings, boundary modes, crosses — that built-in coverage structurally cannot (10.2). But a covergroup only counts when it is sampled, and the most common user-defined-coverage failure is a perfect covergroup that never fills because its sample was never wired or fires on the wrong event. Knowing to write the critical bins and to wire the sample correctly is what makes user-defined coverage actually measure what you intended.

Learning user-defined coverage — the bins that matter and the sampling that makes them fill — is what closes the gap built-in coverage leaves (10.2), and it feeds the collection strategy (10.4) and closure (10.5) that follow.

2. Industry Story — the coverage that was written but never filled

A team, having learned that built-in coverage misses critical values (10.2), does the right thing: they write a careful covergroup for a mode field, with explicit bins for the reserved encoding, the boundary mode, and a cross with the DMA-enable field. The covergroup is reviewed and praised — the bins are exactly right. Then the regression runs, and those critical bins read 0%. More stimulus is added; the bins stay 0%.

The covergroup was perfect but never sampled: its sample() was never wired to any event, so no matter how many tests drove the reserved mode, the covergroup recorded nothing. The design knowledge in the bins was entirely wasted because the plumbing — the call that makes the covergroup fire on a register access — was missing. It looked like a coverage hole (critical bins unhit) but was really a wiring bug (the covergroup never sampled at all). The moment sample() was wired into a post_predict callback on the field, the existing tests immediately filled the bins. The post-mortem lesson: a user-defined covergroup records only when its sample() is called on the right event with the right data — a beautifully written covergroup that is never sampled (or is sampled from an event that does not fire) sits at 0% forever, and that 0% looks like a coverage hole but is really missing sampling wiring; writing the bins is only half the job, wiring the sample is the other half.

3. Concept — write the critical bins, and wire the sample

User-defined coverage has two halves, both required:

  • Write the bins that matter. Translate design knowledge into explicit coverpoint bins and crosses that require the functionally-critical values built-in bins cannot infer (10.2): a reserved bin for an illegal encoding, boundary bins, and a cross with the other field(s) whose combination matters. These must be hit for coverage to close.
  • Wire the sample — right event, right data. A covergroup records only when sample() is called. In RAL, wire it through:
    • a callbackpost_predict (fires on any field-value update, 9.2), or post_read/post_write (bus events, 9.3) — so it samples when the register/field is actually accessed/updated;
    • or a sample_values() override on the reg block/register that RAL invokes. Sample the coherent value at that moment (the value actually written/updated), not a stale or not-yet-updated one.
  • The failure mode: unsampled = 0% forever. If sample() is never called, or is wired to an event that does not fire, or reads stale data, the critical bins read 0 regardless of stimulus — a wiring bug that masquerades as a coverage hole.

Here is a user-defined covergroup with the critical bins and cross that must be hit — which only fill if sample() is wired correctly:

User-defined coverage REQUIRES the critical values + crosses — but only fills if sample() is wired

covergroup
User-defined coverage REQUIRES the critical values + crosses — but only fills if sample() is wiredcovergroup · mode_cov (user-defined) — requires the critical valuesbuilt-in bins misscp_mode (explicit functional bins)normal[0:5]60/1boundary (6)4/1RESERVED (7) — must be exercised2/1cross cp_mode x dma_on (combinations that matter)boundary x dma_on1/1reserved x dma_on1/1
Figure 1 — a user-defined covergroup for a mode field: explicit bins REQUIRE the reserved/illegal encoding and the boundary, and a CROSS requires the mode-with-DMA combinations that actually matter — exactly the values built-in bins do not require (10.2). These hits appear ONLY if the covergroup's sample() is called on the right event (typically a post_predict callback, 9.2) with the coherent value. A perfect covergroup whose sample() is never wired shows 0 on every one of these bins forever — a wiring bug that looks like a coverage hole.

4. Mental Model — a covergroup is a form that only records when someone submits it

5. Working Example — critical bins plus a wired sample

Write the covergroup with the critical bins and cross, then wire sample() into a callback so it fires on the right event:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Half 1 — the BINS that matter (what built-in coverage cannot require, 10.2):
covergroup mode_cov with function sample(bit [2:0] mode, bit dma_on);
  cp_mode: coverpoint mode {
    bins normal[]  = {[0:5]};
    bins boundary  = {6};
    bins reserved  = {7};           // the illegal encoding that must be exercised
  }
  cp_cross: cross cp_mode, dma_on;  // the mode x DMA combinations that matter
endgroup
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Half 2 — WIRE THE SAMPLE: fire the covergroup on the right EVENT with the COHERENT value.
// Here via a post_predict callback (9.2) so it samples on ANY update to the field, with the updated value.
class mode_cov_cb extends uvm_reg_cbs;
  mode_cov cg;
  virtual function void post_predict(input uvm_reg_field fld, input uvm_reg_data_t previous,
                                     inout uvm_reg_data_t value, input uvm_predict_e kind,
                                     input uvm_path_e path, input uvm_reg_map map);
    cg.sample(value[2:0], dma_field.get());   // <-- THE SUBMIT: without this call, the bins never fill
  endfunction
endclass
// Register the callback on the field (9.1) so post_predict — and thus cg.sample — actually fires.
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// Confirm it is actually sampling — do not assume. A quick check that catches the never-sampled bug:
// after stimulus, if cp_mode.reserved is 0 despite the reserved value being driven, sample() is not wired.
`uvm_info("COV", $sformatf("mode_cov = %.1f%% (0 on reserved despite driving it -> check sample wiring)",
                            mode_cov.get_coverage()), UVM_LOW)

The bins encode what matters; the wired sample() in post_predict is what makes them fill. Omit or misdirect the sample, and the critical bins sit at 0% — the bug of the next section.

6. Debugging Session — a perfect covergroup that never samples

1

A correctly written covergroup reads 0% on its critical bins because its sample() is never called — a wiring bug that masquerades as a coverage hole

WIRE THE SAMPLE
Buggy Code
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
// The covergroup is perfect — reserved bin, boundary bin, cross all correct:
covergroup mode_cov with function sample(bit [2:0] mode, bit dma_on);
  cp_mode: coverpoint mode { bins normal[] = {[0:5]}; bins boundary = {6}; bins reserved = {7}; }
  cp_cross: cross cp_mode, dma_on;
endgroup
mode_cov cg = new();
// BUG: cg.sample(...) is NEVER called anywhere — no callback wired, no sample_values override.
// The regression drives the reserved mode many times, but the covergroup records NOTHING -> bins 0% forever.
Symptom

The covergroup's critical bins — reserved, boundary, the cross — read 0%, and stay at 0% no matter how much stimulus is added, including tests that demonstrably drive the reserved mode. It looks like a coverage hole: 'our tests are not hitting the reserved encoding.' But the tests are hitting it; the covergroup simply is not recording it, because sample() is never called. The tell — invisible unless you look for it — is that every bin, even ones the stimulus obviously exercises, reads 0: a real coverage hole would leave some bins filled, but a never-sampled covergroup leaves all of them empty.

Root Cause

A covergroup records only when its sample() is called; the bins define what to record, but nothing is recorded until something invokes the sample on an event. Here cg.sample(...) was never wired — no callback fires it, no sample_values() override calls it — so the covergroup, however perfectly specified, never sampled a single access. The reserved mode being driven in the DUT is irrelevant to the covergroup, because the covergroup only sees values that are passed to its sample(), and sample() was never called with any value. The 0% is therefore a wiring defect (missing sample invocation), not a stimulus defect (values never driven) — but the two produce a similar-looking result (unfilled bins), which is why it masquerades as a coverage hole. The distinguishing signature is that all bins are empty, including ones the stimulus certainly exercises; a genuine hole would show a mix of filled and empty bins. The design knowledge in the bins is intact and correct; the plumbing that feeds them is absent.

Fix

Wire sample() to fire on the right event with the coherent value: register a callback (e.g. post_predict on the field, 9.2, for value updates; or post_read/post_write, 9.3, for bus events) whose body calls cg.sample(current_value, ...), or provide a sample_values() override RAL invokes — so the covergroup samples on every relevant access. With the sample wired, the existing stimulus immediately fills the bins the reserved/boundary/cross values were already exercising. The rule the bug teaches: a user-defined covergroup records only when its sample() is called on the right event with coherent data — writing the bins is half the job, wiring the sample is the other half; a never-sampled (or wrong-event/stale-data) covergroup sits at 0% forever, and that 0% masquerades as a coverage hole. The diagnostic tell: when all bins of a covergroup read 0 — including ones the stimulus clearly exercises — suspect sampling wiring before suspecting the stimulus.

7. Common Mistakes

  • Writing the bins but never wiring sample(). A covergroup records only when sampled — an unsampled covergroup reads 0% forever, looking like a coverage hole.
  • Sampling from an event that does not fire. Wiring sample() to a callback/hook that never runs (unregistered, wrong hook) leaves the bins empty (9.1/9.5).
  • Sampling stale or not-yet-updated data. Sampling before the value updates (wrong pre/post timing, 9.3) or reading a stale mirror records the wrong value into the bins.
  • Assuming perfect bins mean working coverage. The design half (bins) and the plumbing half (sample wiring) are both required — flawless bins fill nothing without a sample.
  • Reading all-bins-0 as a stimulus hole. A genuine hole leaves some bins filled; all bins empty (including exercised ones) is a sampling-wiring bug.

8. Industry Best Practices

  • Require the critical values and crosses explicitly. Reserved encodings, boundary modes, and crosses that built-in coverage cannot infer (10.2) belong in user-defined coverpoints/crosses.
  • Wire the sample deliberately. Fire sample() from a callback (post_predict for updates, post_read/post_write for bus events) or a sample_values() override, on the right event with the coherent value.
  • Verify the covergroup actually samples. Confirm bins fill for values the stimulus drives; all-bins-0 means the sample is not wired.
  • Sample coherent data at the right moment. Match the sampling event to the value you mean to record (updated value, returned value) — avoid stale/pre-update data.
  • Treat bins and sampling as one deliverable. User-defined coverage is not done when the bins are written — it is done when they fill from real stimulus.

9. Interview / Review Questions

10. Key Takeaways

  • User-defined coverage complements built-in coverage by requiring the functionally-critical values and crosses the automatic bins cannot infer (10.2) — reserved encodings, boundary modes, and mode-with-other-field crosses, written as explicit coverpoint bins and crosses.
  • It has two halves, both required: write the bins that matter (design knowledge) and wire the sample() to fire on the right event with coherent data — typically via a callback (post_predict/post_read/post_write, 9.2/9.3) or a sample_values() override.
  • A covergroup records only when sample() is called — so a perfect covergroup that is never sampled (or sampled on a non-firing event / stale data) reads 0% forever.
  • That 0% masquerades as a coverage hole but is really a sampling-wiring bug — the tell is that all bins are empty, including ones the stimulus clearly exercises (a genuine hole leaves a mix).
  • Treat bins and sampling as one deliverable: user-defined coverage is done when the critical bins demonstrably fill from real stimulus — and when critical bins read 0, suspect the sample wiring before the stimulus.