Functional coverage measures scenarios from the specification, while code coverage measures structure from the RTL. This part builds covergroups, bins, crosses, and options, then closes the loop from holes to sign-off.
Topic | Lessons | What it covers | Time |
|---|---|---|---|
4 | Covergroups bundle coverpoints and bins; lessons cover structure, sampling, expressions and a first model. | 30 min | |
4 | Bins turn sampled values into named scenarios, violations and exclusions. | 30 min | |
5 | Cross coverage from product bins to spec-driven patterns, with pruning and explosion control. | 40 min | |
4 | How per-instance options, weights, goals, sampling methods, and covergroup arguments shape coverage. | 30 min | |
5 | Close the coverage loop: triage holes, steer stimulus, waive the unreachable, and sign off with evidence. | 40 min |
The open simulators do not count covergroups. Verilator 5.048 parses them with a COVERIGN warning and ignores the bins, so get_inst_coverage() reads 0.0 however much traffic ran; Icarus 13 has no covergroups at all. Code you run in the Code Lab will compile and report nothing.
So the lessons teach the real covergroup syntax, the code to write at work and in interviews, and count with a small bins model written in plain classes. The model applies the same rules: named bins, ranges, [] and [N] arrays, wildcard and transition bins, default, ignore_bins, illegal_bins, crosses with binsof selections, named cross bins and exclusions, at_least, weights and goals. It prints a report shaped like a simulator's.
Covergroup syntax | Bins model |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| the |
|
|
Here is the model. Benches in this part start with // uses: covmodel: paste them into the Code Lab below this listing, in one file. The course's own checks do the same, so every number on these pages came from a run.
// The course's bins model: what a covergroup counts, written as plain classes.
// The open simulators do not count covergroups (Verilator 5.048 parses them and
// ignores the bins; Icarus 13 has none), so the coverage lessons count with these
// classes and print real numbers.
// Each method is named after the covergroup syntax it stands for.
typedef enum {CM_BIN, CM_IGNORE, CM_ILLEGAL, CM_DEFAULT} cm_kind_e;
class cm_bin;
string name;
cm_kind_e kind;
int vals[$]; // the values that land here (ranges expanded)
int seq[$]; // transition alternatives, oldest value first, one after another
int alt_len[$]; // the length of each alternative in seq
int goto_v, goto_n, goto_then; // (goto_v [->goto_n] => goto_then)
bit [31:0] wval, wmask; // wildcard: (v & wmask) == wval
bit wild;
int hits;
function new(string n, cm_kind_e k);
name = n; kind = k;
endfunction
function bit is_trans();
return alt_len.size() != 0 || goto_n != 0;
endfunction
function bit has(int v);
if (wild) return (v & wmask) == wval;
foreach (vals[i]) if (vals[i] == v) return 1;
return 0;
endfunction
endclass
class cm_point;
string name;
cm_bin bin_list[$];
int at_least = 1, weight = 1, goal = 100;
bit enabled = 1; // start() and stop()
int hist[$]; // recent samples, for transition bins
int last_bin = -1; // value bin hit by the last sample, for crosses
int illegal_hits;
function new(string n);
name = n;
endfunction
// bins n = {[lo:hi]}; ignore_bins and illegal_bins take a kind
function void add(string n, int lo, int hi, cm_kind_e k = CM_BIN);
cm_bin b = new(n, k);
for (int v = lo; v <= hi; v++) b.vals.push_back(v);
bin_list.push_back(b);
endfunction
// bins n = {v0, v1, ...};
function void add_values(string n, int v[$], cm_kind_e k = CM_BIN);
cm_bin b = new(n, k);
b.vals = v;
bin_list.push_back(b);
endfunction
// bins n[] = {[lo:hi]}; one bin per value
function void add_each(string n, int lo, int hi);
for (int v = lo; v <= hi; v++) add($sformatf("%s[%0d]", n, v), v, v);
endfunction
// bins n[count] = {[lo:hi]}; the values dealt into count bins, the last one taking any remainder
function void add_split(string n, int lo, int hi, int count);
int per = (hi - lo + 1) / count;
for (int i = 0; i < count; i++)
add($sformatf("%s[%0d]", n, i), lo + i * per, (i == count - 1) ? hi : lo + (i + 1) * per - 1);
endfunction
// wildcard bins n = {pattern}; mask bits set where the pattern is not ?
function void add_wild(string n, bit [31:0] val, bit [31:0] mask, cm_kind_e k = CM_BIN);
cm_bin b = new(n, k);
b.wild = 1; b.wval = val & mask; b.wmask = mask;
bin_list.push_back(b);
endfunction
// bins n = (v0 => v1 => ...);
function void add_trans(string n, int s[$], cm_kind_e k = CM_BIN);
cm_bin b = new(n, k);
b.seq = s; b.alt_len.push_back(s.size());
bin_list.push_back(b);
endfunction
// bins n = (...), (...); another transition that also hits the last bin added
function void also(int s[$]);
cm_bin b = bin_list[bin_list.size() - 1];
foreach (s[i]) b.seq.push_back(s[i]);
b.alt_len.push_back(s.size());
endfunction
// bins n = (v [->count] => then_v); count v's, gaps allowed, the last one followed by then_v
function void add_goto(string n, int v, int count, int then_v, cm_kind_e k = CM_BIN);
cm_bin b = new(n, k);
b.goto_v = v; b.goto_n = count; b.goto_then = then_v;
bin_list.push_back(b);
endfunction
// bins n = default; counts samples no other bin took, and never counts toward coverage
function void add_default(string n);
cm_bin b = new(n, CM_DEFAULT);
bin_list.push_back(b);
endfunction
function bit seq_hit(cm_bin b);
int start = 0;
if (b.goto_n != 0) begin
int seen = 0;
if (hist.size() < 2 || hist[hist.size() - 1] != b.goto_then || hist[hist.size() - 2] != b.goto_v) return 0;
foreach (hist[i]) if (i < hist.size() - 1 && hist[i] == b.goto_v) seen++;
return seen >= b.goto_n;
end
foreach (b.alt_len[a]) begin
bit ok = hist.size() >= b.alt_len[a];
for (int i = 0; ok && i < b.alt_len[a]; i++)
if (hist[hist.size() - b.alt_len[a] + i] != b.seq[start + i]) ok = 0;
if (ok) return 1;
start += b.alt_len[a];
end
return 0;
endfunction
function void sample(int v);
bit taken = 0;
last_bin = -1;
if (!enabled) return;
hist.push_back(v);
if (hist.size() > 16) void'(hist.pop_front());
foreach (bin_list[i]) if (bin_list[i].kind == CM_IGNORE && (bin_list[i].is_trans() ? seq_hit(bin_list[i]) : bin_list[i].has(v))) begin
bin_list[i].hits++;
return; // ignore_bins win over everything
end
foreach (bin_list[i]) if (bin_list[i].kind == CM_ILLEGAL && (bin_list[i].is_trans() ? seq_hit(bin_list[i]) : bin_list[i].has(v))) begin
bin_list[i].hits++; illegal_hits++;
$display(" [cov] %s: illegal bin %s hit by %0d", name, bin_list[i].name, v);
return;
end
foreach (bin_list[i]) if (bin_list[i].kind == CM_BIN) begin
if (bin_list[i].is_trans() ? seq_hit(bin_list[i]) : bin_list[i].has(v)) begin
bin_list[i].hits++; taken = 1;
if (last_bin < 0 && !bin_list[i].is_trans()) last_bin = i;
end
end
if (!taken) foreach (bin_list[i]) if (bin_list[i].kind == CM_DEFAULT) bin_list[i].hits++;
endfunction
function int n_bins(); int n = 0; foreach (bin_list[i]) if (bin_list[i].kind == CM_BIN) n++; return n; endfunction
function int n_covered(); int n = 0; foreach (bin_list[i]) if (bin_list[i].kind == CM_BIN && bin_list[i].hits >= at_least) n++; return n; endfunction
function real get_coverage();
return n_bins() == 0 ? 100.0 : 100.0 * n_covered() / n_bins();
endfunction
function void report();
$display(" coverpoint %s: %0.2f%% (%0d of %0d bins)%s", name, get_coverage(), n_covered(), n_bins(),
get_coverage() >= goal ? "" : $sformatf(" below goal %0d", goal));
foreach (bin_list[i]) begin
string tag = bin_list[i].kind == CM_IGNORE ? " (ignore)" : bin_list[i].kind == CM_ILLEGAL ? " (illegal)" :
bin_list[i].kind == CM_DEFAULT ? " (default)" : bin_list[i].hits < at_least ? " <- hole" : "";
$display(" %-14s %4d%s", bin_list[i].name, bin_list[i].hits, tag);
end
endfunction
endclass
typedef bit cm_cells[$]; // a selection of cross cells, one bit per (a bin, b bin) pair
class cm_cross;
string name;
cm_point a, b;
int hits[$]; // one counter per (a bin, b bin) pair
bit skip[$]; // ignored or illegal pairs: not counted toward coverage
bit bad[$]; // illegal pairs: a hit is reported
bit taken[$]; // pairs a named bin absorbed: no longer auto bins
string nb_name[$]; // named cross bins
cm_cells nb_cells[$];
int nb_hits[$];
int at_least = 1, weight = 1;
int illegal_hits;
function new(string n, cm_point pa, cm_point pb);
name = n; a = pa; b = pb;
repeat (a.bin_list.size() * b.bin_list.size()) begin hits.push_back(0); skip.push_back(0); bad.push_back(0); taken.push_back(0); end
endfunction
function int idx(int i, int j); return i * b.bin_list.size() + j; endfunction
function bit real_pair(int i, int j);
return a.bin_list[i].kind == CM_BIN && !a.bin_list[i].is_trans() && b.bin_list[j].kind == CM_BIN && !b.bin_list[j].is_trans();
endfunction
// Selections. binsof(a.an) && binsof(b.bn): "" means any bin of that point
function cm_cells sel(string an, string bn);
cm_cells c;
foreach (a.bin_list[i]) foreach (b.bin_list[j])
c.push_back((an == "" || a.bin_list[i].name == an) && (bn == "" || b.bin_list[j].name == bn));
return c;
endfunction
// binsof(a) intersect {values}: every a bin holding one of the values; binsof(b) intersect for b_side = 1
function cm_cells sel_values(int v[$], bit b_side = 0);
cm_cells c;
foreach (a.bin_list[i]) foreach (b.bin_list[j]) begin
bit hit = 0;
foreach (v[k]) if (b_side ? b.bin_list[j].has(v[k]) : a.bin_list[i].has(v[k])) hit = 1;
c.push_back(hit);
end
return c;
endfunction
function cm_cells sel_and(cm_cells x, cm_cells y); cm_cells c; foreach (x[k]) c.push_back(x[k] && y[k]); return c; endfunction
function cm_cells sel_or(cm_cells x, cm_cells y); cm_cells c; foreach (x[k]) c.push_back(x[k] || y[k]); return c; endfunction
function cm_cells sel_not(cm_cells x); cm_cells c; foreach (x[k]) c.push_back(!x[k]); return c; endfunction
function int count(cm_cells x); int n = 0; foreach (x[k]) n += x[k]; return n; endfunction
// bins n = <selection>; one bin, hit by a sample in any selected cell; it absorbs those auto cells
function void add_bin(string n, cm_cells c);
nb_name.push_back(n); nb_cells.push_back(c); nb_hits.push_back(0);
foreach (c[k]) if (c[k]) taken[k] = 1;
endfunction
// ignore_bins n = <selection>; illegal_bins n = <selection>;
function void ignore(cm_cells c); foreach (c[k]) if (c[k]) skip[k] = 1; endfunction
function void illegal(cm_cells c); foreach (c[k]) if (c[k]) begin skip[k] = 1; bad[k] = 1; end endfunction
// The short forms: ignore_bins ... = binsof(a.an) && binsof(b.bn); and binsof(a) intersect {values}
function void exclude(string an, string bn); ignore(sel(an, bn)); endfunction
function void exclude_a_values(int v[$]); ignore(sel_values(v)); endfunction
function void sample();
int k;
if (a.last_bin < 0 || b.last_bin < 0) return;
k = idx(a.last_bin, b.last_bin);
if (bad[k]) begin
illegal_hits++;
$display(" [cov] %s: illegal cross bin <%s, %s> hit", name, a.bin_list[a.last_bin].name, b.bin_list[b.last_bin].name);
return;
end
if (skip[k]) return;
hits[k]++;
foreach (nb_cells[n]) if (nb_cells[n][k]) nb_hits[n]++;
endfunction
// The goal: every named bin with a cell left in it, plus the auto cells no named bin absorbed.
function bit named_live(int n);
foreach (nb_cells[n][k]) if (nb_cells[n][k] && !skip[k]) return 1;
return 0;
endfunction
function bit auto_cell(int i, int j);
return real_pair(i, j) && !skip[idx(i, j)] && !taken[idx(i, j)];
endfunction
function int n_bins();
int n = 0;
foreach (nb_name[x]) n += named_live(x);
foreach (a.bin_list[i]) foreach (b.bin_list[j]) n += auto_cell(i, j);
return n;
endfunction
function int n_covered();
int n = 0;
foreach (nb_name[x]) n += named_live(x) && nb_hits[x] >= at_least;
foreach (a.bin_list[i]) foreach (b.bin_list[j]) n += auto_cell(i, j) && hits[idx(i, j)] >= at_least;
return n;
endfunction
function real get_coverage();
return n_bins() == 0 ? 100.0 : 100.0 * n_covered() / n_bins();
endfunction
function void report();
$display(" cross %s: %0.2f%% (%0d of %0d bins)", name, get_coverage(), n_covered(), n_bins());
foreach (nb_name[x]) if (named_live(x))
$display(" %-14s %4d%s", nb_name[x], nb_hits[x], nb_hits[x] < at_least ? " <- hole" : "");
foreach (a.bin_list[i]) foreach (b.bin_list[j])
if (auto_cell(i, j) && hits[idx(i, j)] < at_least)
$display(" hole: <%s, %s>", a.bin_list[i].name, b.bin_list[j].name);
endfunction
endclass
class cm_group;
string name;
cm_point points[$];
cm_cross crosses[$];
int goal = 100;
function new(string n);
name = n;
endfunction
// Weighted average over every coverpoint and cross, as the standard defines it.
function real get_coverage();
real sum = 0; int w = 0;
foreach (points[i]) begin sum += points[i].get_coverage() * points[i].weight; w += points[i].weight; end
foreach (crosses[i]) begin sum += crosses[i].get_coverage() * crosses[i].weight; w += crosses[i].weight; end
return w == 0 ? 0.0 : sum / w;
endfunction
function void report();
$display("covergroup %s: %0.2f%% (goal %0d)", name, get_coverage(), goal);
foreach (points[i]) points[i].report();
foreach (crosses[i]) crosses[i].report();
endfunction
endclassA first covergroup and the same coverage counted with the model:
// not-run: Verilator 5.048 ignores covergroups (COVERIGN) and Icarus 13 has none; the bench below counts it
covergroup alu_cov with function sample(bit [1:0] op, int len);
cp_op: coverpoint op {
bins add = {0}; bins sub = {1}; bins mul = {2};
illegal_bins rsvd = {3};
}
cp_len: coverpoint len {
ignore_bins zero = {0};
bins len[3] = {[1:12]};
}
op_x_len: cross cp_op, cp_len {
ignore_bins mul_long = binsof(cp_op.mul) && binsof(cp_len) intersect {[9:12]};
}
endgroup// uses: covmodel
// The covergroup above, counted with the bins model.
module tb_cm_demo;
cm_group g = new("alu_cov");
cm_point cp_op = new("cp_op"), cp_len = new("cp_len");
cm_cross op_x_len;
initial begin
// coverpoint op { bins add = {0}; bins sub = {1}; bins mul = {2}; illegal_bins rsvd = {3}; }
cp_op.add("add", 0, 0); cp_op.add("sub", 1, 1); cp_op.add("mul", 2, 2);
cp_op.add("rsvd", 3, 3, CM_ILLEGAL);
// coverpoint len { ignore_bins zero = {0}; bins len[3] = {[1:12]}; }
cp_len.add("zero", 0, 0, CM_IGNORE); cp_len.add_split("len", 1, 12, 3);
// cross cp_op, cp_len { ignore_bins mul_long = binsof(cp_op.mul) && binsof(cp_len) intersect {[9:12]}; }
op_x_len = new("op_x_len", cp_op, cp_len);
op_x_len.exclude("mul", "len[2]");
g.points.push_back(cp_op); g.points.push_back(cp_len); g.crosses.push_back(op_x_len);
// sample(): five transactions
begin
int ops[5] = '{0, 1, 2, 0, 3};
int lens[5] = '{1, 5, 9, 0, 12};
for (int i = 0; i < 5; i++) begin cp_op.sample(ops[i]); cp_len.sample(lens[i]); op_x_len.sample(); end
end
g.report();
if (cp_op.get_coverage() != 100.0 || op_x_len.n_covered() != 2 || op_x_len.n_bins() != 8) $fatal(1, "FAIL: model numbers");
$display("PASS: the model counted the covergroup's bins, its illegal and ignored values, and the cross");
$finish;
end
endmoduleRead the report as a simulator's: the two coverpoints are full, the illegal value was flagged as it happened, the ignored length never counted, and the cross is at 2 of 8 because only two operation-length pairs ever ran. The holes are listed by name. That is the shape every coverage report in this part takes.
Code coverage records which lines, toggles, and branches executed. Only covergroups with planned bins record which specified scenarios occurred, so a required scenario with no bin stays invisible.