Functional Coverage Integration
The scoreboard answers "was the DUT's behavior correct." It says nothing about whether the test actually exercised enough of the FIFO's behavior to be a meaningful check at all — a test that only ever writes and never reads would pass every scoreboard comparison it ever attempts, without saying anything useful. This page wires a covergroup into the environment to track that separately.
Sampling where the transactions already flow
class scoreboard;
mailbox #(fifo_txn) mon2sb;
fifo_ref_model model;
int pass_count = 0;
int fail_count = 0;
fifo_txn current_txn;
covergroup fifo_cg;
coverpoint current_txn.op;
endgroup
function new(mailbox #(fifo_txn) mon2sb, fifo_ref_model model);
this.mon2sb = mon2sb;
this.model = model;
fifo_cg = new();
endfunction
task run();
forever begin
mon2sb.get(current_txn);
fifo_cg.sample();
if (current_txn.op == fifo_txn::WRITE) begin
model.write(current_txn);
end else begin
bit [7:0] expected = model.predict_read();
if (current_txn.rd_data !== expected) fail_count++;
else pass_count++;
end
end
endtask
endclass
The scoreboard is where this sampling happens for a specific reason: it's the one component that sees every transaction that made it all the way through the pipeline — generated, driven onto real pins, and independently observed by the monitor — not just what the generator intended to send. Sampling coverage in the generator instead would answer a different, less useful question: not "did the test exercise this," but only "did the test attempt to."
Reporting it
$display("PASS=%0d FAIL=%0d coverage=%0.1f%%",
env.sb.pass_count, env.sb.fail_count, env.sb.fifo_cg.get_coverage());
get_coverage() reports the covergroup's percentage directly — the same call SystemVerilog's own capstone closed with. It's worth naming precisely what get_coverage() reports: cumulative type coverage — aggregated across every instance of fifo_cg's covergroup type that exists anywhere in the simulation, not just env.sb's own one instance. SystemVerilog has a separate method, get_inst_coverage(), for coverage scoped to one specific instance rather than the type as a whole. This testbench only ever constructs one scoreboard (and therefore one fifo_cg), so type coverage and instance coverage happen to report the identical number here — but a testbench instantiating this environment more than once (say, one per DUT channel in a multi-channel design) would need get_inst_coverage() to see each instance's own progress separately, since get_coverage() would otherwise blend all of them into one aggregate number.
A mechanical alternative: sampling on an event instead of a call
fifo_cg's declaration above — covergroup fifo_cg; ... endgroup — has no sampling event attached, which is exactly why run() has to call fifo_cg.sample() explicitly, right after mon2sb.get(current_txn), every time a new transaction arrives. That explicit call is one mechanical choice among two SystemVerilog supports: a covergroup can instead be declared with a sampling event — covergroup fifo_cg @(posedge clk); for a clock edge, or covergroup fifo_cg @(txn_sampled_ev); for a custom event triggered elsewhere with ->txn_sampled_ev; — in which case the simulator samples the covergroup automatically every time that event fires, and no .sample() call appears in the code at all. Both approaches sample the exact same coverpoints; the difference is purely mechanical — whether sampling is driven by an explicit method call at the precise line the code chooses, or implicitly by an event the covergroup itself is watching. This page uses the explicit form because it keeps the sampling point visually next to the code that receives the transaction, but the event-triggered form is just as valid and more common when a covergroup needs to sample on every clock edge regardless of what other code is doing at that moment.
Where this topic's job stops
This page covers mechanical integration only — where a covergroup lives, when it samples, how its result gets reported. It deliberately does not cover coverage strategy: what bins are actually worth tracking, how cross coverage should be structured, what counts as "done" for a real verification plan. That's the job of Verification, still a stub on this site — this topic's covergroup here is intentionally as minimal as fifo_cg's single coverpoint, since the point being made is where coverage plugs into a layered environment, not what a real coverage plan should contain.
What's next
Every component this topic set out to build now exists, wired together, running as one test. Section F closes the topic: an honest look at how this hand-rolled architecture handles configuration and reuse today, a concrete list of where it actually breaks down at scale, and a capstone assembling everything into one complete testbench.