Skip to content
DenseChip
Learn
PracticeCode labInterview

DenseChip

Navigate

Menu

LearnPracticeCode labInterview

Tools

All toolsSV editorCode repoResume
DenseChip

Practice, courses, and chip-design tools in one focused workspace.

LearnPracticeCode labInterviewToolsLearning paths

© 2026 DenseChip

AboutContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here4
  • UVM Foundations14
  • UVM Phases & Simulation Lifecycle26
  • Factory & Configuration15
  • Transaction-Level Modeling (TLM)13
  • UVM Sequences & Stimulus21
  • UVM Agents & Protocol VIP21
  • Environment & Test Classes11
  • Checking & Coverage in UVM21
  • UVM Register Abstraction Layer (RAL)22
  • Advanced UVM in Practice13
  • Debug & Regression10
  • Coding Practice47
  • Verification in Industry Practice10
  • Senior Prep: Architecture, Leadership and Interviews32
    • Verification Architecture and Planning
    • UVM with SVA and Formal
      • Where Assertions Live
      • Assertion Control from the Test
      • Cover Properties and Coverage Closure
      • Reusing Properties in Formal
      • Reporting Assertion Failures through uvm_report
    • Leadership and Senior Judgment
    • Interview Bank
Loading lesson

‹UVM with SVA and Formal
  1. Topics
  2. UVM Verification
  3. Senior Prep: Architecture, Leadership and Interviews
  4. UVM with SVA and Formal
  5. Cover Properties and Coverage Closure

Part 13 · Senior Prep · Intermediate

Cover Properties and Coverage Closure

Bringing cover-property hits into the UVM report and the plan, required covers in directed tests, and vacuous passes.

On this page

    dma_check_test ends with eight plan holes, among them gap=0 (back-to-back beats), len=16, len>16 and outcome=err. On this machine the covergroups print 0.0%, because Verilator ignores coverpoints, so the plan-item tally is the only coverage evidence in the log. The checker you bound in lesson 1 also has covers. Do they agree with the tally, how do their hits reach the same report, and what does a cover tell you about the assertions sitting next to it?

    The idea: a cover is a witness, and witnesses must reach the jury

    A cover property records that a sequence of signal values happened. It is evidence that the stimulus reached a situation, nothing more: a cover hit is not a pass. When to choose a cover property over a covergroup bin is its own decision, made in SVA cover property vs Covergroups. This lesson is about what happens after the cover is written: getting its hits to wherever the plan is judged, using it to show that each assertion actually had something to check, and making a directed test prove its own scenario happened.

    A witness who saw the crime but never testifies does not help the jury. Cover hits that sit in a simulator database nobody merges, or in a log section nobody reads, are that witness.

    The picture: two routes from a cover to the plan

    Flowchart: a cover property feeds a glue package through its pass action and then a UVM component's report_phase, which updates the plan tally; separately the simulator writes the cover to the coverage database, which is merged and annotated onto the plan.
    The database route is the standard one for regression closure. The action-block route puts the same hits into the UVM log, where per-test checks and the plan tally can use them.

    Use both. The database route is what sign-off reads: commercial simulators record every cover property by hierarchical name next to covergroups, and the vendor's plan tool maps each cover to a plan row and merges hits across the whole regression. The action-block route is what one test can act on during the run: a UVM component can print the hits, add them to the plan-item tally, or fail a directed test whose scenario never happened.

    Build it: covers that report into UVM

    A concurrent cover cannot live in a class, and a class cannot name the checker's hierarchy. A small package shared by both solves it: the cover's pass action calls a package function, and any component can read the package's counters.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // snippet: covers in dma_rules_chk, and the glue that brings their hits into UVM
    package sva_glue_pkg;                     // compiled before the checker and the tests
      int unsigned cover_hits[string];
      function automatic void cover_hit(string name);
        cover_hits[name]++;
      endfunction
    endpackage
    
    // inside dma_rules_chk:
      c_b2b_in:   cover property (@(posedge clk) disable iff (!rst_n) take ##1 take)
        sva_glue_pkg::cover_hit("b2b_in");
      c_len_ge16: cover property (@(posedge clk) disable iff (!rst_n) go && len >= 16)
        sva_glue_pkg::cover_hit("len>=16");
      c_err:      cover property (@(posedge clk) disable iff (!rst_n) $rose(err))
        sva_glue_pkg::cover_hit("err");
    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // snippet: from the scratch bench
    class sva_cover_report extends uvm_component;
      `uvm_component_utils(sva_cover_report)
      string required[$] = '{"b2b_in", "len>=16", "err"};
      function new(string name, uvm_component parent);
        super.new(name, parent);
      endfunction
      function void report_phase(uvm_phase phase);
        string holes = "";
        foreach (required[i]) begin
          int unsigned n = sva_glue_pkg::cover_hits.exists(required[i]) ? sva_glue_pkg::cover_hits[required[i]] : 0;
          `uvm_info("SVA_COV", $sformatf("cover %-8s %0d hit(s)", required[i], n), UVM_LOW)
          if (n == 0) holes = {holes, " ", required[i]};
        end
        if (holes != "") `uvm_info("SVA_COV", {"cover holes:", holes}, UVM_LOW)
      endfunction
    endclass
    • One counter per cover, keyed by plan-item name, so the names match the plan rows rather than the hierarchy.

    • `disable iff (!rst_n)` on every cover, so nothing during reset counts.

    • The report is per test. Merging across tests is still the database's job; this component answers "what did this run reach?"

    Note

    Verilator runs a cover's pass action only when the model is built with coverage enabled (--coverage-user), which is how the scratch bench was built. Commercial simulators run pass actions by default; $assertcontrol control types 6 and 7 switch them on and off.

    Watch it run (scratch runs)

    Diagram
    +UVM_TESTNAME=sva_rand_test
    UVM_INFO sva_tb.sv(98) @ 1305000: uvm_test_top.cov [SVA_COV] cover b2b_in   0 hit(s)
    UVM_INFO sva_tb.sv(98) @ 1305000: uvm_test_top.cov [SVA_COV] cover len>=16  0 hit(s)
    UVM_INFO sva_tb.sv(98) @ 1305000: uvm_test_top.cov [SVA_COV] cover err      0 hit(s)
    UVM_INFO sva_tb.sv(101) @ 1305000: uvm_test_top.cov [SVA_COV] cover holes: b2b_in len>=16 err
    UVM_INFO dma_tb.sv(411) @ 1305000: uvm_test_top.env.sb [SB] 14 words matched, 0 mismatched
    
    +UVM_TESTNAME=sva_truncate_knob_test
    UVM_INFO sva_tb.sv(98) @ 485000: uvm_test_top.cov [SVA_COV] cover b2b_in   0 hit(s)
    UVM_INFO sva_tb.sv(98) @ 485000: uvm_test_top.cov [SVA_COV] cover len>=16  0 hit(s)
    UVM_INFO sva_tb.sv(98) @ 485000: uvm_test_top.cov [SVA_COV] cover err      1 hit(s)
    UVM_INFO sva_tb.sv(101) @ 485000: uvm_test_top.cov [SVA_COV] cover holes: b2b_in len>=16
    • `b2b_in` is 0 in every run, and so is the tally's gap=0. Two independent measurements, one a cover on the RTL's take and one a covergroup on monitor timing, report the same hole: the stream driver always drops TVALID between beats. When two instruments agree, the hole is in the stimulus.

    • `len>=16` is 0, matching the tally's len=16 and len>16 holes. It is also why the fault campaign missed LEN bit 4: the cover names the reason for the miss.

    • `err` is hit only by the truncate test, as expected. In the random test, any assertion about ERR behaviour would have passed without ever being exercised.

    Vacuous passes: the assertion that checked nothing

    An implication A |-> B that never sees A succeeds vacuously on every clock: it reports no failure because it was never asked anything. That is the assertion version of the scoreboard that compared zero words. The defence is the same as the scoreboard's SB_EMPTY check: pair each important assertion with a cover of its antecedent, and treat a zero as a gap. For a_full_packet, the antecedent take && s_tlast happens once per packet, so the random test exercised it three times; for a rule such as "after ERR, BUSY stays low until GO", the random test would have exercised it zero times. Commercial tools report vacuous and non-vacuous success counts per assertion, and the plan should read the non-vacuous count.

    Two counting details come up in interviews. A cover property counts at most one success per attempt, while a cover sequence counts every match of every attempt, so a sequence with a range (##[1:4]) can count several times from one start. And a cover with a long range written as [*3:$] matches from many starting clocks, which inflates its count without meaning more.

    What breaks: a plan item closed by a run that checked nothing

    Look at the truncate knob test again. c_err was hit, and the plan row "a short packet sets ERR" looks covered. In that same run a_full_packet was switched off, which was correct for that test. Now imagine the plan row were "the bench flags packets shorter than LEN": it would be marked covered by a run in which the rule that flags them was disabled. Coverage from a run says what happened; it does not say what was checked during it. Two rules keep this honest. Count coverage only from passing runs, as the plan-ranking script in the industry section does, and for any plan row that names a check, require both a cover hit and the related assertion enabled in that run, which is easy when every checker prints its knob state.

    Interview

    "How do assertion covers feed your coverage closure?"

    A weak answer: "The simulator collects them automatically," or "cover hits show the assertions passed."

    A strong answer: "Covers prove the stimulus reached a situation; they never prove correctness. In regression they go into the coverage database by hierarchical name and the plan tool maps them to plan rows, merged with covergroups. Inside a run, I bring the hits into UVM through a small package the cover's pass action calls, so a component can print them, add them to the plan tally and fail a directed test whose scenario never happened. I pair important assertions with covers of their antecedents to catch vacuous passes. On our DMA, a back-to-back cover and a gap covergroup both read zero, which told us the hole was the driver, not the model."

    Follow-ups: What is a vacuous pass? (an implication whose antecedent never occurred: no failure, no check). cover property versus cover sequence counts? (one per attempt versus every match).

    Check yourself A directed test is meant to exercise the ERR path. It passes, but c_err reports 0 hits. What do you conclude, and what would you change in the test?

    The test did not do what its name says: maybe the truncating driver override did not apply, or the packet was full length. The pass is meaningless for ERR. Make the test check its own scenario: in report_phase (or check_phase), read the cover counter and raise uvm_error if err is 0. A directed test that cannot fail when its scenario is missing is a false pass waiting to happen.

    Check yourself An assertion $rose(irq) |-> irq_en has run in 2,000 regressions without a failure. What do you check before quoting that as evidence?

    The non-vacuous count, or a cover of $rose(irq). If no test ever raised the interrupt (for example because irq_en was always 0), the assertion never had an antecedent to check. Also check it was enabled in those runs, and not killed by a test helper.

    Check yourself Why is the cover c_len_ge16 written on go && len >= 16 rather than on the APB write of LEN?

    Because the plan item is "a job of 16 or more words ran", not "someone wrote a large LEN". A LEN write while busy is ignored by the DMA, and a LEN written but never followed by GO starts nothing. Covering the moment the job starts, with the value the DUT really holds, measures what the design did, not what the bench sent.

    Next: the covers and assertions in this checker are ordinary SVA. Reusing Properties in Formal runs the same file under a formal tool, and brings a counterexample back as a UVM test.

    Related topics

    • Up: UVM with SVA and Formal

    • SVA cover property vs Covergroups

    • Regression merge and the closure loop

    • False-pass playbook

    Previous

    259 / 280

    Next: Reusing Properties in Formal

    Up next

    Up nextReusing Properties in Formal

    Sign in to save progress

    Practice this lessonReport an error

    See also

    • Where Assertions Live
    • Reporting Assertion Failures through uvm_report