From a933e723c2b3ac9dcef334b2053cc232a43a7493 Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Sun, 27 Sep 2026 19:57:38 +0200 Subject: [PATCH] Support covergroup fixed-size bin arrays (#8514) --- include/verilated_covergroup.cpp | 251 ++++++++++++- include/verilated_covergroup.h | 28 ++ include/verilatedos.h | 1 + src/V3AstAttr.h | 7 + src/V3AstNodeOther.h | 3 +- src/V3Covergroup.cpp | 344 +++++++++++++++++- src/V3EmitV.cpp | 5 + src/V3ParseGrammar.h | 21 ++ src/V3Width.cpp | 2 +- src/verilog.y | 80 ++-- .../t_covergroup_coverpoint_method_unsup.out | 5 - .../t/t_covergroup_exclusions_unsup.out | 34 +- .../t/t_covergroup_exclusions_unsup.v | 13 + test_regress/t/t_covergroup_sized_bad.out | 26 ++ test_regress/t/t_covergroup_sized_bad.py | 16 + test_regress/t/t_covergroup_sized_bad.v | 27 ++ test_regress/t/t_covergroup_sized_bins.out | 73 ++++ test_regress/t/t_covergroup_sized_bins.py | 15 + test_regress/t/t_covergroup_sized_bins.v | 261 +++++++++++++ .../t/t_covergroup_sized_dollar_bad.out | 11 + .../t/t_covergroup_sized_dollar_bad.py | 16 + .../t/t_covergroup_sized_dollar_bad.v | 24 ++ .../t/t_covergroup_sized_no_coverage.py | 19 + test_regress/t/t_covergroup_sized_run_bad.out | 6 + test_regress/t/t_covergroup_sized_run_bad.py | 22 ++ test_regress/t/t_covergroup_sized_run_bad.v | 97 +++++ test_regress/t/t_covergroup_unsup.out | 52 +-- test_regress/t/t_covergroup_unsup.v | 2 + test_regress/t/t_covergroup_unsup_ign2.out | 86 ++--- test_regress/t/t_debug_emitv.out | 4 +- test_regress/t/t_debug_emitv.v | 1 + 31 files changed, 1400 insertions(+), 152 deletions(-) create mode 100644 test_regress/t/t_covergroup_sized_bad.out create mode 100755 test_regress/t/t_covergroup_sized_bad.py create mode 100644 test_regress/t/t_covergroup_sized_bad.v create mode 100644 test_regress/t/t_covergroup_sized_bins.out create mode 100755 test_regress/t/t_covergroup_sized_bins.py create mode 100644 test_regress/t/t_covergroup_sized_bins.v create mode 100644 test_regress/t/t_covergroup_sized_dollar_bad.out create mode 100755 test_regress/t/t_covergroup_sized_dollar_bad.py create mode 100644 test_regress/t/t_covergroup_sized_dollar_bad.v create mode 100755 test_regress/t/t_covergroup_sized_no_coverage.py create mode 100644 test_regress/t/t_covergroup_sized_run_bad.out create mode 100755 test_regress/t/t_covergroup_sized_run_bad.py create mode 100644 test_regress/t/t_covergroup_sized_run_bad.v diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index c242a7236..acce0613a 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -56,6 +56,10 @@ struct VlCoverpoint::ValueData final { public: Value() = default; + explicit Value(uint32_t words) // Zero, of 'words' words + : m_size{words} { + if (m_size > INLINE_WORDS) m_heap.assign(m_size, 0); + } Value(const EData* beginp, const EData* endp) : m_size{static_cast(endp - beginp)} { if (m_size <= INLINE_WORDS) { @@ -94,6 +98,30 @@ struct VlCoverpoint::ValueData final { // Outcome of searching a range for a value outside every exclusion enum class Search : uint8_t { EMPTY, VALUE, WORK_LIMIT, DEPTH_LIMIT }; class Query; + // A sized array of bins, 'bins b[N] = {...}' (IEEE 1800-2023 19.5.1). Its values are the + // positions of one list, of its elements' values in declaration order. Of T values, bin k + // holds positions [k * B, (k + 1) * B), B = max(1, T / N), and its last bin all the rest. + struct SizedElement final { + Value m_lo; // First value + Value m_hi; // Last value + Value m_position; // Position of m_lo, of positionWords() words + }; + // An element without values or positions wider than 64 bits, with values in unsigned order + // (see orderValue()) + struct SizedFast final { + uint64_t m_lo; // First value + uint64_t m_hi; // Last value + uint64_t m_position; // Position of m_lo + }; + struct Sized final { + VlCovBinKind m_kind = VlCovBinKind::KIND_NORMAL; // Set of every bin + uint32_t m_first = 0; // Declared index of the first bin + uint32_t m_count = 0; // Bins holding values: min(N, T) + Value m_perBin; // B + std::vector m_elements; // In declaration order + std::vector m_fast; // The elements, if none is wider than 64 bits + uint64_t m_fastPerBin = 0; // B, with m_fast + }; // MEMBERS const uint32_t m_bits; // Width of the coverpoint's effective integral type @@ -104,6 +132,8 @@ struct VlCoverpoint::ValueData final { std::vector m_exclusions; // Normalized state ignore/illegal ranges and patterns uint32_t m_regularExclusions = 0; // Length of the merged interval prefix in m_exclusions std::vector m_reported; // Declared bins that have values, in declaration order + std::vector m_sized; // Sized arrays, in sizedFinish() order + std::vector m_sizedElements; // sizedRange() elements of the next array ValueData(uint32_t bits, bool isSigned, uint32_t bins) : m_bits{bits} @@ -113,6 +143,7 @@ struct VlCoverpoint::ValueData final { assert(m_bits); } static WDataInP view(const Value& value) { return WDataInP::external(value.data()); } + static WDataOutP out(Value& value) { return WDataOutP::external(value.data()); } Value read(WDataInP valuep) const { Value result(valuep.datap(), valuep.datap() + m_words); return result; @@ -142,10 +173,48 @@ struct VlCoverpoint::ValueData final { } // 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)); + VL_ADD_W(static_cast(m_words), out(value), view(value), view(addend)); value.back() &= VL_MASK_E(m_bits); } + // Positions in the value list of a sized array are unsigned numbers, wide enough for any + // position, and for sizedFinish()'s count + uint32_t positionWords() const { return VL_WORDS_I(m_bits + 33); } + Value toPosition(QData number) const { + Value result{positionWords()}; + VL_SET_WQ(out(result), number); + return result; + } + // The position of hi after lo, which precedes it + Value distance(WDataInP lo, WDataInP hi) const { + Value result{positionWords()}; + VL_SUB_W(static_cast(m_words), out(result), hi, lo); + result[m_words - 1] &= VL_MASK_E(m_bits); + return result; + } + int compare(const Value& lhs, const Value& rhs) const { + return _vl_cmp_w(static_cast(positionWords()), view(lhs), view(rhs)); + } + void increase(Value& position, const Value& addend) const { + VL_ADD_W(static_cast(positionWords()), out(position), view(position), view(addend)); + } + void decrease(Value& position, const Value& subtrahend) const { + VL_SUB_W(static_cast(positionWords()), out(position), view(position), + view(subtrahend)); + } + void quotient(const Value& dividend, const Value& divisor, Value& result) const { + VL_DIV_WWW(static_cast(positionWords() * VL_EDATASIZE), out(result), view(dividend), + view(divisor)); + } + // Whether a position is below 2^64, so that number() is its value + bool narrow(const Value& position) const { + return VL_MOSTSETBITP1_W(static_cast(positionWords()), view(position)) <= VL_QUADSIZE; + } + // A narrow() position, or a value of at most 64 bits, whose second inline word is then zero + static uint64_t number(const Value& position) { return VL_SET_QW(view(position)); } + // A value of at most 64 bits in unsigned order: the sign bit of a signed value flipped + uint64_t orderValue(uint64_t value) const { + return m_isSigned ? value ^ VL_BIT_Q(m_bits - 1) : value; + } 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()) { @@ -569,6 +638,171 @@ bool VlCoverpoint::valueExcluded(QData value) const { bool VlCoverpoint::valueExcludedW(WDataInP valuep) const { return m_valuesp->excluded(valuep); } +void VlCoverpoint::sizedRange(QData lo, QData hi) { + VlWide low; + VlWide high; + VL_SET_WQ(low, lo); + VL_SET_WQ(high, hi); + sizedRangeW(low, high); +} + +void VlCoverpoint::sizedRangeW(WDataInP lop, WDataInP hip) { + ValueData& data = *m_valuesp; + assert(!data.less(hip, lop)); // Elements without a coverpoint value are omitted + data.m_sizedElements.push_back({data.read(lop), data.read(hip), {}}); +} + +void VlCoverpoint::sizedFinish(VlCovBinKind kind, QData count, bool positive, uint32_t limit, + const char* name, const char* file, int line, int col) { + ValueData& data = *m_valuesp; + assert(!data.m_frozen); + data.m_sized.emplace_back(); + ValueData::Sized& sized = data.m_sized.back(); + sized.m_kind = kind; + sized.m_first = m_total; + sized.m_elements.swap(data.m_sizedElements); + if (VL_UNLIKELY(!positive)) { + // An error, after which (+verilator+error+limit) the array has no bins + sized.m_elements.clear(); + VL_PRINTF_MT("%%Error: %s:%d: Coverage bin array size must be a positive integer" + " (IEEE 1800-2023 19.5.1)\n", + file, line); + VL_STOP_MT(file, line, ""); + return; + } + // The position of each element's first value, and the number of values + const ValueData::Value one = data.toPosition(1); + ValueData::Value total{data.positionWords()}; + for (ValueData::SizedElement& element : sized.m_elements) { + element.m_position = total; + data.increase(total, + data.distance(ValueData::view(element.m_lo), ValueData::view(element.m_hi))); + data.increase(total, one); + } + const ValueData::Value declared = data.toPosition(count); + const bool fewer = data.compare(total, declared) < 0; // Values for fewer bins than declared + const ValueData::Value& bins = fewer ? total : declared; + if (data.compare(bins, data.toPosition(std::min(limit, UINT32_MAX - m_total))) > 0) { + sized.m_elements.clear(); + VL_WARN_MT(file, line, "", + "Coverage bin array needs more bins than --coverage-max-bins; bin ignored"); + return; + } + sized.m_count = bins[0]; + if (!sized.m_count) return; + sized.m_perBin = one; + if (!fewer) data.quotient(total, declared, sized.m_perBin); + assert(m_nextBase == m_total); + m_total += sized.m_count; + m_counts.resize(m_total, 0); + m_crossIdx.resize(m_total, -1); + data.m_values.resize(m_total); + addNamer(kind, sized.m_count, VlCovBinNaming::Array, name, file, line, col); + // Give each bin the values of its positions, for exclusions and cross selections + size_t element = 0; + ValueData::Value first{data.positionWords()}; // First position of bin k + for (uint32_t k = 0; k < sized.m_count; ++k) { + ValueData::Value last = total; // Last position of bin k + if (k + 1 < sized.m_count) { + last = first; + data.increase(last, sized.m_perBin); + } + data.decrease(last, one); + while (data.compare(first, last) <= 0) { + ValueData::Value end = element + 1 < sized.m_elements.size() + ? sized.m_elements[element + 1].m_position + : total; + data.decrease(end, one); // Last position of the element + if (data.compare(end, first) < 0) { + ++element; + continue; + } + const ValueData::SizedElement& source = sized.m_elements[element]; + const ValueData::Value& upper = data.compare(last, end) < 0 ? last : end; + ValueData::Value from = first; // Positions from the element's first value + ValueData::Value to = upper; + data.decrease(from, source.m_position); + data.decrease(to, source.m_position); + ValueData::Value lo = source.m_lo; + ValueData::Value hi = source.m_lo; + data.add(lo, from); + data.add(hi, to); + data.m_values[sized.m_first + k].m_ranges.push_back({lo, hi, {}}); + first = upper; + data.increase(first, one); + } + } + if (data.m_bits <= 64 && data.narrow(total)) { + for (const ValueData::SizedElement& source : sized.m_elements) { + sized.m_fast.push_back({data.orderValue(ValueData::number(source.m_lo)), + data.orderValue(ValueData::number(source.m_hi)), + ValueData::number(source.m_position)}); + } + sized.m_fastPerBin = ValueData::number(sized.m_perBin); + } +} + +uint32_t VlCoverpoint::sizedFirst(uint32_t sized) const { + return m_valuesp->m_sized[sized].m_first; +} + +uint32_t VlCoverpoint::sizedEnd(uint32_t sized) const { + const ValueData::Sized& data = m_valuesp->m_sized[sized]; + return data.m_first + data.m_count; +} + +bool VlCoverpoint::sizedSample(uint32_t sized, QData value, bool enabled) { + ValueData& data = *m_valuesp; + const ValueData::Sized& array = data.m_sized[sized]; + if (array.m_fast.empty() && !array.m_elements.empty()) { // Positions beyond 64 bits + VlWide words; + VL_SET_WQ(words, value); + return sizedSampleW(sized, words, enabled); + } + const uint64_t ordered = data.orderValue(value); + uint32_t last = UINT32_MAX; // No bin yet; bins index below UINT32_MAX + for (const ValueData::SizedFast& element : array.m_fast) { + if (ordered < element.m_lo || ordered > element.m_hi) continue; + const uint64_t bin = (element.m_position + (ordered - element.m_lo)) / array.m_fastPerBin; + // Bins past the last hold none; it holds the remaining values + sizedHit(array.m_kind, + array.m_first + static_cast(std::min(bin, array.m_count - 1)), + enabled, last); + } + return last != UINT32_MAX; +} + +bool VlCoverpoint::sizedSampleW(uint32_t sized, WDataInP valuep, bool enabled) { + ValueData& data = *m_valuesp; + const ValueData::Sized& array = data.m_sized[sized]; + uint32_t last = UINT32_MAX; // No bin yet; bins index below UINT32_MAX + for (const ValueData::SizedElement& element : array.m_elements) { + if (data.less(valuep, element.m_lo) || data.less(element.m_hi, valuep)) continue; + ValueData::Value position = data.distance(ValueData::view(element.m_lo), valuep); + data.increase(position, element.m_position); + ValueData::Value bin{data.positionWords()}; + data.quotient(position, array.m_perBin, bin); + const bool past = !data.narrow(bin) || ValueData::number(bin) >= array.m_count; + sizedHit(array.m_kind, + array.m_first + (past ? array.m_count - 1 : static_cast(bin[0])), + enabled, last); + } + return last != UINT32_MAX; +} + +void VlCoverpoint::sizedHit(VlCovBinKind kind, uint32_t bin, bool enabled, uint32_t& last) { + // The elements hold consecutive positions, so the bins holding a value come in order, and + // one that two elements share repeats only in a row: count it once (IEEE 1800-2023 19.5) + if (bin == last) return; + last = bin; + if (!enabled) return; + if (kind == VlCovBinKind::KIND_NORMAL) { + incrementNormalBin(bin); + } else { + recordHit(bin); + } +} + void VlCoverpoint::init(const char* hier, uint32_t atLeast, uint32_t nBins) { m_hier = hier; m_atLeast = atLeast; @@ -1044,9 +1278,16 @@ void VlCoverCrossDyn::init(const char* hier, uint32_t dims, VlCoverpoint* const* [](const VlCoverpoint* cpp) { return !cpp->normalBinCount(); }) ? 0 : 1; - for (uint32_t i = 0; i < dims; ++i) tuples *= cps[i]->normalBinCount(); - // Verilation bounds the product of the declared bins, which live bins cannot exceed. - assert(tuples <= UINT32_MAX); + for (uint32_t i = 0; i < dims && tuples <= UINT32_MAX; ++i) { + tuples *= cps[i]->normalBinCount(); + } + if (VL_UNLIKELY(tuples > UINT32_MAX)) { + // Verilation ignores a larger product of the declared bins (COVERIGN), but sized bin + // arrays get their bins at construction + VL_WARN_MT(file, line, "", + "Unsupported: cross coverage with more than 2^32-1 tuples; cross ignored"); + tuples = 0; + } data.m_tuples = static_cast(tuples); data.m_words = VL_BITWORD_Q(static_cast(data.m_tuples) + VL_QUADSIZE - 1); data.m_dimensions.resize(dims); diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index e99d75208..71b879173 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -121,6 +121,10 @@ protected: std::vector m_crossToBin; uint32_t m_hitCount = 0; // entries valid in the hit list this sample + // PROTECTED METHODS + // Normal bin: VlCoverpointT::incrementBin(), for the bins sizedSample() finds + virtual void incrementNormalBin(uint32_t i) = 0; + private: // PRIVATE METHODS const VlCovNamer& namerFor(uint32_t i) const; // obtain the bin-specific name producer @@ -130,6 +134,9 @@ private: uint32_t reportedBin(uint32_t i) const; std::string declaredBinName(uint32_t bin) const; // Name of a declared bin index bool liveBin(uint32_t bin) const; // Normal bin keeps a value outside the exclusions + // Count a sample, if enabled, in a bin of a sized array holding the value, unless it is + // 'last', the bin found before; set 'last' + void sizedHit(VlCovBinKind kind, uint32_t bin, bool enabled, uint32_t& last); public: // CONSTRUCTORS @@ -171,12 +178,30 @@ public: /// Test state exclusions independently of sampling-time iff guards. bool valueExcluded(QData value) const; bool valueExcludedW(WDataInP valuep) const; + /// Add the coverpoint values lo..hi of the next range list element of a sized array of + /// bins, in declaration order. + void sizedRange(QData lo, QData hi); + void sizedRangeW(WDataInP lop, WDataInP hip); + /// Distribute the values sizedRange() added over the bins of the sized array 'name[count]' + /// (IEEE 1800-2023 19.5.1). 'positive' is false for a count below one, which is invalid. + /// At most 'limit' bins may hold values. Needs valueType(); bins append after those of + /// init(). + void sizedFinish(VlCovBinKind kind, QData count, bool positive, uint32_t limit, + const char* name, const char* file, int line, int col); + /// Declared bins [sizedFirst(), sizedEnd()) of the sized array 'sized', counted in + /// sizedFinish() order, for cross selections. + uint32_t sizedFirst(uint32_t sized) const; + uint32_t sizedEnd(uint32_t sized) const; // ---- hot path (from generated sample()) ---- // Clear the hit list at the start of each sample() for a cross-fed coverpoint. void clearHitList() { m_hitCount = 0; } // Ignore/Illegal/Default: count only; never propagates to cross coverage. void recordHit(uint32_t i) { ++m_counts[i]; } + /// Count a sample in the bins of the sized array 'sized' holding the value, once each, + /// if 'enabled'. True if a bin holds the value, enabled or not. + bool sizedSample(uint32_t sized, QData value, bool enabled); + bool sizedSampleW(uint32_t sized, WDataInP valuep, bool enabled); // incrementBin (Normal bin: count + hit-list append) lives in VlCoverpointT, // where MaxHits is the gen-time max per-sample bin overlap. @@ -234,6 +259,9 @@ public: if (cx >= 0 && m_hitCount < MaxHits) m_hits[m_hitCount++] = static_cast(cx); } const uint32_t* hitList() const override { return m_hits; } + +protected: + void incrementNormalBin(uint32_t i) override { incrementBin(i); } }; //============================================================================= diff --git a/include/verilatedos.h b/include/verilatedos.h index b593fc377..e9d7615e1 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -548,6 +548,7 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read() #define VL_BITBIT_I(bit) ((bit) & VL_SIZEBITS_I) ///< Bit number for a bit in a long #define VL_BITBIT_Q(bit) ((bit) & VL_SIZEBITS_Q) ///< Bit number for a bit in a quad #define VL_BITBIT_E(bit) ((bit) & VL_SIZEBITS_E) ///< Bit number for a bit in an EData +#define VL_BIT_Q(bit) (1ULL << VL_BITBIT_Q(bit)) ///< Quad with only the given bit set // Return true if data[bit] set; not 0/1 return, but 0/non-zero return. #define VL_BITISSET_I(data, bit) ((data) & (VL_UL(1) << VL_BITBIT_I(bit))) diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 2646ad446..508ad640a 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -897,6 +897,13 @@ inline std::ostream& operator<<(std::ostream& os, const VBranchPred& rhs) { macro(COVERGROUP_SELECT_OR, "selectOr", false, "") \ macro(COVERGROUP_SELECT_RANGE, "selectRange", false, "rr") \ macro(COVERGROUP_SELECT_RANGE_W, "selectRangeW", false, "rr") \ + macro(COVERGROUP_SIZED_END, "sizedEnd", PURE, "r") \ + macro(COVERGROUP_SIZED_FINISH, "sizedFinish", false, "r+") \ + macro(COVERGROUP_SIZED_FIRST, "sizedFirst", PURE, "r") \ + macro(COVERGROUP_SIZED_RANGE, "sizedRange", false, "rr") \ + macro(COVERGROUP_SIZED_RANGE_W, "sizedRangeW", false, "rr") \ + macro(COVERGROUP_SIZED_SAMPLE, "sizedSample", false, "rrr") \ + macro(COVERGROUP_SIZED_SAMPLE_W, "sizedSampleW", false, "rrr") \ macro(COVERGROUP_TYPE_COVERAGE, "typeCoverage", false, "rrr") \ macro(COVERGROUP_VALUE_EXCLUDED, "valueExcluded", PURE, "r") \ macro(COVERGROUP_VALUE_EXCLUDED_W, "valueExcludedW", PURE, "r") \ diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 3984b1bd3..31263b331 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1042,7 +1042,8 @@ class AstCoverBin final : public AstNode { // @astgen op4 := transp : List[AstCoverTransSet] const string m_name; // Base name of the bin const VCoverBinsType m_binsType; // Bin type (eg AUTO, IGNORE, ILLEGAL) - bool m_isArray = false; // Bin is either an auto-sized array of values or transitions + bool m_isArray = false; // Bin is an array: of a bin per value or transition, or of + // arraySizep() bins bool m_isWildcard = false; // Bin uses wildcard matching (independent of ignore/illegal) public: diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 1017f3246..09c320a3c 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -274,9 +274,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { uint32_t element = 0; // Index of the bin within runp }; struct BinSpan final { - uint32_t first; // First Normal index of the bin declaration - uint32_t count; // Number of Normal bins of the declaration - uint32_t declared; // First runtime bin index, across all bin kinds + uint32_t first = 0; // First Normal index of the bin declaration + uint32_t count = 0; // Number of Normal bins of the declaration + uint32_t declared = 0; // First runtime bin index, across all bin kinds + int32_t sized = -1; // Index of the sized array, placed at construction; or -1 }; struct CoverpointBins final { uint32_t total = 0; // Number of Normal bins @@ -393,9 +394,14 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::map> consumers; std::map> inputs; for (AstCoverpoint* const cpp : m_coverpoints) { + checkSizedArrays(cpp); if (!cpp->exprp()->dtypep()->skipRefp()->isIntegralOrPacked()) continue; // Bins without values leave the report (IEEE 1800-2023 19.11.1), exclusions or not. - if (!coverpointHasStateExclusions(cpp) && !coverpointHasEmptyBins(cpp)) continue; + // A sized array of bins gets its values when the covergroup is constructed. + if (!coverpointHasStateExclusions(cpp) && !coverpointHasEmptyBins(cpp) + && !coverpointHasSizedArrays(cpp)) { + continue; + } m_runtimePoints.insert(cpp); pending.push_back(cpp); } @@ -484,6 +490,43 @@ class FunctionalCoverageVisitor final : public VNVisitor { || binp->binsType() == VCoverBinsType::BINS_AUTO_IMPLICIT; } + // True for a sized array of bins, 'bins b[N] = {...}', whose values an integral coverpoint + // distributes over N bins when the covergroup is constructed (IEEE 1800-2023 19.5.1) + static bool isSizedArray(const AstCoverBin* binp) { + return binp->arraySizep() && !isAutoBins(binp); + } + + // Check the sized arrays of bins of a coverpoint, dropping invalid ones. A real + // coverpoint's are unsupported, and treated as arrays of a bin per value. + void checkSizedArrays(AstCoverpoint* coverpointp) { + const bool integral = coverpointp->exprp()->dtypep()->skipRefp()->isIntegralOrPacked(); + for (AstNode* nodep = coverpointp->binsp(); nodep;) { + AstCoverBin* const binp = VN_AS(nodep, CoverBin); + nodep = nodep->nextp(); + if (!isSizedArray(binp)) continue; + AstNodeExpr* const sizep = binp->arraySizep(); + const AstConst* const constp = VN_CAST(sizep, Const); + if (VN_IS(sizep, Unbounded)) { // A parameter of '$'; see bins_orBraE + binp->v3error("Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1)"); + } else if (!sizep->dtypep()->skipRefp()->isIntegralOrPacked()) { + sizep->v3error("Bins array size must be integral (IEEE 1800-2023 19.5.1)"); + } else if (constp && (constp->num().isFourState() || binsCount(constp) < 1)) { + sizep->v3error("Bins array size must be >= 1, got " + << (constp->num().isFourState() ? constp->num().ascii(false) + : constp->num().toDecimalS()) + << " (IEEE 1800-2023 19.5.1)"); + } else if (!integral) { + binp->v3warn(COVERIGN, "Unsupported: 'bins' explicit array size of a real " + "coverpoint (treated as '[]')"); + VL_DO_DANGLING(pushDeletep(sizep->unlinkFrBack()), sizep); + continue; + } else { + continue; + } + VL_DO_DANGLING(pushDeletep(binp->unlinkFrBack()), binp); + } + } + // 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) { @@ -720,6 +763,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { || cbinp->binsType() == VCoverBinsType::BINS_IGNORE || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) continue; + // The values of a sized array are known at construction; see emitSizedSample + if (isSizedArray(cbinp)) continue; if (isAutoBins(cbinp)) { // Automatic bins partition the whole domain, leaving no default value if (anyBinMatchp) VL_DO_DANGLING(pushDeletep(anyBinMatchp), anyBinMatchp); @@ -749,6 +794,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { return false; } + static bool coverpointHasSizedArrays(const AstCoverpoint* coverpointp) { + for (const AstNode* nodep = coverpointp->binsp(); nodep; nodep = nodep->nextp()) { + if (isSizedArray(VN_AS(nodep, CoverBin))) return true; + } + return false; + } + // True if a Normal state bin, or an array-bin element, has no value of the coverpoint's // type (IEEE 1800-2023 19.5.7). Array ranges enumerate in-type values, so cannot vanish. static bool coverpointHasEmptyBins(const AstCoverpoint* coverpointp) { @@ -1265,6 +1317,22 @@ class FunctionalCoverageVisitor final : public VNVisitor { tgt.isNormal); } + // The condition under which a bin counts a sample: condp, and the bin's iff, and for a + // Normal or default state bin, that the value is not excluded, and the coverpoint's iff + AstNodeExpr* binCondition(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, + AstNodeExpr* condp) { + FileLine* const fl = binp->fileline(); + if (binp->iffp()) condp = new AstLogAnd{fl, binp->iffp()->cloneTree(false), condp}; + const auto excluded = m_excludedVars.find(cpVarp); + if (excluded != m_excludedVars.end() && !binp->transp() + && (binp->binsType().binIsNormal() + || binp->binsType() == VCoverBinsType::BINS_DEFAULT)) { + condp = new AstLogAnd{ + fl, new AstNot{fl, new AstVarRef{fl, excluded->second, VAccess::READ}}, condp}; + } + return applyCoverpointIffCondition(coverpointp, fl, condp); + } + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, AstNodeExpr* idxp, AstNodeExpr* condp) { FileLine* const fl = binp->fileline(); @@ -1274,17 +1342,201 @@ class FunctionalCoverageVisitor final : public VNVisitor { + " hit in coverpoint " + coverpointp->prettyNameQ())); } - if (binp->iffp()) condp = new AstLogAnd{fl, binp->iffp()->cloneTree(false), condp}; - const auto excluded = m_excludedVars.find(cpVarp); - if (excluded != m_excludedVars.end() && !binp->transp() - && (binp->binsType().binIsNormal() - || binp->binsType() == VCoverBinsType::BINS_DEFAULT)) { - condp = new AstLogAnd{ - fl, new AstNot{fl, new AstVarRef{fl, excluded->second, VAccess::READ}}, condp}; - } - AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for coverpoint"); - m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); + m_sampleFuncp->addStmtsp( + new AstIf{fl, binCondition(coverpointp, binp, cpVarp, condp), actionp, nullptr}); + } + + // Emit the sample() code of sized array 'sized': count the value in its bins holding it when + // enabled, as other bins are, and for default bins note in matchedp whether any holds it + void emitSizedSample(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, + AstNodeExpr* exprp, uint32_t sized, AstVar* matchedp) { + FileLine* const fl = binp->fileline(); + AstNodeExpr* const enabledp + = binCondition(coverpointp, binp, cpVarp, new AstConst{fl, AstConst::BitTrue{}}); + AstCMethodHard* const callp + = itemCall(fl, cpVarp, + exprp->isWide() ? VCMethod::COVERGROUP_SIZED_SAMPLE_W + : VCMethod::COVERGROUP_SIZED_SAMPLE, + {cnum(fl, sized), exprp->cloneTree(false), enabledp}); + callp->dtypeSetBit(); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for coverpoint"); + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + m_sampleFuncp->addStmtsp( + new AstIf{fl, new AstLogAnd{fl, callp, enabledp->cloneTree(false)}, + makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + + " hit in coverpoint " + + coverpointp->prettyNameQ())}); + } else if (matchedp && binp->binsType().binIsNormal()) { + m_sampleFuncp->addStmtsp( + new AstAssign{fl, new AstVarRef{fl, matchedp, VAccess::WRITE}, + new AstOr{fl, new AstVarRef{fl, matchedp, VAccess::READ}, callp}}); + } else { + m_sampleFuncp->addStmtsp(callp->makeStmt()); + } + } + + // A variable local to the constructor + AstVar* constructorTemp(FileLine* fl, const string& name, AstNodeDType* dtypep) { + AstVar* const varp = new AstVar{fl, VVarType::BLOCKTEMP, name, dtypep}; + varp->funcLocal(true); + m_constructorp->addStmtsp(varp); + return varp; + } + + // Truncate or extend, as its signedness sets, a value to a type + static AstNodeExpr* resizeValue(AstNodeExpr* valuep, AstNodeDType* dtypep) { + FileLine* const fl = valuep->fileline(); + if (valuep->width() > dtypep->width()) { + valuep = new AstSel{fl, valuep, 0, dtypep->width()}; + } else if (valuep->width() < dtypep->width()) { + valuep = valuep->isSigned() + ? static_cast(new AstExtendS{fl, valuep, dtypep->width()}) + : new AstExtend{fl, valuep, dtypep->width()}; + } + valuep->dtypep(dtypep); + return valuep; + } + + // Emit the constructor code building the sized array of bins 'binp': the values of each + // element that are coverpoint values (IEEE 1800-2023 19.5.7), then its bins + void generateSizedArray(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, + AstNodeExpr* exprp) { + FileLine* const fl = binp->fileline(); + const string prefix + = "__Vsized_" + sanitizeGeneratedName(coverpointp->name() + "__" + binp->name()); + AstNodeExpr* const sizep = binp->arraySizep(); + AstVar* const countp = constructorTemp(fl, prefix + "_count", sizep->dtypep()); + m_constructorp->addStmtsp( + new AstAssign{fl, new AstVarRef{fl, countp, VAccess::WRITE}, sizep->cloneTree(false)}); + uint32_t element = 0; + for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) { + if (VN_IS(rangep, Unbounded)) { // A parameter of '$' + binp->v3error("Bins value may not be '$', which may only bound a range " + "(IEEE 1800-2023 6.20.7)"); + continue; + } + generateSizedElement(cpVarp, rangep, exprp, prefix + "_" + cvtToStr(element++)); + } + const auto countRef = [&]() { return new AstVarRef{fl, countp, VAccess::READ}; }; + AstConst* const zerop = new AstConst{fl, AstConst::DTyped{}, countp->dtypep()}; + AstNodeExpr* const positivep + = countp->isSigned() ? static_cast(new AstGtS{fl, countRef(), zerop}) + : new AstNeq{fl, countRef(), zerop}; + // Saturate a count wider than 64 bits: min(N, T) is unchanged, or over any bins limit + AstNodeExpr* countValuep = resizeValue(countRef(), countp->findUInt64DType()); + if (countp->width() > VL_QUADSIZE) { + countValuep = new AstCond{ + fl, + new AstRedOr{ + fl, new AstSel{fl, countRef(), VL_QUADSIZE, countp->width() - VL_QUADSIZE}}, + new AstConst{fl, AstConst::Unsized64{}, std::numeric_limits::max()}, + countValuep}; + countValuep->dtypeSetUInt64(); + } + const bool prot = v3Global.opt.protectIds(); + m_constructorp->addStmtsp( + itemCall(fl, cpVarp, VCMethod::COVERGROUP_SIZED_FINISH, + {ctext(fl, binp->binsType().binSetEnum()), countValuep, positivep, + cnum(fl, binsLimit()), + ctext(fl, quoted(VIdProtect::protectWordsIf(binp->name(), prot))), + ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))), + cnum(fl, static_cast(fl->lineno())), + cnum(fl, static_cast(fl->firstColumn()))}) + ->makeStmt()); + } + + // Emit 'sizedRange(lo, hi)' for the coverpoint values of an element of a sized array of + // bins: resolved now if constant, else when constructed by clipping to the coverpoint's + // values. 'prefix' names its temporaries. + void generateSizedElement(AstVar* cpVarp, AstNode* rangep, AstNodeExpr* exprp, + const string& prefix) { + FileLine* const fl = rangep->fileline(); + const VCMethod method = exprp->isWide() ? VCMethod::COVERGROUP_SIZED_RANGE_W + : VCMethod::COVERGROUP_SIZED_RANGE; + const AstInsideRange* const irp = VN_CAST(rangep, InsideRange); + AstNodeExpr* const lowp = irp ? irp->lhsp() : VN_AS(rangep, NodeExpr); + AstNodeExpr* const highp = irp ? irp->rhsp() : nullptr; + const auto unbounded + = [](const AstNodeExpr* boundp) { return !boundp || VN_IS(boundp, Unbounded); }; + const auto constant + = [&](const AstNodeExpr* boundp) { return unbounded(boundp) || VN_IS(boundp, Const); }; + const auto integral = [&](const AstNodeExpr* boundp) { + return unbounded(boundp) || boundp->dtypep()->skipRefp()->isIntegralOrPacked(); + }; + if (constant(lowp) && constant(highp)) { + const auto fourState = [](const AstNodeExpr* boundp) { + const AstConst* const constp = VN_CAST(boundp, Const); + return constp && constp->num().isFourState(); + }; + if (irp && (fourState(lowp) || fourState(highp))) { + rangep->v3error("Four-state (x/z) value in array bins range bound; range bounds " + "must be two-state constants"); + return; + } + CrossValueRange range{rangep, resolveWidth(rangep, exprp)}; + if (!resolveValue(rangep, exprp, true, false, range)) { + rangep->v3warn(E_UNSUPPORTED, "Unsupported: non-integral value in a coverage bin " + "of an integral coverpoint."); + } else if (!crossRangeEmpty(range)) { + m_constructorp->addStmtsp(itemCall(fl, cpVarp, method, + {newValueConst(fl, range.lo, exprp), + newValueConst(fl, range.hi, exprp)}) + ->makeStmt()); + } + return; + } + if (!integral(lowp) || !integral(highp)) { + rangep->v3warn(E_UNSUPPORTED, "Unsupported: non-integral value in a coverage bin " + "of an integral coverpoint."); + return; + } + // Compare values and the coverpoint's domain signed, in a width holding all of them + const auto boundWidth + = [&](const AstNodeExpr* boundp) { return unbounded(boundp) ? 0 : boundp->width(); }; + const int width = std::max({exprp->width(), boundWidth(lowp), boundWidth(highp)}) + 1; + const CrossValueRange domain + = crossValueDomain(rangep, exprp->width(), exprp->isSigned(), width); + AstVar* const lop = constructorTemp(fl, prefix + "_lo", exprp->dtypep()); + lop->dtypeSetLogicSized(width, VSigning::SIGNED); + AstVar* const hip = constructorTemp(fl, prefix + "_hi", lop->dtypep()); + const auto bound = [&](AstNodeExpr* boundp, const V3Number& limit) -> AstNodeExpr* { + if (unbounded(boundp)) { + AstConst* const limitp = new AstConst{fl, limit}; + limitp->dtypeFrom(lop); + return limitp; + } + AstNodeExpr* valuep = boundp->cloneTree(false); + // A value no wider than a signed coverpoint has its type (see crossRangeBound) + if (exprp->isSigned() && boundp->width() <= exprp->width()) { + valuep = resizeValue(valuep, exprp->dtypep()); + } + return resizeValue(valuep, lop->dtypep()); + }; + const auto ref = [&](AstVar* varp, VAccess access = VAccess::READ) { + return new AstVarRef{fl, varp, access}; + }; + m_constructorp->addStmtsp( + new AstAssign{fl, ref(lop, VAccess::WRITE), bound(lowp, domain.lo)}); + m_constructorp->addStmtsp( + new AstAssign{fl, ref(hip, VAccess::WRITE), irp ? bound(highp, domain.hi) : ref(lop)}); + AstConst* const minp = new AstConst{fl, domain.lo}; + AstConst* const maxp = new AstConst{fl, domain.hi}; + minp->dtypeFrom(lop); + maxp->dtypeFrom(lop); + AstCond* const lowerp + = new AstCond{fl, new AstLtS{fl, ref(lop), minp}, minp->cloneTree(false), ref(lop)}; + AstCond* const upperp + = new AstCond{fl, new AstGtS{fl, ref(hip), maxp}, maxp->cloneTree(false), ref(hip)}; + lowerp->dtypeFrom(lop); + upperp->dtypeFrom(lop); + m_constructorp->addStmtsp(new AstAssign{fl, ref(lop, VAccess::WRITE), lowerp}); + m_constructorp->addStmtsp(new AstAssign{fl, ref(hip, VAccess::WRITE), upperp}); + m_constructorp->addStmtsp(new AstIf{fl, new AstLteS{fl, ref(lop), ref(hip)}, + itemCall(fl, cpVarp, method, + {resizeValue(ref(lop), exprp->dtypep()), + resizeValue(ref(hip), exprp->dtypep())}) + ->makeStmt()}); } // The runtime index of the bin of a run holding the coverpoint value, which is in the run: @@ -1416,9 +1668,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { } // Walk bins (non-default, then default), assigning sequential indices that match the - // namer append order; emit sample increments and collect namer statements. + // namer append order; emit sample increments and collect namer statements. A sized + // array's bins follow them all, placed when the coverpoint is constructed. std::vector namerStmts; std::vector defaultBins; + std::vector sizedBins; std::vector> metadata; std::vector runMetadata; uint64_t idx = 0; // Runtime index of the next bin; 32-bit once checked below @@ -1429,6 +1683,14 @@ class FunctionalCoverageVisitor final : public VNVisitor { defaultBins.push_back(cbinp); continue; } + if (isSizedArray(cbinp)) { + UASSERT_OBJ(dynamic, cbinp, "Sized bin array without value metadata"); + BinSpan span; + span.sized = static_cast(sizedBins.size()); + m_cpBins.at(cpVarp).spans.emplace(cbinp->name(), span); + sizedBins.push_back(cbinp); + continue; + } if (cbinp->transp()) { // Transition bin (incl. array transition 'bins t[] = (a=>b),(c=>d)' and // illegal_bins/ignore_bins transitions). All sequences of one transition bin @@ -1507,11 +1769,34 @@ class FunctionalCoverageVisitor final : public VNVisitor { ++idx; } } + // A value a sized array's bin holds is no default bin's; only sampling tells which do + AstVar* sizedMatchedp = nullptr; + if (!defaultBins.empty() + && std::any_of(sizedBins.begin(), sizedBins.end(), [](const AstCoverBin* binp) { + return binp->binsType().binIsNormal(); + })) { + sizedMatchedp = new AstVar{fl, VVarType::BLOCKTEMP, + "__VcpSized_" + sanitizeGeneratedName(coverpointp->name()), + coverpointp->findBitDType()}; + sizedMatchedp->funcLocal(true); + m_sampleFuncp->addStmtsp(sizedMatchedp); + m_sampleFuncp->addStmtsp( + new AstAssign{fl, new AstVarRef{fl, sizedMatchedp, VAccess::WRITE}, + new AstConst{fl, AstConst::BitFalse{}}}); + } + for (uint32_t sized = 0; sized < sizedBins.size(); ++sized) { + emitSizedSample(coverpointp, sizedBins[sized], cpVarp, exprp, sized, sizedMatchedp); + } for (AstCoverBin* const defBinp : defaultBins) { + FileLine* const dfl = defBinp->fileline(); namerStmts.push_back(makeNamer(cpVarp, defBinp, -1, static_cast(idx))); - emitConvHitIf(coverpointp, defBinp, cpVarp, - cnum(defBinp->fileline(), static_cast(idx)), - buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + AstNodeExpr* condp = buildDefaultCondition(coverpointp, exprp, dfl); + if (sizedMatchedp) { + condp = new AstLogAnd{ + dfl, new AstNot{dfl, new AstVarRef{dfl, sizedMatchedp, VAccess::READ}}, condp}; + } + emitConvHitIf(coverpointp, defBinp, cpVarp, cnum(dfl, static_cast(idx)), + condp); ++idx; } if (idx > std::numeric_limits::max()) { @@ -1560,6 +1845,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { 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); + for (AstCoverBin* const binp : sizedBins) { + generateSizedArray(coverpointp, binp, cpVarp, exprp); + } m_constructorp->addStmtsp( itemCall(fl, cpVarp, VCMethod::COVERGROUP_VALUE_FINALIZE)->makeStmt()); } @@ -1934,6 +2222,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { uint32_t m_count = 0; // Number of declared normal bins in the selected span uint32_t m_declaredFirst = 0; // First runtime bin index of the selected span uint32_t m_declaredEnd = UINT32_MAX; // One past the last runtime bin index + int32_t m_sized = -1; // Index of the sized array holding the selected bins; or -1 }; struct CrossValueRange final { V3Number lo; // Inclusive lower bound, sign-extended to the comparison width @@ -2455,6 +2744,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { target.m_count = span.count; target.m_declaredFirst = span.declared; target.m_declaredEnd = span.declared + span.count; + target.m_sized = span.sized; } return target; } @@ -2489,10 +2779,22 @@ class FunctionalCoverageVisitor final : public VNVisitor { unsupportedCrossRange(selectp, valid); return false; } + AstNodeExpr* firstp; + AstNodeExpr* endp; + if (target.m_sized < 0) { + firstp = cnum(fl, target.m_declaredFirst); + endp = cnum(fl, target.m_declaredEnd); + } else { // A sized array, whose bins the coverpoint's construction placed + AstVar* const cpVarp = cpVars[target.m_dimension]; + const uint32_t sized = static_cast(target.m_sized); + firstp = itemCall(fl, cpVarp, VCMethod::COVERGROUP_SIZED_FIRST, {cnum(fl, sized)}); + endp = itemCall(fl, cpVarp, VCMethod::COVERGROUP_SIZED_END, {cnum(fl, sized)}); + firstp->dtypeSetUInt32(); + endp->dtypeSetUInt32(); + } m_constructorp->addStmtsp( itemCall(fl, cxp, VCMethod::COVERGROUP_SELECT_DIM, - {cnum(fl, target.m_dimension), cnum(fl, target.m_declaredFirst), - cnum(fl, target.m_declaredEnd), cnum(fl, selectp->isNegated()), + {cnum(fl, target.m_dimension), firstp, endp, cnum(fl, selectp->isNegated()), cnum(fl, selectp->rangesp() != nullptr)}) ->makeStmt()); for (AstNode* rangep = selectp->rangesp(); rangep; rangep = rangep->nextp()) { @@ -2583,6 +2885,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { return {}; } const CoverpointBins& bins = *target.m_binsp; + // Coverpoints with sized arrays are runtime points, so feed only runtime crosses + UASSERT_OBJ(target.m_sized < 0, selectp, "Sized bin array selected by a static cross"); const std::vector selected = selectCoverpointBins(selectp, bins, target.m_first, target.m_count, ctx.valid); if (!ctx.valid) return {}; diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 555bb3e03..ddfd691c7 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -349,6 +349,11 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { default: putfs(nodep, "bins "); break; } puts(nodep->name()); + if (nodep->isArray()) { + puts("["); + if (nodep->arraySizep()) iterateConst(nodep->arraySizep()); + puts("]"); + } if (nodep->binsType() == VCoverBinsType::BINS_DEFAULT) { puts(" = default"); } else if (nodep->transp()) { diff --git a/src/V3ParseGrammar.h b/src/V3ParseGrammar.h index 375efffe0..9df0b6f42 100644 --- a/src/V3ParseGrammar.h +++ b/src/V3ParseGrammar.h @@ -116,6 +116,27 @@ public: AstNode::addNext(nodep, new AstStop{fileline, false}); return nodep; } + // Apply the array marker of a bins declaration of values: '[]' creates a bin per value, and + // '[N]' N bins holding the values (IEEE 1800-2023 19.5.1) + static AstCoverBin* coverBinArray(AstCoverBin* binp, AstNodeExpr* sizep) { + if (!sizep) return binp; + binp->isArray(true); + if (VN_IS(sizep, Unbounded)) { + VL_DO_DANGLING(sizep->deleteTree(), sizep); + } else { + binp->arraySizep(sizep); + } + return binp; + } + // The array marker of another bins declaration, which supports only '[]'; true if present + static bool coverBinArrayUnsized(AstNodeExpr* sizep) { + if (!sizep) return false; + if (!VN_IS(sizep, Unbounded)) { + sizep->v3warn(COVERIGN, "Unsupported: 'bins' explicit array size (treated as '[]')"); + } + VL_DO_DANGLING(sizep->deleteTree(), sizep); + return true; + } AstNodeExpr* createGatePin(AstNodeExpr* exprp) { AstRange* const rangep = m_gateRangep; if (!rangep) return exprp; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index ffe67aa29..1588d8bea 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2235,7 +2235,7 @@ class WidthVisitor final : public VNVisitor { widthCovergroupRanges(nodep->rangesp(), m_vup ? m_vup->dtypep()->width() : 0); if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH); if (nodep->arraySizep()) { - // The size of 'bins auto[N]' is a self-determined constant expression, which + // The size of 'bins b[N]' or 'bins auto[N]' is a self-determined expression, which // V3Covergroup checks once folded userIterateAndNext(nodep->arraySizep(), WidthVP{SELF, BOTH}.p()); V3Const::constifyEdit(nodep->arraySizep()); // arraySizep may change diff --git a/src/verilog.y b/src/verilog.y index ba1aed28b..3a7f96bac 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -7169,9 +7169,7 @@ bins_or_options: // ==IEEE: bins_or_options coverage_option { $$ = $1; } // // Can't use wildcardE as results in conflicts | yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, false, false, $8}; - if ($3) binp->isArray(true); - $$ = binp; } + { $$ = GRAMMARP->coverBinArray(new AstCoverBin{$2, *$2, $6, false, false, false, $8}, $3); } | yBINS idAny/*bin_identifier*/ '[' cgexpr ']' iffE { // Check for automatic bins: bins auto[N] if (*$2 == "auto") { @@ -7184,91 +7182,95 @@ bins_or_options: // ==IEEE: bins_or_options } } | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, true, false, false, $8}; - if ($3) binp->isArray(true); - $$ = binp; } + { $$ = GRAMMARP->coverBinArray(new AstCoverBin{$2, *$2, $6, true, false, false, $8}, $3); } | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, true, false, $8}; - if ($3) binp->isArray(true); - $$ = binp; } + { $$ = GRAMMARP->coverBinArray(new AstCoverBin{$2, *$2, $6, false, true, false, $8}, $3); } | yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, false}; + { GRAMMARP->coverBinArrayUnsized($3); + AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, false}; BBCOVERIGN($8, "Unsupported: 'with' in cover bin (bin created without filter)"); DEL($10, $12); $$ = binp; } | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, true, false}; + { GRAMMARP->coverBinArrayUnsized($3); + AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, true, false}; BBCOVERIGN($8, "Unsupported: 'with' in cover bin (bin created without filter)"); DEL($10, $12); $$ = binp; } | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, true}; + { GRAMMARP->coverBinArrayUnsized($3); + AstCoverBin* const binp = new AstCoverBin{$2, *$2, $6, false, true}; BBCOVERIGN($8, "Unsupported: 'with' in cover bin (bin created without filter)"); DEL($10, $12); $$ = binp; } | yBINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($3); BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($3); BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' id/*cover_point_id*/ yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($3); BBCOVERIGN($6, "Unsupported: 'with' in cover bin"); DEL($8, $10); } | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { $$ = new AstCoverBin{$3, *$3, $7, false, false, true, $9}; } + { GRAMMARP->coverBinArrayUnsized($4); $$ = new AstCoverBin{$3, *$3, $7, false, false, true, $9}; } | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { $$ = new AstCoverBin{$3, *$3, $7, true, false, true, $9}; } + { GRAMMARP->coverBinArrayUnsized($4); $$ = new AstCoverBin{$3, *$3, $7, true, false, true, $9}; } | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' iffE - { $$ = new AstCoverBin{$3, *$3, $7, false, true, true, $9}; } + { GRAMMARP->coverBinArrayUnsized($4); $$ = new AstCoverBin{$3, *$3, $7, false, true, true, $9}; } | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($4); BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($4); BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' '{' range_list '}' yWITH__PAREN '(' cgexpr ')' iffE - { $$ = nullptr; BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($4); BBCOVERIGN($9, "Unsupported: 'with' in wildcard cover bin"); DEL($7, $11, $13); } // // // cgexpr part of trans_list | yBINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { FileLine* isArray = $3; - $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_TRANSITION}, isArray != nullptr}; + { const bool isArray = GRAMMARP->coverBinArrayUnsized($3); + $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_TRANSITION}, isArray}; if ($6) BBCOVERIGN($6->fileline(), "Unsupported: 'iff' in transition cover bin (guard ignored)"); DEL($6); } | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { FileLine* isArray = $3; - $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_IGNORE}, isArray != nullptr}; + { const bool isArray = GRAMMARP->coverBinArrayUnsized($3); + $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_IGNORE}, isArray}; if ($6) BBCOVERIGN($6->fileline(), "Unsupported: 'iff' in transition cover bin (guard ignored)"); DEL($6); } | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { FileLine* isArray = $3; - $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_ILLEGAL}, isArray != nullptr}; + { const bool isArray = GRAMMARP->coverBinArrayUnsized($3); + $$ = new AstCoverBin{$2, *$2, static_cast($5), VCoverBinsType{VCoverBinsType::BINS_ILLEGAL}, isArray}; if ($6) BBCOVERIGN($6->fileline(), "Unsupported: 'iff' in transition cover bin (guard ignored)"); DEL($6); } | yWILDCARD yBINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($4); BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} | yWILDCARD yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($4); BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} | yWILDCARD yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' trans_list iffE - { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($4); BBCOVERIGN($1, "Unsupported: 'wildcard' transition list in cover bin"); DEL($6, $7);} // | yBINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE - { $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_DEFAULT}; + { GRAMMARP->coverBinArrayUnsized($3); + $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_DEFAULT}; if ($6) BBCOVERIGN($6->fileline(), "Unsupported: 'iff' in default cover bin (guard ignored)"); DEL($6); } | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE - { $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_IGNORE}; + { GRAMMARP->coverBinArrayUnsized($3); + $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_IGNORE}; if ($6) BBCOVERIGN($6->fileline(), "Unsupported: 'iff' in default cover bin (guard ignored)"); DEL($6); } | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT iffE - { $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_ILLEGAL}; + { GRAMMARP->coverBinArrayUnsized($3); + $$ = new AstCoverBin{$2, *$2, VCoverBinsType::BINS_ILLEGAL}; if ($6) BBCOVERIGN($6->fileline(), "Unsupported: 'iff' in default cover bin (guard ignored)"); DEL($6); } | yBINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE - { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($3); BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } | yIGNORE_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE - { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($3); BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } | yILLEGAL_BINS idAny/*bin_identifier*/ bins_orBraE '=' yDEFAULT ySEQUENCE iffE - { $$ = nullptr; BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } + { $$ = nullptr; GRAMMARP->coverBinArrayUnsized($3); BBCOVERIGN($6, "Unsupported: 'sequence' in default cover bin"); DEL($7); } ; -bins_orBraE: // IEEE: part of bins_or_options: returns fileline (abuse for boolean flag) +bins_orBraE: // IEEE: part of bins_or_options: AstUnbounded for '[]', else the size /* empty */ { $$ = nullptr; } - | '[' ']' { $$ = $1; /* Mark as array */ } - | '[' cgexpr ']' { BBCOVERIGN($1, "Unsupported: 'bins' explicit array size (treated as '[]')"); DEL($2); $$ = $1; } + | '[' ']' { $$ = new AstUnbounded{$1}; } + | '[' cgexpr ']' + { $$ = $2; + if (VN_IS($2, Unbounded)) $2->v3error("Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1)"); } ; trans_list: // ==IEEE: trans_list diff --git a/test_regress/t/t_covergroup_coverpoint_method_unsup.out b/test_regress/t/t_covergroup_coverpoint_method_unsup.out index dc98d2879..24a8f4bdc 100644 --- a/test_regress/t/t_covergroup_coverpoint_method_unsup.out +++ b/test_regress/t/t_covergroup_coverpoint_method_unsup.out @@ -1,8 +1,3 @@ -%Warning-COVERIGN: t/t_covergroup_coverpoint_method_unsup.v:21:32: Unsupported: 'bins' explicit array size (treated as '[]') - 21 | coverpoint b {bins the_bins[5] = {[0 : 20]};} - | ^ - ... 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_coverpoint_method_unsup.v:31:42: Member 'a' not found in covergroup 'cg' : ... note: In instance 't' 31 | $display("coverage a = %f", the_cg.a.get_inst_coverage()); diff --git a/test_regress/t/t_covergroup_exclusions_unsup.out b/test_regress/t/t_covergroup_exclusions_unsup.out index a6d21260d..5b79d8a96 100644 --- a/test_regress/t/t_covergroup_exclusions_unsup.out +++ b/test_regress/t/t_covergroup_exclusions_unsup.out @@ -7,22 +7,34 @@ : ... note: In instance 't' 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. +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:16:29: Unsupported: non-integral value in a coverage bin of an integral coverpoint. : ... note: In instance 't' - 22 | wildcard bins text = {TEXT}; + 16 | bins text_sized[2] = {TEXT}; | ^~~~ -%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' - 28 | bins text = (1 => TEXT); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:41:59: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:28:12: Unsupported: 'bins' explicit array size of a real coverpoint (treated as '[]') : ... note: In instance 't' - 41 | static_cross: cross cp_real, cp_plain{bins selected = binsof (cp_real) intersect {1};} - | ^~~~~~ + 28 | bins sized[2] = {1.0, 2.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. -%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:42:62: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:25:29: Unsupported: non-integral value in a coverage bin of an integral coverpoint. + : ... note: In instance 't' + 25 | bins real_bound[2] = {[lo : 5]}; + | ^ +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:34:29: Unsupported: non-integral value in a coverage bin of an integral coverpoint. + : ... note: In instance 't' + 34 | wildcard bins text = {TEXT}; + | ^~~~ +%Error-UNSUPPORTED: t/t_covergroup_exclusions_unsup.v:40:25: Unsupported: non-integral value in a transition bin of a coverpoint with exclusions. + : ... note: In instance 't' + 40 | bins text = (1 => TEXT); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:53:59: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. : ... note: In instance 't' - 42 | dynamic_cross: cross cp_real, cp_dynamic{bins selected = binsof (cp_real) intersect {1};} + 53 | static_cross: cross cp_real, cp_plain{bins selected = binsof (cp_real) intersect {1};} + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_exclusions_unsup.v:54:62: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. + : ... note: In instance 't' + 54 | 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 834a67db8..30e9a553d 100644 --- a/test_regress/t/t_covergroup_exclusions_unsup.v +++ b/test_regress/t/t_covergroup_exclusions_unsup.v @@ -13,10 +13,22 @@ module t; cp: coverpoint value { bins text = {TEXT}; bins text_array[] = {TEXT}; + bins text_sized[2] = {TEXT}; ignore_bins ignored = {0}; } endgroup + // A sized array of bins with a non-integral bound given to the constructor, and of a real + // coverpoint, which is treated as an array of a bin per value + covergroup cg_sized(real lo); + cp: coverpoint value { + bins real_bound[2] = {[lo : 5]}; + } + cp_real: coverpoint real_value { + bins sized[2] = {1.0, 2.0}; + } + endgroup + covergroup cg_wild; cp: coverpoint value { wildcard bins text = {TEXT}; @@ -43,6 +55,7 @@ module t; endgroup cg_values values_cov = new; + cg_sized sized_cov = new(1.0); cg_wild wild_cov = new; cg_transition transition_cov = new; cg_cross cross_cov = new; diff --git a/test_regress/t/t_covergroup_sized_bad.out b/test_regress/t/t_covergroup_sized_bad.out new file mode 100644 index 000000000..285b45065 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_bad.out @@ -0,0 +1,26 @@ +%Error: t/t_covergroup_sized_bad.v:16:17: Bins array size must be >= 1, got 0 (IEEE 1800-2023 19.5.1) + : ... note: In instance 't' + 16 | bins zero[0] = {[0 : 3]}; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_covergroup_sized_bad.v:17:21: Bins array size must be >= 1, got -1 (IEEE 1800-2023 19.5.1) + : ... note: In instance 't' + 17 | bins negative[-1] = {[0 : 3]}; + | ^ +%Error: t/t_covergroup_sized_bad.v:18:21: Bins array size must be integral (IEEE 1800-2023 19.5.1) + : ... note: In instance 't' + 18 | bins fraction[1.5] = {[0 : 3]}; + | ^~~ +%Error: t/t_covergroup_sized_bad.v:20:12: Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1) + : ... note: In instance 't' + 20 | bins param_size[UNBOUNDED] = {[0 : 3]}; + | ^~~~~~~~~~ +%Error: t/t_covergroup_sized_bad.v:19:29: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants + : ... note: In instance 't' + 19 | bins four_state[2] = {[4'b000x : 4'hf]}; + | ^ +%Error: t/t_covergroup_sized_bad.v:21:12: Bins value may not be '$', which may only bound a range (IEEE 1800-2023 6.20.7) + : ... note: In instance 't' + 21 | bins param_value[2] = {UNBOUNDED}; + | ^~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_sized_bad.py b/test_regress/t/t_covergroup_sized_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_bad.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('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_sized_bad.v b/test_regress/t/t_covergroup_sized_bad.v new file mode 100644 index 000000000..9543bc4c6 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_bad.v @@ -0,0 +1,27 @@ +// 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 + +// A sized array of bins needs a positive integral size (IEEE 1800-2023 19.5.1), and two-state +// range bounds, of which '$' may only be a whole bound (6.20.7) + +module t; + logic [3:0] value; + localparam UNBOUNDED = $; + + covergroup cg; + cp: coverpoint value { + bins zero[0] = {[0 : 3]}; // <--- Bad: zero + bins negative[-1] = {[0 : 3]}; // <--- Bad: negative + bins fraction[1.5] = {[0 : 3]}; // <--- Bad: not integral + bins four_state[2] = {[4'b000x : 4'hf]}; // <--- Bad: x bound + bins param_size[UNBOUNDED] = {[0 : 3]}; // <--- Bad: '$' + bins param_value[2] = {UNBOUNDED}; // <--- Bad: '$' + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_sized_bins.out b/test_regress/t/t_covergroup_sized_bins.out new file mode 100644 index 000000000..0e65bb4eb --- /dev/null +++ b/test_regress/t/t_covergroup_sized_bins.out @@ -0,0 +1,73 @@ +__vlAnonCG_cg_embedded.cp.b[0]: 0 +__vlAnonCG_cg_embedded.cp.b[1]: 1 +__vlAnonCG_cg_embedded.cp.b[2]: 0 +__vlAnonCG_cg_embedded.cp.b[3]: 0 +cg_dynamic.cp.b[0]: 2 +cg_dynamic.cp.b[1]: 3 +cg_dynamic.cp.b[2]: 2 +cg_dynamic.cp.b[3]: 0 +cg_dynamic.cp.b[4]: 0 +cg_dynamic.cp.b[5]: 0 +cg_dynamic.cp.b[6]: 0 +cg_dynamic.cp.b[7]: 0 +cg_exprs.func_arg.b[0]: 1 +cg_exprs.func_arg.b[1]: 1 +cg_exprs.macro_arg.b[0]: 1 +cg_exprs.macro_arg.b[1]: 0 +cg_exprs.macro_arg.b[2]: 0 +cg_exprs.macro_arg.b[3]: 1 +cg_exprs.param_dollar.b[0]: 1 +cg_exprs.param_dollar.b[1]: 0 +cg_exprs.sys_func.b[0]: 1 +cg_exprs.sys_func.b[1]: 0 +cg_exprs.sys_func.b[2]: 0 +cg_exprs.sys_func.b[3]: 1 +cg_ieee.fixed4.fixed[0]: 1 +cg_ieee.fixed4.fixed[1]: 1 +cg_ieee.fixed4.fixed[2]: 1 +cg_ieee.fixed4.fixed[3]: 3 +cg_ieee.fixed5.fixed[0]: 1 +cg_ieee.fixed5.fixed[1]: 0 +cg_ieee.fixed5.fixed[2]: 1 +cg_kinds.dflt.b[0]: 2 +cg_kinds.dflt.b[1]: 1 +cg_kinds.dflt.others [default]: 1 +cg_kinds.excl.b[1]: 1 +cg_kinds.excl.b[2]: 0 +cg_kinds.excl.ign[0] [ignore]: 1 +cg_kinds.excl.ign[1] [ignore]: 1 +cg_kinds.excl.ill[0] [illegal]: 0 +cg_kinds.excl.ill[1] [illegal]: 0 +cg_kinds.gated.b[0]: 1 +cg_kinds.gated.b[1]: 1 +cg_kinds.mixed.arr[0]: 0 +cg_kinds.mixed.arr[1]: 1 +cg_kinds.mixed.one: 1 +cg_kinds.mixed.sized[0]: 1 +cg_kinds.mixed.sized[1]: 0 +cg_kinds.xx.b[1]_x_b[0] [cross]: 0 +cg_kinds.xx.b[1]_x_b[1] [cross]: 1 +cg_kinds.xx.low [cross]: 1 +cg_types.many.b[0]: 0 +cg_types.many.b[1]: 1 +cg_types.many.b[2]: 0 +cg_types.many.b[3]: 0 +cg_types.open.b[0]: 1 +cg_types.open.b[1]: 1 +cg_types.sgn.b[0]: 2 +cg_types.sgn.b[1]: 0 +cg_types.wide.b[0]: 0 +cg_types.wide.b[1]: 2 +cg_values.dup.b[0]: 1 +cg_values.dup.b[1]: 2 +cg_values.full.b[0]: 1 +cg_values.full.b[1]: 1 +cg_values.full.b[2]: 0 +cg_values.full.b[3]: 0 +cg_values.rep.b[0]: 1 +cg_values.sgn.b[0]: 1 +cg_values.sgn.b[1]: 1 +cg_values.sgn.b[2]: 0 +cg_values.wide.b[0]: 1 +cg_values.wide.b[1]: 0 +cg_values.wide.b[2]: 1 diff --git a/test_regress/t/t_covergroup_sized_bins.py b/test_regress/t/t_covergroup_sized_bins.py new file mode 100755 index 000000000..9f6b5465d --- /dev/null +++ b/test_regress/t/t_covergroup_sized_bins.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap +import coverage_covergroup_common + +test.scenarios('vlt_all') + +coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_covergroup_sized_bins.v b/test_regress/t/t_covergroup_sized_bins.v new file mode 100644 index 000000000..22fedf229 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_bins.v @@ -0,0 +1,261 @@ +// 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 + +// Sized arrays of bins, 'bins b[N] = {...}' (IEEE 1800-2023 19.5.1) + +// 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 + +`define SIZED_BASE 3 + +module t; + bit [3:0] v; + bit signed [4:0] s; + bit [63:0] q; + bit [99:0] w; + bit en; + localparam DOLLAR = $; + + function automatic int twice(int n); + return 2 * n; + endfunction + + // The examples of IEEE 1800-2023 19.5.1 + covergroup cg_ieee; + // 13 values over 4 bins of 3, the last holding the rest: <1,2,3>, <4,5,6>, <7,8,9>, + // <10,1,4,7> + fixed4: coverpoint v { + bins fixed[4] = {[1 : 10], 1, 4, 7}; + } + // More bins than values: <1>, <4>, <7>, and two empty bins, which are not created + fixed5: coverpoint v { + bins fixed[5] = {1, 4, 7}; + } + endgroup + + // Values distribute in the coverpoint's order, and count once in each bin holding them + covergroup cg_values; + // -16..15 over 3 bins of 10, the last holding 12 + sgn: coverpoint s { + bins b[3] = {[$ : $]}; + } + // A value two elements put in one bin + rep: coverpoint v { + bins b[1] = {[1 : 3], 2}; + } + // Values two elements put in two bins: <1,2,3>, <2,3,7> + dup: coverpoint v { + bins b[2] = {[1 : 3], 2, 3, 7}; + } + // All 2^64 values of a 64-bit coverpoint, in bins of 2^62 + full: coverpoint q { + bins b[4] = {[0 : $]}; + } + // 2^100 values, the last bin holding the rest + wide: coverpoint w { + bins b[3] = {[0 : $]}; + } + endgroup + + // Counts and bounds given to the constructor + covergroup cg_dynamic(int lo, int hi, longint unsigned count); + cp: coverpoint v { + bins b[count] = {[lo : hi]}; + } + endgroup + + // Counts and bounds of other types + covergroup cg_types( + bit [2:0] count, bit [69:0] wide_count, byte lo, bit signed [4:0] slo, bit [99:0] wlo + ); + // An unsigned count, and the values from lo to the last coverpoint value + open: coverpoint v { + bins b[count] = {[lo : $]}; + } + // A count beyond 64 bits + many: coverpoint v { + bins b[wide_count] = {[0 : 3]}; + } + // A bound of the signed coverpoint's type + sgn: coverpoint s { + bins b[2] = {[slo : 3]}; + } + // A bound beyond 64 bits + wide: coverpoint w { + bins b[count] = {[wlo : $]}; + } + endgroup + + // Sizes of expressions of constants, constructor arguments, and functions (IEEE 1800-2023 + // 19.5) + covergroup cg_exprs(int n); + // A macro and an argument: 4 bins of 4 + macro_arg: coverpoint v { + bins b[`SIZED_BASE + n] = {[0 : 15]}; + } + // A function of an argument: 2 bins of 8 + func_arg: coverpoint v { + bins b[twice(n)] = {[0 : 15]}; + } + // A constant system function, not '$': 4 bins of 4 + sys_func: coverpoint v { + bins b[$clog2(16)] = {[0 : 15]}; + } + // A parameter of '$' bounding a range (IEEE 1800-2023 6.20.7): 0..5 in 2 bins of 3 + param_dollar: coverpoint v { + bins b[2] = {[DOLLAR : 5]}; + } + endgroup + + // An embedded covergroup sized by a member of its enclosing class + class Sized; + const int m_count; + bit [3:0] m_value; + covergroup cg_embedded; + cp: coverpoint m_value { + bins b[m_count] = {[0 : 15]}; + } + endgroup + function new(int count); + m_count = count; + cg_embedded = new; + endfunction + endclass + + // Other bins of a coverpoint, other kinds of sized arrays, and crosses + covergroup cg_kinds; + // The default bin holds the values no bin holds, 6..15 + dflt: coverpoint v { + bins b[2] = {[0 : 5]}; + bins others = default; + } + // Excluded values leave bins: <0,1,2> of b loses them all, and is removed + excl: coverpoint v { + bins b[3] = {[0 : 8]}; + ignore_bins ign[2] = {[0 : 2], 5}; + illegal_bins ill[2] = {14, 15}; + } + gated: coverpoint v { + bins b[2] = {[0 : 3]} iff (en); + } + // A sized array's bins follow the others: <8,9>, <10,11> + mixed: coverpoint v { + bins sized[2] = {[8 : 11]}; + bins one = {0}; + bins arr[] = {[1 : 2]}; + } + xx: cross dflt, gated{bins low = binsof (dflt.b) intersect {[0 : 2]};} + endgroup + + cg_ieee ieee_inst = new; + cg_values values_inst = new; + cg_dynamic split_inst; + cg_dynamic many_inst; + cg_dynamic clip_inst; + cg_dynamic none_inst; + cg_types types_inst; + cg_exprs exprs_inst; + cg_kinds kinds_inst = new; + Sized obj; + + initial begin + v = 1; + ieee_inst.sample(); + v = 10; + ieee_inst.sample(); + v = 5; + ieee_inst.sample(); + v = 7; + ieee_inst.sample(); + // fixed4: all 4 bins, fixed5: 2 of 3 bins + `checkr(ieee_inst.get_inst_coverage(), 100.0 * (1.0 + 2.0 / 3) / 2); + + s = -7; + v = 2; + q = 64'h3fff_ffff_ffff_ffff; + w = 100'h0; + values_inst.sample(); + s = -6; + v = 7; + q = 64'h4000_0000_0000_0000; + w = {100{1'b1}}; + values_inst.sample(); + // sgn: b[0], b[1]; rep: b[0], once; dup: both; full: b[0], b[1]; wide: b[0], b[2] + `checkr(values_inst.get_inst_coverage(), 100.0 * (2.0 / 3 + 1.0 + 1.0 + 2.0 / 4 + 2.0 / 3) / 5); + + // 6 values over 2 bins: <1,2,3>, <4,5,6> + split_inst = new(1, 6, 2); + v = 3; + split_inst.sample(); + `checkr(split_inst.get_inst_coverage(), 50.0); + v = 4; + split_inst.sample(); + `checkr(split_inst.get_inst_coverage(), 100.0); + // 8 values over more than 2^32 bins: 8 bins of one + many_inst = new(0, 7, 64'h1_0000_0001); + for (int i = 0; i < 3; ++i) begin + v = 4'(i); + many_inst.sample(); + end + `checkr(many_inst.get_inst_coverage(), 100.0 * (3.0 / 8)); + // The values of -5..20 that are coverpoint values, 0..15, over 3 bins of 5 and 6 + clip_inst = new(-5, 20, 3); + v = 15; + clip_inst.sample(); + `checkr(clip_inst.get_inst_coverage(), 100.0 * (1.0 / 3)); + v = 9; + clip_inst.sample(); + `checkr(clip_inst.get_inst_coverage(), 100.0 * (2.0 / 3)); + // No coverpoint value, so no bin + none_inst = new(16, 20, 2); + none_inst.sample(); + `checkr(none_inst.get_inst_coverage(), 0.0); + + // 0..15 of -3..$ over 2 bins of 8, 4 values over more than 2^64 bins, -4..3 over 2 bins + // of 4, and 2^100 - 1 values over 2 bins + types_inst = new(2, {70{1'b1}}, -3, -4, 100'h1); + v = 9; + s = -2; + types_inst.sample(); + v = 1; + types_inst.sample(); + // open: both bins; many: b[1] of 4; sgn: b[0] of 2; wide: b[1] of 2 + `checkr(types_inst.get_inst_coverage(), 100.0 * (1.0 + 1.0 / 4 + 1.0 / 2 + 1.0 / 2) / 4); + + // First and last bins: macro_arg 2 of 4, func_arg both, sys_func 2 of 4, param_dollar 1 of 2 + exprs_inst = new(1); + v = 0; + exprs_inst.sample(); + v = 15; + exprs_inst.sample(); + `checkr(exprs_inst.get_inst_coverage(), 100.0 * (1.0 / 2 + 1.0 + 1.0 / 2 + 1.0 / 2) / 4); + + // 16 values over 4 bins of 4 + obj = new(4); + obj.m_value = 5; + obj.cg_embedded.sample(); + `checkr(obj.cg_embedded.get_inst_coverage(), 25.0); + + v = 2; + en = 0; + kinds_inst.sample(); + v = 9; + en = 1; + kinds_inst.sample(); + v = 3; + kinds_inst.sample(); + v = 0; + kinds_inst.sample(); + // dflt: both bins; excl: b[1] of b[1] and b[2]; gated: both bins; mixed: sized[0], one, + // arr[2] of 5 bins; xx: low and b[1]_x_b[1] of 3 bins + `checkr(kinds_inst.get_inst_coverage(), 100.0 * (1.0 + 1.0 / 2 + 1.0 + 3.0 / 5 + 2.0 / 3) / 5); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_sized_dollar_bad.out b/test_regress/t/t_covergroup_sized_dollar_bad.out new file mode 100644 index 000000000..77da464ca --- /dev/null +++ b/test_regress/t/t_covergroup_sized_dollar_bad.out @@ -0,0 +1,11 @@ +%Error: t/t_covergroup_sized_dollar_bad.v:15:19: Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1) + 15 | bins values[$] = {[0 : 3]}; + | ^ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_covergroup_sized_dollar_bad.v:16:30: Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1) + 16 | wildcard bins patterns[$] = {4'b11??}; + | ^ +%Error: t/t_covergroup_sized_dollar_bad.v:17:19: Bins array size must be integral, not '$' (IEEE 1800-2023 19.5.1) + 17 | bins others[$] = default; + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_sized_dollar_bad.py b/test_regress/t/t_covergroup_sized_dollar_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_dollar_bad.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('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_sized_dollar_bad.v b/test_regress/t/t_covergroup_sized_dollar_bad.v new file mode 100644 index 000000000..848826e2d --- /dev/null +++ b/test_regress/t/t_covergroup_sized_dollar_bad.v @@ -0,0 +1,24 @@ +// 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 + +// '$' is not the size of an array of bins, a positive integral expression (IEEE 1800-2023 +// 19.5.1), unlike '[]' + +module t; + logic [3:0] value; + + covergroup cg; + cp: coverpoint value { + bins values[$] = {[0 : 3]}; // <--- Bad + wildcard bins patterns[$] = {4'b11??}; // <--- Bad + bins others[$] = default; // <--- Bad + bins each[] = {[4 : 5]}; + } + endgroup + + cg cg_inst = new; + initial $finish; +endmodule diff --git a/test_regress/t/t_covergroup_sized_no_coverage.py b/test_regress/t/t_covergroup_sized_no_coverage.py new file mode 100755 index 000000000..0ac4a8604 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_no_coverage.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') +test.top_filename = 't/t_covergroup_sized_bins.v' + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_covergroup_sized_run_bad.out b/test_regress/t/t_covergroup_sized_run_bad.out new file mode 100644 index 000000000..c45939083 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_run_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_covergroup_sized_run_bad.v:21: Coverage bin array size must be a positive integer (IEEE 1800-2023 19.5.1) +-Info: t/t_covergroup_sized_run_bad.v:21: Verilog $stop, ignored due to +verilator+error+limit +%Error: t/t_covergroup_sized_run_bad.v:21: Coverage bin array size must be a positive integer (IEEE 1800-2023 19.5.1) +%Warning: t/t_covergroup_sized_run_bad.v:27: Coverage bin array needs more bins than --coverage-max-bins; bin ignored +%Warning: t/t_covergroup_sized_run_bad.v:45: Unsupported: cross coverage with more than 2^32-1 tuples; cross ignored +*-* All Finished *-* diff --git a/test_regress/t/t_covergroup_sized_run_bad.py b/test_regress/t/t_covergroup_sized_run_bad.py new file mode 100755 index 000000000..498fa56a7 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_run_bad.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +if test.tsan: + test.skip("ThreadSanitizer not compatible with AddressSanitizer\n") + +# Arrays of bins and crosses left without bins, under AddressSanitizer +test.compile(verilator_flags2=['-CFLAGS -fsanitize=address -LDFLAGS -fsanitize=address']) + +test.execute(all_run_flags=['+verilator+error+limit+100'], expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_sized_run_bad.v b/test_regress/t/t_covergroup_sized_run_bad.v new file mode 100644 index 000000000..7fc05e919 --- /dev/null +++ b/test_regress/t/t_covergroup_sized_run_bad.v @@ -0,0 +1,97 @@ +// 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 + +// Arrays of bins, and crosses of them, whose sizes are known when the covergroup is constructed + +// 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; + bit [3:0] value; + bit [15:0] data; + bit [63:0] data64; + + covergroup cg_count(int count); + cp: coverpoint value { + bins b[count] = {[0 : 3]}; // <--- Bad: count + } + endgroup + + covergroup cg_limit(int count); + cp: coverpoint data { + bins b[count] = {[0 : $]}; // <--- Bad: count + } + endgroup + + // Four dimensions of 1024 bins exceed the 2^32-1 bins of a cross + covergroup cg_cross(int count); + c1: coverpoint data { + bins b[count] = {[0 : $]}; + } + c2: coverpoint data { + bins b[count] = {[0 : $]}; + } + c3: coverpoint data { + bins b[count] = {[0 : $]}; + } + c4: coverpoint data { + bins b[count] = {[0 : $]}; + } + xx: cross c1, c2, c3, c4; // <--- Bad: bins + endgroup + + // All 2^64 values, whose positions need more than 64 bits + covergroup cg_full; + cp: coverpoint data64 { + bins b[2] = {[0 : $]}; + } + endgroup + + cg_count zero; + cg_count negative; + cg_count valid; + cg_limit over_limit; + cg_cross oversized; + cg_full full; + + initial begin + // Not a positive size (IEEE 1800-2023 19.5.1), so an error, and the array has no bins + zero = new(0); + negative = new(-1); + zero.sample(); + `checkr(zero.get_inst_coverage(), 0.0); + zero = null; + negative = null; + + // More bins than --coverage-max-bins hold values, so the array is ignored + over_limit = new(2000); + over_limit.sample(); + `checkr(over_limit.get_inst_coverage(), 0.0); + + // The cross is ignored, but not its coverpoints + oversized = new(1024); + oversized.sample(); + `checkr(oversized.get_inst_coverage(), 100.0 / 1024); + oversized = null; + + valid = new(2); + for (int i = 0; i < 4; ++i) begin + value = 4'(i); + valid.sample(); + end + `checkr(valid.get_inst_coverage(), 100.0); + + full = new; + data64 = 64'hffff_ffff_ffff_ffff; + full.sample(); + `checkr(full.get_inst_coverage(), 50.0); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_unsup.out b/test_regress/t/t_covergroup_unsup.out index 297058f01..0da03208d 100644 --- a/test_regress/t/t_covergroup_unsup.out +++ b/test_regress/t/t_covergroup_unsup.out @@ -9,9 +9,6 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:103:18: Unsupported: function call in coverage select expression 103 | bins one = crossfunc(); | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:113:14: Unsupported: 'bins' explicit array size (treated as '[]') - 113 | { bins ba[2] = {a}; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:117:21: Unsupported: 'with' in cover bin (bin created without filter) 117 | { bins ba = {a} with ( b ); } | ^~~~ @@ -84,35 +81,38 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:156:42: Unsupported: 'sequence' in default cover bin 156 | { illegal_bins lib_def_seq = default sequence; } | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:161:24: Unsupported: 'with' in cover bin - 161 | bins div_by_2 = a with (item % 2 == 0); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:162:32: Unsupported: 'with' in cover bin - 162 | bins div_by_2_paren[] = a with (item % 2 == 0); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:20: Unsupported: 'with' in coverage select expression - 168 | bins bin_e = with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:24: Unsupported: 'with' in coverage select expression - 169 | bins bin_not_e = ! with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:158:24: Unsupported: 'bins' explicit array size (treated as '[]') + 158 | { bins trans_sized[2] = ( 1 => 2 ); } | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:33: Unsupported: 'with' in coverage select expression - 171 | bins bin_with = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:163:24: Unsupported: 'with' in cover bin + 163 | bins div_by_2 = a with (item % 2 == 0); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:164:32: Unsupported: 'with' in cover bin + 164 | bins div_by_2_paren[] = a with (item % 2 == 0); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:170:20: Unsupported: 'with' in coverage select expression + 170 | bins bin_e = with (a); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:171:24: Unsupported: 'with' in coverage select expression + 171 | bins bin_not_e = ! with (a); + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:173:33: Unsupported: 'with' in coverage select expression + 173 | bins bin_with = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:49: Unsupported: 'with' in coverage select expression - 172 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:174:49: Unsupported: 'with' in coverage select expression + 174 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:50: Unsupported: 'with' in coverage select expression - 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:175:50: Unsupported: 'with' in coverage select expression + 175 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:175:40: Unsupported: 'with' in coverage select expression - 175 | ignore_bins ib_cross = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:177:40: Unsupported: 'with' in coverage select expression + 177 | ignore_bins ib_cross = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:176:42: Unsupported: 'with' in coverage select expression - 176 | illegal_bins lib_cross = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:178:42: Unsupported: 'with' in coverage select expression + 178 | illegal_bins lib_cross = binsof(a) with (a); | ^~~~ -%Error-UNSUPPORTED: t/t_covergroup_unsup.v:201:5: Unsupported: covergroup inheritance (extends) - 201 | covergroup extends cg_empty; +%Error-UNSUPPORTED: t/t_covergroup_unsup.v:203:5: Unsupported: covergroup inheritance (extends) + 203 | covergroup extends cg_empty; | ^~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_unsup.v b/test_regress/t/t_covergroup_unsup.v index df2989b0a..6d79f6cf3 100644 --- a/test_regress/t/t_covergroup_unsup.v +++ b/test_regress/t/t_covergroup_unsup.v @@ -154,6 +154,8 @@ module t ( // ignore/illegal bins = default sequence { ignore_bins ib_def_seq = default sequence; } { illegal_bins lib_def_seq = default sequence; } + // Explicit array size of transition bins + { bins trans_sized[2] = ( 1 => 2 ); } endgroup covergroup cg_coverpoint_ref; diff --git a/test_regress/t/t_covergroup_unsup_ign2.out b/test_regress/t/t_covergroup_unsup_ign2.out index 71ef992ff..339cce618 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -9,9 +9,6 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:103:18: Unsupported: function call in coverage select expression 103 | bins one = crossfunc(); | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:113:14: Unsupported: 'bins' explicit array size (treated as '[]') - 113 | { bins ba[2] = {a}; } - | ^ %Warning-COVERIGN: t/t_covergroup_unsup.v:117:21: Unsupported: 'with' in cover bin (bin created without filter) 117 | { bins ba = {a} with ( b ); } | ^~~~ @@ -84,32 +81,35 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:156:42: Unsupported: 'sequence' in default cover bin 156 | { illegal_bins lib_def_seq = default sequence; } | ^~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:161:24: Unsupported: 'with' in cover bin - 161 | bins div_by_2 = a with (item % 2 == 0); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:162:32: Unsupported: 'with' in cover bin - 162 | bins div_by_2_paren[] = a with (item % 2 == 0); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:20: Unsupported: 'with' in coverage select expression - 168 | bins bin_e = with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:24: Unsupported: 'with' in coverage select expression - 169 | bins bin_not_e = ! with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:158:24: Unsupported: 'bins' explicit array size (treated as '[]') + 158 | { bins trans_sized[2] = ( 1 => 2 ); } | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:33: Unsupported: 'with' in coverage select expression - 171 | bins bin_with = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:163:24: Unsupported: 'with' in cover bin + 163 | bins div_by_2 = a with (item % 2 == 0); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:164:32: Unsupported: 'with' in cover bin + 164 | bins div_by_2_paren[] = a with (item % 2 == 0); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:170:20: Unsupported: 'with' in coverage select expression + 170 | bins bin_e = with (a); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:171:24: Unsupported: 'with' in coverage select expression + 171 | bins bin_not_e = ! with (a); + | ^ +%Warning-COVERIGN: t/t_covergroup_unsup.v:173:33: Unsupported: 'with' in coverage select expression + 173 | bins bin_with = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:49: Unsupported: 'with' in coverage select expression - 172 | bins bin_or_with = binsof(a) || binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:174:49: Unsupported: 'with' in coverage select expression + 174 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:50: Unsupported: 'with' in coverage select expression - 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:175:50: Unsupported: 'with' in coverage select expression + 175 | bins bin_and_with = binsof(a) && binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:175:40: Unsupported: 'with' in coverage select expression - 175 | ignore_bins ib_cross = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:177:40: Unsupported: 'with' in coverage select expression + 177 | ignore_bins ib_cross = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:176:42: Unsupported: 'with' in coverage select expression - 176 | illegal_bins lib_cross = binsof(a) with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:178:42: Unsupported: 'with' in coverage select expression + 178 | illegal_bins lib_cross = binsof(a) with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:98:18: Ignoring unsupported coverage cross option: 'comment' 98 | cross a, b { option.comment = "cross"; option.weight = 12; option.per_instance = 1; } @@ -120,32 +120,32 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:103:7: Unsupported: explicit coverage cross bins 103 | bins one = crossfunc(); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:168:7: Unsupported: explicit coverage cross bins - 168 | bins bin_e = with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:169:7: Unsupported: explicit coverage cross bins - 169 | bins bin_not_e = ! with (a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:170:7: Unsupported: explicit coverage cross bins + 170 | bins bin_e = with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:171:7: Unsupported: explicit coverage cross bins - 171 | bins bin_with = binsof(a) with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:172:7: Unsupported: explicit coverage cross bins - 172 | bins bin_or_with = binsof(a) || binsof(a) with (a); + 171 | bins bin_not_e = ! with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:173:7: Unsupported: explicit coverage cross bins - 173 | bins bin_and_with = binsof(a) && binsof(a) with (a); + 173 | bins bin_with = binsof(a) with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:174:47: Unsupported: 'binsof' in coverage select expression - 174 | bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); - | ^~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:174:7: Unsupported: explicit coverage cross bins - 174 | bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); + 174 | bins bin_or_with = binsof(a) || binsof(a) with (a); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:175:7: Unsupported: explicit coverage cross bins - 175 | ignore_bins ib_cross = binsof(a) with (a); - | ^~~~~~~~~~~ + 175 | bins bin_and_with = binsof(a) && binsof(a) with (a); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:176:47: Unsupported: 'binsof' in coverage select expression + 176 | bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); + | ^~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins - 176 | illegal_bins lib_cross = binsof(a) with (a); + 176 | bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); + | ^~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:177:7: Unsupported: explicit coverage cross bins + 177 | ignore_bins ib_cross = binsof(a) with (a); + | ^~~~~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_unsup.v:178:7: Unsupported: explicit coverage cross bins + 178 | illegal_bins lib_cross = binsof(a) with (a); | ^~~~~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:46:5: Ignoring unsupported: 'type_option.merge_instances'; type coverage is the weighted average of the instances : ... note: In instance 't' @@ -189,7 +189,7 @@ : ... note: In instance 't' 101 | cross a, b { | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:167:11: Unsupported: cross of 'a' which is not a coverpoint (implicit coverpoint) +%Warning-COVERIGN: t/t_covergroup_unsup.v:169:11: Unsupported: cross of 'a' which is not a coverpoint (implicit coverpoint) : ... note: In instance 't' - 167 | cross a, b { + 169 | cross a, b { | ^ diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 12a961822..beceb0822 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -800,6 +800,7 @@ module Vt_debug_emitv_t; bins low = {['sh0:'sh3]} iff (cg_sig2[0]); bins high = {['sh4:'sh6]}; bins multi = {'sh0, 'sh1, 'sh2}; + bins sized['sh2] = {['sh0:'sh5], 'sh7}; bins dflt = default; ignore_bins ign = {'sh7}; illegal_bins ill = {'sh5}; @@ -907,7 +908,8 @@ module Vt_debug_emitv_t; bins t12 = (3'h1 => 3'h2); bins talt = (3'h2 => 3'h3), (3'h4 => 3'h5); bins trep = (3'h0 => 3'h1); - bins tarr = (3'h0 => 3'h1), (3'h1 => 3'h2); + bins tarr[] = (3'h0 => 3'h1), (3'h1 => + 3'h2); }; endfunction int signed __Vint; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 7662763f6..b81f23f0b 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -399,6 +399,7 @@ module t (/*AUTOARG*/ bins low = {[0:3]} iff (cg_sig2[0]); bins high = {[4:6]}; bins multi = {0, 1, 2}; // multiple values in one bins (exercises EmitV range loop) + bins sized[2] = {[0:5], 7}; // sized array of bins (exercises EmitV array size) bins dflt = default; ignore_bins ign = {7}; illegal_bins ill = {5};