Each row exists because of a risk in the plan, and each is sampled from transactions or derived state, as in Transaction and Temporal Coverage.
Row | What is counted | Risk it answers | Goal |
|---|---|---|---|
CV1 | Reads outstanding when a read is accepted: 0, 1-3, 4-7, 8+ | Empty and full table; 8+ means this read took the last free entry (any index) | 5, 10, 10, 3 |
CV1b | Read allocated table entry 7 (white-box: the DUT's free_idx) | The highest entry index is used; depth alone does not show which entry was taken | 3 |
CV2 | Read accepted with 2 or more reads outstanding on its own ID | Within-ID ordering only matters when a queue has depth | 10 |
CV3 | Response that overtook an older read on another ID | Proves reordering happened, so ordering checks had work | 10 |
CV4 | Partial writes per port and byte lane (8 bins) | Strobe handling per port | 5 each |
CV5 | Read of a word written 3 or fewer cycles earlier | Ordering point between writes and reads | 10 |
CV6 | Response stalled 3 or more cycles | Payload hold under back-pressure | 5 |
CV7 | Contention won by port 0, by port 1 | Arbitration both ways | 5 each |
CV8 | Reset with 4 or more reads outstanding | Reset that actually drops work | 1 |
CV9 | Read on an ID that had reads outstanding at the last reset | State that survives reset | 1 |
CV3 and CV2 are activity gates as much as coverage. A clean scoreboard on traffic that never reordered, or never stacked two reads on one ID, proves nothing about R1.
On a commercial simulator the read and write rows are covergroups sampled from the monitor. Verilator 5.048 ignores covergroups, so the bench uses a procedural mirror (cap_cov) with the same bins and checks its rows against their goals, the approach of the Coverage Modelling topic.
// not-run: covergroups are parsed but ignored by Verilator 5.048 (COVERIGN warnings, get_coverage() returns 0) and rejected by Icarus 13; needs a commercial simulator
// The capstone coverage model as covergroups. The bench runs a procedural mirror of it.
class cap_cg;
covergroup cg_read with function sample(int depth, int depth_id, bit raw_close, bit after_reset);
option.per_instance = 1;
cp_depth: coverpoint depth { // CV1: table the read found
bins empty = {0};
bins low = {[1:3]};
bins high = {[4:7]};
bins full = {[8:$]}; // this read took the last free entry
}
cp_same_id: coverpoint depth_id { // CV2
bins deep = {[2:$]};
}
cp_raw: coverpoint raw_close { bins hit = {1}; } // CV5
cp_reuse: coverpoint after_reset { bins hit = {1}; } // CV9
x_depth_same: cross cp_depth, cp_same_id; // ordering under load
endgroup
covergroup cg_write with function sample(bit port, bit [3:0] strb);
cp_port: coverpoint port;
cp_lane: coverpoint strb iff (strb != 4'hF) { // CV4: partial writes only
wildcard bins lane0 = {4'b???1}; // overlapping bins: a write
wildcard bins lane1 = {4'b??1?}; // counts once in every lane
wildcard bins lane2 = {4'b?1??}; // it touches
wildcard bins lane3 = {4'b1???};
}
x_port_lane: cross cp_port, cp_lane;
endgroup
// White-box: which table entry a read was allocated (DUT's free_idx at a read
// acceptance). Depth says how busy the table was, not which entry was used.
covergroup cg_alloc with function sample(bit [2:0] slot);
cp_slot: coverpoint slot { // CV1b
bins last = {7};
bins other = {[0:6]};
}
endgroup
function new();
cg_read = new();
cg_write = new();
cg_alloc = new();
endfunction
endclassThe lane bins overlap on purpose. A write with strobe 0101 counts once in lane 0 and once in lane 2, which is what the mirror's loop over set strobe bits does. Bins that partitioned the strobe values instead would put that write in an "other" bin and leave lane 2 looking untested.
Rows that describe a shape in time read better as cover property, labelled with their row IDs. CV6 and CV7 could be written either way. The last two read naturally as sequences, although each also has a counter form (the mirror counts overtakes as CV3). cv11_overtake is only an approximation: the other-ID response may belong to a read older than the ID 1 read, and the final ID 1 response may serve an older ID 1 read. An exact overtake needs per-read tickets, which the CV3 mirror keeps.
// snippet: temporal plan rows as cover properties, labelled with their row IDs
cv6_stall3: cover property (@(posedge clk) disable iff (!rst_n)
(rs.valid && !rs.ready) [*3] ##1 (rs.valid && rs.ready)); // CV6: held 3 cycles, then taken
cv7_p1_wins: cover property (@(posedge clk) disable iff (!rst_n)
rq0.valid && rq1.valid && rq1.ready); // CV7: contention, port 1 wins
cv10_b2b_same_id: cover property (@(posedge clk) disable iff (!rst_n)
rsp_hs ##1 (rsp_hs && $stable(rsp_pid))); // two responses back to back on one ID
// Approximation: the other-ID response may belong to an older read, and the last
// response may serve an older ID 1 read. An exact overtake needs tickets (CV3 mirror).
cv11_overtake: cover property (@(posedge clk) disable iff (!rst_n)
(rd_acc && acc_pid == 3'd1) ##[1:8] (rsp_hs && rsp_pid != 3'd1) ##[1:8] (rsp_hs && rsp_pid == 3'd1));Verilator evaluates cover property only when built with --coverage-user, and the course runner does not pass it; Assertion-Backed Coverage shows the sentinel-cover check that tells "not reached" from "not evaluated". The capstone keeps these rows in the mirror so the runnable bench enforces them.
They prove the conditions under which the ordering checks mean something actually occurred: two reads queued on one ID, and a response overtaking an older one. Without them, a clean scoreboard could come from traffic that never tested ordering.