diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h index 519fd214c..529fe6695 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: @@ -53,9 +57,9 @@ public: // 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]") + // 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(int i) const = 0; - virtual VlCovBinKind binKind(int 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 6dc7006f1..2b6c0c30d 100644 --- a/include/verilated_covergroup.cpp +++ b/include/verilated_covergroup.cpp @@ -29,13 +29,28 @@ void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) { m_atLeast = atLeast; m_total = nBins; m_counts.assign(nBins, 0); + m_crossIdx.assign(nBins, -1); } void VlCoverpoint::addNamer(VlCovBinKind set, int 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 its cross index (Normal-only re-indexing); ignore/ + // illegal/default bins keep -1 and never enter a cross. + for (int b = m_nextBase; b < m_nextBase + count; ++b) m_crossIdx[b] = m_nextCrossIdx++; + m_normal += count; + } m_nextBase += count; - if (set == VlCovBinKind::KIND_NORMAL) m_normal += count; +} + +std::string VlCoverpoint::normalBinName(int crossIdx) const { + // Map a cross (Normal-only) index back to its full bin index, then build its name. + // Called only at cross registration, so a linear scan is acceptable. + for (int i = 0; i < m_total; ++i) { + if (m_crossIdx[i] == crossIdx) return binName(i); + } + VL_UNREACHABLE; // LCOV_EXCL_LINE } const VlCovNamer& VlCoverpoint::namerFor(int i) const { @@ -44,7 +59,7 @@ const VlCovNamer& VlCoverpoint::namerFor(int i) const { 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 { @@ -76,3 +91,81 @@ void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* pa } } } + +//============================================================================= +// VlCoverCross + +void VlCoverCross::init(const char* hier, int 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); + m_numAutoBins = 1; + for (int d = 0; d < dims; ++d) { + m_cpBinCounts[d] = cps[d]->normalBinCount(); + m_numAutoBins *= m_cpBinCounts[d]; + } + // stride[d] = product of the Normal bin counts of all dimensions after d. + m_stride.assign(dims, 1); + for (int d = dims - 2; d >= 0; --d) m_stride[d] = m_stride[d + 1] * m_cpBinCounts[d + 1]; + m_flatCounts.assign(m_numAutoBins, 0); +} + +void VlCoverCross::iterateProduct(VlCoverpoint* const* cps, int dim, int64_t baseIdx) { + const int hits = cps[dim]->hitCount(); + const int* const list = cps[dim]->hitList(); + const bool last = (dim == m_dims - 1); + const int64_t stride = m_stride[dim]; + for (int hit = 0; hit < hits; ++hit) { + const int64_t idx = baseIdx + static_cast(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 (int d = 0; d < m_dims; ++d) { + if (cps[d]->hitCount() == 0) return; + } + iterateProduct(cps, 0, 0); +} + +std::string VlCoverCross::binName(int flat) const { + // Built on demand by concatenating each coverpoint's own bin name. + std::string name; + for (int d = 0; d < m_dims; ++d) { + const int crossIdx = static_cast((flat / m_stride[d]) % m_cpBinCounts[d]); + if (d > 0) name += "_x_"; + name += m_cps[d]->normalBinName(crossIdx); + } + return name; +} + +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 (int 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 (int d = 0; d < m_dims; ++d) { + const int crossIdx = static_cast((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()); + } +} diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h index 1ffc98ff7..5e6b49ed6 100644 --- a/include/verilated_covergroup.h +++ b/include/verilated_covergroup.h @@ -87,16 +87,25 @@ 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 + int m_nextCrossIdx = 0; // running cross-index cursor (Normal bins only) std::vector m_counts; // [m_total], one per bin std::vector m_namers; // appended in declaration order + std::vector + m_crossIdx; // [m_total] full bin idx -> cross idx (Normal-only), -1 otherwise + int 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, @@ -119,13 +128,26 @@ public: 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(int 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) ---- + int hitCount() const { return m_hitCount; } + virtual const int* hitList() const = 0; // provided by VlCoverpointT + int normalBinCount() const { return m_normal; } // cross dimension size (Normal bins) + std::string normalBinName(int 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(); } + // 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(int 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. @@ -141,4 +163,84 @@ 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 + int 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(int i) { + ++m_counts[i]; + const int cx = m_crossIdx[i]; + if (cx >= 0 && m_hitCount < MaxHits) m_hits[m_hitCount++] = cx; + } + const int* 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 + int m_dims = 0; // number of feeding coverpoints + int64_t m_numAutoBins = 0; // product of per-dim Normal bin counts + int 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, int dim, int64_t baseIdx); + void incrementTuple(int64_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, int 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). + int binCount() const override { return static_cast(m_numAutoBins); } + std::string binName(int flat) const override; + void coverageParts(double& covered, double& total) const override { + covered = m_numCovered; + total = static_cast(m_numAutoBins); + } +}; + #endif // Guard diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index b1890e001..9d54e374a 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -50,55 +50,41 @@ 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 - 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()); } } @@ -108,8 +94,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: @@ -117,12 +104,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 @@ -408,26 +390,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()) { @@ -437,21 +399,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 @@ -518,102 +465,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). @@ -636,18 +492,153 @@ 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). + struct RangeBounds final { + AstConst* lc = nullptr; // low-bound const (null iff loUnb) + AstConst* hc = nullptr; // high-bound const (null iff hiUnb) + bool loUnb = false; // low bound is '$' + bool hiUnb = false; // high bound is '$' + }; + + // 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 flagged (loUnb/hiUnb), 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.loUnb = VN_IS(irp->lhsp(), Unbounded); + rb.hiUnb = VN_IS(irp->rhsp(), Unbounded); + rb.lc = VN_CAST(irp->lhsp(), Const); + rb.hc = VN_CAST(irp->rhsp(), Const); + if ((!rb.lc && !rb.loUnb) || (!rb.hc && !rb.hiUnb)) return false; + if ((rb.lc && rb.lc->num().isFourState()) || (rb.hc && rb.hc->num().isFourState())) + return false; + return true; + } + if (AstConst* const cp = VN_CAST(rp, Const)) { + if (cp->num().isFourState()) return false; + rb.lc = rb.hc = cp; + return true; + } + return false; + } + + // 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.loUnb ? 0 : rb.lc->toUQuad(); + const uint64_t hi = rb.hiUnb ? maxVal : rb.hc->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()) { + ++slotCount; + std::vector> ivs; + if (cbinp->isWildcard() || !extractRangeIntervals(cbinp, maxVal, ivs)) return false; + bins.push_back(std::move(ivs)); + return true; + } + // Array 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. + bool exact = true; + for (AstNode* rp = cbinp->rangesp(); rp; rp = rp->nextp()) { + RangeBounds rb; + if (!constRangeBounds(rp, rb) || rb.loUnb || rb.hiUnb) { + ++slotCount; + exact = false; + } else if (rb.lc == rb.hc) { // single Const element (lc/hc alias the same node) + ++slotCount; + bins.push_back({{rb.lc->toUQuad(), rb.lc->toUQuad()}}); + } else { // [lo:hi] range: one single-value slot per enumerated value + for (int64_t v = rb.lc->toSInt(); v <= rb.hc->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}; @@ -703,8 +694,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; @@ -729,40 +724,81 @@ class FunctionalCoverageVisitor final : public VNVisitor { } // 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. @@ -775,12 +811,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}); } @@ -799,6 +853,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); } + // 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) const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); AstCStmt* const initp = new AstCStmt{fl}; @@ -816,48 +881,10 @@ class FunctionalCoverageVisitor final : public VNVisitor { } } - // 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"); @@ -875,7 +902,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); } @@ -896,7 +923,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) { @@ -913,9 +940,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) { @@ -983,13 +1009,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) { @@ -1084,77 +1103,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"); @@ -1188,162 +1139,121 @@ 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. + const std::string hier = m_covergroupp->name() + "." + crossp->name(); + const std::string initCall = ".init(\"" + hier + "\", " + std::to_string(dims) + + ", __Vcx_cps, \"" + std::string{fl->filename()} + "\", " + + std::to_string(fl->lineno()) + ", " + + std::to_string(fl->firstColumn()) + ");"; + m_constructorp->addStmtsp(makeCrossCpsCall(fl, cpVars, cxVarp, initCall)); + if (v3Global.opt.coverage()) { + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cxVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" + + m_covergroupp->name() + "\");"); + 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->prettyName() + << "' 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) { @@ -1408,6 +1318,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)}; } @@ -1452,6 +1365,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}; } @@ -1468,15 +1384,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) @@ -1497,198 +1405,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..2f5e10a83 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 @@ -71,7 +92,7 @@ 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 +112,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 +127,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 +154,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 +175,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..794478ec8 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -16,6 +16,9 @@ module t; logic cmd; logic mode; logic parity; + logic [63:0] wide; + logic [3:0] state; + logic [3:0] val; typedef struct packed {logic m_p; logic h_mode;} cfg_t; cfg_t s_cfg = '0; @@ -108,8 +111,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 +120,108 @@ 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 + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -137,6 +234,17 @@ 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; initial begin // Sample 2-way: hit all 4 combinations @@ -320,6 +428,87 @@ 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 + $write("*-* All Finished *-*\n"); $finish; end 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..21134e6cf 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,153 @@ %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 typedef struct packed {logic m_p; logic h_mode;} cfg_t; %000001 cfg_t s_cfg = '0; @@ -292,8 +439,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 +462,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 +483,198 @@ // 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 + %000001 cg2 cg2_inst = new; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore cg_ignore_inst = new; @@ -360,6 +699,28 @@ -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 initial begin -000001 point: type=line comment=block hier=top.t @@ -737,6 +1098,156 @@ %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 + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish;