diff --git a/src/V3Active.cpp b/src/V3Active.cpp index 386fa931c..d6798e5a3 100644 --- a/src/V3Active.cpp +++ b/src/V3Active.cpp @@ -702,11 +702,11 @@ class CovergroupInjectVisitor final : public VNVisitor { void visit(AstVarScope* nodep) override { // Get the underlying var AstVar* const varp = nodep->varp(); - if (!varp) return; // LCOV_EXCL_BR_LINE -- AstVarScope always has non-null varp + UASSERT_OBJ(varp, nodep, "AstVarScope must have non-null varp"); // Check if the variable is of covergroup class type const AstNodeDType* const dtypep = varp->dtypep(); - if (!dtypep) return; // LCOV_EXCL_BR_LINE -- typed vars always have non-null dtypep + UASSERT_OBJ(dtypep, nodep, "AstVar must have non-null dtypep after V3Width"); const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType); if (!classRefp) return; @@ -754,7 +754,7 @@ class CovergroupInjectVisitor final : public VNVisitor { activep->addStmtsp( new AstAlways{fl, VAlwaysKwd::ALWAYS_FF, nullptr, cmethodCallp->makeStmt()}); - UINFO(4, " Added automatic sample() call for covergroup " << varp->name()); + UINFO(4, " Added automatic sample() call for covergroup " << varp->name()); // LCOV_EXCL_BR_LINE } void visit(AstActive*) override {} // Don't iterate into actives diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index de9d72500..e9f8e5f8b 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1080,7 +1080,8 @@ public: , m_name{name} , m_type{type} , m_isArray{isArrayBin} { - if (transp) addTransp(transp); + UASSERT(transp, "AstCoverBin transition constructor requires non-null transp"); + addTransp(transp); } ASTGEN_MEMBERS_AstCoverBin; void dump(std::ostream& str) const override; @@ -1122,7 +1123,6 @@ public: ASTGEN_MEMBERS_AstCoverTransItem; void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; - VTransRepType repType() const { return m_repType; } }; class AstCoverTransSet final : public AstNode { // Represents a transition set: value1 => value2 => value3 @@ -1163,7 +1163,6 @@ public: }; class AstCoverpointRef final : public AstNode { // Reference to a coverpoint used in a cross - // @astgen ptr := m_coverpointp : Optional[AstCoverpoint] const string m_name; // coverpoint name public: @@ -1174,8 +1173,6 @@ public: void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; string name() const override VL_MT_STABLE { return m_name; } - AstCoverpoint* coverpointp() const { return m_coverpointp; } - void coverpointp(AstCoverpoint* nodep) { m_coverpointp = nodep; } }; class AstDefParam final : public AstNode { // A defparam assignment @@ -2700,7 +2697,8 @@ class AstCoverCross final : public AstNodeFuncCovItem { public: AstCoverCross(FileLine* fl, const string& name, AstCoverpointRef* itemsp) : ASTGEN_SUPER_CoverCross(fl, name) { - if (itemsp) addItemsp(itemsp); + UASSERT(itemsp, "AstCoverCross requires at least one coverpoint reference"); + addItemsp(itemsp); } ASTGEN_MEMBERS_AstCoverCross; void dump(std::ostream& str) const override; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 0132c47d9..0c1a6e3d6 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -69,7 +69,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { 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 && bi.crossp && bi.binp) pushDeletep(bi.binp); + if (!bi.coverpointp) pushDeletep(bi.binp); } m_binInfos.clear(); } @@ -137,21 +137,25 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Calculate range division const int width = exprp->width(); const uint64_t maxVal = (width >= 64) ? UINT64_MAX : ((1ULL << width) - 1); - const uint64_t binSize = (maxVal + 1) / numBins; + // For width >= 64: (maxVal+1) would overflow; compute binSize without overflow + const uint64_t binSize + = (width < 64) ? ((maxVal + 1) / numBins) : (UINT64_MAX / numBins + 1); UINFO(4, " Width=" << width << " maxVal=" << maxVal << " numBins=" << numBins << " binSize=" << binSize); // Create expanded bins for (int i = 0; i < numBins; i++) { - const uint64_t lo = i * binSize; + const uint64_t lo = static_cast(i) * binSize; const uint64_t hi = (i == numBins - 1) ? maxVal : ((i + 1) * binSize - 1); - // Create constants for range - AstConst* const loConstp - = new AstConst{cbinp->fileline(), V3Number(cbinp->fileline(), width, lo)}; - AstConst* const hiConstp - = new AstConst{cbinp->fileline(), V3Number(cbinp->fileline(), width, hi)}; + // Create constants for range (use setQuad to handle values > 32-bit) + V3Number loNum{cbinp->fileline(), width, 0}; + loNum.setQuad(lo); + AstConst* const loConstp = new AstConst{cbinp->fileline(), loNum}; + V3Number hiNum{cbinp->fileline(), width, 0}; + hiNum.setQuad(hi); + AstConst* const hiConstp = new AstConst{cbinp->fileline(), hiNum}; // Create InsideRange [lo:hi] AstInsideRange* const rangep @@ -190,14 +194,12 @@ class FunctionalCoverageVisitor final : public VNVisitor { atLeastOut = 1; autoBinMaxOut = -1; // -1 = not set at coverpoint level for (AstNode* optionp = coverpointp->optionsp(); optionp; optionp = optionp->nextp()) { - if (AstCoverOption* optp = VN_CAST(optionp, CoverOption)) { - if (AstConst* constp = VN_CAST(optp->valuep(), Const)) { - if (optp->optionType() == VCoverOptionType::AT_LEAST) { - atLeastOut = constp->toSInt(); - } else if (optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) { - autoBinMaxOut = constp->toSInt(); - } - } + AstCoverOption* const optp = VN_AS(optionp, CoverOption); + AstConst* const constp = VN_AS(optp->valuep(), Const); + if (optp->optionType() == VCoverOptionType::AT_LEAST) { + atLeastOut = constp->toSInt(); + } else if (optp->optionType() == VCoverOptionType::AUTO_BIN_MAX) { + autoBinMaxOut = constp->toSInt(); } } // Fall back to covergroup-level auto_bin_max if not set at coverpoint level @@ -220,13 +222,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { } else if (AstInsideRange* rangep = VN_CAST(np, InsideRange)) { AstNodeExpr* const lhsp = V3Const::constifyEdit(rangep->lhsp()); AstNodeExpr* const rhsp = V3Const::constifyEdit(rangep->rhsp()); - AstConst* const loConstp = VN_CAST(lhsp, Const); - AstConst* const hiConstp = VN_CAST(rhsp, Const); - if (loConstp && hiConstp) { - const uint64_t lo = loConstp->toUQuad(); - const uint64_t hi = hiConstp->toUQuad(); - for (uint64_t v = lo; v <= hi; v++) values.insert(v); - } + AstConst* const loConstp = VN_AS(lhsp, Const); + AstConst* const hiConstp = VN_AS(rhsp, Const); + const uint64_t lo = loConstp->toUQuad(); + const uint64_t hi = hiConstp->toUQuad(); + for (uint64_t v = lo; v <= hi; v++) values.insert(v); + } else { + np->v3error("Non-constant expression in bin value list; values must be constants"); } } } @@ -536,8 +538,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstIf* const ifp = new AstIf{binp->fileline(), fullCondp, stmtp, nullptr}; UINFO(4, " Adding bin match if statement to sample function"); - if (!m_sampleFuncp) // LCOV_EXCL_BR_LINE - binp->v3fatalSrc("m_sampleFuncp is null when trying to add bin match code"); // LCOV_EXCL_LINE + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set when generating bin match code"); m_sampleFuncp->addStmtsp(ifp); UINFO(4, " Successfully added if statement for bin: " << binp->name()); } @@ -563,7 +564,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Build condition for this bin AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); - if (!binCondp) continue; + UASSERT_OBJ(binCondp, cbinp, "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); // OR with previous conditions if (anyBinMatchp) { @@ -594,7 +595,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create if statement AstIf* const ifp = new AstIf{defBinp->fileline(), defaultCondp, stmtp, nullptr}; - if (!m_sampleFuncp) defBinp->v3fatalSrc("m_sampleFuncp is null for default bin"); + 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"); } @@ -701,28 +702,24 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Check if current value matches first item (restart condition) AstNodeExpr* restartCondp = buildTransitionItemCondition(items[0], exprp); - if (restartCondp) { - // Apply iff condition - if (AstNodeExpr* iffp = coverpointp->iffp()) { - restartCondp = new AstAnd{fl, iffp->cloneTree(false), restartCondp}; - } - - // Restart to state 1 - AstNodeStmt* restartActionp - = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, - new AstConst{fl, AstConst::WidthedValue{}, 8, 1}}; - - // Reset to state 0 (else branch) - AstNodeStmt* resetActionp - = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, - new AstConst{fl, AstConst::WidthedValue{}, 8, 0}}; - - noMatchActionp = new AstIf{fl, restartCondp, restartActionp, resetActionp}; - } else { - // Can't build restart condition, just reset - noMatchActionp = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, - new AstConst{fl, AstConst::WidthedValue{}, 8, 0}}; + UASSERT_OBJ(restartCondp, items[0], + "buildTransitionItemCondition returned nullptr for restart"); + // Apply iff condition + if (AstNodeExpr* iffp = coverpointp->iffp()) { + restartCondp = new AstAnd{fl, iffp->cloneTree(false), restartCondp}; } + + // Restart to state 1 + AstNodeStmt* restartActionp + = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, 1}}; + + // Reset to state 0 (else branch) + AstNodeStmt* resetActionp + = new AstAssign{fl, new AstVarRef{fl, stateVarp, VAccess::WRITE}, + new AstConst{fl, AstConst::WidthedValue{}, 8, 0}}; + + noMatchActionp = new AstIf{fl, restartCondp, restartActionp, resetActionp}; } // For state 0, no action needed if no match (stay in state 0) @@ -753,35 +750,52 @@ class FunctionalCoverageVisitor final : public VNVisitor { 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) { + if (constp->width() == targetWidth) return constp->cloneTree(false); + V3Number num{fl, targetWidth, 0}; + num.opAssign(constp->num()); + return new AstConst{fl, num}; + } + // Build a range condition: minp <= exprp <= maxp. // Uses signed comparisons if exprp is signed; omits trivially-true bounds for unsigned. // All arguments are non-owning; clones exprp/minp/maxp as needed. AstNodeExpr* makeRangeCondition(FileLine* fl, AstNodeExpr* exprp, AstNodeExpr* minp, AstNodeExpr* maxp) { + const int exprWidth = exprp->widthMin(); + AstConst* const minConstp = VN_AS(minp, Const); + AstConst* const maxConstp = VN_AS(maxp, Const); + // Widen constants to match expression width so post-V3Width nodes use correct macros + AstConst* const minWidep = widenConst(fl, minConstp, exprWidth); + AstConst* const maxWidep = widenConst(fl, maxConstp, exprWidth); if (exprp->isSigned()) { - return new AstAnd{fl, new AstGteS{fl, exprp->cloneTree(false), minp->cloneTree(false)}, - new AstLteS{fl, exprp->cloneTree(false), maxp->cloneTree(false)}}; + return new AstAnd{fl, + new AstGteS{fl, exprp->cloneTree(false), minWidep}, + new AstLteS{fl, exprp->cloneTree(false), maxWidep}}; } // Unsigned: skip bounds that are trivially satisfied for the expression width - AstConst* const minConstp = VN_CAST(minp, Const); - AstConst* const maxConstp = VN_CAST(maxp, Const); - const int exprWidth = exprp->widthMin(); - bool skipLowerCheck = (minConstp && minConstp->toUQuad() == 0); + const bool skipLowerCheck = (minConstp->toUQuad() == 0); bool skipUpperCheck = false; - if (maxConstp && exprWidth > 0 && exprWidth <= 64) { + if (exprWidth <= 64) { const uint64_t maxVal = (exprWidth == 64) ? ~static_cast(0) : ((1ULL << exprWidth) - 1ULL); skipUpperCheck = (maxConstp->toUQuad() == maxVal); } if (skipLowerCheck && skipUpperCheck) { + VL_DO_DANGLING(pushDeletep(minWidep), minWidep); + VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep); return new AstConst{fl, AstConst::BitTrue{}}; } else if (skipLowerCheck) { - return new AstLte{fl, exprp->cloneTree(false), maxp->cloneTree(false)}; + VL_DO_DANGLING(pushDeletep(minWidep), minWidep); + return new AstLte{fl, exprp->cloneTree(false), maxWidep}; } else if (skipUpperCheck) { - return new AstGte{fl, exprp->cloneTree(false), minp->cloneTree(false)}; + VL_DO_DANGLING(pushDeletep(maxWidep), maxWidep); + return new AstGte{fl, exprp->cloneTree(false), minWidep}; } else { - return new AstAnd{fl, new AstGte{fl, exprp->cloneTree(false), minp->cloneTree(false)}, - new AstLte{fl, exprp->cloneTree(false), maxp->cloneTree(false)}}; + return new AstAnd{fl, new AstGte{fl, exprp->cloneTree(false), minWidep}, + new AstLte{fl, exprp->cloneTree(false), maxWidep}}; } } @@ -803,12 +817,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { for (AstNode* valp = itemp->valuesp(); valp; valp = valp->nextp()) { AstNodeExpr* singleCondp = nullptr; - if (AstConst* constp = VN_CAST(valp, Const)) { - singleCondp = new AstEq{constp->fileline(), exprp->cloneTree(false), - constp->cloneTree(false)}; - } else { - valp->v3fatalSrc("Unexpected node type in transition item: " << valp->typeName()); - } + AstConst* const constp = VN_AS(valp, Const); + singleCondp = new AstEq{constp->fileline(), exprp->cloneTree(false), + constp->cloneTree(false)}; if (condp) { condp = new AstOr{itemp->fileline(), condp, singleCondp}; @@ -835,7 +846,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstNodeExpr* const maxp = V3Const::constifyEdit(insideRangep->rhsp()); AstConst* const minConstp = VN_CAST(minp, Const); AstConst* const maxConstp = VN_CAST(maxp, Const); - if (minConstp && maxConstp) { + if (minConstp && maxConstp) { // LCOV_EXCL_BR_LINE const int minVal = minConstp->toSInt(); const int maxVal = maxConstp->toSInt(); UINFO(6, " Expanding InsideRange [" << minVal << ":" << maxVal << "]" @@ -907,7 +918,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create if statement AstIf* const ifp = new AstIf{binp->fileline(), condp, stmtp, nullptr}; - if (!m_sampleFuncp) binp->v3fatalSrc("m_sampleFuncp is null for array bin"); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set when generating array bin code"); m_sampleFuncp->addStmtsp(ifp); } @@ -919,11 +930,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Extract all transition sets std::vector transSets; - for (AstNode* transSetp = arrayBinp->transp(); transSetp; transSetp = transSetp->nextp()) { - if (AstCoverTransSet* ts = VN_CAST(transSetp, CoverTransSet)) { - transSets.push_back(ts); - } - } + 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; @@ -962,36 +970,18 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Get or create previous value variable AstVar* const prevVarp = createPrevValueVar(coverpointp, exprp); - if (!transSetp) { // LCOV_EXCL_BR_LINE - binp->v3error("Transition bin without transition set"); // LCOV_EXCL_LINE - return; // LCOV_EXCL_LINE - } + UASSERT_OBJ(transSetp, binp, "Transition bin has no transition set (transp() was checked before calling this)"); // Get transition items (the sequence: item1 => item2 => item3) std::vector items; - for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp()) { - if (AstCoverTransItem* transp = VN_CAST(itemp, CoverTransItem)) { - items.push_back(transp); - } - } + for (AstNode* itemp = transSetp->itemsp(); itemp; itemp = itemp->nextp()) + items.push_back(VN_AS(itemp, CoverTransItem)); if (items.empty()) { binp->v3error("Transition set without items"); return; } - // Check for unsupported repetition operators - // Note: the grammar handles [*], [->], [=] at parse time via COVERIGN warning, - // resulting in null AstCoverTransItem nodes which are filtered out above. - // This check is therefore unreachable from normal SV parsing. - for (AstCoverTransItem* item : items) { // LCOV_EXCL_START - if (item->repType() != VTransRepType::NONE) { - binp->v3warn(E_UNSUPPORTED, - "Transition repetition operators ([*], [->], [=]) not yet supported"); - return; - } - } // LCOV_EXCL_STOP - if (items.size() == 1) { // Single item transition not valid (need at least 2 values for =>) binp->v3error("Transition requires at least two values"); @@ -1127,28 +1117,26 @@ class FunctionalCoverageVisitor final : public VNVisitor { AstNode* itemp = crossp->itemsp(); while (itemp) { AstNode* const nextp = itemp->nextp(); - AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef); - if (refp) { - // 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; + AstCoverpointRef* const refp = VN_AS(itemp, CoverpointRef); + // 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 likely a direct variable - // reference (implicit coverpoint). Silently ignore; cross is dropped. - UINFO(4, " Ignoring cross with implicit variable reference: " << refp->name() - ); - return; - } - - coverpointRefs.push_back(foundCpp); - - // Delete the reference node - it's no longer needed - VL_DO_DANGLING(pushDeletep(refp->unlinkFrBack()), refp); + if (!foundCpp) { + // Name not found as an explicit coverpoint - it's likely a direct variable + // reference (implicit coverpoint). Silently ignore; cross is dropped. + UINFO(4, " Ignoring cross with implicit variable reference: " << refp->name() + ); + 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"); @@ -1187,9 +1175,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { if (AstInsideRange* irp = VN_CAST(currRangep, InsideRange)) { AstNodeExpr* const minExprp = irp->lhsp(); AstNodeExpr* const maxExprp = irp->rhsp(); - AstConst* const minConstp = VN_CAST(minExprp, Const); - AstConst* const maxConstp = VN_CAST(maxExprp, Const); - if (minConstp && maxConstp && minConstp->toSInt() == maxConstp->toSInt()) { + AstConst* const minConstp = VN_AS(minExprp, Const); + AstConst* const maxConstp = VN_AS(maxExprp, Const); + if (minConstp->toUQuad() == maxConstp->toUQuad()) { // Single value if (isWildcard) { rangeCondp = buildWildcardCondition(binp, exprp, minConstp); @@ -1207,12 +1195,14 @@ class FunctionalCoverageVisitor final : public VNVisitor { rangeCondp = new AstEq{binp->fileline(), exprp->cloneTree(false), constp->cloneTree(false)}; } + } else { + currRangep->v3error("Non-constant expression in bin range; values must be constants"); + return nullptr; } - if (rangeCondp) { - fullCondp - = fullCondp ? new AstOr{binp->fileline(), fullCondp, rangeCondp} : rangeCondp; - } + UASSERT_OBJ(rangeCondp, binp, "rangeCondp is null after building range condition"); + fullCondp + = fullCondp ? new AstOr{binp->fileline(), fullCondp, rangeCondp} : rangeCondp; } return fullCondp; @@ -1400,20 +1390,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Coverpoint bin: use coverpoint name or generate from expression std::string cpName = coverpointp->name(); if (cpName.empty()) { - // Generate name from expression - if (coverpointp->exprp()) { - cpName = coverpointp->exprp()->name(); - if (cpName.empty()) cpName = "cp"; - } else { - cpName = "cp"; - } + // Unlabeled coverpoint: name comes from its expression (always a VarRef) + UASSERT_OBJ(coverpointp->exprp(), coverpointp, + "Coverpoint without expression and without name"); + cpName = coverpointp->exprp()->name(); + UASSERT_OBJ(!cpName.empty(), coverpointp, + "Coverpoint expression has empty name"); } hierName += "." + cpName; - } else if (crossp) { - // Cross bin: use cross name - std::string crossName = crossp->name(); - if (crossName.empty()) crossName = "cross"; - hierName += "." + crossName; + } else { + // Cross bin: grammar always provides a name (user label or auto "__crossN") + hierName += "." + crossp->name(); } hierName += "." + binName; @@ -1486,36 +1473,35 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Store the event in the global map for V3Active to retrieve later // V3LinkParse only creates this sentinel AstCovergroup node when a clocking // event exists, so cgp->eventp() is always non-null here. - if (cgp->eventp()) { // LCOV_EXCL_BR_LINE - // Check if the clocking event references a member variable (unsupported) - // Clocking events should be on signals/nets, not class members - bool eventUnsupported = false; - for (AstNode* senp = cgp->eventp()->sensesp(); senp; - senp = senp->nextp()) { - if (AstSenItem* const senItemp = VN_CAST(senp, SenItem)) { // LCOV_EXCL_BR_LINE - if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE - = VN_CAST(senItemp->sensp(), VarRef)) { - if (varrefp->varp()->isClassMember()) { - cgp->v3warn(COVERIGN, - "Unsupported: 'covergroup' clocking event " - "on member variable"); - eventUnsupported = true; - hasUnsupportedEvent = true; - break; - } - } + UASSERT_OBJ(cgp->eventp(), cgp, + "Sentinel AstCovergroup in class must have non-null eventp"); + // Check if the clocking event references a member variable (unsupported) + // Clocking events should be on signals/nets, not class members + bool eventUnsupported = false; + for (AstNode* senp = cgp->eventp()->sensesp(); senp; + senp = senp->nextp()) { + AstSenItem* const senItemp = VN_AS(senp, SenItem); + if (AstVarRef* const varrefp // LCOV_EXCL_BR_LINE + = VN_CAST(senItemp->sensp(), VarRef)) { + if (varrefp->varp()->isClassMember()) { + cgp->v3warn(COVERIGN, + "Unsupported: 'covergroup' clocking event " + "on member variable"); + eventUnsupported = true; + hasUnsupportedEvent = true; + break; } } + } - if (!eventUnsupported) { - // Leave cgp in the class membersp so the SenTree stays - // linked in the AST. V3Active will find it via membersp, - // use the event, then delete the AstCovergroup itself. - UINFO(4, "Keeping covergroup event node for V3Active: " - << nodep->name()); - itemp = nextp; - continue; - } + if (!eventUnsupported) { + // Leave cgp in the class membersp so the SenTree stays + // linked in the AST. V3Active will find it via membersp, + // use the event, then delete the AstCovergroup itself. + UINFO(4, "Keeping covergroup event node for V3Active: " + << nodep->name()); + itemp = nextp; + continue; } // Remove the AstCovergroup node - either unsupported event or no event VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp); diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index c6b2a65f1..6b7c3f9e4 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -342,7 +342,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { if (setp != nodep->transp()) puts(", "); iterateConst(setp); } - } else if (nodep->rangesp()) { + } else if (nodep->rangesp()) { // LCOV_EXCL_BR_LINE - false: CoverBin always has transp/rangesp/default puts(" = {"); for (AstNode* rangep = nodep->rangesp(); rangep; rangep = rangep->nextp()) { if (rangep != nodep->rangesp()) puts(", "); @@ -1014,7 +1014,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst { } } void visit(AstClassRefDType* nodep) override { - putfs(nodep, nodep->classp() ? EmitCUtil::prefixNameProtect(nodep->classp()) + putfs(nodep, nodep->classp() ? EmitCUtil::prefixNameProtect(nodep->classp()) // LCOV_EXCL_BR_LINE - false: classp always set after linking : nodep->prettyDTypeName(false)); } void visit(AstRequireDType* nodep) override { iterateConst(nodep->lhsp()); } diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 5e0bcd57f..75d40e459 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1130,36 +1130,36 @@ class LinkParseVisitor final : public VNVisitor { // Handle constructor arguments - add function parameters and assignments if (argsp) { - // Find the 'new' function to add parameters to + // Find the 'new' function to add parameters to. + // At this point the only AstFunc in the class is the "new" constructor + // created just above; other members are AstVar or AstCovergroup sentinel. AstFunc* newFuncp = nullptr; for (AstNode* memberp = nodep->membersp(); memberp; memberp = memberp->nextp()) { if (AstFunc* const funcp = VN_CAST(memberp, Func)) { - if (funcp->name() == "new") { - newFuncp = funcp; - break; - } + UASSERT_OBJ(funcp->name() == "new", funcp, + "Unexpected non-new function in covergroup class during arg setup"); + newFuncp = funcp; + break; } } - if (newFuncp) { - // Save the existing body statements and unlink them - AstNode* const existingBodyp = newFuncp->stmtsp(); - if (existingBodyp) existingBodyp->unlinkFrBackWithNext(); - // Add function parameters and assignments - for (AstNode* argp = argsp; argp; argp = argp->nextp()) { - if (AstVar* const origVarp = VN_CAST(argp, Var)) { - AstVar* const paramp = origVarp->cloneTree(false); - paramp->funcLocal(true); - paramp->direction(VDirection::INPUT); - newFuncp->addStmtsp(paramp); - AstNodeExpr* const lhsp - = new AstParseRef{origVarp->fileline(), origVarp->name()}; - AstNodeExpr* const rhsp - = new AstParseRef{paramp->fileline(), paramp->name()}; - newFuncp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp}); - } - } - if (existingBodyp) newFuncp->addStmtsp(existingBodyp); + UASSERT_OBJ(newFuncp, nodep, "Covergroup class must have a 'new' constructor function"); + // Save the existing body statements and unlink them + AstNode* const existingBodyp = newFuncp->stmtsp(); + if (existingBodyp) existingBodyp->unlinkFrBackWithNext(); + // Add function parameters and assignments + for (AstNode* argp = argsp; argp; argp = argp->nextp()) { + AstVar* const origVarp = VN_AS(argp, Var); + AstVar* const paramp = origVarp->cloneTree(false); + paramp->funcLocal(true); + paramp->direction(VDirection::INPUT); + newFuncp->addStmtsp(paramp); + AstNodeExpr* const lhsp + = new AstParseRef{origVarp->fileline(), origVarp->name()}; + AstNodeExpr* const rhsp + = new AstParseRef{paramp->fileline(), paramp->name()}; + newFuncp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp}); } + if (existingBodyp) newFuncp->addStmtsp(existingBodyp); } // IEEE: option / type_option members allow external access (cg_inst.option.X) @@ -1189,17 +1189,16 @@ class LinkParseVisitor final : public VNVisitor { AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr}; if (sampleArgsp) { for (AstNode* argp = sampleArgsp; argp; argp = argp->nextp()) { - if (AstVar* const origVarp = VN_CAST(argp, Var)) { - AstVar* const paramp = origVarp->cloneTree(false); - paramp->funcLocal(true); - paramp->direction(VDirection::INPUT); - funcp->addStmtsp(paramp); - AstNodeExpr* const lhsp - = new AstParseRef{origVarp->fileline(), origVarp->name()}; - AstNodeExpr* const rhsp - = new AstParseRef{paramp->fileline(), paramp->name()}; - funcp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp}); - } + AstVar* const origVarp = VN_AS(argp, Var); + AstVar* const paramp = origVarp->cloneTree(false); + paramp->funcLocal(true); + paramp->direction(VDirection::INPUT); + funcp->addStmtsp(paramp); + AstNodeExpr* const lhsp + = new AstParseRef{origVarp->fileline(), origVarp->name()}; + AstNodeExpr* const rhsp + = new AstParseRef{paramp->fileline(), paramp->name()}; + funcp->addStmtsp(new AstAssign{origVarp->fileline(), lhsp, rhsp}); } } funcp->classMethod(true); @@ -1283,24 +1282,22 @@ class LinkParseVisitor final : public VNVisitor { // Convert constructor args to member variables for (AstNode* argp = nodep->argsp(); argp; argp = argp->nextp()) { - if (AstVar* const origVarp = VN_CAST(argp, Var)) { - AstVar* const memberp = origVarp->cloneTree(false); - memberp->varType(VVarType::MEMBER); - memberp->funcLocal(false); - memberp->direction(VDirection::NONE); - cgClassp->addMembersp(memberp); - } + AstVar* const origVarp = VN_AS(argp, Var); + AstVar* const memberp = origVarp->cloneTree(false); + memberp->varType(VVarType::MEMBER); + memberp->funcLocal(false); + memberp->direction(VDirection::NONE); + cgClassp->addMembersp(memberp); } // Convert sample args to member variables for (AstNode* argp = nodep->sampleArgsp(); argp; argp = argp->nextp()) { - if (AstVar* const origVarp = VN_CAST(argp, Var)) { - AstVar* const memberp = origVarp->cloneTree(false); - memberp->varType(VVarType::MEMBER); - memberp->funcLocal(false); - memberp->direction(VDirection::NONE); - cgClassp->addMembersp(memberp); - } + AstVar* const origVarp = VN_AS(argp, Var); + AstVar* const memberp = origVarp->cloneTree(false); + memberp->varType(VVarType::MEMBER); + memberp->funcLocal(false); + memberp->direction(VDirection::NONE); + cgClassp->addMembersp(memberp); } // Create the constructor; detach membersp (coverage body) and use as its body diff --git a/src/V3Timing.cpp b/src/V3Timing.cpp index e8e99a512..e8fec01cd 100644 --- a/src/V3Timing.cpp +++ b/src/V3Timing.cpp @@ -343,18 +343,9 @@ class TimingSuspendableVisitor final : public VNVisitor { } } void visit(AstNodeCCall* nodep) override { - AstCFunc* funcp = nodep->funcp(); - if (!funcp) { // LCOV_EXCL_BR_LINE -- AstNodeCCall always has non-null funcp - iterateChildren(nodep); - return; - } - - // Skip if we're not inside a function/procedure (m_procp would be null) - // This can happen for calls in Active nodes at module scope - if (!m_procp) { // LCOV_EXCL_BR_LINE -- m_procp is always set when CCall is inside a function - iterateChildren(nodep); - return; - } + AstCFunc* const funcp = nodep->funcp(); + UASSERT_OBJ(funcp, nodep, "AstNodeCCall must have non-null funcp post-link"); + UASSERT_OBJ(m_procp, nodep, "AstNodeCCall must be inside a procedure/CFunc/Begin"); UINFO(9, "V3Timing: Processing CCall to " << funcp->name() << " in dependency graph\n"); new V3GraphEdge{&m_suspGraph, getSuspendDepVtx(funcp), getSuspendDepVtx(m_procp), P_CALL}; diff --git a/test_regress/t/t_covergroup_args.v b/test_regress/t/t_covergroup_args.v index c6bbe9633..dfcf5fd4c 100644 --- a/test_regress/t/t_covergroup_args.v +++ b/test_regress/t/t_covergroup_args.v @@ -4,16 +4,23 @@ // SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 +// A plain (non-covergroup) class — exercises the non-covergroup class scope/varscope paths +class PlainClass; + int x; +endclass + // verilator lint_off COVERIGN module t; + int i, j; + covergroup cg(int var1, int var2 = 42); + cp1: coverpoint i; // Non-empty body with args: exercises constructor-body path endgroup cg cov1 = new(69, 77); cg cov2 = new(69); - - int i, j; + PlainClass plain_inst = new; // Non-covergroup class instance: exercises early-return paths function void x(); cov1.set_inst_name("the_inst_name"); diff --git a/test_regress/t/t_covergroup_autobins_bad.out b/test_regress/t/t_covergroup_autobins_bad.out index 4c77ffbfa..c9926061a 100644 --- a/test_regress/t/t_covergroup_autobins_bad.out +++ b/test_regress/t/t_covergroup_autobins_bad.out @@ -7,4 +7,24 @@ : ... note: In instance 't' 24 | bins auto[0]; | ^~~~ +%Error: t/t_covergroup_autobins_bad.v:34:26: Non-constant expression in bin value list; values must be constants + : ... note: In instance 't' + 34 | ignore_bins ign = {size_var}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:31:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 31 | bins b[] = {[size_var:size_var]}; + | ^ +%Error: t/t_covergroup_autobins_bad.v:32:12: Non-constant expression in array bins range; range bounds must be constants + : ... note: In instance 't' + 32 | bins b_mixed[] = {[0:size_var]}; + | ^~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:33:18: Non-constant expression in bin range; values must be constants + : ... note: In instance 't' + 33 | bins b2 = {size_var}; + | ^~~~~~~~ +%Error: t/t_covergroup_autobins_bad.v:34:26: Non-constant expression in bin range; values must be constants + : ... note: In instance 't' + 34 | ignore_bins ign = {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 768754b02..b0e38f429 100644 --- a/test_regress/t/t_covergroup_autobins_bad.v +++ b/test_regress/t/t_covergroup_autobins_bad.v @@ -25,8 +25,19 @@ module t; } endgroup + // Error: non-constant value in bin ranges + covergroup cg3; + cp1: coverpoint cp_expr { + bins b[] = {[size_var:size_var]}; // non-constant array bins range (both bounds non-const) + bins b_mixed[] = {[0:size_var]}; // non-constant array bins range (max bound non-const) + bins b2 = {size_var}; // non-constant simple bin value + ignore_bins ign = {size_var}; // non-constant ignore_bins value + } + endgroup + cg1 cg1_inst = new; cg2 cg2_inst = new; + cg3 cg3_inst = new; initial $finish; endmodule diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 536ac7ca5..0a1052f79 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -57,6 +57,15 @@ cg5.cp_addr.addr0: 1 cg5.cp_addr.addr1: 1 cg5.cp_cmd.read: 1 cg5.cp_cmd.write: 1 +cg_ignore.cp_addr.a0: 2 +cg_ignore.cp_addr.a1: 2 +cg_ignore.cp_addr.ign [ignore]: 1 +cg_ignore.cp_cmd.read: 3 +cg_ignore.cp_cmd.write: 2 +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_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 @@ -65,3 +74,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_unnamed_cross.__cross7.a0_x_read [cross]: 1 +cg_unnamed_cross.__cross7.a0_x_write [cross]: 0 +cg_unnamed_cross.__cross7.a1_x_read [cross]: 0 +cg_unnamed_cross.__cross7.a1_x_write [cross]: 1 +cg_unnamed_cross.cp_a.a0: 1 +cg_unnamed_cross.cp_a.a1: 1 +cg_unnamed_cross.cp_c.read: 1 +cg_unnamed_cross.cp_c.write: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 20063da01..bab587ff1 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -92,11 +92,35 @@ module t; addr_cmd_range: cross cp_addr, cp_cmd; endgroup + // Cross where one coverpoint has ignore_bins: exercises BINS_USER FALSE branch + // in collectBins during cross code generation (L1139) + covergroup cg_ignore; + cp_addr: coverpoint addr { + ignore_bins ign = {3}; // BINS_IGNORE: not BINS_USER, exercises L1139 FALSE path + bins a0 = {0}; + bins a1 = {1}; + } + cp_cmd: coverpoint cmd { + bins read = {0}; + bins write = {1}; + } + cross_ab: cross cp_addr, cp_cmd; + endgroup + + // Covergroup with unnamed cross: exercises crossName.empty() fallback to "cross" (L1395) + covergroup cg_unnamed_cross; + cp_a: coverpoint addr { bins a0 = {0}; bins a1 = {1}; } + cp_c: coverpoint cmd { bins read = {0}; bins write = {1}; } + cross cp_a, cp_c; // no label -> crossName is empty + endgroup + cg2 cg2_inst = new; + cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; cg3 cg3_inst = new; cg4 cg4_inst = new; cg5 cg5_inst = new; + cg_unnamed_cross cg_unnamed_cross_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -121,12 +145,23 @@ module t; addr = 0; cmd = 0; cg5_inst.sample(); addr = 1; cmd = 1; cg5_inst.sample(); + // Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it + addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read + addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write + addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write + addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read + addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins) + // Sample range-bin cross addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read + // Sample cg_unnamed_cross: exercises unnamed cross (crossName fallback to "cross") + addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read + addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index ec7fde6f9..188f4ae58 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -2,3 +2,19 @@ cg.data.high: 1 cg.data.low: 1 cg.data.other: 2 cg2.cp_only_default.all: 4 +cg3.data.bad [ignore]: 1 +cg3.data.err [illegal]: 0 +cg3.data.normal: 2 +cg3.data.other: 2 +cg4.cp_idx.auto_0: 1 +cg4.cp_idx.auto_1: 1 +cg4.cp_idx.auto_2: 1 +cg4.cp_idx.skip [ignore]: 0 +cg5.cp_data64.auto[0]: 2 +cg5.cp_data64.auto[1]: 0 +cg5.cp_data64.auto[2]: 0 +cg5.cp_data64.auto[3]: 0 +cg6.cp_data65.hi: 1 +cg6.cp_data65.lo: 1 +cg7.data.hi: 1 +cg7.data.lo: 1 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index 9b221e56a..b2142d417 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -6,8 +6,18 @@ // Test default bins - catch-all for values not in other bins +// Non-covergroup class: exercises V3Active isCovergroup()=false branch +class DataHelper; + bit [7:0] val; + function new(bit [7:0] v); val = v; endfunction +endclass + module t; bit [7:0] data; + logic [1:0] idx; + logic [63:0] data64; // 64-bit: exercises width>=64 auto-bin path (L139) + logic [64:0] data65; // 65-bit: exercises exprWidth>64 in makeRangeCondition + DataHelper helper; // Module-level class var: exercises V3Active isCovergroup()=false covergroup cg; coverpoint data { @@ -24,12 +34,59 @@ module t; } endgroup + // Covergroup with default + ignore + illegal bins: exercises BINS_IGNORE/BINS_ILLEGAL + // skip paths in generateDefaultBinMatchCode (L558-L559) + covergroup cg3; + coverpoint data { + ignore_bins bad = {255}; // BINS_IGNORE skip path + illegal_bins err = {254}; // BINS_ILLEGAL skip path + bins normal = {[1:10]}; + bins other = default; + } + endgroup + + // Covergroup with auto-bins + ignore_bins on small range: exercises L295 excluded-value continue + // When numValidValues <= auto_bin_max, single-value auto-bins are created per value; the + // excluded.find() check at L295 fires for the ignore_bins value (idx=2). + covergroup cg4; + cp_idx: coverpoint idx { + ignore_bins skip = {2}; // value 2 excluded; auto-bins created for 0,1,3 + } + endgroup + + // 64-bit signal with 4 auto-bins: exercises width>=64 branch in auto-bin range calculation + covergroup cg5; + cp_data64: coverpoint data64 { bins auto[4]; } + endgroup + + // 65-bit signal with range bins: exercises exprWidth>64 path in makeRangeCondition + covergroup cg6; + cp_data65: coverpoint data65 { bins lo = {[0:15]}; bins hi = {[100:200]}; } + endgroup + + // Unlabeled coverpoint: exercises cpName fallback via exprp()->name() (L1394-1398) + covergroup cg7; + coverpoint data { bins lo = {[0:7]}; bins hi = {[8:15]}; } + endgroup + initial begin cg cg_inst; cg2 cg2_inst; + cg3 cg3_inst; + cg4 cg4_inst; + cg5 cg5_inst; + cg6 cg6_inst; + cg7 cg7_inst; cg_inst = new(); cg2_inst = new(); + cg3_inst = new(); + cg4_inst = new(); + cg5_inst = new(); + cg6_inst = new(); + cg7_inst = new(); + helper = new(8'h42); + data = helper.val; // Use helper to avoid optimization // Hit low bin data = 2; @@ -51,6 +108,31 @@ module t; cg_inst.sample(); cg2_inst.sample(); + // Sample cg3: exercises BINS_IGNORE/BINS_ILLEGAL skip in default-bin detection loop + data = 2; cg3_inst.sample(); // hits normal bin + data = 7; cg3_inst.sample(); // hits normal bin again + data = 255; cg3_inst.sample(); // ignore_bins (not counted) + // note: do not sample 254 (illegal_bins would cause runtime assertion) + data = 100; cg3_inst.sample(); // hits default (other) bin + + // Sample cg4: exercises auto-bin generation with excluded value (L295) + // idx=2 is in ignore_bins, so auto-bins cover 0,1,3 only + idx = 0; cg4_inst.sample(); + idx = 1; cg4_inst.sample(); + idx = 3; cg4_inst.sample(); + + // Sample cg5: 64-bit signal, sample into bin b[0] (value 0 is in first quarter) + data64 = 0; cg5_inst.sample(); + data64 = 5; cg5_inst.sample(); + + // Sample cg6: 65-bit signal with range bins + data65 = 5; cg6_inst.sample(); // hits bin lo=[0:15] + data65 = 150; cg6_inst.sample(); // hits bin hi=[100:200] + + // Sample cg7: unlabeled coverpoint (exercises exprp()->name() path) + data = 3; cg7_inst.sample(); // hits bin lo + data = 10; cg7_inst.sample(); // hits bin hi + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_only_ignore_illegal.out b/test_regress/t/t_covergroup_only_ignore_illegal.out new file mode 100644 index 000000000..a99435df2 --- /dev/null +++ b/test_regress/t/t_covergroup_only_ignore_illegal.out @@ -0,0 +1,2 @@ +cg.cp.ign [ignore]: 2 +cg.cp.ill [illegal]: 0 diff --git a/test_regress/t/t_covergroup_only_ignore_illegal.py b/test_regress/t/t_covergroup_only_ignore_illegal.py new file mode 100644 index 000000000..9add21d5e --- /dev/null +++ b/test_regress/t/t_covergroup_only_ignore_illegal.py @@ -0,0 +1,17 @@ +# DESCRIPTION: Verilator: Verilog Test driver for Verilog simulator +# +# Copyright 2026 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') +test.compile(verilator_flags2=['--coverage']) +test.execute() +test.covergroup_coverage_report() +test.files_identical(test.obj_dir + "/covergroup_report.txt", test.golden_filename) +test.passes() diff --git a/test_regress/t/t_covergroup_only_ignore_illegal.v b/test_regress/t/t_covergroup_only_ignore_illegal.v new file mode 100644 index 000000000..60af952a1 --- /dev/null +++ b/test_regress/t/t_covergroup_only_ignore_illegal.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// Test covergroup where all bins are ignore_bins or illegal_bins (no regular +// bins). This exercises the totalBins==0 path in generateCoverageMethodBody() +// which returns 100.0 immediately. +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by Wilson Snyder. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +module t; + logic [1:0] data; + + covergroup cg; + cp: coverpoint data { + ignore_bins ign = {0, 1}; + illegal_bins ill = {2, 3}; + // No regular bins -> totalBins == 0 -> get_coverage returns 100.0 + } + endgroup + + initial begin + automatic cg cg_inst = new; + + // Only sample values that fall in ignore_bins, never illegal_bins + data = 0; cg_inst.sample(); + data = 1; cg_inst.sample(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_unsup.out b/test_regress/t/t_covergroup_unsup.out index 27e9927a4..20a1a50fd 100644 --- a/test_regress/t/t_covergroup_unsup.out +++ b/test_regress/t/t_covergroup_unsup.out @@ -237,8 +237,8 @@ %Warning-COVERIGN: t/t_covergroup_unsup.v:198:7: Unsupported: explicit coverage cross bins 198 | illegal_bins lib_cross = binsof(a); | ^~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_covergroup_unsup.v:220:5: Unsupported: covergroup inheritance (extends) - 220 | covergroup extends cg_empty; +%Error-UNSUPPORTED: t/t_covergroup_unsup.v:223:5: Unsupported: covergroup inheritance (extends) + 223 | covergroup extends cg_empty; | ^~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_unsup.v b/test_regress/t/t_covergroup_unsup.v index 15d237213..2f18f4cae 100644 --- a/test_regress/t/t_covergroup_unsup.v +++ b/test_regress/t/t_covergroup_unsup.v @@ -207,8 +207,11 @@ module t ( int m_y; int m_z; covergroup cov1 @m_z; - coverpoint m_x; - coverpoint m_y; + cp_x: coverpoint m_x; + cp_y: coverpoint m_y; +`ifdef T_COVERGROUP_UNSUP_IGN + xy_cross: cross cp_x, cp_y; // exercises cross cleanup in hasUnsupportedEvent path +`endif endgroup `ifndef T_COVERGROUP_UNSUP_IGN function new(); cov1 = new; endfunction diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index 06c432778..5a5c3a01f 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -676,10 +676,15 @@ module Vt_debug_emitv_t; cp_sig: coverpoint cg_sig { bins low = {['sh0:'sh3]}; bins high = {['sh4:'sh6]}; + bins multi = {'sh0, 'sh1, 'sh2}; bins dflt = default; ignore_bins ign = {'sh7}; illegal_bins ill = {'sh5}; }; + cp_options: coverpoint cg_sig2 { + + ???? // COVEROPTION + 'sh2}; endfunction int signed __Vint; struct { @@ -784,6 +789,7 @@ module Vt_debug_emitv_t; cp_t: coverpoint cg_sig { bins t01 = (3'h0 => 3'h1); bins t12 = (3'h1 => 3'h2); + bins talt = (3'h2 => 3'h3), (3'h4 => 3'h5); }; endfunction int signed __Vint; diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index 3d2dd6607..219ed41c6 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -358,10 +358,15 @@ module t (/*AUTOARG*/ cp_sig: coverpoint cg_sig { bins low = {[0:3]}; bins high = {[4:6]}; + bins multi = {0, 1, 2}; // multiple values in one bins (exercises EmitV range loop) bins dflt = default; ignore_bins ign = {7}; illegal_bins ill = {5}; } + // Coverpoint with per-coverpoint option but no explicit bins + cp_options: coverpoint cg_sig2 { + option.at_least = 2; + } endgroup // Covergroup with clocking event @@ -372,8 +377,9 @@ module t (/*AUTOARG*/ // Covergroup with transition bins covergroup cg_trans; cp_t: coverpoint cg_sig { - bins t01 = (3'b000 => 3'b001); - bins t12 = (3'b001 => 3'b010); + bins t01 = (3'b000 => 3'b001); + bins t12 = (3'b001 => 3'b010); + bins talt = (3'b010 => 3'b011), (3'b100 => 3'b101); // multiple transition sets } endgroup diff --git a/test_regress/t/t_dump.v b/test_regress/t/t_dump.v index d24004b1d..7c9c98652 100644 --- a/test_regress/t/t_dump.v +++ b/test_regress/t/t_dump.v @@ -151,4 +151,14 @@ module Test(/*AUTOARG*/ endcase end + logic [1:0] cg_v1; + logic [1:0] cg_v2; + covergroup cg @(posedge clk); + option.at_least = 2; + cp1: coverpoint cg_v1 { option.weight = 1; bins lo = {0}; } + cp2: coverpoint cg_v2; + cx: cross cp1, cp2; + endgroup + cg cg_inst = new; + endmodule diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 831347177..bdba7118c 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -9,7 +9,7 @@ module t; %000001 logic [1:0] addr; -%000000 logic cmd; +%000001 logic cmd; %000001 logic mode; %000001 logic parity; @@ -93,11 +93,35 @@ %000001 addr_cmd_range: cross cp_addr, cp_cmd; endgroup + // Cross where one coverpoint has ignore_bins: exercises BINS_USER FALSE branch + // in collectBins during cross code generation (L1139) +%000005 covergroup cg_ignore; +%000001 cp_addr: coverpoint addr { +%000001 ignore_bins ign = {3}; // BINS_IGNORE: not BINS_USER, exercises L1139 FALSE path +%000002 bins a0 = {0}; +%000002 bins a1 = {1}; + } +%000001 cp_cmd: coverpoint cmd { +%000003 bins read = {0}; +%000002 bins write = {1}; + } +%000001 cross_ab: cross cp_addr, cp_cmd; + endgroup + + // Covergroup with unnamed cross: exercises crossName.empty() fallback to "cross" (L1395) +%000002 covergroup cg_unnamed_cross; +%000001 cp_a: coverpoint addr { bins a0 = {0}; bins a1 = {1}; } +%000001 cp_c: coverpoint cmd { bins read = {0}; bins write = {1}; } +%000001 cross cp_a, cp_c; // no label -> crossName is empty + endgroup + %000001 cg2 cg2_inst = new; +%000001 cg_ignore cg_ignore_inst = new; %000001 cg_range cg_range_inst = new; %000001 cg3 cg3_inst = new; %000001 cg4 cg4_inst = new; %000001 cg5 cg5_inst = new; +%000001 cg_unnamed_cross cg_unnamed_cross_inst = new; %000001 initial begin // Sample 2-way: hit all 4 combinations @@ -122,12 +146,23 @@ %000001 addr = 0; cmd = 0; cg5_inst.sample(); %000001 addr = 1; cmd = 1; cg5_inst.sample(); + // Sample cg_ignore: addr=3 is in ignore_bins so no cross bins for it +%000001 addr = 0; cmd = 0; cg_ignore_inst.sample(); // a0 x read +%000001 addr = 1; cmd = 1; cg_ignore_inst.sample(); // a1 x write +%000001 addr = 0; cmd = 1; cg_ignore_inst.sample(); // a0 x write +%000001 addr = 1; cmd = 0; cg_ignore_inst.sample(); // a1 x read +%000001 addr = 3; cmd = 0; cg_ignore_inst.sample(); // ignored (addr=3 in ignore_bins) + // Sample range-bin cross %000001 addr = 0; cmd = 0; cg_range_inst.sample(); // lo_range x read %000001 addr = 2; cmd = 1; cg_range_inst.sample(); // hi_range x write %000001 addr = 1; cmd = 1; cg_range_inst.sample(); // lo_range x write %000001 addr = 3; cmd = 0; cg_range_inst.sample(); // hi_range x read + // Sample cg_unnamed_cross: exercises unnamed cross (crossName fallback to "cross") +%000001 addr = 0; cmd = 0; cg_unnamed_cross_inst.sample(); // a0 x read +%000001 addr = 1; cmd = 1; cg_unnamed_cross_inst.sample(); // a1 x write + %000001 $write("*-* All Finished *-*\n"); %000001 $finish; end diff --git a/test_regress/t/t_vlcov_covergroup.out b/test_regress/t/t_vlcov_covergroup.out index 86d8314f4..776bd7b5f 100644 --- a/test_regress/t/t_vlcov_covergroup.out +++ b/test_regress/t/t_vlcov_covergroup.out @@ -1,7 +1,8 @@ COVERGROUP COVERAGE REPORT ========================== -TOTAL: 45/67 bins covered (67.16%) +TOTAL: 59/83 bins covered (71.08%) + (1 ignored, 0 illegal) ------------------------------------------------------------------------------ Covergroup Type: cg2 [t/t_covergroup_cross.v:18] @@ -138,6 +139,31 @@ Covergroup Type: cg5 [t/t_covergroup_cross.v:70] ZERO addr1_x_read 0 hits COVERED addr1_x_write 1 hits +------------------------------------------------------------------------------ +Covergroup Type: cg_ignore [t/t_covergroup_cross.v:100] + Type Coverage: 8/8 bins (100.00%) + + Coverpoint: cp_addr + Coverage: 2/2 bins (100.00%) + Bins: + COVERED a0 2 hits + COVERED a1 2 hits + IGNORE ign 1 hits + + Coverpoint: cp_cmd + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 3 hits + COVERED write 2 hits + + Cross: cross_ab + Coverage: 4/4 bins (100.00%) + Bins: + COVERED a0_x_read 1 hits + COVERED a0_x_write 1 hits + COVERED a1_x_read 1 hits + COVERED a1_x_write 1 hits + ------------------------------------------------------------------------------ Covergroup Type: cg_range [t/t_covergroup_cross.v:85] Type Coverage: 8/8 bins (100.00%) @@ -163,3 +189,27 @@ Covergroup Type: cg_range [t/t_covergroup_cross.v:85] COVERED lo_range_x_write 1 hits ------------------------------------------------------------------------------ +Covergroup Type: cg_unnamed_cross [t/t_covergroup_cross.v:112] + Type Coverage: 6/8 bins (75.00%) + + Coverpoint: cp_a + Coverage: 2/2 bins (100.00%) + Bins: + COVERED a0 1 hits + COVERED a1 1 hits + + Coverpoint: cp_c + Coverage: 2/2 bins (100.00%) + Bins: + COVERED read 1 hits + COVERED write 1 hits + + Cross: __cross7 + Coverage: 2/4 bins (50.00%) + Bins: + COVERED a0_x_read 1 hits + ZERO a0_x_write 0 hits + ZERO a1_x_read 0 hits + COVERED a1_x_write 1 hits + +------------------------------------------------------------------------------