"A beat waited exactly two cycles, then was accepted." "Three beats were accepted back to back." These rows describe a shape in time. A covergroup can bin a number computed by a monitor, but writing a monitor state machine for every shape is slow and easy to get wrong. A cover property states the shape directly, in the same language as the assertions that check the protocol. Directives introduces cover; this lesson makes covers carry plan rows.
A cover property starts an evaluation attempt on every clock, like an assertion. It counts at most one success per attempt and reports vacuous successes in a separate count. By default the action block also runs on a vacuous success (20.12), so avoid implication inside a cover, or turn vacuous passes off with $assertcontrol (NonvacuousOn) or $assertvacuousoff. A plain sequence, like every cover in this lesson, has no vacuous success. A cover sequence instead counts every match of every attempt (IEEE 1800-2017, 16.14.3). Two practical rules follow.
Write each scenario so one attempt can match in only one way, or the count depends on the form of cover you used. stall [*2] has one match per attempt; stall [*1:3] has up to three.
Anchor the start. (valid && ready) [*3] started on every cycle counts a run of four transfers twice. That is fine if the plan row means "windows of three", wrong if it means "runs of three".
Add disable iff (!rst_n) so reset does not produce or abort hits, and keep covers on sampled signals only, the same as assertions.
The plan has rows with IDs; the coverage report has item names. The link is the name. Label each cover with its row ID, and have the action block count hits per row in an associative array keyed by the ID. Vendor plan tools (for example VCS hierarchical verification plans, Questa test-plan tracking and Cadence vPlan) import a spreadsheet and map each row to coverage items by name, so a consistent label is what makes the report readable at a review.
Row | Scenario | Cover |
|---|---|---|
P3.1 | Accept with no wait after idle |
|
P3.2 | Exactly two stall cycles, then accept |
|
P3.3 | Three accepts in consecutive cycles |
|
P3.4 | A stall longer than three cycles |
|
The DUT is a sink that stalls for data[1:0] cycles after each accepted beat, so the stimulus chooses the stall lengths. The bench has two counters per row: one filled by the cover action blocks and one by a procedural mirror built from a four-cycle history of sampled values. It checks the mirror against hand-counted expected hits and, when the tool evaluated the covers, checks the covers against the mirror.
A tool fact decides that last clause. Verilator 5.048 evaluates cover property only when the model is built with --coverage-user (or --coverage, which includes it). Without that flag the covers compile but never run, and their action blocks never execute; this was confirmed by running the bench both ways. The course runner does not pass the flag, so on this site only the procedural mirror is checked: a wrong cover still passes here. To check the covers too, build it yourself with verilator --binary --timing --assert --coverage-user tb_plan_covers.sv and run obj_dir/Vtb_plan_covers. A sentinel cover, c_alive, tells the bench which case it is in: if even a cover on rst_n never hit, covers were not evaluated in this build.
// Sink that stalls for data[1:0] cycles after each accepted beat.
module busy_sink (
input logic clk,
input logic rst_n,
input logic valid,
input logic [7:0] data,
output logic ready
);
timeunit 1ns; timeprecision 1ps;
logic [1:0] busy;
assign ready = (busy == 2'd0);
always_ff @(posedge clk or negedge rst_n)
if (!rst_n) busy <= 2'd0;
else if (valid && ready) busy <= data[1:0];
else if (busy != 2'd0) busy <= busy - 2'd1;
endmodule
The hand count follows from the stream. P3.1 hits at beat 1 and at beat 7, the two beats that follow idle cycles. P3.2 hits once, at beat 4, which waits two cycles behind beat 3; beat 6 waits three cycles and does not match. P3.3 hits at beats 1 to 3 and twice in the run of four, beats 7 to 10. P3.4 needs four stall cycles and the DUT never stalls more than three, so it stays at 0.
Built without the flag, as the course runner builds it, the run prints the note and checks the mirror alone. Built with --coverage-user, each cover count equals the mirror. Two mutants show the difference. A DUT that ignores data and never stalls drops P3.2 to 0 and raises P3.3 to 6 in the mirror, so it fails in both builds. Changing the P3.3 cover to xfer [*2] makes it report 6 against the mirror's 3, but only the --coverage-user build sees that; the course runner still prints PASS.
P3.4 is a hole by design. The stall field is two bits, so no stimulus can make the sink wait four cycles. Three answers are possible, and the plan must record which: remove the row because the spec caps stalls at three; keep it and reconfigure the DUT so long stalls happen; or ask a formal tool to prove the cover unreachable and waive it with that proof. A cover that is reachable in the spec but unreachable in the bench is the dangerous case, because it usually means the stimulus is too narrow.
A cover's action block can also feed a covergroup. Calling cg.sample(stall_len) from a cover that ends at the accept bins the stall lengths that actually occurred, which joins a temporal shape to a value distribution.
cover property (@(posedge clk) xfer [*3]) count it, and why?Twice. An attempt starts on every clock; the attempts starting at the first and second transfer of the run each see three transfers. Each attempt counts at most once, so a run of four gives two hits.
Whether covers were evaluated: the build options (Verilator needs --coverage-user), assertion control calls such as $assertoff, and the disable iff condition. A sentinel cover on an always-true condition answers this in one line of the report.