Support iff in covergroup bins (#8406)

This commit is contained in:
Marco Bartoli
2026-09-18 20:29:22 -04:00
committed by GitHub
parent a93317b6cc
commit 1cefe95c85
16 changed files with 218 additions and 19 deletions
+2 -1
View File
@@ -1067,7 +1067,7 @@ class AstCoverBin final : public AstNode {
public:
AstCoverBin(FileLine* fl, const string& name, AstNode* rangesp, bool isIgnore, bool isIllegal,
bool isWildcard = false)
bool isWildcard = false, AstNodeExpr* iffp = nullptr)
: ASTGEN_SUPER_CoverBin(fl)
, m_name{name}
, m_binsType{isIllegal ? VCoverBinsType::BINS_ILLEGAL
@@ -1076,6 +1076,7 @@ public:
: VCoverBinsType::BINS_USER))}
, m_isWildcard{isWildcard} {
addRangesp(rangesp);
this->iffp(iffp);
}
// Constructor for automatic bins
AstCoverBin(FileLine* fl, const string& name, AstNodeExpr* arraySizep)
+1
View File
@@ -1017,6 +1017,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
+ " hit in coverpoint "
+ coverpointp->prettyNameQ()));
}
if (binp->iffp()) condp = new AstLogAnd{fl, binp->iffp()->cloneTree(false), condp};
AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp);
UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for coverpoint");
m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr});
+5
View File
@@ -352,6 +352,11 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
}
puts("}");
}
if (nodep->iffp()) {
puts(" iff (");
iterateConst(nodep->iffp());
puts(")");
}
puts(";\n");
}
void visit(AstCoverBinsof* nodep) override {
+1 -1
View File
@@ -2101,7 +2101,7 @@ class WidthVisitor final : public VNVisitor {
}
void visit(AstCoverBin* nodep) override {
widthCovergroupRanges(nodep->rangesp());
userIterateAndNext(nodep->iffp(), nullptr);
if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH);
userIterateAndNext(nodep->arraySizep(), nullptr);
userIterateAndNext(nodep->transp(), nullptr);
}
+9 -12
View File
@@ -7169,9 +7169,9 @@ bins_or_options<nodep>: // ==IEEE: bins_or_options
coverage_option { $$ = $1; }
// // Can't use wildcardE as results in conflicts
| yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, false, false};
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, false, false, false, $8};
if ($3) binp->isArray(true);
$$ = binp; DEL($8); }
$$ = binp; }
| yBINS idAny/*bin_identifier*/ '[' cgexpr ']' iffE
{ // Check for automatic bins: bins auto[N]
if (*$2 == "auto") {
@@ -7184,13 +7184,13 @@ bins_or_options<nodep>: // ==IEEE: bins_or_options
}
}
| yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, true, false};
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, true, false, false, $8};
if ($3) binp->isArray(true);
$$ = binp; DEL($8); }
$$ = binp; }
| yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, false, true};
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, false, true, false, $8};
if ($3) binp->isArray(true);
$$ = binp; DEL($8); }
$$ = binp; }
| yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE
{ AstCoverBin* const binp = new AstCoverBin{$<fl>2, *$2, $6, false, false};
BBCOVERIGN($<fl>8, "Unsupported: 'with' in cover bin (bin created without filter)");
@@ -7210,14 +7210,11 @@ bins_or_options<nodep>: // ==IEEE: bins_or_options
| yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE
{ $$ = nullptr; BBCOVERIGN($<fl>6, "Unsupported: 'with' in cover bin"); DEL($8, $10); }
| yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE
{ $$ = new AstCoverBin{$<fl>3, *$3, $7, false, false, true};
DEL($9); }
{ $$ = new AstCoverBin{$<fl>3, *$3, $7, false, false, true, $9}; }
| yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE
{ $$ = new AstCoverBin{$<fl>3, *$3, $7, true, false, true};
DEL($9); }
{ $$ = new AstCoverBin{$<fl>3, *$3, $7, true, false, true, $9}; }
| yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE
{ $$ = new AstCoverBin{$<fl>3, *$3, $7, false, true, true};
DEL($9); }
{ $$ = new AstCoverBin{$<fl>3, *$3, $7, false, true, true, $9}; }
| yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE
{ $$ = nullptr; BBCOVERIGN($<fl>9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); }
| yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE
+13 -1
View File
@@ -1,4 +1,4 @@
__vlAnonCG_cg.cp_a.one: 2
__vlAnonCG_cg.cp_a.one: 1
__vlAnonCG_cg.cp_b.one: 2
__vlAnonCG_cg.cross_ab.one_x_one [cross]: 1
cg_and.cp_concat.b00: 0
@@ -8,6 +8,9 @@ cg_and.cp_concat.b11: 0
cg_array_iff.cp.arr[0]: 1
cg_array_iff.cp.arr[1]: 0
cg_array_iff.cp.arr[2]: 0
cg_bin_cross.cp_a.one: 2
cg_bin_cross.cp_b.one: 2
cg_bin_cross.cross_ab.one_x_one [cross]: 1
cg_bitw.cp.seven: 1
cg_cross_coverpoint_iff.cp_a.one: 2
cg_cross_coverpoint_iff.cp_b.one: 2
@@ -29,5 +32,14 @@ cg_or.cp.lo: 1
cg_part.cp.one: 1
cg_rel.cp.five: 1
cg_rel.cp.two: 0
cg_state_bin_iff.cp.grouped: 2
cg_state_bin_iff.cp.ignored [ignore]: 1
cg_state_bin_iff.cp.illegal [illegal]: 0
cg_state_bin_iff.cp.scalar: 1
cg_state_bin_iff.cp.values[0]: 1
cg_state_bin_iff.cp.values[1]: 1
cg_state_bin_iff.cp.wild: 2
cg_state_bin_iff.cp.wild_ignored [ignore]: 2
cg_state_bin_iff.cp.wild_illegal [illegal]: 0
cg_trans2_iff.cp.t2: 1
cg_trans3_iff.cp.t3: 1
+69 -2
View File
@@ -27,9 +27,11 @@ class CrossIffEmbedded;
bit b;
bit enabled;
// cross-level iff using an enclosing class member
// Bin- and cross-level iff using an enclosing class member.
covergroup cg;
cp_a: coverpoint a {bins one = {1};}
cp_a: coverpoint a {
bins one = {1} iff (this.enabled);
}
cp_b: coverpoint b {bins one = {1};}
cross_ab: cross cp_a, cp_b iff (this.enabled);
endgroup
@@ -56,6 +58,10 @@ module t;
logic [1:0] m_a;
logic [1:0] m_b;
int count;
logic [3:0] bin_value;
bit point_enable;
bit bin_enable;
logic [2:0] bin_mask;
// iff on explicit value bins
covergroup cg_iff;
@@ -144,6 +150,29 @@ module t;
cross cp_a, cp_b iff (!disable_cross) {}
endgroup
covergroup cg_state_bin_iff;
cp: coverpoint bin_value iff (point_enable) {
bins scalar = {2} iff (bin_enable);
bins values[] = {0, 1} iff (bin_enable && bin_mask[0]);
bins grouped = {3, 4} iff (bin_enable && (bin_mask[2:1] != 0));
wildcard bins wild = {4'b01?1} iff (bin_enable && !(bin_mask[2] == 0));
ignore_bins ignored = {8} iff (bin_enable);
wildcard ignore_bins wild_ignored = {4'b10?1} iff (bin_enable && bin_mask[1]);
illegal_bins illegal = {12} iff (bin_enable);
wildcard illegal_bins wild_illegal = {4'b11?1} iff (bin_enable && bin_mask[2]);
}
endgroup
covergroup cg_bin_cross with function sample (bit a, bit b, bit enable_a, bit enable_b);
cp_a: coverpoint a {
bins one = {1} iff (enable_a);
}
cp_b: coverpoint b {
bins one = {1} iff (enable_b);
}
cross_ab: cross cp_a, cp_b;
endgroup
cg_iff cg1 = new;
cg_default_iff cg2 = new;
cg_array_iff cg3 = new;
@@ -158,6 +187,8 @@ module t;
cg_cross_iff cx = new;
cg_cross_iff_unnamed cxu = new;
CrossIffEmbedded cxe = new;
cg_state_bin_iff state_bins = new;
cg_bin_cross cxb = new;
initial begin
cxc.sample(1, 1, 0, 1);
@@ -172,6 +203,42 @@ module t;
cxe.observe(1, 1, 0);
cxe.observe(1, 1, 1);
cxb.sample(1, 1, 0, 0);
`checkr(cxb.get_inst_coverage(), 0.0);
cxb.sample(1, 1, 1, 0);
cxb.sample(1, 1, 0, 1);
`checkr(cxb.get_inst_coverage() < 100.0, 1);
cxb.sample(1, 1, 1, 1);
`checkr(cxb.get_inst_coverage(), 100.0);
cxb.sample(1, 1, 0, 0);
// IEEE 1800-2023 19.5.1: iff gates sampling, not bin construction.
point_enable = 1;
bin_enable = 0;
bin_mask = 3'b111;
for (int i = 0; i < 8; ++i) begin
bin_value = 4'(i);
state_bins.sample();
end
`checkr(state_bins.get_inst_coverage(), 0.0);
for (int i = 8; i < 16; ++i) begin
bin_value = 4'(i);
state_bins.sample();
end
point_enable = 0;
bin_enable = 1;
for (int i = 0; i < 16; ++i) begin
bin_value = 4'(i);
state_bins.sample();
end
`checkr(state_bins.get_inst_coverage(), 0.0);
point_enable = 1;
for (int i = 0; i < 12; ++i) begin
bin_value = 4'(i);
state_bins.sample();
end
`checkr(state_bins.get_inst_coverage(), 100.0);
// Sample disabled_lo and disabled_hi with enable=0 -- must not be recorded
enable = 0;
value = 1;
@@ -0,0 +1,4 @@
Disabled guards produced no illegal-bin errors.
[0] %Error: t_covergroup_iff_illegal.v:18: Assertion failed in top.t.cg.sample: Illegal bin 'scalar' hit in coverpoint 'cp'
%Error: t/t_covergroup_iff_illegal.v:18: Verilog $stop
Aborting...
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.compile(verilator_flags2=['--coverage'], threads=(2 if test.vltmt else 1))
test.execute(fails=True,
check_finished=False,
all_run_flags=['+value=5'],
expect_filename=test.golden_filename)
test.passes()
+37
View File
@@ -0,0 +1,37 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0);
// verilog_format: on
module t;
int sample_value;
covergroup cg with function sample (bit [2:0] value, bit point_enable, bit bin_enable);
cp: coverpoint value iff (point_enable) {
bins good = {4};
illegal_bins scalar = {5} iff (bin_enable);
illegal_bins values[] = {0, 1} iff (bin_enable);
wildcard illegal_bins wild = {3'b11?} iff (bin_enable);
}
endgroup
cg inst = new;
initial begin
if (!$value$plusargs("value=%d", sample_value)) `stop;
for (int i = 0; i < 8; ++i) inst.sample(3'(i), 1, 0);
for (int i = 0; i < 8; ++i) inst.sample(3'(i), 0, 1);
`ifdef VERILATOR
`checkd($c32("Verilated::threadContextp()->errorCount()"), 0);
`endif
$display("Disabled guards produced no illegal-bin errors.");
inst.sample(3'(sample_value), 1, 1);
$finish;
end
endmodule
@@ -0,0 +1,4 @@
Disabled guards produced no illegal-bin errors.
[0] %Error: t_covergroup_iff_illegal.v:19: Assertion failed in top.t.cg.sample: Illegal bin 'values' hit in coverpoint 'cp'
%Error: t/t_covergroup_iff_illegal.v:19: Verilog $stop
Aborting...
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.top_filename = 't/t_covergroup_iff_illegal.v'
test.compile(verilator_flags2=['--coverage'], threads=(2 if test.vltmt else 1))
test.execute(fails=True,
check_finished=False,
all_run_flags=['+value=1'],
expect_filename=test.golden_filename)
test.passes()
@@ -0,0 +1,4 @@
Disabled guards produced no illegal-bin errors.
[0] %Error: t_covergroup_iff_illegal.v:20: Assertion failed in top.t.cg.sample: Illegal bin 'wild' hit in coverpoint 'cp'
%Error: t/t_covergroup_iff_illegal.v:20: Verilog $stop
Aborting...
+23
View File
@@ -0,0 +1,23 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt_all')
test.top_filename = 't/t_covergroup_iff_illegal.v'
test.compile(verilator_flags2=['--coverage'], threads=(2 if test.vltmt else 1))
test.execute(fails=True,
check_finished=False,
all_run_flags=['+value=6'],
expect_filename=test.golden_filename)
test.passes()
+1 -1
View File
@@ -792,7 +792,7 @@ module Vt_debug_emitv_t;
covergroup Vt_debug_emitv_cg_basic;
function new;
cp_sig: coverpoint cg_sig {
bins low = {['sh0:'sh3]};
bins low = {['sh0:'sh3]} iff (cg_sig2[0]);
bins high = {['sh4:'sh6]};
bins multi = {'sh0, 'sh1, 'sh2};
bins dflt = default;
+1 -1
View File
@@ -395,7 +395,7 @@ module t (/*AUTOARG*/
option.per_instance = 1;
option.weight = 2;
cp_sig: coverpoint cg_sig {
bins low = {[0:3]};
bins low = {[0:3]} iff (cg_sig2[0]);
bins high = {[4:6]};
bins multi = {0, 1, 2}; // multiple values in one bins (exercises EmitV range loop)
bins dflt = default;