Part 5 · Functional Coverage · Intermediate

Closing Techniques

Constraint steering toward holes, directed tests for stubborn corners, seed sweeps, bin adjustments, and the diminishing-returns curve.

Technique 1 — steer constraints toward the holes

When triage says needs-constraint-change, the cheapest fix is usually an inline constraint that biases randomization onto the hole's ranges — either in a dedicated 'closure sequence' or via randomize() with at the call site. The base constraints stay untouched; the steering is additive and visible.

systemverilog
// Hole: <incr, max_len, unaligned> never hit — alignment starved it.
// Closure sequence: same transaction class, steered at the call.
task run_closure_burst(int n);
  repeat (n) begin
    dma_txn t = new();
    if (!t.randomize() with {
          btype == INCR;
          blen  == 16;                  // the max_len bin
          addr[1:0] != 2'b00;           // force unaligned
        })
      $fatal(1, "closure randomize failed");
    drive(t);
  end
endtask

// Distribution steering for a rare-but-not-empty bin (below at_least):
constraint c_close_fixed { btype dist { FIXED := 6, INCR := 3, WRAP := 1 }; }
  • Inline with constraints compose with (and can be contradicted by) class constraints — a randomize() failure here means the hole may be constraint-unreachable: back to triage.

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

  • Keep steering in clearly named closure sequences/tests — reviewers must be able to see that mainline stimulus was not narrowed.


Techniques 2 & 3 — directed tests and seed sweeps

Some corners resist steering: multi-step setups (fill the FIFO, then collide two events), exact timing races, or error recovery chains. These get directed tests — short, explicit, named after the hole they close. Before writing one, the cheaper intermediate is a seed sweep : more seeds of an existing test whose stimulus is in the right neighborhood. Sweeps help when hit probability per seed is small but not vanishing; they do not help when the probability is structurally near zero.

systemverilog
// Directed test for hole: timeout recovery (cp_fsm XFER => IDLE on timeout)
// Random stimulus can't hold the bus idle long enough; force it.
task test_timeout_recovery();
  cfg.timeout_cycles = 50;            // shortened via config — 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

Choosing between the three

diagram
per-seed hit probability      tool of choice
  ──────────────────────────────────────────────────────
  ~0   (structurally blocked)   directed test (or triage
                                says unreachable)
  tiny (one setup in millions)  steered constraints
  small (1 in ~10 seeds)        seed sweep
  fine (already hit, < at_least) dist reshaping

Technique 4 — adjust the bins (carefully) and know when to stop

Sometimes the right fix is in the model: add bins when triage keeps finding interesting scenarios between your buckets (a 'mid' range hiding a boundary), and widen or merge bins when distinctions carry no verification meaning (16 length bins where the design only distinguishes single/burst). Widening to make a hole disappear is only legitimate when the distinction was meaningless — that judgement belongs in review, not in a deadline commit.

diagram
THE DIMINISHING-RETURNS CURVE

  coverage %
  100 ┤                                    ____________ ●●● waiver/
   95 ┤                          _________/             directed-only
   90 ┤                  ______/        ▲ each point here costs
   85 ┤            _____/                 days of engineer time
   80 ┤        ___/
   70 ┤     __/        ▲ here a constraint tweak buys 5%
   50 ┤   _/
   30 ┤  /   ▲ here every seed adds whole percents
    0 ┤ /
      └──┬─────┬─────┬─────┬─────┬────────► effort (seeds + engr time)
       day1  week1  week2  week3  week4

  Strategy by region:
  0–80%  : run seeds, fix wrong-bins — coverage is cheap here
  80–95% : triage seriously; constraint steering and dist reshaping
  95–99% : directed tests for named stubborn corners only
  last 1%: every remaining hole is individually known — close it,
           waive it with documentation, or it blocks sign-off

Interview angle: "How do you close the last 5%?" is asked precisely because brute force stops working there. The expected answer is technique selection by hole class and hit probability — steering for starved ranges, directed tests for structural corners, sweeps only where probability justifies them, bin fixes where the model is wrong — plus the honesty that some of the last percent is waivers, properly documented.

Key takeaways

  • Match technique to triage class: steering for starved, directed for structural, sweeps for low-probability, bin fixes for model bugs.

  • Keep closure steering in named sequences/tests so mainline stimulus visibly stays broad.

  • A randomize()-with failure during steering is information: the hole may be constraint-unreachable.

  • Know where you are on the returns curve — the right tool at 60% is the wrong tool at 97%.

Common pitfalls

  • Closing holes by narrowing base-class constraints — every other test now generates less diverse stimulus.

  • Seed sweeps against structurally-blocked corners — thousands of CPU-hours, zero new bins.

  • Directed tests that hit the bin but check nothing — coverage without checking is a scenario observed, not verified.

  • Widening bins at deadline to delete holes — reviewers diff the model; silent goal-shrinking torpedoes trust.