diff --git a/bin/verilator b/bin/verilator index f5148e41f..bc76fe86b 100755 --- a/bin/verilator +++ b/bin/verilator @@ -419,6 +419,8 @@ detailed descriptions of these arguments. --coverage-expr-max Maximum permutations allowed for an expression --coverage-fsm Enable FSM state/arc coverage --coverage-line Enable line coverage + --coverage-max-bins Maximum bins of an integral coverpoint bins declaration + --coverage-max-real-bins Maximum bins of a real coverpoint bins declaration --coverage-max-width Maximum array depth for coverage --coverage-per-instance Enable per-instance coverage counters --coverage-toggle Enable toggle coverage diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index fd5867354..2798963b0 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -316,6 +316,33 @@ Summary: Enables basic block line coverage analysis. See :ref:`Line Coverage`. +.. option:: --coverage-max-bins + + Rarely needed. Specifies the maximum number of bins one covergroup bins + declaration of an integral coverpoint may create: an array of bins such + as ``bins b[] = {[0:$]}``, automatic bins ``bins auto[N]``, or the + automatic bins of a coverpoint from ``option.auto_bin_max``. Defaults to + 1024, and may be at most 4294967295 (``2**32 - 1``). + + A larger array of bins is ignored with a :option:`COVERIGN` warning, a + larger ``bins auto[N]`` is an error, and a larger + ``option.auto_bin_max`` is reduced to this limit with a + :option:`COVERIGN` warning. Increasing the limit increases the memory + the model uses for the bins. See also + :vlopt:`--coverage-max-real-bins`. + +.. option:: --coverage-max-real-bins + + Rarely needed. Specifies the maximum number of values of one array of + bins of a coverpoint of a ``real`` expression, such as + ``bins b[] = {[1:10]}``, each of which is a bin compared separately. + Defaults to 1024, and may be at most 4294967295 (``2**32 - 1``). + + A larger array of bins is ignored with a :option:`COVERIGN` warning. + Increasing the limit increases the size of the generated code, which + grows with the number of these bins. See also + :vlopt:`--coverage-max-bins`. + .. option:: --coverage-max-width Rarely needed. Specify the maximum bit width of a signal subject to diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index 0f3cff09e..c242a7236 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -140,6 +140,12 @@ struct VlCoverpoint::ValueData final { } value.back() &= VL_MASK_E(m_bits); } + // Add in m_bits-wide modular arithmetic, which orders correctly within a run of bins + void add(Value& value, const Value& addend) const { + VL_ADD_W(static_cast(m_words), WDataOutP::external(value.data()), view(value), + view(addend)); + value.back() &= VL_MASK_E(m_bits); + } bool contains(const Range& range, WDataInP value) const { if (less(value, range.m_lo) || less(range.m_hi, value)) return false; if (!range.m_mask.empty()) { @@ -441,6 +447,43 @@ void VlCoverpoint::valueRanges(std::initializer_list entries) { } } +void VlCoverpoint::valueRuns(std::initializer_list entries) { + // The compiler describes each run of bins with one entry, rather than one per bin, so the + // constructor's code does not grow with the number of bins. An entry holds the first bin + // and the bin count, then the low, span, and high values, each of 'words' words. + static constexpr uint32_t HEADER_WORDS = 2; // First bin and bin count, before the values + static constexpr uint32_t VALUES = 3; // Low, span, and high values + ValueData& data = *m_valuesp; + assert(!data.m_frozen); + const uint32_t words = data.m_words; + const uint32_t entryWords = HEADER_WORDS + VALUES * words; + const EData* const endp = entries.end(); + for (const EData* entryp = entries.begin(); entryp != endp; entryp += entryWords) { + assert(static_cast(endp - entryp) >= entryWords); // Only whole entries + const uint32_t first = entryp[0]; + const uint32_t count = entryp[1]; + const EData* const valuesp = entryp + HEADER_WORDS; + ValueData::Value lo = data.read(WDataInP::external(valuesp)); + const ValueData::Value span = data.read(WDataInP::external(valuesp + words)); + const ValueData::Value hi = data.read(WDataInP::external(valuesp + 2 * words)); + // Expand the run into the value range of each of its bins, as valueRanges() gives them: + // until valueRelease(), valueFinalize() finds the bins exclusions leave without values, + // and runtime cross selections intersect their filters, from these ranges. Each bin + // starts after the previous bin's last value and holds span + 1 values, except the last + // bin, which extends to the run's high value. + for (uint32_t k = 0; k < count; ++k) { + ValueData::Value last = hi; + if (k + 1 < count) { + last = lo; + data.add(last, span); + } + data.m_values[first + k].m_ranges.push_back({lo, last, {}}); + lo = last; + data.increment(lo); + } + } +} + void VlCoverpoint::valuePatterns(std::initializer_list entries) { ValueData& data = *m_valuesp; assert(!data.m_frozen); @@ -566,7 +609,11 @@ const VlCovNamer& VlCoverpoint::namerFor(uint32_t i) const { std::string VlCoverpoint::declaredBinName(uint32_t bin) const { const VlCovNamer& nm = namerFor(bin); std::string name = nm.name(); - if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(bin - nm.base()) + ']'; + if (nm.naming() == VlCovBinNaming::Array) { + name += '[' + std::to_string(bin - nm.base()) + ']'; + } else if (nm.naming() == VlCovBinNaming::Numbered) { + name += '_' + std::to_string(bin - nm.base()); + } return name; } diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index 69b031b62..e99d75208 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -50,6 +50,7 @@ class VerilatedCovContext; enum class VlCovBinNaming : uint8_t { Single, // "" one bin Array, // "[i]" bins b[N] value array + Numbered, // "_" automatic bins of a coverpoint without bins }; // Specifies the naming scheme for a range of bins, allowing the @@ -145,12 +146,20 @@ public: int line, int col) { addNamer(set, count, VlCovBinNaming::Array, name, file, line, col); } + void addNumberedNamer(VlCovBinKind set, uint32_t count, const char* name, const char* file, + int line, int col) { + addNamer(set, count, VlCovBinNaming::Numbered, name, file, line, col); + } void registerBins(VerilatedCovContext* covcontextp, const char* page); /// Configure construction-time value metadata for exclusions and cross selections. void valueType(uint32_t bits, bool isSigned); /// Describe bin values as {bin, low words, high words} entries, without enumerating them. void valueRanges(std::initializer_list entries); + /// Describe runs of bins as {first bin, count, low words, span words, high words} entries: + /// bin k of a run holds [low + k * (span + 1), low + k * (span + 1) + span], and its last + /// bin extends to high. + void valueRuns(std::initializer_list entries); /// Describe wildcard patterns as {bin, value words, mask words, low words, high words}. void valuePatterns(std::initializer_list entries); /// State exclusions do not remove values from these transition bins. diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 9b36f3229..2646ad446 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -875,6 +875,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_ADD_COVERPOINT, "addCoverpoint", false, "") \ macro(COVERGROUP_ADD_CROSS, "addCross", false, "") \ macro(COVERGROUP_ADD_CROSS_DYN, "addCrossDyn", false, "") \ + macro(COVERGROUP_ADD_NUMBERED_NAMER, "addNumberedNamer", false, "r+") \ macro(COVERGROUP_ADD_SINGLE_NAMER, "addSingleNamer", false, "r+") \ macro(COVERGROUP_ATTACH, "attach", false, "r") \ macro(COVERGROUP_CLEAR_HIT_LIST, "clearHitList", false, "") \ @@ -903,6 +904,7 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_VALUE_PATTERNS, "valuePatterns", false, "r") \ macro(COVERGROUP_VALUE_RANGES, "valueRanges", false, "r") \ macro(COVERGROUP_VALUE_RELEASE, "valueRelease", false, "") \ + macro(COVERGROUP_VALUE_RUNS, "valueRuns", false, "r") \ macro(COVERGROUP_VALUE_TRANSITIONS, "valueTransitions", false, "r") \ macro(COVERGROUP_VALUE_TYPE, "valueType", false, "rr") \ macro(COVERGROUP_WEIGHT, "weight", false, "rr") \ @@ -1181,7 +1183,8 @@ class VCoverBinsType final { public: enum en : uint8_t { BINS_ARRAY, // Array of bins with user-speciifed size - BINS_AUTO, // Auto-sized array of bins (eg auto_bin_max) + BINS_AUTO, // Automatic bins of a 'bins auto[N]' declaration + BINS_AUTO_IMPLICIT, // Automatic bins of a coverpoint without bins (IEEE 1800-2023 19.5.3) BINS_DEFAULT, // Default bin BINS_IGNORE, // Ignore bin BINS_ILLEGAL, // Illegal bin @@ -1197,8 +1200,9 @@ public: : m_e{_e} {} constexpr operator en() const { return m_e; } // LCOV_EXCL_LINE const char* ascii() const { - static const char* const names[] = {"array", "auto", "default", "ignore_bins", - "illegal_bins", "transition", "bins", "wildcard"}; + static const char* const names[] + = {"array", "auto", "auto_implicit", "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 52d53a57e..ee8d701eb 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1091,10 +1091,11 @@ public: this->iffp(iffp); } // Constructor for automatic bins - AstCoverBin(FileLine* fl, const string& name, AstNodeExpr* arraySizep) + AstCoverBin(FileLine* fl, const string& name, AstNodeExpr* arraySizep, + VCoverBinsType type = VCoverBinsType::BINS_AUTO) : ASTGEN_SUPER_CoverBin(fl) , m_name{name} - , m_binsType{VCoverBinsType::BINS_AUTO} + , m_binsType{type} , m_isArray{true} { this->arraySizep(arraySizep); } diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 25db6d73c..1017f3246 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -195,6 +196,31 @@ public: void scan(AstNode* nodep) { iterate(nodep); } }; +//###################################################################### +// Bins of one declaration whose values are computed rather than listed: bin k covers +// [m_lo + k * m_stride, m_lo + (k + 1) * m_stride - 1], and the last bin extends to m_hi. An +// array bin element is a run of single-value bins; automatic bins partition the coverpoint +// domain. Bounds are coverpoint values at FunctionalCoverageVisitor::runWidth(), +// sign-extended like a CrossValueRange's. + +class BinRun final { +public: + // MEMBERS + uint32_t m_count; // Number of bins + V3Number m_lo; // Lowest value of the first bin + V3Number m_stride; // Number of values of each bin but the last + V3Number m_hi; // Highest value of the last bin + bool m_empty = false; // A single bin without a value of the coverpoint type + uint32_t m_declared = 0; // Runtime index of the first bin, once generated + + // CONSTRUCTORS + BinRun(AstNode* nodep, int width, uint32_t count) + : m_count{count} + , m_lo{nodep, width} + , m_stride{nodep, width, 1} + , m_hi{nodep, width} {} +}; + //###################################################################### // Functional coverage visitor @@ -236,9 +262,16 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::set m_crossedCpNames; // Coverpoints referenced by a cross std::map m_cpVarMap; // Coverpoint name -> its VlCoverpoint member + struct BinRuns final { + std::vector runs; // Runs of an array or automatic bins declaration, in order + uint32_t count = 0; // Bins across all runs + bool unsupported = false; // Too many bins, or invalid: the declaration is ignored + }; struct CrossBinValues final { AstCoverBin* binp; // Declaration owning this Normal bin AstNodeExpr* valuep; // Individual array-bin value, or nullptr for a scalar bin + const BinRun* runp = nullptr; // Run computing the bin's values, if any + uint32_t element = 0; // Index of the bin within runp }; struct BinSpan final { uint32_t first; // First Normal index of the bin declaration @@ -248,8 +281,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { struct CoverpointBins final { uint32_t total = 0; // Number of Normal bins AstNodeExpr* exprp = nullptr; // Sampled expression, for the value domain + bool crossed = false; // Feeds a cross, which needs 'values' std::vector values; // Values in runtime Normal-bin index order std::unordered_map spans; // Declared bin name -> index span + BinSpan implicitAuto{0, 0, 0}; // Implicit automatic bins, each named 'auto_' + std::deque runs; // Runs 'values' refers to }; std::map m_cpBins; // Runtime coverpoint -> binsof index ranges std::vector m_detachedValues; // Array-bin values m_cpBins refers to @@ -416,93 +452,59 @@ class FunctionalCoverageVisitor final : public VNVisitor { generateCoverageComputationCode(); } - static constexpr int COVER_BINS_LIMIT - = 1000; // Sanity limit to avoid hangs from e.g. signed underflow + // Limit on the bins of one array or automatic bins declaration of an integral coverpoint, + // and on the automatic bins of option.auto_bin_max: --coverage-max-bins, like other + // simulators' limits. It guards against hangs from e.g. signed underflow. Such bins + // generate as runs, whose code size does not depend on their number. + static uint32_t binsLimit() { return v3Global.opt.coverageMaxBins(); } + // Limit on the values of one array bins declaration of a real coverpoint, which generates a + // comparison per value (see extractArrayValues): --coverage-max-real-bins + static uint32_t realBinsLimit() { return v3Global.opt.coverageMaxRealBins(); } static constexpr size_t VALUE_LIST_ENTRIES = 256; // Metadata entries per constructor call - void expandAutomaticBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp) { - // Find and expand any automatic bins - AstNode* prevBinp = nullptr; - for (AstNode* binp = coverpointp->binsp(); binp;) { + // The number of bins a constant array size requests: -1 if it is negative, and saturated + // above the largest limit + static int64_t binsCount(const AstConst* constp) { + const V3Number& num = constp->num(); + if (constp->isSigned() && num.isNegative()) return -1; + return num.mostSetBitP1() > 32 ? INT64_MAX : static_cast(num.toUQuad()); + } + + // The number of bins requested by a valid 'bins auto[N]', or 0 + static uint32_t autoBinsRequested(const AstCoverBin* binp) { + const AstConst* const constp = VN_CAST(binp->arraySizep(), Const); + if (!constp) return 0; + const int64_t count = binsCount(constp); + return count < 1 || count > binsLimit() ? 0 : static_cast(count); + } + + // True for a 'bins auto[N]' declaration, or the implicit automatic bins of a coverpoint + static bool isAutoBins(const AstCoverBin* binp) { + return binp->binsType() == VCoverBinsType::BINS_AUTO + || binp->binsType() == VCoverBinsType::BINS_AUTO_IMPLICIT; + } + + // Check the automatic bins declarations of a coverpoint. Each stays one declaration, which + // generates as a partition of the coverpoint domain (see autoBinRuns). + void checkAutomaticBins(AstCoverpoint* coverpointp, const AstNodeExpr* exprp) { + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - AstNode* const nextBinp = binp->nextp(); - - if (cbinp->binsType() == VCoverBinsType::BINS_AUTO) { - UINFO(4, " Expanding automatic bin: " << cbinp->name()); - - // Get array size - must be a constant - AstNodeExpr* const sizep = cbinp->arraySizep(); - - // Evaluate as constant - const AstConst* constp = VN_CAST(sizep, Const); - if (!constp) { - cbinp->v3error("Automatic bins array size must be a constant"); - binp = nextBinp; - continue; - } - - const int numBins = constp->toSInt(); - if (numBins <= 0) { - cbinp->v3error("Automatic bins array size must be >= 1, got " << numBins); - binp = nextBinp; - continue; - } - if (numBins > COVER_BINS_LIMIT) { - cbinp->v3error("Automatic bins array size of " - << numBins << " exceeds limit of " << COVER_BINS_LIMIT); - binp = nextBinp; - continue; - } - - // Calculate range division - const int width = exprp->width(); - const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); - // For width >= 64: (maxVal+1) would overflow; compute binSize without overflow - const uint64_t binSize - = (width < 64) ? ((maxVal + 1) / numBins) : (UINT64_MAX / numBins + 1); - - UINFO(4, " Width=" << width << " maxVal=" << maxVal << " numBins=" << numBins - << " binSize=" << binSize); - - // Create expanded bins - for (int i = 0; i < numBins; i++) { - const uint64_t lo = static_cast(i) * binSize; - const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1); - - // Create constants for range (use setQuad to handle values > 32-bit) - V3Number loNum{cbinp->fileline(), width, 0}; - loNum.setQuad(lo); - AstConst* const loConstp = new AstConst{cbinp->fileline(), loNum}; - V3Number hiNum{cbinp->fileline(), width, 0}; - hiNum.setQuad(hi); - AstConst* const hiConstp = new AstConst{cbinp->fileline(), hiNum}; - - // Create InsideRange [lo:hi] - AstInsideRange* const rangep - = new AstInsideRange{cbinp->fileline(), loConstp, hiConstp}; - rangep->dtypeFrom(exprp); // Set dtype from coverpoint expression - - // Create new bin - const string binName = cbinp->name() + "[" + std::to_string(i) + "]"; - AstCoverBin* const newBinp - = new AstCoverBin{cbinp->fileline(), binName, rangep, false, false}; - - // Insert after previous bin - if (prevBinp) { - prevBinp->addNext(newBinp); - } else { - coverpointp->addBinsp(newBinp); - } - prevBinp = newBinp; - } - - // Remove the AUTO bin from the list - VL_DO_DANGLING(pushDeletep(binp->unlinkFrBack()), binp); - } else { - prevBinp = binp; + if (cbinp->binsType() != VCoverBinsType::BINS_AUTO) continue; + const AstConst* const constp = VN_CAST(cbinp->arraySizep(), Const); + if (!constp) { + cbinp->v3error("Automatic bins array size must be a constant"); + } else if (binsCount(constp) < 1) { + cbinp->v3error("Automatic bins array size must be >= 1, got " + << constp->num().toDecimalS()); + } else if (binsCount(constp) > binsLimit()) { + cbinp->v3error("Automatic bins array size of " + << constp->num().toDecimalU() << " exceeds limit of " << binsLimit() + << '\n' + << cbinp->warnMore() << "... Suggest a larger --coverage-max-bins"); + } else if (!exprp->dtypep()->skipRefp()->isIntegralOrPacked()) { + cbinp->v3error("Automatic bins are not allowed on a coverpoint of a non-integral " + "expression (IEEE 1800-2023 19.5.3)."); } - - binp = nextBinp; } } @@ -545,7 +547,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { } } - // IEEE 1800-2023 19.5.3/19.11.1: partition first, then apply exclusions. // IEEE 1800-2023 19.5.2: an enum coverpoint has one automatic bin per enumeration value void createEnumAutoBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp, const AstEnumDType* enump) { @@ -560,6 +561,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { } } + // IEEE 1800-2023 19.5.3/19.11.1: partition first, then apply exclusions. The partition is one + // automatic bins declaration, generated as a run like 'bins auto[N]' but numbering its bins. void createImplicitAutoBins(AstCoverpoint* coverpointp, AstNodeExpr* exprp, int autoBinMax) { for (AstNode* nodep = coverpointp->binsp(); nodep; nodep = nodep->nextp()) { const VCoverBinsType kind = VN_AS(nodep, CoverBin)->binsType(); @@ -572,43 +575,27 @@ class FunctionalCoverageVisitor final : public VNVisitor { return; } const int width = exprp->width(); - const int arithmeticWidth = width + 1; - V3Number total{coverpointp, arithmeticWidth}; - total.setBit(width, 1); - const uint32_t count = width < 31 ? std::min(uint32_t{1} << width, autoBinMax) - : static_cast(autoBinMax); + uint32_t count = width < 31 ? std::min(uint32_t{1} << width, autoBinMax) + : static_cast(autoBinMax); if (!count) return; - V3Number divisor{coverpointp, arithmeticWidth, count}; - V3Number stride{coverpointp, arithmeticWidth}; - stride.opDiv(total, divisor); - const CrossValueRange domain - = crossValueDomain(coverpointp, width, exprp->isSigned(), arithmeticWidth); - const V3Number one{coverpointp, arithmeticWidth, 1}; - for (uint32_t bin = 0; bin < count; ++bin) { - const V3Number ordinal{coverpointp, arithmeticWidth, bin}; - const V3Number nextOrdinal{coverpointp, arithmeticWidth, bin + 1}; - V3Number low{coverpointp, arithmeticWidth}; - V3Number high{coverpointp, arithmeticWidth}; - low.opMul(stride, ordinal); - if (bin + 1 == count) { - high = total; - } else { - high.opMul(stride, nextOrdinal); - } - V3Number adjusted{coverpointp, arithmeticWidth}; - adjusted.opSub(high, one); - high = adjusted; - adjusted.opAdd(low, domain.lo); - low = adjusted; - adjusted.opAdd(high, domain.lo); - high = adjusted; - AstConst* const lop = newValueConst(coverpointp->fileline(), low, exprp); - AstConst* const hip = newValueConst(coverpointp->fileline(), high, exprp); - AstInsideRange* const rangep = new AstInsideRange{coverpointp->fileline(), lop, hip}; - rangep->dtypeFrom(exprp); - coverpointp->addBinsp(new AstCoverBin{coverpointp->fileline(), "auto_" + cvtToStr(bin), - rangep, false, false}); + if (!exprp->dtypep()->skipRefp()->isIntegralOrPacked()) { + coverpointp->v3error("Coverpoint of a non-integral expression requires explicit bins " + "(IEEE 1800-2023 19.5.3)."); + return; } + if (count > binsLimit()) { + coverpointp->v3warn(COVERIGN, "Unsupported: more than " + << binsLimit() + << " automatic bins from 'option.auto_bin_max'; " + "using " + << binsLimit() << ".\n" + << coverpointp->warnMore() + << "... Suggest a larger --coverage-max-bins"); + count = binsLimit(); + } + FileLine* const fl = coverpointp->fileline(); + coverpointp->addBinsp(new AstCoverBin{fl, "auto", new AstConst{fl, count}, + VCoverBinsType::BINS_AUTO_IMPLICIT}); } // Sanitize generated names to be valid C++ identifiers @@ -692,8 +679,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Get the coverpoint expression AstNodeExpr* exprp = coverpointp->exprp(); - // Expand automatic bins before processing - expandAutomaticBins(coverpointp, exprp); + // Check automatic bins before processing + checkAutomaticBins(coverpointp, exprp); // Extract all coverpoint options in a single pass int atLeastValue; @@ -733,6 +720,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { || cbinp->binsType() == VCoverBinsType::BINS_IGNORE || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) continue; + if (isAutoBins(cbinp)) { + // Automatic bins partition the whole domain, leaving no default value + if (anyBinMatchp) VL_DO_DANGLING(pushDeletep(anyBinMatchp), anyBinMatchp); + return new AstConst{fl, AstConst::BitFalse{}}; + } AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); UASSERT_OBJ(binCondp, cbinp, "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); @@ -908,15 +900,20 @@ class FunctionalCoverageVisitor final : public VNVisitor { return true; } - // Append one Normal bin's cross-slot interval-sets to `bins` and bump `slotCount` by the - // number of cross slots the bin contributes. Returns false if any part isn't statically - // enumerable (the caller then falls back to the always-safe Normal-slot count). A non-array - // bin is one slot covering the union of its intervals; an array bin contributes one - // single-value slot per element value (mirroring how it lowers to b[0]..b[N-1]). - bool appendBinCrossSlots(AstCoverBin* cbinp, uint64_t maxVal, + // Append one Normal bin's cross-slot interval-sets to `bins` and bump `slotCount` by an upper + // bound of the bin's slots holding one value. Returns false if any part isn't statically + // enumerable (the caller then falls back to the always-safe slot count). A non-array bin + // is one slot covering the union of its intervals; the bins of an array element or of an + // automatic bins declaration hold disjoint values, so the element or declaration counts once. + bool appendBinCrossSlots(AstCoverBin* cbinp, uint64_t maxVal, AstNodeExpr* exprp, std::vector>>& bins, int& slotCount) { - if (cbinp->isArray()) return appendArrayBinCrossSlots(cbinp, bins, slotCount); + if (isAutoBins(cbinp)) { + ++slotCount; + bins.push_back({{0, maxVal}}); + return exprp->width() <= 64; + } + if (cbinp->isArray()) return appendArrayBinCrossSlots(cbinp, exprp, bins, slotCount); // Non-array bin: one slot covering the union of its intervals. ++slotCount; std::vector> ivs; @@ -925,27 +922,32 @@ class FunctionalCoverageVisitor final : public VNVisitor { return true; } - // Append the cross slots of an array Normal bin: each element value is its own single-value - // Normal bin. '$'-bounded or non-constant elements can't be enumerated, so they count one - // slot but lose exactness. Returns false if any element wasn't enumerable to exact values. - bool appendArrayBinCrossSlots(AstCoverBin* cbinp, + // Append the cross slots of an array Normal bin: each element is a slot covering its + // values, as at most one of its single-value bins holds a value. An element holds the + // values arrayBinRuns() gives it: those of the coverpoint type (IEEE 1800-2023 19.5.7). + // Elements of a signed coverpoint, non-constant elements, and elements with values beyond + // 64 bits can't be enumerated, so they count one slot but lose exactness. Returns false if + // any element wasn't enumerable to exact values. + bool appendArrayBinCrossSlots(AstCoverBin* cbinp, AstNodeExpr* exprp, std::vector>>& bins, int& slotCount) { - bool exact = true; + // Signed values resolve sign-extended, not as unsigned intervals + bool exact = !exprp->isSigned(); for (AstNode* rp = cbinp->rangesp(); rp; rp = rp->nextp()) { + ++slotCount; RangeBounds rb; - if (!constRangeBounds(rp, rb) || rb.loUnbounded() || rb.hiUnbounded()) { - ++slotCount; + CrossValueRange range{rp, resolveWidth(rp, exprp)}; + if (!exact || !constRangeBounds(rp, rb) + || !resolveValue(rp, exprp, true, false, range)) { exact = false; - } else if (rb.loNodep == rb.hiNodep) { // single Const element (both alias one node) - ++slotCount; - bins.push_back({{rb.loConstp()->toUQuad(), rb.loConstp()->toUQuad()}}); - } else { // [lo:hi] range: one single-value slot per enumerated value - for (int64_t v = rb.loConstp()->toSInt(); v <= rb.hiConstp()->toSInt(); ++v) { - ++slotCount; - bins.push_back({{static_cast(v), static_cast(v)}}); - } + continue; } + if (crossRangeEmpty(range)) continue; // Its bin, if any, holds no value + if (range.hi.mostSetBitP1() > 64) { + exact = false; + continue; + } + bins.push_back({{range.lo.toUQuad(), range.hi.toUQuad()}}); } return exact; } @@ -967,7 +969,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstCoverBin* const cbinp = VN_AS(binp, CoverBin); if (!cbinp->binsType().binIsNormal()) continue; // ignore/illegal/default: not hit-listed - if (!appendBinCrossSlots(cbinp, maxVal, bins, slotCount)) exact = false; + if (!appendBinCrossSlots(cbinp, maxVal, exprp, bins, slotCount)) exact = false; } if (!exact) return std::max(1, slotCount); if (bins.empty()) return 1; @@ -1039,11 +1041,37 @@ class FunctionalCoverageVisitor final : public VNVisitor { + std::to_string(fl->lineno()) + "}"); } - // Individual equality targets of an array bin (bins b[] = {values/ranges}), in order. + // Check that an element of an array bin (bins b[] = {values/ranges}) is a two-state + // constant value or range; false if not, after reporting it. + static bool checkArrayBinElement(AstCoverBin* arrayBinp, AstNode* rangep) { + if (const AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { + const AstConst* const minp = VN_CAST(irp->lhsp(), Const); + const AstConst* const maxp = VN_CAST(irp->rhsp(), Const); + if ((!minp && !VN_IS(irp->lhsp(), Unbounded)) + || (!maxp && !VN_IS(irp->rhsp(), Unbounded))) { + arrayBinp->v3error("Non-constant expression in array bins range; " + "range bounds must be constants (IEEE 1800-2023 19.5)"); + return false; + } + if ((minp && minp->num().isFourState()) || (maxp && maxp->num().isFourState())) { + arrayBinp->v3error("Four-state (x/z) value in array bins range bound; " + "range bounds must be two-state constants"); + return false; + } + } else if (!VN_IS(rangep, Const)) { + arrayBinp->v3error("Non-constant expression in array bins value list; " + "values must be constants (IEEE 1800-2023 19.5)"); + return false; + } + return true; + } + + // Individual equality targets of an array bin (bins b[] = {values/ranges}) of a real + // coverpoint, in order; integral coverpoints generate array bins as runs (see arrayBinRuns). // An open-ended bound ('$', AstUnbounded) resolves to the coverpoint domain: '[lo:$]' - // covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; a - // range whose resolved size would exceed COVER_BINS_LIMIT (e.g. an open '[lo:$]' over a - // wide coverpoint) is unsupported -- emits COVERIGN, sets unsupportedOut, yields nothing. + // covers [lo:maxVal] and '[$:hi]' covers [0:hi]. One target is produced per value; ranges + // whose resolved size would exceed --coverage-max-real-bins (e.g. an open '[lo:$]') are + // unsupported -- emits COVERIGN, sets unsupportedOut, yields nothing. std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp, bool& unsupportedOut) { unsupportedOut = false; @@ -1052,34 +1080,22 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::vector values; for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { rangep = V3Const::constifyEdit(rangep); + if (!checkArrayBinElement(arrayBinp, rangep)) return values; if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { - AstNodeExpr* const lhsp = irp->lhsp(); - AstNodeExpr* const rhsp = irp->rhsp(); - const bool loUnb = VN_IS(lhsp, Unbounded); - const bool hiUnb = VN_IS(rhsp, Unbounded); - AstConst* const minp = VN_CAST(lhsp, Const); - AstConst* const maxp = VN_CAST(rhsp, Const); - if ((!minp && !loUnb) || (!maxp && !hiUnb)) { - arrayBinp->v3error("Non-constant expression in array bins range; " - "range bounds must be constants (IEEE 1800-2023 19.5)"); - return values; - } - if ((minp && minp->num().isFourState()) || (maxp && maxp->num().isFourState())) { - arrayBinp->v3error("Four-state (x/z) value in array bins range bound; " - "range bounds must be two-state constants"); - return values; - } - const uint64_t lo = loUnb ? 0 : minp->toUQuad(); - const uint64_t hi = hiUnb ? maxVal : maxp->toUQuad(); + const bool loUnb = VN_IS(irp->lhsp(), Unbounded); + const bool hiUnb = VN_IS(irp->rhsp(), Unbounded); + const uint64_t lo = loUnb ? 0 : VN_AS(irp->lhsp(), Const)->toUQuad(); + const uint64_t hi = hiUnb ? maxVal : VN_AS(irp->rhsp(), Const)->toUQuad(); if (hi < lo) continue; // empty range contributes no bins // Guard against a '$'-bounded or otherwise huge range exploding the bin count. const uint64_t span = hi - lo; // == valueCount - 1 (no overflow: hi >= lo) - if (span >= static_cast(COVER_BINS_LIMIT) - || values.size() + span + 1 > static_cast(COVER_BINS_LIMIT)) { - arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than " - << COVER_BINS_LIMIT - << " values (e.g. an open '[lo:$]' range over " - "a wide coverpoint); bin ignored"); + if (span >= realBinsLimit() || values.size() + span + 1 > realBinsLimit()) { + arrayBinp->v3warn(COVERIGN, + "Unsupported: array 'bins' of a real coverpoint " + "covering more than " + << realBinsLimit() << " values; bin ignored.\n" + << arrayBinp->warnMore() + << "... Suggest a larger --coverage-max-real-bins"); unsupportedOut = true; for (AstNodeExpr* const vp : values) VL_DO_DANGLING(pushDeletep(vp), vp); values.clear(); @@ -1088,28 +1104,122 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (uint64_t v = lo; v <= hi; ++v) values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, width, static_cast(v)}); - } else if (VN_IS(rangep, Const)) { - values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); } else { - arrayBinp->v3error("Non-constant expression in array bins value list; " - "values must be constants (IEEE 1800-2023 19.5)"); - return values; + values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); } } return values; } + static int runWidth(const AstNodeExpr* exprp) { return exprp->width() + 1; } + + // Automatic bins partition the coverpoint domain in value order (IEEE 1800-2023 19.5.3): N + // bins, capped at the number of values, each hold 2^width / N values, and the last bin also + // holds the remainder. False for an invalid declaration, already reported. + bool autoBinRuns(AstCoverBin* binp, AstNodeExpr* exprp, BinRuns& out) { + const uint32_t requested = autoBinsRequested(binp); + if (!requested || !exprp->dtypep()->skipRefp()->isIntegralOrPacked()) return false; + const int width = exprp->width(); + const int arithmeticWidth = runWidth(exprp); + const uint32_t count + = width < 32 + ? static_cast(std::min(uint64_t{1} << width, requested)) + : requested; + BinRun run{binp, arithmeticWidth, count}; + V3Number total{binp, arithmeticWidth}; + total.setBit(width, 1); + run.m_stride.opDiv(total, V3Number{binp, arithmeticWidth, count}); + const CrossValueRange domain + = crossValueDomain(binp, width, exprp->isSigned(), arithmeticWidth); + run.m_lo = domain.lo; + run.m_hi = domain.hi; + out.runs.push_back(std::move(run)); + out.count = count; + return true; + } + + // The elements of an array bin (bins b[] = {values/ranges}), in order, as runs of + // single-value bins. A range holds the values of the coverpoint type it contains (IEEE + // 1800-2023 19.5.7), while a singleton names a bin even without such a value. Errors on a + // non-constant element. More than --coverage-max-bins bins (e.g. an open '[lo:$]' range over + // a wide coverpoint) are unsupported -- emits COVERIGN, and sets unsupported. + BinRuns arrayBinRuns(AstCoverBin* arrayBinp, AstNodeExpr* exprp) { + BinRuns out; + const int width = runWidth(exprp); + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + rangep = V3Const::constifyEdit(rangep); + if (!checkArrayBinElement(arrayBinp, rangep)) return out; + const AstInsideRange* const irp = VN_CAST(rangep, InsideRange); + CrossValueRange range{rangep, resolveWidth(rangep, exprp)}; + bool empty = true; + if (!resolveValue(rangep, exprp, true, false, range)) { + rangep->v3warn(E_UNSUPPORTED, "Unsupported: non-integral value in a coverage bin " + "of an integral coverpoint."); + } else { + empty = crossRangeEmpty(range); + } + if (empty && irp) continue; // A range without values contributes no bins + BinRun run{rangep, width, 1}; + run.m_empty = empty; + uint64_t count = 1; + if (!empty) { + run.m_lo.opAssign(range.lo); + run.m_hi.opAssign(range.hi); + V3Number span{rangep, width}; + span.opSub(run.m_hi, run.m_lo); + // Wider spans exceed any limit + count = span.mostSetBitP1() > 32 ? UINT64_MAX : span.toUQuad() + 1; + } + if (count > binsLimit() - out.count) { + arrayBinp->v3warn(COVERIGN, "Unsupported: array 'bins' covering more than " + << binsLimit() + << " values (e.g. an open '[lo:$]' range over " + "a wide coverpoint); bin ignored\n" + << arrayBinp->warnMore() + << "... Suggest a larger --coverage-max-bins"); + out.runs.clear(); + out.count = 0; + out.unsupported = true; + return out; + } + run.m_count = static_cast(count); + out.count += run.m_count; + out.runs.push_back(std::move(run)); + } + return out; + } + + // The runs of an automatic bins declaration, or of an array bin of an integral coverpoint. + // False for other bins, which do not generate as runs. + bool binRunsFor(AstCoverBin* binp, AstNodeExpr* exprp, BinRuns& out) { + if (isAutoBins(binp)) { + if (!autoBinRuns(binp, exprp, out)) out.unsupported = true; + return true; + } + if (!binp->isArray() || binp->transp() || binp->isWildcard() + || !exprp->dtypep()->skipRefp()->isIntegralOrPacked()) { + return false; + } + out = arrayBinRuns(binp, exprp); + return true; + } + // Emit a 'this->m_cp->addSingleNamer/addArrayNamer(...)' statement for one bin whose first // runtime bin index is 'declared' - AstNodeStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count, uint32_t declared, + AstNodeStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int64_t count, uint32_t declared, const std::vector& values = {}) { FileLine* const fl = binp->fileline(); CoverpointBins& bins = m_cpBins.at(cpVarp); const uint32_t normalCount = binp->binsType().binIsNormal() ? static_cast(count < 0 ? 1 : count) : 0; - bins.spans.emplace(binp->name(), BinSpan{bins.total, normalCount, declared}); + const BinSpan span{bins.total, normalCount, declared}; + if (binp->binsType() == VCoverBinsType::BINS_AUTO_IMPLICIT) { + bins.implicitAuto = span; // Selected by bin, see implicitAutoBinSpan + } else { + bins.spans.emplace(binp->name(), span); + } bins.total += normalCount; - for (uint32_t i = 0; i < normalCount; ++i) { + for (uint32_t i = 0; bins.crossed && i < normalCount; ++i) { bins.values.push_back({binp, values.empty() ? nullptr : values[i]}); } // Under --protect-ids the filename and bin name flow into the coverage database @@ -1125,7 +1235,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { args.push_back(cnum(fl, static_cast(fl->firstColumn()))); return itemCall(fl, cpVarp, single ? VCMethod::COVERGROUP_ADD_SINGLE_NAMER - : VCMethod::COVERGROUP_ADD_ARRAY_NAMER, + : binp->binsType() == VCoverBinsType::BINS_AUTO_IMPLICIT + ? VCMethod::COVERGROUP_ADD_NUMBERED_NAMER + : VCMethod::COVERGROUP_ADD_ARRAY_NAMER, args) ->makeStmt(); } @@ -1134,24 +1246,29 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Where a bin's hit is recorded in the runtime VlCoverpoint member. struct ConvBinTarget final { AstVar* cpVarp; // the __Vcp_ member - int idx; // bin index within that coverpoint + uint32_t idx; // bin index within that coverpoint bool isNormal; // Normal -> incrementBin (count + cross hit list); else recordHit (count) }; // Emit 'this->m_cp.incrementBin(idx);' (Normal) or '.recordHit(idx);' // (ignore/illegal/default). - AstNodeStmt* makeRuntimeBinHit(FileLine* fl, const ConvBinTarget& tgt) { - return itemCall(fl, tgt.cpVarp, - tgt.isNormal ? VCMethod::COVERGROUP_INCREMENT_BIN - : VCMethod::COVERGROUP_RECORD_HIT, - {cnum(fl, static_cast(tgt.idx))}) + AstNodeStmt* makeRuntimeBinHit(FileLine* fl, AstVar* cpVarp, AstNodeExpr* idxp, + bool isNormal) { + return itemCall(fl, cpVarp, + isNormal ? VCMethod::COVERGROUP_INCREMENT_BIN + : VCMethod::COVERGROUP_RECORD_HIT, + {idxp}) ->makeStmt(); } + AstNodeStmt* makeRuntimeBinHit(FileLine* fl, const ConvBinTarget& tgt) { + return makeRuntimeBinHit(fl, tgt.cpVarp, cnum(fl, static_cast(tgt.idx)), + tgt.isNormal); + } - void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx, - AstNodeExpr* condp) { + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, + AstNodeExpr* idxp, AstNodeExpr* condp) { FileLine* const fl = binp->fileline(); - AstNode* actionp = makeRuntimeBinHit(fl, {cpVarp, idx, binp->binsType().binIsNormal()}); + AstNode* actionp = makeRuntimeBinHit(fl, cpVarp, idxp, binp->binsType().binIsNormal()); if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + " hit in coverpoint " @@ -1170,6 +1287,64 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); } + // The runtime index of the bin of a run holding the coverpoint value, which is in the run: + // declared + (value - lo) / stride, capped at the last bin, which holds any remainder. + static AstNodeExpr* runBinIndex(FileLine* fl, AstNodeExpr* exprp, const BinRun& run) { + if (run.m_count == 1) return cnum(fl, run.m_declared); + const int width = exprp->width(); + // A run spans at most 2^width values, so offsets in it are unsigned width-bit numbers + AstNodeExpr* indexp + = new AstSub{fl, exprp->cloneTree(false), newValueConst(fl, run.m_lo, exprp)}; + indexp->dtypeSetLogicSized(width, VSigning::UNSIGNED); + V3Number stride{fl, width, 0}; + stride.opAssign(run.m_stride); + if (stride.countOnes() != 1) { + indexp = new AstDiv{fl, indexp, new AstConst{fl, stride}}; + } else if (!stride.isEqOne()) { + indexp = new AstShiftR{fl, indexp, new AstConst{fl, stride.mostSetBitP1() - 1}}; + } + // Compare the run's values with those of count bins of stride values, without overflow + const int extWidth = run.m_lo.width() + 1; + V3Number lo{fl, extWidth, 0}; + lo.opExtendS(run.m_lo, run.m_lo.width()); + V3Number span{fl, extWidth, 0}; + span.opExtendS(run.m_hi, run.m_hi.width()); + span.opSub(V3Number{span}, lo); + V3Number covered{fl, extWidth, 0}; + covered.opAssign(run.m_stride); + covered.opMul(V3Number{covered}, V3Number{fl, extWidth, run.m_count}); + V3Number hasRemainder{fl, 1, 0}; + if (!hasRemainder.opGte(span, covered).isEqZero()) { + AstConst* const lastp = new AstConst{fl, V3Number{fl, width, run.m_count - 1}}; + indexp = new AstCond{fl, new AstGt{fl, indexp->cloneTree(false), lastp}, + lastp->cloneTree(false), indexp}; + } + if (width < VL_IDATASIZE) { + indexp = new AstExtend{fl, indexp, VL_IDATASIZE}; + } else if (width > VL_IDATASIZE) { + indexp = new AstSel{fl, indexp, 0, VL_IDATASIZE}; + } + return new AstAdd{fl, cnum(fl, run.m_declared), indexp}; + } + + // Emit the sample() hit of a run of bins, whose code does not grow with its number of bins: + // if (iff && lo <= value && value <= hi) m_cp.incrementBin(); + void emitRunHit(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, + AstNodeExpr* exprp, const BinRun& run) { + FileLine* const fl = binp->fileline(); + AstConst* const lop = newValueConst(fl, run.m_lo, exprp); + AstNodeExpr* condp = nullptr; + if (run.m_lo.isCaseEq(run.m_hi)) { + condp = new AstEq{fl, exprp->cloneTree(false), lop}; + } else { + AstConst* const hip = newValueConst(fl, run.m_hi, exprp); + condp = makeRangeCondition(fl, exprp, lop, hip); + VL_DO_DANGLING(pushDeletep(lop), lop); + VL_DO_DANGLING(pushDeletep(hip), hip); + } + emitConvHitIf(coverpointp, binp, cpVarp, runBinIndex(fl, exprp, run), condp); + } + // Emit a transition bin's hit action into sample(): // if (iff && cond) { m_cp.incrementBin/recordHit(idx); [illegal: $error; $stop] } // Used by the transition generators so a completed sequence records into the runtime bin. @@ -1210,6 +1385,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_cpVarMap[coverpointp->name()] = cpVarp; m_cpBins.emplace(cpVarp, CoverpointBins{}); m_cpBins.at(cpVarp).exprp = exprp; + m_cpBins.at(cpVarp).crossed = crossFed; // Create the runtime in the instance node first; everything below configures it. m_constructorp->addStmtsp(makeItemCreate(fl, cpVarp, VCMethod::COVERGROUP_ADD_COVERPOINT)); generateItemWeight(fl, cpVarp, coverpointp->optionsp()); @@ -1244,7 +1420,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::vector namerStmts; std::vector defaultBins; std::vector> metadata; - int idx = 0; + std::vector runMetadata; + uint64_t idx = 0; // Runtime index of the next bin; 32-bit once checked below for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { AstCoverBin* const cbinp = VN_AS(binp, CoverBin); const int errorsBefore = dynamic ? V3Error::errorCount() : 0; @@ -1260,30 +1437,61 @@ class FunctionalCoverageVisitor final : public VNVisitor { // matching is generated as a state machine, with the hit routed to this bin's // runtime slot. namerStmts.push_back(makeNamer(cpVarp, cbinp, -1, static_cast(idx))); - const ConvBinTarget tgt{cpVarp, idx, cbinp->binsType().binIsNormal()}; + const ConvBinTarget tgt{cpVarp, static_cast(idx), + cbinp->binsType().binIsNormal()}; for (AstNode* sp = cbinp->transp(); sp; sp = sp->nextp()) generateSingleTransitionCode(coverpointp, cbinp, exprp, tgt, VN_AS(sp, CoverTransSet)); if (dynamic && V3Error::errorCount() == errorsBefore) { - metadata.emplace_back(cbinp, idx, nullptr); + metadata.emplace_back(cbinp, static_cast(idx), nullptr); } ++idx; continue; } - if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] + BinRuns plan; + if (binRunsFor(cbinp, exprp, plan)) { + // Array elements and automatic bins generate as runs, so neither sample() nor + // the constructor grows with their number of bins. + if (plan.unsupported) continue; // bin ignored or invalid; reserve no slot + CoverpointBins& bins = m_cpBins.at(cpVarp); + const uint32_t firstValue = bins.total; + const uint32_t firstDeclared = static_cast(idx); + namerStmts.push_back(makeNamer(cpVarp, cbinp, plan.count, firstDeclared)); + for (BinRun& run : plan.runs) { + run.m_declared = static_cast(idx); + bins.runs.push_back(std::move(run)); + const BinRun& stored = bins.runs.back(); + if (bins.crossed && cbinp->binsType().binIsNormal()) { + const uint32_t first = firstValue + stored.m_declared - firstDeclared; + for (uint32_t element = 0; element < stored.m_count; ++element) { + bins.values[first + element].runp = &stored; + bins.values[first + element].element = element; + } + } + if (!stored.m_empty) { + emitRunHit(coverpointp, cbinp, cpVarp, exprp, stored); + if (dynamic && V3Error::errorCount() == errorsBefore) { + runMetadata.push_back(&stored); + } + } + idx += stored.m_count; + } + continue; + } + if (cbinp->isArray()) { // value array of a real coverpoint: b[0]..b[N-1] + // Only integral coverpoints have runtime value metadata (m_runtimePoints) + UASSERT_OBJ(!dynamic, cbinp, "Runtime value metadata for a real coverpoint"); bool unsupported = false; std::vector values = extractArrayValues(cbinp, exprp, unsupported); if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot - namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()), + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()), static_cast(idx), values)); for (AstNodeExpr* valuep : values) { // The cross selections of this covergroup still read the value. m_detachedValues.push_back(valuep); - emitConvHitIf(coverpointp, cbinp, cpVarp, idx, + emitConvHitIf(coverpointp, cbinp, cpVarp, + cnum(cbinp->fileline(), static_cast(idx)), buildValueCondition(cbinp, exprp, valuep)); - if (dynamic && V3Error::errorCount() == errorsBefore) { - metadata.emplace_back(cbinp, idx, valuep); - } ++idx; } } else { @@ -1291,17 +1499,26 @@ class FunctionalCoverageVisitor final : public VNVisitor { // buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin // still gets a reserved slot (recorded, never incremented). if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp)) - emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp); + emitConvHitIf(coverpointp, cbinp, cpVarp, + cnum(cbinp->fileline(), static_cast(idx)), condp); if (dynamic && V3Error::errorCount() == errorsBefore) { - metadata.emplace_back(cbinp, idx, nullptr); + metadata.emplace_back(cbinp, static_cast(idx), nullptr); } ++idx; } } for (AstCoverBin* const defBinp : defaultBins) { namerStmts.push_back(makeNamer(cpVarp, defBinp, -1, static_cast(idx))); - emitConvHitIf(coverpointp, defBinp, cpVarp, idx++, + emitConvHitIf(coverpointp, defBinp, cpVarp, + cnum(defBinp->fileline(), static_cast(idx)), buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + ++idx; + } + if (idx > std::numeric_limits::max()) { + // The runtime indexes bins with 32 bits; stop before generating a model + coverpointp->v3warn(E_UNSUPPORTED, "Unsupported: coverpoint with more than " + << std::numeric_limits::max() + << " bins"); } // Transition coverpoints track the previous sampled value; update it once at the end of @@ -1338,7 +1555,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { collectValueMetadata(lists, exprp, std::get<0>(entry), std::get<1>(entry), std::get<2>(entry)); } + for (const BinRun* const runp : runMetadata) collectRunMetadata(lists, exprp, *runp); emitValueList(fl, cpVarp, VCMethod::COVERGROUP_VALUE_RANGES, lists.m_ranges); + emitValueList(fl, cpVarp, VCMethod::COVERGROUP_VALUE_RUNS, lists.m_runs); emitValueList(fl, cpVarp, VCMethod::COVERGROUP_VALUE_PATTERNS, lists.m_patterns); emitValueList(fl, cpVarp, VCMethod::COVERGROUP_VALUE_TRANSITIONS, lists.m_transitions); m_constructorp->addStmtsp( @@ -1750,6 +1969,25 @@ class FunctionalCoverageVisitor final : public VNVisitor { return values; } + // The values of the element-th bin of a run, sign-extended to 'width' + static CrossValueRange runBinRange(AstNode* nodep, const BinRun& run, uint32_t element, + int width) { + const int runw = run.m_lo.width(); + V3Number offset{nodep, runw}; + offset.opMul(run.m_stride, V3Number{nodep, runw, element}); + V3Number lo{nodep, runw}; + lo.opAdd(run.m_lo, offset); + V3Number hi = run.m_hi; + if (element + 1 < run.m_count) { + offset.opSub(run.m_stride, V3Number{nodep, runw, 1}); + hi.opAdd(lo, offset); + } + CrossValueRange range{nodep, width}; + range.lo.opExtendS(lo, runw); + range.hi.opExtendS(hi, runw); + return range; + } + static int crossRangeWidth(AstNode* nodep) { if (const AstInsideRange* const rangep = VN_CAST(nodep, InsideRange)) { return std::max(rangep->lhsp()->width(), rangep->rhsp()->width()); @@ -2043,7 +2281,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { if (selectp->rangesp()) { values.reserve(count); for (uint32_t i = first; i < first + count; ++i) { - values.push_back(crossBinValues(bins.values[i])); + // A run's values are in the coverpoint type, whose width 'width' covers + values.push_back(bins.values[i].runp ? std::vector{} + : crossBinValues(bins.values[i])); for (AstNode* const valuep : values.back()) { width = std::max(width, crossRangeWidth(valuep)); } @@ -2067,6 +2307,15 @@ class FunctionalCoverageVisitor final : public VNVisitor { selected[i] = true; continue; } + if (const BinRun* const runp = bins.values[i].runp) { + const CrossValueRange range + = runBinRange(selectp, *runp, bins.values[i].element, width); + selected[i] = std::any_of(filters.begin(), filters.end(), + [&](const CrossValueRange& filter) { + return crossRangesIntersect(range, filter); + }); + continue; + } for (AstNode* const valuep : values[i - first]) { selected[i] = crossValueMatchesFilters( selectp, valuep, bins.exprp, bins.values[i].binp, domain, filters, valid); @@ -2083,6 +2332,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Constructor-time value metadata of one coverpoint, as C++ list entries struct ValueLists final { std::vector m_ranges; // Bin, then low and high words + std::vector m_runs; // First bin, count, then low, span, and high words std::vector m_patterns; // Bin, then value, mask, low, and high words std::vector m_transitions; // Transition bin }; @@ -2124,6 +2374,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { } } + // Describe a run with one entry, from which the runtime computes the values of its bins. + static void collectRunMetadata(ValueLists& lists, AstNodeExpr* exprp, const BinRun& run) { + V3Number span{exprp, run.m_stride.width(), 0}; + span.opSub(run.m_stride, V3Number{exprp, run.m_stride.width(), 1}); + std::string entry = cvtToStr(run.m_declared) + "U, " + cvtToStr(run.m_count) + "U"; + appendWords(entry, run.m_lo, exprp); + appendWords(entry, span, exprp); + appendWords(entry, run.m_hi, exprp); + lists.m_runs.push_back(entry); + } + // Emit one batched metadata list, bounding the size of each call's temporary list. void emitValueList(FileLine* fl, AstVar* cpVarp, VCMethod method, const std::vector& entries) { @@ -2151,6 +2412,21 @@ class FunctionalCoverageVisitor final : public VNVisitor { return false; } + // The span of the implicit automatic bin reported as 'name' ('auto_', see + // createImplicitAutoBins), found without naming each of its bins + static bool implicitAutoBinSpan(const CoverpointBins& bins, const std::string& name, + BinSpan& span) { + const std::string prefix = "auto_"; + if (!VString::startsWith(name, prefix)) return false; + const std::string digits = name.substr(prefix.size()); + const unsigned long index = std::strtoul(digits.c_str(), nullptr, 10); + // Only the reported spelling names the bin, not e.g. 'auto_01' or 'auto_x' + if (index >= bins.implicitAuto.count || digits != cvtToStr(index)) return false; + const uint32_t offset = static_cast(index); + span = BinSpan{bins.implicitAuto.first + offset, 1, bins.implicitAuto.declared + offset}; + return true; + } + CrossBinsofTarget resolveBinsofTarget(const AstCoverBinsof* selectp, const AstCoverCross* crossp, const std::vector& cpVars, @@ -2166,16 +2442,19 @@ class FunctionalCoverageVisitor final : public VNVisitor { CrossBinsofTarget target{&bins, dim->second, 0, bins.total}; if (!selectp->name().empty()) { const auto bin = bins.spans.find(selectp->name()); - if (bin == bins.spans.end()) { + BinSpan span{0, 0, 0}; + if (bin != bins.spans.end()) { + span = bin->second; + } else if (!implicitAutoBinSpan(bins, selectp->name(), span)) { selectp->v3error("Cannot find bin " << selectp->prettyNameQ() << " in coverpoint " << selectp->pointp()->prettyNameQ() << " (IEEE 1800-2023 19.6.1)."); return {}; } - target.m_first = bin->second.first; - target.m_count = bin->second.count; - target.m_declaredFirst = bin->second.declared; - target.m_declaredEnd = bin->second.declared + bin->second.count; + target.m_first = span.first; + target.m_count = span.count; + target.m_declaredFirst = span.declared; + target.m_declaredEnd = span.declared + span.count; } return target; } diff --git a/src/V3Options.cpp b/src/V3Options.cpp index f72093fca..d3abec589 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1374,6 +1374,24 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-coverage-expr-max", Set, &m_coverageExprMax); DECL_OPTION("-coverage-fsm", OnOff, &m_coverageFsm); DECL_OPTION("-coverage-line", OnOff, &m_coverageLine); + // Covergroup bins limits; the runtime indexes bins with 32 bits + const auto parseBinsLimit = [fl](const char* optp, const char* valp, uint32_t& limitr) { + char* endp = nullptr; + const unsigned long long value = std::strtoull(valp, &endp, 10); + if (*endp || value < 1 || value > std::numeric_limits::max()) { + fl->v3error(optp << " requires an integer from 1 to " + << std::numeric_limits::max() << ", but '" << valp + << "' was passed"); + return; + } + limitr = static_cast(value); + }; + DECL_OPTION("-coverage-max-bins", CbVal, [this, parseBinsLimit](const char* valp) { + parseBinsLimit("--coverage-max-bins", valp, m_coverageMaxBins); + }); + DECL_OPTION("-coverage-max-real-bins", CbVal, [this, parseBinsLimit](const char* valp) { + parseBinsLimit("--coverage-max-real-bins", valp, m_coverageMaxRealBins); + }); DECL_OPTION("-coverage-max-width", Set, &m_coverageMaxWidth); DECL_OPTION("-coverage-per-instance", OnOff, &m_coveragePerInstance); DECL_OPTION("-coverage-toggle", OnOff, &m_coverageToggle); diff --git a/src/V3Options.h b/src/V3Options.h index 9f4efa0e8..68d7c5f4c 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -319,6 +319,8 @@ private: int m_buildJobs = -1; // main switch: --build-jobs, -j int m_coverageExprMax = 32; // main switch: --coverage-expr-max int m_convergeLimit = 10000; // main switch: --converge-limit + uint32_t m_coverageMaxBins = 1024; // main switch: --coverage-max-bins + uint32_t m_coverageMaxRealBins = 1024; // main switch: --coverage-max-real-bins int m_coverageMaxWidth = 256; // main switch: --coverage-max-width int m_debugAllocRandom = 0; // main switch: --debug-alloc-random int m_expandLimit = 256; // main switch: --expand-limit @@ -622,6 +624,8 @@ public: int buildJobs() const VL_MT_SAFE { return m_buildJobs; } int convergeLimit() const { return m_convergeLimit; } int coverageExprMax() const { return m_coverageExprMax; } + uint32_t coverageMaxBins() const { return m_coverageMaxBins; } + uint32_t coverageMaxRealBins() const { return m_coverageMaxRealBins; } int coverageMaxWidth() const { return m_coverageMaxWidth; } int debugAllocRandom() const { return m_debugAllocRandom; } bool dumpTreeAddrids() const VL_MT_SAFE; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index d69fc5fd0..cd47febd8 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2233,7 +2233,12 @@ class WidthVisitor final : public VNVisitor { // No m_vup for a bin directly in a covergroup body (unsupported, already warned) widthCovergroupRanges(nodep->rangesp(), m_vup ? m_vup->dtypep()->width() : 0); if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH); - userIterateAndNext(nodep->arraySizep(), nullptr); + if (nodep->arraySizep()) { + // The size of 'bins auto[N]' is a self-determined constant expression, which + // V3Covergroup checks once folded + userIterateAndNext(nodep->arraySizep(), WidthVP{SELF, BOTH}.p()); + V3Const::constifyEdit(nodep->arraySizep()); // arraySizep may change + } userIterateAndNext(nodep->transp(), m_vup); } void visit(AstCoverTransSet* nodep) override { userIterateAndNext(nodep->itemsp(), m_vup); } diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index f2d20a87d..290b1a689 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -2,6 +2,53 @@ cg.data.grouped: 2 cg.data.values[0]: 1 cg.data.values[1]: 1 cg.data.values[2]: 1 +cg10.cp.near_zero[0]: 1 +cg10.cp.near_zero[1]: 0 +cg10.cp.near_zero[2]: 0 +cg10.cp.near_zero[3]: 0 +cg10.cp.near_zero[4]: 0 +cg10.cp.near_zero[5]: 0 +cg10.cp.near_zero[6]: 1 +cg11.cp.top[0]: 1 +cg11.cp.top[1]: 0 +cg11.cp.top[2]: 1 +cg11.cp.top[3]: 0 +cg11.cp.top[4]: 0 +cg11.cp.top[5]: 1 +cg12.cpX.v[0]: 0 +cg12.cpX.v[1]: 0 +cg12.cpX.v[2]: 1 +cg12.cpX.v[3]: 1 +cg12.cpY.one: 2 +cg12.x.mid [cross]: 1 +cg12.x.v[0]_x_one [cross]: 0 +cg12.x.v[3]_x_one [cross]: 1 +cg13.cp.a[0]: 0 +cg13.cp.a[1]: 1 +cg13.cp.a[2]: 0 +cg13.cp.a[3]: 0 +cg13.cp.a[4]: 0 +cg13.cp.a[5]: 1 +cg13.cp.a[6]: 0 +cg13.cp.a[7]: 0 +cg13.cp.b: 1 +cg13.cps.one: 1 +cg13.x.a[0]_x_one [cross]: 0 +cg13.x.a[1]_x_one [cross]: 1 +cg13.x.a[2]_x_one [cross]: 0 +cg13.x.a[3]_x_one [cross]: 0 +cg13.x.a[4]_x_one [cross]: 0 +cg13.x.a[5]_x_one [cross]: 1 +cg13.x.a[6]_x_one [cross]: 0 +cg13.x.a[7]_x_one [cross]: 0 +cg13.x.b_x_one [cross]: 1 +cg14.cp.w[0]: 0 +cg14.cp.w[1]: 1 +cg14.cps.one: 1 +cg14.x.w[0]_x_one [cross]: 0 +cg14.x.w[1]_x_one [cross]: 1 +cg15.cp.v[0]: 0 +cg15.cp.v[1]: 1 cg2.cp.range_arr[0]: 1 cg2.cp.range_arr[1]: 1 cg2.cp.range_arr[2]: 1 diff --git a/test_regress/t/t_covergroup_array_bins.v b/test_regress/t/t_covergroup_array_bins.v index 209e1b549..69d2d6105 100644 --- a/test_regress/t/t_covergroup_array_bins.v +++ b/test_regress/t/t_covergroup_array_bins.v @@ -15,6 +15,9 @@ module t; bit [7:0] data; bit [1:0] sel; bit [63:0] wide; + bit signed [7:0] sdata; + bit [69:0] wide70; + real rdata; covergroup cg; coverpoint data { @@ -77,14 +80,13 @@ module t; } endgroup - // cg9: two ranges that are each under COVER_BINS_LIMIT (1000) but whose + // cg9: two ranges that are each under --coverage-max-bins (1024) but whose // cumulative size exceeds it. The first range populates the value list, the // second trips the running-total guard -> COVERIGN, the whole bin is ignored. - // cpA is crossed, so it is non-convertible and routes through the legacy - // per-bin generateArrayBins() path (exercising its unsupported-bin guard). + // cpA is crossed, so the guard also runs for a cross-fed coverpoint. covergroup cg9; cpA: coverpoint wide { - bins cumulative[] = {[0 : 500], [0 : 500]}; + bins cumulative[] = {[0 : 600], [0 : 600]}; bins ok = {5}; } cpB: coverpoint sel { @@ -93,6 +95,63 @@ module t; cross cpA, cpB; endgroup + // cg10: a signed range holds signed values: 7 bins for -3..3 + covergroup cg10; + cp: coverpoint sdata { + bins near_zero[] = {[-3 : 3]}; + } + endgroup + + // cg11: a range holds only the values of the coverpoint type (IEEE 1800-2023 19.5.7): + // 6 bins for 250..255 + covergroup cg11; + cp: coverpoint data { + bins top[] = {[250 : 260]}; + } + endgroup + + // cg12: an intersect selects array elements by value: v[1] and v[2] hold 2 and 3 + covergroup cg12; + cpX: coverpoint data { + bins v[] = {[1 : 4]}; + } + cpY: coverpoint sel { + bins one = {1}; + } + x: cross cpX, cpY{bins mid = binsof (cpX) intersect {[2 : 3]};} + endgroup + + // cg13: [-1 : 3] holds only 0..3, like [0 : 3], so data 1 hits a[1], a[5] and b; each hit + // also reaches the cross + covergroup cg13; + cp: coverpoint data { + bins a[] = {[-1 : 3], [0 : 3]}; + bins b = {[0 : 3]}; + } + cps: coverpoint sel { + bins one = {1}; + } + x: cross cp, cps; + endgroup + + // cg14: a crossed array bin with values beyond 64 bits + covergroup cg14; + cp: coverpoint wide70 { + bins w[] = {[70'h2_0000_0000_0000_0000 : 70'h2_0000_0000_0000_0001]}; + } + cps: coverpoint sel { + bins one = {1}; + } + x: cross cp, cps; + endgroup + + // cg15: an array bin of a real coverpoint has one bin per value + covergroup cg15; + cp: coverpoint rdata { + bins v[] = {1.5, 2.5}; + } + endgroup + initial begin cg cg_inst; cg2 cg2_inst; @@ -103,6 +162,12 @@ module t; cg7 cg7_inst; cg8 cg8_inst; cg9 cg9_inst; + cg10 cg10_inst; + cg11 cg11_inst; + cg12 cg12_inst; + cg13 cg13_inst; + cg14 cg14_inst; + cg15 cg15_inst; cg_inst = new(); cg2_inst = new(); @@ -113,6 +178,12 @@ module t; cg7_inst = new(); cg8_inst = new(); cg9_inst = new(); + cg10_inst = new(); + cg11_inst = new(); + cg12_inst = new(); + cg13_inst = new(); + cg14_inst = new(); + cg15_inst = new(); // Hit first array bin value (1) data = 1; @@ -192,11 +263,49 @@ module t; cg8_inst.sample(); `checkr(cg8_inst.get_inst_coverage(), 50.0); - // Exercise cg9 (crossed cpA with an ignored cumulative array bin, legacy path) + // Exercise cg9 (crossed cpA with an ignored cumulative array bin) wide = 5; sel = 1; cg9_inst.sample(); + // Hit cg10 signed bins (-3..3, 7 bins): cover 2 of 7 + sdata = -3; + cg10_inst.sample(); + sdata = 3; + cg10_inst.sample(); + `checkr(cg10_inst.get_inst_coverage(), 100.0 * (2.0 / 7)); + + // Hit cg11 clipped bins (250..255, 6 bins): cover 3 of 6 + data = 250; + cg11_inst.sample(); + data = 252; + cg11_inst.sample(); + data = 255; + cg11_inst.sample(); + `checkr(cg11_inst.get_inst_coverage(), 50.0); + + // Hit cg12: data 3 is in the 'mid' cross bin, data 4 in an automatic cross bin + sel = 1; + data = 3; + cg12_inst.sample(); + data = 4; + cg12_inst.sample(); + + // Hit cg13: three bins of cp at once + sel = 1; + data = 1; + cg13_inst.sample(); + + // Hit cg14: w[1] and its cross bin + sel = 1; + wide70 = 70'h2_0000_0000_0000_0001; + cg14_inst.sample(); + + // Hit cg15: v[1] holds 2.5 + rdata = 2.5; + cg15_inst.sample(); + `checkr(cg15_inst.get_inst_coverage(), 50.0); + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_auto_bin_max_bad.out b/test_regress/t/t_covergroup_auto_bin_max_bad.out index b00d58cd8..caddccf38 100644 --- a/test_regress/t/t_covergroup_auto_bin_max_bad.out +++ b/test_regress/t/t_covergroup_auto_bin_max_bad.out @@ -1,7 +1,12 @@ -%Warning-COVERIGN: t/t_covergroup_auto_bin_max_bad.v:14:27: Ignoring unsupported: non-constant 'option.auto_bin_max'; using default value +%Warning-COVERIGN: t/t_covergroup_auto_bin_max_bad.v:15:27: Ignoring unsupported: non-constant 'option.auto_bin_max'; using default value : ... note: In instance 't' - 14 | option.auto_bin_max = size_var; + 15 | option.auto_bin_max = size_var; | ^~~~~~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_covergroup_auto_bin_max_bad.v:22:9: Unsupported: more than 1024 automatic bins from 'option.auto_bin_max'; using 1024. + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-bins + 22 | cp: coverpoint cp_32bit; + | ^~~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_auto_bin_max_bad.v b/test_regress/t/t_covergroup_auto_bin_max_bad.v index 6ae565574..56267b546 100644 --- a/test_regress/t/t_covergroup_auto_bin_max_bad.v +++ b/test_regress/t/t_covergroup_auto_bin_max_bad.v @@ -8,6 +8,7 @@ module t; int size_var; logic [3:0] cp_expr; + logic [31:0] cp_32bit; // Error: option.auto_bin_max must be a constant expression (group level) covergroup cg; @@ -15,6 +16,13 @@ module t; cp: coverpoint cp_expr; endgroup + // Warning (COVERIGN): more automatic bins than --coverage-max-bins (1024) + covergroup cg_limit; + option.auto_bin_max = 2000000; + cp: coverpoint cp_32bit; + endgroup + cg cg_i = new; + cg_limit cg_limit_i = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_auto_bins.out b/test_regress/t/t_covergroup_auto_bins.out index 41d820337..bab03b142 100644 --- a/test_regress/t/t_covergroup_auto_bins.out +++ b/test_regress/t/t_covergroup_auto_bins.out @@ -13,3 +13,66 @@ cg_4bit_excl.data4.auto[1]: 0 cg_4bit_excl.data4.auto[2]: 1 cg_4bit_excl.data4.auto[3]: 0 cg_4bit_excl.data4.bad [ignore]: 0 +cg_70bit.data70.auto[0]: 2 +cg_70bit.data70.auto[1]: 1 +cg_70bit.data70.auto[2]: 1 +cg_70bit.data70.auto[3]: 1 +cg_70bit.data70.auto[4]: 1 +cg_70bit_excl.data70.all_of_1 [ignore]: 0 +cg_70bit_excl.data70.auto[0]: 1 +cg_70bit_excl.data70.auto[2]: 0 +cg_70bit_excl.data70.auto[3]: 0 +cg_70bit_excl.data70.auto[4]: 0 +cg_auto_default.data.auto[0]: 1 +cg_auto_default.data.auto[1]: 1 +cg_auto_default.data.one: 1 +cg_auto_default.data.other [default]: 0 +cg_cross_auto.cp_a.auto_0: 0 +cg_cross_auto.cp_a.auto_1: 0 +cg_cross_auto.cp_a.auto_2: 1 +cg_cross_auto.cp_a.auto_3: 0 +cg_cross_auto.cp_a.auto_4: 0 +cg_cross_auto.cp_a.auto_5: 1 +cg_cross_auto.cp_a.auto_6: 0 +cg_cross_auto.cp_a.auto_7: 0 +cg_cross_auto.cp_ax.auto_1: 0 +cg_cross_auto.cp_ax.auto_2: 1 +cg_cross_auto.cp_ax.auto_3: 0 +cg_cross_auto.cp_ax.auto_4: 0 +cg_cross_auto.cp_ax.auto_5: 1 +cg_cross_auto.cp_ax.auto_6: 0 +cg_cross_auto.cp_ax.auto_7: 0 +cg_cross_auto.cp_ax.zero [ignore]: 0 +cg_cross_auto.cp_b.one: 2 +cg_cross_auto.x.auto_0_x_one [cross]: 0 +cg_cross_auto.x.auto_1_x_one [cross]: 0 +cg_cross_auto.x.auto_3_x_one [cross]: 0 +cg_cross_auto.x.auto_4_x_one [cross]: 0 +cg_cross_auto.x.auto_5_x_one [cross]: 1 +cg_cross_auto.x.auto_6_x_one [cross]: 0 +cg_cross_auto.x.auto_7_x_one [cross]: 0 +cg_cross_auto.x.sel [cross]: 1 +cg_cross_auto.xx.auto_1_x_one [cross]: 0 +cg_cross_auto.xx.auto_3_x_one [cross]: 0 +cg_cross_auto.xx.auto_4_x_one [cross]: 0 +cg_cross_auto.xx.auto_5_x_one [cross]: 1 +cg_cross_auto.xx.auto_6_x_one [cross]: 0 +cg_cross_auto.xx.auto_7_x_one [cross]: 0 +cg_cross_auto.xx.sel [cross]: 1 +cg_expr_size.data.auto[0]: 0 +cg_expr_size.data.auto[1]: 1 +cg_expr_size.data.auto[2]: 0 +cg_expr_size.data.auto[3]: 0 +cg_many.data.auto[0]: 0 +cg_many.data.auto[1]: 0 +cg_many.data.auto[2]: 0 +cg_many.data.auto[3]: 0 +cg_many.data.auto[4]: 0 +cg_many.data.auto[5]: 0 +cg_many.data.auto[6]: 0 +cg_many.data.auto[7]: 1 +cg_signed.sdata.auto[0]: 2 +cg_signed.sdata.auto[1]: 1 +cg_signed.sdata.auto[2]: 1 +cg_signed.sdata.auto[3]: 1 +cg_signed.sdata.auto[4]: 1 diff --git a/test_regress/t/t_covergroup_auto_bins.v b/test_regress/t/t_covergroup_auto_bins.v index 3828ff1e9..5d1beb0cc 100644 --- a/test_regress/t/t_covergroup_auto_bins.v +++ b/test_regress/t/t_covergroup_auto_bins.v @@ -13,9 +13,12 @@ // verilog_format: on module t; + localparam int N_AUTO = 5; logic [2:0] data; // 3-bit: 0-7 logic [3:0] data4; // 4-bit signal logic [63:0] data64; // 64-bit signal + logic signed [7:0] sdata; // signed 8-bit: -128..127 + logic [69:0] data70; // wider than 64 bits covergroup cg; coverpoint data { @@ -44,11 +47,79 @@ module t; coverpoint data64; endgroup + // Signed values partition in value order, 51 values per bin, the last bin also holding the + // remainder: [-128:-78], [-77:-27], [-26:24], [25:75], [76:127] + covergroup cg_signed; + coverpoint sdata { + bins auto[5]; + } + endgroup + + // Wider than 64 bits: 5 bins of 2^70/5 values, the last bin also holding the remainder + covergroup cg_70bit; + coverpoint data70 { + bins auto[5]; + } + endgroup + + // The runtime computes the values of each wide bin for exclusions, carrying between words: + // ignoring every value of auto[1] leaves it without values, so out of the coverage + covergroup cg_70bit_excl; + coverpoint data70 { + bins auto[5]; + ignore_bins all_of_1 = {[70'd236118324143482260684 : 70'd472236648286964521367]}; + } + endgroup + + // More bins than values: one bin per value + covergroup cg_many; + coverpoint data { + bins auto[16]; + } + endgroup + + // Crosses select an implicit automatic bin by its reported name, also when exclusions make + // the cross select at run time + covergroup cg_cross_auto; + cp_a: coverpoint data; + cp_ax: coverpoint data { + ignore_bins zero = {0}; + } + cp_b: coverpoint data4 { + bins one = {1}; + } + x: cross cp_a, cp_b{bins sel = binsof (cp_a.auto_2);} + xx: cross cp_ax, cp_b{bins sel = binsof (cp_ax.auto_2);} + endgroup + + // A constant expression sizes automatic bins: 4 bins of 2 values + covergroup cg_expr_size; + coverpoint data { + bins auto[N_AUTO - 1]; + } + endgroup + + // Automatic bins hold every value, leaving none to a default bin + covergroup cg_auto_default; + coverpoint data { + bins one = {1}; + bins auto[2]; + bins other = default; + } + endgroup + initial begin automatic cg cg_inst = new; automatic cg_4bit cg4_inst = new; automatic cg_4bit_excl cg4e_inst = new; automatic cg2 cg2_inst = new; + automatic cg_signed cgs_inst = new; + automatic cg_70bit cg70_inst = new; + automatic cg_70bit_excl cg70x_inst = new; + automatic cg_many cgm_inst = new; + automatic cg_cross_auto cgx_inst = new; + automatic cg_expr_size cge_inst = new; + automatic cg_auto_default cgd_inst = new; // Sample 3-bit cg: one value per bin - 4 bins: [0:1],[2:3],[4:5],[6:7] data = 0; cg_inst.sample(); @@ -82,11 +153,82 @@ module t; cg4e_inst.sample(); `checkr(cg4e_inst.get_inst_coverage(), 50.0); - // Sample 64-bit cg2 - SKIP checkr: Verilator 64-bit bin boundary bug causes 100% at first sample + // Sample 64-bit cg2 - two bins: [0:2^63-1], [2^63:2^64-1] data64 = 64'd0; cg2_inst.sample(); + `checkr(cg2_inst.get_inst_coverage(), 50.0); data64 = 64'hFFFF_FFFF_FFFF_FFFF; cg2_inst.sample(); + `checkr(cg2_inst.get_inst_coverage(), 100.0); + + // Sample the signed bins at their boundaries + sdata = -128; + cgs_inst.sample(); + `checkr(cgs_inst.get_inst_coverage(), 20.0); + sdata = -78; + cgs_inst.sample(); // still the first bin + `checkr(cgs_inst.get_inst_coverage(), 20.0); + sdata = -77; + cgs_inst.sample(); + `checkr(cgs_inst.get_inst_coverage(), 40.0); + sdata = 24; + cgs_inst.sample(); + `checkr(cgs_inst.get_inst_coverage(), 60.0); + sdata = 75; + cgs_inst.sample(); + `checkr(cgs_inst.get_inst_coverage(), 80.0); + sdata = 127; + cgs_inst.sample(); + `checkr(cgs_inst.get_inst_coverage(), 100.0); + + // Sample the wide bins at their boundaries + data70 = 70'd0; + cg70_inst.sample(); + `checkr(cg70_inst.get_inst_coverage(), 20.0); + data70 = 70'd236118324143482260683; + cg70_inst.sample(); // last value of the first bin + `checkr(cg70_inst.get_inst_coverage(), 20.0); + data70 = 70'd236118324143482260684; + cg70_inst.sample(); + `checkr(cg70_inst.get_inst_coverage(), 40.0); + data70 = 70'd472236648286964521368; + cg70_inst.sample(); + `checkr(cg70_inst.get_inst_coverage(), 60.0); + data70 = 70'd944473296573929042735; + cg70_inst.sample(); // last value of the fourth bin + `checkr(cg70_inst.get_inst_coverage(), 80.0); + data70 = '1; + cg70_inst.sample(); // in the remainder of the last bin + `checkr(cg70_inst.get_inst_coverage(), 100.0); + + // Sample one of the 4 bins with values + data70 = 70'd0; + cg70x_inst.sample(); + `checkr(cg70x_inst.get_inst_coverage(), 25.0); + + // Sample one of the 8 single-value bins + data = 7; + cgm_inst.sample(); + `checkr(cgm_inst.get_inst_coverage(), 12.5); + + // Hit each 'sel' cross bin, and one automatic cross bin + data4 = 1; + data = 2; + cgx_inst.sample(); + data = 5; + cgx_inst.sample(); + + // Hit every bin but the default bin, which holds no value + data = 1; + cgd_inst.sample(); + data = 5; + cgd_inst.sample(); + `checkr(cgd_inst.get_inst_coverage(), 100.0); + + // Hit auto[1], which holds 2 and 3 + data = 3; + cge_inst.sample(); + `checkr(cge_inst.get_inst_coverage(), 25.0); $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index 86d8155c1..499144658 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -1,192 +1,241 @@ -%Error: t/t_covergroup_autobins_bad.v:18:12: Automatic bins array size must be a constant +%Error: t/t_covergroup_autobins_bad.v:19:12: Automatic bins array size must be a constant : ... note: In instance 't' - 18 | bins auto[size_var]; + 19 | bins auto[size_var]; | ^~~~ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_covergroup_autobins_bad.v:25:12: Automatic bins array size must be >= 1, got 0 +%Error: t/t_covergroup_autobins_bad.v:26:12: Automatic bins array size must be >= 1, got 0 : ... note: In instance 't' - 25 | bins auto[0]; + 26 | bins auto[0]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:32:12: Automatic bins array size of 1001 exceeds limit of 1000 +%Error: t/t_covergroup_autobins_bad.v:29:12: Automatic bins array size must be >= 1, got -1 : ... note: In instance 't' - 32 | bins auto[1001]; + 29 | bins auto[-1]; | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:39:12: Non-constant expression in array bins range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:36:12: Automatic bins array size of 1025 exceeds limit of 1024 : ... note: In instance 't' - 39 | bins b[] = {[size_var:size_var]}; + : ... Suggest a larger --coverage-max-bins + 36 | bins auto[1025]; + | ^~~~ +%Error: t/t_covergroup_autobins_bad.v:43:12: Non-constant expression in array bins range; range bounds must be constants (IEEE 1800-2023 19.5) + : ... note: In instance 't' + 43 | bins b[] = {[size_var:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:40:12: Non-constant expression in array bins range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:44:12: Non-constant expression in array bins range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 40 | bins b_mixed[] = {[0:size_var]}; + 44 | bins b_mixed[] = {[0:size_var]}; | ^~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:41:23: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:45:23: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 41 | bins b_range = {[size_var:4]}; + 45 | bins b_range = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:42:24: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:46:24: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 42 | bins b_range2 = {[0:size_var]}; + 46 | bins b_range2 = {[0:size_var]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:43:18: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:47:18: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 43 | bins b2 = {size_var}; + 47 | bins b2 = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:44:26: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:48:26: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 44 | ignore_bins ign = {size_var}; + 48 | ignore_bins ign = {size_var}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:45:32: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:49:32: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 45 | ignore_bins ign_range = {[0:size_var]}; + 49 | ignore_bins ign_range = {[0:size_var]}; | ^ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:52:25: Ignoring unsupported: non-constant 'option.at_least'; using default value +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:56:25: Ignoring unsupported: non-constant 'option.at_least'; using default value : ... note: In instance 't' - 52 | option.at_least = size_var; + 56 | option.at_least = size_var; | ^~~~~~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Error: t/t_covergroup_autobins_bad.v:59:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:63:20: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 59 | bins b_xz = {[4'bxxxx:4'hF]}; + 63 | bins b_xz = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:60:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:64:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 60 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; + 64 | ignore_bins ign_xz_lo = {[4'bxxxx:4'hF]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:61:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:65:32: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 61 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; + 65 | ignore_bins ign_xz_hi = {[4'h0:4'bzzzz]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:62:31: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:66:31: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 62 | ignore_bins ign_nclo = {[size_var:4]}; + 66 | ignore_bins ign_nclo = {[size_var:4]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:63:23: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:67:23: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 63 | bins b_nc_ub = {[size_var:$]}; + 67 | bins b_nc_ub = {[size_var:$]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:64:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:68:23: Four-state (x/z) value in bin range bound; range bounds must be two-state constants : ... note: In instance 't' - 64 | bins b_xz_ub = {[4'bxxxx:$]}; + 68 | bins b_xz_ub = {[4'bxxxx:$]}; | ^ -%Error: t/t_covergroup_autobins_bad.v:65:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:69:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants : ... note: In instance 't' - 65 | bins b_xz_arr[] = {[4'bxxxx:4'hF]}; + 69 | bins b_xz_arr[] = {[4'bxxxx:4'hF]}; | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:66:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:70:12: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants : ... note: In instance 't' - 66 | bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; + 70 | bins b_xz_arr_hi[] = {[4'h0:4'bzzzz]}; | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:73:12: Unsupported: array 'bins' covering more than 1000 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:77:12: Unsupported: array 'bins' covering more than 1024 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored : ... note: In instance 't' - 73 | bins b_huge[] = {[0:$]}; + : ... Suggest a larger --coverage-max-bins + 77 | bins b_huge[] = {[0:$]}; | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:78:25: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:82:25: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 78 | bins bns1 = (1 => size_var); + 82 | bins bns1 = (1 => size_var); | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:79:20: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:83:20: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 79 | bins bns2 = (size_var => 2); + 83 | bins bns2 = (size_var => 2); | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:80:20: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:84:20: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 80 | bins bns3 = (size_var => size_var); + 84 | bins bns3 = (size_var => size_var); | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:80:32: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:84:32: Non-constant expression in transition bin; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 80 | bins bns3 = (size_var => size_var); + 84 | bins bns3 = (size_var => size_var); | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:91:41: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:95:41: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 91 | cp_a: coverpoint cp_expr {bins x = {size_var};} + 95 | cp_a: coverpoint cp_expr {bins x = {size_var};} | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:94:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:98:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 94 | bins filtered = binsof(cp_a) intersect {0}; + 98 | bins filtered = binsof(cp_a) intersect {0}; | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:98:41: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) - : ... note: In instance 't' - 98 | cp_a: coverpoint cp_expr {bins x = {[size_var : 1]};} +%Error: t/t_covergroup_autobins_bad.v:102:41: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) + : ... note: In instance 't' + 102 | cp_a: coverpoint cp_expr {bins x = {[size_var : 1]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:103:41: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:107:41: Non-constant expression in bin range; range bounds must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 103 | cp_a: coverpoint cp_expr {bins x = {[0 : size_var]};} + 107 | cp_a: coverpoint cp_expr {bins x = {[0 : size_var]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:108:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:112:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants : ... note: In instance 't' - 108 | cp_a: coverpoint cp_expr {bins x[] = {[4'b000x : 4'hF]};} + 112 | cp_a: coverpoint cp_expr {bins x[] = {[4'b000x : 4'hF]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:113:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:117:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants : ... note: In instance 't' - 113 | cp_a: coverpoint cp_expr {bins x[] = {[4'h0 : 4'b000x]};} + 117 | cp_a: coverpoint cp_expr {bins x[] = {[4'h0 : 4'b000x]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:118:36: Non-constant expression in array bins value list; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:122:36: Non-constant expression in array bins value list; values must be constants (IEEE 1800-2023 19.5) : ... note: In instance 't' - 118 | cp_a: coverpoint cp_expr {bins x[] = {size_var};} + 122 | cp_a: coverpoint cp_expr {bins x[] = {size_var};} | ^ -%Error: t/t_covergroup_autobins_bad.v:133:28: binsof coverpoint 'missing' is not an item of cross 'xc' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:137:28: binsof coverpoint 'missing' is not an item of cross 'xc' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 133 | bins missing_point = binsof(missing); + 137 | bins missing_point = binsof(missing); | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:134:30: binsof coverpoint 'cp_other' is not an item of cross 'xc' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:138:30: binsof coverpoint 'cp_other' is not an item of cross 'xc' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 134 | bins uncrossed_point = binsof(cp_other); + 138 | bins uncrossed_point = binsof(cp_other); | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:135:26: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:139:26: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 135 | bins missing_bin = binsof(cp_a.missing); + 139 | bins missing_bin = binsof(cp_a.missing); | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:137:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:141:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 137 | bins duplicate = binsof(cp_b); + 141 | bins duplicate = binsof(cp_b); | ^~~~ -%Error: t/t_covergroup_autobins_bad.v:138:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:142:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 138 | ignore_bins duplicate = binsof(cp_a); + 142 | ignore_bins duplicate = binsof(cp_a); | ^~~~~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:139:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:143:7: Duplicate cross bin 'duplicate' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 139 | illegal_bins duplicate = binsof(cp_b); + 143 | illegal_bins duplicate = binsof(cp_b); | ^~~~~~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:141:7: Duplicate cross bin 'ignored_duplicate' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:145:7: Duplicate cross bin 'ignored_duplicate' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 141 | illegal_bins ignored_duplicate = binsof(cp_b); + 145 | illegal_bins ignored_duplicate = binsof(cp_b); | ^~~~~~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:143:7: Duplicate cross bin 'illegal_duplicate' (IEEE 1800-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:147:7: Duplicate cross bin 'illegal_duplicate' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 143 | bins illegal_duplicate = binsof(cp_b); + 147 | 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-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:148:36: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 144 | ignore_bins missing_ignore = binsof(cp_a.missing); + 148 | 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-2023 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:149:40: binsof coverpoint 'cp_other' is not an item of cross 'xc' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 145 | illegal_bins uncrossed_illegal = binsof(cp_other); + 149 | 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. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:150:26: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 146 | bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); + 150 | bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:147:32: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:151:32: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 147 | bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; + 151 | bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:158:9: Unsupported: cross coverage with more than 2^32-1 tuples. - : ... note: In instance 't' - 158 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f { - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:161:16: Unsupported: cross coverage with more than 2^32-1 tuples. - : ... note: In instance 't' - 161 | auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; - | ^~~~~ -%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:172:9: Unsupported: cross coverage with more than 2^32-1 tuples. - : ... note: In instance 't' - 172 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; - | ^~~~~ -%Error: t/t_covergroup_autobins_bad.v:178:34: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) +%Error: t/t_covergroup_autobins_bad.v:161:27: Cannot find bin 'auto_16' in coverpoint 'cp_imp' (IEEE 1800-2023 19.6.1). : ... note: In instance 't' - 178 | ignore_bins nonconstant = {size_var}; + 161 | bins out_of_range = binsof(cp_imp.auto_16); + | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:162:27: Cannot find bin 'auto_01' in coverpoint 'cp_imp' (IEEE 1800-2023 19.6.1). + : ... note: In instance 't' + 162 | bins leading_zero = binsof(cp_imp.auto_01); + | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:163:27: Cannot find bin 'auto_x' in coverpoint 'cp_imp' (IEEE 1800-2023 19.6.1). + : ... note: In instance 't' + 163 | bins not_a_number = binsof(cp_imp.auto_x); + | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:164:23: Cannot find bin 'auto_' in coverpoint 'cp_imp' (IEEE 1800-2023 19.6.1). + : ... note: In instance 't' + 164 | bins no_index = binsof(cp_imp.auto_); + | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:165:26: Cannot find bin 'auto' in coverpoint 'cp_imp' (IEEE 1800-2023 19.6.1). + : ... note: In instance 't' + 165 | bins declaration = binsof(cp_imp.auto); + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:176:9: Unsupported: cross coverage with more than 2^32-1 tuples. + : ... note: In instance 't' + 176 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f { + | ^~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:179:16: Unsupported: cross coverage with more than 2^32-1 tuples. + : ... note: In instance 't' + 179 | auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; + | ^~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:190:9: Unsupported: cross coverage with more than 2^32-1 tuples. + : ... note: In instance 't' + 190 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; + | ^~~~~ +%Error: t/t_covergroup_autobins_bad.v:196:34: Non-constant expression in bin range; values must be constants (IEEE 1800-2023 19.5) + : ... note: In instance 't' + 196 | ignore_bins nonconstant = {size_var}; | ^~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:243:12: Unsupported: array 'bins' covering more than 1024 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-bins + 243 | bins over_limit[] = {[0 : 1024]}; + | ^~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:244:12: Unsupported: array 'bins' covering more than 1024 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-bins + 244 | bins split_over_limit[] = {[0 : 511], [512 : 1024]}; + | ^~~~~~~~~~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:251:18: Coverpoint of a non-integral expression requires explicit bins (IEEE 1800-2023 19.5.3). + : ... note: In instance 't' + 251 | cp_implicit: coverpoint cp_real; + | ^~~~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:253:12: Automatic bins are not allowed on a coverpoint of a non-integral expression (IEEE 1800-2023 19.5.3). + : ... note: In instance 't' + 253 | bins auto[4]; + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:257:12: Unsupported: array 'bins' of a real coverpoint covering more than 1024 values; bin ignored. + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-real-bins + 257 | bins over_limit[] = {[0 : 1024]}; + | ^~~~~~~~~~ %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 d80663b0e..a31ee0bc0 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -11,6 +11,7 @@ module t; int size_var; logic [3:0] cp_expr; logic [15:0] cp_wide; + real cp_real; // Error: array size must be a constant covergroup cg1; @@ -24,12 +25,15 @@ module t; cp1: coverpoint cp_expr { bins auto[0]; } + cp2: coverpoint cp_expr { + bins auto[-1]; // Error: negative + } endgroup - // Error: array size exceeds limit of 1000 + // Error: array size exceeds limit of 1024 covergroup cg2b; cp1: coverpoint cp_expr { - bins auto[1001]; + bins auto[1025]; } endgroup @@ -67,7 +71,7 @@ module t; } endgroup - // Warning (COVERIGN): array bins range exceeds COVER_BINS_LIMIT + // Warning (COVERIGN): array bins range exceeds --coverage-max-bins covergroup cg6; cp1: coverpoint cp_wide { bins b_huge[] = {[0:$]}; // open '[lo:$]' over 16-bit coverpoint exceeds bin limit @@ -148,6 +152,20 @@ module t; } endgroup + // Error: the implicit automatic bins of a coverpoint are selected by their reported names + covergroup cgx_binsof_auto; + cp_imp: coverpoint cp_expr; // auto_0 .. auto_15 + cp_a: coverpoint cp_expr {bins a = {0};} + xc: cross cp_imp, cp_a { + bins last_bin = binsof(cp_imp.auto_15); // OK + bins out_of_range = binsof(cp_imp.auto_16); + bins leading_zero = binsof(cp_imp.auto_01); + bins not_a_number = binsof(cp_imp.auto_x); + bins no_index = binsof(cp_imp.auto_); + bins declaration = binsof(cp_imp.auto); + } + endgroup + covergroup cgx_binsof_large; cp_a: coverpoint cp_wide; cp_b: coverpoint cp_wide; @@ -211,6 +229,35 @@ module t; } endgroup + // Exactly --coverage-max-bins (1024) bins are accepted + covergroup cg_limit; + cp_auto: coverpoint cp_wide { + bins auto[1024]; + } + cp_open: coverpoint cp_wide { + bins all[] = {[16'hfc00 : $]}; + } + cp_range: coverpoint cp_wide { + bins at_limit[] = {[0 : 1023]}; + bins split_at_limit[] = {[0 : 511], [512 : 1023]}; + bins over_limit[] = {[0 : 1024]}; // Warning (COVERIGN): 1025 values + bins split_over_limit[] = {[0 : 511], [512 : 1024]}; // Warning (COVERIGN) + } + endgroup + + // Error: automatic bins are not allowed on a coverpoint of a real expression. Its array + // bins have their own limit, --coverage-max-real-bins (1024). + covergroup cg_real; + cp_implicit: coverpoint cp_real; + cp_explicit: coverpoint cp_real { + bins auto[4]; + } + cp_array: coverpoint cp_real { + bins at_limit[] = {[0 : 1023]}; + bins over_limit[] = {[0 : 1024]}; // Warning (COVERIGN): 1025 values + } + endgroup + cg1 cg1_inst = new; cg2 cg2_inst = new; cg2b cg2b_inst = new; @@ -226,10 +273,13 @@ module t; cgx_arr_ncval cgx_arr_ncval_inst = new; cgx_arr_open cgx_arr_open_inst = new; cgx_binsof cgx_binsof_inst = new; + cgx_binsof_auto cgx_binsof_auto_inst = new; cgx_binsof_large cgx_binsof_large_inst = new; cgx_dynamic_large cgx_dynamic_large_inst = new; cgx_binsof_excluded cgx_binsof_excluded_inst = new; cgx_binsof_many_values cgx_binsof_many_values_inst = new; + cg_limit cg_limit_inst = new; + cg_real cg_real_inst = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_bins_large.py b/test_regress/t/t_covergroup_bins_large.py new file mode 100755 index 000000000..3a112c8d2 --- /dev/null +++ b/test_regress/t/t_covergroup_bins_large.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.compile(verilator_flags2=['--coverage-max-bins 1048576']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_bins_large.v b/test_regress/t/t_covergroup_bins_large.v new file mode 100644 index 000000000..90f1f1e6b --- /dev/null +++ b/test_regress/t/t_covergroup_bins_large.v @@ -0,0 +1,148 @@ +// 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 + +// Array and automatic bins declarations of up to 1048576 bins, with --coverage-max-bins +// raising the limit of one declaration. Their sample() code must not grow with their +// number of bins. + +// 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; + logic [32:0] data33; + logic [19:0] data20; + logic [30:0] data31; + logic signed [14:0] data15; + + // 1048576 automatic bins of 8192 values + covergroup cg_auto; + option.auto_bin_max = 1048576; + coverpoint data33; + endgroup + + // 1048576 single-value bins, over the whole domain + covergroup cg_array; + coverpoint data20 { + bins b[] = {[0 : $]}; + } + endgroup + + // 1000000 implicit automatic bins of 2147 values, the last also holding the remainder + covergroup cg_implicit; + option.auto_bin_max = 1000000; + coverpoint data31; + endgroup + + // Ignored values leave 16384 of the 32768 single-value bins without values + covergroup cg_array_ignore; + coverpoint data15 { + bins b[] = {[-16384 : 16383]}; + ignore_bins neg = {[$ : -1]}; + } + endgroup + + // 1000 automatic bins of 32 values, the last also holding the remainder: ignored + // values leave the first 10 bins without values + covergroup cg_auto_ignore; + option.auto_bin_max = 1000; + coverpoint data15 { + ignore_bins low = {[$ : -16065]}; + } + endgroup + + initial begin + automatic cg_auto cga = new; + automatic cg_array cgr = new; + automatic cg_implicit cgi = new; + automatic cg_array_ignore cgri = new; + automatic cg_auto_ignore cgai = new; + + // Coverage is expected as get_inst_coverage() computes it: 100 * (covered / total) + + // Bin boundaries + data33 = 0; + cga.sample(); + data33 = 8191; + cga.sample(); + `checkr(cga.get_inst_coverage(), 100.0 * (1.0 / 1048576)); + data33 = 8192; + cga.sample(); + data33 = '1; + cga.sample(); + `checkr(cga.get_inst_coverage(), 100.0 * (3.0 / 1048576)); + // Every bin + for (int i = 0; i < 1048576; ++i) begin + data33 = 33'(i) * 8192 + 33'((i * 7) % 8192); + cga.sample(); + end + `checkr(cga.get_inst_coverage(), 100.0); + + data20 = 0; + cgr.sample(); + data20 = '1; + cgr.sample(); + `checkr(cgr.get_inst_coverage(), 100.0 * (2.0 / 1048576)); + for (int i = 0; i < 1048576; ++i) begin + data20 = 20'(i); + cgr.sample(); + end + `checkr(cgr.get_inst_coverage(), 100.0); + + data31 = 0; + cgi.sample(); + data31 = 2146; + cgi.sample(); + `checkr(cgi.get_inst_coverage(), 100.0 * (1.0 / 1000000)); + data31 = 2147; + cgi.sample(); + data31 = 31'd2146997852; // last value of bin 999998 + cgi.sample(); + `checkr(cgi.get_inst_coverage(), 100.0 * (3.0 / 1000000)); + data31 = 31'd2146997853; // first value of the last bin + cgi.sample(); + data31 = '1; // in the remainder of the last bin + cgi.sample(); + `checkr(cgi.get_inst_coverage(), 100.0 * (4.0 / 1000000)); + for (int i = 0; i < 1000000; ++i) begin + data31 = 31'(i) * 2147 + 31'(i % 2147); + cgi.sample(); + end + `checkr(cgi.get_inst_coverage(), 100.0); + + data15 = -1; + cgri.sample(); + `checkr(cgri.get_inst_coverage(), 0.0); + data15 = 0; + cgri.sample(); + data15 = 16383; + cgri.sample(); + `checkr(cgri.get_inst_coverage(), 100.0 * (2.0 / 16384)); + for (int i = -16384; i < 16384; ++i) begin + data15 = 15'(i); + cgri.sample(); + end + `checkr(cgri.get_inst_coverage(), 100.0); + + data15 = -16065; + cgai.sample(); + `checkr(cgai.get_inst_coverage(), 0.0); + data15 = -16064; // first value of bin 10 + cgai.sample(); + data15 = 16383; // in the remainder of the last bin + cgai.sample(); + `checkr(cgai.get_inst_coverage(), 100.0 * (2.0 / 990)); + for (int i = -16384; i < 16384; ++i) begin + data15 = 15'(i); + cgai.sample(); + end + `checkr(cgai.get_inst_coverage(), 100.0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_exclusions_unsup.out b/test_regress/t/t_covergroup_exclusions_unsup.out index 763782ea1..a6d21260d 100644 --- a/test_regress/t/t_covergroup_exclusions_unsup.out +++ b/test_regress/t/t_covergroup_exclusions_unsup.out @@ -3,22 +3,26 @@ 14 | bins text = {TEXT}; | ^~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:21:29: Unsupported: non-integral value in a coverage bin of an integral coverpoint. +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:15:28: Unsupported: non-integral value in a coverage bin of an integral coverpoint. : ... note: In instance 't' - 21 | wildcard bins text = {TEXT}; + 15 | bins text_array[] = {TEXT}; + | ^~~~ +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:22:29: Unsupported: non-integral value in a coverage bin of an integral coverpoint. + : ... note: In instance 't' + 22 | wildcard bins text = {TEXT}; | ^~~~ -%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:27:25: Unsupported: non-integral value in a transition bin of a coverpoint with exclusions. +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:28:25: Unsupported: non-integral value in a transition bin of a coverpoint with exclusions. : ... note: In instance 't' - 27 | bins text = (1 => TEXT); + 28 | bins text = (1 => TEXT); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:40:59: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:41:59: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 40 | static_cross: cross cp_real, cp_plain{bins selected = binsof (cp_real) intersect {1};} + 41 | static_cross: cross cp_real, cp_plain{bins selected = binsof (cp_real) intersect {1};} | ^~~~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:41:62: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:42:62: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 41 | dynamic_cross: cross cp_real, cp_dynamic{bins selected = binsof (cp_real) intersect {1};} + 42 | dynamic_cross: cross cp_real, cp_dynamic{bins selected = binsof (cp_real) intersect {1};} | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_exclusions_unsup.v b/test_regress/t/t_covergroup_exclusions_unsup.v index 93f3cb736..834a67db8 100644 --- a/test_regress/t/t_covergroup_exclusions_unsup.v +++ b/test_regress/t/t_covergroup_exclusions_unsup.v @@ -12,6 +12,7 @@ module t; covergroup cg_values; cp: coverpoint value { bins text = {TEXT}; + bins text_array[] = {TEXT}; ignore_bins ignored = {0}; } endgroup diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index c280ef0b4..7cc321fde 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -1,11 +1,13 @@ cg.data.arr[0] [ignore]: 0 cg.data.arr[1] [ignore]: 0 +cg.data.arr_rng[0] [ignore]: 0 +cg.data.arr_rng[1] [ignore]: 1 cg.data.bad[0] [illegal]: 0 cg.data.bad[1] [illegal]: 0 cg.data.catch_all [ignore]: 0 cg.data.high: 1 cg.data.low: 1 -cg.data.reserved [ignore]: 1 +cg.data.reserved [ignore]: 2 cg.data.wib [ignore]: 0 cg2.cp_auto.auto_0: 1 cg2.cp_auto.auto_1: 1 diff --git a/test_regress/t/t_covergroup_ignore_bins.v b/test_regress/t/t_covergroup_ignore_bins.v index 2f81b9912..9e3626d2e 100644 --- a/test_regress/t/t_covergroup_ignore_bins.v +++ b/test_regress/t/t_covergroup_ignore_bins.v @@ -22,6 +22,7 @@ module t; ignore_bins reserved = {[12 : 15]}; ignore_bins catch_all = default; // default ignore-bin: all values not in other bins are ignored ignore_bins arr[] = {4, 5}; // array form: one ignore-bin per value + ignore_bins arr_rng[] = {[14 : 15]}; // array form of a range: one ignore-bin per value wildcard ignore_bins wib = {4'b1?00}; // wildcard ignore-bin with don't-care bits illegal_bins bad[] = {6, 7}; // illegal array form: one illegal-bin per value } @@ -66,6 +67,9 @@ module t; data = 13; cg_inst.sample(); // reserved - ignored `checkr(cg_inst.get_inst_coverage(), 0.0); + data = 15; + cg_inst.sample(); // reserved and arr_rng[1] - ignored + `checkr(cg_inst.get_inst_coverage(), 0.0); data = 1; cg_inst.sample(); // low `checkr(cg_inst.get_inst_coverage(), 50.0); diff --git a/test_regress/t/t_covergroup_max_bins.py b/test_regress/t/t_covergroup_max_bins.py new file mode 100755 index 000000000..fa68875de --- /dev/null +++ b/test_regress/t/t_covergroup_max_bins.py @@ -0,0 +1,16 @@ +#!/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') + +test.lint(verilator_flags2=['--coverage-max-bins 4294967295']) + +test.passes() diff --git a/test_regress/t/t_covergroup_max_bins.v b/test_regress/t/t_covergroup_max_bins.v new file mode 100644 index 000000000..507bc0be1 --- /dev/null +++ b/test_regress/t/t_covergroup_max_bins.v @@ -0,0 +1,28 @@ +// 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 + +// Bins declarations up to the largest --coverage-max-bins, 4294967295 (2^32 - 1), are +// accepted. They are too many bins to simulate, so are only linted. + +module t; + logic [31:0] data; + + covergroup cg; + cp_array: coverpoint data { + bins at_limit[] = {[0 : 32'hffff_fffe]}; + } + // Beyond a signed 32-bit size + cp_auto: coverpoint data { + bins auto[32'd4294967295]; + } + cp_implicit: coverpoint data { + option.auto_bin_max = 2147483647; + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_max_bins_unsup.out b/test_regress/t/t_covergroup_max_bins_unsup.out new file mode 100644 index 000000000..59b05a11d --- /dev/null +++ b/test_regress/t/t_covergroup_max_bins_unsup.out @@ -0,0 +1,24 @@ +%Warning-COVERIGN: t/t_covergroup_max_bins_unsup.v:17:12: Unsupported: array 'bins' covering more than 4294967295 values (e.g. an open '[lo:$]' range over a wide coverpoint); bin ignored + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-bins + 17 | bins over_limit[] = {[0 : $]}; + | ^~~~~~~~~~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: t/t_covergroup_max_bins_unsup.v:21:12: Automatic bins array size of 4294967296 exceeds limit of 4294967295 + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-bins + 21 | bins auto[33'd4294967296]; + | ^~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error-UNSUPPORTED: t/t_covergroup_max_bins_unsup.v:24:15: Unsupported: coverpoint with more than 4294967295 bins + : ... note: In instance 't' + 24 | cp_total: coverpoint data { + | ^~~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Warning-COVERIGN: t/t_covergroup_max_bins_unsup.v:30:12: Unsupported: array 'bins' of a real coverpoint covering more than 2 values; bin ignored. + : ... note: In instance 't' + : ... Suggest a larger --coverage-max-real-bins + 30 | bins over_limit[] = {[1 : 3]}; + | ^~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_max_bins_unsup.py b/test_regress/t/t_covergroup_max_bins_unsup.py new file mode 100755 index 000000000..ab16843aa --- /dev/null +++ b/test_regress/t/t_covergroup_max_bins_unsup.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') + +test.lint(verilator_flags2=['--coverage-max-bins 4294967295 --coverage-max-real-bins 2'], + fails=True, + expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_max_bins_unsup.v b/test_regress/t/t_covergroup_max_bins_unsup.v new file mode 100644 index 000000000..d35fb7998 --- /dev/null +++ b/test_regress/t/t_covergroup_max_bins_unsup.v @@ -0,0 +1,36 @@ +// 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 + +// Bins beyond the largest --coverage-max-bins, 4294967295 (2^32 - 1), and array bins of a +// real coverpoint beyond their own limit, --coverage-max-real-bins 2 + +module t; + logic [31:0] data; + real rdata; + + covergroup cg; + // Warning (COVERIGN): array bins over the limit + cp_array_over: coverpoint data { + bins over_limit[] = {[0 : $]}; + } + // Error: automatic bins over the limit + cp_auto_over: coverpoint data { + bins auto[33'd4294967296]; + } + // Error: more bins in a coverpoint than the runtime indexes + cp_total: coverpoint data { + bins at_limit[] = {[0 : 32'hffff_fffe]}; + bins one = {0}; + } + cp_real: coverpoint rdata { + bins at_limit[] = {[1 : 2]}; + bins over_limit[] = {[1 : 3]}; // Warning (COVERIGN): 3 values + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_flag_coverage_max_bins_bad.out b/test_regress/t/t_flag_coverage_max_bins_bad.out new file mode 100644 index 000000000..f25a1d19c --- /dev/null +++ b/test_regress/t/t_flag_coverage_max_bins_bad.out @@ -0,0 +1,4 @@ +%Error: --coverage-max-bins requires an integer from 1 to 4294967295, but '4294967296' was passed + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: --coverage-max-real-bins requires an integer from 1 to 4294967295, but '0' was passed +%Error: Exiting due to diff --git a/test_regress/t/t_flag_coverage_max_bins_bad.py b/test_regress/t/t_flag_coverage_max_bins_bad.py new file mode 100755 index 000000000..85375ae96 --- /dev/null +++ b/test_regress/t/t_flag_coverage_max_bins_bad.py @@ -0,0 +1,20 @@ +#!/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') +test.top_filename = "t/t_flag_werror.v" + +test.lint( + fails=True, + verilator_flags=["--lint-only --coverage-max-bins 4294967296 --coverage-max-real-bins 0"], + expect_filename=test.golden_filename) + +test.passes()