diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h index 519fd214c..6d8b28f9d 100644 --- a/include/verilated_cov_model.h +++ b/include/verilated_cov_model.h @@ -41,9 +41,13 @@ enum class VlCovBinKind : uint8_t { //============================================================================= // VlCoverpointIf -/// Read-side view of a coverpoint. The writer queries bins by index; the -/// implementor computes names/kinds on demand. Bounded bin count, so random -/// access by index is the primary usage. +/// Read-side view of a coverpoint -- a named, index-addressable set of bins +/// with a coverage fraction. A cross is also a coverpoint from this view: its +/// auto cross bins (one per element of the Cartesian product of the feeding +/// coverpoints' Normal bins) are all Normal, and their names are built on +/// demand by concatenating the feeding coverpoints' bin names. The writer +/// queries bins by index; the implementor computes names/kinds on demand. +/// Bounded bin count, so random access by index is the primary usage. class VlCoverpointIf VL_NOT_FINAL { public: @@ -51,11 +55,11 @@ public: virtual ~VlCoverpointIf() = default; // METHODS - // All bins, across every set; index range [0, binCount()) - virtual int binCount() const = 0; - // Bin name in declaration order (e.g. "myBin" or "b[3]") - virtual std::string binName(int i) const = 0; - virtual VlCovBinKind binKind(int i) const = 0; + // All bins, across every set; index range [0, binCount()). + virtual uint32_t binCount() const = 0; + // Bin name in declaration order (e.g. "myBin" or "b[3]"); for a cross, + // the concatenated cross bin name (e.g. "b1_x_b2_x_b3") + virtual std::string binName(uint32_t i) const = 0; // Bins covered / effective total (Normal set only) for the coverage calc virtual void coverageParts(double& covered, double& total) const = 0; }; diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp index 9bf0fc53f..f7cb3b6ae 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -23,34 +23,57 @@ #include "verilated_covergroup.h" +#include "verilated.h" + +// This file is compiled whenever covergroups are used, with or without +// "verilator --coverage" (see V3Global::verilatedCppFiles). Bin counts are +// members of the covergroup objects themselves, so sampling, bin naming, and +// coverage queries such as get_inst_coverage() all work with no coverage +// database present. VL_COVER_INSERT does not copy a count; it hands the +// database the address of a counter to read at write time. Only that +// publication step needs verilated_cov.cpp, which is compiled solely under +// --coverage, so only the registerBins() bodies are gated on VM_COVERAGE. #if VM_COVERAGE #include "verilated_cov.h" #endif -void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) { +void VlCoverpoint::init(const char* hier, uint32_t atLeast, uint32_t nBins) { m_hier = hier; m_atLeast = atLeast; m_total = nBins; m_counts.assign(nBins, 0); + m_crossIdx.assign(nBins, -1); + m_crossToBin.clear(); } -void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, - const char* file, int line, int col) { +void VlCoverpoint::addNamer(VlCovBinKind set, uint32_t count, VlCovBinNaming naming, + const char* name, const char* file, int line, int col) { m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col); + if (set == VlCovBinKind::KIND_NORMAL) { + // Assign each Normal bin a cross index, and record the inverse map. + for (uint32_t b = m_nextBase; b < m_nextBase + count; ++b) { + m_crossIdx[b] = static_cast(m_crossToBin.size()); + m_crossToBin.push_back(b); + } + m_normal += count; + } m_nextBase += count; - if (set == VlCovBinKind::KIND_NORMAL) m_normal += count; } -const VlCovNamer& VlCoverpoint::namerFor(int i) const { - // Namers are appended in ascending, contiguous index order covering [0, m_total), - // and i is always a valid bin index, so the matching namer always exists. +std::string VlCoverpoint::normalBinName(uint32_t crossIdx) const { + // Build the bin name based on the bin index + return binName(m_crossToBin[crossIdx]); +} + +const VlCovNamer& VlCoverpoint::namerFor(uint32_t i) const { + // Namers are appended in ascending order covering [0, m_total), for (const VlCovNamer& nm : m_namers) { if (i < nm.base() + nm.count()) return nm; } - VL_UNREACHABLE; + VL_UNREACHABLE; // LCOV_EXCL_LINE } -std::string VlCoverpoint::binName(int i) const { +std::string VlCoverpoint::binName(uint32_t i) const { const VlCovNamer& nm = namerFor(i); std::string name = nm.name(); if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']'; @@ -59,7 +82,7 @@ std::string VlCoverpoint::binName(int i) const { #if VM_COVERAGE void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) { - for (int i = 0; i < binCount(); ++i) { + for (uint32_t i = 0; i < binCount(); ++i) { const VlCovNamer& nm = namerFor(i); const VlCovBinKind kind = binKind(i); const std::string binp = binName(i); @@ -81,3 +104,89 @@ void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* pa } } #endif // VM_COVERAGE + +//============================================================================= +// VlCoverCross + +void VlCoverCross::init(const char* hier, uint32_t dims, VlCoverpoint* const* cps, + const char* file, int line, int col) { + m_hier = hier; + m_file = file; + m_line = line; + m_col = col; + m_dims = dims; + m_cps.assign(cps, cps + dims); + m_cpBinCounts.resize(dims); + // Accumulate in 64 bits so the overflow check itself cannot overflow. + uint64_t product = 1; + for (uint32_t d = 0; d < dims; ++d) { + m_cpBinCounts[d] = cps[d]->normalBinCount(); + product *= m_cpBinCounts[d]; + 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); + // 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); +} + +void VlCoverCross::iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx) { + const uint32_t hits = cps[dim]->hitCount(); + const uint32_t* const list = cps[dim]->hitList(); + const bool last = (dim == m_dims - 1); + const uint32_t stride = m_stride[dim]; + for (uint32_t hit = 0; hit < hits; ++hit) { + const uint32_t idx = baseIdx + list[hit] * stride; + if (last) { + incrementTuple(idx); + } else { + iterateProduct(cps, dim + 1, idx); + } + } +} + +void VlCoverCross::sample(VlCoverpoint* const* cps) { + // Fast path: if any dimension had no Normal-bin hit, the cross cannot hit. + for (uint32_t d = 0; d < m_dims; ++d) { + if (cps[d]->hitCount() == 0) return; + } + iterateProduct(cps, 0, 0); +} + +std::string VlCoverCross::binName(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]; + if (d > 0) name += "_x_"; + name += m_cps[d]->normalBinName(crossIdx); + } + return name; +} + +#if VM_COVERAGE +void VlCoverCross::registerBins(VerilatedCovContext* covcontextp, const char* page) { + // Register every auto cross bin (zero-count bins included), so the report + // shows the full Cartesian product of cross bins. Names are built on the fly. + const std::string lineStr = std::to_string(m_line); + const std::string colStr = std::to_string(m_col); + for (uint32_t flat = 0; flat < binCount(); ++flat) { + const std::string bin = binName(flat); // "b1_x_b2_x_..." + // 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]; + if (d > 0) crossBins += ","; + crossBins += m_cps[d]->normalBinName(crossIdx); + } + const std::string full = m_hier + "." + bin; + VL_COVER_INSERT(covcontextp, full.c_str(), &m_flatCounts[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()); + } +} +#endif // VM_COVERAGE diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index 1ffc98ff7..9c1825daf 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -22,6 +22,9 @@ /// it in the constructor (init + add*Namer), increments bins from sample(), /// and registers via registerBins(). /// +/// Collection and coverage queries are always available; only registerBins(), +/// which publishes bin counters to the coverage database, requires VM_COVERAGE. +/// //============================================================================= #ifndef VERILATOR_VERILATED_COVERGROUP_H_ @@ -49,8 +52,8 @@ enum class VlCovBinNaming : uint8_t { class VlCovNamer final { // MEMBERS VlCovBinKind m_set; // which set the bins belong to - int m_count; // bins this namer covers (1 for Single) - int m_base; // first bin index (declaration order), assigned on append + uint32_t m_count; // bins this namer covers (1 for Single) + uint32_t m_base; // first bin index (declaration order), assigned on append VlCovBinNaming m_naming; // how bin names are built const char* m_name; // bin name (Single) or array base name (Array) const char* m_file; // declaration file @@ -59,8 +62,8 @@ class VlCovNamer final { public: // CONSTRUCTORS - VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name, - const char* file, int line, int col) + VlCovNamer(VlCovBinKind set, uint32_t count, uint32_t base, VlCovBinNaming naming, + const char* name, const char* file, int line, int col) : m_set{set} , m_count{count} , m_base{base} @@ -72,8 +75,8 @@ public: // METHODS VlCovBinKind set() const { return m_set; } - int count() const { return m_count; } - int base() const { return m_base; } + uint32_t count() const { return m_count; } + uint32_t base() const { return m_base; } VlCovBinNaming naming() const { return m_naming; } const char* name() const { return m_name; } const char* file() const { return m_file; } @@ -87,19 +90,30 @@ public: /// bin's set/name come from the owning namer. coverage() is computed on demand /// by scanning bin counts, keeping the sample() hot path a plain counter bump. -class VlCoverpoint final : public VlCoverpointIf { - // MEMBERS +// Base coverpoint runtime (read side + collection logic, no hit-list storage). +// VlCoverpointT adds the inline hit-list array and the incrementBin write +// path; the cross holds VlCoverpoint* and reads via hitCount()/hitList(). +class VlCoverpoint VL_NOT_FINAL : public VlCoverpointIf { +protected: + // MEMBERS (protected so VlCoverpointT::incrementBin can update them) std::string m_hier; // "covergroup.coverpoint" uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide) - int m_total = 0; // bins across all sets - int m_normal = 0; // Normal bins (coverage denominator) - int m_nextBase = 0; // running append cursor + uint32_t m_total = 0; // bins across all sets + uint32_t m_normal = 0; // Normal bins (coverage denominator) + uint32_t m_nextBase = 0; // running append cursor std::vector m_counts; // [m_total], one per bin std::vector m_namers; // appended in declaration order + // [m_total] full bin idx -> cross idx (Normal-only), -1 otherwise. The only + // signed index here: -1 marks a non-Normal bin, which incrementBin filters on. + std::vector m_crossIdx; + // [m_normal] inverse of m_crossIdx: cross idx -> full bin idx, appended in cross-index order + std::vector m_crossToBin; + uint32_t m_hitCount = 0; // entries valid in the hit list this sample +private: // PRIVATE METHODS - const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer - void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const VlCovNamer& namerFor(uint32_t i) const; // obtain the bin-specific name producer + void addNamer(VlCovBinKind set, uint32_t count, VlCovBinNaming naming, const char* name, const char* file, int line, int col); public: @@ -108,31 +122,44 @@ public: // METHODS // ---- configuration (from generated constructor) ---- - void init(const char* hier, uint32_t atLeast, int nBins); + void init(const char* hier, uint32_t atLeast, uint32_t nBins); void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) { addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col); } - void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line, - int col) { + void addArrayNamer(VlCovBinKind set, uint32_t count, const char* name, const char* file, + int line, int col) { addNamer(set, count, VlCovBinNaming::Array, name, file, line, col); } void registerBins(VerilatedCovContext* covcontextp, const char* page); // ---- hot path (from generated sample()) ---- - void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only - void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only + // Clear the hit list at the start of each sample() for a cross-fed coverpoint. + void clearHitList() { m_hitCount = 0; } + // Ignore/Illegal/Default: count only; never propagates to cross coverage. + void recordHit(uint32_t i) { ++m_counts[i]; } + // incrementBin (Normal bin: count + hit-list append) lives in VlCoverpointT, + // where MaxHits is the gen-time max per-sample bin overlap. + + // ---- cross support (read by VlCoverCross) ---- + uint32_t hitCount() const { return m_hitCount; } + virtual const uint32_t* hitList() const = 0; // provided by VlCoverpointT + uint32_t normalBinCount() const { return m_normal; } // cross dimension size (Normal bins) + std::string normalBinName(uint32_t crossIdx) const; // name of the crossIdx-th Normal bin // ---- VlCoverpointIf ---- - int binCount() const override { return m_total; } - std::string binName(int i) const override; - VlCovBinKind binKind(int i) const override { return namerFor(i).set(); } + uint32_t binCount() const override { return m_total; } + std::string binName(uint32_t i) const override; + // Deliberately not on VlCoverpointIf: only registerBins() needs it, via the + // concrete coverpoint. A cross has all-Normal bins and exposes no kind, so the + // interface omits it; add it back only if a writer needs it polymorphically. + VlCovBinKind binKind(uint32_t i) const { return namerFor(i).set(); } void coverageParts(double& covered, double& total) const override { // Count Normal bins that reached option.at_least on demand, so the hot // path (incrementBin) stays a plain counter bump. - int numCovered = 0; + uint32_t numCovered = 0; for (const VlCovNamer& nm : m_namers) { if (nm.set() != VlCovBinKind::KIND_NORMAL) continue; - for (int i = nm.base(); i < nm.base() + nm.count(); ++i) { + for (uint32_t i = nm.base(); i < nm.base() + nm.count(); ++i) { if (m_counts[i] >= m_atLeast) ++numCovered; } } @@ -141,4 +168,90 @@ public: } }; +//============================================================================= +// VlCoverpointT +/// Concrete coverpoint with an inline hit-list array sized to MaxHits -- the +/// gen-time maximum number of Normal bins one sample value can match (1 for the +/// common non-overlapping case). The bound is a compile-time constant, so for +/// MaxHits == 1 incrementBin collapses to a single store. Generated code holds +/// the coverpoint as VlCoverpointT and calls incrementBin via the concrete +/// type; the cross reads it polymorphically through VlCoverpoint*. + +template +class VlCoverpointT final : public VlCoverpoint { + // MEMBERS + uint32_t m_hits[MaxHits]; // cross indices of Normal bins hit this sample + +public: + // CONSTRUCTORS + VlCoverpointT() = default; + + // METHODS + // Normal bin: bump count and append the bin's cross index to the hit list. + // m_hitCount can never exceed MaxHits (the gen-time overlap bound), so no hit + // is ever dropped; the bound check is a compile-time-folded safety net. + void incrementBin(uint32_t i) { + ++m_counts[i]; + // m_crossIdx is signed only to carry the -1 "not a Normal bin" marker; + // the >= 0 test below is what makes every stored hit index unsigned-safe. + const int cx = m_crossIdx[i]; + if (cx >= 0 && m_hitCount < MaxHits) m_hits[m_hitCount++] = static_cast(cx); + } + const uint32_t* hitList() const override { return m_hits; } +}; + +//============================================================================= +// VlCoverCross +/// Per-instance auto 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 from the coverpoints, so no per-bin name is stored. + +class VlCoverCross final : public VlCoverpointIf { + // MEMBERS + std::string m_hier; // "covergroup.cross" + const char* m_file = nullptr; // Cross declaration file (registration metadata) + int m_line = 0; // Cross declaration line + int m_col = 0; // Cross declaration column + uint32_t m_dims = 0; // Number of feeding coverpoints + // 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. + 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 (the only name source) + + // PRIVATE METHODS + void iterateProduct(VlCoverpoint* const* cps, uint32_t dim, uint32_t baseIdx); + void incrementTuple(uint32_t idx) { + if (m_flatCounts[idx]++ == 0) ++m_numCovered; + } + +public: + // CONSTRUCTORS + VlCoverCross() = default; + + // 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); + void registerBins(VerilatedCovContext* covcontextp, const char* page); + + // ---- hot path (from generated sample(), after all coverpoints sampled) ---- + void sample(VlCoverpoint* const* cps); + + // ---- VlCoverpointIf ---- + // A cross is a coverpoint whose bins are the auto cross bins (all Normal). + uint32_t binCount() const override { return m_numAutoBins; } + std::string binName(uint32_t flat) const override; + void coverageParts(double& covered, double& total) const override { + covered = m_numCovered; + total = m_numAutoBins; + } +}; + #endif // Guard diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index c49e3294e..bf924a96b 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -27,6 +27,7 @@ #include "V3Covergroup.h" #include "V3Const.h" +#include "V3File.h" #include "V3MemberMap.h" #include @@ -53,25 +54,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { std::map m_coverpointMap; // Name -> coverpoint for fast lookup std::vector m_coverCrosses; // Cross coverage items in current covergroup - // Structure to track bins with their variables and options - struct BinInfo final { - AstCoverBin* binp; - AstVar* varp; - int atLeast; // Minimum hits required for coverage (from option.at_least) - AstCoverpoint* coverpointp; // Associated coverpoint (or nullptr for cross bins) - AstCoverCross* crossp; // Associated cross (or nullptr for coverpoint bins) - string crossBins; // For cross bins: comma-separated individual bin names, in order - BinInfo(AstCoverBin* b, AstVar* v, int al = 1, AstCoverpoint* cp = nullptr, - AstCoverCross* cr = nullptr, const string& cb = "") - : binp{b} - , varp{v} - , atLeast{al} - , coverpointp{cp} - , crossp{cr} - , crossBins{cb} {} - }; - std::vector m_binInfos; // All bins in current covergroup - struct EmbeddedEventTrigger final { FileLine* eventFl; // Clocking-event source location AstVar* baseVarp; // Base enclosing-class member in the event expression @@ -87,37 +69,42 @@ class FunctionalCoverageVisitor final : public VNVisitor { , prevVarp{nullptr} {} }; - std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) - std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints - AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type + std::set m_crossedCpNames; // Coverpoints referenced by a cross + 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 + std::set + m_droppedCrosses; // Crosses with a bare-variable item: drop (COVERIGN) + std::map m_vlCoverpointTypes; // hit-list bound K -> "VlCoverpointT" type + AstCDType* m_vlCoverCrossDTypep = nullptr; // Shared "VlCoverCross" C++ member type VMemberMap m_memberMap; // Member names cached for fast lookup // METHODS - void clearBinInfos() { - // Delete pseudo-bins created for cross coverage (they're never inserted into the AST) - for (const BinInfo& bi : m_binInfos) { - if (!bi.coverpointp) pushDeletep(bi.binp); - } - m_binInfos.clear(); - } - void processCovergroup() { UINFO(4, "Processing covergroup: " << m_covergroupp->name() << " with " << m_coverpoints.size() << " coverpoints and " << m_coverCrosses.size() << " crosses"); - // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) - clearBinInfos(); - - // Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross - // reads those members); collect their names before they are consumed by the cross. m_crossedCpNames.clear(); - m_convCpVars.clear(); + m_cpVars.clear(); + m_crossVars.clear(); + m_cpVarMap.clear(); + m_droppedCrosses.clear(); + + // Scan every cross item to record the coverpoints it references (the cross dimensions) + // and to flag any cross naming a bare variable -- a would-be implicit coverpoint, which + // Verilator does not synthesize. An unresolvable item drops only that one cross (with a + // COVERIGN in generateCrossCode), leaving the rest of the covergroup intact. for (AstCoverCross* crossp : m_coverCrosses) { for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { - if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef)) - if (!refp->exprp()) m_crossedCpNames.insert(refp->name()); + const AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + if (refp->exprp()) continue; // hierarchical ref: dropped in generateCrossCode + if (m_coverpointMap.find(refp->name()) == m_coverpointMap.end()) { + m_droppedCrosses.insert(crossp); // bare variable: drop this cross only + } else { + m_crossedCpNames.insert(refp->name()); + } } } @@ -127,8 +114,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { // For each cross, generate sampling code for (AstCoverCross* crossp : m_coverCrosses) generateCrossCode(crossp); - // Generate coverage computation code (even for empty covergroups) - generateCoverageComputationCode(); + // Generate coverage computation code (even for empty covergroups). Bin registration + // with the coverage database is handled per coverpoint/cross by their runtime + // registerBins() calls (emitted in generateCoverpoint/generateCross). // TODO: Generate instance registry infrastructure for static get_coverage() // This requires: @@ -136,12 +124,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // - registerInstance() / unregisterInstance() methods // - Proper C++ emission in EmitC backend // For now, get_coverage() returns 0.0 (placeholder) - - // Generate coverage database registration if coverage is enabled - if (v3Global.opt.coverage()) generateCoverageRegistration(); - - // Clean up orphaned cross pseudo-bins now that we're done with them - clearBinInfos(); + generateCoverageComputationCode(); } static constexpr int COVER_BINS_LIMIT @@ -427,26 +410,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { return name; } - AstVar* createCoverageCounterVar(FileLine* fl, const string& varName, AstNodeDType* dtypep) { - AstVar* const varp = new AstVar{fl, VVarType::MEMBER, varName, dtypep}; - varp->isStatic(false); - varp->valuep(new AstConst{fl, AstConst::WidthedValue{}, 32, 0}); - m_covergroupp->addMembersp(varp); - return varp; - } - - AstVar* createTrackedCoverpointBinCounter(AstCoverpoint* coverpointp, AstCoverBin* binp, - const string& generatedBinName, int atLeastValue, - const string& logPrefix, - const string& logSuffix = "") { - const string varName = "__Vcov_" + coverpointp->name() + "_" + generatedBinName; - AstVar* const varp - = createCoverageCounterVar(binp->fileline(), varName, binp->findUInt32DType()); - UINFO(4, " " << logPrefix << ": " << varName << logSuffix); - m_binInfos.push_back(BinInfo(binp, varp, atLeastValue, coverpointp)); - return varp; - } - AstNodeExpr* applyCoverpointIffCondition(AstCoverpoint* coverpointp, FileLine* fl, AstNodeExpr* condp) { if (AstNodeExpr* const iffp = coverpointp->iffp()) { @@ -456,21 +419,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { return condp; } - void addCoverpointBinHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* hitVarp, - AstNodeExpr* condp, const string& illegalErrMsg, - const char* assertMsg) { - AstNode* stmtp = makeBinHitIncrement(binp->fileline(), hitVarp); - if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - stmtp = stmtp->addNext(makeIllegalBinAction(binp->fileline(), illegalErrMsg)); - } - - AstIf* const ifp = new AstIf{ - binp->fileline(), applyCoverpointIffCondition(coverpointp, binp->fileline(), condp), - stmtp, nullptr}; - UASSERT_OBJ(m_sampleFuncp, binp, assertMsg); - m_sampleFuncp->addStmtsp(ifp); - } - // Create previous value variable for transition tracking AstVar* createPrevValueVar(AstCoverpoint* coverpointp, AstNodeExpr* exprp) { // Check if already created @@ -537,102 +485,11 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create implicit automatic bins if no regular bins exist createImplicitAutoBins(coverpointp, exprp, autoBinMax); - // Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or - // transition-bearing) keep the legacy per-bin-member path below. - if (coverpointConvertible(coverpointp)) { - generateConvertedCoverpoint(coverpointp, exprp, atLeastValue); - return; - } - - // Generate member variables and matching code for each bin - // Process in two passes: first non-default bins, then default bins - std::vector defaultBins; - bool hasTransition = false; - for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - - // Defer default bins to second pass - if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) { - defaultBins.push_back(cbinp); - continue; - } - - // Handle array bins: create separate bin for each value/transition - if (cbinp->isArray()) { - if (cbinp->transp()) { // transition bin (includes illegal_bins with transitions) - hasTransition = true; - generateTransitionArrayBins(coverpointp, cbinp, exprp, atLeastValue); - } else { - generateArrayBins(coverpointp, cbinp, exprp, atLeastValue); - } - continue; - } - - // Create a member variable to track hits for this bin - // Sanitize bin name to make it a valid C++ identifier - const string binName = sanitizeGeneratedName(cbinp->name()); - AstVar* const varp = createTrackedCoverpointBinCounter( - coverpointp, cbinp, binName, atLeastValue, "Created member variable", - " type=" + string{cbinp->binsType().ascii()}); - - // Note: Coverage database registration happens later via VL_COVER_INSERT - // (see generateCoverageDeclarations() method around line 1164) - // Classes use "v_covergroup/" hier prefix vs modules - - // Generate bin matching code in sample() - // Handle transition bins specially (includes illegal_bins with transition syntax) - if (cbinp->transp()) { - hasTransition = true; - generateTransitionBinMatchCode(coverpointp, cbinp, exprp, varp); - } else { - generateBinMatchCode(coverpointp, cbinp, exprp, varp); - } - } - - // Second pass: Handle default bins - // Default bin matches when value doesn't match any other explicit bin - for (AstCoverBin* defBinp : defaultBins) { - // Create member variable for default bin - const string binName = sanitizeGeneratedName(defBinp->name()); - AstVar* const varp = createTrackedCoverpointBinCounter( - coverpointp, defBinp, binName, atLeastValue, "Created default bin variable"); - - // Generate matching code: if (NOT (bin1 OR bin2 OR ... OR binN)) - generateDefaultBinMatchCode(coverpointp, defBinp, exprp, varp); - } - - // After all bins processed, if coverpoint has transition bins, update previous value - if (hasTransition) { - AstVar* const prevVarp = VN_AS(coverpointp->user1p(), Var); - // Generate: __Vprev_cpname = current_value; - AstNodeStmt* updateStmtp - = new AstAssign{coverpointp->fileline(), - new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::WRITE}, - exprp->cloneTree(false)}; - m_sampleFuncp->addStmtsp(updateStmtp); - UINFO(4, " Added previous value update at end of sample()"); - } - } - - void generateBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, AstNodeExpr* exprp, - AstVar* hitVarp) { - UINFO(4, " Generating bin match for: " << binp->name()); - - // Build the bin matching condition using the shared function - AstNodeExpr* fullCondp = buildBinCondition(binp, exprp); - - if (!fullCondp) { - // Reachable: e.g. 'ignore_bins ib = default' creates a BINS_IGNORE bin - // with null rangesp. Skipping match code generation is correct in that case. - return; - } - - UINFO(4, " Adding bin match if statement to sample function"); - addCoverpointBinHitIf(coverpointp, binp, hitVarp, fullCondp, - "Illegal bin " + binp->prettyNameQ() + " hit in coverpoint " - + coverpointp->prettyNameQ(), - "sample() CFunc not set when generating bin match code"); - UINFO(4, " Successfully added if statement for bin: " << binp->name()); + // Every coverpoint routes through the VlCoverpoint runtime. Transition coverpoints are + // included: their per-value matching is still generated as a state machine in sample() + // (see generateCoverpoint), but the bin hit is recorded in the runtime bin + // rather than a bare counter. + generateCoverpoint(coverpointp, exprp, atLeastValue); } // Build the condition under which a default bin matches: NOT(OR of all normal bins). @@ -655,18 +512,162 @@ class FunctionalCoverageVisitor final : public VNVisitor { } //==================================================================== - // VlCoverpoint conversion (eligible coverpoints) + // VlCoverpoint conversion - // True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints - // (the cross reads their per-bin members) and transition-bearing ones stay legacy. - bool coverpointConvertible(AstCoverpoint* coverpointp) { - if (m_crossedCpNames.count(coverpointp->name())) return false; + // True if a coverpoint has any transition bin. Used to decide whether sample() emits the + // end-of-sample previous-value update that transition matching needs. + static bool coverpointHasTransition(AstCoverpoint* coverpointp) { for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - if (VN_AS(binp, CoverBin)->transp()) return false; + if (VN_AS(binp, CoverBin)->transp()) return true; + } + return false; + } + + // Get (or create) the "VlCoverpointT" member type for hit-list bound K. + AstCDType* vlCoverpointType(FileLine* fl, int hitBound) { + const auto it = m_vlCoverpointTypes.find(hitBound); + if (it != m_vlCoverpointTypes.end()) return it->second; + AstCDType* const typep + = new AstCDType{fl, "VlCoverpointT<" + std::to_string(hitBound) + ">"}; + v3Global.rootp()->typeTablep()->addTypesp(typep); + m_vlCoverpointTypes.emplace(hitBound, typep); + return typep; + } + + // Constant bounds of one rangesp() element (an InsideRange or a single Const). Each bound is + // the raw AST node -- an AstConst or an AstUnbounded ('$') -- with the const/unbounded view + // derived on demand, so there is one source of truth per bound. After a successful + // constRangeBounds() neither node is null; a single Const has both bounds aliasing one node. + struct RangeBounds final { + AstNode* loNodep = nullptr; // low bound: AstConst or AstUnbounded + AstNode* hiNodep = nullptr; // high bound: AstConst or AstUnbounded + bool loUnbounded() const { return VN_IS(loNodep, Unbounded); } + bool hiUnbounded() const { return VN_IS(hiNodep, Unbounded); } + AstConst* loConstp() const { return VN_CAST(loNodep, Const); } + AstConst* hiConstp() const { return VN_CAST(hiNodep, Const); } + }; + + // Decode one rangesp() element into its constant bounds. Returns false if rp is neither an + // InsideRange nor a single Const, or if a present bound is non-constant or 4-state. '$' + // bounds are left as AstUnbounded (not resolved) -- the caller applies its own policy. + // Centralizes the InsideRange/Const/Unbounded decode shared by the hit-list-bound paths. + static bool constRangeBounds(AstNode* rp, RangeBounds& rb) { + if (AstInsideRange* const irp = VN_CAST(rp, InsideRange)) { + rb.loNodep = irp->lhsp(); + rb.hiNodep = irp->rhsp(); + } else if (AstConst* const cp = VN_CAST(rp, Const)) { + rb.loNodep = rb.hiNodep = cp; + } else { + return false; + } + // Each bound must be a constant unless it is '$'; reject non-const and 4-state. + AstConst* const lc = rb.loConstp(); + AstConst* const hc = rb.hiConstp(); + if ((!lc && !rb.loUnbounded()) || (!hc && !rb.hiUnbounded())) return false; + if ((lc && lc->num().isFourState()) || (hc && hc->num().isFourState())) return false; + return true; + } + + // Collect the covered value intervals of a single (non-array) Normal bin. Returns false + // if any range isn't a constant/open InsideRange or single Const (e.g. wildcard, non-const). + static bool extractRangeIntervals(AstCoverBin* cbinp, uint64_t maxVal, + std::vector>& out) { + if (!cbinp->rangesp()) return false; + for (AstNode* rp = cbinp->rangesp(); rp; rp = rp->nextp()) { + RangeBounds rb; + if (!constRangeBounds(rp, rb)) return false; + 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; + out.emplace_back(lo, hi); } return true; } + // Append one Normal bin's cross-slot interval-sets to `bins` and bump `slotCount` by the + // number of cross slots the bin contributes. Returns false if any part isn't statically + // enumerable (the caller then falls back to the always-safe Normal-slot count). A non-array + // bin is one slot covering the union of its intervals; an array bin contributes one + // single-value slot per element value (mirroring how it lowers to b[0]..b[N-1]). + bool appendBinCrossSlots(AstCoverBin* cbinp, uint64_t maxVal, + std::vector>>& bins, + int& slotCount) { + if (cbinp->isArray()) return appendArrayBinCrossSlots(cbinp, bins, slotCount); + // Non-array bin: one slot covering the union of its intervals. + ++slotCount; + std::vector> ivs; + if (cbinp->isWildcard() || !extractRangeIntervals(cbinp, maxVal, ivs)) return false; + bins.push_back(std::move(ivs)); + return true; + } + + // Append the cross slots of an array Normal bin: each element value is its own single-value + // Normal bin. '$'-bounded or non-constant elements can't be enumerated, so they count one + // slot but lose exactness. Returns false if any element wasn't enumerable to exact values. + bool appendArrayBinCrossSlots(AstCoverBin* cbinp, + std::vector>>& bins, + int& slotCount) { + bool exact = true; + for (AstNode* rp = cbinp->rangesp(); rp; rp = rp->nextp()) { + RangeBounds rb; + if (!constRangeBounds(rp, rb) || rb.loUnbounded() || rb.hiUnbounded()) { + ++slotCount; + exact = false; + } else if (rb.loNodep == rb.hiNodep) { // single Const element (both alias one node) + ++slotCount; + bins.push_back({{rb.loConstp()->toUQuad(), rb.loConstp()->toUQuad()}}); + } else { // [lo:hi] range: one single-value slot per enumerated value + for (int64_t v = rb.loConstp()->toSInt(); v <= rb.hiConstp()->toSInt(); ++v) { + ++slotCount; + bins.push_back({{static_cast(v), static_cast(v)}}); + } + } + } + return exact; + } + + // Compute the hit-list bound for a coverpoint: the maximum number of Normal + // bins one sample value can match. Non-cross-fed coverpoints don't feed a cross, so + // their hit list is unused -> 1. Otherwise compute the exact max bin overlap; fall back + // to the (always-safe) Normal-slot count when any bin isn't statically analyzable. + int computeHitListBound(AstCoverpoint* coverpointp, AstNodeExpr* exprp, bool crossFed) { + if (!crossFed) return 1; + const int width = exprp->width(); + const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); + // 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; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (!cbinp->binsType().binIsNormal()) + continue; // ignore/illegal/default: not hit-listed + if (!appendBinCrossSlots(cbinp, maxVal, bins, slotCount)) exact = false; + } + if (!exact) return std::max(1, slotCount); + if (bins.empty()) return 1; + // Max overlap occurs at some interval start; count covering bins at each lo. + std::vector pts; + for (const auto& b : bins) + for (const auto& iv : b) pts.push_back(iv.first); + std::sort(pts.begin(), pts.end()); + pts.erase(std::unique(pts.begin(), pts.end()), pts.end()); + int maxOverlap = 1; + for (const uint64_t p : pts) { + int cnt = 0; + for (const auto& b : bins) { + for (const auto& iv : b) { + if (iv.first <= p && p <= iv.second) { + ++cnt; + break; // count each bin at most once + } + } + } + if (cnt > maxOverlap) maxOverlap = cnt; + } + return maxOverlap; + } + // A 'this->m_member' reference for embedding in an AstCStmt AstVarRef* memberRef(FileLine* fl, AstVar* varp) { AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ}; @@ -722,8 +723,12 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (uint64_t v = lo; v <= hi; ++v) values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, width, static_cast(v)}); - } else { + } else if (VN_IS(rangep, Const)) { values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); + } else { + arrayBinp->v3error("Non-constant expression in array bins value list; " + "values must be constants"); + return values; } } return values; @@ -734,54 +739,100 @@ class FunctionalCoverageVisitor final : public VNVisitor { FileLine* const fl = binp->fileline(); AstCStmt* const cs = new AstCStmt{fl}; cs->add(memberRef(fl, cpVarp)); - const std::string loc = "\"" + std::string{fl->filename()} + "\", " + // 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. + const bool prot = v3Global.opt.protectIds(); + const std::string loc = "\"" + VIdProtect::protectIf(fl->filename(), prot) + "\", " + std::to_string(fl->lineno()) + ", " + std::to_string(fl->firstColumn()) + ");"; + const std::string binName = VIdProtect::protectWordsIf(binp->name(), prot); if (count < 0) { // single bin cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \"" - + binp->name() + "\", " + loc); + + binName + "\", " + loc); } else { // value array bin cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", " - + std::to_string(count) + ", \"" + binp->name() + "\", " + loc); + + std::to_string(count) + ", \"" + binName + "\", " + loc); } return cs; } // Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample() + // Where a bin's hit is recorded in the runtime VlCoverpoint member. + struct ConvBinTarget final { + AstVar* cpVarp; // the __Vcp_ member + int idx; // bin index within that coverpoint + bool isNormal; // Normal -> incrementBin (count + cross hit list); else recordHit (count) + }; + + // Emit 'this->m_cp.incrementBin(idx);' (Normal) or '.recordHit(idx);' + // (ignore/illegal/default). + AstNodeStmt* makeRuntimeBinHit(FileLine* fl, const ConvBinTarget& tgt) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add(memberRef(fl, tgt.cpVarp)); + cs->add((tgt.isNormal ? ".incrementBin(" : ".recordHit(") + std::to_string(tgt.idx) + + ");"); + return cs; + } + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx, AstNodeExpr* condp) { FileLine* const fl = binp->fileline(); - AstCStmt* const hitp = new AstCStmt{fl}; - hitp->add(memberRef(fl, cpVarp)); - hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(") - + std::to_string(idx) + ");"); - AstNode* actionp = hitp; + AstNode* actionp = makeRuntimeBinHit(fl, {cpVarp, idx, binp->binsType().binIsNormal()}); if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + " hit in coverpoint " + coverpointp->prettyNameQ())); } AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); - UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint"); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for coverpoint"); m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); } - // Route an eligible coverpoint through a VlCoverpoint member: emit the member, its - // sample() increments, the constructor configuration (init + namers), and registration. - void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp, - int atLeastValue) { - FileLine* const fl = coverpointp->fileline(); - UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name()); - - if (!m_vlCoverpointDTypep) { - m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"}; - v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep); + // Emit a transition bin's hit action into sample(): + // if (iff && cond) { m_cp.incrementBin/recordHit(idx); [illegal: $error; $stop] } + // Used by the transition generators so a completed sequence records into the runtime bin. + void addConvTransHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, const ConvBinTarget& tgt, + AstNodeExpr* condp) { + FileLine* const fl = binp->fileline(); + AstNode* actionp = makeRuntimeBinHit(fl, tgt); + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + actionp->addNext(makeIllegalBinAction( + fl, "Illegal transition bin " + binp->prettyNameQ() + " hit in coverpoint " + + coverpointp->prettyNameQ())); } + AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set for transition bin"); + m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); + } + + // Route a coverpoint through a VlCoverpoint member: emit the member, its sample() + // increments, the constructor configuration (init + namers), and registration. + void generateCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp, int atLeastValue) { + FileLine* const fl = coverpointp->fileline(); + UINFO(4, " Generating VlCoverpoint member: " << coverpointp->name()); + + // Size the hit list to the gen-time max bin overlap (1 unless cross-fed with + // overlapping ranges), so no cross hit is ever dropped and storage is minimal. + const bool crossFed = m_crossedCpNames.count(coverpointp->name()) != 0; + const int hitBound = computeHitListBound(coverpointp, exprp, crossFed); + UINFO(6, " Hit-list bound (max bin overlap) = " << hitBound); AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(), - m_vlCoverpointDTypep}; + vlCoverpointType(fl, hitBound)}; cpVarp->isStatic(false); m_covergroupp->addMembersp(cpVarp); - m_convCpVars.push_back(cpVarp); + m_cpVars.push_back(cpVarp); + m_cpVarMap[coverpointp->name()] = cpVarp; + + // A cross reads this coverpoint's hit list, so clear it at the start of the + // coverpoint's sample() contribution (before any incrementBin appends to it). + if (crossFed) { + AstCStmt* const clrp = new AstCStmt{fl}; + clrp->add(memberRef(fl, cpVarp)); + clrp->add(".clearHitList();"); + UASSERT_OBJ(m_sampleFuncp, coverpointp, "sample() CFunc not set for clearHitList"); + m_sampleFuncp->addStmtsp(clrp); + } // Walk bins (non-default, then default), assigning sequential indices that match the // namer append order; emit sample increments and collect namer statements. @@ -794,12 +845,30 @@ class FunctionalCoverageVisitor final : public VNVisitor { defaultBins.push_back(cbinp); continue; } + if (cbinp->transp()) { + // Transition bin (incl. array transition 'bins t[] = (a=>b),(c=>d)' and + // illegal_bins/ignore_bins transitions). All sequences of one transition bin + // share a bin name and merge in the coverage DB to a single point, so model + // them as one runtime bin incremented by any matching sequence. The sequence + // matching is generated as a state machine, with the hit routed to this bin's + // runtime slot. + namerStmts.push_back(makeNamer(cpVarp, cbinp, -1)); + const ConvBinTarget tgt{cpVarp, idx, cbinp->binsType().binIsNormal()}; + for (AstNode* sp = cbinp->transp(); sp; sp = sp->nextp()) + generateSingleTransitionCode(coverpointp, cbinp, exprp, tgt, + VN_AS(sp, CoverTransSet)); + ++idx; + continue; + } if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] 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()))); 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 + // (x/z collapse to 0); switch to AstEqCase when 4-state sim support lands. emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep}); } @@ -818,8 +887,24 @@ class FunctionalCoverageVisitor final : public VNVisitor { buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); } - // Constructor: init (allocates), namers, then registration (under --coverage) - const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); + // Transition coverpoints track the previous sampled value; update it once at the end of + // this coverpoint's sample() contribution (the prev var was created on demand by the + // transition matching above). + if (coverpointHasTransition(coverpointp)) { + AstVar* const prevVarp = VN_AS(coverpointp->user1p(), Var); + m_sampleFuncp->addStmtsp( + new AstAssign{coverpointp->fileline(), + new AstVarRef{prevVarp->fileline(), prevVarp, VAccess::WRITE}, + exprp->cloneTree(false)}); + } + + // Constructor: init (allocates), namers, then registration (under --coverage). + // Under --protect-ids the hierarchy and page string reach the coverage database + // verbatim, so obfuscate them like line/toggle points (per-word hierarchy, whole- + // unit page). No-ops when --protect-ids is off. + const bool prot = v3Global.opt.protectIds(); + const std::string hier + = VIdProtect::protectWordsIf(m_covergroupp->name() + "." + coverpointp->name(), prot); AstCStmt* const initp = new AstCStmt{fl}; initp->add(memberRef(fl, cpVarp)); initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", " @@ -827,56 +912,19 @@ class FunctionalCoverageVisitor final : public VNVisitor { m_constructorp->addStmtsp(initp); for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns); if (v3Global.opt.coverage()) { + const std::string page + = VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot); AstCStmt* const regp = new AstCStmt{fl}; regp->add(memberRef(fl, cpVarp)); - regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" - + m_covergroupp->name() + "\");"); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"" + page + "\");"); m_constructorp->addStmtsp(regp); } } - // Generate matching code for default bins - // Default bins match when value doesn't match any other explicit bin - void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, - AstNodeExpr* exprp, AstVar* hitVarp) { - UINFO(4, " Generating default bin match for: " << defBinp->name()); - - AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline()); - - // Apply iff condition if present - if (AstNodeExpr* iffp = coverpointp->iffp()) { - defaultCondp = new AstAnd{defBinp->fileline(), iffp->cloneTree(false), defaultCondp}; - } - - // Create increment statement - AstNode* const stmtp = makeBinHitIncrement(defBinp->fileline(), hitVarp); - - // Create if statement - AstIf* const ifp = new AstIf{defBinp->fileline(), defaultCondp, stmtp, nullptr}; - - UASSERT_OBJ(m_sampleFuncp, defBinp, - "sample() CFunc not set when generating default bin code"); - m_sampleFuncp->addStmtsp(ifp); - UINFO(4, " Successfully added default bin if statement"); - } - - // Generate matching code for transition bins - // Transition bins match sequences like: (val1 => val2 => val3) - void generateTransitionBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, - AstNodeExpr* exprp, AstVar* hitVarp) { - UINFO(4, " Generating transition bin match for: " << binp->name()); - - // Get the (single) transition set - AstCoverTransSet* const transSetp = binp->transp(); - - // Use the helper function to generate code for this transition - generateSingleTransitionCode(coverpointp, binp, exprp, hitVarp, transSetp); - } - // Generate state machine code for multi-value transition sequences // Handles transitions like (1 => 2 => 3 => 4) void generateMultiValueTransitionCode(AstCoverpoint* coverpointp, AstCoverBin* binp, - AstNodeExpr* exprp, AstVar* hitVarp, + AstNodeExpr* exprp, const ConvBinTarget& tgt, const std::vector& items) { UINFO(4, " Generating multi-value transition state machine for: " << binp->name()); UINFO(4, " Sequence length: " << items.size() << " items"); @@ -894,7 +942,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Generate each case item in the switch statement for (size_t state = 0; state < items.size(); ++state) { - AstCaseItem* caseItemp = generateTransitionStateCase(coverpointp, binp, exprp, hitVarp, + AstCaseItem* caseItemp = generateTransitionStateCase(coverpointp, binp, exprp, tgt, stateVarp, items, state); casep->addItemsp(caseItemp); } @@ -915,7 +963,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Generate code for a single state in the transition state machine // Returns the case item for this state AstCaseItem* generateTransitionStateCase(AstCoverpoint* coverpointp, AstCoverBin* binp, - AstNodeExpr* exprp, AstVar* hitVarp, + AstNodeExpr* exprp, const ConvBinTarget& tgt, AstVar* stateVarp, const std::vector& items, size_t state) { @@ -932,9 +980,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstNodeStmt* matchActionp = nullptr; if (state == items.size() - 1) { - // Last state: sequence complete! - // Increment bin counter - matchActionp = makeBinHitIncrement(fl, hitVarp); + // Last state: sequence complete! Record the hit in the runtime VlCoverpoint. + matchActionp = makeRuntimeBinHit(fl, tgt); // For illegal_bins, add error message if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { @@ -1002,13 +1049,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { return errorp; } - // Create: hitVarp = hitVarp + 1 - AstAssign* makeBinHitIncrement(FileLine* fl, AstVar* hitVarp) { - return new AstAssign{fl, new AstVarRef{fl, hitVarp, VAccess::WRITE}, - new AstAdd{fl, new AstVarRef{fl, hitVarp, VAccess::READ}, - new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}}; - } - // Clone a constant node, widening to targetWidth if needed (zero-extend). // Used to ensure comparisons use matching widths after V3Width has run. static AstConst* widenConst(FileLine* fl, AstConst* constp, int targetWidth) { @@ -1103,77 +1143,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { return condp; } - // Generate multiple bins for array bins - // Array bins create one bin per value in the range list - void generateArrayBins(AstCoverpoint* coverpointp, AstCoverBin* arrayBinp, AstNodeExpr* exprp, - int atLeastValue) { - UINFO(4, " Generating array bins for: " << arrayBinp->name()); - - // Extract all values from the range list (resolves '$', caps/ignores huge ranges) - bool unsupported = false; - std::vector values = extractArrayValues(arrayBinp, exprp, unsupported); - if (unsupported) return; // bin ignored (COVERIGN emitted) - - // Create a separate bin for each value - int index = 0; - for (AstNodeExpr* valuep : values) { - const string sanitizedName = arrayBinp->name() + "_" + std::to_string(index); - AstVar* const varp = createTrackedCoverpointBinCounter( - coverpointp, arrayBinp, sanitizedName, atLeastValue, - "Created array bin [" + std::to_string(index) + "]"); - - // Generate matching code for this specific value - generateArrayBinMatchCode(coverpointp, arrayBinp, exprp, varp, valuep); - - ++index; - } - - UINFO(4, " Generated " << index << " array bins"); - } - - // Generate matching code for a single array bin element - void generateArrayBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* binp, - AstNodeExpr* exprp, AstVar* hitVarp, AstNodeExpr* valuep) { - // Create condition: expr == value - AstNodeExpr* condp = new AstEq{binp->fileline(), exprp->cloneTree(false), valuep}; - - addCoverpointBinHitIf(coverpointp, binp, hitVarp, condp, - "Illegal bin " + binp->prettyNameQ() + " hit in coverpoint " - + coverpointp->prettyNameQ(), - "sample() CFunc not set when generating array bin code"); - } - - // Generate multiple bins for transition array bins - // Array bins with transitions create one bin per transition sequence - void generateTransitionArrayBins(AstCoverpoint* coverpointp, AstCoverBin* arrayBinp, - AstNodeExpr* exprp, int atLeastValue) { - UINFO(4, " Generating transition array bins for: " << arrayBinp->name()); - - // Extract all transition sets - std::vector transSets; - for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp()) - transSets.push_back(VN_AS(transSetp, CoverTransSet)); - - UINFO(4, " Found " << transSets.size() << " transition sets"); - int index = 0; - for (AstCoverTransSet* transSetp : transSets) { - const string sanitizedName = arrayBinp->name() + "_" + std::to_string(index); - AstVar* const varp = createTrackedCoverpointBinCounter( - coverpointp, arrayBinp, sanitizedName, atLeastValue, - "Created transition array bin [" + std::to_string(index) + "]"); - - // Generate matching code for this specific transition - generateSingleTransitionCode(coverpointp, arrayBinp, exprp, varp, transSetp); - - ++index; - } - - UINFO(4, " Generated " << index << " transition array bins"); - } - // Generate code for a single transition sequence (used by both regular and array bins) void generateSingleTransitionCode(AstCoverpoint* coverpointp, AstCoverBin* binp, - AstNodeExpr* exprp, AstVar* hitVarp, + AstNodeExpr* exprp, const ConvBinTarget& tgt, AstCoverTransSet* transSetp) { UINFO(4, " Generating code for transition sequence"); @@ -1207,162 +1179,125 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Combine: prev matches val1 AND current matches val2 AstNodeExpr* fullCondp = new AstAnd{binp->fileline(), cond1p, cond2p}; - addCoverpointBinHitIf(coverpointp, binp, hitVarp, fullCondp, - "Illegal transition bin " + binp->prettyNameQ() - + " hit in coverpoint " + coverpointp->prettyNameQ(), - "sample() CFunc not set when generating transition bin code"); + addConvTransHitIf(coverpointp, binp, tgt, fullCondp); UINFO(4, " Successfully added 2-value transition if statement"); } else { // Multi-value sequence (a => b => c => ...) // Use state machine to track position in sequence - generateMultiValueTransitionCode(coverpointp, binp, exprp, hitVarp, items); + generateMultiValueTransitionCode(coverpointp, binp, exprp, tgt, items); } } - // Recursive helper to generate Cartesian product of cross bins - void generateCrossBinsRecursive(AstCoverCross* crossp, - const std::vector& coverpointRefs, - const std::vector>& allCpBins, - std::vector currentCombination, - size_t dimension) { - if (dimension == allCpBins.size()) { - // Base case: we have a complete combination, generate the cross bin - generateOneCrossBin(crossp, coverpointRefs, currentCombination); - return; - } - - // Recursive case: iterate through bins at current dimension - for (AstCoverBin* binp : allCpBins[dimension]) { - currentCombination.push_back(binp); - generateCrossBinsRecursive(crossp, coverpointRefs, allCpBins, currentCombination, - dimension + 1); - currentCombination.pop_back(); + // Append a "{ VlCoverpoint* __Vcx_cps[] = {&cp0, &cp1, ...}; . }" statement. + AstCStmt* makeCrossCpsCall(FileLine* fl, const std::vector& cpVars, AstVar* cxVarp, + const std::string& callText) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ VlCoverpoint* __Vcx_cps[] = {"); + for (size_t d = 0; d < cpVars.size(); ++d) { + cs->add(d == 0 ? "&" : ", &"); + cs->add(memberRef(fl, cpVars[d])); } + cs->add("}; "); + cs->add(memberRef(fl, cxVarp)); + cs->add(callText); + cs->add(" }"); + return cs; } - // Generate a single cross bin for a specific combination of bins - void generateOneCrossBin(AstCoverCross* crossp, - const std::vector& coverpointRefs, - const std::vector& bins) { - // Build sanitized name from all bins - string binName; - string varName = "__Vcov_" + crossp->name(); - string crossBins; // Comma-separated individual bin names (one per coverpoint dimension) + // Route a cross through a VlCoverCross member: emit the member, its constructor init + + // registration, and the sample() call. The feeding coverpoints are already generated + // (their hit lists drive the cross), so only O(1) generated code is needed here. + void generateCross(AstCoverCross* crossp) { + FileLine* const fl = crossp->fileline(); + UINFO(4, " Generating VlCoverCross member: " << crossp->name()); - for (size_t i = 0; i < bins.size(); ++i) { - const string sanitized = sanitizeGeneratedName(bins[i]->name()); + // Resolve and unlink the coverpoint refs, in dimension order. Every ref resolves to a + // known coverpoint (a cross with an unresolvable item was dropped earlier). + std::vector cpVars; + for (AstNode* itemp = crossp->itemsp(); itemp;) { + AstNode* const nextp = itemp->nextp(); + AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + const auto it = m_cpVarMap.find(refp->name()); + UASSERT_OBJ(it != m_cpVarMap.end(), crossp, "Cross references an unknown coverpoint"); + cpVars.push_back(it->second); + VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp); + itemp = nextp; + } + const int dims = static_cast(cpVars.size()); - if (i > 0) { - binName += "_x_"; - varName += "_x_"; - crossBins += ","; - } - binName += sanitized; - varName += "_" + sanitized; - crossBins += sanitized; + if (!m_vlCoverCrossDTypep) { + m_vlCoverCrossDTypep = new AstCDType{fl, "VlCoverCross"}; + v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverCrossDTypep); + } + AstVar* const cxVarp + = new AstVar{fl, VVarType::MEMBER, "__Vcx_" + crossp->name(), m_vlCoverCrossDTypep}; + cxVarp->isStatic(false); + m_covergroupp->addMembersp(cxVarp); + m_crossVars.push_back(cxVarp); + + // Constructor: init (after the coverpoints, which generate earlier) then registration. + // Obfuscate the hierarchy/filename/page under --protect-ids as for coverpoints above. + const bool prot = v3Global.opt.protectIds(); + const std::string hier + = VIdProtect::protectWordsIf(m_covergroupp->name() + "." + crossp->name(), prot); + const std::string initCall + = ".init(\"" + hier + "\", " + std::to_string(dims) + ", __Vcx_cps, \"" + + VIdProtect::protectIf(fl->filename(), prot) + "\", " + std::to_string(fl->lineno()) + + ", " + std::to_string(fl->firstColumn()) + ");"; + m_constructorp->addStmtsp(makeCrossCpsCall(fl, cpVars, cxVarp, initCall)); + if (v3Global.opt.coverage()) { + const std::string page + = VIdProtect::protectIf("v_covergroup/" + m_covergroupp->name(), prot); + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cxVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"" + page + "\");"); + m_constructorp->addStmtsp(regp); } - // Create member variable for this cross bin - AstVar* const varp - = createCoverageCounterVar(crossp->fileline(), varName, bins[0]->findUInt32DType()); - - UINFO(4, " Created cross bin variable: " << varName); - - // Track this for coverage computation - AstCoverBin* const pseudoBinp = new AstCoverBin{ - crossp->fileline(), binName, static_cast(nullptr), false, false}; - m_binInfos.push_back(BinInfo(pseudoBinp, varp, 1, nullptr, crossp, crossBins)); - - // Generate matching code: if (bin1 && bin2 && ... && binN) varName++; - generateNWayCrossBinMatchCode(crossp, coverpointRefs, bins, varp); - } - - // Generate matching code for N-way cross bin - void generateNWayCrossBinMatchCode(AstCoverCross* crossp, - const std::vector& coverpointRefs, - const std::vector& bins, AstVar* hitVarp) { - UINFO(4, " Generating " << bins.size() << "-way cross bin match"); - - // Build combined condition by ANDing all bin conditions - AstNodeExpr* fullCondp = nullptr; - - for (size_t i = 0; i < bins.size(); ++i) { - AstNodeExpr* const exprp = coverpointRefs[i]->exprp(); - AstNodeExpr* const condp = buildBinCondition(bins[i], exprp); - - if (fullCondp) { - fullCondp = new AstAnd{crossp->fileline(), fullCondp, condp}; - } else { - fullCondp = condp; - } - } - - // Generate: if (cond1 && cond2 && ... && condN) { ++varName; } - AstNodeStmt* const incrp = makeBinHitIncrement(crossp->fileline(), hitVarp); - - AstIf* const ifp = new AstIf{crossp->fileline(), fullCondp, incrp}; - m_sampleFuncp->addStmtsp(ifp); + // sample(): after all coverpoints have sampled (cross loop runs after coverpoint loop). + UASSERT_OBJ(m_sampleFuncp, crossp, "sample() CFunc not set for cross"); + m_sampleFuncp->addStmtsp(makeCrossCpsCall(fl, cpVars, cxVarp, ".sample(__Vcx_cps);")); } void generateCrossCode(AstCoverCross* crossp) { UINFO(4, " Generating code for cross: " << crossp->name()); - // Resolve coverpoint references and build list - std::vector coverpointRefs; - AstNode* itemp = crossp->itemsp(); - while (itemp) { - AstNode* const nextp = itemp->nextp(); - AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + // Non-standard hierarchical/dotted cross item (e.g. 'cross a.b'): an implicit coverpoint + // over the referenced expression (carried in refp->exprp()). The grammar already warned + // NONSTD; implicit coverpoints are not yet implemented, so generate no sampling code for + // this cross. When support is added the implicit coverpoint should be synthesized + // upstream (V3LinkParse) as a real AstCoverpoint so it flows through the normal coverpoint + // path - by here coverpoint lowering has already run. + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + const AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); if (refp->exprp()) { - // Non-standard hierarchical/dotted cross item (e.g. 'cross a.b'): an implicit - // coverpoint over the referenced expression (carried in refp->exprp()). The - // grammar already warned NONSTD; implicit coverpoints are not yet implemented, so - // generate no sampling code for this cross. When support is added the implicit - // coverpoint should be synthesized upstream (V3LinkParse) as a real AstCoverpoint - // so it flows through the normal coverpoint path - by here coverpoint lowering has - // already run. refp->v3warn(COVERIGN, "Unsupported: cross of hierarchical reference (implicit coverpoint)"); return; } - // Find the referenced coverpoint via name map (O(log n) vs O(n) linear scan) - const auto it = m_coverpointMap.find(refp->name()); - AstCoverpoint* const foundCpp = (it != m_coverpointMap.end()) ? it->second : nullptr; - - if (!foundCpp) { - // Name not found as an explicit coverpoint - it's a direct variable - // reference (implicit coverpoint), which Verilator does not support. - // Warn and drop the whole cross. - refp->v3warn(COVERIGN, "Unsupported: cross of '" - << refp->prettyName() - << "' which is not a coverpoint (implicit coverpoint)"); - return; - } - - coverpointRefs.push_back(foundCpp); - - // Delete the reference node - it's no longer needed - VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp); - itemp = nextp; - } // LCOV_EXCL_BR_LINE - - UINFO(4, " Generating " << coverpointRefs.size() << "-way cross"); - - // Collect bins from all coverpoints (excluding ignore/illegal bins) - std::vector> allCpBins; - for (AstCoverpoint* cpp : coverpointRefs) { - std::vector cpBins; - for (AstNode* binp = cpp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - if (cbinp->binsType() == VCoverBinsType::BINS_USER) { cpBins.push_back(cbinp); } - } - UINFO(4, " Found " << cpBins.size() << " bins in " << cpp->name()); - allCpBins.push_back(cpBins); } - // Generate cross bins using Cartesian product - generateCrossBinsRecursive(crossp, coverpointRefs, allCpBins, {}, 0); + // A cross naming a bare variable (implicit coverpoint, which Verilator does not + // synthesize) is dropped entirely with a COVERIGN warning -- it produces no coverage + // either way -- but only this cross is dropped; its sibling crosses are still generated + // and the real coverpoints it referenced remain as independent coverpoints. + if (m_droppedCrosses.count(crossp)) { + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + const AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + if (m_coverpointMap.find(refp->name()) == m_coverpointMap.end()) { + refp->v3warn(COVERIGN, "Unsupported: cross of " + << refp->prettyNameQ() + << " which is not a coverpoint (implicit " + "coverpoint)"); + break; + } + } + return; + } + + // Every cross that isn't dropped routes through a VlCoverCross member. + generateCross(crossp); } AstNodeExpr* buildBinCondition(AstCoverBin* binp, AstNodeExpr* exprp) { @@ -1427,6 +1362,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { if (isWildcard) { rangeCondp = buildWildcardCondition(binp, exprp, constp); } else { + // 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 + // (x/z collapse to 0); switch to AstEqCase when 4-state sim support lands. rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false), constp->cloneTree(false)}; } @@ -1471,6 +1409,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstNodeExpr* const exprMasked = new AstAnd{fl, exprp->cloneTree(false), maskConstp}; AstNodeExpr* const valueMasked = new AstAnd{fl, valueConstp, maskConstp->cloneTree(false)}; + // TODO: masking the wildcard (don't-care) bits is correct, but the defined-bit + // comparison should use === (AstEqCase) per IEEE 1800-2023 19.5.4 once 4-state sim + // support lands; == is equivalent under 2-state sim (x/z collapse to 0). return new AstEq{fl, exprMasked, valueMasked}; } @@ -1487,15 +1428,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstFunc* const getInstCoveragep = VN_CAST(m_memberMap.findMember(m_covergroupp, "get_inst_coverage"), Func); - // Even if there are no bins, we still need to generate the coverage methods - // Empty covergroups should return 100% coverage - if (m_binInfos.empty()) { - UINFO(4, " No bins found, will generate method to return 100%"); - } else { - UINFO(6, " Found " << m_binInfos.size() << " bins for coverage"); - } - - // Generate code for get_inst_coverage() + // Generate code for get_inst_coverage() (an empty covergroup returns 100%). generateCoverageMethodBody(getInstCoveragep); // Generate code for get_coverage() (type-level) @@ -1516,198 +1449,37 @@ class FunctionalCoverageVisitor final : public VNVisitor { FileLine* const fl = funcp->fileline(); AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions - // (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat - // covered/total ratio the all-legacy path below computes. Normal bins only: ignore, - // illegal, and default are excluded (LRM 19.5). - if (!m_convCpVars.empty()) { - AstCStmt* const headp = new AstCStmt{fl}; - headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); - funcp->addStmtsp(headp); - for (AstVar* const cpVarp : m_convCpVars) { - AstCStmt* const cs = new AstCStmt{fl}; - cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); - cs->add(memberRef(fl, cpVarp)); - cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); - funcp->addStmtsp(cs); - } - int legacyRegular = 0; - for (const BinInfo& bi : m_binInfos) { - if (!bi.binp->binsType().binIsNormal()) continue; - ++legacyRegular; - AstCStmt* const cs = new AstCStmt{fl}; - cs->add("if ("); - cs->add(memberRef(fl, bi.varp)); - cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;"); - funcp->addStmtsp(cs); - } - if (legacyRegular) { - AstCStmt* const cs = new AstCStmt{fl}; - cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;"); - funcp->addStmtsp(cs); - } - AstCStmt* const retp = new AstCStmt{fl}; - retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); - retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); - funcp->addStmtsp(retp); - return; - } - - // Count total bins (Normal only: excludes ignore/illegal/default) - int totalBins = 0; - for (const BinInfo& bi : m_binInfos) { - UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii()); - if (bi.binp->binsType().binIsNormal()) totalBins++; - } - - UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); - - if (totalBins == 0) { - // No coverage to compute - return 100%. - // Any parser-generated initialization of returnVar is overridden by our assignment. - UINFO(4, " Empty covergroup, returning 100.0"); + // Every coverpoint and cross holds its bins in the runtime (VlCoverpoint/VlCoverCross). + // Sum their covered/total contributions via coverageParts (Normal bins only; ignore, + // illegal, and default are excluded per LRM 19.5). A covergroup with no coverpoints + // (and hence no crosses) has nothing to cover and reports 100%. + if (m_cpVars.empty()) { funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, new AstConst{fl, AstConst::RealDouble{}, 100.0}}); - UINFO(4, " Added assignment to return 100.0"); return; } - - // Create local variable to count covered bins - AstVar* const coveredCountp - = new AstVar{fl, VVarType::BLOCKTEMP, "__Vcovered_count", funcp->findUInt32DType()}; - coveredCountp->funcLocal(true); - funcp->addStmtsp(coveredCountp); - - // Initialize: covered_count = 0 - funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, coveredCountp, VAccess::WRITE}, - new AstConst{fl, AstConst::WidthedValue{}, 32, 0}}); - - // For each regular bin, if count > 0, increment covered_count - for (const BinInfo& bi : m_binInfos) { - // Skip ignore/illegal/default bins in coverage calculation - if (!bi.binp->binsType().binIsNormal()) continue; - - // if (bin_count >= at_least) covered_count++; - AstIf* ifp = new AstIf{ - fl, - new AstGte{fl, new AstVarRef{fl, bi.varp, VAccess::READ}, - new AstConst{fl, AstConst::WidthedValue{}, 32, - static_cast(bi.atLeast)}}, - new AstAssign{fl, new AstVarRef{fl, coveredCountp, VAccess::WRITE}, - new AstAdd{fl, new AstVarRef{fl, coveredCountp, VAccess::READ}, - new AstConst{fl, AstConst::WidthedValue{}, 32, 1}}}, - nullptr}; - funcp->addStmtsp(ifp); + AstCStmt* const headp = new AstCStmt{fl}; + headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); + funcp->addStmtsp(headp); + for (AstVar* const cpVarp : m_cpVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cpVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); } - - // Calculate coverage: (covered_count / total_bins) * 100.0 - // return_var = (double)covered_count / (double)total_bins * 100.0 - - // Cast covered_count to real/double - AstNodeExpr* const coveredReal - = new AstIToRD{fl, new AstVarRef{fl, coveredCountp, VAccess::READ}}; - - // Create total bins as a double constant - AstNodeExpr* const totalReal - = new AstConst{fl, AstConst::RealDouble{}, static_cast(totalBins)}; - - // Divide using AstDivD (double division that emits native /) - AstNodeExpr* const divExpr = new AstDivD{fl, coveredReal, totalReal}; - - // Multiply by 100 using AstMulD (double multiplication that emits native *) - AstNodeExpr* const hundredConst = new AstConst{fl, AstConst::RealDouble{}, 100.0}; - AstNodeExpr* const coverageExpr = new AstMulD{fl, hundredConst, divExpr}; - - // Assign to return variable - funcp->addStmtsp( - new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, coverageExpr}); - - UINFO(6, " Added coverage computation to " << funcp->name() << " with " << totalBins - << " bins (excluding ignore/illegal)"); - } - - void generateCoverageRegistration() { - // Generate VL_COVER_INSERT calls for each bin in the covergroup - // This registers the bins with the coverage database so they can be reported - - UINFO(4, - " Generating coverage database registration for " << m_binInfos.size() << " bins"); - - if (m_binInfos.empty()) return; - - // For each bin, generate a VL_COVER_INSERT call - // The calls use CCall nodes to invoke VL_COVER_INSERT macro - for (const BinInfo& binInfo : m_binInfos) { - AstVar* const varp = binInfo.varp; - AstCoverBin* const binp = binInfo.binp; - AstCoverpoint* const coverpointp = binInfo.coverpointp; - AstCoverCross* const crossp = binInfo.crossp; - - FileLine* const fl = binp->fileline(); - - // Build hierarchical name: covergroup.coverpoint.bin or covergroup.cross.bin - std::string hierName = m_covergroupp->name(); - const std::string binName = binp->name(); - - if (coverpointp) { - // Coverpoint bin: V3LinkParse guarantees a non-empty name for every - // coverpoint (user label, single-variable name, or synthesized __Vcoverpoint). - UASSERT_OBJ(!coverpointp->name().empty(), coverpointp, - "Coverpoint without a name (should be set in V3LinkParse)"); - hierName += "." + coverpointp->name(); - } else { - // Cross bin: grammar always provides a name (user label or auto "__crossN") - hierName += "." + crossp->name(); - } - hierName += "." + binName; - - // Generate: VL_COVER_INSERT(contextp, hier, &binVar, "page", "v_covergroup/...", ...) - - UINFO(6, " Registering bin: " << hierName << " -> " << varp->name()); - - // Build the coverage insert as a C statement mixing literal text with a proper - // AstVarRef for the bin variable. Using AstVarRef (with selfPointer=This) lets - // V3Name apply __PVT__ mangling and the emitter apply nameProtect(), which also - // handles --protect-ids correctly. The vlSymsp->_vm_contextp__ path is the - // established convention used by the existing __vlCoverInsert helper. - // Use "page" field with v_covergroup prefix so the coverage type is identified - // correctly (consistent with code coverage). - const std::string pageName = "v_covergroup/" + m_covergroupp->name(); - AstCStmt* const cstmtp = new AstCStmt{fl}; - cstmtp->add("VL_COVER_INSERT(vlSymsp->_vm_contextp__->coveragep(), " - "\"" - + hierName + "\", &("); - AstVarRef* const binVarRefp = new AstVarRef{fl, varp, VAccess::READ}; - binVarRefp->selfPointer(VSelfPointerText{VSelfPointerText::This{}}); - cstmtp->add(binVarRefp); - cstmtp->add("), \"page\", \"" + pageName - + "\", " - "\"filename\", \"" - + fl->filename() - + "\", " - "\"lineno\", \"" - + std::to_string(fl->lineno()) - + "\", " - "\"column\", \"" - + std::to_string(fl->firstColumn()) + "\", "); - const std::string crossSuffix - = crossp ? (", \"cross\", \"1\", \"cross_bins\", \"" + binInfo.crossBins + "\"") - : ""; - if (binp->binsType() == VCoverBinsType::BINS_IGNORE) { - cstmtp->add("\"bin\", \"" + binName + "\", \"bin_type\", \"ignore\"" + crossSuffix - + ");"); - } else if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - cstmtp->add("\"bin\", \"" + binName + "\", \"bin_type\", \"illegal\"" + crossSuffix - + ");"); - } else { - cstmtp->add("\"bin\", \"" + binName + "\"" + crossSuffix + ");"); - } - - // Add to constructor - m_constructorp->addStmtsp(cstmtp); - - UINFO(6, " Added VL_COVER_INSERT call to constructor"); + // Crosses contribute the same covered/total ratio as their per-tuple bins. + for (AstVar* const cxVarp : m_crossVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cxVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); } + AstCStmt* const retp = new AstCStmt{fl}; + retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); + retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); + funcp->addStmtsp(retp); } // VISITORS diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 903c2b307..c3d0a77e9 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -22,7 +22,6 @@ cg7.cp.rev[0]: 1 cg7.cp.rev[1]: 0 cg8.cp.w[0]: 0 cg8.cp.w[1]: 1 -cg9.__cross7.cumulative_x_lo [cross]: 1 cg9.__cross7.ok_x_lo [cross]: 1 cg9.cpA.ok: 1 cg9.cpB.lo: 1 diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index 2e5bb49e5..98bca7f0c 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -85,4 +85,28 @@ : ... note: In instance 't' 73 | bins b_huge[] = {[0:$]}; | ^~~~~~ +%Error: t/t_covergroup_autobins_bad.v:84:41: Non-constant expression in bin range; values must be constants + : ... 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 + : ... note: In instance 't' + 89 | 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 + : ... note: In instance 't' + 94 | 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 + : ... note: In instance 't' + 104 | cp_a: coverpoint cp_expr {bins x[] = {[4'h0 : 4'b000x]};} + | ^ +%Error: t/t_covergroup_autobins_bad.v:109:36: Non-constant expression in array bins value list; values must be constants + : ... note: In instance 't' + 109 | cp_a: coverpoint cp_expr {bins x[] = {size_var};} + | ^ %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 db8a48fd5..ed41554c7 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -74,6 +74,48 @@ module t; } endgroup + // Malformed bins on a coverpoint that feeds a *cross*. The cross path + // sizes the coverpoint's hit list (computeHitListBound/extractRangeIntervals) + // before the bin condition is built, so the malformed-bin guards there run gracefully and + // the user error is then diagnosed downstream by the bin-condition / array-value builders. + // (Without a cross the same errors fire via cg3/cg5 above; the cross also exercises the + // hit-list-sizing guard path.) + 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; + endgroup + covergroup cgx_nc_range_lo; // non-constant low bound, non-array range + cp_a: coverpoint cp_expr {bins x = {[size_var : 1]};} + cp_c: coverpoint cp_expr {bins r = {0}; bins w = {1};} + xc: cross cp_a, cp_c; + endgroup + covergroup cgx_nc_range_hi; // non-constant high bound, non-array range + cp_a: coverpoint cp_expr {bins x = {[0 : size_var]};} + cp_c: coverpoint cp_expr {bins r = {0}; bins w = {1};} + xc: cross cp_a, cp_c; + endgroup + covergroup cgx_arr_4state_lo; // four-state low bound, array range + cp_a: coverpoint cp_expr {bins x[] = {[4'b000x : 4'hF]};} + cp_c: coverpoint cp_expr {bins r = {0}; bins w = {1};} + xc: cross cp_a, cp_c; + endgroup + covergroup cgx_arr_4state_hi; // four-state high bound, array range + cp_a: coverpoint cp_expr {bins x[] = {[4'h0 : 4'b000x]};} + cp_c: coverpoint cp_expr {bins r = {0}; bins w = {1};} + xc: cross cp_a, cp_c; + endgroup + covergroup cgx_arr_ncval; // non-constant value, array value list + 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; + endgroup + covergroup cgx_arr_open; // open-ended ('$') bounds, array range + cp_a: coverpoint cp_expr {bins x[] = {[2 : $], [$ : 1]};} + cp_c: coverpoint cp_expr {bins r = {0}; bins w = {1};} + xc: cross cp_a, cp_c; + endgroup + cg1 cg1_inst = new; cg2 cg2_inst = new; cg2b cg2b_inst = new; @@ -81,6 +123,13 @@ module t; cg4 cg4_inst = new; cg5 cg5_inst = new; cg6 cg6_inst = new; + cgx_nc_value cgx_nc_value_inst = new; + cgx_nc_range_lo cgx_nc_range_lo_inst = new; + cgx_nc_range_hi cgx_nc_range_hi_inst = new; + cgx_arr_4state_lo cgx_arr_4state_lo_inst = new; + cgx_arr_4state_hi cgx_arr_4state_hi_inst = new; + cgx_arr_ncval cgx_arr_ncval_inst = new; + cgx_arr_open cgx_arr_open_inst = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index abb035e5e..687d59256 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -57,6 +57,27 @@ cg5.cp_addr.addr0: 1 cg5.cp_addr.addr1: 1 cg5.cp_cmd.read: 1 cg5.cp_cmd.write: 1 +cg_arr_4state.a4.av[0]_x_read [cross]: 1 +cg_arr_4state.a4.av[0]_x_write [cross]: 1 +cg_arr_4state.cp_addr.av[0]: 2 +cg_arr_4state.cp_cmd.read: 1 +cg_arr_4state.cp_cmd.write: 1 +cg_arr_range.ar.av[0]_x_read [cross]: 1 +cg_arr_range.ar.av[0]_x_write [cross]: 1 +cg_arr_range.ar.av[1]_x_read [cross]: 1 +cg_arr_range.ar.av[1]_x_write [cross]: 1 +cg_arr_range.cp_addr.av[0]: 2 +cg_arr_range.cp_addr.av[1]: 2 +cg_arr_range.cp_cmd.read: 2 +cg_arr_range.cp_cmd.write: 2 +cg_arr_vals.av.av[0]_x_read [cross]: 1 +cg_arr_vals.av.av[0]_x_write [cross]: 1 +cg_arr_vals.av.av[1]_x_read [cross]: 1 +cg_arr_vals.av.av[1]_x_write [cross]: 1 +cg_arr_vals.cp_addr.av[0]: 2 +cg_arr_vals.cp_addr.av[1]: 2 +cg_arr_vals.cp_cmd.read: 2 +cg_arr_vals.cp_cmd.write: 2 cg_at_least.addr_cmd_al.addr0_x_read [cross]: 1 cg_at_least.addr_cmd_al.addr0_x_write [cross]: 0 cg_at_least.addr_cmd_al.addr1_x_read [cross]: 0 @@ -65,13 +86,29 @@ cg_at_least.cp_addr.addr0: 1 cg_at_least.cp_addr.addr1: 1 cg_at_least.cp_cmd.read: 1 cg_at_least.cp_cmd.write: 1 +cg_be.cp_addr.hi: 2 +cg_be.cp_addr.lo: 2 +cg_be.cp_cmd.read: 2 +cg_be.cp_cmd.write: 2 +cg_be.x.hi_x_read [cross]: 1 +cg_be.x.hi_x_write [cross]: 1 +cg_be.x.lo_x_read [cross]: 1 +cg_be.x.lo_x_write [cross]: 1 +cg_be_arr.ax.av[0]_x_read [cross]: 1 +cg_be_arr.ax.av[0]_x_write [cross]: 1 +cg_be_arr.ax.av[1]_x_read [cross]: 1 +cg_be_arr.ax.av[1]_x_write [cross]: 1 +cg_be_arr.cp_addr.av[0]: 2 +cg_be_arr.cp_addr.av[1]: 2 +cg_be_arr.cp_cmd.read: 2 +cg_be_arr.cp_cmd.write: 2 cg_def_cross.axc.a0_x_read [cross]: 1 cg_def_cross.axc.a0_x_write [cross]: 0 cg_def_cross.axc.a1_x_read [cross]: 0 cg_def_cross.axc.a1_x_write [cross]: 0 cg_def_cross.cp_a.a0: 1 cg_def_cross.cp_a.a1: 0 -cg_def_cross.cp_a.ad: 1 +cg_def_cross.cp_a.ad [default]: 1 cg_def_cross.cp_c.read: 1 cg_def_cross.cp_c.write: 1 cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1 @@ -91,6 +128,11 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1 cg_ignore.cross_ab.a0_x_write [cross]: 1 cg_ignore.cross_ab.a1_x_read [cross]: 1 cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_inv.cp_addr.inv: 0 +cg_inv.cp_cmd.read: 1 +cg_inv.cp_cmd.write: 1 +cg_inv.iv.inv_x_read [cross]: 0 +cg_inv.iv.inv_x_write [cross]: 0 cg_mixed.ab.addr0_x_read [cross]: 1 cg_mixed.ab.addr0_x_write [cross]: 1 cg_mixed.ab.addr1_x_read [cross]: 1 @@ -101,6 +143,25 @@ cg_mixed.cp_cmd.read: 2 cg_mixed.cp_cmd.write: 2 cg_mixed.cp_solo.debug: 2 cg_mixed.cp_solo.normal: 2 +cg_noNormal.cp_addr.ig [ignore]: 2 +cg_noNormal.cp_cmd.read: 1 +cg_noNormal.cp_cmd.write: 1 +cg_openrange.cp_addr.hi: 2 +cg_openrange.cp_addr.lo: 2 +cg_openrange.cp_cmd.read: 2 +cg_openrange.cp_cmd.write: 2 +cg_openrange.orc.hi_x_read [cross]: 1 +cg_openrange.orc.hi_x_write [cross]: 1 +cg_openrange.orc.lo_x_read [cross]: 1 +cg_openrange.orc.lo_x_write [cross]: 1 +cg_overlap.cp_addr.hi: 3 +cg_overlap.cp_addr.lo: 3 +cg_overlap.cp_cmd.read: 3 +cg_overlap.cp_cmd.write: 2 +cg_overlap.ov.hi_x_read [cross]: 2 +cg_overlap.ov.hi_x_write [cross]: 1 +cg_overlap.ov.lo_x_read [cross]: 2 +cg_overlap.ov.lo_x_write [cross]: 1 cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 @@ -109,6 +170,11 @@ cg_range.cp_addr.hi_range: 2 cg_range.cp_addr.lo_range: 2 cg_range.cp_cmd.read: 2 cg_range.cp_cmd.write: 2 +cg_trans.cp_t.t01: 1 +cg_trans.cp_v.v5: 2 +cg_trans.cp_v.v6: 1 +cg_trans.tx.t01_x_v5 [cross]: 1 +cg_trans.tx.t01_x_v6 [cross]: 0 cg_unnamed_cross.__cross8.a0_x_read [cross]: 1 cg_unnamed_cross.__cross8.a0_x_write [cross]: 0 cg_unnamed_cross.__cross8.a1_x_read [cross]: 0 @@ -125,3 +191,21 @@ cg_unsup_cross_opt.cp_addr.addr0: 1 cg_unsup_cross_opt.cp_addr.addr1: 1 cg_unsup_cross_opt.cp_cmd.read: 1 cg_unsup_cross_opt.cp_cmd.write: 1 +cg_wide.cp_cmd.read: 2 +cg_wide.cp_cmd.write: 2 +cg_wide.cp_wide.hi: 2 +cg_wide.cp_wide.lo: 2 +cg_wide.wd.hi_x_read [cross]: 1 +cg_wide.wd.hi_x_write [cross]: 1 +cg_wide.wd.lo_x_read [cross]: 1 +cg_wide.wd.lo_x_write [cross]: 1 +cg_wild_arr.cp_addr.wb: 4 +cg_wild_arr.cp_cmd.read: 2 +cg_wild_arr.cp_cmd.write: 2 +cg_wild_arr.wa.wb_x_read [cross]: 2 +cg_wild_arr.wa.wb_x_write [cross]: 2 +cg_wild_solo.cp_addr.wb: 2 +cg_wild_solo.cp_cmd.read: 1 +cg_wild_solo.cp_cmd.write: 1 +cg_wild_solo.ws.wb_x_read [cross]: 1 +cg_wild_solo.ws.wb_x_write [cross]: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index f0857bc01..b1ca88426 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -16,6 +16,18 @@ module t; logic cmd; logic mode; logic parity; + logic [63:0] wide; + logic [3:0] state; + logic [3:0] val; + + // Ascending ("opposite-endian") bit ranges: bit 0 is the MSB. Bit ordering must + // not change coverage results: range bins operate on the sampled value, so an + // ascending-declared coverpoint produces identical hit counts to the usual + // descending declaration. + /* verilator lint_off ASCRANGE */ + logic [0:1] be_addr; + logic [0:0] be_cmd; + /* verilator lint_on ASCRANGE */ typedef struct packed {logic m_p; logic h_mode;} cfg_t; cfg_t s_cfg = '0; @@ -108,8 +120,8 @@ module t; cross cp_a, cp_c; // no label: reported under the default cross name endgroup - // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted - // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the un-crossed + // coverpoint cp_solo with the cross and its crossed coverpoints. covergroup cg_mixed; cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} @@ -117,14 +129,125 @@ module t; ab: cross cp_addr, cp_cmd; endgroup - // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the - // legacy default-bin codegen path that converted coverpoints bypass. + // Crossed coverpoint that also has a default bin: exercises default-bin handling on a + // coverpoint that feeds a cross. covergroup cg_def_cross; cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} axc: cross cp_a, cp_c; endgroup + // Crossed coverpoint with an array range bin (bins av[] = {[0:1]}): exercises the + // coverpoint hit-list sizing for array range elements feeding a cross. + covergroup cg_arr_range; + cp_addr: coverpoint addr {bins av[] = {[0 : 1]};} // -> av[0]=0, av[1]=1 + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + ar: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint with an array value bin (bins av[] = {0, 2}): array value elements. + covergroup cg_arr_vals; + cp_addr: coverpoint addr {bins av[] = {0, 2};} // -> av[0]=0, av[1]=2 + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + av: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint with a wildcard array bin (wildcard bins wb[] = {2'b0?}): the '0?' + // wildcard expands to addr values 0 and 1, each its own array element. + covergroup cg_wild_arr; + cp_addr: coverpoint addr {wildcard bins wb[] = {2'b0?};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + wa: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint with a non-array wildcard bin (wildcard bins wb = {2'b0?}): single + // wildcard bin matching addr 0 or 1, feeding a cross. + covergroup cg_wild_solo; + cp_addr: coverpoint addr {wildcard bins wb = {2'b0?};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + ws: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint with a four-state literal in a non-wildcard array bin + // (bins av[] = {2'b0x}): LRM 1800-2023 19.5.4 permits 4-state values in a bin definition. + // The hit-list sizing cannot statically analyze a 4-state value, so it falls back to the + // safe slot count. Under Verilator's 2-state simulation the value matches addr=0. + covergroup cg_arr_4state; + cp_addr: coverpoint addr {bins av[] = {2'b0x};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + a4: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint with two *overlapping* Normal range bins (addr=1 is in both lo and + // hi): the hit-list sizing must report a bound > 1 (a single sample can fall in two bins), + // exercising the max-overlap computation in computeHitListBound. + covergroup cg_overlap; + cp_addr: coverpoint addr {bins lo = {[0 : 1]}; bins hi = {[1 : 2]};} // overlap at addr=1 + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + ov: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint whose sampled expression is wider than 64 bits: exercises the + // width>=64 max-value path in the hit-list sizing (where 1< hi bound): the bin matches no + // value, so the hit-list sizing rejects it (lo > hi) and falls back to the safe slot count. + // The 'inv' bin and its cross bins are therefore never hit (coverage stays at 40%). + covergroup cg_inv; + cp_addr: coverpoint addr {bins inv = {[3 : 0]};} // inverted -> never matches + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + iv: cross cp_addr, cp_cmd; + endgroup + + // Crossed coverpoint with *no* Normal bins (only ignore_bins): the cross has an empty bin + // product, so the hit-list sizing returns the safe bound of 1. Coverage is the cmd + // coverpoint alone (vacuously 100% once both cmd bins are hit). + covergroup cg_noNormal; + cp_addr: coverpoint addr {ignore_bins ig = {[0 : 3]};} // zero Normal bins + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + nn: cross cp_addr, cp_cmd; + endgroup + + // Cross of a *transition* coverpoint with a value coverpoint. Transition coverpoints route + // through the VlCoverpoint runtime (their completion appends to the hit list), so a cross + // can read them like any other coverpoint. + covergroup cg_trans; + cp_t: coverpoint state {bins t01 = (0 => 1);} // one Normal (transition) bin + cp_v: coverpoint val {bins v5 = {5}; bins v6 = {6};} + tx: cross cp_t, cp_v; + endgroup + + // Range bins over an ascending-declared coverpoint, crossed with another: the + // range interval extraction must ignore bit endianness (mirror of cg_range). + covergroup cg_be; + cp_addr: coverpoint be_addr {bins lo = {[0 : 1]}; bins hi = {[2 : 3]};} + cp_cmd: coverpoint be_cmd {bins read = {0}; bins write = {1};} + x: cross cp_addr, cp_cmd; + endgroup + + // Array range bin over an ascending-declared coverpoint: {[0:1]} enumerates to + // av[0]=0, av[1]=1, exercising array-range slot enumeration under opposite + // endian (mirror of cg_arr_range). + covergroup cg_be_arr; + cp_addr: coverpoint be_addr {bins av[] = {[0 : 1]};} + cp_cmd: coverpoint be_cmd {bins read = {0}; bins write = {1};} + ax: cross cp_addr, cp_cmd; + endgroup + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -137,6 +260,19 @@ module t; cg_unnamed_cross cg_unnamed_cross_inst = new; cg_mixed cg_mixed_inst = new; cg_def_cross cg_def_cross_inst = new; + cg_arr_range cg_arr_range_inst = new; + cg_arr_vals cg_arr_vals_inst = new; + cg_wild_arr cg_wild_arr_inst = new; + cg_wild_solo cg_wild_solo_inst = new; + cg_arr_4state cg_arr_4state_inst = new; + cg_overlap cg_overlap_inst = new; + cg_wide cg_wide_inst = new; + cg_openrange cg_openrange_inst = new; + cg_inv cg_inv_inst = new; + cg_noNormal cg_noNormal_inst = new; + cg_trans cg_trans_inst = new; + cg_be cg_be_inst = new; + cg_be_arr cg_be_arr_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -320,6 +456,106 @@ module t; addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write + // Sample cg_arr_range: array range bin {[0:1]} -> av[0]=0, av[1]=1; cross 2x2 + // cg_arr_range: 2+2+4=8 bins; sample all combinations -> 100% + addr = 0; cmd = 0; cg_arr_range_inst.sample(); // av[0] x read + addr = 0; cmd = 1; cg_arr_range_inst.sample(); // av[0] x write + addr = 1; cmd = 0; cg_arr_range_inst.sample(); // av[1] x read + addr = 1; cmd = 1; cg_arr_range_inst.sample(); // av[1] x write + `checkr(cg_arr_range_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_arr_vals: array value bin {0,2} -> av[0]=0, av[1]=2; cross 2x2 + // cg_arr_vals: 2+2+4=8 bins; sample all combinations -> 100% + addr = 0; cmd = 0; cg_arr_vals_inst.sample(); // av[0] x read + addr = 0; cmd = 1; cg_arr_vals_inst.sample(); // av[0] x write + addr = 2; cmd = 0; cg_arr_vals_inst.sample(); // av[1] x read + addr = 2; cmd = 1; cg_arr_vals_inst.sample(); // av[1] x write + `checkr(cg_arr_vals_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_wild_arr: wildcard array {2'b0?} matches addr 0,1; cross 2x2 + // cg_wild_arr: 2+2+4=8 bins; sample all combinations -> 100% + addr = 0; cmd = 0; cg_wild_arr_inst.sample(); + addr = 0; cmd = 1; cg_wild_arr_inst.sample(); + addr = 1; cmd = 0; cg_wild_arr_inst.sample(); + addr = 1; cmd = 1; cg_wild_arr_inst.sample(); + `checkr(cg_wild_arr_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_wild_solo: single wildcard bin {2'b0?} matches addr 0,1; cross 1x2 + // cg_wild_solo: 1+2+2=5 bins; sample both cmd values -> 100% + addr = 0; cmd = 0; cg_wild_solo_inst.sample(); + addr = 0; cmd = 1; cg_wild_solo_inst.sample(); + `checkr(cg_wild_solo_inst.get_inst_coverage(), 100.0); // 5/5 + + // Sample cg_arr_4state: 4-state literal bin {2'b0x} matches addr=0 (2-state sim); cross 1x2 + // cg_arr_4state: 1+2+2=5 bins; sample both cmd values -> 100% + addr = 0; cmd = 0; cg_arr_4state_inst.sample(); + addr = 0; cmd = 1; cg_arr_4state_inst.sample(); + `checkr(cg_arr_4state_inst.get_inst_coverage(), 100.0); // 5/5 + + // Sample cg_overlap: overlapping range bins lo={0,1}, hi={1,2}; cross 2x2 + // cg_overlap: 2+2+4=8 bins; cover lo/hi via addr 0 and 2, plus addr=1 double-hits both + addr = 0; cmd = 0; cg_overlap_inst.sample(); // lo x read + addr = 0; cmd = 1; cg_overlap_inst.sample(); // lo x write + addr = 2; cmd = 0; cg_overlap_inst.sample(); // hi x read + addr = 2; cmd = 1; cg_overlap_inst.sample(); // hi x write + addr = 1; cmd = 0; cg_overlap_inst.sample(); // addr=1 in both lo and hi (hit-list bound 2) + `checkr(cg_overlap_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_wide: 64-bit coverpoint, lo={0,1}, hi={2,3}; cross 2x2 + // cg_wide: 2+2+4=8 bins; sample all combinations -> 100% + wide = 0; cmd = 0; cg_wide_inst.sample(); // lo x read + wide = 0; cmd = 1; cg_wide_inst.sample(); // lo x write + wide = 2; cmd = 0; cg_wide_inst.sample(); // hi x read + wide = 2; cmd = 1; cg_wide_inst.sample(); // hi x write + `checkr(cg_wide_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_openrange: lo=[$:1] matches 0,1; hi=[2:$] matches 2,3; cross 2x2 + // cg_openrange: 2+2+4=8 bins; sample all combinations -> 100% + addr = 0; cmd = 0; cg_openrange_inst.sample(); // lo x read + addr = 0; cmd = 1; cg_openrange_inst.sample(); // lo x write + addr = 2; cmd = 0; cg_openrange_inst.sample(); // hi x read + addr = 2; cmd = 1; cg_openrange_inst.sample(); // hi x write + `checkr(cg_openrange_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_inv: inverted range bin never matches; only cmd bins are hittable + // cg_inv: 1+2+2=5 bins; inv and its 2 cross bins never hit -> 2/5=40% + addr = 0; cmd = 0; cg_inv_inst.sample(); // read + addr = 1; cmd = 1; cg_inv_inst.sample(); // write + `checkr(cg_inv_inst.get_inst_coverage(), 40.0); // 2/5: read + write only + + // Sample cg_noNormal: coverpoint has no Normal bins; cross product is empty + // cg_noNormal: 0+2+0=2 bins (cmd only); both hit -> 100% + addr = 0; cmd = 0; cg_noNormal_inst.sample(); // read + addr = 1; cmd = 1; cg_noNormal_inst.sample(); // write + `checkr(cg_noNormal_inst.get_inst_coverage(), 100.0); // 2/2 + + // Sample cg_trans: transition coverpoint crossed with a value coverpoint + // cg_trans: 1+2+2=5 bins; t01_x_v6 never completes -> 4/5=80% + // __Vprev_cp_t initializes to 0. + state = 0; val = 5; cg_trans_inst.sample(); // prev=0,cur=0: no t01; v5 + state = 1; val = 5; cg_trans_inst.sample(); // prev=0,cur=1: t01 completes; t01_x_v5 + state = 0; val = 6; cg_trans_inst.sample(); // prev=1,cur=0: no t01; v6 (no cross) + `checkr(cg_trans_inst.get_inst_coverage(), 80.0); // 4/5: t01_x_v6 not hit + + // Sample cg_be: range bins over an ascending-declared coverpoint; cross 2x2 + // cg_be: 2+2+4=8 bins; endianness must not change results (mirror of cg_range) + be_addr = 0; be_cmd = 0; cg_be_inst.sample(); // lo x read + `checkr(cg_be_inst.get_inst_coverage(), 37.5); // 3/8 + be_addr = 2; be_cmd = 1; cg_be_inst.sample(); // hi x write + `checkr(cg_be_inst.get_inst_coverage(), 75.0); // 6/8 + be_addr = 1; be_cmd = 1; cg_be_inst.sample(); // lo x write + `checkr(cg_be_inst.get_inst_coverage(), 87.5); // 7/8 + be_addr = 3; be_cmd = 0; cg_be_inst.sample(); // hi x read + `checkr(cg_be_inst.get_inst_coverage(), 100.0); // 8/8 + + // Sample cg_be_arr: array range bin over an ascending-declared coverpoint; cross 2x2 + // cg_be_arr: 2+2+4=8 bins; sample all combinations -> 100% + be_addr = 0; be_cmd = 0; cg_be_arr_inst.sample(); // av[0] x read + be_addr = 0; be_cmd = 1; cg_be_arr_inst.sample(); // av[0] x write + be_addr = 1; be_cmd = 0; cg_be_arr_inst.sample(); // av[1] x read + be_addr = 1; be_cmd = 1; cg_be_arr_inst.sample(); // av[1] x write + `checkr(cg_be_arr_inst.get_inst_coverage(), 100.0); // 8/8 + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_cross_secret.py b/test_regress/t/t_covergroup_cross_secret.py new file mode 100755 index 000000000..292cd9bd0 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_secret.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt_all') + +# Compile the cross-coverage design with --protect-ids and confirm the coverage +# database is obfuscated like line/toggle points: none of the "cgsecret"-marked +# covergroup/coverpoint/cross/bin names, nor the source filename, may leak into +# coverage.dat. (Historically covergroup coverage bypassed --protect-ids and leaked +# these in cleartext.) The design also self-checks get_inst_coverage(), so sampling +# correctness under hashed names is verified too. +# +# Everything that must stay hidden spells "secret" -- the identifiers in the .v and +# the filename itself -- so one grep covers the whole property. +test.compile(verilator_flags2=[ + '--coverage', + '--protect-ids', + '--protect-key SECRET_KEY', + '-Wno-INSECURE', +]) + +test.execute() + +# Security property: no original identifier or source filename in the coverage DB. +test.file_grep_not(test.coverage_filename, r'secret') + +# Sanity: 'to="PS"' in the id map means something already-protected was re-protected. +test.file_grep_not(test.obj_dir + "/" + test.vm_prefix + "__idmap.xml", r'to="PS') + +test.passes() diff --git a/test_regress/t/t_covergroup_cross_secret.v b/test_regress/t/t_covergroup_cross_secret.v new file mode 100644 index 000000000..c3f7f9255 --- /dev/null +++ b/test_regress/t/t_covergroup_cross_secret.v @@ -0,0 +1,60 @@ +// 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 + +// Test cross coverage compiled with --protect-ids. --protect-ids obfuscates +// identifiers in the generated C++ for IP protection, and the coverage database must +// be obfuscated too -- exactly as line/toggle coverage points are. Every covergroup, +// coverpoint, cross and bin name here carries the distinctive "cgsecret" marker so the +// driver can assert (via file_grep_not) that none of them leak into coverage.dat. The +// get_inst_coverage() self-checks additionally confirm sampling still works when the +// names are hashed. + +// verilog_format: off +`define stop $stop +`define checkr(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got=%f exp=%f\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + logic [1:0] cgsecret_addr; + logic cgsecret_cmd; + + // Value-bin cross + covergroup cgsecret_cg; + cgsecret_cp_addr: coverpoint cgsecret_addr {bins cgsecret_a0 = {0}; bins cgsecret_a1 = {1};} + cgsecret_cp_cmd: coverpoint cgsecret_cmd {bins cgsecret_rd = {0}; bins cgsecret_wr = {1};} + cgsecret_ac: cross cgsecret_cp_addr, cgsecret_cp_cmd; + endgroup + + // Range-bin cross (cross bin names are built at runtime from the range-bin names) + covergroup cgsecret_cg_rng; + cgsecret_cp_addr: coverpoint cgsecret_addr {bins cgsecret_lo = {[0 : 1]}; bins cgsecret_hi = {[2 : 3]};} + cgsecret_cp_cmd: coverpoint cgsecret_cmd {bins cgsecret_rd = {0}; bins cgsecret_wr = {1};} + cgsecret_rc: cross cgsecret_cp_addr, cgsecret_cp_cmd; + endgroup + + cgsecret_cg cg_inst = new; + cgsecret_cg_rng cg_rng_inst = new; + + initial begin + // cg_inst: 2 + 2 + 4 = 8 bins; hit all four cross combinations + cgsecret_addr = 0; cgsecret_cmd = 0; cg_inst.sample(); // a0 x rd + cgsecret_addr = 1; cgsecret_cmd = 1; cg_inst.sample(); // a1 x wr + cgsecret_addr = 0; cgsecret_cmd = 1; cg_inst.sample(); // a0 x wr + cgsecret_addr = 1; cgsecret_cmd = 0; cg_inst.sample(); // a1 x rd + `checkr(cg_inst.get_inst_coverage(), 100.0); // 8/8 + + // cg_rng_inst: 2 + 2 + 4 = 8 bins; hit all four cross combinations + cgsecret_addr = 0; cgsecret_cmd = 0; cg_rng_inst.sample(); // lo x rd + cgsecret_addr = 2; cgsecret_cmd = 1; cg_rng_inst.sample(); // hi x wr + cgsecret_addr = 1; cgsecret_cmd = 1; cg_rng_inst.sample(); // lo x wr + cgsecret_addr = 3; cgsecret_cmd = 0; cg_rng_inst.sample(); // hi x rd + `checkr(cg_rng_inst.get_inst_coverage(), 100.0); // 8/8 + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index 17e059ede..0183d42f0 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -7,5 +7,7 @@ cg.cp_trans2.trans2: 1 cg.cp_trans2.trans3: 1 cg.cp_trans3.seq_a: 1 cg.cp_trans3.seq_b: 1 -cg_legacy.cp_mix.tr: 1 -cg_legacy.cp_mix.vals: 1 +cg_array.cp_mix.tr: 1 +cg_array.cp_mix.vals[0]: 1 +cg_array.cp_mix.vals[1]: 0 +cg_array.cp_mix.vals[2]: 0 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index f95561ed2..063a1dc60 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -39,17 +39,18 @@ module t; } endgroup - // Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin, - // so the legacy array codegen path (which converted coverpoints bypass) is still exercised. - covergroup cg_legacy; + // Coverpoint mixing a transition bin and a value-array bin: both route through the + // VlCoverpoint runtime (the value array reports indexed as vals[0]..vals[N-1], like every + // other array bin; the transition records into its own runtime bin). + covergroup cg_array; cp_mix: coverpoint state { bins tr = (0 => 1); - bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths) + bins vals[] = {2, [4:5]}; // discrete + range elements } endgroup cg cg_inst = new; - cg_legacy cg_legacy_inst = new; + cg_array cg_array_inst = new; initial begin // Drive sequence 0->1->2->3->4 which hits all bins @@ -66,11 +67,11 @@ module t; cg_inst.sample(); // 3=>4: seq_b done `checkr(cg_inst.get_inst_coverage(), 100.0); - // cg_legacy: exercise legacy array codegen (non-convertible coverpoint) - state = 0; cg_legacy_inst.sample(); - state = 1; cg_legacy_inst.sample(); // 0=>1: tr - state = 2; cg_legacy_inst.sample(); // vals - state = 3; cg_legacy_inst.sample(); // vals + // cg_array: exercise the mixed transition + value-array coverpoint + state = 0; cg_array_inst.sample(); + state = 1; cg_array_inst.sample(); // 0=>1: tr + state = 2; cg_array_inst.sample(); // vals + state = 3; cg_array_inst.sample(); // vals $write("*-* All Finished *-*\n"); $finish; diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 16b86a66e..be1774562 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -14,9 +14,9 @@ module t; %000001 logic [1:0] addr; --000000 point: type=toggle comment=addr[0]:0->1 hier=top.t +-000001 point: type=toggle comment=addr[0]:0->1 hier=top.t -000000 point: type=toggle comment=addr[0]:1->0 hier=top.t --000001 point: type=toggle comment=addr[1]:0->1 hier=top.t +-000000 point: type=toggle comment=addr[1]:0->1 hier=top.t -000000 point: type=toggle comment=addr[1]:1->0 hier=top.t %000001 logic cmd; -000001 point: type=toggle comment=cmd:0->1 hier=top.t @@ -27,6 +27,168 @@ %000001 logic parity; -000001 point: type=toggle comment=parity:0->1 hier=top.t -000000 point: type=toggle comment=parity:1->0 hier=top.t +%000001 logic [63:0] wide; +-000000 point: type=toggle comment=wide[0]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[0]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[10]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[10]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[11]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[11]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[12]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[12]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[13]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[13]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[14]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[14]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[15]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[15]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[16]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[16]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[17]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[17]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[18]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[18]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[19]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[19]:1->0 hier=top.t +-000001 point: type=toggle comment=wide[1]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[1]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[20]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[20]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[21]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[21]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[22]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[22]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[23]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[23]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[24]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[24]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[25]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[25]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[26]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[26]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[27]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[27]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[28]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[28]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[29]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[29]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[2]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[2]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[30]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[30]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[31]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[31]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[32]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[32]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[33]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[33]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[34]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[34]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[35]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[35]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[36]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[36]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[37]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[37]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[38]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[38]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[39]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[39]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[3]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[3]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[40]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[40]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[41]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[41]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[42]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[42]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[43]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[43]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[44]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[44]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[45]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[45]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[46]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[46]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[47]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[47]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[48]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[48]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[49]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[49]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[4]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[4]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[50]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[50]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[51]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[51]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[52]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[52]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[53]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[53]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[54]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[54]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[55]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[55]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[56]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[56]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[57]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[57]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[58]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[58]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[59]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[59]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[5]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[5]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[60]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[60]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[61]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[61]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[62]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[62]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[63]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[63]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[6]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[6]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[7]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[7]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[8]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[8]:1->0 hier=top.t +-000000 point: type=toggle comment=wide[9]:0->1 hier=top.t +-000000 point: type=toggle comment=wide[9]:1->0 hier=top.t +%000000 logic [3:0] state; +-000000 point: type=toggle comment=state[0]:0->1 hier=top.t +-000000 point: type=toggle comment=state[0]:1->0 hier=top.t +-000000 point: type=toggle comment=state[1]:0->1 hier=top.t +-000000 point: type=toggle comment=state[1]:1->0 hier=top.t +-000000 point: type=toggle comment=state[2]:0->1 hier=top.t +-000000 point: type=toggle comment=state[2]:1->0 hier=top.t +-000000 point: type=toggle comment=state[3]:0->1 hier=top.t +-000000 point: type=toggle comment=state[3]:1->0 hier=top.t +%000001 logic [3:0] val; +-000000 point: type=toggle comment=val[0]:0->1 hier=top.t +-000000 point: type=toggle comment=val[0]:1->0 hier=top.t +-000001 point: type=toggle comment=val[1]:0->1 hier=top.t +-000000 point: type=toggle comment=val[1]:1->0 hier=top.t +-000001 point: type=toggle comment=val[2]:0->1 hier=top.t +-000000 point: type=toggle comment=val[2]:1->0 hier=top.t +-000000 point: type=toggle comment=val[3]:0->1 hier=top.t +-000000 point: type=toggle comment=val[3]:1->0 hier=top.t + + // Ascending ("opposite-endian") bit ranges: bit 0 is the MSB. Bit ordering must + // not change coverage results: range bins operate on the sampled value, so an + // ascending-declared coverpoint produces identical hit counts to the usual + // descending declaration. + /* verilator lint_off ASCRANGE */ +%000001 logic [0:1] be_addr; +-000000 point: type=toggle comment=be_addr[0]:0->1 hier=top.t +-000000 point: type=toggle comment=be_addr[0]:1->0 hier=top.t +-000001 point: type=toggle comment=be_addr[1]:0->1 hier=top.t +-000000 point: type=toggle comment=be_addr[1]:1->0 hier=top.t +%000001 logic [0:0] be_cmd; +-000001 point: type=toggle comment=be_cmd[0]:0->1 hier=top.t +-000000 point: type=toggle comment=be_cmd[0]:1->0 hier=top.t + /* verilator lint_on ASCRANGE */ typedef struct packed {logic m_p; logic h_mode;} cfg_t; %000001 cfg_t s_cfg = '0; @@ -292,8 +454,8 @@ // cross: [a1, write] endgroup - // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted - // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the un-crossed + // coverpoint cp_solo with the cross and its crossed coverpoints. covergroup cg_mixed; %000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} -000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0 @@ -315,8 +477,8 @@ // cross: [addr1, write] endgroup - // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the - // legacy default-bin codegen path that converted coverpoints bypass. + // Crossed coverpoint that also has a default bin: exercises default-bin handling on a + // coverpoint that feeds a cross. covergroup cg_def_cross; %000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} -000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0 @@ -336,6 +498,239 @@ // cross: [a1, write] endgroup + // Crossed coverpoint with an array range bin (bins av[] = {[0:1]}): exercises the + // coverpoint hit-list sizing for array range elements feeding a cross. + covergroup cg_arr_range; +%000002 cp_addr: coverpoint addr {bins av[] = {[0 : 1]};} // -> av[0]=0, av[1]=1 +-000002 point: type=covergroup comment= hier=cg_arr_range.cp_addr.av[0] +-000002 point: type=covergroup comment= hier=cg_arr_range.cp_addr.av[1] +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_arr_range.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_arr_range.cp_cmd.write +%000001 ar: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_arr_range.ar.av[0]_x_read + // cross: [av[0], read] +-000001 point: type=covergroup comment= hier=cg_arr_range.ar.av[0]_x_write + // cross: [av[0], write] +-000001 point: type=covergroup comment= hier=cg_arr_range.ar.av[1]_x_read + // cross: [av[1], read] +-000001 point: type=covergroup comment= hier=cg_arr_range.ar.av[1]_x_write + // cross: [av[1], write] + endgroup + + // Crossed coverpoint with an array value bin (bins av[] = {0, 2}): array value elements. + covergroup cg_arr_vals; +%000002 cp_addr: coverpoint addr {bins av[] = {0, 2};} // -> av[0]=0, av[1]=2 +-000002 point: type=covergroup comment= hier=cg_arr_vals.cp_addr.av[0] +-000002 point: type=covergroup comment= hier=cg_arr_vals.cp_addr.av[1] +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_arr_vals.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_arr_vals.cp_cmd.write +%000001 av: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_arr_vals.av.av[0]_x_read + // cross: [av[0], read] +-000001 point: type=covergroup comment= hier=cg_arr_vals.av.av[0]_x_write + // cross: [av[0], write] +-000001 point: type=covergroup comment= hier=cg_arr_vals.av.av[1]_x_read + // cross: [av[1], read] +-000001 point: type=covergroup comment= hier=cg_arr_vals.av.av[1]_x_write + // cross: [av[1], write] + endgroup + + // Crossed coverpoint with a wildcard array bin (wildcard bins wb[] = {2'b0?}): the '0?' + // wildcard expands to addr values 0 and 1, each its own array element. + covergroup cg_wild_arr; +%000004 cp_addr: coverpoint addr {wildcard bins wb[] = {2'b0?};} +-000004 point: type=covergroup comment= hier=cg_wild_arr.cp_addr.wb +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_wild_arr.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_wild_arr.cp_cmd.write +%000002 wa: cross cp_addr, cp_cmd; +-000002 point: type=covergroup comment= hier=cg_wild_arr.wa.wb_x_read + // cross: [wb, read] +-000002 point: type=covergroup comment= hier=cg_wild_arr.wa.wb_x_write + // cross: [wb, write] + endgroup + + // Crossed coverpoint with a non-array wildcard bin (wildcard bins wb = {2'b0?}): single + // wildcard bin matching addr 0 or 1, feeding a cross. + covergroup cg_wild_solo; +%000002 cp_addr: coverpoint addr {wildcard bins wb = {2'b0?};} +-000002 point: type=covergroup comment= hier=cg_wild_solo.cp_addr.wb +%000001 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_wild_solo.cp_cmd.read +-000001 point: type=covergroup comment= hier=cg_wild_solo.cp_cmd.write +%000001 ws: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_wild_solo.ws.wb_x_read + // cross: [wb, read] +-000001 point: type=covergroup comment= hier=cg_wild_solo.ws.wb_x_write + // cross: [wb, write] + endgroup + + // Crossed coverpoint with a four-state literal in a non-wildcard array bin + // (bins av[] = {2'b0x}): LRM 1800-2023 19.5.4 permits 4-state values in a bin definition. + // The hit-list sizing cannot statically analyze a 4-state value, so it falls back to the + // safe slot count. Under Verilator's 2-state simulation the value matches addr=0. + covergroup cg_arr_4state; +%000002 cp_addr: coverpoint addr {bins av[] = {2'b0x};} +-000002 point: type=covergroup comment= hier=cg_arr_4state.cp_addr.av[0] +%000001 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_arr_4state.cp_cmd.read +-000001 point: type=covergroup comment= hier=cg_arr_4state.cp_cmd.write +%000001 a4: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_arr_4state.a4.av[0]_x_read + // cross: [av[0], read] +-000001 point: type=covergroup comment= hier=cg_arr_4state.a4.av[0]_x_write + // cross: [av[0], write] + endgroup + + // Crossed coverpoint with two *overlapping* Normal range bins (addr=1 is in both lo and + // hi): the hit-list sizing must report a bound > 1 (a single sample can fall in two bins), + // exercising the max-overlap computation in computeHitListBound. + covergroup cg_overlap; +%000003 cp_addr: coverpoint addr {bins lo = {[0 : 1]}; bins hi = {[1 : 2]};} // overlap at addr=1 +-000003 point: type=covergroup comment= hier=cg_overlap.cp_addr.lo +-000003 point: type=covergroup comment= hier=cg_overlap.cp_addr.hi +%000003 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000003 point: type=covergroup comment= hier=cg_overlap.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_overlap.cp_cmd.write +%000002 ov: cross cp_addr, cp_cmd; +-000002 point: type=covergroup comment= hier=cg_overlap.ov.hi_x_read + // cross: [hi, read] +-000001 point: type=covergroup comment= hier=cg_overlap.ov.hi_x_write + // cross: [hi, write] +-000002 point: type=covergroup comment= hier=cg_overlap.ov.lo_x_read + // cross: [lo, read] +-000001 point: type=covergroup comment= hier=cg_overlap.ov.lo_x_write + // cross: [lo, write] + endgroup + + // Crossed coverpoint whose sampled expression is wider than 64 bits: exercises the + // width>=64 max-value path in the hit-list sizing (where 1< hi bound): the bin matches no + // value, so the hit-list sizing rejects it (lo > hi) and falls back to the safe slot count. + // The 'inv' bin and its cross bins are therefore never hit (coverage stays at 40%). + covergroup cg_inv; +%000000 cp_addr: coverpoint addr {bins inv = {[3 : 0]};} // inverted -> never matches +-000000 point: type=covergroup comment= hier=cg_inv.cp_addr.inv +%000001 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_inv.cp_cmd.read +-000001 point: type=covergroup comment= hier=cg_inv.cp_cmd.write +%000000 iv: cross cp_addr, cp_cmd; +-000000 point: type=covergroup comment= hier=cg_inv.iv.inv_x_read + // cross: [inv, read] +-000000 point: type=covergroup comment= hier=cg_inv.iv.inv_x_write + // cross: [inv, write] + endgroup + + // Crossed coverpoint with *no* Normal bins (only ignore_bins): the cross has an empty bin + // product, so the hit-list sizing returns the safe bound of 1. Coverage is the cmd + // coverpoint alone (vacuously 100% once both cmd bins are hit). + covergroup cg_noNormal; +%000002 cp_addr: coverpoint addr {ignore_bins ig = {[0 : 3]};} // zero Normal bins +-000002 point: type=covergroup comment= hier=cg_noNormal.cp_addr.ig +%000001 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_noNormal.cp_cmd.read +-000001 point: type=covergroup comment= hier=cg_noNormal.cp_cmd.write + nn: cross cp_addr, cp_cmd; + endgroup + + // Cross of a *transition* coverpoint with a value coverpoint. Transition coverpoints route + // through the VlCoverpoint runtime (their completion appends to the hit list), so a cross + // can read them like any other coverpoint. + covergroup cg_trans; +%000001 cp_t: coverpoint state {bins t01 = (0 => 1);} // one Normal (transition) bin +-000001 point: type=covergroup comment= hier=cg_trans.cp_t.t01 +%000002 cp_v: coverpoint val {bins v5 = {5}; bins v6 = {6};} +-000002 point: type=covergroup comment= hier=cg_trans.cp_v.v5 +-000001 point: type=covergroup comment= hier=cg_trans.cp_v.v6 +%000001 tx: cross cp_t, cp_v; +-000001 point: type=covergroup comment= hier=cg_trans.tx.t01_x_v5 + // cross: [t01, v5] +-000000 point: type=covergroup comment= hier=cg_trans.tx.t01_x_v6 + // cross: [t01, v6] + endgroup + + // Range bins over an ascending-declared coverpoint, crossed with another: the + // range interval extraction must ignore bit endianness (mirror of cg_range). + covergroup cg_be; +%000002 cp_addr: coverpoint be_addr {bins lo = {[0 : 1]}; bins hi = {[2 : 3]};} +-000002 point: type=covergroup comment= hier=cg_be.cp_addr.lo +-000002 point: type=covergroup comment= hier=cg_be.cp_addr.hi +%000002 cp_cmd: coverpoint be_cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_be.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_be.cp_cmd.write +%000001 x: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_be.x.hi_x_read + // cross: [hi, read] +-000001 point: type=covergroup comment= hier=cg_be.x.hi_x_write + // cross: [hi, write] +-000001 point: type=covergroup comment= hier=cg_be.x.lo_x_read + // cross: [lo, read] +-000001 point: type=covergroup comment= hier=cg_be.x.lo_x_write + // cross: [lo, write] + endgroup + + // Array range bin over an ascending-declared coverpoint: {[0:1]} enumerates to + // av[0]=0, av[1]=1, exercising array-range slot enumeration under opposite + // endian (mirror of cg_arr_range). + covergroup cg_be_arr; +%000002 cp_addr: coverpoint be_addr {bins av[] = {[0 : 1]};} +-000002 point: type=covergroup comment= hier=cg_be_arr.cp_addr.av[0] +-000002 point: type=covergroup comment= hier=cg_be_arr.cp_addr.av[1] +%000002 cp_cmd: coverpoint be_cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_be_arr.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_be_arr.cp_cmd.write +%000001 ax: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_be_arr.ax.av[0]_x_read + // cross: [av[0], read] +-000001 point: type=covergroup comment= hier=cg_be_arr.ax.av[0]_x_write + // cross: [av[0], write] +-000001 point: type=covergroup comment= hier=cg_be_arr.ax.av[1]_x_read + // cross: [av[1], read] +-000001 point: type=covergroup comment= hier=cg_be_arr.ax.av[1]_x_write + // cross: [av[1], write] + endgroup + %000001 cg2 cg2_inst = new; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore cg_ignore_inst = new; @@ -360,6 +755,32 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_def_cross cg_def_cross_inst = new; -000001 point: type=line comment=block hier=top.t +%000001 cg_arr_range cg_arr_range_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_arr_vals cg_arr_vals_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_wild_arr cg_wild_arr_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_wild_solo cg_wild_solo_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_arr_4state cg_arr_4state_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_overlap cg_overlap_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_wide cg_wide_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_openrange cg_openrange_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_inv cg_inv_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_noNormal cg_noNormal_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_trans cg_trans_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_be cg_be_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_be_arr cg_be_arr_inst = new; +-000001 point: type=line comment=block hier=top.t %000001 initial begin -000001 point: type=line comment=block hier=top.t @@ -737,6 +1158,198 @@ %000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write -000001 point: type=line comment=block hier=top.t + // Sample cg_arr_range: array range bin {[0:1]} -> av[0]=0, av[1]=1; cross 2x2 + // cg_arr_range: 2+2+4=8 bins; sample all combinations -> 100% +%000001 addr = 0; cmd = 0; cg_arr_range_inst.sample(); // av[0] x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_arr_range_inst.sample(); // av[0] x write +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; cg_arr_range_inst.sample(); // av[1] x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; cg_arr_range_inst.sample(); // av[1] x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_arr_range_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_arr_vals: array value bin {0,2} -> av[0]=0, av[1]=2; cross 2x2 + // cg_arr_vals: 2+2+4=8 bins; sample all combinations -> 100% +%000001 addr = 0; cmd = 0; cg_arr_vals_inst.sample(); // av[0] x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_arr_vals_inst.sample(); // av[0] x write +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 0; cg_arr_vals_inst.sample(); // av[1] x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_arr_vals_inst.sample(); // av[1] x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_arr_vals_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_wild_arr: wildcard array {2'b0?} matches addr 0,1; cross 2x2 + // cg_wild_arr: 2+2+4=8 bins; sample all combinations -> 100% +%000001 addr = 0; cmd = 0; cg_wild_arr_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_wild_arr_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; cg_wild_arr_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; cg_wild_arr_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_wild_arr_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_wild_solo: single wildcard bin {2'b0?} matches addr 0,1; cross 1x2 + // cg_wild_solo: 1+2+2=5 bins; sample both cmd values -> 100% +%000001 addr = 0; cmd = 0; cg_wild_solo_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_wild_solo_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_wild_solo_inst.get_inst_coverage(), 100.0); // 5/5 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_arr_4state: 4-state literal bin {2'b0x} matches addr=0 (2-state sim); cross 1x2 + // cg_arr_4state: 1+2+2=5 bins; sample both cmd values -> 100% +%000001 addr = 0; cmd = 0; cg_arr_4state_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_arr_4state_inst.sample(); +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_arr_4state_inst.get_inst_coverage(), 100.0); // 5/5 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_overlap: overlapping range bins lo={0,1}, hi={1,2}; cross 2x2 + // cg_overlap: 2+2+4=8 bins; cover lo/hi via addr 0 and 2, plus addr=1 double-hits both +%000001 addr = 0; cmd = 0; cg_overlap_inst.sample(); // lo x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_overlap_inst.sample(); // lo x write +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 0; cg_overlap_inst.sample(); // hi x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_overlap_inst.sample(); // hi x write +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; cg_overlap_inst.sample(); // addr=1 in both lo and hi (hit-list bound 2) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_overlap_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_wide: 64-bit coverpoint, lo={0,1}, hi={2,3}; cross 2x2 + // cg_wide: 2+2+4=8 bins; sample all combinations -> 100% +%000001 wide = 0; cmd = 0; cg_wide_inst.sample(); // lo x read +-000001 point: type=line comment=block hier=top.t +%000001 wide = 0; cmd = 1; cg_wide_inst.sample(); // lo x write +-000001 point: type=line comment=block hier=top.t +%000001 wide = 2; cmd = 0; cg_wide_inst.sample(); // hi x read +-000001 point: type=line comment=block hier=top.t +%000001 wide = 2; cmd = 1; cg_wide_inst.sample(); // hi x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_wide_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_openrange: lo=[$:1] matches 0,1; hi=[2:$] matches 2,3; cross 2x2 + // cg_openrange: 2+2+4=8 bins; sample all combinations -> 100% +%000001 addr = 0; cmd = 0; cg_openrange_inst.sample(); // lo x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 1; cg_openrange_inst.sample(); // lo x write +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 0; cg_openrange_inst.sample(); // hi x read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_openrange_inst.sample(); // hi x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_openrange_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_inv: inverted range bin never matches; only cmd bins are hittable + // cg_inv: 1+2+2=5 bins; inv and its 2 cross bins never hit -> 2/5=40% +%000001 addr = 0; cmd = 0; cg_inv_inst.sample(); // read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; cg_inv_inst.sample(); // write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_inv_inst.get_inst_coverage(), 40.0); // 2/5: read + write only +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_noNormal: coverpoint has no Normal bins; cross product is empty + // cg_noNormal: 0+2+0=2 bins (cmd only); both hit -> 100% +%000001 addr = 0; cmd = 0; cg_noNormal_inst.sample(); // read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; cg_noNormal_inst.sample(); // write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_noNormal_inst.get_inst_coverage(), 100.0); // 2/2 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_trans: transition coverpoint crossed with a value coverpoint + // cg_trans: 1+2+2=5 bins; t01_x_v6 never completes -> 4/5=80% + // __Vprev_cp_t initializes to 0. +%000001 state = 0; val = 5; cg_trans_inst.sample(); // prev=0,cur=0: no t01; v5 +-000001 point: type=line comment=block hier=top.t +%000001 state = 1; val = 5; cg_trans_inst.sample(); // prev=0,cur=1: t01 completes; t01_x_v5 +-000001 point: type=line comment=block hier=top.t +%000001 state = 0; val = 6; cg_trans_inst.sample(); // prev=1,cur=0: no t01; v6 (no cross) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_trans_inst.get_inst_coverage(), 80.0); // 4/5: t01_x_v6 not hit +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_be: range bins over an ascending-declared coverpoint; cross 2x2 + // cg_be: 2+2+4=8 bins; endianness must not change results (mirror of cg_range) +%000001 be_addr = 0; be_cmd = 0; cg_be_inst.sample(); // lo x read +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_be_inst.get_inst_coverage(), 37.5); // 3/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 be_addr = 2; be_cmd = 1; cg_be_inst.sample(); // hi x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_be_inst.get_inst_coverage(), 75.0); // 6/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 be_addr = 1; be_cmd = 1; cg_be_inst.sample(); // lo x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_be_inst.get_inst_coverage(), 87.5); // 7/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 be_addr = 3; be_cmd = 0; cg_be_inst.sample(); // hi x read +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_be_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_be_arr: array range bin over an ascending-declared coverpoint; cross 2x2 + // cg_be_arr: 2+2+4=8 bins; sample all combinations -> 100% +%000001 be_addr = 0; be_cmd = 0; cg_be_arr_inst.sample(); // av[0] x read +-000001 point: type=line comment=block hier=top.t +%000001 be_addr = 0; be_cmd = 1; cg_be_arr_inst.sample(); // av[0] x write +-000001 point: type=line comment=block hier=top.t +%000001 be_addr = 1; be_cmd = 0; cg_be_arr_inst.sample(); // av[1] x read +-000001 point: type=line comment=block hier=top.t +%000001 be_addr = 1; be_cmd = 1; cg_be_arr_inst.sample(); // av[1] x write +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_be_arr_inst.get_inst_coverage(), 100.0); // 8/8 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish; diff --git a/test_regress/t/t_vlcov_covergroup.py b/test_regress/t/t_vlcov_covergroup.py index 05f12efa0..a3103fb9e 100755 --- a/test_regress/t/t_vlcov_covergroup.py +++ b/test_regress/t/t_vlcov_covergroup.py @@ -14,7 +14,8 @@ test.scenarios('vlt') test.top_filename = "t/t_covergroup_cross.v" -test.compile(verilator_flags2=['--coverage', '--Wno-COVERIGN']) +# --Wno-ASCRANGE: cg_be / cg_be_arr in the shared top intentionally declare ascending [0:N] +test.compile(verilator_flags2=['--coverage', '--Wno-COVERIGN', '--Wno-ASCRANGE']) test.execute()