Coverage architecture and planning

Sample coverage where it proves what the design did, cross it with the register state that gives a requirement its meaning, and trace one requirement from the specification to the test that hits it.

Lesson
U15.1
Time
30 min
Runs on
Verilator with UVM
Check
5 questions

The burst test passed with no errors, and its coverage report listed REQ-7, a write burst is accepted while CTRL.burst = 1, as hit three times. The port never accepted a write burst. The test had written CTRL = 01, leaving the burst bit (bit 1) clear, so the port refused all three and the scoreboard, predicting from the register, agreed. The report came from a collector fed by the driver.

Collector fed byREQ-7 (burst accepted)REQ-8 (burst refused)Holes
The driver, before driving30REQ-8
The monitor, at the observed answer03REQ-7

Measured: the example run, ten requests with three write bursts, CTRL = 01.

Three places a collector can listen

Two collectors, one write burstThe example at 80-105 ns, the fourth request (a write burst of length 2). Arrows carry data: an item, pin values or an observation handle.
Two collectors, one write burstThe example at 80-105 ns, the fourth request (a write burst of length 2). Arrows carry data: an item, pin values or an observation handle. sequence req_seq Item 4: write, len 2 driver Driver Knows the item andthe configuration subscriber drv_cov Counts what wasasked for design req_port CTRL = 01: burstsoff monitor Monitor Request at the takeedge, ok at theanswer subscriber mon_cov Counts what the portdid checker Scoreboard Predicts fromCTRL.burst itemwrite(intent)valid, wr, lenpins + ctrl_burstwrite(obs)write(obs)
  1. 80 ns: intent

    The driver takes item 4 and, before any pin moves, writes its intent. The configuration says ctrl_value = 2'b11, so drv_cov counts REQ-7: a write burst with bursts on.

    More context

    The intent is counted from the item and the configuration alone. Nothing the port does later can change this count: it ends at REQ-7 3, REQ-8 0.

    Follow the calls, values and waits
    1. data item 4: write, len 2: req_seq → Driver
    2. data intent: REQ-7: Driver → drv_cov
  2. 90 ns: valid rises

    The driver raises valid at 90 ns with write and length 2 on the pins. The port does nothing until its next clock edge.

    More context

    The timing probe of the example bench puts the drive 10 ns after the intent: the driver waits for the negative edge before it changes a pin.

    Follow the calls, values and waits
    1. data valid, wr = 1, len = 2: Driver → req_port
    2. Waiting: Driver — holding valid until the take edge
  3. 95 ns: taken and recorded

    The port takes the request at the 95 ns edge, holding CTRL = 01: the test wrote bit 0 and left the burst bit clear. At the same edge the monitor records write, length 2 and ctrl_burst = 0, the register state the request met.

    More context

    The monitor reads ctrl_burst from the pin at the take edge. Reading the configuration instead would record 1, the value the test meant to write.

    Follow the calls, values and waits
    1. data taken: write, len 2, ctrl_burst = 0: req_port → Monitor
    2. Waiting: Monitor — holding the request until the answer
    3. Released: Driver
  4. 105 ns: the answer is observed

    The port registered ok = 0 at the 95 ns edge; the monitor samples it at the 105 ns edge. It writes one observation to both listeners: mon_cov counts REQ-8 (refused), and the scoreboard predicts a refusal from ctrl_burst = 0, so it matches.

    More context

    Both listeners get the same handle in one ap.write. Over the ten requests the monitor side ends at REQ-7 0, REQ-8 3, and the scoreboard at 10 answers checked, 0 mismatches.

    Follow the calls, values and waits
    1. data ok = 0: req_port → Monitor
    2. data obs: write, len 2, ctrl_burst 0, ok 0: Monitor → mon_cov
    3. data obs: write, len 2, ctrl_burst 0, ok 0: Monitor → Scoreboard
    4. Released: Monitor
Read the diagram as text
  • req_seq: Item 4: write, len 2
  • Driver: Knows the item and the configuration
  • drv_cov: Counts what was asked for
  • req_port: CTRL = 01: bursts off
  • Monitor: Request at the take edge, ok at the answer
  • mon_cov: Counts what the port did
  • Scoreboard: Predicts from CTRL.burst
  • req_seq → Driver: item
  • Driver → drv_cov: write(intent)
  • Driver → req_port: valid, wr, len
  • req_port → Monitor: pins + ctrl_burst
  • Monitor → mon_cov: write(obs)
  • Monitor → Scoreboard: write(obs)

Both collectors run the same bins. Only the one fed by the monitor counts what the port did.

A coverage collector is usually a uvm_subscriber: an analysis export and a write() that samples each transaction it receives. The architecture question is which port feeds it, because that decides what a hit means.

Sampling pointWhat a hit recordsUse it for
Driver intent, before the pins moveThe stimulus asked for thisStimulus quality: did the sequences ask for bursts at all?
Monitor transaction, at completionThe design took it and answeredRequirements: what the design did
Register or configuration stateThe mode the request metCrosses that give a bin its meaning

The third row is not a separate stream: it is a field the monitor adds to each transaction. In this bench a missing answer is also an error, so every planned request must reach the collector.

Sample at the event that establishes the requirement (here, the answer), and attach the state the request met at the event it qualifies (here, the take edge). REQ-7 and REQ-8 are about the answer, so their bins sample when the monitor observes it, one edge after the port took the request. The mode they cross with is the one sampled at the take edge. Other requirements need other events: an acceptance-rate or occupancy goal can sample at the take edge or every cycle.

Cross the transaction with the state that gives it meaning

REQ-7 and REQ-8 describe the same stimulus, a write burst. They differ only in the CTRL.burst value the request met and in the answer. So the bin crosses four things: direction, length, register state and response.

REQ-7 and REQ-8 as cross bins of a covergroup inside the subscriber. With legal lengths 1 to 4, burst matches the counter's len > 1.
covergroup req_cg with function sample(req_obs t);
  dir:  coverpoint t.wr  { bins rd = {0}; bins wr = {1}; }
  len:  coverpoint t.len { bins single = {1}; bins burst = {[2:4]}; }
  mode: coverpoint t.ctrl_burst;
  resp: coverpoint t.ok;
  rows: cross dir, len, mode, resp {
    bins req7 = binsof(dir.wr) && binsof(len.burst) && binsof(mode) intersect {1} && binsof(resp) intersect {1};
    bins req8 = binsof(dir.wr) && binsof(len.burst) && binsof(mode) intersect {0} && binsof(resp) intersect {0};
  }                      // the other combinations get automatic cross bins
endgroup
// in req_coverage: function new ... req_cg = new(); ... write(t): req_cg.sample(t);

With a covergroup, one more trap appears. drv_cov and mon_cov are two instances of one class, and by default their covergroups share one type coverage. The driver's REQ-7 hits would then hide the monitor's REQ-7 hole, the same false closure as the hook. Set option.per_instance = 1 and option.name = get_full_name() so the report keeps the two apart.

Which state goes into the cross matters as much as which port. The driver's collector took CTRL.burst from the configuration object, the value the test meant to program. The monitor's collector takes it from the design at the take edge. Only the second can reveal a register write that went wrong.

This port shows CTRL.burst on a pin. In a bench with a register model, the monitor copies the mirrored field into the transaction at the take edge instead (U13, U14.3). Reading the mirror later, when the answer arrives, could attach a mode written after the request. Two more conditions apply. A predictor must see every CTRL write. The snapshot must also follow the design's rule for a write and a request at the same edge. This port gives such a request the old value, and the bench must order its snapshot to match. Analysis listeners at one edge run one after another, in an order the bench must not rely on (U13.3).

One requirement, from specification to test

REQ-7 from the specification to a testEach arrow is a document or code reference. The row cites the requirement, the bin and the check cite the row, and the row names the test that hits it.
REQ-7 from the specification to a testEach arrow is a document or code reference. The row cites the requirement, the bin and the check cite the row, and the row names the test that hits it. document Specification REQ-7: withCTRL.burst = 1 awrite burst isaccepted document Test plan row REQ-7: bin, check,test, closure rule coverage mon_cov binREQ-7 write, len > 1,ctrl_burst = 1, ok =1 checker Scoreboard rule ok must equal !(wr&& len > 1 &&!ctrl_burst) test burst_test(repaired) Intended: CTRL = 11,then 3 write bursts decision Closure Hit in a complete,checked run ofburst_test becomes a rowmeasured byjudged byhit byno mismatchhits counted
  1. A testable sentence

    The specification sentence names the condition (CTRL.burst = 1), the stimulus (a write burst) and the result (accepted).

    More context

    A sentence without a condition or a result gives the row nothing to measure or judge. The row keeps the REQ-7 name, so every later artifact can cite it.

    Follow the calls, values and waits
    1. data REQ-7 sentence: Specification → Test plan row
  2. A bin and a check

    The row points to one coverage bin and one checker rule. The bin proves the case happened; the rule proves the result was right.

    More context

    The bin is on the monitor-side collector, because only the monitor sees the answer and the CTRL state the request met.

    Follow the calls, values and waits
    1. data row REQ-7: bin: Test plan row → mon_cov bin REQ-7
    2. data row REQ-7: check: Test plan row → Scoreboard rule
  3. A test that owns it

    The row names burst_test as the test expected to hit REQ-7. In the example, its CTRL write was wrong, so the bin stayed at 0.

    More context

    The example prints "port accepted: ... REQ-7 0, REQ-8 3; holes: REQ-7" while the driver side claims REQ-7 3.

    Follow the calls, values and waits
    1. data REQ-7 hits = 0: mon_cov bin REQ-7 → burst_test (repaired)
    2. Waiting: burst_test (repaired) — REQ-7 not hit: CTRL written as 01
  4. Closed only when both agree

    REQ-7 closes when a run of burst_test hits the bin, answers and checks all ten planned requests, and reports no mismatch. A hit in a failing or incomplete run does not count.

    More context

    This is the repaired test, writing cfg.ctrl_value: the port side reports REQ-7 3, and the scoreboard still checks 10 answers with 0 mismatches.

    Follow the calls, values and waits
    1. data REQ-7 hits = 3: mon_cov bin REQ-7 → burst_test (repaired)
    2. data REQ-7 hits = 3: burst_test (repaired) → Closure
    3. data 10 checked, 0 mismatches: Scoreboard rule → Closure
    4. Released: burst_test (repaired)
Read the diagram as text
  • Specification: REQ-7: with CTRL.burst = 1 a write burst is accepted
  • Test plan row: REQ-7: bin, check, test, closure rule
  • mon_cov bin REQ-7: write, len > 1, ctrl_burst = 1, ok = 1
  • Scoreboard rule: ok must equal !(wr && len > 1 && !ctrl_burst)
  • burst_test (repaired): Intended: CTRL = 11, then 3 write bursts
  • Closure: Hit in a complete, checked run of burst_test
  • Specification → Test plan row: becomes a row
  • Test plan row → mon_cov bin REQ-7: measured by
  • Test plan row → Scoreboard rule: judged by
  • mon_cov bin REQ-7 → burst_test (repaired): hit by
  • Scoreboard rule → Closure: no mismatch
  • burst_test (repaired) → Closure: hits counted

A requirement is closed by a hit in a complete, checked run, both traceable to one plan row.

RowRequirementBin condition (at the answer)Owning test
REQ-3A single read is acceptedrd, len 1, okburst_test
REQ-4A read burst is acceptedrd, len > 1, okburst_test
REQ-5A single write is acceptedwr, len 1, okburst_test
REQ-7With CTRL.burst = 1, a write burst is acceptedwr, len > 1, ctrl_burst 1, okburst_test
REQ-8With CTRL.burst = 0, a write burst is refusedwr, len > 1, ctrl_burst 0, !oknot yet assigned

The test plan for the port. REQ-8 has no owning test, which the next sections turn into a decision.

Each row needs both a bin and a check. A bin without a check proves the case happened but not that the result was right. A check without a bin may pass in every run because the case never occurred.

Explicit and implicit coverage

Every bin in this lesson is explicit coverage: an engineer wrote the goal, the values and the sampling point. Its weakness is that it measures only what someone thought of. Implicit coverage is generated from the design by the simulator: line, branch, toggle and state-machine coverage. It needs no model, but a missed line rarely names the missing feature.

Build the explicit model from the plan first, then use code coverage as a cross-check. Full functional coverage with low code coverage is a reason to inspect the unhit code: the model may miss behaviour, or the code may be unreachable or out of scope. Full code coverage with functional holes means the code ran, but never in the combination a requirement names.

Explicit coverage has two usual homes. A covergroup in a subscriber suits transaction-level goals like these rows. A cover property in the interface checker suits cycle-level sequences, such as a stall held for two edges (U7.4).

A switch that must say it is off

coverage_enable is a VIP convention, not a library field: the uvm-core 2020.3.1 source never defines it. The collector reads it in build_phase. So a test sets it in the config database before the build reaches the collector, or assigns the field directly after build and before the first sample.

A switched-off collector must report that it is off. If it printed its empty bins as holes, a run that skipped coverage for speed would look like a run that missed every requirement. The example's summary() returns disabled, no samples taken instead.

The lookup follows the ordinary config rules. A set in connect_phase comes after the read and changes nothing; a set with a different type, such as int for a bit field, never matches. U15.2 shows how to see both in a trace.

Holes that random stimulus cannot reach

Fix the CTRL write and REQ-7 is hit three times, but REQ-8 becomes the hole. REQ-8 needs a write burst that meets CTRL.burst = 0. This test writes CTRL once, before any traffic, so every request meets the same value. The example's sequence is a fixed list, but suppose it were extended to randomize direction and length: CTRL would still be written once, and no seed would reach REQ-8.

More random runs

// randomized extension, 50 seeds
// CTRL = 11 for every request

REQ-8 stays at 0 in all of them: the seeds vary the items, never the register written once at the start.

A directed step

// at a quiet point (no request in flight):
// write CTRL = 01, send one write burst,
// expect ok = 0, then restore CTRL = 11

One item hits REQ-8, with a checked refusal. The plan row names this step, or a second test, as its owner.

Separate a rare bin from an unreachable one. A rare bin gets more seeds or a weighted constraint. An unreachable bin needs a different configuration or a directed step, and U16.2 shows how to close it.

Try it

The scoreboard is clean because it predicts from the CTRL value the port held. The two coverage lines disagree on REQ-7 and REQ-8: the driver counted its intent, the monitor counted the answers.

Change env.vif.cfg_wdata <= 2'b01 to env.vif.cfg_wdata <= cfg.ctrl_value and rerun. Both collectors now report REQ-7 hit three times, and REQ-8 becomes a hole for both.

RUNVerilator + UVM 2020.3.1 · simulator service
Edit the code, then run it
Expected among the UVM messages: scoreboard: 10 answers checked, 0 mismatches driver intent: REQ-3 2, REQ-4 2, REQ-5 3, REQ-7 3, REQ-8 0; holes: REQ-8 port accepted: REQ-3 2, REQ-4 2, REQ-5 3, REQ-7 0, REQ-8 3; holes: REQ-7 UVM_ERROR : 0

Check yourself

Predict the outputThe test now writes cfg.ctrl_value (2'b11) to CTRL before traffic. The sequence is unchanged: ten requests, three of them write bursts. What line does the $display print?
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// A request port with a control register. CTRL bit 1 (burst) allows write bursts; this reduced
// port ignores bit 0. At the rising edge that takes a request (valid = 1) the port registers
// resp_valid = 1 and ok = 1 (accepted) or ok = 0 (refused: a write burst while CTRL.burst = 0);
// a monitor samples that answer at the next rising edge. Legal lengths are 1 to 4.
interface req_if (input logic clk);
  logic       valid = 0, wr = 0;
  logic [2:0] len = 1;
  logic       cfg_we = 0;
  logic [1:0] cfg_wdata = 0;
  logic       resp_valid = 0, ok = 0;
  logic       ctrl_burst;                  // CTRL.burst as the port holds it
endinterface

module req_port (req_if bus);
  logic [1:0] ctrl = 0;
  assign bus.ctrl_burst = ctrl[1];
  always @(posedge bus.clk) begin
    if (bus.cfg_we) ctrl <= bus.cfg_wdata;
    bus.resp_valid <= bus.valid;
    bus.ok         <= bus.valid && !(bus.wr && bus.len > 1 && !ctrl[1]);
  end
endmodule

class req_item extends uvm_sequence_item;
  `uvm_object_utils(req_item)
  bit wr; bit [2:0] len;
  function new(string name = "req_item"); super.new(name); endfunction
endclass

// One observation: what was asked, the CTRL.burst it met, and the answer.
class req_obs extends uvm_object;
  `uvm_object_utils(req_obs)
  bit wr; bit [2:0] len; bit ctrl_burst; bit ok;
  function new(string name = "req_obs"); super.new(name); endfunction
endclass

// The configuration the test means to program.
class req_cfg extends uvm_object;
  `uvm_object_utils(req_cfg)
  bit [1:0] ctrl_value = 2'b11;
  function new(string name = "req_cfg"); super.new(name); endfunction
endclass

// The coverage model: one bin per test-plan row, counted per sample.
class req_coverage extends uvm_subscriber #(req_obs);
  `uvm_component_utils(req_coverage)
  bit coverage_enable = 1;
  int hits[string];
  string rows[$] = '{"REQ-3", "REQ-4", "REQ-5", "REQ-7", "REQ-8"};
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    void'(uvm_config_db #(bit)::get(this, "", "coverage_enable", coverage_enable));
    foreach (rows[i]) hits[rows[i]] = 0;
  endfunction
  function void write(req_obs t);
    if (!coverage_enable) return;
    if (!t.wr && t.len == 1 && t.ok)                  hits["REQ-3"]++;   // single read accepted
    if (!t.wr && t.len > 1 && t.ok)                   hits["REQ-4"]++;   // read burst accepted
    if (t.wr && t.len == 1 && t.ok)                   hits["REQ-5"]++;   // single write accepted
    if (t.wr && t.len > 1 && t.ctrl_burst && t.ok)    hits["REQ-7"]++;   // write burst accepted
    if (t.wr && t.len > 1 && !t.ctrl_burst && !t.ok)  hits["REQ-8"]++;   // write burst refused
  endfunction
  function string summary();
    string s = "", holes = "";
    if (!coverage_enable) return "disabled, no samples taken";
    foreach (rows[i]) begin
      if (i > 0) s = {s, ", "};
      s = {s, $sformatf("%s %0d", rows[i], hits[rows[i]])};
      if (hits[rows[i]] == 0) holes = {holes, " ", rows[i]};
    end
    if (holes == "") holes = " none";
    return {s, "; holes:", holes};
  endfunction
endclass

class req_driver extends uvm_driver #(req_item);
  `uvm_component_utils(req_driver)
  virtual req_if vif;
  req_cfg cfg;
  uvm_analysis_port #(req_obs) intent_ap;    // what the driver is about to send
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase); intent_ap = new("intent_ap", this); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      req_obs o;
      seq_item_port.get_next_item(req);
      o = req_obs::type_id::create("o");
      o.wr = req.wr; o.len = req.len;
      o.ctrl_burst = cfg.ctrl_value[1];                   // the configured intent
      o.ok = !(req.wr && req.len > 1 && !o.ctrl_burst);   // assumes the port obeys it
      intent_ap.write(o);
      @(negedge vif.clk);
      vif.valid <= 1; vif.wr <= req.wr; vif.len <= req.len;
      @(negedge vif.clk);
      vif.valid <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

// One observation per answer, with CTRL.burst taken at the edge that took the request.
// A request with no answer, or an answer with no request, is an error.
class req_monitor extends uvm_monitor;
  `uvm_component_utils(req_monitor)
  virtual req_if vif;
  req_cfg cfg;
  uvm_analysis_port #(req_obs) ap;
  req_obs taken;                             // taken, answer not yet seen
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase); ap = new("ap", this); endfunction
  function bit ctrl_now();
    return vif.ctrl_burst;
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.resp_valid) begin
        if (taken == null) `uvm_error("MON", "answer with no request")
        else begin
          taken.ok = vif.ok;
          ap.write(taken);
          taken = null;
        end
      end
      if (vif.valid) begin
        if (taken != null) `uvm_error("MON", "request taken before the previous one was answered")
        taken = req_obs::type_id::create("taken");
        taken.wr = vif.wr; taken.len = vif.len; taken.ctrl_burst = ctrl_now();
      end
    end
  endtask
  function void check_phase(uvm_phase phase);
    if (taken != null) `uvm_error("MON", "a request was still waiting for its answer at the end")
  endfunction
endclass

// Predicts each answer from the CTRL.burst the port held.
class req_scoreboard extends uvm_subscriber #(req_obs);
  `uvm_component_utils(req_scoreboard)
  int checked, mismatches;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(req_obs t);
    bit want = !(t.wr && t.len > 1 && !t.ctrl_burst);
    checked++;
    if (t.ok != want) begin
      mismatches++;
      `uvm_error("SB", $sformatf("wr=%0d len=%0d: ok=%0d, expected %0d", t.wr, t.len, t.ok, want))
    end
  endfunction
endclass

// Ten requests: reads and writes, single and burst.
class req_seq extends uvm_sequence #(req_item);
  `uvm_object_utils(req_seq)
  function new(string name = "req_seq"); super.new(name); endfunction
  task body();
    bit       wrs[10]  = '{0, 0, 1, 1, 0, 1, 1, 0, 1, 1};
    bit [2:0] lens[10] = '{1, 4, 1, 2, 1, 4, 1, 2, 3, 1};
    foreach (wrs[i]) begin
      req = req_item::type_id::create("req");
      start_item(req);
      req.wr = wrs[i]; req.len = lens[i];
      finish_item(req);
    end
  endtask
endclass

class req_env extends uvm_env;
  `uvm_component_utils(req_env)
  uvm_sequencer #(req_item) sqr;
  req_driver drv;
  req_monitor mon;
  req_scoreboard sb;
  req_coverage mon_cov, drv_cov;
  req_cfg cfg;
  virtual req_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    void'(uvm_config_db #(virtual req_if)::get(this, "", "vif", vif));
    void'(uvm_config_db #(req_cfg)::get(this, "", "cfg", cfg));
    sqr = uvm_sequencer #(req_item)::type_id::create("sqr", this);
    drv = req_driver::type_id::create("drv", this);
    mon = req_monitor::type_id::create("mon", this);
    sb  = req_scoreboard::type_id::create("sb", this);
    mon_cov = req_coverage::type_id::create("mon_cov", this);
    drv_cov = req_coverage::type_id::create("drv_cov", this);
    drv.vif = vif; drv.cfg = cfg; mon.vif = vif; mon.cfg = cfg;
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_export);
    mon.ap.connect(sb.analysis_export);
    mon.ap.connect(mon_cov.analysis_export);
    drv.intent_ap.connect(drv_cov.analysis_export);
  endfunction
endclass

class burst_test extends uvm_test;
  `uvm_component_utils(burst_test)
  req_env env;
  req_cfg cfg;
  int planned = 10;                                       // requests req_seq sends
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    cfg = req_cfg::type_id::create("cfg");
    cfg.ctrl_value = 2'b11;                               // burst on (bit 0 unused here)
    uvm_config_db #(req_cfg)::set(this, "env", "cfg", cfg);
    env = req_env::type_id::create("env", this);
  endfunction

  task run_phase(uvm_phase phase);
    req_seq seq = req_seq::type_id::create("seq");
    phase.raise_objection(this);
    @(negedge env.vif.clk);                               // program CTRL before any traffic
    env.vif.cfg_we <= 1; env.vif.cfg_wdata <= cfg.ctrl_value;
    @(negedge env.vif.clk);
    env.vif.cfg_we <= 0;
    seq.start(env.sqr);
    @(negedge env.vif.clk);                               // the last answer arrives
    phase.drop_objection(this);
  endtask
  // Completion: every planned request was answered and checked.
  function void check_phase(uvm_phase phase);
    if (env.sb.checked != planned)
      `uvm_error("DONE", $sformatf("%0d of %0d planned requests answered and checked", env.sb.checked, planned))
  endfunction
  function void report_phase(uvm_phase phase);
    $display("port accepted: %s", env.mon_cov.summary());
  endfunction
endclass

module tb;
  bit clk = 0;
  always #5 clk = ~clk;
  req_if bus (clk);
  req_port dut (bus);
  initial begin
    uvm_config_db #(virtual req_if)::set(null, "uvm_test_top.env", "vif", bus);
    run_test("burst_test");
  end
endmodule
Predict the outputWith the original CTRL write (2'b01), the test adds a connect_phase that calls uvm_config_db #(bit)::set(this, "env.drv_cov", "coverage_enable", 0). What line does the $display print?
`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// A request port with a control register. CTRL bit 1 (burst) allows write bursts; this reduced
// port ignores bit 0. At the rising edge that takes a request (valid = 1) the port registers
// resp_valid = 1 and ok = 1 (accepted) or ok = 0 (refused: a write burst while CTRL.burst = 0);
// a monitor samples that answer at the next rising edge. Legal lengths are 1 to 4.
interface req_if (input logic clk);
  logic       valid = 0, wr = 0;
  logic [2:0] len = 1;
  logic       cfg_we = 0;
  logic [1:0] cfg_wdata = 0;
  logic       resp_valid = 0, ok = 0;
  logic       ctrl_burst;                  // CTRL.burst as the port holds it
endinterface

module req_port (req_if bus);
  logic [1:0] ctrl = 0;
  assign bus.ctrl_burst = ctrl[1];
  always @(posedge bus.clk) begin
    if (bus.cfg_we) ctrl <= bus.cfg_wdata;
    bus.resp_valid <= bus.valid;
    bus.ok         <= bus.valid && !(bus.wr && bus.len > 1 && !ctrl[1]);
  end
endmodule

class req_item extends uvm_sequence_item;
  `uvm_object_utils(req_item)
  bit wr; bit [2:0] len;
  function new(string name = "req_item"); super.new(name); endfunction
endclass

// One observation: what was asked, the CTRL.burst it met, and the answer.
class req_obs extends uvm_object;
  `uvm_object_utils(req_obs)
  bit wr; bit [2:0] len; bit ctrl_burst; bit ok;
  function new(string name = "req_obs"); super.new(name); endfunction
endclass

// The configuration the test means to program.
class req_cfg extends uvm_object;
  `uvm_object_utils(req_cfg)
  bit [1:0] ctrl_value = 2'b11;
  function new(string name = "req_cfg"); super.new(name); endfunction
endclass

// The coverage model: one bin per test-plan row, counted per sample.
class req_coverage extends uvm_subscriber #(req_obs);
  `uvm_component_utils(req_coverage)
  bit coverage_enable = 1;
  int hits[string];
  string rows[$] = '{"REQ-3", "REQ-4", "REQ-5", "REQ-7", "REQ-8"};
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    void'(uvm_config_db #(bit)::get(this, "", "coverage_enable", coverage_enable));
    foreach (rows[i]) hits[rows[i]] = 0;
  endfunction
  function void write(req_obs t);
    if (!coverage_enable) return;
    if (!t.wr && t.len == 1 && t.ok)                  hits["REQ-3"]++;   // single read accepted
    if (!t.wr && t.len > 1 && t.ok)                   hits["REQ-4"]++;   // read burst accepted
    if (t.wr && t.len == 1 && t.ok)                   hits["REQ-5"]++;   // single write accepted
    if (t.wr && t.len > 1 && t.ctrl_burst && t.ok)    hits["REQ-7"]++;   // write burst accepted
    if (t.wr && t.len > 1 && !t.ctrl_burst && !t.ok)  hits["REQ-8"]++;   // write burst refused
  endfunction
  function string summary();
    string s = "", holes = "";
    if (!coverage_enable) return "disabled, no samples taken";
    foreach (rows[i]) begin
      if (i > 0) s = {s, ", "};
      s = {s, $sformatf("%s %0d", rows[i], hits[rows[i]])};
      if (hits[rows[i]] == 0) holes = {holes, " ", rows[i]};
    end
    if (holes == "") holes = " none";
    return {s, "; holes:", holes};
  endfunction
endclass

class req_driver extends uvm_driver #(req_item);
  `uvm_component_utils(req_driver)
  virtual req_if vif;
  req_cfg cfg;
  uvm_analysis_port #(req_obs) intent_ap;    // what the driver is about to send
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase); intent_ap = new("intent_ap", this); endfunction
  task run_phase(uvm_phase phase);
    forever begin
      req_obs o;
      seq_item_port.get_next_item(req);
      o = req_obs::type_id::create("o");
      o.wr = req.wr; o.len = req.len;
      o.ctrl_burst = cfg.ctrl_value[1];                   // the configured intent
      o.ok = !(req.wr && req.len > 1 && !o.ctrl_burst);   // assumes the port obeys it
      intent_ap.write(o);
      @(negedge vif.clk);
      vif.valid <= 1; vif.wr <= req.wr; vif.len <= req.len;
      @(negedge vif.clk);
      vif.valid <= 0;
      seq_item_port.item_done();
    end
  endtask
endclass

// One observation per answer, with CTRL.burst taken at the edge that took the request.
// A request with no answer, or an answer with no request, is an error.
class req_monitor extends uvm_monitor;
  `uvm_component_utils(req_monitor)
  virtual req_if vif;
  req_cfg cfg;
  uvm_analysis_port #(req_obs) ap;
  req_obs taken;                             // taken, answer not yet seen
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase); ap = new("ap", this); endfunction
  function bit ctrl_now();
    return vif.ctrl_burst;
  endfunction
  task run_phase(uvm_phase phase);
    forever begin
      @(posedge vif.clk);
      if (vif.resp_valid) begin
        if (taken == null) `uvm_error("MON", "answer with no request")
        else begin
          taken.ok = vif.ok;
          ap.write(taken);
          taken = null;
        end
      end
      if (vif.valid) begin
        if (taken != null) `uvm_error("MON", "request taken before the previous one was answered")
        taken = req_obs::type_id::create("taken");
        taken.wr = vif.wr; taken.len = vif.len; taken.ctrl_burst = ctrl_now();
      end
    end
  endtask
  function void check_phase(uvm_phase phase);
    if (taken != null) `uvm_error("MON", "a request was still waiting for its answer at the end")
  endfunction
endclass

// Predicts each answer from the CTRL.burst the port held.
class req_scoreboard extends uvm_subscriber #(req_obs);
  `uvm_component_utils(req_scoreboard)
  int checked, mismatches;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void write(req_obs t);
    bit want = !(t.wr && t.len > 1 && !t.ctrl_burst);
    checked++;
    if (t.ok != want) begin
      mismatches++;
      `uvm_error("SB", $sformatf("wr=%0d len=%0d: ok=%0d, expected %0d", t.wr, t.len, t.ok, want))
    end
  endfunction
endclass

// Ten requests: reads and writes, single and burst.
class req_seq extends uvm_sequence #(req_item);
  `uvm_object_utils(req_seq)
  function new(string name = "req_seq"); super.new(name); endfunction
  task body();
    bit       wrs[10]  = '{0, 0, 1, 1, 0, 1, 1, 0, 1, 1};
    bit [2:0] lens[10] = '{1, 4, 1, 2, 1, 4, 1, 2, 3, 1};
    foreach (wrs[i]) begin
      req = req_item::type_id::create("req");
      start_item(req);
      req.wr = wrs[i]; req.len = lens[i];
      finish_item(req);
    end
  endtask
endclass

class req_env extends uvm_env;
  `uvm_component_utils(req_env)
  uvm_sequencer #(req_item) sqr;
  req_driver drv;
  req_monitor mon;
  req_scoreboard sb;
  req_coverage mon_cov, drv_cov;
  req_cfg cfg;
  virtual req_if vif;
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    void'(uvm_config_db #(virtual req_if)::get(this, "", "vif", vif));
    void'(uvm_config_db #(req_cfg)::get(this, "", "cfg", cfg));
    sqr = uvm_sequencer #(req_item)::type_id::create("sqr", this);
    drv = req_driver::type_id::create("drv", this);
    mon = req_monitor::type_id::create("mon", this);
    sb  = req_scoreboard::type_id::create("sb", this);
    mon_cov = req_coverage::type_id::create("mon_cov", this);
    drv_cov = req_coverage::type_id::create("drv_cov", this);
    drv.vif = vif; drv.cfg = cfg; mon.vif = vif; mon.cfg = cfg;
  endfunction
  function void connect_phase(uvm_phase phase);
    drv.seq_item_port.connect(sqr.seq_item_export);
    mon.ap.connect(sb.analysis_export);
    mon.ap.connect(mon_cov.analysis_export);
    drv.intent_ap.connect(drv_cov.analysis_export);
  endfunction
endclass

class burst_test extends uvm_test;
  `uvm_component_utils(burst_test)
  req_env env;
  req_cfg cfg;
  int planned = 10;                                       // requests req_seq sends
  function new(string name, uvm_component parent); super.new(name, parent); endfunction
  function void build_phase(uvm_phase phase);
    cfg = req_cfg::type_id::create("cfg");
    cfg.ctrl_value = 2'b11;                               // burst on (bit 0 unused here)
    uvm_config_db #(req_cfg)::set(this, "env", "cfg", cfg);
    env = req_env::type_id::create("env", this);
  endfunction
  function void connect_phase(uvm_phase phase);
    uvm_config_db #(bit)::set(this, "env.drv_cov", "coverage_enable", 0);   // switch off driver-side coverage
  endfunction
  task run_phase(uvm_phase phase);
    req_seq seq = req_seq::type_id::create("seq");
    phase.raise_objection(this);
    @(negedge env.vif.clk);                               // program CTRL before any traffic
    env.vif.cfg_we <= 1; env.vif.cfg_wdata <= 2'b01;
    @(negedge env.vif.clk);
    env.vif.cfg_we <= 0;
    seq.start(env.sqr);
    @(negedge env.vif.clk);                               // the last answer arrives
    phase.drop_objection(this);
  endtask
  // Completion: every planned request was answered and checked.
  function void check_phase(uvm_phase phase);
    if (env.sb.checked != planned)
      `uvm_error("DONE", $sformatf("%0d of %0d planned requests answered and checked", env.sb.checked, planned))
  endfunction
  function void report_phase(uvm_phase phase);
    $display("driver intent: %s", env.drv_cov.summary());
  endfunction
endclass

module tb;
  bit clk = 0;
  always #5 clk = ~clk;
  req_if bus (clk);
  req_port dut (bus);
  initial begin
    uvm_config_db #(virtual req_if)::set(null, "uvm_test_top.env", "vif", bus);
    run_test("burst_test");
  end
endmodule
Spot the bugEach option is the body of the monitor's ctrl_now(), called at the edge where the port takes a request. The test writes CTRL = 01, so the monitor's coverage should show REQ-7 0 and REQ-8 3. Which option makes it misreport?
ConceptREQ-7 says a write burst is accepted while CTRL.burst = 1. Which sampling point lets its bin prove that, on this port?
ConceptAfter the CTRL fix, REQ-8 is the only hole. Suppose the sequence is extended to randomize each item's direction and length, while the test still writes CTRL = 11 once before traffic. What closes REQ-8?

Remember

  • Sample a requirement at the event that establishes it, here the monitor's observed answer; driver-side coverage measures what the stimulus asked for.
  • Cross the transaction with the register state the design held when it took the request, not with the configuration the test intended.
  • Trace each requirement to one plan row with a bin, a check and an owning test; a row closes when a complete, checked run hits its bin.
  • coverage_enable is a convention read in build; a collector disabled before sampling must say so rather than report holes.
  • A bin that needs a register state the test never creates is unreachable by more seeds; close it with a directed step or another test (U16.2).