From 1aa87913c89c72a01bfaf9708d952fef83e507ce Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Wed, 16 Sep 2026 23:53:15 +0200 Subject: [PATCH] Support `illegal_bins` and `ignore_bins` for cross bins (#8372) --- include/verilated_covergroup.cpp | 40 ++-- include/verilated_covergroup.h | 16 +- src/V3AstAttr.h | 4 +- src/V3AstNodeOther.h | 14 +- src/V3AstNodes.cpp | 1 + src/V3Covergroup.cpp | 32 ++- src/V3EmitV.cpp | 2 +- src/verilog.y | 4 +- test_regress/t/t_covergroup_autobins_bad.out | 56 ++++-- test_regress/t/t_covergroup_autobins_bad.v | 8 + test_regress/t/t_covergroup_cross_bins.out | 133 +++++++++++++ test_regress/t/t_covergroup_cross_bins.py | 22 +++ test_regress/t/t_covergroup_cross_bins.v | 182 ++++++++++++++++++ test_regress/t/t_covergroup_cross_bins_off.py | 18 ++ .../t/t_covergroup_cross_illegal.error.out | 14 ++ test_regress/t/t_covergroup_cross_illegal.out | 36 ++++ test_regress/t/t_covergroup_cross_illegal.py | 22 +++ test_regress/t/t_covergroup_cross_illegal.v | 112 +++++++++++ .../t/t_covergroup_cross_protect_ids.py | 21 ++ test_regress/t/t_covergroup_unsup.out | 16 +- test_regress/t/t_covergroup_unsup.v | 5 +- test_regress/t/t_covergroup_unsup_ign2.out | 18 +- test_regress/t/t_debug_emitv.out | 3 + test_regress/t/t_debug_emitv.v | 2 + 24 files changed, 718 insertions(+), 63 deletions(-) create mode 100644 test_regress/t/t_covergroup_cross_bins.out create mode 100755 test_regress/t/t_covergroup_cross_bins.py create mode 100644 test_regress/t/t_covergroup_cross_bins.v create mode 100755 test_regress/t/t_covergroup_cross_bins_off.py create mode 100644 test_regress/t/t_covergroup_cross_illegal.error.out create mode 100644 test_regress/t/t_covergroup_cross_illegal.out create mode 100755 test_regress/t/t_covergroup_cross_illegal.py create mode 100644 test_regress/t/t_covergroup_cross_illegal.v create mode 100755 test_regress/t/t_covergroup_cross_protect_ids.py diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index 2ef89f88d..845a4caf9 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -133,8 +133,8 @@ void VlCoverCross::init(const char* hier, uint32_t dims, VlCoverpoint* const* cp } } -void VlCoverCross::addBin(std::initializer_list selection, const char* namep, - const char* filep, int line, int col) { +void VlCoverCross::addBin(VlCovBinKind kind, std::initializer_list selection, + const char* namep, const char* filep, int line, int col) { if (!m_numAutoBins) return; // An empty product creates no cross bin. Explicit& data = *m_explicitp; const uint32_t words = m_numAutoBins / 64 + (m_numAutoBins % 64 != 0); @@ -148,6 +148,8 @@ void VlCoverCross::addBin(std::initializer_list selection, const char* bin.filep = filep; bin.line = line; bin.col = col; + bin.kind = kind; + if (kind == VlCovBinKind::KIND_NORMAL) ++data.normalBins; uint32_t word = 0; for (const uint64_t bits : selection) { data.wordsp[word++].autoExcluded |= bits; } } @@ -204,20 +206,27 @@ void VlCoverCross::iterateProduct(uint32_t dim, uint32_t baseIdx) { } } +void VlCoverCross::incrementBin(Bin& bin) { + if (bin.count++ == 0 && bin.kind == VlCovBinKind::KIND_NORMAL) ++m_numCovered; + if (VL_UNLIKELY(bin.kind == VlCovBinKind::KIND_ILLEGAL)) { + VL_PRINTF_MT("%%Error: %s:%d: Illegal cross bin '%s' hit in cross '%s'.\n", bin.filep, + bin.line, bin.namep, m_hier.c_str()); + VL_STOP_MT(bin.filep, bin.line, ""); + } +} + template void VlCoverCross::sampleSingleTuple(uint32_t idx, const bool* binIffs) { Explicit& data = *m_explicitp; - const uint32_t word = idx / 64; - const uint64_t bit = uint64_t{1} << (idx % 64); + const uint32_t word = idx / VL_QUADSIZE; + const uint64_t bit = uint64_t{1} << VL_BITBIT_Q(idx); if (!(data.wordsp[word].autoExcluded & bit)) { incrementAuto(idx); return; } for (Bin& bin : data.bins) { if (T_ApplyIffs && !*binIffs++) continue; - if (bin.selectionp[word] & bit) { - if (bin.count++ == 0) ++m_numCovered; - } + if (bin.selectionp[word] & bit) incrementBin(bin); } } @@ -260,7 +269,7 @@ void VlCoverCross::sampleBins(const bool* binIffs) { } } } - if (matched && bin.count++ == 0) ++m_numCovered; + if (matched) incrementBin(bin); } for (uint32_t i = 0; i < data.numTouchedWords; ++i) { data.wordsp[wordsp[i].touchedWord].hitBits = 0; @@ -372,9 +381,18 @@ void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* pa Bin& userBin = m_explicitp->bins[i]; const std::string binLineStr = std::to_string(userBin.line); const std::string binColStr = std::to_string(userBin.col); - VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, "filename", - userBin.filep, "lineno", binLineStr.c_str(), "column", - binColStr.c_str(), "bin", bin.c_str(), "cross", "1"); + if (userBin.kind == VlCovBinKind::KIND_NORMAL) { + VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, + "filename", userBin.filep, "lineno", binLineStr.c_str(), "column", + binColStr.c_str(), "bin", bin.c_str(), "cross", "1"); + } else { + const char* const binType + = userBin.kind == VlCovBinKind::KIND_IGNORE ? "ignore" : "illegal"; + VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, + "filename", userBin.filep, "lineno", binLineStr.c_str(), "column", + binColStr.c_str(), "bin", bin.c_str(), "cross", "1", "bin_type", + binType); + } continue; } const uint32_t flat = autoIndex(i - explicitCount); diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index 152f39ddd..04b5dd9c5 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -154,9 +154,7 @@ public: // ---- VlCoverpointIf ---- uint32_t binCount() const override { return m_total; } std::string binName(uint32_t i) const override; - // Deliberately not on VlCoverpointIf: only registerBins() needs it, via the - // concrete coverpoint. A cross has all-Normal bins and exposes no kind, so the - // interface omits it; add it back only if a writer needs it polymorphically. + // Deliberately not on VlCoverpointIf: only coverage-database registration needs it. VlCovBinKind binKind(uint32_t i) const { return namerFor(i).set(); } void coverageParts(double& covered, double& total) const override { // Count Normal bins that reached option.at_least on demand, so the hot @@ -230,6 +228,7 @@ protected: const char* filep; // Bin declaration file int line; // Bin declaration line int col; // Bin declaration column + VlCovBinKind kind = VlCovBinKind::KIND_NORMAL; // Normal, ignore, or illegal bin uint32_t count = 0; // Samples matching the selection and guard uint32_t numWords = 0; // Number of nonzero selection-word indices const uint32_t* wordIndicesp = nullptr; // Slice of the packed selection-word indices @@ -261,6 +260,7 @@ protected: View binWords; // Nonzero selection words, grouped by bin uint64_t* selectionp; // [bins.size() * ceil(m_numAutoBins / 64)] uint32_t numBins = 0; // Bins configured by addBin() + uint32_t normalBins = 0; // Explicit bins contributing to coverage uint32_t minBinWords = 0; // Minimum nonzero-word count across explicit bins uint32_t numTouchedWords = 0; // Active prefix of wordsp[].touchedWord }; @@ -289,6 +289,7 @@ private: void incrementAuto(uint32_t idx) { if (m_flatCountsp[idx]++ == 0) ++m_numCovered; } + void incrementBin(Bin& bin); template void incrementTuple(uint32_t idx) { Explicit& data = *m_explicitp; @@ -334,8 +335,8 @@ public: void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file, int line, int col); /// Add a cross bin using a verilation-time bitmap of selected Normal-bin tuples. - void addBin(std::initializer_list selection, const char* namep, const char* filep, - int line, int col); + void addBin(VlCovBinKind kind, std::initializer_list selection, const char* namep, + const char* filep, int line, int col); /// Retain only automatic cross bins not selected by any explicit bin. void finalizeBins(); void registerBins(VerilatedCovContext* covcontextp, const char* page); @@ -346,7 +347,7 @@ public: void sample(const bool* binIffs = nullptr); // ---- VlCoverpointIf ---- - // Explicit bins precede retained automatic bins; all are Normal bins. + // Explicit bins (including ignore/illegal) precede retained automatic bins. uint32_t binCount() const override { return hasExplicitBins() ? static_cast(m_explicitp->bins.size() + m_explicitp->autoBins.size()) @@ -355,7 +356,8 @@ public: std::string binName(uint32_t i) const override; void coverageParts(double& covered, double& total) const override { covered = m_numCovered; - total = binCount(); + total = hasExplicitBins() ? m_explicitp->normalBins + m_explicitp->autoBins.size() + : m_numAutoBins; } }; diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 077593309..e5367c343 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1176,8 +1176,8 @@ public: : m_e{_e} {} constexpr operator en() const { return m_e; } // LCOV_EXCL_LINE const char* ascii() const { - static const char* const names[] - = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; + static const char* const names[] = {"array", "auto", "default", "ignore_bins", + "illegal_bins", "transition", "bins", "wildcard"}; return names[m_e]; } // VlCovBinKind enumerator naming the bin's set diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index fe3c914e8..d1983695d 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1136,16 +1136,26 @@ class AstCoverCrossBin final : public AstNode { // @astgen op1 := selectp : Optional[AstNode] // Null for unsupported selections // @astgen op2 := iffp : Optional[AstNodeExpr] const string m_name; // Declared cross bin name + // dist-ast-dump-suppress // Bin kind is shown by verilogKwd() in emitted Verilog. + const VCoverBinsType m_binsType; // Normal, ignore, or illegal bin public: - AstCoverCrossBin(FileLine* fl, const string& name, AstNode* selectp, AstNodeExpr* iffp) + AstCoverCrossBin(FileLine* fl, const string& name, AstNode* selectp, AstNodeExpr* iffp, + VCoverBinsType binsType = VCoverBinsType::BINS_USER) : ASTGEN_SUPER_CoverCrossBin(fl) - , m_name{name} { + , m_name{name} + , m_binsType{binsType} { this->selectp(selectp); this->iffp(iffp); } ASTGEN_MEMBERS_AstCoverCrossBin; string name() const override VL_MT_STABLE { return m_name; } + string verilogKwd() const override; + VCoverBinsType binsType() const { return m_binsType; } + bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START + const AstCoverCrossBin* const asamep = VN_DBG_AS(samep, CoverCrossBin); + return m_name == asamep->m_name && m_binsType.m_e == asamep->m_binsType.m_e; + } // LCOV_EXCL_STOP }; class AstCoverCrossSelect final : public AstNode { // Intersection or union of two cross-bin selections diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 81b3d73da..587c8f245 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1214,6 +1214,7 @@ void AstCoverBinsof::dumpJson(std::ostream& str) const { } void AstCoverCross::dump(std::ostream& str) const { Super::dump(str); } void AstCoverCross::dumpJson(std::ostream& str) const { Super::dumpJson(str); } +string AstCoverCrossBin::verilogKwd() const { return binsType().ascii(); } string AstCoverCrossDType::cppTemplateArgs() const { return cvtToStr(dimensions()) + ", " + cvtToStr(tuples()) + ", " + cvtToStr(bins()) + ", " + cvtToStr(autoBins()) + ", " + cvtToStr(binWords()); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 9eab4cb62..12ec751df 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -1998,6 +1998,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { } ctx.tuples = tuples; layout.tuples = tuples; + CrossSelection occupied; CrossSelection excluded; std::set names; for (AstNode* itemp = crossp->binsp(); itemp; itemp = itemp->nextp()) { @@ -2014,16 +2015,37 @@ class FunctionalCoverageVisitor final : public VNVisitor { })) { continue; } - if (excluded.empty()) excluded.resize(selection.size(), 0); + if (occupied.empty()) { + occupied.resize(selection.size(), 0); + excluded.resize(selection.size(), 0); + } for (size_t i = 0; i < selection.size(); ++i) { - excluded[i] |= selection[i]; - if (selection[i]) ++layout.binWords; + occupied[i] |= selection[i]; + if (!binp->binsType().binIsNormal()) excluded[i] |= selection[i]; } layout.bins.push_back({binp, std::move(selection)}); } if (!layout.bins.empty()) { + // IEEE 1800-2023 19.6.2/19.6.3: exclusions also remove tuples from named + // bins, independently of declaration order and sampling guards. + for (ResolvedCrossBin& resolved : layout.bins) { + for (size_t i = 0; i < resolved.selection.size(); ++i) { + if (resolved.binp->binsType().binIsNormal()) { + resolved.selection[i] &= ~excluded[i]; + } + if (resolved.selection[i]) ++layout.binWords; + } + } + layout.bins.erase(std::remove_if(layout.bins.begin(), layout.bins.end(), + [](const ResolvedCrossBin& resolved) { + return std::all_of( + resolved.selection.begin(), + resolved.selection.end(), + [](uint64_t word) { return word == 0; }); + }), + layout.bins.end()); layout.autoBins = layout.tuples; - for (const uint64_t word : excluded) { + for (const uint64_t word : occupied) { layout.autoBins -= static_cast(std::bitset{word}.count()); } } @@ -2058,7 +2080,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { mask += "}"; m_constructorp->addStmtsp( itemCall(fl, cxVarp, VCMethod::COVERGROUP_ADD_BIN, - {ctext(fl, mask), + {ctext(fl, binp->binsType().binSetEnum()), ctext(fl, mask), ctext(fl, quoted(VIdProtect::protectWordsIf(binp->name(), prot))), ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))), cnum(fl, static_cast(fl->lineno())), diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 5353d1630..a1ae67069 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -366,7 +366,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } } void visit(AstCoverCrossBin* nodep) override { - putfs(nodep, "bins " + nodep->name() + " = "); + putfs(nodep, nodep->verilogKwd() + " " + nodep->name() + " = "); iterateConstNull(nodep->selectp()); if (nodep->iffp()) { puts(" iff ("); diff --git a/src/verilog.y b/src/verilog.y index 77a041973..6d3de095e 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -7398,9 +7398,9 @@ cross_body_item: // ==IEEE: cross_body_item | yBINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' { $$ = new AstCoverCrossBin{$1, *$2, $4, $5}; } | yIGNORE_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' - { $$ = nullptr; BBCOVERIGN($1, "Unsupported: explicit coverage cross bins"); DEL($4, $5); } + { $$ = new AstCoverCrossBin{$1, *$2, $4, $5, VCoverBinsType::BINS_IGNORE}; } | yILLEGAL_BINS idAny/*new-bin_identifier*/ '=' select_expression iffE ';' - { $$ = nullptr; BBCOVERIGN($1, "Unsupported: explicit coverage cross bins"); DEL($4, $5); } + { $$ = new AstCoverCrossBin{$1, *$2, $4, $5, VCoverBinsType::BINS_ILLEGAL}; } | error ';' { $$ = nullptr; } // LCOV_EXCL_LINE ; diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index 55c506bf8..dbb9a783a 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -145,36 +145,60 @@ : ... note: In instance 't' 137 | bins duplicate = binsof(cp_b); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:138:26: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Error: t/t_covergroup_autobins_bad.v:138:7: Duplicate cross bin 'duplicate' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 138 | ignore_bins duplicate = binsof(cp_a); + | ^~~~~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:139:7: Duplicate cross bin 'duplicate' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 139 | illegal_bins duplicate = binsof(cp_b); + | ^~~~~~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:141:7: Duplicate cross bin 'ignored_duplicate' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 141 | illegal_bins ignored_duplicate = binsof(cp_b); + | ^~~~~~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:143:7: Duplicate cross bin 'illegal_duplicate' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 143 | bins illegal_duplicate = binsof(cp_b); + | ^~~~ +%Error: t/t_covergroup_autobins_bad.v:144:36: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 144 | ignore_bins missing_ignore = binsof(cp_a.missing); + | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:145:40: binsof coverpoint 'cp_other' is not an item of cross 'xc' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 145 | illegal_bins uncrossed_illegal = binsof(cp_other); + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:146:26: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 138 | bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); + 146 | bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:139:32: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:147:32: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 139 | bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; + 147 | bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:150:9: Unsupported: cross coverage with more than 2^32-1 tuples. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:158:9: Unsupported: cross coverage with more than 2^32-1 tuples. : ... note: In instance 't' - 150 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f { + 158 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f { | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:153:16: Unsupported: cross coverage with more than 2^32-1 tuples. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:161:16: Unsupported: cross coverage with more than 2^32-1 tuples. : ... note: In instance 't' - 153 | auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; + 161 | auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; | ^~~~~ -%Error: t/t_covergroup_autobins_bad.v:159:34: Non-constant expression in bin value list; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:167:34: Non-constant expression in bin value list; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 159 | ignore_bins nonconstant = {size_var}; + 167 | ignore_bins nonconstant = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:159:34: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:167:34: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 159 | ignore_bins nonconstant = {size_var}; + 167 | ignore_bins nonconstant = {size_var}; | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:163:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:171:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 163 | bins selected = binsof(cp_a) intersect {0}; + 171 | bins selected = binsof(cp_a) intersect {0}; | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:202:23: Unsupported: 'intersect' exclusion matching exceeds the selection work limit. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:210:23: Unsupported: 'intersect' exclusion matching exceeds the selection work limit. : ... note: In instance 't' - 202 | bins selected = binsof(cp_a.whole) intersect {[0:30'h3fffffff]} && binsof(cp_b); + 210 | bins selected = binsof(cp_a.whole) intersect {[0:30'h3fffffff]} && binsof(cp_b); | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_autobins_bad.v b/test_regress/t/t_covergroup_autobins_bad.v index f193ca42e..44804af79 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -135,6 +135,14 @@ module t; bins missing_bin = binsof(cp_a.missing); bins duplicate = binsof(cp_a); bins duplicate = binsof(cp_b); + ignore_bins duplicate = binsof(cp_a); + illegal_bins duplicate = binsof(cp_b); + ignore_bins ignored_duplicate = binsof(cp_a); + illegal_bins ignored_duplicate = binsof(cp_b); + illegal_bins illegal_duplicate = binsof(cp_a); + bins illegal_duplicate = binsof(cp_b); + ignore_bins missing_ignore = binsof(cp_a.missing); + illegal_bins uncrossed_illegal = binsof(cp_other); bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; } diff --git a/test_regress/t/t_covergroup_cross_bins.out b/test_regress/t/t_covergroup_cross_bins.out new file mode 100644 index 000000000..6eefa3723 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_bins.out @@ -0,0 +1,133 @@ +cg_auto.cp_a.values[0]: 2 +cg_auto.cp_a.values[1]: 2 +cg_auto.cp_a.values[2]: 2 +cg_auto.cp_a.values[3]: 2 +cg_auto.cp_b.auto_0: 4 +cg_auto.cp_b.auto_1: 4 +cg_auto.cx.forbidden [illegal,cross]: 0 +cg_auto.cx.ignored [ignore,cross]: 1 +cg_auto.cx.values[1]_x_auto_0 [cross]: 1 +cg_auto.cx.values[1]_x_auto_1 [cross]: 1 +cg_auto.cx.values[2]_x_auto_0 [cross]: 1 +cg_auto.cx.values[2]_x_auto_1 [cross]: 1 +cg_empty.cp_a.values[0]: 2 +cg_empty.cp_a.values[1]: 2 +cg_empty.cp_a.values[2]: 2 +cg_empty.cp_a.values[3]: 2 +cg_empty.cp_b.auto_0: 4 +cg_empty.cp_b.auto_1: 4 +cg_empty.cx.forbidden [illegal,cross]: 0 +cg_empty.cx.ignored [ignore,cross]: 8 +cg_order.cp_a.values[0]: 2 +cg_order.cp_a.values[1]: 2 +cg_order.cp_a.values[2]: 2 +cg_order.cp_a.values[3]: 2 +cg_order.cp_b.auto_0: 4 +cg_order.cp_b.auto_1: 4 +cg_order.exclusions_first.combined [cross]: 4 +cg_order.exclusions_first.forbidden [illegal,cross]: 0 +cg_order.exclusions_first.ignored [ignore,cross]: 1 +cg_order.normal_first.combined [cross]: 4 +cg_order.normal_first.forbidden [illegal,cross]: 0 +cg_order.normal_first.ignored [ignore,cross]: 1 +cg_overlap.cp_a.either: 8 +cg_overlap.cp_a.high: 4 +cg_overlap.cp_a.low: 4 +cg_overlap.cp_b.either: 8 +cg_overlap.cp_b.one: 4 +cg_overlap.cp_b.zero: 4 +cg_overlap.cx.combined [cross]: 8 +cg_overlap.cx.forbidden [illegal,cross]: 0 +cg_overlap.cx.ignored [ignore,cross]: 4 +cg_three.cp_a.auto_0: 4 +cg_three.cp_a.auto_1: 4 +cg_three.cp_b.auto_0: 4 +cg_three.cp_b.auto_1: 4 +cg_three.cp_c.auto_0: 4 +cg_three.cp_c.auto_1: 4 +cg_three.cx.combined [cross]: 4 +cg_three.cx.forbidden [illegal,cross]: 0 +cg_three.cx.ignored [ignore,cross]: 3 +cg_transition.cp_a.one: 4 +cg_transition.cp_a.seq: 4 +cg_transition.cp_a.zero: 4 +cg_transition.cp_b.auto_0: 4 +cg_transition.cp_b.auto_1: 4 +cg_transition.cx.combined [cross]: 6 +cg_transition.cx.forbidden [illegal,cross]: 0 +cg_transition.cx.ignored [ignore,cross]: 4 +cg_wide.cp_a.values[0]: 2 +cg_wide.cp_a.values[10]: 2 +cg_wide.cp_a.values[11]: 2 +cg_wide.cp_a.values[12]: 2 +cg_wide.cp_a.values[13]: 2 +cg_wide.cp_a.values[14]: 2 +cg_wide.cp_a.values[15]: 2 +cg_wide.cp_a.values[16]: 2 +cg_wide.cp_a.values[17]: 2 +cg_wide.cp_a.values[18]: 2 +cg_wide.cp_a.values[19]: 2 +cg_wide.cp_a.values[1]: 2 +cg_wide.cp_a.values[20]: 2 +cg_wide.cp_a.values[21]: 2 +cg_wide.cp_a.values[22]: 2 +cg_wide.cp_a.values[23]: 2 +cg_wide.cp_a.values[24]: 2 +cg_wide.cp_a.values[25]: 2 +cg_wide.cp_a.values[26]: 2 +cg_wide.cp_a.values[27]: 2 +cg_wide.cp_a.values[28]: 2 +cg_wide.cp_a.values[29]: 2 +cg_wide.cp_a.values[2]: 2 +cg_wide.cp_a.values[30]: 2 +cg_wide.cp_a.values[31]: 2 +cg_wide.cp_a.values[32]: 2 +cg_wide.cp_a.values[33]: 2 +cg_wide.cp_a.values[34]: 2 +cg_wide.cp_a.values[35]: 2 +cg_wide.cp_a.values[36]: 2 +cg_wide.cp_a.values[37]: 2 +cg_wide.cp_a.values[38]: 2 +cg_wide.cp_a.values[39]: 2 +cg_wide.cp_a.values[3]: 2 +cg_wide.cp_a.values[40]: 2 +cg_wide.cp_a.values[41]: 2 +cg_wide.cp_a.values[42]: 2 +cg_wide.cp_a.values[43]: 2 +cg_wide.cp_a.values[44]: 2 +cg_wide.cp_a.values[45]: 2 +cg_wide.cp_a.values[46]: 2 +cg_wide.cp_a.values[47]: 2 +cg_wide.cp_a.values[48]: 2 +cg_wide.cp_a.values[49]: 2 +cg_wide.cp_a.values[4]: 2 +cg_wide.cp_a.values[50]: 2 +cg_wide.cp_a.values[51]: 2 +cg_wide.cp_a.values[52]: 2 +cg_wide.cp_a.values[53]: 2 +cg_wide.cp_a.values[54]: 2 +cg_wide.cp_a.values[55]: 2 +cg_wide.cp_a.values[56]: 2 +cg_wide.cp_a.values[57]: 2 +cg_wide.cp_a.values[58]: 2 +cg_wide.cp_a.values[59]: 2 +cg_wide.cp_a.values[5]: 2 +cg_wide.cp_a.values[60]: 2 +cg_wide.cp_a.values[61]: 2 +cg_wide.cp_a.values[62]: 2 +cg_wide.cp_a.values[63]: 2 +cg_wide.cp_a.values[64]: 2 +cg_wide.cp_a.values[6]: 2 +cg_wide.cp_a.values[7]: 2 +cg_wide.cp_a.values[8]: 2 +cg_wide.cp_a.values[9]: 2 +cg_wide.cp_b.auto_0: 65 +cg_wide.cp_b.auto_1: 65 +cg_wide.cx.combined [cross]: 122 +cg_wide.cx.forbidden [illegal,cross]: 0 +cg_wide.cx.ignored [ignore,cross]: 6 +cg_wide.cx.partial [cross]: 4 +cg_wide.cx.sparse [cross]: 2 +cg_zero_product.cp_a.ignored [ignore]: 8 +cg_zero_product.cp_b.auto_0: 4 +cg_zero_product.cp_b.auto_1: 4 diff --git a/test_regress/t/t_covergroup_cross_bins.py b/test_regress/t/t_covergroup_cross_bins.py new file mode 100755 index 000000000..4fed6d965 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_bins.py @@ -0,0 +1,22 @@ +#!/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 +import coverage_covergroup_common + +test.scenarios('vlt_all') + +coverage_covergroup_common.run(test, + verilator_flags2=[ + '--no-skip-identical', '--dumpi-tree 9', '--debug-emitv', + '--dumpi-V3EmitV 9' + ], + threads=(2 if test.vltmt else 1)) + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_bins.v b/test_regress/t/t_covergroup_cross_bins.v new file mode 100644 index 000000000..bab0af0df --- /dev/null +++ b/test_regress/t/t_covergroup_cross_bins.v @@ -0,0 +1,182 @@ +// 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 checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0); +// verilog_format: on + +module t ( + input clk +); + int cyc = 0; + + covergroup cg_order with function sample (bit [1:0] a, bit b, bit enabled); + cp_a: coverpoint a { + bins values[] = {[0 : 3]}; + } + cp_b: coverpoint b; + normal_first: cross cp_a, cp_b{ + bins combined = binsof (cp_a); + bins empty = binsof (cp_a) intersect {0}; + ignore_bins ignored = binsof (cp_a) intersect {0} iff (enabled); + illegal_bins forbidden = binsof (cp_a) intersect {3} iff (enabled); + ignore_bins empty_ignore = binsof (cp_a) intersect {7}; + illegal_bins empty_illegal = binsof (cp_a) intersect {7}; + } + exclusions_first: cross cp_a, cp_b{ + illegal_bins forbidden = binsof (cp_a) intersect {3} iff (enabled); + ignore_bins ignored = binsof (cp_a) intersect {0} iff (enabled); + bins empty = binsof (cp_a) intersect {0}; + bins combined = binsof (cp_a); + } + endgroup + + covergroup cg_auto with function sample (bit [1:0] a, bit b, bit enabled); + cp_a: coverpoint a { + bins values[] = {[0 : 3]}; + } + cp_b: coverpoint b; + cx: cross cp_a, cp_b{ + ignore_bins ignored = binsof (cp_a) intersect {0} iff (enabled); + illegal_bins forbidden = binsof (cp_a) intersect {3} iff (enabled); + } + endgroup + + covergroup cg_overlap with function sample (bit [1:0] a, bit b); + cp_a: coverpoint a { + bins low = {[0 : 1]}; + bins high = {[2 : 3]}; + bins either = {[0 : 3]}; + } + cp_b: coverpoint b { + bins zero = {0}; + bins one = {1}; + bins either = {[0 : 1]}; + } + cx: cross cp_a, cp_b{ + bins combined = binsof (cp_a); + bins empty = binsof (cp_a.low) && binsof (cp_b.zero); + ignore_bins ignored = binsof (cp_a.low); + illegal_bins forbidden = binsof (cp_a.high) && binsof (cp_b.one) iff (0); + } + endgroup + + covergroup cg_wide with function sample (bit [6:0] a, bit b); + cp_a: coverpoint a { + bins values[] = {[0 : 64]}; + } + cp_b: coverpoint b; + cx: cross cp_a, cp_b{ + bins combined = binsof (cp_a); + bins partial = binsof (cp_a) intersect {[30 : 34]}; + bins sparse = binsof (cp_a) intersect {0, 64}; + bins empty = binsof (cp_a) intersect {[31 : 33]}; + ignore_bins ignored = binsof (cp_a) intersect {[31 : 33]}; + illegal_bins forbidden = binsof (cp_a) intersect {64} iff (0); + } + endgroup + + covergroup cg_empty with function sample (bit [1:0] a, bit b); + cp_a: coverpoint a { + bins values[] = {[0 : 3]}; + } + cp_b: coverpoint b; + cx: cross cp_a, cp_b{ + bins empty = binsof (cp_a); + ignore_bins ignored = binsof (cp_a); + illegal_bins forbidden = binsof (cp_a) iff (0); + } + endgroup + + covergroup cg_transition with function sample (bit a, bit b); + cp_a: coverpoint a { + bins seq = (0 => 1); + bins zero = {0}; + bins one = {1}; + } + cp_b: coverpoint b; + cx: cross cp_a, cp_b{ + bins combined = binsof (cp_a); + ignore_bins ignored = binsof (cp_a.seq); + illegal_bins forbidden = binsof (cp_a.one) && binsof (cp_b) intersect {1} iff (0); + } + endgroup + + covergroup cg_zero_product with function sample (bit a, bit b); + cp_a: coverpoint a { + ignore_bins ignored = {0, 1}; + } + cp_b: coverpoint b; + cx: cross cp_a, cp_b{ + ignore_bins ignored = binsof (cp_b); + illegal_bins forbidden = binsof (cp_b); + } + endgroup + + covergroup cg_three with function sample (bit a, bit b, bit c); + cp_a: coverpoint a; + cp_b: coverpoint b; + cp_c: coverpoint c; + cx: cross cp_a, cp_b, cp_c{ + bins combined = binsof (cp_a); + ignore_bins ignored = binsof(cp_a) intersect {0} + && (binsof(cp_b) intersect {1} || !binsof(cp_c) intersect { + 1 + }); + illegal_bins forbidden = binsof(cp_a) intersect {1} + && binsof(cp_b) intersect {1} && binsof(cp_c) intersect { + 1 + } iff (0); + } + endgroup + + cg_order order_cov = new; + cg_auto auto_cov = new; + cg_overlap overlap_cov = new; + cg_wide wide_cov = new; + cg_empty empty_cov = new; + cg_transition trans_cov = new; + cg_three three_cov = new; + cg_zero_product zero_cov = new; + + always @(posedge clk) begin + if (cyc < 8) begin + order_cov.sample(2'(cyc / 2), 1'(cyc), cyc < 6 && 1'(cyc)); + auto_cov.sample(2'(cyc / 2), 1'(cyc), cyc < 6 && 1'(cyc)); + overlap_cov.sample(2'(cyc / 2), 1'(cyc)); + empty_cov.sample(2'(cyc / 2), 1'(cyc)); + trans_cov.sample(1'(cyc), 1'(cyc / 2)); + three_cov.sample(1'(cyc / 4), 1'(cyc / 2), 1'(cyc)); + zero_cov.sample(1'(cyc / 2), 1'(cyc)); + if (cyc == 1) begin + `checkr(order_cov.get_inst_coverage(), 37.5); + `checkr(auto_cov.get_inst_coverage(), 30.0); + end + if (cyc == 5) begin + `checkr(order_cov.get_inst_coverage(), 87.5); + `checkr(auto_cov.get_inst_coverage(), 90.0); + end + end + if (cyc < 65) begin + wide_cov.sample(7'(cyc), 0); + wide_cov.sample(7'(cyc), 1); + end + else begin + `checkr(order_cov.get_inst_coverage(), 100.0); + `checkr(auto_cov.get_inst_coverage(), 100.0); + `checkr(overlap_cov.get_inst_coverage(), 100.0); + `checkr(wide_cov.get_inst_coverage(), 100.0); + `checkr(empty_cov.get_inst_coverage(), 100.0); + `checkr(trans_cov.get_inst_coverage(), 100.0); + `checkr(three_cov.get_inst_coverage(), 100.0); + `checkr(zero_cov.get_inst_coverage(), 100.0); + $write("*-* All Finished *-*\n"); + $finish; + end + ++cyc; + end +endmodule diff --git a/test_regress/t/t_covergroup_cross_bins_off.py b/test_regress/t/t_covergroup_cross_bins_off.py new file mode 100755 index 000000000..30ce18c63 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_bins_off.py @@ -0,0 +1,18 @@ +#!/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_cross_bins.v' + +test.compile(verilator_flags2=['-CFLAGS -std=c++14'], threads=(2 if test.vltmt else 1)) +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_illegal.error.out b/test_regress/t/t_covergroup_cross_illegal.error.out new file mode 100644 index 000000000..ddd17e61f --- /dev/null +++ b/test_regress/t/t_covergroup_cross_illegal.error.out @@ -0,0 +1,14 @@ +%Error: t/t_covergroup_cross_illegal.v:26: Illegal cross bin 'forbidden' hit in cross 'cg_iff.illegal_last'. +-Info: t/t_covergroup_cross_illegal.v:26: Verilog $stop, ignored due to +verilator+error+limit +%Error: t/t_covergroup_cross_illegal.v:31: Illegal cross bin 'forbidden' hit in cross 'cg_iff.illegal_first'. +%Error: t/t_covergroup_cross_illegal.v:34: Illegal cross bin 'forbidden_again' hit in cross 'cg_iff.illegal_first'. +%Error: t/t_covergroup_cross_illegal.v:26: Illegal cross bin 'forbidden' hit in cross 'cg_iff.illegal_last'. +%Error: t/t_covergroup_cross_illegal.v:31: Illegal cross bin 'forbidden' hit in cross 'cg_iff.illegal_first'. +%Error: t/t_covergroup_cross_illegal.v:34: Illegal cross bin 'forbidden_again' hit in cross 'cg_iff.illegal_first'. +%Error: t/t_covergroup_cross_illegal.v:48: Illegal cross bin 'forbidden' hit in cross 'cg_plain.cx'. +%Error: t/t_covergroup_cross_illegal.v:51: Illegal cross bin 'forbidden' hit in cross 'cg_plain.only_illegal'. +%Error: t/t_covergroup_cross_illegal.v:78: Illegal cross bin 'forbidden_seq' hit in cross 'cg_multi.guarded'. +%Error: t/t_covergroup_cross_illegal.v:72: Illegal cross bin 'forbidden' hit in cross 'cg_multi.cx'. +%Error: t/t_covergroup_cross_illegal.v:72: Illegal cross bin 'forbidden' hit in cross 'cg_multi.cx'. +%Error: t/t_covergroup_cross_illegal.v:77: Illegal cross bin 'forbidden' hit in cross 'cg_multi.guarded'. +*-* All Finished *-* diff --git a/test_regress/t/t_covergroup_cross_illegal.out b/test_regress/t/t_covergroup_cross_illegal.out new file mode 100644 index 000000000..7978069dd --- /dev/null +++ b/test_regress/t/t_covergroup_cross_illegal.out @@ -0,0 +1,36 @@ +cg_iff.cp_a.auto_0: 1 +cg_iff.cp_a.auto_1: 5 +cg_iff.cp_b.auto_0: 1 +cg_iff.cp_b.auto_1: 5 +cg_iff.illegal_first.forbidden [illegal,cross]: 2 +cg_iff.illegal_first.forbidden_again [illegal,cross]: 2 +cg_iff.illegal_first.ignored [ignore,cross]: 2 +cg_iff.illegal_first.normal [cross]: 1 +cg_iff.illegal_last.forbidden [illegal,cross]: 2 +cg_iff.illegal_last.ignored [ignore,cross]: 2 +cg_iff.illegal_last.normal [cross]: 1 +cg_multi.cp_a.either: 5 +cg_multi.cp_a.one: 3 +cg_multi.cp_a.seq: 1 +cg_multi.cp_a.zero: 2 +cg_multi.cp_b.either: 6 +cg_multi.cp_b.one: 4 +cg_multi.cp_b.zero: 2 +cg_multi.cx.forbidden [illegal,cross]: 2 +cg_multi.cx.ignored [ignore,cross]: 3 +cg_multi.cx.normal [cross]: 5 +cg_multi.guarded.forbidden [illegal,cross]: 1 +cg_multi.guarded.forbidden_seq [illegal,cross]: 1 +cg_multi.guarded.ignored [ignore,cross]: 2 +cg_multi.guarded.normal [cross]: 5 +cg_plain.cp_a.auto_0: 2 +cg_plain.cp_a.auto_1: 2 +cg_plain.cp_b.auto_0: 2 +cg_plain.cp_b.auto_1: 2 +cg_plain.cx.forbidden [illegal,cross]: 1 +cg_plain.cx.ignored [ignore,cross]: 2 +cg_plain.cx.normal [cross]: 2 +cg_plain.only_illegal.auto_0_x_auto_0 [cross]: 1 +cg_plain.only_illegal.auto_0_x_auto_1 [cross]: 1 +cg_plain.only_illegal.auto_1_x_auto_0 [cross]: 1 +cg_plain.only_illegal.forbidden [illegal,cross]: 1 diff --git a/test_regress/t/t_covergroup_cross_illegal.py b/test_regress/t/t_covergroup_cross_illegal.py new file mode 100755 index 000000000..22570f5a8 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_illegal.py @@ -0,0 +1,22 @@ +#!/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 +import coverage_covergroup_common + +test.scenarios('vlt_all') + +test.compile(verilator_flags2=['--coverage'], threads=(2 if test.vltmt else 1)) +test.execute(all_run_flags=['+verilator+error+limit+100'], + expect_filename=test.golden_filename.replace('.out', '.error.out')) + +coverage_covergroup_common.covergroup_coverage_report(test) +test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_illegal.v b/test_regress/t/t_covergroup_cross_illegal.v new file mode 100644 index 000000000..772b9b84a --- /dev/null +++ b/test_regress/t/t_covergroup_cross_illegal.v @@ -0,0 +1,112 @@ +// 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); +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0); +// verilog_format: on + +module t ( + input clk +); + int cyc = 0; + + covergroup cg_iff with function sample ( + bit a, bit b, bit enabled, bit bad_enabled, bit ignore_enabled + ); + cp_a: coverpoint a; + cp_b: coverpoint b; + illegal_last: cross cp_a, cp_b iff (enabled) { + bins normal = binsof (cp_a); + ignore_bins ignored = binsof (cp_a) intersect {1} iff (ignore_enabled); + illegal_bins forbidden = binsof (cp_a) intersect {1} && binsof (cp_b) intersect { + 1 + } iff (bad_enabled); + } + illegal_first: cross cp_a, cp_b iff (enabled) { + illegal_bins forbidden = binsof (cp_a) intersect {1} && binsof (cp_b) intersect { + 1 + } iff (bad_enabled); + illegal_bins forbidden_again = binsof (cp_a) intersect {1} && binsof (cp_b) intersect { + 1 + } iff (bad_enabled); + ignore_bins ignored = binsof (cp_a) intersect {1} iff (ignore_enabled); + bins normal = binsof (cp_a); + } + endgroup + + covergroup cg_plain with function sample (bit a, bit b); + cp_a: coverpoint a; + cp_b: coverpoint b; + cx: cross cp_a, cp_b{ + bins normal = binsof (cp_a); + ignore_bins ignored = binsof (cp_a) intersect {1}; + illegal_bins forbidden = binsof (cp_a) intersect {1} && binsof (cp_b) intersect {1}; + } + only_illegal: cross cp_a, cp_b{ + illegal_bins forbidden = binsof (cp_a) intersect {1} && binsof (cp_b) intersect {1}; + } + endgroup + + covergroup cg_multi with function sample ( + bit a, bit b, bit enabled, bit bad_enabled, bit ignore_enabled + ); + cp_a: coverpoint a iff (enabled) { + bins zero = {0}; + bins one = {1}; + bins either = {[0 : 1]}; + bins seq = (0 => 1); + } + cp_b: coverpoint b { + bins zero = {0}; + bins one = {1}; + bins either = {[0 : 1]}; + } + cx: cross cp_a, cp_b{ + bins normal = binsof (cp_a); + ignore_bins ignored = binsof (cp_a.one); + illegal_bins forbidden = binsof (cp_a.one) && binsof (cp_b.one); + } + guarded: cross cp_a, cp_b iff (enabled) { + bins normal = binsof (cp_a); + ignore_bins ignored = binsof (cp_a.one) iff (ignore_enabled); + illegal_bins forbidden = binsof (cp_a.one) && binsof (cp_b.one) iff (bad_enabled); + illegal_bins forbidden_seq = binsof (cp_a.seq) iff (bad_enabled); + } + endgroup + + cg_iff iff_cov = new; + cg_plain plain_cov = new; + cg_multi multi_cov = new; + + always @(posedge clk) begin + case (cyc) + 0: iff_cov.sample(1, 1, 0, 1, 1); + 1: iff_cov.sample(1, 1, 1, 0, 1); + 2: iff_cov.sample(1, 1, 1, 1, 0); + 3: iff_cov.sample(1, 1, 1, 1, 1); + 4: iff_cov.sample(1, 1, 1, 0, 0); + 5: iff_cov.sample(0, 0, 1, 1, 1); + 6, 7, 8, 9: plain_cov.sample(1'((cyc - 6) / 2), 1'(cyc)); + 10, 11, 12, 13: multi_cov.sample(1'((cyc - 10) / 2), 1'(cyc), cyc != 13, cyc == 12, 1); + 14: multi_cov.sample(1, 1, 1, 0, 1); + 15: multi_cov.sample(1, 1, 1, 1, 0); + 16: begin +`ifdef VERILATOR + `checkd($c32("Verilated::threadContextp()->errorCount()"), 12); +`endif + `checkr(iff_cov.get_inst_coverage(), 100.0); + `checkr(plain_cov.get_inst_coverage(), 100.0); + `checkr(multi_cov.get_inst_coverage(), 100.0); + $write("*-* All Finished *-*\n"); + $finish; + end + default: `stop; + endcase + ++cyc; + end +endmodule diff --git a/test_regress/t/t_covergroup_cross_protect_ids.py b/test_regress/t/t_covergroup_cross_protect_ids.py new file mode 100755 index 000000000..9146c9033 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_protect_ids.py @@ -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.top_filename = 't/t_covergroup_cross_illegal.v' + +test.compile(verilator_flags2=[ + '--coverage', '--protect-ids', '--protect-key CROSS_BINS_KEY', '-Wno-INSECURE' +], + threads=(2 if test.vltmt else 1)) +test.execute(all_run_flags=['+verilator+error+limit+100']) + +test.passes() diff --git a/test_regress/t/t_covergroup_unsup.out b/test_regress/t/t_covergroup_unsup.out index 25ad77978..297058f01 100644 --- a/test_regress/t/t_covergroup_unsup.out +++ b/test_regress/t/t_covergroup_unsup.out @@ -105,14 +105,14 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:173:50: Unsupported: 'with' in coverage select expression 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins - 176 | ignore_bins ib_cross = binsof(a); - | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:177:7: Unsupported: explicit coverage cross bins - 177 | illegal_bins lib_cross = binsof(a); - | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_covergroup_unsup.v:202:5: Unsupported: covergroup inheritance (extends) - 202 | covergroup extends cg_empty; +%Warning-COVERIGN: t/t_covergroup_unsup.v:175:40: Unsupported: 'with' in coverage select expression + 175 | ignore_bins ib_cross = binsof(a) with (a); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:176:42: Unsupported: 'with' in coverage select expression + 176 | illegal_bins lib_cross = binsof(a) with (a); + | ^~~~ +%Error-UNSUPPORTED: t/t_covergroup_unsup.v:201:5: Unsupported: covergroup inheritance (extends) + 201 | covergroup extends cg_empty; | ^~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_unsup.v b/test_regress/t/t_covergroup_unsup.v index 931a0ce9b..df2989b0a 100644 --- a/test_regress/t/t_covergroup_unsup.v +++ b/test_regress/t/t_covergroup_unsup.v @@ -172,9 +172,8 @@ module t ( bins bin_or_with = binsof(a) || binsof(a) with (a); bins bin_and_with = binsof(a) && binsof(a) with (a); bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); - // explicit cross ignore/illegal bins (unsupported) - ignore_bins ib_cross = binsof(a); - illegal_bins lib_cross = binsof(a); + ignore_bins ib_cross = binsof(a) with (a); + illegal_bins lib_cross = binsof(a) with (a); } endgroup diff --git a/test_regress/t/t_covergroup_unsup_ign2.out b/test_regress/t/t_covergroup_unsup_ign2.out index 3e42d767b..bf3faa158 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -105,12 +105,12 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:173:50: Unsupported: 'with' in coverage select expression 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins - 176 | ignore_bins ib_cross = binsof(a); - | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:177:7: Unsupported: explicit coverage cross bins - 177 | illegal_bins lib_cross = binsof(a); - | ^~~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:175:40: Unsupported: 'with' in coverage select expression + 175 | ignore_bins ib_cross = binsof(a) with (a); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:176:42: Unsupported: 'with' in coverage select expression + 176 | illegal_bins lib_cross = binsof(a) with (a); + | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:98:18: Ignoring unsupported coverage cross option: 'comment' 98 | cross a, b { option.comment = "cross"; option.weight = 12; option.per_instance = 1; } | ^~~~~~ @@ -144,6 +144,12 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:174:7: Unsupported: explicit coverage cross bins 174 | bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:175:7: Unsupported: explicit coverage cross bins + 175 | ignore_bins ib_cross = binsof(a) with (a); + | ^~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins + 176 | illegal_bins lib_cross = binsof(a) with (a); + | ^~~~~~~~~~~~ %Warning-WIDTHTRUNC: t/t_covergroup_unsup.v:76:5: Logical operator COVERPOINT 'a' expects 1 bit on the iff condition, but iff condition's VARREF 'b' generates 32 bits. : ... note: In instance 't' 76 | coverpoint a iff (b); diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 9d9a466cc..103d7a65d 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -974,6 +974,9 @@ module Vt_debug_emitv_t; bins grouped = ((binsof(cp_x.x0) || binsof( cp_y.y0)) && !binsof(cp_x.x1)); + ignore_bins ignored = binsof(cp_x.x0) iff ( + cg_sig[0]); + illegal_bins forbidden = binsof(cp_y.y1); } endfunction int signed __Vint; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index b138c2e9c..d783e1816 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -446,6 +446,8 @@ module t (/*AUTOARG*/ bins either = binsof (cp_x.x0) || binsof (cp_y.y0); bins both = binsof (cp_x.x1) && binsof (cp_y.y1) iff (cg_sig[1]); bins grouped = (binsof (cp_x.x0) || binsof (cp_y.y0)) && !binsof (cp_x.x1); + ignore_bins ignored = binsof (cp_x.x0) iff (cg_sig[0]); + illegal_bins forbidden = binsof (cp_y.y1); } endgroup