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

AboutRoadmapCareersInternshipsFAQContactPrivacyTerms
Skip to lesson

Course contents

  • Start Here5
  • SystemVerilog Language Foundations28
  • Object-Oriented SystemVerilog for Verification30
  • Constraint Randomization — Interview-Critical Deep Dive39
  • SystemVerilog Assertions — Interview-Critical Deep Dive41
  • Functional Coverage in SystemVerilog25
    • Covergroups & Coverpoints
    • Bins: Explicit, Auto, Illegal & Ignore
    • Cross Coverage
    • Coverage Options & Methods
    • Coverage Closure & Sign-Off
      • From Spec to Coverage Plan
      • Hole Analysis and Triage
      • Closing Techniques
      • Exclusions and Waivers
      • Sign-Off Metrics and Review
  • Testbench Architecture in SystemVerilog24
  • Advanced Language & Integration26
  • Senior & Interview Preparation23
  • Principal-Level Verification34
Loading lesson

←Coverage Closure & Sign-Off
  1. Topics
  2. SystemVerilog
  3. Functional Coverage in SystemVerilog
  4. Coverage Closure & Sign-Off
  5. Closing Techniques
On this page

    Part 5 of 9, lesson 23 of 25Part 5 of 9/Functional Coverage/Lesson 23 of 25Intermediate

    Closing Techniques

    Match each hole to its cheapest fix: steering, directed tests, sweeps, or bin fixes.

    A team narrowed the base transaction class to close one alignment hole, and every other test lost unaligned traffic. A steered closure sequence would have closed it without touching the base.

    After this lesson you can: match each hole to its cheapest fix: steered constraints, a seed sweep, a directed test, or a bin change.

    Steering constraints toward holes

    When triage says needs-constraint-change, the cheapest fix is usually an inline constraint that biases randomization onto the hole ranges. The base constraints stay untouched, so the steering is additive and visible.

    The listing below is not run in this course's checks: it is a fragment that calls the base test's driver.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // not-run: fragment (drive_burst is the base test's driver task)
    class dma_txn;
      typedef enum {INCR, FIXED, WRAP} btype_e;
      rand btype_e btype;
      rand int unsigned blen;
      rand bit [31:0] addr;
      constraint c_basic {
        blen inside {[1:16]};
        (btype == WRAP) -> (blen inside {2, 4, 8, 16});
      }
      constraint c_close_fixed {
        btype dist {FIXED := 6, INCR := 3, WRAP := 1};   // below-at_least reshaping
      }
    endclass
    
    // Hole: <incr, max_len, unaligned> never hit — alignment starved it.
    // Closure sequence: same transaction class, steered at the call site.
    // drive_burst is the existing bus-driver task from the base test.
    task automatic run_closure_burst(int n);
      dma_txn t = new();
      repeat (n) begin
        if (!t.randomize() with {
              btype == INCR;
              blen == 16;                  // the max_len bin
              addr[1:0] != 2'b00;          // force unaligned
            })
          $fatal(1, "closure randomize found no solution");
        drive_burst(t);
      end
    endtask
    • Inline constraints compose with class constraints. A randomize() failure here means the hole may be constraint-unreachable, so go back to triage.

    • dist reshaping closes below-at_least holes without excluding the rest of the space.

    • Keep steering in clearly named closure sequences. Reviewers must see that mainline stimulus was not narrowed.

    Directed tests and seed sweeps

    Some corners resist steering: multi-step setups, exact timing races, or error recovery chains. These get directed tests, short and explicit, named after the hole they close.

    Before writing one, try the cheaper intermediate: a seed sweep. That is more seeds of an existing test whose stimulus is already in the right neighborhood.

    The listing below is not run in this course's checks because it is a fragment of a full DMA testbench: the DUT and the base-test helpers are not shown.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // not-run: fragment: needs the DMA DUT and the base-test helpers, which are not shown
    // Directed test for hole: timeout recovery (cp_fsm XFER => IDLE on timeout).
    // Random stimulus cannot hold the bus idle long enough, so force it.
    // start_burst, stall_ready, and check_abort_status are the base-test helpers.
    task automatic test_timeout_recovery();
      cfg_timeout_cycles(50);            // shortened through config, a legal knob
      start_burst(INCR, 8);
      stall_ready(100);                  // hold ready low past the timeout
      wait (fsm_state == IDLE);          // recovery observed
      check_abort_status();              // closure AND checking
      start_burst(INCR, 4);              // prove the DUT still works after
    endtask

    Per-seed hit probability

    Tool of choice

    About 0: structurally blocked

    A directed test, or triage says unreachable

    Tiny: one setup in millions

    Steered constraints

    Small: about 1 seed in 10

    A seed sweep

    Fine: already hit, below at_least

    Reshaping the dist

    “A hole you cannot hit — what do you do?” Triage before effort, cheapest check first: bin correctness, reachability with the designer, steering for starved stimulus, and a directed test that checks the behavior, not just the bin, only for reachable structural corners.

    Adjusting bins and knowing when to stop

    Sometimes the right fix is in the model. Add bins when triage keeps finding interesting scenarios between buckets, and widen or merge bins when distinctions carry no verification meaning.

    Widening to delete a hole is legitimate only when the distinction was meaningless. That judgement belongs in review, not in a deadline commit.

    The diminishing-returns curve.

    “Describe coverage-driven verification.” The plan defines measurable goals first, constrained-random stimulus does the broad work, and merged coverage feedback decides what happens next: steering, directed tests, or waivers. Progress is newly hit bins per unit effort, which surfaces the diminishing-returns decision points.

    The bench crosses burst type with alignment, not length with alignment, for a reason worth knowing: Verilator 5.048's solver does not spread blen inside {[1:16]} evenly. In 1,000 solves, length 16 came up about a third as often as uniform odds predict, so a length-based cross would have shown a hole the stimulus, not the tool, should explain.

    Narrowing base-class constraints to close one hole. Every other test then generates weaker stimulus, and the review cannot see what breadth was lost.

    Check yourself A hole has a tiny per-seed hit probability, but the setup exists in random stimulus. Which tool?

    Steered constraints. Sweeps only pay when the probability justifies them, and directed tests are for structurally blocked corners.

    Check yourself Steering fails randomize() at the call site. What does that tell you?

    The hole may be constraint-unreachable. Go back to triage instead of forcing the constraints harder.

    Testbench

    The bench reproduces hole 1 from the triage lesson. The transaction class aligns every address, so 300 random bursts never fill the unaligned column of a burst-type × alignment cross, counted with the bins model. A closure sequence then turns that one constraint off and steers at the call site, and the class goes back to normal afterwards.

    Predict first 300 random bursts, every address aligned by c_aligned. What does the cross read, and how many steered bursts close it?

    2 of 4: the aligned column only. 8 steered bursts close it, 4 INCR and 4 FIXED with addr[1:0] != 0, because the hole was blocked by one constraint, not rare.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // uses: covmodel
    // Hole: unaligned INCR bursts were never sent, because one constraint aligned every address.
    // The closure sequence turns that constraint off and steers at the call site.
    class dma_txn;
      typedef enum {INCR, FIXED, WRAP} btype_e;
      rand btype_e btype;
      rand int unsigned blen;
      rand bit [31:0] addr;
      constraint c_basic   { blen inside {[1:16]}; (btype == WRAP) -> (blen inside {2, 4, 8, 16}); }
      constraint c_aligned { addr[1:0] == 2'b00; }           // in every sequence: this starved the unaligned cells
    endclass
    
    Terminal
    after 300 random bursts:
      cross x_align: 50.00% (2 of 4 bins)
        hole: <incr, unalign>
        hole: <fixed, unalign>
    after 8 steered bursts:
      cross x_align: 100.00% (4 of 4 bins)
    PASS: 300 random bursts left the unaligned column empty; 8 steered bursts closed it

    PacketFIFO step 5: close a hole. Suppose the generator so far makes only legal DATA and CTRL packets. The kind x dest cross then has four empty cells, all ERR. Name them from the report, then close them with four directed ERR packets, one per destination.

    design.sv
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // uses: covmodel
    // PacketFIFO step 5: close a hole. Stimulus so far sends only legal DATA and CTRL packets, so the ERR cells stay empty.
    module tb_pkt_close_holes;
      cm_point cp_kind = new("cp_kind"), cp_dest = new("cp_dest");
      cm_cross x_kind_dest;
      function automatic void send(int kind, int dest);
        cp_kind.sample(kind); cp_dest.sample(dest); x_kind_dest.sample();
      endfunction
      initial begin
        cp_kind.add("data", 0, 0); cp_kind.add("ctrl", 1, 1); cp_kind.add("err", 2, 2);
        cp_dest.add_each("d", 0, 3);
        x_kind_dest = new("x_kind_dest", cp_kind, cp_dest);
        for (int i = 0; i < 40; i++) send(i % 2, (i / 2) % 4);   // the generator never makes kind 2
        $display("after 40 packets:");
        x_kind_dest.report();
        if (x_kind_dest.n_covered() != 8 || x_kind_dest.n_bins() != 12) $fatal(1, "FAIL: expected 8 of 12 cells");
        for (int d = 0; d < 4; d++) send(2, d);                  // four directed ERR packets, one per dest
        $display("after four directed ERR packets:");
        x_kind_dest.report();
        if (x_kind_dest.get_coverage() != 100.0) $fatal(1, "FAIL: cross not closed");
        $display("PASS: the cross named four ERR holes and four directed packets closed it");
        $finish;
      end
    endmodule
    Terminal
    after 40 packets:
      cross x_kind_dest: 66.67% (8 of 12 bins)
        hole: <err, d[0]>
        hole: <err, d[1]>
        hole: <err, d[2]>
        hole: <err, d[3]>
    after four directed ERR packets:
      cross x_kind_dest: 100.00% (12 of 12 bins)
    PASS: the cross named four ERR holes and four directed packets closed it

    Forty more random-looking DATA and CTRL packets would not have moved the number. The holes were a stimulus gap, so the fix was a small directed test, as this lesson describes.

    • Steer at the call site; leave base constraints alone.

    • A failed steered randomize() means back to triage.

    • Late percents cost days each: know where you are on the curve.

    Rule of thumb: steer in named closure sequences reviewers can see.

    How this was checked

    Counted with the course's bins model (rich/coverage/cov_model.sv, listed on The Bins Model page) on Verilator 5.048 (--binary --timing) on 2026-10-18; every bench printed the PASS line shown. Verilator 5.048 parses covergroups with a COVERIGN warning and ignores their bins, and Icarus 13 has none, so each covergroup listing is marked not run and its bench counts the same bins with the model.

    300 random bursts left the unaligned column empty; with c_aligned off for one sequence, 8 steered bursts closed it.

    In 1,000 solves Verilator 5.048 drew blen = 16 about a third as often as uniform odds predict, so the bench crosses burst type with alignment instead of length.

    PacketFIFO step 5: 40 DATA and CTRL packets left four ERR cells as named holes, and four directed ERR packets closed the cross.

    Related topics

    • Stimulus generation

    • Randomization interview Q and A

    Previous

    166 / 275

    Next: Exclusions and Waivers

    Up next

    Up nextExclusions and Waivers

    Sign in to save progress

    Practice this lessonPrevious lessonReport an error

    See also

    • From Spec to Coverage Plan
    • Sign-Off Metrics and Review