diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 532618d97..c836afe7d 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -592,6 +592,10 @@ List Of Warnings ``covergroup``, ``coverpoint``, and coverage options, and the construct was ignored. + This includes crosses whose normal-bin Cartesian product exceeds + ``2**32 - 1`` tuples. The limit is checked during verilation for both + automatic and explicit cross bins. + Disabling the :option:`UNSUPPORTED` error also disables this warning. Ignoring this warning may make Verilator ignore lint checking on the diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index 76b91ac00..2ef89f88d 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -115,94 +115,245 @@ void VlCoverCross::init(const char* hier, uint32_t dims, VlCoverpoint* const* cp m_file = file; m_line = line; m_col = col; - m_dims = dims; - m_cps.assign(cps, cps + dims); - m_cpBinCounts.resize(dims); + assert(dims == m_dims); // Accumulate in 64 bits so the overflow check itself cannot overflow. - uint64_t product = 1; + uint64_t product = m_numAutoBins ? 1 : 0; for (uint32_t d = 0; d < dims; ++d) { - m_cpBinCounts[d] = cps[d]->normalBinCount(); - product *= m_cpBinCounts[d]; + m_dimensionsp[d] = {cps[d], nullptr, cps[d]->normalBinCount(), 1}; + product *= m_dimensionsp[d].bins; if (VL_UNLIKELY(product > UINT32_MAX)) { // LCOV_EXCL_START VL_FATAL_MT(file, line, "", "Cross has too many auto bins to represent"); } // LCOV_EXCL_STOP } - m_numAutoBins = static_cast(product); + assert(product == m_numAutoBins); // stride[d] = product of the Normal bin counts of all dimensions after d. // Counts down with an offset so the unsigned index never wraps below zero. - m_stride.assign(dims, 1); - for (uint32_t d = dims; d > 1; --d) m_stride[d - 2] = m_stride[d - 1] * m_cpBinCounts[d - 1]; - m_flatCounts.assign(m_numAutoBins, 0); + for (uint32_t d = dims; d > 1; --d) { + m_dimensionsp[d - 2].stride = m_dimensionsp[d - 1].stride * m_dimensionsp[d - 1].bins; + } } -void VlCoverCross::addBin(uint32_t dim, uint32_t first, uint32_t bins, const char* namep, +void VlCoverCross::addBin(std::initializer_list selection, const char* namep, const char* filep, int line, int col) { if (!m_numAutoBins) return; // An empty product creates no cross bin. - if (m_bins.empty()) m_autoExcluded.assign(m_numAutoBins, false); - m_bins.emplace_back(dim, first, bins, namep, filep, line, col); - // Visit only selected tuples. Multiple explicit bins may select the same tuple. - const uint64_t stride = m_stride[dim]; - const uint64_t period = stride * m_cpBinCounts[dim]; - for (uint64_t base = first * stride; base < m_numAutoBins; base += period) { - for (uint64_t flat = base; flat < base + bins * stride; ++flat) { - m_autoExcluded[flat] = true; - } - } + Explicit& data = *m_explicitp; + const uint32_t words = m_numAutoBins / 64 + (m_numAutoBins % 64 != 0); + assert(selection.size() == words); + assert(data.numBins < data.bins.size()); + uint64_t* const selectionp = data.selectionp + static_cast(data.numBins) * words; + std::copy(selection.begin(), selection.end(), selectionp); + Bin& bin = data.bins[data.numBins++]; + bin.selectionp = selectionp; + bin.namep = namep; + bin.filep = filep; + bin.line = line; + bin.col = col; + uint32_t word = 0; + for (const uint64_t bits : selection) { data.wordsp[word++].autoExcluded |= bits; } } void VlCoverCross::finalizeBins() { + if (!hasExplicitBins()) return; + Explicit& data = *m_explicitp; + assert(data.numBins == data.bins.size()); + uint32_t autoIdx = 0; for (uint32_t flat = 0; flat < m_numAutoBins; ++flat) { - if (!m_autoExcluded[flat]) m_autoBins.push_back(flat); + if (!(data.wordsp[flat / 64].autoExcluded & (uint64_t{1} << (flat % 64)))) { + assert(autoIdx < data.autoBins.size()); + data.autoBins[autoIdx++] = flat; + } } + const uint32_t words = m_numAutoBins / 64 + (m_numAutoBins % 64 != 0); + assert(autoIdx == data.autoBins.size()); + data.minBinWords = words; + uint64_t pos = 0; + const uint32_t* const indicesp = data.binWords.begin(); + for (Bin& bin : data.bins) { + const uint64_t begin = pos; + for (uint32_t word = 0; word < words; ++word) { + if (bin.selectionp[word]) { + assert(pos < data.binWords.size()); + data.binWords[pos++] = word; + } + } + bin.wordIndicesp = indicesp ? indicesp + begin : nullptr; + bin.numWords = static_cast(pos - begin); + data.minBinWords = std::min(data.minBinWords, bin.numWords); + } + assert(pos == data.binWords.size()); } +template void VlCoverCross::iterateProduct(uint32_t dim, uint32_t baseIdx) { - const VlCoverpoint* const cpp = m_cps[dim]; + const VlCoverpoint* const cpp = m_dimensionsp[dim].cpp; const uint32_t hits = cpp->hitCount(); - const uint32_t* const list = cpp->hitList(); + const uint32_t* const list = m_dimensionsp[dim].hitsp; const bool last = (dim == m_dims - 1); - const uint32_t stride = m_stride[dim]; + const uint32_t stride = m_dimensionsp[dim].stride; for (uint32_t hit = 0; hit < hits; ++hit) { const uint32_t idx = baseIdx + list[hit] * stride; if (last) { - incrementTuple(idx); + if (T_Explicit) { + incrementTuple(idx); + } else { + incrementAuto(idx); + } } else { - iterateProduct(dim + 1, idx); + iterateProduct(dim + 1, idx); } } } +template +void VlCoverCross::sampleSingleTuple(uint32_t idx, const bool* binIffs) { + Explicit& data = *m_explicitp; + const uint32_t word = idx / 64; + const uint64_t bit = uint64_t{1} << (idx % 64); + if (!(data.wordsp[word].autoExcluded & bit)) { + incrementAuto(idx); + return; + } + for (Bin& bin : data.bins) { + if (T_ApplyIffs && !*binIffs++) continue; + if (bin.selectionp[word] & bit) { + if (bin.count++ == 0) ++m_numCovered; + } + } +} + +template +void VlCoverCross::sampleBins(const bool* binIffs) { + struct HitWord final { + uint32_t index; + uint64_t bits; + }; + Explicit& data = *m_explicitp; + const uint64_t bins = data.numBins; + const uint64_t touched = T_Touched ? T_Touched : data.numTouchedWords; + const Word* const wordsp = data.wordsp; + std::array cached{}; + for (uint32_t i = 0; i < T_Touched; ++i) { + const uint32_t word = wordsp[i].touchedWord; + cached[i] = {word, wordsp[word].hitBits}; + } + for (uint64_t binIdx = 0; binIdx < bins; ++binIdx) { + if (T_ApplyIffs && !*binIffs++) continue; + Bin& bin = data.bins[binIdx]; + bool matched = false; + if (T_Touched == 1) { + matched = (bin.selectionp[cached[0].index] & cached[0].bits) != 0; + } else if (T_Dense || bin.numWords >= touched) { + for (uint64_t i = 0; i < touched; ++i) { + const uint32_t word = T_Touched ? cached[i].index : wordsp[i].touchedWord; + const uint64_t hits = T_Touched ? cached[i].bits : wordsp[word].hitBits; + if (bin.selectionp[word] & hits) { + matched = true; + break; + } + } + } else { + for (uint32_t pos = 0; pos < bin.numWords; ++pos) { + const uint32_t word = bin.wordIndicesp[pos]; + if (bin.selectionp[word] & wordsp[word].hitBits) { + matched = true; + break; + } + } + } + if (matched && bin.count++ == 0) ++m_numCovered; + } + for (uint32_t i = 0; i < data.numTouchedWords; ++i) { + data.wordsp[wordsp[i].touchedWord].hitBits = 0; + } + data.numTouchedWords = 0; +} + +template +void VlCoverCross::sampleHitWords(const bool* binIffs) { + switch (m_explicitp->numTouchedWords) { + case 1: sampleBins(binIffs); break; + case 2: sampleBins(binIffs); break; + case 3: sampleBins(binIffs); break; + default: sampleBins(binIffs); break; + } +} + void VlCoverCross::sample(const bool* binIffs) { // Fast path: if any dimension had no Normal-bin hit, the cross cannot hit. + bool single = true; for (uint32_t d = 0; d < m_dims; ++d) { - if (m_cps[d]->hitCount() == 0) return; + const uint32_t hits = m_dimensionsp[d].cpp->hitCount(); + if (hits == 0) return; + single &= hits == 1; } - for (Bin& bin : m_bins) { - if (binIffs && !*binIffs++) continue; - const VlCoverpoint* const cpp = m_cps[bin.dim]; - for (uint32_t hit = 0; hit < cpp->hitCount(); ++hit) { - const uint32_t idx = cpp->hitList()[hit]; - if (idx >= bin.first && idx - bin.first < bin.bins) { - if (bin.count++ == 0) ++m_numCovered; - break; + if (single) { + uint32_t idx = 0; + for (uint32_t d = 0; d < m_dims; ++d) { + idx += m_dimensionsp[d].cpp->hitList()[0] * m_dimensionsp[d].stride; + } + if (hasExplicitBins()) { + if (binIffs) { + sampleSingleTuple(idx, binIffs); + } else { + sampleSingleTuple(idx, nullptr); + } + } else { + incrementAuto(idx); + } + return; + } + bool enabled = true; + if (hasExplicitBins() && binIffs && !binIffs[0]) { + const bool* const endp = binIffs + m_explicitp->bins.size(); + enabled = std::find(binIffs + 1, endp, true) != endp; + if (!enabled && m_explicitp->autoBins.empty()) return; + } + for (uint32_t d = 0; d < m_dims; ++d) { + m_dimensionsp[d].hitsp = m_dimensionsp[d].cpp->hitList(); + } + if (!hasExplicitBins()) { + iterateProduct(0, 0); + return; + } + if (!enabled) { + iterateProduct(0, 0); + return; + } + iterateProduct(0, 0); + if (m_explicitp->numTouchedWords) { + const bool dense = m_explicitp->minBinWords >= m_explicitp->numTouchedWords; + if (binIffs) { + if (dense) { + sampleHitWords(binIffs); + } else { + sampleHitWords(binIffs); + } + } else { + if (dense) { + sampleHitWords(nullptr); + } else { + sampleHitWords(nullptr); } } } - iterateProduct(0, 0); } std::string VlCoverCross::binName(uint32_t i) const { - if (i < m_bins.size()) return m_bins[i].namep; - return autoBinName(autoIndex(i - static_cast(m_bins.size()))); + if (hasExplicitBins()) { + if (i < m_explicitp->bins.size()) return m_explicitp->bins[i].namep; + i -= static_cast(m_explicitp->bins.size()); + } + return autoBinName(autoIndex(i)); } std::string VlCoverCross::autoBinName(uint32_t flat) const { // Built on demand by concatenating each coverpoint's own bin name. std::string name; for (uint32_t d = 0; d < m_dims; ++d) { - const uint32_t crossIdx = (flat / m_stride[d]) % m_cpBinCounts[d]; + const Dimension& dimension = m_dimensionsp[d]; + const uint32_t crossIdx = (flat / dimension.stride) % dimension.bins; if (d > 0) name += "_x_"; - name += m_cps[d]->normalBinName(crossIdx); + name += dimension.cpp->normalBinName(crossIdx); } return name; } @@ -211,13 +362,14 @@ std::string VlCoverCross::autoBinName(uint32_t flat) const { void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page) { const std::string lineStr = std::to_string(m_line); const std::string colStr = std::to_string(m_col); - const uint32_t explicitCount = static_cast(m_bins.size()); + const uint32_t explicitCount + = hasExplicitBins() ? static_cast(m_explicitp->bins.size()) : 0; // Use the same indexed names for registration and the runtime read interface. for (uint32_t i = 0; i < binCount(); ++i) { const std::string bin = binName(i); const std::string full = m_hier + "." + bin; if (i < explicitCount) { - Bin& userBin = m_bins[i]; + Bin& userBin = m_explicitp->bins[i]; const std::string binLineStr = std::to_string(userBin.line); const std::string binColStr = std::to_string(userBin.col); VL_COVER_INSERT(covcontextp, full.c_str(), &userBin.count, "page", page, "filename", @@ -229,11 +381,12 @@ void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* pa // cross_bins metadata: the same components joined by ',' (not read by the report) std::string crossBins; for (uint32_t d = 0; d < m_dims; ++d) { - const uint32_t crossIdx = (flat / m_stride[d]) % m_cpBinCounts[d]; + const Dimension& dimension = m_dimensionsp[d]; + const uint32_t crossIdx = (flat / dimension.stride) % dimension.bins; if (d > 0) crossBins += ","; - crossBins += m_cps[d]->normalBinName(crossIdx); + crossBins += dimension.cpp->normalBinName(crossIdx); } - VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCounts[flat], "page", page, "filename", + VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCountsp[flat], "page", page, "filename", m_file, "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", bin.c_str(), "cross", "1", "cross_bins", crossBins.c_str()); } diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index b9b68b2d0..152f39ddd 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -35,7 +35,9 @@ #include "verilated.h" #include "verilated_cov_model.h" +#include #include +#include #include #include #include @@ -207,32 +209,63 @@ public: // VlCoverCross /// Per-instance cross runtime. Holds flat uint32_t[] storage over the /// Cartesian product of the feeding coverpoints' Normal bins. Each sample() -/// walks the coverpoint hit lists (O(hits), not O(product)). Bin names are -/// built on demand for automatic bins; explicit bins select a Normal-bin span -/// in one dimension and replace the corresponding automatic cross bins. +/// walks only hit tuples, not the entire product. Bin names are +/// built on demand for automatic bins; explicit bins select sets of tuples +/// and replace the corresponding automatic cross bins. Explicit selections +/// are intersected with hit-tuple words once per sample. +/// VlCoverCrossT owns the fixed arrays. This shared core does not allocate bin +/// storage, and its borrowed storage pointers remain valid for the instance. -class VlCoverCross final : public VlCoverpointIf { +class VlCoverCross VL_NOT_FINAL : public VlCoverpointIf { +protected: + struct Dimension final { + VlCoverpoint* cpp; // Feeding coverpoint + const uint32_t* hitsp; // Hit list cached for Cartesian traversal + uint32_t bins; // Normal bin count + uint32_t stride; // Flat-index stride + }; struct Bin final { - const uint32_t dim; // Selected coverpoint dimension - const uint32_t first; // First selected Normal bin index - const uint32_t bins; // Number of selected Normal bins - const char* const namep; // Explicit bin name - const char* const filep; // Bin declaration file - const int line; // Bin declaration line - const int col; // Bin declaration column + const uint64_t* selectionp; // Slice of the fixed selection storage + const char* namep; // Explicit bin name + const char* filep; // Bin declaration file + int line; // Bin declaration line + int col; // Bin declaration column uint32_t count = 0; // Samples matching the selection and guard + uint32_t numWords = 0; // Number of nonzero selection-word indices + const uint32_t* wordIndicesp = nullptr; // Slice of the packed selection-word indices + }; + struct Word final { + uint64_t autoExcluded = 0; // Tuples replaced by explicit bins + uint64_t hitBits = 0; // Selected hit tuples, cleared after each sample + uint32_t touchedWord = 0; // Flat word ID, stored by touched-list position + }; + template + class View final { + T* m_beginp; + T* m_endp; - Bin(uint32_t dim, uint32_t first, uint32_t bins, const char* namep, const char* filep, - int line, int col) - : dim{dim} - , first{first} - , bins{bins} - , namep{namep} - , filep{filep} - , line{line} - , col{col} {} + public: + View(T* datap, uint64_t size) + : m_beginp{datap} + , m_endp{datap ? datap + size : nullptr} {} + T& operator[](uint64_t i) const { return m_beginp[i]; } + uint64_t size() const { return m_beginp == m_endp ? 0 : m_endp - m_beginp; } + bool empty() const { return m_beginp == m_endp; } + T* begin() const { return m_beginp; } + T* end() const { return m_endp; } + }; + struct Explicit final { + View bins; // Explicit bins in declaration order + Word* wordsp; // Masks use flat word indices; touchedWord uses a dense prefix + View autoBins; // Retained flat indices + View binWords; // Nonzero selection words, grouped by bin + uint64_t* selectionp; // [bins.size() * ceil(m_numAutoBins / 64)] + uint32_t numBins = 0; // Bins configured by addBin() + uint32_t minBinWords = 0; // Minimum nonzero-word count across explicit bins + uint32_t numTouchedWords = 0; // Active prefix of wordsp[].touchedWord }; +private: // MEMBERS std::string m_hier; // "covergroup.cross" const char* m_file = nullptr; // Cross declaration file (registration metadata) @@ -242,37 +275,66 @@ class VlCoverCross final : public VlCoverpointIf { // Cross bin indexes are unsigned, like the coverpoint bin indexes they are // built from. init() fatals if the product would exceed UINT32_MAX, so every // index computed here provably fits. That bound is far beyond anything - // storable anyway: m_flatCounts alone would need 16GB. + // storable anyway: m_flatCountsp alone would need 16GB. uint32_t m_numAutoBins = 0; // Product of per-dim Normal bin counts uint32_t m_numCovered = 0; // Distinct bins hit >= 1 (maintained incrementally) - std::vector m_cpBinCounts; // [m_dims] Normal bin count per dimension - std::vector m_stride; // [m_dims] Flat-index stride per dimension - std::vector m_flatCounts; // [m_numAutoBins] Per-bin hit counts - std::vector m_cps; // Feeding coverpoints, set by init() - std::vector m_bins; // Explicit bins in declaration order - std::vector - m_autoExcluded; // Tuples replaced by explicit bins; empty for auto-only crosses - std::vector m_autoBins; // Retained flat indices, when explicit bins are present + Dimension* m_dimensionsp = nullptr; // [m_dims], owned by VlCoverCrossT + uint32_t* m_flatCountsp = nullptr; // [m_numAutoBins] Per-bin hit counts + Explicit* m_explicitp = nullptr; // Absent for automatic-only crosses // PRIVATE METHODS + bool hasExplicitBins() const { return m_explicitp != nullptr; } + template void iterateProduct(uint32_t dim, uint32_t baseIdx); - void incrementTuple(uint32_t idx) { - if (!m_autoExcluded.empty() && m_autoExcluded[idx]) return; - if (m_flatCounts[idx]++ == 0) ++m_numCovered; + void incrementAuto(uint32_t idx) { + if (m_flatCountsp[idx]++ == 0) ++m_numCovered; + } + template + void incrementTuple(uint32_t idx) { + Explicit& data = *m_explicitp; + const uint32_t wordIdx = idx / 64; + Word& word = data.wordsp[wordIdx]; + if ((word.autoExcluded >> (idx % 64)) & 1U) { + if (T_RecordHits) { + if (!word.hitBits) { data.wordsp[data.numTouchedWords++].touchedWord = wordIdx; } + word.hitBits |= uint64_t{1} << (idx % 64); + } + // Explicit selections consume automatic tuples independently of iff. + return; + } + incrementAuto(idx); + } + template + void sampleSingleTuple(uint32_t idx, const bool* binIffs); + template + void sampleBins(const bool* binIffs); + template + void sampleHitWords(const bool* binIffs); + uint32_t autoIndex(uint32_t i) const { + return hasExplicitBins() ? m_explicitp->autoBins[i] : i; } - uint32_t autoIndex(uint32_t i) const { return m_bins.empty() ? i : m_autoBins[i]; } std::string autoBinName(uint32_t flat) const; -public: +protected: // CONSTRUCTORS - VlCoverCross() = default; + VlCoverCross(uint32_t dims, uint32_t tuples) + : m_dims{dims} + , m_numAutoBins{tuples} {} + void bindStorage(Dimension* dimensionsp, uint32_t* countsp, Explicit* explicitp = nullptr) { + m_dimensionsp = dimensionsp; + m_flatCountsp = countsp; + m_explicitp = explicitp; + } + +public: + VL_UNCOPYABLE(VlCoverCross); // METHODS // ---- configuration (from generated constructor, after coverpoints init'd) ---- void init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, const char* file, int line, int col); - /// Add a single-binsof cross bin using verilation-time resolved Normal-bin indices. - void addBin(uint32_t dim, uint32_t first, uint32_t bins, const char* namep, const char* filep, + /// Add a cross bin using a verilation-time bitmap of selected Normal-bin tuples. + void addBin(std::initializer_list selection, const char* namep, const char* filep, int line, int col); /// Retain only automatic cross bins not selected by any explicit bin. void finalizeBins(); @@ -280,14 +342,15 @@ public: // ---- hot path (from generated sample(), after all coverpoints sampled) ---- /// Sample automatic and explicit bins, optionally applying per-bin iff guards. - /// Reads the feeding coverpoints from m_cps, so the caller passes no coverpoints. + /// Reads the feeding coverpoints saved by init(), so the caller passes no coverpoints. void sample(const bool* binIffs = nullptr); // ---- VlCoverpointIf ---- // Explicit bins precede retained automatic bins; all are Normal bins. uint32_t binCount() const override { - return m_bins.empty() ? m_numAutoBins - : static_cast(m_bins.size() + m_autoBins.size()); + return hasExplicitBins() + ? static_cast(m_explicitp->bins.size() + m_explicitp->autoBins.size()) + : m_numAutoBins; } std::string binName(uint32_t i) const override; void coverageParts(double& covered, double& total) const override { @@ -296,6 +359,51 @@ public: } }; +//============================================================================= +// VlCoverCrossT +/// Cross storage with verilation-time dimensions and bin capacities. All bin +/// data stays at the registry-owned object's address; no per-buffer allocations +/// or per-shape copies of the sampling algorithm are needed. + +template +class VlCoverCrossT final : public VlCoverCross { + static constexpr uint32_t WORDS = Tuples / 64 + (Tuples % 64 != 0); + static_assert(Bins > 0, "Explicit cross storage requires bins"); + + std::array m_dimensions; + std::array m_counts{}; + std::array m_bins; + std::array m_words{}; + std::array m_autoBins; + std::array m_binWords; + std::array(Bins) * WORDS> m_selections; + Explicit m_explicit; + +public: + VlCoverCrossT() + : VlCoverCross{Dims, Tuples} + , m_explicit{{m_bins.data(), Bins}, + m_words.data(), + {m_autoBins.data(), AutoBins}, + {m_binWords.data(), BinWords}, + m_selections.data()} { + bindStorage(m_dimensions.data(), m_counts.data(), &m_explicit); + } +}; + +/// Automatic-only crosses omit every explicit-bin array and its bookkeeping. +template +class VlCoverCrossT final : public VlCoverCross { + std::array m_dimensions; + std::array m_counts{}; + +public: + VlCoverCrossT() + : VlCoverCross{Dims, Tuples} { + bindStorage(m_dimensions.data(), m_counts.data()); + } +}; + class VlCovergroupType; //============================================================================= @@ -340,8 +448,9 @@ public: m_items.emplace_back(cpp); return cpp; // borrowed by the generated class } - VlCoverCross* addCross() { - VlCoverCross* const cxp = new VlCoverCross{}; + template + VlCoverCrossT* addCross() { + auto* const cxp = new VlCoverCrossT{}; m_items.emplace_back(cxp); return cxp; // borrowed by the generated class } diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 574b8db57..6db9e2bb0 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -510,7 +510,6 @@ public: RANDOM_GENERATOR, RANDOM_STDGENERATOR, COVERGROUP_INSTHANDLE, - COVERGROUP_CROSS, // Unsigned and two state; fundamental types UINT32, UINT64, @@ -547,7 +546,6 @@ public: "VlRandomizer", "VlStdRandomizer", "VlCovInstHandle", - "VlCoverCross*", "IData", "QData", "LOGIC_IMPLICIT", @@ -581,7 +579,6 @@ public: "%E-rand-gen", "%E-stdrand-gen", "%E-cg-insthandle", - "%E-cover-cross", "IData", "QData", "%E-logic-implct", @@ -627,7 +624,6 @@ public: case RANDOM_GENERATOR: return 0; // opaque case RANDOM_STDGENERATOR: return 0; // opaque case COVERGROUP_INSTHANDLE: return 0; // opaque - case COVERGROUP_CROSS: return 0; // opaque case UINT32: return 32; case UINT64: return 64; default: return 0; @@ -669,8 +665,7 @@ public: || m_e == MTASKSTATE || m_e == DELAY_SCHEDULER || m_e == TRIGGER_SCHEDULER || m_e == DYNAMIC_TRIGGER_SCHEDULER || m_e == FORK_SYNC || m_e == PROCESS_REFERENCE || m_e == RANDOM_GENERATOR || m_e == RANDOM_STDGENERATOR - || m_e == COVERGROUP_INSTHANDLE || m_e == COVERGROUP_CROSS || m_e == DOUBLE - || m_e == UNTYPED); + || m_e == COVERGROUP_INSTHANDLE || m_e == DOUBLE || m_e == UNTYPED); } bool isCHandle() const VL_MT_SAFE { return m_e == CHANDLE; } bool isDouble() const VL_MT_SAFE { return m_e == DOUBLE; } @@ -726,7 +721,6 @@ public: /* RANDOM_GENERATOR: */ "", // Should not be traced /* RANDOM_STD_GENERATOR: */ "", // Should not be traced /* COVERGROUP_INSTHANDLE: */ "", // Should not be traced - /* COVERGROUP_CROSS: */ "", // Should not be traced /* UINT32: */ "BIT", /* UINT64: */ "BIT", /* LOGIC_IMPLICIT: */ "", // Should not be traced diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 4cd019497..ba73c3854 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -501,9 +501,6 @@ public: bool isCovergroupInstHandle() const VL_MT_SAFE { return keyword() == VBasicDTypeKwd::COVERGROUP_INSTHANDLE; } - bool isCovergroupCross() const VL_MT_SAFE { - return keyword() == VBasicDTypeKwd::COVERGROUP_CROSS; - } bool isOpaque() const VL_MT_SAFE { return keyword().isOpaque(); } bool isString() const VL_MT_STABLE { return keyword().isString(); } bool isZeroInit() const { return keyword().isZeroInit(); } @@ -693,6 +690,50 @@ public: int widthTotalBytes() const override { return 1; } bool isCompound() const override { return false; } }; +class AstCoverCrossDType final : public AstNodeDType { + // Borrowed pointer to VlCoverCrossT. + const uint32_t m_dimensions; + const uint32_t m_tuples; + const uint32_t m_bins; + const uint32_t m_autoBins; + const uint64_t m_binWords; + +public: + AstCoverCrossDType(FileLine* fl, uint32_t dimensions, uint32_t tuples, uint32_t bins, + uint32_t autoBins, uint64_t binWords) + : ASTGEN_SUPER_CoverCrossDType(fl) + , m_dimensions{dimensions} + , m_tuples{tuples} + , m_bins{bins} + , m_autoBins{autoBins} + , m_binWords{binWords} { + dtypep(this); + } + ASTGEN_MEMBERS_AstCoverCrossDType; + const char* broken() const override { + BROKEN_RTN(m_dimensions == 0); + return nullptr; + } + bool sameNode(const AstNode* samep) const override { + const AstCoverCrossDType* const sp = VN_DBG_AS(samep, CoverCrossDType); + return dimensions() == sp->dimensions() && tuples() == sp->tuples() && bins() == sp->bins() + && autoBins() == sp->autoBins() && binWords() == sp->binWords(); + } + bool similarDTypeNode(const AstNodeDType* samep) const override { return this == samep; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + void dumpSmall(std::ostream& str) const override; + uint32_t dimensions() const { return m_dimensions; } + uint32_t tuples() const { return m_tuples; } + uint32_t bins() const { return m_bins; } + uint32_t autoBins() const { return m_autoBins; } + uint64_t binWords() const { return m_binWords; } + string cppTemplateArgs() const; + AstBasicDType* basicp() const override VL_MT_STABLE { return nullptr; } + int widthAlignBytes() const override { return sizeof(void*); } + int widthTotalBytes() const override { return sizeof(void*); } + bool isCompound() const override { return true; } +}; class AstCoverpointDType final : public AstNodeDType { // Borrowed pointer to a covergroup coverpoint runtime, 'VlCoverpointT*'. // Follows pattern of AstQueueDType in capturing the compile-time max bin overlap diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 744239284..fe3c914e8 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1108,16 +1108,28 @@ public: class AstCoverBinsof final : public AstNode { // A binsof selection of a coverpoint or one of its named bins // @astgen op1 := pointp : AstCoverpointRef + // @astgen op2 := rangesp : List[AstNode] // Optional intersect value ranges string m_name; // Selected bin name, or empty for all bins of the coverpoint + const bool m_isNegated; // Complement the selection within the cross product public: - AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp) - : ASTGEN_SUPER_CoverBinsof(fl) { + AstCoverBinsof(FileLine* fl, AstCoverpointRef* pointp, bool isNegated = false, + AstNode* rangesp = nullptr) + : ASTGEN_SUPER_CoverBinsof(fl) + , m_isNegated{isNegated} { this->pointp(pointp); + addRangesp(rangesp); } ASTGEN_MEMBERS_AstCoverBinsof; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; string name() const override VL_MT_STABLE { return m_name; } void name(const string& name) override { m_name = name; } + bool isNegated() const { return m_isNegated; } + bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START + const AstCoverBinsof* const asamep = VN_DBG_AS(samep, CoverBinsof); + return m_name == asamep->m_name && m_isNegated == asamep->m_isNegated; + } // LCOV_EXCL_STOP }; class AstCoverCrossBin final : public AstNode { // A named cross bin and its selection expression @@ -1135,6 +1147,28 @@ public: ASTGEN_MEMBERS_AstCoverCrossBin; string name() const override VL_MT_STABLE { return m_name; } }; +class AstCoverCrossSelect final : public AstNode { + // Intersection or union of two cross-bin selections + // @astgen op1 := lhsp : Optional[AstNode] // Null for an unsupported selection + // @astgen op2 := rhsp : Optional[AstNode] // Null for an unsupported selection + const bool m_isOr; // Union (||), rather than intersection (&&) + +public: + AstCoverCrossSelect(FileLine* fl, AstNode* lhsp, AstNode* rhsp, bool isOr) + : ASTGEN_SUPER_CoverCrossSelect(fl) + , m_isOr{isOr} { + this->lhsp(lhsp); + this->rhsp(rhsp); + } + ASTGEN_MEMBERS_AstCoverCrossSelect; + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + bool isOr() const { return m_isOr; } + string verilogKwd() const override { return isOr() ? "||" : "&&"; } + bool sameNode(const AstNode* samep) const override { // LCOV_EXCL_START + return m_isOr == VN_DBG_AS(samep, CoverCrossSelect)->m_isOr; + } // LCOV_EXCL_STOP +}; class AstCoverOption final : public AstNode { // Coverage-option assignment // @astgen op1 := valuep : AstNodeExpr diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index aac049918..b3e86a07f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1209,8 +1209,44 @@ void AstCoverBin::dumpJson(std::ostream& str) const { dumpJsonBoolIf(str, "isWildcard", isWildcard()); str << ", \"binsType\": \"" << binsType().ascii() << "\""; } +void AstCoverBinsof::dump(std::ostream& str) const { + Super::dump(str); + if (isNegated()) str << " [NEGATED]"; +} +void AstCoverBinsof::dumpJson(std::ostream& str) const { + Super::dumpJson(str); + dumpJsonBoolIf(str, "isNegated", isNegated()); +} void AstCoverCross::dump(std::ostream& str) const { Super::dump(str); } void AstCoverCross::dumpJson(std::ostream& str) const { Super::dumpJson(str); } +string AstCoverCrossDType::cppTemplateArgs() const { + return cvtToStr(dimensions()) + ", " + cvtToStr(tuples()) + ", " + cvtToStr(bins()) + ", " + + cvtToStr(autoBins()) + ", " + cvtToStr(binWords()); +} +void AstCoverCrossDType::dump(std::ostream& str) const { + Super::dump(str); + str << " [" << cppTemplateArgs() << "]"; +} +void AstCoverCrossDType::dumpJson(std::ostream& str) const { + dumpJsonNumFunc(str, dimensions); + dumpJsonNumFunc(str, tuples); + dumpJsonNumFunc(str, bins); + dumpJsonNumFunc(str, autoBins); + dumpJsonNumFunc(str, binWords); + dumpJsonGen(str); +} +void AstCoverCrossDType::dumpSmall(std::ostream& str) const { + Super::dumpSmall(str); + str << "covercross[" << cppTemplateArgs() << "]"; +} +void AstCoverCrossSelect::dump(std::ostream& str) const { + Super::dump(str); + str << (isOr() ? " [OR]" : " [AND]"); +} +void AstCoverCrossSelect::dumpJson(std::ostream& str) const { + Super::dumpJson(str); + dumpJsonBoolIf(str, "isOr", isOr()); +} void AstCoverInc::dump(std::ostream& str) const { Super::dump(str); str << " -> "; @@ -2148,6 +2184,9 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe // + 1 below as VlQueue uses 0 to mean unlimited, 1 to mean size() max is 1 if (adtypep->boundp()) info.m_type += ", " + cvtToStr(adtypep->boundConst() + 1); info.m_type += ">"; + } else if (const auto* const adtypep = VN_CAST(dtypep, CoverCrossDType)) { + UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); + info.m_type = "VlCoverCrossT<" + adtypep->cppTemplateArgs() + ">*"; } else if (const auto* const adtypep = VN_CAST(dtypep, CoverpointDType)) { UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); info.m_type = "VlCoverpointT<" + cvtToStr(adtypep->hitBound()) + ">*"; @@ -2228,10 +2267,6 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe info.m_type = "VlStdRandomizer"; } else if (bdtypep->isCovergroupInstHandle()) { info.m_type = "VlCovInstHandle"; - } else if (bdtypep->isCovergroupCross()) { - // Borrowed pointer: VlCovergroupInst owns the cross runtime, so its bins outlive the - // SV covergroup object (the coverage DB holds raw count pointers read at write() time) - info.m_type = "VlCoverCross*"; } else if (bdtypep->isEvent()) { info.m_type = v3Global.assignsEvents() ? "VlAssignableEvent" : "VlEvent"; } else if (dtypep->widthMin() <= 8) { // Handle unpacked arrays; not bdtypep->width diff --git a/src/V3Clean.cpp b/src/V3Clean.cpp index 048c67175..afd7d3f14 100644 --- a/src/V3Clean.cpp +++ b/src/V3Clean.cpp @@ -93,6 +93,7 @@ class CleanVisitor final : public VNVisitor { || VN_IS(nodep->dtypep()->skipRefp(), QueueDType) || VN_IS(nodep->dtypep()->skipRefp(), StreamDType) || VN_IS(nodep->dtypep()->skipRefp(), UnpackArrayDType) + || VN_IS(nodep->dtypep()->skipRefp(), CoverCrossDType) || VN_IS(nodep->dtypep()->skipRefp(), CoverpointDType) || VN_IS(nodep->dtypep()->skipRefp(), VoidDType)) { } else { @@ -141,12 +142,12 @@ class CleanVisitor final : public VNVisitor { computeCppWidth(nodep); if (!isClean(nodep)) insertClean(nodep); } - void ensureCleanAndNext(AstNodeExpr* nodep) { + void ensureCleanAndNext(AstNode* nodep) { // Editing list, careful looping! - for (AstNodeExpr* exprp = nodep; exprp;) { - AstNodeExpr* const nextp = VN_AS(exprp->nextp(), NodeExpr); - ensureClean(exprp); - exprp = nextp; + for (AstNode* argp = nodep; argp;) { + AstNode* const nextp = argp->nextp(); + if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp); + argp = nextp; } } @@ -250,9 +251,7 @@ class CleanVisitor final : public VNVisitor { setClean(nodep, false); // We always clean, as we don't trust those pesky users. if (!VN_IS(nodep->backp(), And)) insertClean(nodep); - for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) { - if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp); - } + ensureCleanAndNext(nodep->nodesp()); } void visit(AstTraceDecl* nodep) override {} // Nothing to do here void visit(AstTraceInc* nodep) override { @@ -288,11 +287,13 @@ class CleanVisitor final : public VNVisitor { ensureCleanAndNext(nodep->exprsp()); setClean(nodep, true); // generates a string, so not relevant } + void visit(AstCStmt* nodep) override { + iterateChildren(nodep); + ensureCleanAndNext(nodep->nodesp()); + } void visit(AstCStmtUser* nodep) override { iterateChildren(nodep); - for (AstNode* argp = nodep->nodesp(); argp; argp = argp->nextp()) { - if (AstNodeExpr* const exprp = VN_CAST(argp, NodeExpr)) ensureClean(exprp); - } + ensureCleanAndNext(nodep->nodesp()); } void visit(AstNodeCCall* nodep) override { iterateChildren(nodep); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 664636c10..13d54f14e 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -30,7 +30,10 @@ #include "V3File.h" #include "V3MemberMap.h" +#include +#include #include +#include #include #include @@ -75,6 +78,7 @@ class CovergroupExprValidVisitor final : public VNVisitor { iterateAndNextNull(nodep->selectp()); scanSampleExpression(nodep->iffp()); } + void visit(AstCoverBinsof* nodep) override { scanCoverageExpression(nodep->rangesp()); } void visit(AstCoverBin* nodep) override { scanCoverageExpression(nodep->rangesp()); scanSampleExpression(nodep->iffp()); @@ -162,8 +166,15 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::vector m_cpVars; // VlCoverpoint member, one per coverpoint std::vector m_crossVars; // VlCoverCross member, one per cross std::map m_cpVarMap; // Coverpoint name -> its VlCoverpoint member + struct CrossBinValues final { + AstCoverBin* binp; // Declaration owning this Normal bin + AstNodeExpr* valuep; // Individual array-bin value, or nullptr for a scalar bin + }; struct CoverpointBins final { uint32_t total = 0; // Number of Normal bins + AstNodeExpr* exprp = nullptr; // Sampled expression, for the value domain + std::vector values; // Values in runtime Normal-bin index order + std::vector excluded; // State ignore/illegal bins removing values std::unordered_map> spans; // Declared bin name -> first Normal index and number of bins }; @@ -171,6 +182,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::set m_droppedCrosses; // Crosses with a bare-variable item: drop (COVERIGN) std::map m_cpDTypes; // Hit-list bound -> interned dtype + using CrossShape = std::tuple; + std::map m_cxDTypes; AstVar* m_cgInstVarp = nullptr; // __Vcg_inst handle member of the current covergroup VMemberMap m_memberMap; // Member names cached for fast lookup @@ -745,6 +758,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (AstNode* rp = cbinp->rangesp(); rp; rp = rp->nextp()) { RangeBounds rb; if (!constRangeBounds(rp, rb)) return false; + if ((rb.loConstp() && rb.loConstp()->width() > 64) + || (rb.hiConstp() && rb.hiConstp()->width() > 64)) { + return false; // Use the safe slot-count bound for wide values. + } const uint64_t lo = rb.loUnbounded() ? 0 : rb.loConstp()->toUQuad(); const uint64_t hi = rb.hiUnbounded() ? maxVal : rb.hiConstp()->toUQuad(); if (lo > hi) return false; @@ -806,7 +823,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // One entry per Normal bin (cross slot): its covered intervals. std::vector>> bins; int slotCount = 0; // == runtime m_normal; the safe fallback bound - bool exact = true; + // Unsigned intervals cannot establish overlap between differently sized signed values. + bool exact = !exprp->isSigned(); for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { AstCoverBin* const cbinp = VN_AS(binp, CoverBin); if (!cbinp->binsType().binIsNormal()) @@ -862,7 +880,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // A literal C++ argument with no AST equivalent: a 'const char*' string literal (an SV // string AstConst emits '"..."s', a std::string temporary the runtime cannot borrow), a - // VlCovBinKind enum token, or a '__V' temporary declared by the enclosing AstCStmt. + // VlCovBinKind enum token, a constant selection-word initializer list, or a '__V' temporary + // declared by the enclosing AstCStmt. static AstCExpr* ctext(FileLine* fl, const std::string& text) { return new AstCExpr{fl, text}; } @@ -934,13 +953,22 @@ class FunctionalCoverageVisitor final : public VNVisitor { } // Emit a 'this->m_cp->addSingleNamer/addArrayNamer(...)' statement for one bin - AstNodeStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) { + AstNodeStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count, + const std::vector& values = {}) { FileLine* const fl = binp->fileline(); CoverpointBins& bins = m_cpBins.at(cpVarp); const uint32_t normalCount = binp->binsType().binIsNormal() ? static_cast(count < 0 ? 1 : count) : 0; bins.spans.emplace(binp->name(), std::make_pair(bins.total, normalCount)); bins.total += normalCount; + for (uint32_t i = 0; i < normalCount; ++i) { + bins.values.push_back({binp, values.empty() ? nullptr : values[i]}); + } + if (!binp->transp() + && (binp->binsType() == VCoverBinsType::BINS_IGNORE + || binp->binsType() == VCoverBinsType::BINS_ILLEGAL)) { + bins.excluded.push_back(binp); + } // Under --protect-ids the filename and bin name flow into the coverage database // verbatim, so obfuscate them exactly as line/toggle coverage points are (whole- // unit filename, per-word bin name). A no-op when --protect-ids is off. @@ -1030,6 +1058,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_cpVars.push_back(cpVarp); m_cpVarMap[coverpointp->name()] = cpVarp; m_cpBins.emplace(cpVarp, CoverpointBins{}); + m_cpBins.at(cpVarp).exprp = exprp; // Create the runtime in the instance node first; everything below configures it. m_constructorp->addStmtsp(makeItemCreate(fl, cpVarp, VCMethod::COVERGROUP_ADD_COVERPOINT)); @@ -1071,7 +1100,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { bool unsupported = false; std::vector values = extractArrayValues(cbinp, exprp, unsupported); if (unsupported) continue; // bin ignored (COVERIGN emitted); reserve no slot - namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); + namerStmts.push_back( + makeNamer(cpVarp, cbinp, static_cast(values.size()), values)); for (AstNodeExpr* valuep : values) { // TODO: A 4-state bin value (e.g. bins b[] = {2'b0x}) must match with === // (AstEqCase) per IEEE 1800-2023 19.5.4. == is equivalent under 2-state sim @@ -1426,30 +1456,542 @@ class FunctionalCoverageVisitor final : public VNVisitor { return cs; } - // Append a "{ const bool __Vcx_iffs[] = {, ...}; }" statement, one entry per - // explicit cross bin in declaration order (true where the bin has no iff). As above, the - // temporary array is literal text because a CMethodHard is one call, not a block. + // Assign the per-bin flags individually: one-bit SV results have integer C++ storage types, + // which may narrow in a bool initializer list but convert implicitly in assignments. AstCStmt* makeCrossIffsCall(FileLine* fl, const std::vector& bins, AstCMethodHard* callp) { AstCStmt* const cs = new AstCStmt{fl}; - cs->add("{ const bool __Vcx_iffs[] = {"); - bool first = true; - for (const AstCoverCrossBin* const binp : bins) { - if (!first) cs->add(", "); - first = false; + cs->add("{ bool __Vcx_iffs[" + cvtToStr(bins.size()) + "]; "); + for (size_t i = 0; i < bins.size(); ++i) { + const AstCoverCrossBin* const binp = bins[i]; + cs->add("__Vcx_iffs[" + cvtToStr(i) + "] = "); cs->add(binp->iffp() ? binp->iffp()->cloneTree(false) : new AstConst{fl, AstConst::BitTrue{}}); + cs->add("; "); } - cs->add("}; "); cs->add(callp); cs->add("; }"); return cs; } - std::vector - generateCrossBins(AstCoverCross* crossp, AstVar* cxVarp, const std::vector& cpVars, - const std::map& dimensions) { - std::vector bins; + using CrossSelection = std::vector; + struct ResolvedCrossBin final { + AstCoverCrossBin* binp; + CrossSelection selection; + }; + struct CrossLayout final { + uint32_t tuples = 0; + uint32_t autoBins = 0; + uint64_t binWords = 0; + bool valid = true; + std::vector bins; + }; + struct CrossSelectionContext final { + AstCoverCross* crossp; // Cross whose tuple space is being selected + const std::vector& cpVars; // Feeding coverpoints in dimension order + const std::map& dimensions; // Coverpoint name -> dimension + uint32_t tuples; // Size of the Cartesian product + std::vector strides; // Flat-index stride per dimension + bool valid = true; // False if this explicit bin cannot be implemented + }; + struct CrossValueRange final { + V3Number lo; // Inclusive lower bound, sign-extended to the comparison width + V3Number hi; // Inclusive upper bound + V3Number pattern; // Allowed bit values; all X for an ordinary interval + bool singleton = false; // A single value, possibly a wildcard pattern + bool wildcard = false; // A wildcard singleton rather than an exact four-state value + + CrossValueRange(AstNode* nodep, int width) + : lo{nodep, width} + , hi{nodep, width} + , pattern{nodep, width} { + pattern.setAllBitsX(); + } + }; + + static std::vector crossBinValues(const CrossBinValues& bin) { + if (bin.valuep) return {bin.valuep}; + std::vector values; + if (bin.binp->transp()) { + // IEEE 1800-2023 19.6.1: binsof uses the last value of each transition. + for (AstNode* setp = bin.binp->transp(); setp; setp = setp->nextp()) { + AstCoverTransItem* lastp = VN_AS(setp, CoverTransSet)->itemsp(); + while (lastp->nextp()) lastp = VN_AS(lastp->nextp(), CoverTransItem); + for (AstNode* valuep = lastp->valuesp(); valuep; valuep = valuep->nextp()) { + values.push_back(valuep); + } + } + } else { + for (AstNode* valuep = bin.binp->rangesp(); valuep; valuep = valuep->nextp()) { + values.push_back(valuep); + } + } + return values; + } + + static int crossRangeWidth(AstNode* nodep) { + if (const AstInsideRange* const rangep = VN_CAST(nodep, InsideRange)) { + return std::max(rangep->lhsp()->width(), rangep->rhsp()->width()); + } + return nodep->width(); + } + + static bool crossValueLess(const V3Number& lhs, const V3Number& rhs) { + V3Number result{&lhs}; + return !result.opLtS(lhs, rhs).isEqZero(); + } + + static bool crossRangeBound(AstNode* nodep, AstNodeExpr* exprp, bool upper, bool binValue, + V3Number& result) { + if (VN_IS(nodep, Unbounded)) { + V3Number limit{nodep, exprp->width()}; + if (upper) limit.setAllBits1(); + if (exprp->isSigned()) { + limit.setBit(exprp->width() - 1, !upper); + result.opExtendS(limit, limit.width()); + } else { + result.opAssign(limit); + } + return true; + } + const AstConst* const constp = VN_CAST(nodep, Const); + if (!constp || constp->num().isOpaque()) return false; + if (binValue && exprp->isSigned() && constp->width() <= exprp->width()) { + // Bin bit patterns use the coverpoint's effective type (IEEE 1800-2023 19.5.7). + // Wider values and intersect filters retain their values for domain clipping. + V3Number value{nodep, exprp->width()}; + if (constp->isSigned()) { + value.opExtendS(constp->num(), constp->width()); + } else { + value.opAssign(constp->num()); + } + result.opExtendS(value, value.width()); + } else if (constp->isSigned()) { + result.opExtendS(constp->num(), constp->width()); + } else { + result.opAssign(constp->num()); + } + return true; + } + + static CrossValueRange crossValueDomain(AstNode* nodep, int valueWidth, bool isSigned, + int width) { + CrossValueRange domain{nodep, width}; + V3Number lo{nodep, valueWidth}; + V3Number hi{nodep, valueWidth}; + hi.setAllBits1(); + if (isSigned) { + lo.setBit(valueWidth - 1, 1); + hi.setBit(valueWidth - 1, 0); + domain.lo.opExtendS(lo, valueWidth); + domain.hi.opExtendS(hi, valueWidth); + } else { + domain.lo.opAssign(lo); + domain.hi.opAssign(hi); + } + return domain; + } + + static void intersectCrossRange(CrossValueRange& range, const CrossValueRange& other) { + if (crossValueLess(range.lo, other.lo)) range.lo = other.lo; + if (crossValueLess(other.hi, range.hi)) range.hi = other.hi; + } + + static bool crossValueRange(AstNode* nodep, AstNodeExpr* exprp, bool binValue, bool wildcard, + const CrossValueRange& domain, CrossValueRange& range) { + if (const AstInsideRange* const rangep = VN_CAST(nodep, InsideRange)) { + if (!crossRangeBound(rangep->lhsp(), exprp, false, binValue, range.lo) + || !crossRangeBound(rangep->rhsp(), exprp, true, binValue, range.hi) + || range.lo.isFourState() || range.hi.isFourState()) { + return false; + } + } else { + range.singleton = true; + if (!crossRangeBound(nodep, exprp, false, binValue, range.lo)) return false; + range.hi = range.lo; + range.wildcard = wildcard; + if (wildcard) { + range.pattern = range.lo; + range.lo = domain.lo; + range.hi = domain.hi; + if (const AstConst* const constp = VN_CAST(nodep, Const)) { + if (constp->isSigned()) { + // Replicated X sign bits are correlated, not independent wildcards. + // The source domain preserves expansion-before-casting (19.5.7). + intersectCrossRange(range, crossValueDomain(nodep, constp->width(), true, + domain.lo.width())); + } + } + } + } + if (!range.lo.isFourState()) intersectCrossRange(range, domain); + return true; + } + + enum class CrossMatchResult : uint8_t { MATCH, NO_MATCH, WORK_LIMIT }; + + enum CrossRangeState : uint8_t { + CROSS_INSIDE_BOUNDS = 0, // Prefix is strictly inside the interval + CROSS_AT_LOWER = 1, + CROSS_AT_UPPER = 2, + CROSS_AT_BOUNDS = CROSS_AT_LOWER | CROSS_AT_UPPER, + CROSS_NO_MATCH = 4 // Prefix cannot match the interval/pattern + }; + static constexpr size_t CROSS_MATCH_LINEAR_ALLOWANCE = 4; // Minimum linear traversals + static constexpr size_t CROSS_MATCH_WORK_LIMIT = 1U << 20; // Base bit-step budget per search + + static CrossRangeState crossRangeStep(const CrossValueRange& range, CrossRangeState state, + int bit, int value) { + if (state == CROSS_NO_MATCH) return CROSS_NO_MATCH; + // Flipping the sign bit makes signed order lexicographic. + const bool sign = bit == range.pattern.width() - 1; + if (!range.pattern.bitIsXZ(bit) && value != (range.pattern.bitIs1(bit) ^ sign)) { + return CROSS_NO_MATCH; + } + const int low = range.lo.bitIs1(bit) ^ sign; + const int high = range.hi.bitIs1(bit) ^ sign; + if (((state & CROSS_AT_LOWER) && value < low) + || ((state & CROSS_AT_UPPER) && value > high)) { + return CROSS_NO_MATCH; + } + return static_cast( + ((state & CROSS_AT_LOWER) && value == low ? CROSS_AT_LOWER : CROSS_INSIDE_BOUNDS) + | ((state & CROSS_AT_UPPER) && value == high ? CROSS_AT_UPPER : CROSS_INSIDE_BOUNDS)); + } + + static bool crossWildcardIntersects(const CrossValueRange& range) { + unsigned states = 1U << CROSS_AT_BOUNDS; + for (int bit = range.pattern.width() - 1; bit >= 0 && states; --bit) { + unsigned next = 0; + for (const CrossRangeState state : + {CROSS_INSIDE_BOUNDS, CROSS_AT_LOWER, CROSS_AT_UPPER, CROSS_AT_BOUNDS}) { + if (!(states & (1U << state))) continue; + for (int value = 0; value < 2; ++value) { + const CrossRangeState equal = crossRangeStep(range, state, bit, value); + if (equal != CROSS_NO_MATCH) next |= 1U << equal; + } + } + states = next; + } + return states != 0; + } + + static std::array crossFreeBelow(const CrossValueRange& range) { + const int width = range.pattern.width(); + int fixed = width; + int lower = width; + int upper = width; + for (int bit = 0; bit < width; ++bit) { + if (fixed == width && !range.pattern.bitIsXZ(bit)) fixed = bit; + if (lower == width && range.lo.bitIs1(bit)) lower = bit; + if (upper == width && !range.hi.bitIs1(bit)) upper = bit; + } + return {fixed, std::min(fixed, lower), std::min(fixed, upper), + std::min({fixed, lower, upper})}; + } + + static bool crossRangeContains(const CrossValueRange& range, const V3Number& value) { + if (range.lo.isFourState() || crossValueLess(value, range.lo) + || crossValueLess(range.hi, value)) { + return false; + } + V3Number result{&value}; + return !result.opWildEq(value, range.pattern).isEqZero(); + } + + static CrossMatchResult crossOutsideExcluded(const CrossValueRange& range, + const std::vector& excluded) { + // Array-bin elements and singleton filters need no prefix search. + if (range.lo.isCaseEq(range.hi)) { + if (!crossRangeContains(range, range.lo)) return CrossMatchResult::NO_MATCH; + return std::none_of(excluded.begin(), excluded.end(), + [&](const CrossValueRange& exclusion) { + return crossRangeContains(exclusion, range.lo); + }) + ? CrossMatchResult::MATCH + : CrossMatchResult::NO_MATCH; + } + std::vector blockers; + std::vector> freeBelow; + for (const CrossValueRange& exclusion : excluded) { + if (exclusion.lo.isFourState() || crossValueLess(exclusion.hi, exclusion.lo) + || crossValueLess(exclusion.hi, range.lo) + || crossValueLess(range.hi, exclusion.lo)) { + continue; + } + blockers.push_back(&exclusion); + freeBelow.push_back(crossFreeBelow(exclusion)); + } + if (blockers.empty()) { + return !range.wildcard || crossWildcardIntersects(range) ? CrossMatchResult::MATCH + : CrossMatchResult::NO_MATCH; + } + + struct Frame final { + int bit; // Next bit to assign + std::vector state; // Bound states for candidate and exclusions + int nextValue = 0; // Next bit value to try + }; + std::vector stack{ + {range.pattern.width() - 1, + std::vector(blockers.size() + 1, CROSS_AT_BOUNDS), 0}}; + std::set>> failed; + size_t work = 0; + const size_t stepCost = blockers.size() + 1; + const size_t workLimit + = std::max(CROSS_MATCH_WORK_LIMIT, static_cast(range.pattern.width()) + * stepCost * CROSS_MATCH_LINEAR_ALLOWANCE); + // Seek one witness, pruning prefixes wholly covered by an exclusion. Memoizing + // failed prefixes avoids repeated work; a budget bounds hard wildcard unions. + while (!stack.empty()) { + Frame& frame = stack.back(); + if (frame.nextValue == 2) { + failed.emplace(frame.bit, std::move(frame.state)); + stack.pop_back(); + continue; + } + if (stepCost > workLimit - work) return CrossMatchResult::WORK_LIMIT; + work += stepCost; + const int value = frame.nextValue++; + const CrossRangeState candidate + = crossRangeStep(range, frame.state[0], frame.bit, value); + if (candidate == CROSS_NO_MATCH) continue; + std::vector successor = frame.state; + successor[0] = candidate; + bool covered = false; + for (size_t i = 0; i < blockers.size(); ++i) { + const CrossRangeState match + = crossRangeStep(*blockers[i], frame.state[i + 1], frame.bit, value); + successor[i + 1] = match; + if (match != CROSS_NO_MATCH && freeBelow[i][match] >= frame.bit) { + covered = true; + break; + } + } + if (covered) continue; + if (frame.bit == 0) return CrossMatchResult::MATCH; + const int bit = frame.bit - 1; + if (failed.find({bit, successor}) == failed.end()) { + stack.push_back({bit, std::move(successor), 0}); + } + } + return CrossMatchResult::NO_MATCH; + } + + static CrossMatchResult crossRangesIntersect(const CrossValueRange& bin, + const CrossValueRange& filter, + const std::vector& excluded, + bool excludeValues) { + if (filter.lo.isFourState() || bin.lo.isFourState()) { + if (!bin.singleton || !filter.singleton || !bin.lo.isCaseEq(filter.lo)) { + return CrossMatchResult::NO_MATCH; + } + return (!excludeValues + || std::none_of(excluded.begin(), excluded.end(), + [&](const CrossValueRange& range) { + return !range.wildcard && range.singleton + && bin.lo.isCaseEq(range.lo); + })) + ? CrossMatchResult::MATCH + : CrossMatchResult::NO_MATCH; + } + CrossValueRange match = bin; + intersectCrossRange(match, filter); + if (crossValueLess(match.hi, match.lo)) return CrossMatchResult::NO_MATCH; + if (!excludeValues || excluded.empty()) { + return !bin.wildcard || crossWildcardIntersects(match) ? CrossMatchResult::MATCH + : CrossMatchResult::NO_MATCH; + } + return crossOutsideExcluded(match, excluded); + } + + static void unsupportedCrossRange(AstCoverBinsof* selectp, bool& valid) { + selectp->v3warn(COVERIGN, "Unsupported: non-constant or non-integral 'intersect' value, " + "or four-state range bound."); + valid = false; + } + + static bool crossValueMatchesFilters(AstCoverBinsof* selectp, AstNode* valuep, + AstNodeExpr* exprp, const AstCoverBin* binp, + const CrossValueRange& domain, + const std::vector& filters, + const std::vector& excluded, + bool& valid) { + CrossValueRange range{valuep, domain.lo.width()}; + if (!crossValueRange(valuep, exprp, true, binp->isWildcard(), domain, range)) { + unsupportedCrossRange(selectp, valid); + return false; + } + for (const CrossValueRange& filter : filters) { + // State exclusions do not remove values from transition sequences. + const CrossMatchResult result + = crossRangesIntersect(range, filter, excluded, !binp->transp()); + if (result == CrossMatchResult::WORK_LIMIT) { + selectp->v3warn(COVERIGN, "Unsupported: 'intersect' exclusion matching exceeds " + "the selection work limit."); + valid = false; + return false; + } + if (result == CrossMatchResult::MATCH) return true; + } + return false; + } + + std::vector selectCoverpointBins(AstCoverBinsof* selectp, const CoverpointBins& bins, + uint32_t first, uint32_t count, bool& valid) { + std::vector selected(bins.total, false); + std::vector> values; + int width = bins.exprp->width(); + for (AstNode* rangep = selectp->rangesp(); rangep; rangep = rangep->nextp()) { + width = std::max(width, crossRangeWidth(rangep)); + } + if (selectp->rangesp()) { + for (AstCoverBin* const binp : bins.excluded) { + for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) { + width = std::max(width, crossRangeWidth(rangep)); + } + } + values.reserve(count); + for (uint32_t i = first; i < first + count; ++i) { + values.push_back(crossBinValues(bins.values[i])); + for (AstNode* const valuep : values.back()) { + width = std::max(width, crossRangeWidth(valuep)); + } + } + } + // One extra bit preserves both unsigned maxima and negative signed bounds. + ++width; + const CrossValueRange domain + = crossValueDomain(selectp, bins.exprp->width(), bins.exprp->isSigned(), width); + std::vector filters; + for (AstNode* rangep = selectp->rangesp(); rangep; rangep = rangep->nextp()) { + CrossValueRange filter{rangep, width}; + if (!crossValueRange(rangep, bins.exprp, false, false, domain, filter)) { + unsupportedCrossRange(selectp, valid); + return {}; + } + filters.push_back(std::move(filter)); + } + std::vector excluded; + if (selectp->rangesp()) { + for (AstCoverBin* const binp : bins.excluded) { + for (AstNode* rangep = binp->rangesp(); rangep; rangep = rangep->nextp()) { + CrossValueRange range{rangep, width}; + if (!crossValueRange(rangep, bins.exprp, true, binp->isWildcard(), domain, + range)) { + unsupportedCrossRange(selectp, valid); + return {}; + } + excluded.push_back(std::move(range)); + } + } + } + for (uint32_t i = first; i < first + count; ++i) { + if (!selectp->rangesp()) { + selected[i] = true; + continue; + } + for (AstNode* const valuep : values[i - first]) { + selected[i] + = crossValueMatchesFilters(selectp, valuep, bins.exprp, bins.values[i].binp, + domain, filters, excluded, valid); + if (!valid) return {}; + if (selected[i]) break; + } + } + if (selectp->isNegated()) { + for (uint32_t i = 0; i < bins.total; ++i) selected[i] = !selected[i]; + } + return selected; + } + + static void setCrossSelectionRange(CrossSelection& selection, uint64_t first, uint64_t end) { + while (first < end) { + const unsigned bit = first % 64; + const unsigned bits = std::min(64 - bit, end - first); + selection[first / 64] |= (bits == 64 ? ~uint64_t{0} : (uint64_t{1} << bits) - 1) + << bit; + first += bits; + } + } + + CrossSelection crossSelection(AstNode* nodep, CrossSelectionContext& ctx) { + if (AstCoverCrossSelect* const opp = VN_CAST(nodep, CoverCrossSelect)) { + CrossSelection lhs = crossSelection(opp->lhsp(), ctx); + const CrossSelection rhs = crossSelection(opp->rhsp(), ctx); + if (!ctx.valid) return {}; + for (size_t i = 0; i < lhs.size(); ++i) { + lhs[i] = opp->isOr() ? lhs[i] | rhs[i] : lhs[i] & rhs[i]; + } + return lhs; + } + AstCoverBinsof* const selectp = VN_AS(nodep, CoverBinsof); + const auto dimIt = ctx.dimensions.find(selectp->pointp()->name()); + if (dimIt == ctx.dimensions.end()) { + selectp->v3error("binsof coverpoint " + << selectp->pointp()->prettyNameQ() << " is not an item of cross " + << ctx.crossp->prettyNameQ() << " (IEEE 1800-2012 19.6.1)."); + ctx.valid = false; + return {}; + } + const uint32_t dim = dimIt->second; + const CoverpointBins& bins = m_cpBins.at(ctx.cpVars[dim]); + uint32_t first = 0; + uint32_t count = bins.total; + if (!selectp->name().empty()) { + const auto binIt = bins.spans.find(selectp->name()); + if (binIt == bins.spans.end()) { + selectp->v3error("Cannot find bin " << selectp->prettyNameQ() << " in coverpoint " + << selectp->pointp()->prettyNameQ() + << " (IEEE 1800-2012 19.6.1)."); + ctx.valid = false; + return {}; + } + first = binIt->second.first; + count = binIt->second.second; + } + const std::vector selected + = selectCoverpointBins(selectp, bins, first, count, ctx.valid); + if (!ctx.valid) return {}; + CrossSelection result((static_cast(ctx.tuples) + 63) / 64, 0); + const uint64_t stride = ctx.strides[dim]; + const uint64_t period = stride * bins.total; + for (uint64_t base = 0; base < ctx.tuples; base += period) { + for (uint32_t i = 0; i < bins.total;) { + if (!selected[i]) { + ++i; + continue; + } + const uint32_t begin = i++; + while (i < bins.total && selected[i]) ++i; + setCrossSelectionRange(result, base + begin * stride, base + i * stride); + } + } + return result; + } + + CrossLayout resolveCrossLayout(AstCoverCross* crossp, const std::vector& cpVars, + const std::map& dimensions) { + CrossLayout layout; + CrossSelectionContext ctx{crossp, cpVars, dimensions, 0, {}}; + uint64_t tuples = std::any_of(cpVars.begin(), cpVars.end(), + [this](AstVar* varp) { return !m_cpBins.at(varp).total; }) + ? 0 + : 1; + ctx.strides.resize(cpVars.size()); + for (size_t d = cpVars.size(); d > 0; --d) { + ctx.strides[d - 1] = tuples; + tuples *= m_cpBins.at(cpVars[d - 1]).total; + if (tuples > UINT32_MAX) { + crossp->v3warn(COVERIGN, + "Unsupported: cross coverage with more than 2^32-1 tuples."); + layout.valid = false; + return layout; + } + } + ctx.tuples = tuples; + layout.tuples = tuples; + CrossSelection excluded; std::set names; for (AstNode* itemp = crossp->binsp(); itemp; itemp = itemp->nextp()) { AstCoverCrossBin* const binp = VN_AS(itemp, CoverCrossBin); @@ -1458,38 +2000,58 @@ class FunctionalCoverageVisitor final : public VNVisitor { << " (IEEE 1800-2012 19.6.1)."); continue; } - const AstCoverBinsof* const selectp = VN_AS(binp->selectp(), CoverBinsof); - const auto dimIt = dimensions.find(selectp->pointp()->name()); - if (dimIt == dimensions.end()) { - selectp->v3error("binsof coverpoint " - << selectp->pointp()->prettyNameQ() << " is not an item of cross " - << crossp->prettyNameQ() << " (IEEE 1800-2012 19.6.1)."); + ctx.valid = true; + CrossSelection selection = crossSelection(binp->selectp(), ctx); + if (!ctx.valid || std::all_of(selection.begin(), selection.end(), [](uint64_t word) { + return word == 0; + })) { continue; } - const uint32_t dim = dimIt->second; - const CoverpointBins& cpBins = m_cpBins.at(cpVars[dim]); - uint32_t first = 0; - uint32_t count = cpBins.total; - if (!selectp->name().empty()) { - const auto binIt = cpBins.spans.find(selectp->name()); - if (binIt == cpBins.spans.end()) { - selectp->v3error("Cannot find bin " << selectp->prettyNameQ() - << " in coverpoint " - << selectp->pointp()->prettyNameQ() - << " (IEEE 1800-2012 19.6.1)."); - continue; - } - first = binIt->second.first; - count = binIt->second.second; + if (excluded.empty()) excluded.resize(selection.size(), 0); + for (size_t i = 0; i < selection.size(); ++i) { + excluded[i] |= selection[i]; + if (selection[i]) ++layout.binWords; } - // IEEE 1800-2012 19.6 excludes default, ignored and illegal coverpoint bins - // from cross products. An empty selection is valid, not an unsupported construct. - if (!count) continue; + layout.bins.push_back({binp, std::move(selection)}); + } + if (!layout.bins.empty()) { + layout.autoBins = layout.tuples; + for (const uint64_t word : excluded) { + layout.autoBins -= static_cast(std::bitset{word}.count()); + } + } + return layout; + } + + AstCoverCrossDType* crossDType(FileLine* fl, uint32_t dimensions, const CrossLayout& layout) { + const uint32_t bins = static_cast(layout.bins.size()); + const CrossShape shape{dimensions, layout.tuples, bins, layout.autoBins, layout.binWords}; + AstCoverCrossDType*& typep = m_cxDTypes[shape]; + if (!typep) { + typep = new AstCoverCrossDType{fl, dimensions, layout.tuples, + bins, layout.autoBins, layout.binWords}; + v3Global.rootp()->typeTablep()->addTypesp(typep); + } + return typep; + } + + std::vector generateCrossBins(AstCoverCross* crossp, AstVar* cxVarp, + const CrossLayout& layout) { + std::vector bins; + for (const ResolvedCrossBin& resolved : layout.bins) { + AstCoverCrossBin* const binp = resolved.binp; + const CrossSelection& selection = resolved.selection; FileLine* const fl = binp->fileline(); const bool prot = v3Global.opt.protectIds(); + std::string mask = "{"; + for (size_t i = 0; i < selection.size(); ++i) { + if (i) mask += ", "; + mask += std::to_string(selection[i]) + "ULL"; + } + mask += "}"; m_constructorp->addStmtsp( itemCall(fl, cxVarp, VCMethod::COVERGROUP_ADD_BIN, - {cnum(fl, dim), cnum(fl, first), cnum(fl, count), + {ctext(fl, mask), ctext(fl, quoted(VIdProtect::protectWordsIf(binp->name(), prot))), ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))), cnum(fl, static_cast(fl->lineno())), @@ -1532,9 +2094,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { itemp = nextp; } const int dims = static_cast(cpVars.size()); + const CrossLayout layout = resolveCrossLayout(crossp, cpVars, dimensions); + if (!layout.valid) return; AstVar* const cxVarp = new AstVar{fl, VVarType::MEMBER, "__Vcx_" + crossp->name(), - basicDType(fl, VBasicDTypeKwd::COVERGROUP_CROSS)}; + crossDType(fl, static_cast(dims), layout)}; m_covergroupp->addMembersp(cxVarp); m_crossVars.push_back(cxVarp); m_constructorp->addStmtsp(makeItemCreate(fl, cxVarp, VCMethod::COVERGROUP_ADD_CROSS)); @@ -1552,8 +2116,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { ctext(fl, quoted(VIdProtect::protectIf(fl->filename(), prot))), cnum(fl, static_cast(fl->lineno())), cnum(fl, static_cast(fl->firstColumn()))}))); - const std::vector bins - = generateCrossBins(crossp, cxVarp, cpVars, dimensions); + const std::vector bins = generateCrossBins(crossp, cxVarp, layout); if (v3Global.opt.coverage()) { const std::string page = VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot); @@ -1565,15 +2128,18 @@ class FunctionalCoverageVisitor final : public VNVisitor { // sample(): after all coverpoints have sampled (cross loop runs after coverpoint loop). UASSERT_OBJ(m_sampleFuncp, crossp, "sample() CFunc not set for cross"); - // The cross reads its coverpoints from its own m_cps, so sample() needs no cps array; + // The cross remembers its feeding coverpoints, so sample() needs no cps array; // per-bin iff guards still need a temporary array, hence the block form. + const bool hasIffs + = std::any_of(bins.begin(), bins.end(), + [](const AstCoverCrossBin* binp) { return binp->iffp() != nullptr; }); AstNodeStmt* const samplep - = bins.empty() ? static_cast( - itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE)->makeStmt()) - : static_cast(makeCrossIffsCall( - fl, bins, - itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE_IFFS, - {ctext(fl, "__Vcx_iffs")}))); + = !hasIffs ? static_cast( + itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE)->makeStmt()) + : static_cast(makeCrossIffsCall( + fl, bins, + itemCall(fl, cxVarp, VCMethod::COVERGROUP_SAMPLE_IFFS, + {ctext(fl, "__Vcx_iffs")}))); if (AstNodeExpr* const iffp = crossp->iffp()) { m_sampleFuncp->addStmtsp(new AstIf{fl, iffp->cloneTree(false), samplep}); } else { @@ -2355,6 +2921,9 @@ public: ~FunctionalCoverageVisitor() override = default; }; +// C++14 requires definitions for constexpr members passed by reference. +constexpr size_t FunctionalCoverageVisitor::CROSS_MATCH_WORK_LIMIT; + //###################################################################### // Functional coverage class functions diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index e285a3c4a..1dfc033db 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -541,7 +541,7 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, depth + 1, suffix + ".atDefault()", nullptr); } else if (VN_IS(dtypep, CDType)) { return ""; // Constructor does it - } else if (VN_IS(dtypep, CoverpointDType)) { + } else if (VN_IS(dtypep, CoverCrossDType) || VN_IS(dtypep, CoverpointDType)) { return ""; // Covergroup constructor creates the runtime and assigns the pointer } else if (const AstClassRefDType* const adtypep = VN_CAST(dtypep, ClassRefDType)) { return adtypep->rawPointer() ? varNameProtected + suffix + " = nullptr;\n" : ""; @@ -596,9 +596,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, return ""; } else if (basicp && (basicp->isRandomGenerator() || basicp->isStdRandomGenerator())) { return ""; - } else if (basicp && (basicp->isCovergroupInstHandle() || basicp->isCovergroupCross())) { - // The handle's own constructor deals with it; the cross is a borrowed pointer that - // the covergroup constructor assigns once it creates the runtime + } else if (basicp && basicp->isCovergroupInstHandle()) { + // The handle's own constructor deals with it. return ""; } else if (basicp && (basicp->isEvent())) { return "VlAssignableEvent{};\n"; diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index f1e8eb92c..2b40ac5d4 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -819,6 +819,10 @@ public: const AstCoverpointDType* const cpdtypep = VN_AS(nodep->dtypep()->skipRefp(), CoverpointDType); puts("<" + cvtToStr(cpdtypep->hitBound()) + ">"); + } else if (nodep->method() == VCMethod::COVERGROUP_ADD_CROSS) { + const AstCoverCrossDType* const cxdtypep + = VN_AS(nodep->dtypep()->skipRefp(), CoverCrossDType); + puts("<" + cxdtypep->cppTemplateArgs() + ">"); } puts("("); bool comma = false; diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index 2ae304ab3..5353d1630 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -354,7 +354,38 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } puts(";\n"); } - void visit(AstCoverpointRef* nodep) override { putfs(nodep, nodep->name()); } + void visit(AstCoverBinsof* nodep) override { + putfs(nodep, nodep->isNegated() ? "!binsof(" : "binsof("); + iterateConst(nodep->pointp()); + if (!nodep->name().empty()) puts("." + nodep->name()); + puts(")"); + if (nodep->rangesp()) { + puts(" intersect {"); + iterateAndCommaConstNull(nodep->rangesp()); + puts("}"); + } + } + void visit(AstCoverCrossBin* nodep) override { + putfs(nodep, "bins " + nodep->name() + " = "); + iterateConstNull(nodep->selectp()); + if (nodep->iffp()) { + puts(" iff ("); + iterateConst(nodep->iffp()); + puts(")"); + } + puts(";\n"); + } + void visit(AstCoverCrossSelect* nodep) override { + putfs(nodep, "("); + iterateConstNull(nodep->lhsp()); + putbs(" " + nodep->verilogKwd() + " "); + iterateConstNull(nodep->rhsp()); + puts(")"); + } + void visit(AstCoverpointRef* nodep) override { + putfs(nodep, nodep->name()); + iterateConstNull(nodep->exprp()); + } void visit(AstCoverCross* nodep) override { putfs(nodep, nodep->name() + ": cross "); for (AstNode* itemp = nodep->itemsp(); itemp; itemp = itemp->nextp()) { @@ -366,7 +397,13 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { iterateConst(nodep->iffp()); puts(")"); } - puts(";\n"); + if (nodep->binsp()) { + puts(" {\n"); + iterateAndNextConstNull(nodep->binsp()); + puts("}\n"); + } else { + puts(";\n"); + } } void visit(AstCoverTransSet* nodep) override { puts("("); @@ -1207,6 +1244,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } } void visit(AstConst* nodep) override { putfs(nodep, nodep->num().ascii(m_prefixed, true)); } + void visit(AstUnbounded* nodep) override { emitVerilogFormat(nodep, nodep->emitVerilog()); } // Just iterate void visit(AstTopScope* nodep) override { iterateChildrenConst(nodep); } diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 96b05f73a..4bca5fcec 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1447,6 +1447,16 @@ class LinkParseVisitor final : public VNVisitor { } } + void visit(AstCoverCrossSelect* nodep) override { + cleanFileline(nodep); + iterateChildren(nodep); + if (!nodep->lhsp() + || !nodep->rhsp()) { // Due to earlier Unsupported errors dropping only one operand + // would silently change the selected set. + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + } + void visit(AstCoverCross* nodep) override { cleanFileline(nodep); // Move options out of the mixed parse-time body, leaving only cross bins. diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 2c4c16797..b25435113 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2080,13 +2080,13 @@ class WidthVisitor final : public VNVisitor { if (nodep->iffp()) iterateCheckBool(nodep, "iff condition", nodep->iffp(), BOTH); userIterateAndNext(nodep->optionsp(), nullptr); } - void visit(AstCoverBin* nodep) override { - // Bin range/value entries are self-determined constant expressions (IEEE 1800-2023 + void widthCovergroupRanges(AstNode* rangesp) { + // Bin range/value entries are self-determined expressions (IEEE 1800-2023 // 19.5). Width each plain single-value entry self-determined so a referenced // parameter acquires a dtype, then constify so the reference folds to the AstConst // value that V3Covergroup requires. AstInsideRange entries fold their own bounds in // visit(AstInsideRange). - for (AstNode *nextp, *itemp = nodep->rangesp(); itemp; itemp = nextp) { + for (AstNode *nextp, *itemp = rangesp; itemp; itemp = nextp) { nextp = itemp->nextp(); if (VN_IS(itemp, InsideRange)) { userIterate(itemp, nullptr); @@ -2095,6 +2095,13 @@ class WidthVisitor final : public VNVisitor { V3Const::constifyEdit(itemp); // itemp may change } } + } + void visit(AstCoverBinsof* nodep) override { + userIterateAndNext(nodep->pointp(), nullptr); + widthCovergroupRanges(nodep->rangesp()); + } + void visit(AstCoverBin* nodep) override { + widthCovergroupRanges(nodep->rangesp()); userIterateAndNext(nodep->iffp(), nullptr); userIterateAndNext(nodep->arraySizep(), nullptr); userIterateAndNext(nodep->transp(), nullptr); diff --git a/src/verilog.y b/src/verilog.y index 49377f202..279a903d8 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -7407,9 +7407,9 @@ select_expression: // ==IEEE: select_expression select_expression_r { $$ = $1; } | select_expression yP_ANDAND select_expression - { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '&&' in coverage select expression"); DEL($1, $3); } + { $$ = new AstCoverCrossSelect{$2, $1, $3, false}; } | select_expression yP_OROR select_expression - { $$ = nullptr; BBCOVERIGN($2, "Unsupported: '||' in coverage select expression"); DEL($1, $3); } + { $$ = new AstCoverCrossSelect{$2, $1, $3, true}; } ; // This non-terminal exists to disambiguate select_expression and make "with" bind tighter @@ -7418,11 +7418,12 @@ select_expression_r: yBINSOF '(' bins_expression ')' { $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$3->fileline(), $3}}; } | '!' yBINSOF '(' bins_expression ')' - { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'binsof' in coverage select expression"); DEL($4); } - | yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' - { $$ = nullptr; BBCOVERIGN($5, "Unsupported: 'intersect' in coverage select expression"); DEL($3, $7); } - | '!' yBINSOF '(' bins_expression ')' yINTERSECT '{' covergroup_range_list '}' { } - { $$ = nullptr; BBCOVERIGN($5, "Unsupported: 'intersect' in coverage select expression"); DEL($4, $8); } + { $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$4->fileline(), $4}, true}; } + // // IEEE: covergroup_range_list has the same syntax as range_list + | yBINSOF '(' bins_expression ')' yINTERSECT '{' range_list '}' + { $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$3->fileline(), $3}, false, $7}; } + | '!' yBINSOF '(' bins_expression ')' yINTERSECT '{' range_list '}' + { $$ = new AstCoverBinsof{$1, new AstCoverpointRef{$4->fileline(), $4}, true, $8}; } | yWITH__PAREN '(' cgexpr ')' { $$ = nullptr; BBCOVERIGN($1, "Unsupported: 'with' in coverage select expression"); DEL($3); } | '!' yWITH__PAREN '(' cgexpr ')' diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index 84e87a158..8756ad725 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -89,40 +89,76 @@ : ... note: In instance 't' 84 | cp_a: coverpoint cp_expr {bins x = {size_var};} | ^~~~~~~~ -%Error: t/t_covergroup_autobins_bad.v:89:41: Non-constant expression in bin range; range bounds must be constants +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:87:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. + : ... note: In instance 't' + 87 | bins filtered = binsof(cp_a) intersect {0}; + | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:91:41: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 89 | cp_a: coverpoint cp_expr {bins x = {[size_var : 1]};} + 91 | cp_a: coverpoint cp_expr {bins x = {[size_var : 1]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:94:41: Non-constant expression in bin range; range bounds must be constants +%Error: t/t_covergroup_autobins_bad.v:96:41: Non-constant expression in bin range; range bounds must be constants : ... note: In instance 't' - 94 | cp_a: coverpoint cp_expr {bins x = {[0 : size_var]};} + 96 | cp_a: coverpoint cp_expr {bins x = {[0 : size_var]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:99:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants - : ... note: In instance 't' - 99 | cp_a: coverpoint cp_expr {bins x[] = {[4'b000x : 4'hF]};} - | ^ -%Error: t/t_covergroup_autobins_bad.v:104:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants +%Error: t/t_covergroup_autobins_bad.v:101:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants : ... note: In instance 't' - 104 | cp_a: coverpoint cp_expr {bins x[] = {[4'h0 : 4'b000x]};} + 101 | cp_a: coverpoint cp_expr {bins x[] = {[4'b000x : 4'hF]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:109:36: Non-constant expression in array bins value list; values must be constants +%Error: t/t_covergroup_autobins_bad.v:106:36: Four-state (x/z) value in array bins range bound; range bounds must be two-state constants : ... note: In instance 't' - 109 | cp_a: coverpoint cp_expr {bins x[] = {size_var};} + 106 | cp_a: coverpoint cp_expr {bins x[] = {[4'h0 : 4'b000x]};} | ^ -%Error: t/t_covergroup_autobins_bad.v:124:28: binsof coverpoint 'missing' is not an item of cross 'xc' (IEEE 1800-2012 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:111:36: Non-constant expression in array bins value list; values must be constants : ... note: In instance 't' - 124 | bins missing_point = binsof(missing); + 111 | cp_a: coverpoint cp_expr {bins x[] = {size_var};} + | ^ +%Error: t/t_covergroup_autobins_bad.v:126:28: binsof coverpoint 'missing' is not an item of cross 'xc' (IEEE 1800-2012 19.6.1). + : ... note: In instance 't' + 126 | bins missing_point = binsof(missing); | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:125:30: binsof coverpoint 'cp_other' is not an item of cross 'xc' (IEEE 1800-2012 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:127:30: binsof coverpoint 'cp_other' is not an item of cross 'xc' (IEEE 1800-2012 19.6.1). : ... note: In instance 't' - 125 | bins uncrossed_point = binsof(cp_other); + 127 | bins uncrossed_point = binsof(cp_other); | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:126:26: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2012 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:128:26: Cannot find bin 'missing' in coverpoint 'cp_a' (IEEE 1800-2012 19.6.1). : ... note: In instance 't' - 126 | bins missing_bin = binsof(cp_a.missing); + 128 | bins missing_bin = binsof(cp_a.missing); | ^~~~~~ -%Error: t/t_covergroup_autobins_bad.v:128:7: Duplicate cross bin 'duplicate' (IEEE 1800-2012 19.6.1). +%Error: t/t_covergroup_autobins_bad.v:130:7: Duplicate cross bin 'duplicate' (IEEE 1800-2012 19.6.1). : ... note: In instance 't' - 128 | bins duplicate = binsof(cp_b); + 130 | bins duplicate = binsof(cp_b); | ^~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:131:26: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. + : ... note: In instance 't' + 131 | bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:132:32: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. + : ... note: In instance 't' + 132 | bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:143:9: Unsupported: cross coverage with more than 2^32-1 tuples. + : ... note: In instance 't' + 143 | xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f { + | ^~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:146:16: Unsupported: cross coverage with more than 2^32-1 tuples. + : ... note: In instance 't' + 146 | auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; + | ^~~~~ +%Error: t/t_covergroup_autobins_bad.v:152:34: Non-constant expression in bin value list; values must be constants + : ... note: In instance 't' + 152 | ignore_bins nonconstant = {size_var}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:152:34: Non-constant expression in bin range; values must be constants + : ... note: In instance 't' + 152 | ignore_bins nonconstant = {size_var}; + | ^~~~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:156:23: Unsupported: non-constant or non-integral 'intersect' value, or four-state range bound. + : ... note: In instance 't' + 156 | bins selected = binsof(cp_a) intersect {0}; + | ^~~~~~ +%Warning-COVERIGN: t/t_covergroup_autobins_bad.v:195:23: Unsupported: 'intersect' exclusion matching exceeds the selection work limit. + : ... note: In instance 't' + 195 | bins selected = binsof(cp_a.whole) intersect {[0:30'h3fffffff]} && binsof(cp_b); + | ^~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_autobins_bad.v b/test_regress/t/t_covergroup_autobins_bad.v index 113fe9a75..1e9362e54 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -83,7 +83,9 @@ module t; covergroup cgx_nc_value; // non-constant value, non-array bin cp_a: coverpoint cp_expr {bins x = {size_var};} cp_c: coverpoint cp_expr {bins r = {0}; bins w = {1};} - xc: cross cp_a, cp_c; + xc: cross cp_a, cp_c { + bins filtered = binsof(cp_a) intersect {0}; + } endgroup covergroup cgx_nc_range_lo; // non-constant low bound, non-array range cp_a: coverpoint cp_expr {bins x = {[size_var : 1]};} @@ -126,6 +128,99 @@ module t; bins missing_bin = binsof(cp_a.missing); bins duplicate = binsof(cp_a); bins duplicate = binsof(cp_b); + bins nonconstant = binsof(cp_a) intersect {size_var} || binsof(cp_b); + bins nonconstant_range = binsof(cp_a) intersect {[0:size_var]}; + } + endgroup + + covergroup cgx_binsof_large; + cp_a: coverpoint cp_wide; + cp_b: coverpoint cp_wide; + cp_c: coverpoint cp_wide; + cp_d: coverpoint cp_wide; + cp_e: coverpoint cp_wide; + cp_f: coverpoint cp_wide; + xc: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f { + bins selected = binsof(cp_a); + } + auto_only: cross cp_a, cp_b, cp_c, cp_d, cp_e, cp_f; + endgroup + + covergroup cgx_binsof_excluded; + cp_a: coverpoint cp_expr { + bins normal = {0}; + ignore_bins nonconstant = {size_var}; + } + cp_b: coverpoint cp_expr {bins normal = {0};} + xc: cross cp_a, cp_b { + bins selected = binsof(cp_a) intersect {0}; + } + endgroup + + logic [29:0] complex_value; + localparam logic [29:0] ANY = 30'bx; + + covergroup cgx_binsof_complex; + cp_a: coverpoint complex_value { + bins whole = {[0:30'h3fffffff]}; + // Six pigeons in five holes: a deliberately hard union of excluded assignments. + wildcard ignore_bins no_hole = { + (ANY & ~30'h0000001f), (ANY & ~30'h000003e0), (ANY & ~30'h00007c00), + (ANY & ~30'h000f8000), (ANY & ~30'h01f00000), (ANY & ~30'h3e000000) + }; + wildcard ignore_bins shared_hole = { + (ANY | 30'h00000021), (ANY | 30'h00000401), (ANY | 30'h00008001), (ANY | 30'h00100001), + (ANY | 30'h02000001), (ANY | 30'h00000420), (ANY | 30'h00008020), (ANY | 30'h00100020), + (ANY | 30'h02000020), (ANY | 30'h00008400), (ANY | 30'h00100400), (ANY | 30'h02000400), + (ANY | 30'h00108000), (ANY | 30'h02008000), (ANY | 30'h02100000), (ANY | 30'h00000042), + (ANY | 30'h00000802), (ANY | 30'h00010002), (ANY | 30'h00200002), (ANY | 30'h04000002), + (ANY | 30'h00000840), (ANY | 30'h00010040), (ANY | 30'h00200040), (ANY | 30'h04000040), + (ANY | 30'h00010800), (ANY | 30'h00200800), (ANY | 30'h04000800), (ANY | 30'h00210000), + (ANY | 30'h04010000), (ANY | 30'h04200000), (ANY | 30'h00000084), (ANY | 30'h00001004), + (ANY | 30'h00020004), (ANY | 30'h00400004), (ANY | 30'h08000004), (ANY | 30'h00001080), + (ANY | 30'h00020080), (ANY | 30'h00400080), (ANY | 30'h08000080), (ANY | 30'h00021000), + (ANY | 30'h00401000), (ANY | 30'h08001000), (ANY | 30'h00420000), (ANY | 30'h08020000), + (ANY | 30'h08400000), (ANY | 30'h00000108), (ANY | 30'h00002008), (ANY | 30'h00040008), + (ANY | 30'h00800008), (ANY | 30'h10000008), (ANY | 30'h00002100), (ANY | 30'h00040100), + (ANY | 30'h00800100), (ANY | 30'h10000100), (ANY | 30'h00042000), (ANY | 30'h00802000), + (ANY | 30'h10002000), (ANY | 30'h00840000), (ANY | 30'h10040000), (ANY | 30'h10800000), + (ANY | 30'h00000210), (ANY | 30'h00004010), (ANY | 30'h00080010), (ANY | 30'h01000010), + (ANY | 30'h20000010), (ANY | 30'h00004200), (ANY | 30'h00080200), (ANY | 30'h01000200), + (ANY | 30'h20000200), (ANY | 30'h00084000), (ANY | 30'h01004000), (ANY | 30'h20004000), + (ANY | 30'h01080000), (ANY | 30'h20080000), (ANY | 30'h21000000) + }; + } + cp_b: coverpoint cp_expr; + xc: cross cp_a, cp_b { + bins selected = binsof(cp_a.whole) intersect {[0:30'h3fffffff]} && binsof(cp_b); + } + endgroup + + covergroup cgx_binsof_many_values; + cp_a: coverpoint cp_wide { + bins whole[] = {[0:767]}; + // These singleton queries must not consume an aggregate nonlinear-search budget. + wildcard ignore_bins removed = { + 16'b??1????11???????, 16'b?0????0??1??????, 16'b????1?1????????0, 16'b1??????1?1??????, + 16'b???????????0??10, 16'b???????0?????1?0, 16'b????1????1???0??, 16'b?1??0?????1?????, + 16'b??????0????0?0??, 16'b?????1?????00???, 16'b???????0??1?0???, 16'b????????0??0???0, + 16'b???????0??1????0, 16'b?0??1??????????0, 16'b0?????????0???1?, 16'b???????1????01??, + 16'b??0????????1???0, 16'b?1???????1??0???, 16'b?10??0??????????, 16'b10??????1???????, + 16'b1???????1????1??, 16'b1????????????1?0, 16'b??1????????01???, 16'b???1???0???????0, + 16'b1???????1?1?????, 16'b?10???0?????????, 16'b?1???1??1???????, 16'b10?0????????????, + 16'b?00???0?????????, 16'b????????10???1??, 16'b??????1????10???, 16'b??????01???????0, + 16'b???1????1???1???, 16'b???1????1??0????, 16'b???1?0????????1?, 16'b????0?0??0??????, + 16'b????1??0?????1??, 16'b??0??1????????1?, 16'b?????11????????1, 16'b????1?01????????, + 16'b?????0?00???????, 16'b???1????0????1??, 16'b?0????????0???1?, 16'b??????10??????0?, + 16'b1?0??0??????????, 16'b10?????0????????, 16'b10?0????????????, 16'b???10??????????0, + 16'b1????0????1?????, 16'b?0?????????1??0?, 16'b???0?????1?????0, 16'b?00???????0?????, + 16'b????0????1????0?, 16'b0????1??????1???, 16'b?1??1????0??????, 16'b???????0??????10, + 16'b?????0??01??????, 16'b?0???1?0????????, 16'b?1????1?1???????, 16'b?????00??0?????? + }; + } + cp_b: coverpoint cp_expr; + xc: cross cp_a, cp_b { + bins selected = binsof(cp_a.whole) intersect {[0:16'hffff]}; } endgroup @@ -144,6 +239,10 @@ module t; cgx_arr_ncval cgx_arr_ncval_inst = new; cgx_arr_open cgx_arr_open_inst = new; cgx_binsof cgx_binsof_inst = new; + cgx_binsof_large cgx_binsof_large_inst = new; + cgx_binsof_excluded cgx_binsof_excluded_inst = new; + cgx_binsof_complex cgx_binsof_complex_inst = new; + cgx_binsof_many_values cgx_binsof_many_values_inst = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_binsof.py b/test_regress/t/t_covergroup_binsof.py index 6f388a5e4..2de8054ad 100755 --- a/test_regress/t/t_covergroup_binsof.py +++ b/test_regress/t/t_covergroup_binsof.py @@ -21,6 +21,6 @@ test.run(cmd=[ test.coverage_filename ], verilator_run=True) -test.file_grep(merged, r"cg_binsof\.all_products\.combined.*' 10") +test.file_grep(merged, r"cg_binsof\.all_products\.combined.*' (\d+)", 10) test.passes() diff --git a/test_regress/t/t_covergroup_binsof_ops.out b/test_regress/t/t_covergroup_binsof_ops.out new file mode 100644 index 000000000..ac51a9808 --- /dev/null +++ b/test_regress/t/t_covergroup_binsof_ops.out @@ -0,0 +1,458 @@ +cg_excluded.cp_a.ignored [ignore]: 0 +cg_excluded.cp_a.illegal [illegal]: 0 +cg_excluded.cp_a.normal: 4 +cg_excluded.cp_b.auto_0: 2 +cg_excluded.cp_b.auto_1: 2 +cg_excluded.selected.complement [cross]: 4 +cg_excluded.selected.kept [cross]: 4 +cg_excluded.selected.partial [cross]: 4 +cg_excluded_four_state.cp_a.other [ignore]: 0 +cg_excluded_four_state.cp_a.states: 0 +cg_excluded_four_state.cp_a.xstate [ignore]: 0 +cg_excluded_four_state.cp_b.auto_0: 0 +cg_excluded_four_state.cp_b.auto_1: 0 +cg_excluded_four_state.selected.kept [cross]: 0 +cg_excluded_four_state.selected.numeric [cross]: 0 +cg_excluded_many.cp_a.whole: 2 +cg_excluded_many.cp_a.zero_bit [ignore]: 0 +cg_excluded_many.cp_b.auto_0: 1 +cg_excluded_many.cp_b.auto_1: 1 +cg_excluded_many.selected.kept [cross]: 2 +cg_excluded_wide.cp_a.ignored [ignore]: 0 +cg_excluded_wide.cp_a.normal: 2 +cg_excluded_wide.cp_b.auto_0: 1 +cg_excluded_wide.cp_b.auto_1: 1 +cg_excluded_wide.selected.kept [cross]: 2 +cg_excluded_wildcard.cp_a.band [illegal]: 0 +cg_excluded_wildcard.cp_a.empty_range [ignore]: 0 +cg_excluded_wildcard.cp_a.normal: 4 +cg_excluded_wildcard.cp_a.odds [ignore]: 0 +cg_excluded_wildcard.cp_b.auto_0: 2 +cg_excluded_wildcard.cp_b.auto_1: 2 +cg_excluded_wildcard.selected.kept [cross]: 4 +cg_fast_paths.cp_a.first: 16 +cg_fast_paths.cp_a.last: 10 +cg_fast_paths.cp_a.second: 16 +cg_fast_paths.cp_b.first: 15 +cg_fast_paths.cp_b.last: 10 +cg_fast_paths.cp_b.second: 15 +cg_fast_paths.cp_c.first: 16 +cg_fast_paths.cp_c.last: 10 +cg_fast_paths.cp_c.second: 16 +cg_fast_paths.selected.early_a [cross]: 7 +cg_fast_paths.selected.early_b [cross]: 7 +cg_fast_paths.selected.first_x_last_x_first [cross]: 1 +cg_fast_paths.selected.first_x_last_x_last [cross]: 1 +cg_fast_paths.selected.first_x_last_x_second [cross]: 1 +cg_fast_paths.selected.first_x_second_x_first [cross]: 7 +cg_fast_paths.selected.first_x_second_x_last [cross]: 1 +cg_fast_paths.selected.first_x_second_x_second [cross]: 7 +cg_fast_paths.selected.last_x_first_x_first [cross]: 1 +cg_fast_paths.selected.last_x_first_x_last [cross]: 1 +cg_fast_paths.selected.last_x_first_x_second [cross]: 1 +cg_fast_paths.selected.last_x_last_x_first [cross]: 1 +cg_fast_paths.selected.last_x_last_x_second [cross]: 1 +cg_fast_paths.selected.last_x_second_x_first [cross]: 1 +cg_fast_paths.selected.last_x_second_x_last [cross]: 1 +cg_fast_paths.selected.last_x_second_x_second [cross]: 1 +cg_fast_paths.selected.late [cross]: 2 +cg_fast_paths.selected.second_x_first_x_first [cross]: 7 +cg_fast_paths.selected.second_x_first_x_last [cross]: 1 +cg_fast_paths.selected.second_x_first_x_second [cross]: 7 +cg_fast_paths.selected.second_x_last_x_first [cross]: 1 +cg_fast_paths.selected.second_x_last_x_last [cross]: 1 +cg_fast_paths.selected.second_x_last_x_second [cross]: 1 +cg_fast_paths.selected.second_x_second_x_first [cross]: 7 +cg_fast_paths.selected.second_x_second_x_last [cross]: 1 +cg_fast_paths.selected.second_x_second_x_second [cross]: 7 +cg_fixed_words.cp_a.b0: 4 +cg_fixed_words.cp_a.b1: 4 +cg_fixed_words.cp_a.b10: 10 +cg_fixed_words.cp_a.b11: 10 +cg_fixed_words.cp_a.b12: 13 +cg_fixed_words.cp_a.b13: 13 +cg_fixed_words.cp_a.b14: 13 +cg_fixed_words.cp_a.b15: 16 +cg_fixed_words.cp_a.b2: 4 +cg_fixed_words.cp_a.b3: 4 +cg_fixed_words.cp_a.b4: 7 +cg_fixed_words.cp_a.b5: 7 +cg_fixed_words.cp_a.b6: 7 +cg_fixed_words.cp_a.b7: 7 +cg_fixed_words.cp_a.b8: 10 +cg_fixed_words.cp_a.b9: 10 +cg_fixed_words.cp_b.auto_0: 1 +cg_fixed_words.cp_b.auto_1: 1 +cg_fixed_words.cp_b.auto_10: 1 +cg_fixed_words.cp_b.auto_11: 1 +cg_fixed_words.cp_b.auto_12: 1 +cg_fixed_words.cp_b.auto_13: 1 +cg_fixed_words.cp_b.auto_14: 1 +cg_fixed_words.cp_b.auto_15: 1 +cg_fixed_words.cp_b.auto_2: 1 +cg_fixed_words.cp_b.auto_3: 1 +cg_fixed_words.cp_b.auto_4: 1 +cg_fixed_words.cp_b.auto_5: 1 +cg_fixed_words.cp_b.auto_6: 1 +cg_fixed_words.cp_b.auto_7: 1 +cg_fixed_words.cp_b.auto_8: 1 +cg_fixed_words.cp_b.auto_9: 1 +cg_fixed_words.guarded.all_values [cross]: 16 +cg_fixed_words.guarded.subset [cross]: 2 +cg_fixed_words.selected.all_values [cross]: 16 +cg_fixed_words.sparse.all_values [cross]: 16 +cg_fixed_words.sparse.subset [cross]: 4 +cg_four_state.cp_a.known: 0 +cg_four_state.cp_a.xstate: 0 +cg_four_state.cp_a.zstate: 0 +cg_four_state.cp_b.auto_0: 0 +cg_four_state.cp_b.auto_1: 0 +cg_four_state.selected.exact_x [cross]: 0 +cg_four_state.selected.exact_z [cross]: 0 +cg_four_state.selected.not_x [cross]: 0 +cg_guards.cp_a.zero: 8 +cg_guards.cp_b.zero: 8 +cg_guards.selected.constant_true [cross]: 8 +cg_guards.selected.high_bit [cross]: 4 +cg_guards.selected.low_bit [cross]: 4 +cg_guards.selected.low_slice [cross]: 6 +cg_guards.selected.selected_bit [cross]: 3 +cg_guards.selected.signed_vector [cross]: 4 +cg_guards.selected.unguarded [cross]: 8 +cg_guards.selected.whole_vector [cross]: 7 +cg_guards.selected.wide_bit [cross]: 4 +cg_guards.selected.wide_vector [cross]: 6 +cg_hit_words.cp_a.b0: 7 +cg_hit_words.cp_a.b1: 5 +cg_hit_words.cp_a.b2: 4 +cg_hit_words.cp_a.b3: 4 +cg_hit_words.cp_a.b4: 4 +cg_hit_words.cp_a.b5: 4 +cg_hit_words.cp_a.b6: 4 +cg_hit_words.cp_a.b7: 5 +cg_hit_words.cp_a.b8: 5 +cg_hit_words.cp_b.b0: 9 +cg_hit_words.cp_b.b1: 6 +cg_hit_words.cp_b.b2: 6 +cg_hit_words.cp_b.b3: 6 +cg_hit_words.cp_b.b4: 6 +cg_hit_words.cp_b.b5: 6 +cg_hit_words.cp_b.b6: 6 +cg_hit_words.cp_b.b7: 6 +cg_hit_words.cp_b.b8: 8 +cg_hit_words.selected.b1_x_b0 [cross]: 4 +cg_hit_words.selected.b1_x_b1 [cross]: 3 +cg_hit_words.selected.b1_x_b2 [cross]: 3 +cg_hit_words.selected.b1_x_b3 [cross]: 3 +cg_hit_words.selected.b1_x_b4 [cross]: 3 +cg_hit_words.selected.b1_x_b5 [cross]: 3 +cg_hit_words.selected.b1_x_b6 [cross]: 3 +cg_hit_words.selected.b1_x_b7 [cross]: 3 +cg_hit_words.selected.b1_x_b8 [cross]: 4 +cg_hit_words.selected.b2_x_b0 [cross]: 3 +cg_hit_words.selected.b2_x_b1 [cross]: 2 +cg_hit_words.selected.b2_x_b2 [cross]: 2 +cg_hit_words.selected.b2_x_b3 [cross]: 2 +cg_hit_words.selected.b2_x_b4 [cross]: 2 +cg_hit_words.selected.b2_x_b5 [cross]: 2 +cg_hit_words.selected.b2_x_b6 [cross]: 2 +cg_hit_words.selected.b2_x_b7 [cross]: 2 +cg_hit_words.selected.b2_x_b8 [cross]: 3 +cg_hit_words.selected.b3_x_b0 [cross]: 3 +cg_hit_words.selected.b3_x_b1 [cross]: 2 +cg_hit_words.selected.b3_x_b2 [cross]: 2 +cg_hit_words.selected.b3_x_b3 [cross]: 2 +cg_hit_words.selected.b3_x_b4 [cross]: 2 +cg_hit_words.selected.b3_x_b5 [cross]: 2 +cg_hit_words.selected.b3_x_b6 [cross]: 2 +cg_hit_words.selected.b3_x_b7 [cross]: 2 +cg_hit_words.selected.b3_x_b8 [cross]: 3 +cg_hit_words.selected.b4_x_b0 [cross]: 3 +cg_hit_words.selected.b4_x_b1 [cross]: 2 +cg_hit_words.selected.b4_x_b2 [cross]: 2 +cg_hit_words.selected.b4_x_b3 [cross]: 2 +cg_hit_words.selected.b4_x_b4 [cross]: 2 +cg_hit_words.selected.b4_x_b5 [cross]: 2 +cg_hit_words.selected.b4_x_b6 [cross]: 2 +cg_hit_words.selected.b4_x_b7 [cross]: 2 +cg_hit_words.selected.b4_x_b8 [cross]: 3 +cg_hit_words.selected.b5_x_b0 [cross]: 3 +cg_hit_words.selected.b5_x_b1 [cross]: 2 +cg_hit_words.selected.b5_x_b2 [cross]: 2 +cg_hit_words.selected.b5_x_b3 [cross]: 2 +cg_hit_words.selected.b5_x_b4 [cross]: 2 +cg_hit_words.selected.b5_x_b5 [cross]: 2 +cg_hit_words.selected.b5_x_b6 [cross]: 2 +cg_hit_words.selected.b5_x_b7 [cross]: 2 +cg_hit_words.selected.b5_x_b8 [cross]: 3 +cg_hit_words.selected.b6_x_b0 [cross]: 3 +cg_hit_words.selected.b6_x_b1 [cross]: 2 +cg_hit_words.selected.b6_x_b2 [cross]: 2 +cg_hit_words.selected.b6_x_b3 [cross]: 2 +cg_hit_words.selected.b6_x_b4 [cross]: 2 +cg_hit_words.selected.b6_x_b5 [cross]: 2 +cg_hit_words.selected.b6_x_b6 [cross]: 2 +cg_hit_words.selected.b6_x_b7 [cross]: 2 +cg_hit_words.selected.b6_x_b8 [cross]: 3 +cg_hit_words.selected.boundary [cross]: 2 +cg_hit_words.selected.ends [cross]: 4 +cg_hit_words.selected.high [cross]: 4 +cg_hit_words.selected.low [cross]: 3 +cg_hit_words.single_guarded.b1_x_b0 [cross]: 4 +cg_hit_words.single_guarded.b1_x_b1 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b2 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b3 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b4 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b5 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b6 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b7 [cross]: 3 +cg_hit_words.single_guarded.b1_x_b8 [cross]: 4 +cg_hit_words.single_guarded.b2_x_b0 [cross]: 3 +cg_hit_words.single_guarded.b2_x_b1 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b2 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b3 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b4 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b5 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b6 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b7 [cross]: 2 +cg_hit_words.single_guarded.b2_x_b8 [cross]: 3 +cg_hit_words.single_guarded.b3_x_b0 [cross]: 3 +cg_hit_words.single_guarded.b3_x_b1 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b2 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b3 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b4 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b5 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b6 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b7 [cross]: 2 +cg_hit_words.single_guarded.b3_x_b8 [cross]: 3 +cg_hit_words.single_guarded.b4_x_b0 [cross]: 3 +cg_hit_words.single_guarded.b4_x_b1 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b2 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b3 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b4 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b5 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b6 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b7 [cross]: 2 +cg_hit_words.single_guarded.b4_x_b8 [cross]: 3 +cg_hit_words.single_guarded.b5_x_b0 [cross]: 3 +cg_hit_words.single_guarded.b5_x_b1 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b2 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b3 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b4 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b5 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b6 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b7 [cross]: 2 +cg_hit_words.single_guarded.b5_x_b8 [cross]: 3 +cg_hit_words.single_guarded.b6_x_b0 [cross]: 3 +cg_hit_words.single_guarded.b6_x_b1 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b2 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b3 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b4 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b5 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b6 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b7 [cross]: 2 +cg_hit_words.single_guarded.b6_x_b8 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b0 [cross]: 4 +cg_hit_words.single_guarded.b7_x_b1 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b2 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b3 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b4 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b5 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b6 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b7 [cross]: 3 +cg_hit_words.single_guarded.b7_x_b8 [cross]: 4 +cg_hit_words.single_guarded.ends [cross]: 4 +cg_hit_words.whole.all_values [cross]: 10 +cg_narrow_wildcard.cp_b.auto_0: 2 +cg_narrow_wildcard.cp_b.auto_1: 2 +cg_narrow_wildcard.cp_s.outside [ignore]: 0 +cg_narrow_wildcard.cp_s.w: 4 +cg_narrow_wildcard.cp_u.w: 4 +cg_narrow_wildcard.signed_values.complement [cross]: 4 +cg_narrow_wildcard.signed_values.negative [cross]: 4 +cg_narrow_wildcard.signed_values.positive [cross]: 4 +cg_narrow_wildcard.unsigned_values.positive [cross]: 4 +cg_numeric.cp_a.encoded: 2 +cg_numeric.cp_a.encoded_range: 2 +cg_numeric.cp_a.narrow_signed: 2 +cg_numeric.cp_a.negative: 2 +cg_numeric.cp_a.positive: 2 +cg_numeric.cp_a.zero: 2 +cg_numeric.cp_b.high: 3 +cg_numeric.cp_b.low: 3 +cg_numeric.numeric.narrow_signed_x_high [cross]: 1 +cg_numeric.numeric.narrow_signed_x_low [cross]: 1 +cg_numeric.numeric.negative_high [cross]: 1 +cg_numeric.numeric.negative_x_low [cross]: 1 +cg_numeric.numeric.nonnegative_low [cross]: 2 +cg_numeric.numeric.positive_x_high [cross]: 1 +cg_numeric.numeric.typed_negative [cross]: 2 +cg_numeric.numeric.zero_x_high [cross]: 1 +cg_partial.cp_a.auto_0: 2 +cg_partial.cp_a.auto_1: 2 +cg_partial.cp_b.auto_0: 2 +cg_partial.cp_b.auto_1: 2 +cg_partial.selected.auto_1_x_auto_1 [cross]: 1 +cg_partial.selected.either_zero [cross]: 3 +cg_precedence.a.auto_0: 4 +cg_precedence.a.auto_1: 5 +cg_precedence.b.auto_0: 5 +cg_precedence.b.auto_1: 4 +cg_precedence.c.auto_0: 5 +cg_precedence.c.auto_1: 4 +cg_precedence.three_axes.auto_1_x_auto_1_x_auto_0 [cross]: 1 +cg_precedence.three_axes.grouped [cross]: 3 +cg_precedence.three_axes.mixed [cross]: 4 +cg_precedence.three_axes.ungrouped [cross]: 5 +cg_registry.cp_a.auto_0: 2 +cg_registry.cp_a.auto_1: 1 +cg_registry.cp_b.auto_0: 2 +cg_registry.cp_b.auto_1: 1 +cg_registry.selected.auto_0_x_auto_0 [cross]: 1 +cg_registry.selected.auto_1_x_auto_1 [cross]: 0 +cg_registry.selected.off_diagonal [cross]: 2 +cg_sets.cp_a.arrayed[0]: 2 +cg_sets.cp_a.arrayed[1]: 2 +cg_sets.cp_a.high: 4 +cg_sets.cp_a.ignored [ignore]: 0 +cg_sets.cp_a.low: 8 +cg_sets.cp_a.overlap: 8 +cg_sets.cp_a.rest [default]: 4 +cg_sets.cp_b.both: 24 +cg_sets.cp_b.one: 12 +cg_sets.cp_b.zero: 12 +cg_sets.guarded.arrayed[0]_x_both [cross]: 2 +cg_sets.guarded.arrayed[0]_x_zero [cross]: 1 +cg_sets.guarded.arrayed[1]_x_both [cross]: 2 +cg_sets.guarded.arrayed[1]_x_zero [cross]: 1 +cg_sets.guarded.high_x_both [cross]: 4 +cg_sets.guarded.high_x_one [cross]: 2 +cg_sets.guarded.high_x_zero [cross]: 2 +cg_sets.guarded.low_x_both [cross]: 6 +cg_sets.guarded.low_x_zero [cross]: 3 +cg_sets.guarded.overlap_x_both [cross]: 6 +cg_sets.guarded.overlap_x_one [cross]: 3 +cg_sets.guarded.overlap_x_zero [cross]: 3 +cg_sets.guarded.selected [cross]: 4 +cg_sets.logic_ops.all_bins [cross]: 20 +cg_sets.logic_ops.both [cross]: 4 +cg_sets.logic_ops.either [cross]: 14 +cg_sets.logic_ops.named_not_hit [cross]: 16 +cg_sets.logic_ops.named_not_miss [cross]: 20 +cg_sets.logic_ops.negated [cross]: 16 +cg_sets.logic_ops.negated_range [cross]: 8 +cg_sets.logic_ops.not_hit_negation [cross]: 8 +cg_sets.logic_ops.repeated [cross]: 8 +cg_sets.ranges.lower [cross]: 8 +cg_sets.ranges.middle [cross]: 12 +cg_sets.ranges.named [cross]: 2 +cg_sets.ranges.upper [cross]: 2 +cg_sets.ranges.values [cross]: 14 +cg_transition.cp_a.combined: 4 +cg_transition.cp_a.first: 2 +cg_transition.cp_a.second: 2 +cg_transition.cp_a.value: 2 +cg_transition.cp_b.auto_0: 5 +cg_transition.cp_b.auto_1: 5 +cg_transition.transitions.ending_one [cross]: 4 +cg_transition.transitions.named [cross]: 4 +cg_transition.transitions.not_ending_one [cross]: 4 +cg_transition_ignore.cp_a.seq: 2 +cg_transition_ignore.cp_a.value_only [ignore]: 2 +cg_transition_ignore.cp_b.auto_0: 2 +cg_transition_ignore.cp_b.auto_1: 2 +cg_transition_ignore.selected.kept [cross]: 2 +cg_wildcard.cp_a.high: 2 +cg_wildcard.cp_a.odd: 2 +cg_wildcard.cp_b.auto_0: 2 +cg_wildcard.cp_b.auto_1: 2 +cg_wildcard.wildcard_range.interior [cross]: 2 +cg_wildcard.wildcard_range.other [cross]: 2 +cg_wildcard.wildcard_range.signed_pattern [cross]: 2 +cg_words.all_tuples.all_bins [cross]: 81 +cg_words.cp_a.values[0]: 9 +cg_words.cp_a.values[1]: 9 +cg_words.cp_a.values[2]: 9 +cg_words.cp_a.values[3]: 9 +cg_words.cp_a.values[4]: 9 +cg_words.cp_a.values[5]: 9 +cg_words.cp_a.values[6]: 9 +cg_words.cp_a.values[7]: 9 +cg_words.cp_a.values[8]: 9 +cg_words.cp_b.values[0]: 9 +cg_words.cp_b.values[1]: 9 +cg_words.cp_b.values[2]: 9 +cg_words.cp_b.values[3]: 9 +cg_words.cp_b.values[4]: 9 +cg_words.cp_b.values[5]: 9 +cg_words.cp_b.values[6]: 9 +cg_words.cp_b.values[7]: 9 +cg_words.cp_b.values[8]: 9 +cg_words.partial.boundary [cross]: 9 +cg_words.partial.either [cross]: 17 +cg_words.partial.last_row [cross]: 1 +cg_words.partial.values[0]_x_values[0] [cross]: 1 +cg_words.partial.values[0]_x_values[1] [cross]: 1 +cg_words.partial.values[0]_x_values[2] [cross]: 1 +cg_words.partial.values[0]_x_values[3] [cross]: 1 +cg_words.partial.values[0]_x_values[4] [cross]: 1 +cg_words.partial.values[0]_x_values[5] [cross]: 1 +cg_words.partial.values[0]_x_values[6] [cross]: 1 +cg_words.partial.values[0]_x_values[7] [cross]: 1 +cg_words.partial.values[1]_x_values[0] [cross]: 1 +cg_words.partial.values[1]_x_values[1] [cross]: 1 +cg_words.partial.values[1]_x_values[2] [cross]: 1 +cg_words.partial.values[1]_x_values[3] [cross]: 1 +cg_words.partial.values[1]_x_values[4] [cross]: 1 +cg_words.partial.values[1]_x_values[5] [cross]: 1 +cg_words.partial.values[1]_x_values[6] [cross]: 1 +cg_words.partial.values[1]_x_values[7] [cross]: 1 +cg_words.partial.values[2]_x_values[0] [cross]: 1 +cg_words.partial.values[2]_x_values[1] [cross]: 1 +cg_words.partial.values[2]_x_values[2] [cross]: 1 +cg_words.partial.values[2]_x_values[3] [cross]: 1 +cg_words.partial.values[2]_x_values[4] [cross]: 1 +cg_words.partial.values[2]_x_values[5] [cross]: 1 +cg_words.partial.values[2]_x_values[6] [cross]: 1 +cg_words.partial.values[2]_x_values[7] [cross]: 1 +cg_words.partial.values[3]_x_values[0] [cross]: 1 +cg_words.partial.values[3]_x_values[1] [cross]: 1 +cg_words.partial.values[3]_x_values[2] [cross]: 1 +cg_words.partial.values[3]_x_values[3] [cross]: 1 +cg_words.partial.values[3]_x_values[4] [cross]: 1 +cg_words.partial.values[3]_x_values[5] [cross]: 1 +cg_words.partial.values[3]_x_values[6] [cross]: 1 +cg_words.partial.values[3]_x_values[7] [cross]: 1 +cg_words.partial.values[4]_x_values[0] [cross]: 1 +cg_words.partial.values[4]_x_values[1] [cross]: 1 +cg_words.partial.values[4]_x_values[2] [cross]: 1 +cg_words.partial.values[4]_x_values[3] [cross]: 1 +cg_words.partial.values[4]_x_values[4] [cross]: 1 +cg_words.partial.values[4]_x_values[5] [cross]: 1 +cg_words.partial.values[4]_x_values[6] [cross]: 1 +cg_words.partial.values[4]_x_values[7] [cross]: 1 +cg_words.partial.values[5]_x_values[0] [cross]: 1 +cg_words.partial.values[5]_x_values[1] [cross]: 1 +cg_words.partial.values[5]_x_values[2] [cross]: 1 +cg_words.partial.values[5]_x_values[3] [cross]: 1 +cg_words.partial.values[5]_x_values[4] [cross]: 1 +cg_words.partial.values[5]_x_values[5] [cross]: 1 +cg_words.partial.values[5]_x_values[6] [cross]: 1 +cg_words.partial.values[5]_x_values[7] [cross]: 1 +cg_words.partial.values[6]_x_values[0] [cross]: 1 +cg_words.partial.values[6]_x_values[1] [cross]: 1 +cg_words.partial.values[6]_x_values[2] [cross]: 1 +cg_words.partial.values[6]_x_values[3] [cross]: 1 +cg_words.partial.values[6]_x_values[4] [cross]: 1 +cg_words.partial.values[6]_x_values[5] [cross]: 1 +cg_words.partial.values[6]_x_values[6] [cross]: 1 +cg_words.partial.values[6]_x_values[7] [cross]: 1 +cg_words.partial.values[8]_x_values[1] [cross]: 1 +cg_words.partial.values[8]_x_values[2] [cross]: 1 +cg_words.partial.values[8]_x_values[3] [cross]: 1 +cg_words.partial.values[8]_x_values[4] [cross]: 1 +cg_words.partial.values[8]_x_values[5] [cross]: 1 +cg_words.partial.values[8]_x_values[6] [cross]: 1 +cg_words.partial.values[8]_x_values[7] [cross]: 1 +cg_zero_product.cp_a.zero: 1 +cg_zero_product.cp_empty.other [default]: 1 diff --git a/test_regress/t/t_covergroup_binsof_ops.py b/test_regress/t/t_covergroup_binsof_ops.py new file mode 100755 index 000000000..19df33cf8 --- /dev/null +++ b/test_regress/t/t_covergroup_binsof_ops.py @@ -0,0 +1,29 @@ +#!/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', 'ms') + +# The exclusion checks intentionally reference ignore/default bins. +test.ms_run_flags += ['-suppress', '13196'] +test.compile(verilator_flags2=[ + '--coverage', '--debug-self-test', '--dump-tree', '--dump-tree-json', '--timing' +], + ms_flags2=['-suppress', '13196'], + timing_loop=True, + threads=(2 if test.vltmt else 1)) +test.execute() + +if test.vlt_all: + coverage_covergroup_common.covergroup_coverage_report(test) + test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_binsof_ops.v b/test_regress/t/t_covergroup_binsof_ops.v new file mode 100644 index 000000000..ff5f6f504 --- /dev/null +++ b/test_regress/t/t_covergroup_binsof_ops.v @@ -0,0 +1,557 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0); +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0); +// verilog_format: on + +module t; + timeunit 1ns; timeprecision 1ps; + bit clk = 0; + always #5 clk = ~clk; + + int cyc = 0; + + covergroup cg_sets with function sample (bit [6:0] a, bit b, bit enabled); + cp_a: coverpoint a { + bins low = {[0 : 3]}; + bins overlap = {[2 : 5]}; + bins high = {[6 : 7]}; + bins arrayed[] = {8, 9}; + ignore_bins ignored = {12}; + bins rest = default; + } + cp_b: coverpoint b {bins zero = {0}; bins one = {1}; bins both = {0, 1};} + ranges: cross cp_a, cp_b{ + // Intersect selects entire bins, including values outside the filter. + bins middle = binsof (cp_a) intersect { + [3 : 4] + }; + bins named = binsof (cp_a.arrayed) intersect {9}; + bins lower = binsof (cp_a) intersect {[$ : 0]}; + bins upper = binsof (cp_a) intersect {[9 : $]}; + bins values = binsof (cp_a) intersect {1, [7 : 8]}; + bins absent = binsof (cp_a) intersect {30}; + bins outside_domain = binsof (cp_a) intersect {128}; + bins reversed = binsof (cp_a) intersect {[5 : 3]}; + // Negative signed filters must not wrap into an unsigned bin's bit pattern. + bins unsigned_negative = binsof (cp_b.one) intersect { + 1'sb1 + }; + } + logic_ops: cross cp_a, cp_b{ + bins negated = !binsof (cp_a.low); + bins negated_range = !binsof (cp_a) intersect {[3 : 4]}; + bins named_not_hit = !binsof (cp_a.low) intersect {3}; + bins named_not_miss = !binsof (cp_a.low) intersect {4}; + bins either = binsof (cp_a.low) || binsof (cp_b.one); + bins both = binsof (cp_a.low) && binsof (cp_b.one); + bins repeated = binsof (cp_a.low) || binsof (cp_a.low); + bins all_bins = binsof (cp_a.low) || !binsof (cp_a.low); + bins no_bins = !binsof (cp_a); + // Distinct bins may overlap in values but never in bin identity. + bins no_tuple = binsof (cp_a.low) && binsof (cp_a.overlap); + bins not_hit_negation = binsof (cp_a.low) && !binsof (cp_a.overlap); + bins excluded = binsof (cp_a.ignored) || binsof (cp_a.rest); + } + guarded: cross cp_a, cp_b iff (enabled) { + bins selected = (binsof(cp_a.low) || binsof(cp_a.arrayed)) + && binsof(cp_b.one) iff (a != 0); + } + endgroup + + covergroup cg_partial with function sample (bit a, bit b); + cp_a: coverpoint a; + cp_b: coverpoint b; + // Each item has two bins. The union consumes three tuples, leaving one + // explicit bin and the automatic (1,1) bin in the cross. + selected: cross cp_a, cp_b{ + bins either_zero = binsof (cp_a) intersect {0} || binsof (cp_b) intersect {0}; + } + endgroup + + covergroup cg_zero_product with function sample (bit value); + cp_a: coverpoint value {bins zero = {0};} + cp_empty: coverpoint value {bins other = default;} + selected: cross cp_a, cp_empty; + endgroup + + covergroup cg_fixed_words with function sample (bit [3:0] a, bit [3:0] b, bit enable); + cp_a: coverpoint a { + bins b0 = {0}; + bins b1 = {[0 : 1]}; + bins b2 = {[0 : 2]}; + bins b3 = {[0 : 3]}; + bins b4 = {[0 : 4]}; + bins b5 = {[0 : 5]}; + bins b6 = {[0 : 6]}; + bins b7 = {[0 : 7]}; + bins b8 = {[0 : 8]}; + bins b9 = {[0 : 9]}; + bins b10 = {[0 : 10]}; + bins b11 = {[0 : 11]}; + bins b12 = {[0 : 12]}; + bins b13 = {[0 : 13]}; + bins b14 = {[0 : 14]}; + bins b15 = {[0 : 15]}; + } + cp_b: coverpoint b; + selected: cross cp_a, cp_b{bins all_values = binsof (cp_a);} + sparse: cross cp_a, cp_b{ + bins all_values = binsof (cp_a); + bins subset = binsof (cp_a.b0); + } + guarded: cross cp_a, cp_b{ + bins all_values = binsof (cp_a); + bins subset = binsof (cp_a.b0) iff (enable); + } + endgroup + + covergroup cg_precedence with function sample (bit a, bit b, bit c); + coverpoint a; + coverpoint b; + coverpoint c; + three_axes: cross a, b, c{ + bins ungrouped = binsof(a) intersect {0} + || binsof(b) intersect {0} && binsof(c) intersect { + 1 + }; + bins grouped = (binsof (a) intersect {0} || binsof (b) intersect { + 0 + }) && binsof (c) intersect { + 1 + }; + bins mixed = !binsof (a) intersect {0} && (binsof (b) intersect {0} || !binsof (c) intersect { + 0 + }); + } + endgroup + + typedef bit signed [6:0] signed_t; + typedef bit [64:0] wide_t; + localparam wide_t LOW = 65'h0_ffff_ffff_ffff_ffff; + localparam wide_t HIGH = 65'h1_0000_0000_0000_0000; + + covergroup cg_numeric with function sample (signed_t a, wide_t b); + cp_a: coverpoint a { + bins negative = {[-4 : -1]}; + bins encoded = {7'b1111111}; + bins encoded_range = {[7'd120 : 7'd127]}; + bins narrow_signed = {-7'sd1}; + bins zero = {0}; + bins positive = {[1 : 4]}; + } + cp_b: coverpoint b {bins low = {LOW}; bins high = {HIGH};} + numeric: cross cp_a, cp_b{ + bins negative_high = binsof (cp_a) intersect {[-3 : -2]} && binsof (cp_b) intersect { + [HIGH : $] + }; + bins nonnegative_low = !binsof (cp_a) intersect {[$ : -1]} && binsof (cp_b) intersect {LOW}; + bins typed_negative = binsof(cp_a.encoded) intersect {-1} + || binsof(cp_a.encoded_range) intersect { + -2 + }; + bins unsigned_negative = binsof (cp_b.low) intersect {64'shffff_ffff_ffff_ffff}; + } + endgroup + + covergroup cg_transition with function sample (bit [6:0] a, bit b); + cp_a: coverpoint a { + bins first = (0 => 1); + bins second = (2 => 3); + bins combined = (0 => 1), (2 => 3); + bins value = {4}; + } + cp_b: coverpoint b; + transitions: cross cp_a, cp_b{ + bins ending_one = binsof (cp_a) intersect {1}; + bins not_ending_one = !binsof (cp_a) intersect {1}; + bins named = binsof (cp_a.combined) intersect {3}; + bins not_initial = binsof (cp_a.first) intersect {0}; + } + endgroup + + covergroup cg_wildcard with function sample (signed_t a, bit b); + cp_a: coverpoint a {wildcard bins odd = {7'b??????1}; bins high = {[32 : 35]};} + cp_b: coverpoint b; + wildcard_range: cross cp_a, cp_b{ + // Neither endpoint matches, but the interior value 3 does. + bins interior = binsof (cp_a) intersect { + [2 : 4] + }; + bins even = binsof (cp_a) intersect {2}; + bins other = !binsof (cp_a) intersect {[2 : 4]}; + bins signed_pattern = binsof (cp_a.odd) intersect {[-4 : -1]}; + } + endgroup + + covergroup cg_words with function sample (bit [6:0] a, bit [6:0] b); + cp_a: coverpoint a {bins values[] = {[0 : 8]};} + cp_b: coverpoint b {bins values[] = {[0 : 8]};} + partial: cross cp_a, cp_b{ + bins boundary = binsof (cp_a) intersect {7}; + bins either = binsof (cp_a) intersect {7} || binsof (cp_b) intersect {8}; + bins last_row = binsof (cp_a) intersect {8} && binsof (cp_b) intersect {0}; + } + all_tuples: cross cp_a, cp_b{bins all_bins = binsof (cp_a);} + endgroup + + covergroup cg_fast_paths with function sample ( + bit [2:0] a, bit [2:0] b, bit [2:0] c, bit enable_a, bit enable_b, bit enable_late + ); + cp_a: coverpoint a {bins first = {0, 1}; bins second = {0, 2}; bins last = {3};} + cp_b: coverpoint b {bins first = {0, 1}; bins second = {0, 2}; bins last = {3};} + cp_c: coverpoint c {bins first = {0, 1}; bins second = {0, 2}; bins last = {3};} + selected: cross cp_a, cp_b, cp_c{ + bins early_a = binsof (cp_a.first) && binsof (cp_b.first) iff (enable_a); + bins early_b = binsof (cp_a.first) && binsof (cp_b.first) iff (enable_b); + bins late = binsof (cp_a.last) && binsof (cp_b.last) && binsof (cp_c.last) iff (enable_late); + } + endgroup + + covergroup cg_guards with function sample ( + bit [6:0] enables, bit [64:0] wide_enable, bit signed [6:0] signed_enable, bit [2:0] index + ); + cp_a: coverpoint 1'b0 {bins zero = {0};} + cp_b: coverpoint 1'b0 {bins zero = {0};} + selected: cross cp_a, cp_b{ + bins low_bit = binsof (cp_a) iff (enables[0]); + bins high_bit = binsof (cp_a) iff (enables[1]); + bins selected_bit = binsof (cp_a) iff (enables[index]); + bins low_slice = binsof (cp_a) iff (|enables[1:0]); + bins whole_vector = binsof (cp_a) iff (|enables); + bins wide_bit = binsof (cp_a) iff (wide_enable[0]); + bins wide_vector = binsof (cp_a) iff (|wide_enable); + bins signed_vector = binsof (cp_a) iff (|signed_enable); + bins constant_true = binsof (cp_a) iff (1'b1); + bins unguarded = binsof (cp_a); + } + endgroup + + covergroup cg_hit_words with function sample (bit [3:0] a, bit [3:0] b, bit [2:0] enables); + cp_a: coverpoint a { + bins b0 = {0, 1}; + bins b1 = {0, 2}; + bins b2 = {0, 3}; + bins b3 = {0, 4}; + bins b4 = {0, 5}; + bins b5 = {0, 6}; + bins b6 = {0, 7}; + bins b7 = {0, 8}; + bins b8 = {0, 9}; + } + cp_b: coverpoint b { + bins b0 = {0, 1}; + bins b1 = {0, 2}; + bins b2 = {0, 3}; + bins b3 = {0, 4}; + bins b4 = {0, 5}; + bins b5 = {0, 6}; + bins b6 = {0, 7}; + bins b7 = {0, 8}; + bins b8 = {0, 9}; + } + selected: cross cp_a, cp_b{ + bins low = binsof (cp_a.b0) && binsof (cp_b.b0) iff (enables[0]); + bins boundary = binsof (cp_a.b7) iff (enables[1]); + bins high = binsof (cp_a.b8) && binsof (cp_b.b8); + bins ends = binsof (cp_a.b0) || binsof (cp_a.b8) iff (enables[2]); + } + whole: cross cp_a, cp_b{bins all_values = binsof (cp_a);} + // A multiword selection remains excluded from automatic bins when its guard is false. + single_guarded: cross cp_a, cp_b{ + bins ends = binsof (cp_a.b0) || binsof (cp_a.b8) iff (enables[2]); + } + endgroup + + // Check four-state bin identities without relying on four-state sampling. + covergroup cg_four_state with function sample (logic [2:0] a, bit b); + cp_a: coverpoint a {bins known = {3'b001}; bins xstate = {3'bx01}; bins zstate = {3'bz01};} + cp_b: coverpoint b; + selected: cross cp_a, cp_b{ + bins exact_x = binsof (cp_a) intersect {3'bx01}; + bins exact_z = binsof (cp_a) intersect {3'bz01}; + bins not_x = !binsof (cp_a) intersect {3'bx01}; + } + endgroup + + covergroup cg_narrow_wildcard with function sample (signed_t a, bit b); + cp_s: coverpoint a {wildcard bins w = {3'sb?01}; ignore_bins outside = {7'sd7};} + cp_u: coverpoint 7'd1 {wildcard bins w = {3'sb?01};} + cp_b: coverpoint b; + signed_values: cross cp_s, cp_b{ + bins positive = binsof (cp_s.w) intersect {1}; + bins negative = binsof (cp_s.w) intersect {-3}; + bins not_expanded = binsof (cp_s.w) intersect {5}; + bins pattern_miss = binsof (cp_s.w) intersect {0}; + bins complement = !binsof (cp_s.w) intersect {5}; + } + unsigned_values: cross cp_u, cp_b{ + bins positive = binsof (cp_u.w) intersect {1}; + bins not_expanded = binsof (cp_u.w) intersect {5}; + } + endgroup + + covergroup cg_excluded with function sample (bit [6:0] a, bit b); + cp_a: coverpoint a { + bins normal = {[0 : 3]}; ignore_bins ignored = {1}; illegal_bins illegal = {3}; + } + cp_b: coverpoint b; + selected: cross cp_a, cp_b{ + bins kept = binsof (cp_a.normal) intersect {0}; + bins partial = binsof (cp_a.normal) intersect {[1 : 3]}; + bins removed_ignore = binsof (cp_a.normal) intersect {1}; + bins removed_illegal = binsof (cp_a.normal) intersect {3}; + bins removed_union = binsof (cp_a.normal) intersect {1, 3}; + bins complement = !binsof (cp_a.normal) intersect {1}; + } + endgroup + + covergroup cg_excluded_wildcard with function sample (bit [6:0] a, bit b); + cp_a: coverpoint a { + bins normal = {[0 : 15]}; + wildcard ignore_bins odds = {7'b??????1}; + illegal_bins band = {[4 : 7]}; + ignore_bins empty_range = {[9 : 8]}; + } + cp_b: coverpoint b; + selected: cross cp_a, cp_b{ + bins kept = binsof (cp_a.normal) intersect {2}; + bins removed_pattern = binsof (cp_a.normal) intersect {1, 9, 15}; + bins removed_range = binsof (cp_a.normal) intersect {[4 : 7]}; + bins removed_union = binsof (cp_a.normal) intersect {[3 : 6]}; + } + endgroup + + covergroup cg_excluded_wide with function sample (wide_t a, bit b); + cp_a: coverpoint a {bins normal = {LOW, HIGH}; ignore_bins ignored = {LOW};} + cp_b: coverpoint b; + selected: cross cp_a, cp_b{ + bins kept = binsof (cp_a.normal) intersect {HIGH}; + bins removed = binsof (cp_a.normal) intersect {LOW}; + } + endgroup + + // Keep the large symbolic-exclusion stress for Verilator; Questa construction + // is prohibitively slow at 31 bits, so use the same pattern at seven bits elsewhere. +`ifdef VERILATOR + typedef bit [30:0] excluded_t; +`else + typedef bit [6:0] excluded_t; +`endif + localparam excluded_t EXCLUDED_MAX = '1; + + covergroup cg_excluded_many with function sample (excluded_t a, bit b); + cp_a: coverpoint a { + bins whole = {[0 : EXCLUDED_MAX]}; + // Only the all-ones value remains; enumerating live prefix subsets is exponential. +`ifdef VERILATOR + wildcard ignore_bins zero_bit = { + (31'bx & ~31'h00000001), (31'bx & ~31'h00000002), (31'bx & ~31'h00000004), + (31'bx & ~31'h00000008), (31'bx & ~31'h00000010), (31'bx & ~31'h00000020), + (31'bx & ~31'h00000040), (31'bx & ~31'h00000080), (31'bx & ~31'h00000100), + (31'bx & ~31'h00000200), (31'bx & ~31'h00000400), (31'bx & ~31'h00000800), + (31'bx & ~31'h00001000), (31'bx & ~31'h00002000), (31'bx & ~31'h00004000), + (31'bx & ~31'h00008000), (31'bx & ~31'h00010000), (31'bx & ~31'h00020000), + (31'bx & ~31'h00040000), (31'bx & ~31'h00080000), (31'bx & ~31'h00100000), + (31'bx & ~31'h00200000), (31'bx & ~31'h00400000), (31'bx & ~31'h00800000), + (31'bx & ~31'h01000000), (31'bx & ~31'h02000000), (31'bx & ~31'h04000000), + (31'bx & ~31'h08000000), (31'bx & ~31'h10000000), (31'bx & ~31'h20000000), + (31'bx & ~31'h40000000) + }; +`else + wildcard ignore_bins zero_bit = { + 7'b??????0, 7'b?????0?, 7'b????0??, 7'b???0???, 7'b??0????, 7'b?0?????, 7'b0?????? + }; +`endif + } + cp_b: coverpoint b; + selected: cross cp_a, cp_b{bins kept = binsof (cp_a.whole) intersect {[0 : EXCLUDED_MAX]};} + endgroup + + covergroup cg_transition_ignore with function sample (bit a, bit b); + cp_a: coverpoint a {bins seq = (0 => 1); ignore_bins value_only = {1};} + cp_b: coverpoint b; + selected: cross cp_a, cp_b{bins kept = binsof (cp_a.seq) intersect {1};} + endgroup + + // State-bin exclusions also apply to exact four-state selections at construction. + covergroup cg_excluded_four_state with function sample (logic [2:0] a, bit b); + cp_a: coverpoint a { + bins states = {3'b001, 3'bx01, 3'bz01}; + ignore_bins xstate = {3'bx01}; + wildcard ignore_bins other = {3'b1?1}; + } + cp_b: coverpoint b; + selected: cross cp_a, cp_b{ + bins kept = binsof (cp_a.states) intersect {3'bz01}; + bins numeric = binsof (cp_a.states) intersect {1}; + bins removed = binsof (cp_a.states) intersect {3'bx01}; + } + endgroup + + covergroup cg_registry with function sample (bit a, bit b); + cp_a: coverpoint a; + cp_b: coverpoint b; + selected: cross cp_a, cp_b{ + bins off_diagonal = binsof(cp_a) intersect {0} && binsof(cp_b) intersect {1} + || binsof(cp_a) intersect {1} && binsof(cp_b) intersect { + 0 + }; + } + endgroup + + task automatic sample_retired(bit a, bit b); + cg_registry transient = new; + transient.sample(a, b); + endtask + + cg_sets sets_cov = new; + cg_partial partial_cov = new; + cg_zero_product zero_product_cov = new; + cg_fixed_words fixed_words_cov = new; + cg_precedence precedence_cov = new; + cg_numeric numeric_cov = new; + cg_transition transition_cov = new; + cg_wildcard wildcard_cov = new; + cg_words words_cov = new; + cg_fast_paths fast_paths_cov = new; + cg_guards guards_cov = new; + cg_hit_words hit_words_cov = new; + cg_four_state four_state_cov = new; + cg_narrow_wildcard narrow_wildcard_cov = new; + cg_excluded excluded_cov = new; + cg_excluded_wildcard excluded_wildcard_cov = new; + cg_excluded_wide excluded_wide_cov = new; + cg_excluded_many excluded_many_cov = new; + cg_transition_ignore transition_ignore_cov = new; + cg_excluded_four_state excluded_four_state_cov = new; + + always @(posedge clk) begin + if (cyc == 0) sample_retired(0, 1); + if (cyc == 1) sample_retired(1, 0); + if (cyc == 2) sample_retired(0, 0); + if (cyc < 81) begin + if (cyc == 0) zero_product_cov.sample(0); + if (cyc < 16) fixed_words_cov.sample(4'(cyc % 5 == 4 ? 15 : 4 * (cyc % 5)), 4'(cyc), 1'(cyc)); + if (cyc < 24) sets_cov.sample(7'(cyc / 2), 1'(cyc), cyc / 2 != 2); + if (cyc < 4) begin + if (cyc == 0) `checkr(partial_cov.get_inst_coverage(), 0.0); + partial_cov.sample(1'(cyc / 2), 1'(cyc)); + case (cyc) + 0: begin + `checkr(partial_cov.get_inst_coverage(), 50.0); + end + 1: begin + `checkr(partial_cov.get_inst_coverage(), 100.0 * 4.0 / 6.0); + end + 2: begin + `checkr(partial_cov.get_inst_coverage(), 100.0 * 5.0 / 6.0); + end + 3: begin + `checkr(partial_cov.get_inst_coverage(), 100.0); + end + default: ; + endcase + end + if (cyc < 8) precedence_cov.sample(1'(cyc / 4), 1'(cyc / 2), 1'(cyc)); + if (cyc == 8) precedence_cov.sample(1, 0, 0); + if (cyc < 8) + guards_cov.sample(7'(cyc), 65'(cyc / 4) << 64 | 65'(cyc % 2), cyc < 4 ? -7'sd1 : 7'sd0, + 3'(cyc % 3)); + // Alternate multiword, empty, single-tuple, and automatic-only hit sets. + if (cyc < 12) begin + case (cyc) + 0: hit_words_cov.sample(0, 0, 3'b111); + 1: hit_words_cov.sample(10, 0, 3'b111); + 2: hit_words_cov.sample(1, 1, 3'b000); + 3: hit_words_cov.sample(9, 9, 3'b000); + 4: hit_words_cov.sample(1, 0, 3'b001); + 5: hit_words_cov.sample(0, 1, 3'b100); + 6: hit_words_cov.sample(0, 0, 3'b000); + 7: hit_words_cov.sample(8, 0, 3'b010); + 8: hit_words_cov.sample(0, 9, 3'b100); + 9: hit_words_cov.sample(10, 10, 3'b111); + 10: hit_words_cov.sample(2, 0, 3'b111); + 11: hit_words_cov.sample(1, 1, 3'b101); + default: ; + endcase + end + // Vary guards and include a no-hit sample before covering the remaining tuples. + if (cyc < 8) begin + case (cyc) + 0: fast_paths_cov.sample(0, 0, 0, 1, 0, 0); + 1: fast_paths_cov.sample(0, 0, 0, 0, 0, 0); + 2: fast_paths_cov.sample(0, 4, 0, 1, 1, 1); + 3: fast_paths_cov.sample(0, 0, 0, 0, 1, 1); + 4: fast_paths_cov.sample(0, 0, 0, 1, 1, 1); + 5: fast_paths_cov.sample(0, 0, 0, 1, 1, 0); + 6: fast_paths_cov.sample(3, 3, 3, 1, 1, 1); + 7: fast_paths_cov.sample(0, 0, 0, 1, 1, 1); + default: ; + endcase + end + else if (cyc < 35) begin + fast_paths_cov.sample(3'(1 + (cyc - 8) / 9), 3'(1 + ((cyc - 8) / 3) % 3), + 3'(1 + (cyc - 8) % 3), 1, 1, 1); + end + if (cyc < 6) + numeric_cov.sample(signed_t'(cyc < 2 ? -1 : 2 * (cyc / 2) - 2), + (cyc % 2 != 0) ? HIGH : LOW); + if (cyc < 10) transition_cov.sample(7'(cyc % 5), 1'(cyc / 5)); + if (cyc < 4) wildcard_cov.sample(cyc < 2 ? 7'sd1 : 7'sd32, 1'(cyc)); + if (cyc < 4) begin + narrow_wildcard_cov.sample(cyc < 2 ? 7'sd1 : -7'sd3, 1'(cyc)); + excluded_cov.sample(cyc < 2 ? 7'd0 : 7'd2, 1'(cyc)); + excluded_wildcard_cov.sample(cyc < 2 ? 7'd0 : 7'd8, 1'(cyc)); + transition_ignore_cov.sample(1'(cyc), 1'(cyc / 2)); + end + if (cyc < 2) begin + excluded_wide_cov.sample(HIGH, 1'(cyc)); + excluded_many_cov.sample(EXCLUDED_MAX, 1'(cyc)); + end + words_cov.sample(7'(cyc / 9), 7'(cyc % 9)); + end + else begin + `checkr(sets_cov.get_inst_coverage(), 100.0); + `checkr(partial_cov.get_inst_coverage(), 100.0); + `checkr(zero_product_cov.get_inst_coverage(), 100.0); + `checkr(fixed_words_cov.get_inst_coverage(), 100.0); + `checkr(precedence_cov.get_inst_coverage(), 100.0); + `checkr(numeric_cov.get_inst_coverage(), 100.0); + `checkr(transition_cov.get_inst_coverage(), 100.0); + `checkr(wildcard_cov.get_inst_coverage(), 100.0); + `checkr(words_cov.get_inst_coverage(), 100.0); + `checkr(fast_paths_cov.get_inst_coverage(), 100.0); + `checkr(guards_cov.get_inst_coverage(), 100.0); + `checkr(hit_words_cov.get_inst_coverage(), 100.0); + `checkr(four_state_cov.get_inst_coverage(), 0.0); + `checkr(narrow_wildcard_cov.get_inst_coverage(), 100.0); + `checkr(excluded_cov.get_inst_coverage(), 100.0); + `checkr(excluded_wildcard_cov.get_inst_coverage(), 100.0); + `checkr(excluded_wide_cov.get_inst_coverage(), 100.0); + `checkr(excluded_many_cov.get_inst_coverage(), 100.0); + `checkr(transition_ignore_cov.get_inst_coverage(), 100.0); + `checkr(excluded_four_state_cov.get_inst_coverage(), 0.0); +`ifdef VERILATOR + // Bin counts alone cannot detect leaked live instances. Standard SystemVerilog + // cannot query the registry's live/retired state, so check retirement directly. + `checkd( + $c32( + "Verilated::threadContextp()->covergroupRegistryp()->liveInstanceCount(\"cg_registry\")"), + 0); + `checkd($c32( + "Verilated::threadContextp()->covergroupRegistryp()->retiredInstanceCount(\"cg_registry\")" + ), 3); +`endif + $write("*-* All Finished *-*\n"); + $finish; + end + ++cyc; + end +endmodule diff --git a/test_regress/t/t_covergroup_unsup.out b/test_regress/t/t_covergroup_unsup.out index b1c7b79dc..25ad77978 100644 --- a/test_regress/t/t_covergroup_unsup.out +++ b/test_regress/t/t_covergroup_unsup.out @@ -90,50 +90,29 @@ %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:21: Unsupported: 'binsof' in coverage select expression - 168 | bins bin_na = ! binsof(a); - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:30: Unsupported: 'intersect' in coverage select expression - 170 | bins bin_d = binsof(a) intersect { b }; - | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:31: Unsupported: 'intersect' in coverage select expression - 171 | bins bin_nd = ! binsof(a) intersect { b }; - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:20: Unsupported: 'with' in coverage select expression - 173 | bins bin_e = with (a); +%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:174:24: Unsupported: 'with' in coverage select expression - 174 | bins bin_not_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:176:32: Unsupported: '&&' in coverage select expression - 176 | bins bin_and = binsof(a) && binsof(b); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:177:31: Unsupported: '||' in coverage select expression - 177 | bins bin_or = binsof(a) || binsof(b); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:178:33: Unsupported: 'with' in coverage select expression - 178 | bins bin_with = binsof(a) with (a); +%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:179:49: Unsupported: 'with' in coverage select expression - 179 | bins bin_or_with = binsof(a) || 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:179:36: Unsupported: '||' in coverage select expression - 179 | bins bin_or_with = binsof(a) || binsof(a) with (a); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:180:50: Unsupported: 'with' in coverage select expression - 180 | bins bin_and_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:180:37: Unsupported: '&&' in coverage select expression - 180 | bins bin_and_with = binsof(a) && binsof(a) with (a); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:183:7: Unsupported: explicit coverage cross bins - 183 | ignore_bins ib_cross = binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins + 176 | ignore_bins ib_cross = binsof(a); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:184:7: Unsupported: explicit coverage cross bins - 184 | illegal_bins lib_cross = binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:177:7: Unsupported: explicit coverage cross bins + 177 | illegal_bins lib_cross = binsof(a); | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_covergroup_unsup.v:209:5: Unsupported: covergroup inheritance (extends) - 209 | covergroup extends cg_empty; +%Error-UNSUPPORTED: t/t_covergroup_unsup.v:202:5: Unsupported: covergroup inheritance (extends) + 202 | 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 ebb69ad21..931a0ce9b 100644 --- a/test_regress/t/t_covergroup_unsup.v +++ b/test_regress/t/t_covergroup_unsup.v @@ -165,20 +165,13 @@ module t ( covergroup cg_cross_bins; cross a, b { - bins bin_na = ! binsof(a); - - bins bin_d = binsof(a) intersect { b }; - bins bin_nd = ! binsof(a) intersect { b }; - bins bin_e = with (a); bins bin_not_e = ! with (a); - bins bin_and = binsof(a) && binsof(b); - bins bin_or = binsof(a) || binsof(b); bins bin_with = binsof(a) with (a); bins bin_or_with = binsof(a) || binsof(a) with (a); bins bin_and_with = binsof(a) && binsof(a) with (a); - bins bin_multiple_fields = binsof(p.inner_packet.field); + bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); // explicit cross ignore/illegal bins (unsupported) ignore_bins ib_cross = binsof(a); illegal_bins lib_cross = binsof(a); diff --git a/test_regress/t/t_covergroup_unsup_ign2.out b/test_regress/t/t_covergroup_unsup_ign2.out index 895e28660..3e42d767b 100644 --- a/test_regress/t/t_covergroup_unsup_ign2.out +++ b/test_regress/t/t_covergroup_unsup_ign2.out @@ -90,47 +90,26 @@ %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:21: Unsupported: 'binsof' in coverage select expression - 168 | bins bin_na = ! binsof(a); - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:30: Unsupported: 'intersect' in coverage select expression - 170 | bins bin_d = binsof(a) intersect { b }; - | ^~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:171:31: Unsupported: 'intersect' in coverage select expression - 171 | bins bin_nd = ! binsof(a) intersect { b }; - | ^ -%Warning-COVERIGN: t/t_covergroup_unsup.v:173:20: Unsupported: 'with' in coverage select expression - 173 | bins bin_e = with (a); +%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:174:24: Unsupported: 'with' in coverage select expression - 174 | bins bin_not_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:176:32: Unsupported: '&&' in coverage select expression - 176 | bins bin_and = binsof(a) && binsof(b); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:177:31: Unsupported: '||' in coverage select expression - 177 | bins bin_or = binsof(a) || binsof(b); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:178:33: Unsupported: 'with' in coverage select expression - 178 | bins bin_with = binsof(a) with (a); +%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:179:49: Unsupported: 'with' in coverage select expression - 179 | bins bin_or_with = binsof(a) || 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:179:36: Unsupported: '||' in coverage select expression - 179 | bins bin_or_with = binsof(a) || binsof(a) with (a); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:180:50: Unsupported: 'with' in coverage select expression - 180 | bins bin_and_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:180:37: Unsupported: '&&' in coverage select expression - 180 | bins bin_and_with = binsof(a) && binsof(a) with (a); - | ^~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:183:7: Unsupported: explicit coverage cross bins - 183 | ignore_bins ib_cross = binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins + 176 | ignore_bins ib_cross = binsof(a); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:184:7: Unsupported: explicit coverage cross bins - 184 | illegal_bins lib_cross = binsof(a); +%Warning-COVERIGN: t/t_covergroup_unsup.v:177:7: Unsupported: explicit coverage cross bins + 177 | illegal_bins lib_cross = binsof(a); | ^~~~~~~~~~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:98:18: Ignoring unsupported coverage cross option: 'comment' 98 | cross a, b { option.comment = "cross"; option.weight = 12; option.per_instance = 1; } @@ -145,40 +124,25 @@ 103 | bins one = crossfunc(); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:168:7: Unsupported: explicit coverage cross bins - 168 | bins bin_na = ! binsof(a); + 168 | bins bin_e = with (a); | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:170:7: Unsupported: explicit coverage cross bins - 170 | bins bin_d = binsof(a) intersect { b }; +%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:171:7: Unsupported: explicit coverage cross bins - 171 | bins bin_nd = ! binsof(a) intersect { b }; + 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); | ^~~~ %Warning-COVERIGN: t/t_covergroup_unsup.v:173:7: Unsupported: explicit coverage cross bins - 173 | bins bin_e = with (a); + 173 | bins bin_and_with = binsof(a) && 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_not_e = ! with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:176:7: Unsupported: explicit coverage cross bins - 176 | bins bin_and = binsof(a) && binsof(b); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:177:7: Unsupported: explicit coverage cross bins - 177 | bins bin_or = binsof(a) || binsof(b); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:178:7: Unsupported: explicit coverage cross bins - 178 | bins bin_with = binsof(a) with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:179:7: Unsupported: explicit coverage cross bins - 179 | bins bin_or_with = binsof(a) || binsof(a) with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:180:7: Unsupported: explicit coverage cross bins - 180 | bins bin_and_with = binsof(a) && binsof(a) with (a); - | ^~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:181:34: Unsupported: 'binsof' in coverage select expression - 181 | bins bin_multiple_fields = binsof(p.inner_packet.field); - | ^~~~~~ -%Warning-COVERIGN: t/t_covergroup_unsup.v:181:7: Unsupported: explicit coverage cross bins - 181 | bins bin_multiple_fields = binsof(p.inner_packet.field); + 174 | bins bin_multiple_fields = binsof(a) && binsof(p.inner_packet.field); | ^~~~ %Warning-WIDTHTRUNC: t/t_covergroup_unsup.v:76:5: Logical operator COVERPOINT 'a' expects 1 bit on the iff condition, but iff condition's VARREF 'b' generates 32 bits. : ... note: In instance 't' diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index ea7bb32f5..013bce73a 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -954,6 +954,23 @@ module Vt_debug_emitv_t; bins y1 = {'sh1}; }; cx: cross cp_x, cp_y iff ((cg_sig[0] == cg_sig2[0])); + cx_select: cross cp_x, cp_y { + bins plain = binsof(cp_x); + bins named = binsof(cp_x.x0); + bins filtered = binsof(cp_x) intersect {'sh0, ['sh1:'sh2]}; + bins lower = binsof(cp_x) intersect {[$:'sh0]}; + bins upper = binsof(cp_x) intersect {['sh1:$]}; + bins negated = !binsof(cp_x.x0); + bins complemented = !binsof(cp_x) intersect {'sh0}; + bins either = (binsof(cp_x.x0) || binsof( + cp_y.y0)); + bins both = (binsof(cp_x.x1) && binsof( + cp_y.y1)) iff ( + cg_sig[1]); + bins grouped = ((binsof(cp_x.x0) || binsof( + cp_y.y0)) + && !binsof(cp_x.x1)); + } endfunction int signed __Vint; struct { diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index caeac8094..0fe96dbbf 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -434,6 +434,18 @@ module t (/*AUTOARG*/ bins y1 = {1}; } cx: cross cp_x, cp_y iff (cg_sig[0] == cg_sig2[0]); + cx_select: cross cp_x, cp_y{ + bins plain = binsof (cp_x); + bins named = binsof (cp_x.x0); + bins filtered = binsof (cp_x) intersect {0, [1 : 2]}; + bins lower = binsof (cp_x) intersect {[$ : 0]}; + bins upper = binsof (cp_x) intersect {[1 : $]}; + bins negated = !binsof (cp_x.x0); + bins complemented = !binsof (cp_x) intersect {0}; + bins either = binsof (cp_x.x0) || binsof (cp_y.y0); + bins both = binsof (cp_x.x1) && binsof (cp_y.y1) iff (cg_sig[1]); + bins grouped = (binsof (cp_x.x0) || binsof (cp_y.y0)) && !binsof (cp_x.x1); + } endgroup cg_basic cg_basic_inst = new;