From 7e116ca601191d4bd7cc49fd9619de3835325e86 Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Sat, 1 Aug 2026 08:13:53 +0200 Subject: [PATCH] Support embedded covergroup member references (Fix #7749) (#8015) --- src/V3AstNodeDType.h | 7 +- src/V3AstNodeOther.h | 5 + src/V3AstNodes.cpp | 19 +- src/V3Covergroup.cpp | 249 +++++++-- src/V3EmitCFunc.cpp | 4 +- src/V3EmitCHeaders.cpp | 33 +- src/V3LinkDot.cpp | 14 +- src/V3LinkParse.cpp | 1 + src/V3WidthCommit.cpp | 6 +- src/Verilator.cpp | 1 + test_regress/t/t_constraint_json_only.out | 2 +- test_regress/t/t_covergroup_embedded.out | 134 +++++ test_regress/t/t_covergroup_embedded.py | 26 + test_regress/t/t_covergroup_embedded.v | 487 ++++++++++++++++++ .../t/t_covergroup_embedded_nested_bad.out | 18 + .../t/t_covergroup_embedded_nested_bad.py | 16 + .../t/t_covergroup_embedded_nested_bad.v | 60 +++ .../t/t_covergroup_embedded_unsup.out | 12 +- test_regress/t/t_covergroup_embedded_unsup.v | 21 +- 19 files changed, 1044 insertions(+), 71 deletions(-) create mode 100644 test_regress/t/t_covergroup_embedded.out create mode 100755 test_regress/t/t_covergroup_embedded.py create mode 100644 test_regress/t/t_covergroup_embedded.v create mode 100644 test_regress/t/t_covergroup_embedded_nested_bad.out create mode 100755 test_regress/t/t_covergroup_embedded_nested_bad.py create mode 100644 test_regress/t/t_covergroup_embedded_nested_bad.v diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 6c791c2f2..9ee73a84c 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -584,6 +584,7 @@ class AstClassRefDType final : public AstNodeDType { // // @astgen ptr := m_classp : Optional[AstClass] // data type pointed to, BELOW the AstTypedef // @astgen ptr := m_classOrPackagep : Optional[AstNodeModule] // Package hierarchy + bool m_rawPointer = false; // Emit as a non-owning C++ pointer rather than VlClassRef public: AstClassRefDType(FileLine* fl, AstClass* classp, AstPin* paramsp) : ASTGEN_SUPER_ClassRefDType(fl) @@ -595,7 +596,8 @@ public: // METHODS bool sameNode(const AstNode* samep) const override { const AstClassRefDType* const asamep = VN_DBG_AS(samep, ClassRefDType); - return (m_classp == asamep->m_classp && m_classOrPackagep == asamep->m_classOrPackagep); + return (m_classp == asamep->m_classp && m_classOrPackagep == asamep->m_classOrPackagep + && m_rawPointer == asamep->m_rawPointer); } bool similarDTypeNode(const AstNodeDType* samep) const override; void dump(std::ostream& str = std::cout) const override; @@ -613,6 +615,9 @@ public: void classOrPackagep(AstNodeModule* nodep) { m_classOrPackagep = nodep; } AstClass* classp() const VL_MT_STABLE { return m_classp; } void classp(AstClass* nodep) { m_classp = nodep; } + bool rawPointer() const { return m_rawPointer; } + void rawPointer(bool flag) { m_rawPointer = flag; } + static void selfTest(); bool isCompound() const override { return true; } }; class AstConstDType final : public AstNodeDType { diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index d2bbf21e4..4b2b62bac 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2897,6 +2897,7 @@ class AstClass final : public AstNodeModule { // @astgen op4 := extendsp : List[AstClassExtends] // MEMBERS // @astgen ptr := m_classOrPackagep : Optional[AstClassPackage] // Package to be emitted with + // @astgen ptr := m_covergroupEnclosingClassp : Optional[AstClass] // Lexical enclosing class uint32_t m_declTokenNum; // Declaration token number VBaseOverride m_baseOverride; // BaseOverride (inital/final/extends) bool m_covergroup = false; // Is covergroup (TODO perhaps make a new Ast node type for CG?) @@ -2921,6 +2922,10 @@ public: bool timescaleMatters() const override { return false; } AstClassPackage* classOrPackagep() const VL_MT_STABLE { return m_classOrPackagep; } void classOrPackagep(AstClassPackage* classpackagep) { m_classOrPackagep = classpackagep; } + AstClass* covergroupEnclosingClassp() const VL_MT_STABLE { + return m_covergroupEnclosingClassp; + } + void covergroupEnclosingClassp(AstClass* classp) { m_covergroupEnclosingClassp = classp; } AstNode* membersp() const VL_MT_STABLE { return stmtsp(); } void addMembersp(AstNode* nodep) { addStmtsp(nodep); } bool isCovergroup() const { return m_covergroup; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index b3b560307..48f0f05a7 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1194,7 +1194,8 @@ AstNodeDType::CTypeRecursed AstNodeDType::cTypeRecurse(bool compound, bool packe info.m_type = "VlSampleQueue<" + sub.m_type + ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, ClassRefDType)) { UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); - info.m_type = "VlClassRef<" + EmitCUtil::prefixNameProtect(adtypep) + ">"; + const string className = EmitCUtil::prefixNameProtect(adtypep); + info.m_type = adtypep->rawPointer() ? className + "*" : "VlClassRef<" + className + ">"; } else if (const auto* const adtypep = VN_CAST(dtypep, IfaceRefDType)) { UASSERT_OBJ(!packed, this, "Unsupported type for packed struct or union"); info.m_type = EmitCUtil::prefixNameProtect(adtypep->ifaceViaCellp()) + "*"; @@ -2196,8 +2197,22 @@ void AstClassRefDType::dump(std::ostream& str) const { } else { str << " -> UNLINKED"; } + if (rawPointer()) str << " [RAWPTR]"; +} +void AstClassRefDType::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, rawPointer); + dumpJsonGen(str); +} +void AstClassRefDType::selfTest() { + FileLine* const fl = new FileLine{FileLine::commandLineFilename()}; + AstClassRefDType* const owningp = new AstClassRefDType{fl, nullptr, nullptr}; + AstClassRefDType* const rawp = new AstClassRefDType{fl, nullptr, nullptr}; + rawp->rawPointer(true); + UASSERT_OBJ(!owningp->sameNode(rawp) && !rawp->sameNode(owningp) && rawp->sameNode(rawp), rawp, + "Raw class pointer must have distinct type identity"); + VL_DO_DANGLING(owningp->deleteTree(), owningp); + VL_DO_DANGLING(rawp->deleteTree(), rawp); } -void AstClassRefDType::dumpJson(std::ostream& str) const { dumpJsonGen(str); } void AstClassRefDType::dumpSmall(std::ostream& str) const { this->AstNodeDType::dumpSmall(str); str << "class:" << name(); diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 90bae71a2..67b612914 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -30,6 +30,7 @@ #include "V3MemberMap.h" #include +#include VL_DEFINE_DEBUG_FUNCTIONS; @@ -44,6 +45,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // STATE AstClass* m_covergroupp = nullptr; // Current covergroup being processed + AstClass* m_enclosingClassp = nullptr; // Class lexically enclosing the covergroup (if any) AstFunc* m_sampleFuncp = nullptr; // Current sample() function AstFunc* m_constructorp = nullptr; // Current constructor std::vector m_coverpoints; // Coverpoints in current covergroup @@ -68,6 +70,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { , 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 @@ -1692,38 +1695,185 @@ class FunctionalCoverageVisitor final : public VNVisitor { } // VISITORS - AstNode* findUnsupportedCoverpointRef(AstClass* cgClassp) { - // An embedded covergroup is lowered into a sibling AstClass that (currently) has - // no handle to the enclosing object. Identify refs to the containing context - // or a formal param and flag as unsupported + static bool isEnclosingInstanceVar(const AstVar* varp) { + return varp->isClassMember() && !varp->lifetime().isStatic() && !varp->isParam(); + } + + void rewriteThisRef(AstThisRef* refp, AstVar* handleVarp) { + const AstClassRefDType* const refDTypep + = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType); + UASSERT_OBJ(refDTypep && refDTypep->classp() == m_covergroupp, refp, + "Unexpected this reference in embedded covergroup"); + AstNodeExpr* const newp = new AstVarRef{refp->fileline(), handleVarp, VAccess::READ}; + refp->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(refp), refp); + } + + void rewriteVarRef(AstVarRef* refp, AstVar* handleVarp) { + FileLine* const fl = refp->fileline(); + AstMemberSel* const selp + = new AstMemberSel{fl, new AstVarRef{fl, handleVarp, VAccess::READ}, refp->varp()}; + selp->access(refp->access()); + refp->replaceWith(selp); + VL_DO_DANGLING(pushDeletep(refp), refp); + } + + void deleteCoverageItems() { + for (AstCoverpoint* const cpp : m_coverpoints) { + VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); + } + for (AstCoverCross* const crossp : m_coverCrosses) { + VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); + } + } + + class FormalRefVisitor final : public VNVisitor { + const std::set& m_constructorArgs; + AstMemberSel* m_memberSelp = nullptr; + AstNode* m_offenderp = nullptr; + + void visit(AstMemberSel* nodep) override { + if (m_offenderp) return; + VL_RESTORER(m_memberSelp); + if (!m_memberSelp) m_memberSelp = nodep; + iterateChildren(nodep); + } + void visit(AstVarRef* nodep) override { + if (m_memberSelp && m_constructorArgs.count(nodep->varp())) { + m_offenderp = m_memberSelp; + } + } + void visit(AstNode* nodep) override { + if (!m_offenderp) iterateChildren(nodep); + } + + public: + explicit FormalRefVisitor(const std::set& constructorArgs) + : m_constructorArgs{constructorArgs} {} + void scan(AstNode* nodep) { + if (nodep && !m_offenderp) iterate(nodep); + } + AstNode* offenderp() const { return m_offenderp; } + }; + + AstNode* findUnsupportedFormalRef() { + std::set constructorArgs; + for (AstNode* stmtp = m_constructorp->stmtsp(); stmtp; stmtp = stmtp->nextp()) { + if (const AstVar* const varp = VN_CAST(stmtp, Var)) { + if (varp->isIO()) constructorArgs.insert(varp); + } + } + FormalRefVisitor visitor{constructorArgs}; + for (AstCoverpoint* const cpp : m_coverpoints) { + visitor.scan(cpp->exprp()); + visitor.scan(cpp->iffp()); + } + return visitor.offenderp(); + } + + AstVarRef* installEnclosingBackPointer() { + // Simple-case support for embedded covergroups (IEEE 1800-2023 19.4) whose + // coverpoints reference members of the enclosing class ("Class members can be used + // in coverpoint expressions"). The covergroup is lowered into a sibling class with + // no implicit handle to the enclosing object, so such references would emit + // uncompilable C++. Add an explicit back-pointer member to the enclosing instance, + // route the member references through it, and initialize it right after the + // 'cgvar = new' construction. The enclosing member values are only read in + // sample(), which runs after construction, so this ordering is safe. Returns an invalid + // reference if an outer class member cannot be reached; otherwise returns an empty result. + if (!m_enclosingClassp) return nullptr; // Offending refs require an enclosing class + + AstVarRef* invalidp = nullptr; + AstNode* offenderp = nullptr; std::set ownVars; - for (AstNode* itemp = cgClassp->membersp(); itemp; itemp = itemp->nextp()) { + for (AstNode* itemp = m_covergroupp->membersp(); itemp; itemp = itemp->nextp()) { if (const AstVar* const varp = VN_CAST(itemp, Var)) ownVars.insert(varp); } - // Flag non-static enclosing-class members reached without a handle as unsupported - AstNode* offenderp = nullptr; - const auto scanEnclosing = [&](AstNode* rootp) { + std::set enclosingVars; + m_enclosingClassp->foreachMember([&](AstClass* const, AstVar* const varp) { + if (isEnclosingInstanceVar(varp)) enclosingVars.insert(varp); + }); + + std::vector refsToRewrite; + std::vector thisRefsToRewrite; + const auto scan = [&](AstNode* rootp) { rootp->foreach([&](AstVarRef* refp) { - if (offenderp) return; + if (invalidp) return; const AstVar* const varp = refp->varp(); - if (varp->isClassMember() && !ownVars.count(varp)) offenderp = refp; + if (!isEnclosingInstanceVar(varp) || ownVars.count(varp)) return; + if (!enclosingVars.count(varp)) { + invalidp = refp; + return; + } + refsToRewrite.push_back(refp); + if (!offenderp) offenderp = refp; + }); + if (invalidp) return; + rootp->foreach([&](AstThisRef* refp) { + const AstClassRefDType* const refDTypep + = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType); + if (refDTypep && refDTypep->classp() == m_covergroupp) { + thisRefsToRewrite.push_back(refp); + if (!offenderp) offenderp = refp; + } }); }; - for (AstCoverpoint* cpp : m_coverpoints) scanEnclosing(cpp); - for (AstCoverCross* crossp : m_coverCrosses) scanEnclosing(crossp); - if (offenderp) return offenderp; - // Flag references to covergroup formal parameters as currently unsupported - const auto scanHandleDeref = [&](AstNode* rootp) { - if (!rootp) return; - rootp->foreach([&](AstMemberSel* selp) { - if (!offenderp) offenderp = selp; - }); - }; - for (AstCoverpoint* cpp : m_coverpoints) { - scanHandleDeref(cpp->exprp()); - scanHandleDeref(cpp->iffp()); + for (AstCoverpoint* const cpp : m_coverpoints) scan(cpp); + for (AstCoverCross* const crossp : m_coverCrosses) scan(crossp); + if (invalidp || !offenderp) return invalidp; + + AstVar* embeddedVarp = nullptr; + for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) { + AstVar* const varp = VN_CAST(itemp, Var); + if (!varp) continue; + const AstClassRefDType* const refp + = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType); + if (refp && refp->classp() == m_covergroupp) { + embeddedVarp = varp; + break; + } } - return offenderp; + UASSERT_OBJ(embeddedVarp, m_covergroupp, "Embedded covergroup variable not found"); + + std::vector constructps; + AstFunc* const enclosingNewp + = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func); + if (enclosingNewp) { + enclosingNewp->foreach([&](AstNodeAssign* asgnp) { + const AstNew* const newp = VN_CAST(asgnp->rhsp(), New); + const AstVarRef* const lhsRefp = VN_CAST(asgnp->lhsp(), VarRef); + if (newp && lhsRefp && lhsRefp->varp() == embeddedVarp) { + const AstClassRefDType* const refp = VN_CAST(newp->dtypep(), ClassRefDType); + if (refp && refp->classp() == m_covergroupp) constructps.push_back(asgnp); + } + }); + } + // Commit: add the back-pointer member, rewrite the references, initialize the handle. + FileLine* const fl = m_covergroupp->fileline(); + AstClassRefDType* const enclDTypep = new AstClassRefDType{fl, m_enclosingClassp, nullptr}; + enclDTypep->rawPointer(true); + v3Global.rootp()->typeTablep()->addTypesp(enclDTypep); + AstVar* const handleVarp + = new AstVar{fl, VVarType::MEMBER, "__Vcg_enclosingp", enclDTypep}; + handleVarp->isStatic(false); + m_covergroupp->addMembersp(handleVarp); + + // Route each enclosing-member reference through the back-pointer: 'm' -> 'h.m'. + for (AstVarRef* const refp : refsToRewrite) { rewriteVarRef(refp, handleVarp); } + for (AstThisRef* const refp : thisRefsToRewrite) { rewriteThisRef(refp, handleVarp); } + + // Initialize the raw back-pointer after each construction. With no construction site, + // the embedded covergroup handle remains null, so no back-pointer is observed. + for (AstNodeAssign* const constructp : constructps) { + FileLine* const cfl = constructp->fileline(); + AstMemberSel* const lhsp + = new AstMemberSel{cfl, constructp->lhsp()->cloneTree(false), handleVarp}; + lhsp->access(VAccess::WRITE); + AstCExpr* const thisp = new AstCExpr{cfl, "this"}; + thisp->dtypep(enclDTypep); + constructp->addNextHere(new AstAssign{cfl, lhsp, thisp}); + } + return nullptr; } void visit(AstClass* nodep) override { @@ -1785,12 +1935,7 @@ class FunctionalCoverageVisitor final : public VNVisitor { // but still clean up coverpoints so they don't reach downstream passes if (hasUnsupportedEvent) { iterateChildren(nodep); - for (AstCoverpoint* cpp : m_coverpoints) { - VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); - } - for (AstCoverCross* crossp : m_coverCrosses) { - VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); - } + deleteCoverageItems(); return; } @@ -1806,35 +1951,35 @@ class FunctionalCoverageVisitor final : public VNVisitor { iterateChildren(nodep); - // Identify embedded covergroup refs to enclosing class members or - // covergroup formal parameters and flag as currently unsupported. - if (AstNode* const offenderp = findUnsupportedCoverpointRef(nodep)) { - const bool viaHandle = VN_IS(offenderp, MemberSel); - offenderp->v3warn(COVERIGN, - "Unsupported: 'covergroup' coverpoint " - << (viaHandle ? "dereferencing a class handle member " - "(parameterized covergroup)" - : "referencing enclosing class member") - << "; ignoring covergroup " << nodep->prettyNameQ()); - for (AstCoverpoint* cpp : m_coverpoints) { - VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); - } - for (AstCoverCross* crossp : m_coverCrosses) { - VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); - } + if (AstNode* const offenderp = findUnsupportedFormalRef()) { + offenderp->v3warn(COVERIGN, "Unsupported: 'covergroup' coverpoint dereferencing a " + "class handle member; ignoring covergroup " + << nodep->prettyNameQ()); + deleteCoverageItems(); return; } + // Embedded covergroups (IEEE 1800-2023 19.4): coverpoints, iff expressions, and + // crosses may reference members of the enclosing class. The covergroup is lowered + // into a sibling class with no implicit handle to the enclosing instance. Install + // an explicit back-pointer and route the references through it. + if (AstVarRef* const invalidp = installEnclosingBackPointer()) { + invalidp->v3error("Non-static member " + << invalidp->varp()->prettyNameQ() + << " of an outer class requires an explicit " + "object handle (IEEE 1800-2023 8.23)."); + deleteCoverageItems(); + return; + } processCovergroup(); // Remove lowered coverpoints/crosses from the class - they have been // fully translated into C++ code and must not reach downstream passes - for (AstCoverpoint* cpp : m_coverpoints) { - VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp); - } - for (AstCoverCross* crossp : m_coverCrosses) { - VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp); - } + deleteCoverageItems(); } else { + // Track the lexically enclosing class so a nested covergroup can resolve + // references to the enclosing object's members (installEnclosingBackPointer). + VL_RESTORER(m_enclosingClassp); + m_enclosingClassp = nodep; iterateChildren(nodep); } } diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 11f2ac14a..6fa58ab8d 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -541,8 +541,8 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, depth + 1, suffix + ".atDefault()", nullptr); } else if (VN_IS(dtypep, CDType)) { return ""; // Constructor does it - } else if (VN_IS(dtypep, ClassRefDType)) { - return ""; // Constructor does it + } else if (const AstClassRefDType* const adtypep = VN_CAST(dtypep, ClassRefDType)) { + return adtypep->rawPointer() ? varNameProtected + suffix + " = nullptr;\n" : ""; } else if (VN_IS(dtypep, IfaceRefDType)) { return varNameProtected + suffix + " = nullptr;\n"; } else if (const AstDynArrayDType* const adtypep = VN_CAST(dtypep, DynArrayDType)) { diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 8b2f2add0..862e28379 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -249,8 +249,37 @@ class EmitCHeader final : public EmitCConstInit { if (const AstClass* const classp = VN_CAST(modp, Class)) { if (!classp->isInterfaceClass() && !classp->isVirtual()) { decorateFirst(first, section); - putns(classp, "VlClass* clone() const { return new " - + EmitCUtil::prefixNameProtect(classp) + "(*this); }\n"); + using EmbeddedCovergroupVar = std::pair; + std::vector embeddedCovergroupVars; + const auto hasEnclosingBackPointer = [](const AstClass* covergroupp) { + return covergroupp->exists([](const AstVar* const varp) { + const AstClassRefDType* const refp + = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType); + return refp && refp->rawPointer(); + }); + }; + const_cast(classp)->foreachMember( + [&](AstClass* const memberClassp, AstVar* const varp) { + const AstClassRefDType* const refp + = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType); + if (refp && refp->classp()->isCovergroup() + && hasEnclosingBackPointer(refp->classp())) { + embeddedCovergroupVars.emplace_back(memberClassp, varp); + } + }); + const string className = EmitCUtil::prefixNameProtect(classp); + if (embeddedCovergroupVars.empty()) { + putns(classp, + "VlClass* clone() const { return new " + className + "(*this); }\n"); + } else { + putns(classp, "VlClass* clone() const { " + className + "* const clonep = new " + + className + "(*this); "); + for (const EmbeddedCovergroupVar& item : embeddedCovergroupVars) { + puts("clonep->" + EmitCUtil::prefixNameProtect(item.first) + + "::" + item.second->nameProtect() + " = VlNull{}; "); + } + puts("return clonep; }\n"); + } } } } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 6e725abf8..e6e3e14f5 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -4338,6 +4338,16 @@ class LinkDotResolveVisitor final : public VNVisitor { } else { foundp = m_ds.m_dotSymp->findIdFlat(nodep->name()); } + if (!foundp && m_ds.m_dotp && VN_IS(m_ds.m_dotp->lhsp(), ParseRef) + && m_ds.m_dotp->lhsp()->name() == "this") { + const AstClass* const classp = VN_CAST(m_ds.m_dotSymp->nodep(), Class); + if (classp && classp->isCovergroup() && classp->covergroupEnclosingClassp()) { + VSymEnt* const enclosingClassSymp + = m_statep->getNodeSym(classp->covergroupEnclosingClassp()); + foundp = enclosingClassSymp->findIdFallback(nodep->name()); + if (foundp) m_ds.m_dotSymp = enclosingClassSymp; + } + } // If not found in modport, check interface fallback for parameters and typedefs. // Parameters and typedefs are always visible through a modport (IEEE 1800-2023 25.5). // This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef). @@ -5848,7 +5858,9 @@ class LinkDotResolveVisitor final : public VNVisitor { VL_RESTORER(m_insideClassExtParam); { m_ds.init(m_curSymp); - m_insideClassExtParam = false; + m_insideClassExtParam = nodep->isCovergroup() && nodep->covergroupEnclosingClassp() + && m_extendsParam.find(nodep->covergroupEnclosingClassp()) + != m_extendsParam.end(); // Until overridden by a SCOPE m_ds.m_dotSymp = m_curSymp = m_modSymp = m_statep->getNodeSym(nodep); m_modp = nodep; diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index 3e3565842..86569766a 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -1285,6 +1285,7 @@ class LinkParseVisitor final : public VNVisitor { const string libname = m_modp->libname(); AstClass* const cgClassp = new AstClass{nodep->fileline(), nodep->name(), libname}; cgClassp->isCovergroup(true); + cgClassp->covergroupEnclosingClassp(VN_CAST(m_modp, Class)); v3Global.useCovergroup(true); // Clocking event: unlink before deleteTree, attach as AstCovergroup child on class diff --git a/src/V3WidthCommit.cpp b/src/V3WidthCommit.cpp index 900474423..be4aa99be 100644 --- a/src/V3WidthCommit.cpp +++ b/src/V3WidthCommit.cpp @@ -119,7 +119,11 @@ private: nodep->v3fatalSrc("ref to unhandled definition type " << defp->prettyTypeName()); } if (local || prot) { - const auto refClassp = VN_CAST(m_modp, Class); + // In case of covergroup, the reference is to the enclosing class, not the covergroup + // itself + const AstClass* refClassp = VN_CAST(m_modp, Class); + if (refClassp && refClassp->isCovergroup()) + refClassp = refClassp->covergroupEnclosingClassp(); const char* how = nullptr; // Inner nested classes can access `local` or `protected` members of their outer class const auto nestedAccess = [refClassp](const AstClass*, const AstNode* memberp) { diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 58af01afa..b11aea8b0 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -736,6 +736,7 @@ static bool verilate(const string& argString) { // and after removing files as may make debug output) VBasicDTypeKwd::selfTest(); if (v3Global.opt.debugSelfTest()) { + AstClassRefDType::selfTest(); V3Os::selfTest(); V3Number::selfTest(); VCMethod::selfTest(); diff --git a/test_regress/t/t_constraint_json_only.out b/test_regress/t/t_constraint_json_only.out index c18d8a989..48585c6f7 100644 --- a/test_regress/t/t_constraint_json_only.out +++ b/test_regress/t/t_constraint_json_only.out @@ -17,7 +17,7 @@ ]}, {"type":"PACKAGE","name":"$unit","addr":"(E)","loc":"a,0:0,0:0","origName":"__024unit","verilogName":"\\$unit ","level":2,"inLibrary":true,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"CLASS","name":"Packet","addr":"(O)","loc":"d,7:1,7:6","origName":"Packet","verilogName":"Packet","level":3,"timeunit":"1ps","classOrPackagep":"UNLINKED","inlinesp": [], + {"type":"CLASS","name":"Packet","addr":"(O)","loc":"d,7:1,7:6","origName":"Packet","verilogName":"Packet","level":3,"timeunit":"1ps","classOrPackagep":"UNLINKED","covergroupEnclosingClassp":"UNLINKED","inlinesp": [], "stmtsp": [ {"type":"VAR","name":"header","addr":"(P)","loc":"d,8:12,8:18","dtypep":"(Q)","origName":"header","verilogName":"header","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"int","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"length","addr":"(R)","loc":"d,9:12,9:18","dtypep":"(Q)","origName":"length","verilogName":"length","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"int","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, diff --git a/test_regress/t/t_covergroup_embedded.out b/test_regress/t/t_covergroup_embedded.out new file mode 100644 index 000000000..6daad856d --- /dev/null +++ b/test_regress/t/t_covergroup_embedded.out @@ -0,0 +1,134 @@ +GlobalCg.cp_global.auto_0: 0 +GlobalCg.cp_global.auto_1: 0 +GlobalCg.cp_global.auto_10: 0 +GlobalCg.cp_global.auto_11: 0 +GlobalCg.cp_global.auto_12: 0 +GlobalCg.cp_global.auto_13: 0 +GlobalCg.cp_global.auto_14: 0 +GlobalCg.cp_global.auto_15: 0 +GlobalCg.cp_global.auto_2: 0 +GlobalCg.cp_global.auto_3: 0 +GlobalCg.cp_global.auto_4: 0 +GlobalCg.cp_global.auto_5: 0 +GlobalCg.cp_global.auto_6: 0 +GlobalCg.cp_global.auto_7: 0 +GlobalCg.cp_global.auto_8: 0 +GlobalCg.cp_global.auto_9: 0 +__vlAnonCG_base_cg.cp_base.hi: 8 +__vlAnonCG_base_cg.cp_base.lo: 8 +__vlAnonCG_branch_cg.cp_branch.hi: 8 +__vlAnonCG_branch_cg.cp_branch.lo: 8 +__vlAnonCG_cg.cp_base.auto_0: 0 +__vlAnonCG_cg.cp_base.auto_1: 0 +__vlAnonCG_cg.cp_base.auto_10: 0 +__vlAnonCG_cg.cp_base.auto_11: 0 +__vlAnonCG_cg.cp_base.auto_12: 0 +__vlAnonCG_cg.cp_base.auto_13: 0 +__vlAnonCG_cg.cp_base.auto_14: 0 +__vlAnonCG_cg.cp_base.auto_15: 0 +__vlAnonCG_cg.cp_base.auto_2: 0 +__vlAnonCG_cg.cp_base.auto_3: 0 +__vlAnonCG_cg.cp_base.auto_4: 0 +__vlAnonCG_cg.cp_base.auto_5: 0 +__vlAnonCG_cg.cp_base.auto_6: 0 +__vlAnonCG_cg.cp_base.auto_7: 0 +__vlAnonCG_cg.cp_base.auto_8: 0 +__vlAnonCG_cg.cp_base.auto_9: 0 +__vlAnonCG_cg.cp_derived.auto_0: 0 +__vlAnonCG_cg.cp_derived.auto_1: 0 +__vlAnonCG_cg.cp_derived.auto_10: 0 +__vlAnonCG_cg.cp_derived.auto_11: 0 +__vlAnonCG_cg.cp_derived.auto_12: 0 +__vlAnonCG_cg.cp_derived.auto_13: 0 +__vlAnonCG_cg.cp_derived.auto_14: 0 +__vlAnonCG_cg.cp_derived.auto_15: 0 +__vlAnonCG_cg.cp_derived.auto_2: 0 +__vlAnonCG_cg.cp_derived.auto_3: 0 +__vlAnonCG_cg.cp_derived.auto_4: 0 +__vlAnonCG_cg.cp_derived.auto_5: 0 +__vlAnonCG_cg.cp_derived.auto_6: 0 +__vlAnonCG_cg.cp_derived.auto_7: 0 +__vlAnonCG_cg.cp_derived.auto_8: 0 +__vlAnonCG_cg.cp_derived.auto_9: 0 +__vlAnonCG_copy_cg.cp_copy.hi: 0 +__vlAnonCG_copy_cg.cp_copy.lo: 1 +__vlAnonCG_derived_cg.cp_cross.hi_x_hi [cross]: 0 +__vlAnonCG_derived_cg.cp_cross.hi_x_lo [cross]: 8 +__vlAnonCG_derived_cg.cp_cross.lo_x_hi [cross]: 8 +__vlAnonCG_derived_cg.cp_cross.lo_x_lo [cross]: 0 +__vlAnonCG_derived_cg.cp_derived.hi: 8 +__vlAnonCG_derived_cg.cp_derived.lo: 8 +__vlAnonCG_derived_cg.cp_inherited.hi: 8 +__vlAnonCG_derived_cg.cp_inherited.lo: 8 +__vlAnonCG_derived_cg.cp_this_inherited.hi: 8 +__vlAnonCG_derived_cg.cp_this_inherited.lo: 8 +__vlAnonCG_first_cg.cp_first.hi: 8 +__vlAnonCG_first_cg.cp_first.lo: 8 +__vlAnonCG_leaf_cg.cp_cross.hi_x_hi [cross]: 0 +__vlAnonCG_leaf_cg.cp_cross.hi_x_lo [cross]: 8 +__vlAnonCG_leaf_cg.cp_cross.lo_x_hi [cross]: 8 +__vlAnonCG_leaf_cg.cp_cross.lo_x_lo [cross]: 0 +__vlAnonCG_leaf_cg.cp_leaf.hi: 8 +__vlAnonCG_leaf_cg.cp_leaf.lo: 8 +__vlAnonCG_leaf_cg.cp_root.hi: 8 +__vlAnonCG_leaf_cg.cp_root.lo: 8 +__vlAnonCG_mon_cg.addr_x_op_b.hi_x_auto_0 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.hi_x_auto_1 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.hi_x_auto_2 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.hi_x_auto_3 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.lo_x_auto_0 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.lo_x_auto_1 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.lo_x_auto_2 [cross]: 2 +__vlAnonCG_mon_cg.addr_x_op_b.lo_x_auto_3 [cross]: 2 +__vlAnonCG_mon_cg.cp_addr.hi: 8 +__vlAnonCG_mon_cg.cp_addr.lo: 8 +__vlAnonCG_mon_cg.cp_enabled.hi: 8 +__vlAnonCG_mon_cg.cp_enabled.lo: 0 +__vlAnonCG_mon_cg.cp_inner.hi: 8 +__vlAnonCG_mon_cg.cp_inner.lo: 8 +__vlAnonCG_mon_cg.cp_op_a.hi: 8 +__vlAnonCG_mon_cg.cp_op_a.lo: 8 +__vlAnonCG_mon_cg.cp_op_b.auto_0: 4 +__vlAnonCG_mon_cg.cp_op_b.auto_1: 4 +__vlAnonCG_mon_cg.cp_op_b.auto_2: 4 +__vlAnonCG_mon_cg.cp_op_b.auto_3: 4 +__vlAnonCG_nested_cg.cp_container.hi: 8 +__vlAnonCG_nested_cg.cp_container.lo: 8 +__vlAnonCG_nested_cg.cp_local.hi: 8 +__vlAnonCG_nested_cg.cp_local.lo: 8 +__vlAnonCG_nested_cg.cp_static.hi: 8 +__vlAnonCG_nested_cg.cp_static.lo: 8 +__vlAnonCG_parameterized_cg.cp_parameterized.hi: 8 +__vlAnonCG_parameterized_cg.cp_parameterized.lo: 8 +__vlAnonCG_second_cg.cp_second.hi: 8 +__vlAnonCG_second_cg.cp_second.lo: 8 +__vlAnonCG_static_cg.cp_instance.hi: 8 +__vlAnonCG_static_cg.cp_instance.lo: 8 +__vlAnonCG_static_cg.cp_static.hi: 8 +__vlAnonCG_static_cg.cp_static.lo: 8 +__vlAnonCG_static_only_cg.cp_static_only.hi: 8 +__vlAnonCG_static_only_cg.cp_static_only.lo: 8 +__vlAnonCG_this_cg.cp_this.hi: 8 +__vlAnonCG_this_cg.cp_this.lo: 8 +__vlAnonCG_this_handle_cg.cp_this_handle.hi: 8 +__vlAnonCG_this_handle_cg.cp_this_handle.lo: 8 +__vlAnonCG_this_sample_cg.cp_this_member.hi: 8 +__vlAnonCG_this_sample_cg.cp_this_member.lo: 8 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_0: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_1: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_10: 16 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_11: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_12: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_13: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_14: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_15: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_2: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_3: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_4: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_5: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_6: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_7: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_8: 0 +__vlAnonCG_this_sample_cg.cp_this_parameter.auto_9: 0 +__vlAnonCG_this_sample_cg.cp_this_sample.hi: 8 +__vlAnonCG_this_sample_cg.cp_this_sample.lo: 8 diff --git a/test_regress/t/t_covergroup_embedded.py b/test_regress/t/t_covergroup_embedded.py new file mode 100755 index 000000000..a5fa769b7 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +import coverage_covergroup_common + +test.scenarios('vlt') + +coverage_covergroup_common.run( + test, + verilator_flags2=[ + "--dumpi-tree", + "9", + "--dumpi-tree-json", + "9", + "--no-json-ids", + "--debug-self-test", + ], +) diff --git a/test_regress/t/t_covergroup_embedded.v b/test_regress/t/t_covergroup_embedded.v new file mode 100644 index 000000000..7b5f863a9 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded.v @@ -0,0 +1,487 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +// Embedded covergroups whose coverage constructs reference members of the +// enclosing class. IEEE 1800-2023 19.4 allows class members in coverpoint +// expressions, conditional guards, option initialization, and other coverage +// constructs; IEEE 1800-2023 8.11 also allows 'this' within embedded covergroups. + +// verilog_format: off +`define stop $stop +`define checkd(gotv, expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__, `__LINE__, (gotv), (expv)); `stop; end while (0); +// verilog_format: on + +bit [3:0] global_value; + +covergroup GlobalCg; + cp_global: coverpoint global_value; +endgroup + +class GlobalCgHolder; + GlobalCg cg; + + function new(); + cg = new; + endfunction +endclass + +class Inner; + bit [3:0] value; +endclass + +class Transaction; + bit [7:0] operand_a; + bit [1:0] operand_b; + Inner inner; +endclass + +class Monitor; + bit [3:0] addr; // Direct member of the enclosing class + bit enable; + Transaction trx; // Class-handle member of the enclosing class + + covergroup mon_cg; + cp_addr: coverpoint addr {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_enabled: coverpoint addr iff (enable) {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_op_a: coverpoint trx.operand_a {bins lo = {[0 : 127]}; bins hi = {[128 : 255]};} + cp_op_b: coverpoint trx.operand_b; + cp_inner: coverpoint trx.inner.value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + addr_x_op_b: cross cp_addr, cp_op_b; + endgroup + + function new(); + trx = new; + trx.inner = new; + mon_cg = new; + endfunction + + function void observe(bit [3:0] a, bit [7:0] oa, bit [1:0] ob, bit [3:0] iv); + addr = a; + enable = a[3]; + trx.operand_a = oa; + trx.operand_b = ob; + trx.inner.value = iv; + mon_cg.sample(); + endfunction +endclass + +class BranchMonitor; + bit [2:0] value; + + covergroup branch_cg; + cp_branch: coverpoint value {bins lo = {[0 : 3]}; bins hi = {[4 : 7]};} + endgroup + + function new(bit choose_first); + if (choose_first) begin + branch_cg = new; + end + else begin + branch_cg = new; + end + endfunction + + function void observe(bit [2:0] v); + value = v; + branch_cg.sample(); + endfunction +endclass + +// IEEE 1800-2012 8.13 makes inherited members part of the derived class as if declared +// there; 19.4 permits those class members in an embedded covergroup. +class BaseMonitor; + protected bit [3:0] inherited_value; + + covergroup base_cg; + cp_base: coverpoint inherited_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + base_cg = new; + endfunction +endclass + +class DerivedMonitor extends BaseMonitor; + bit [3:0] derived_value; + + covergroup derived_cg; + cp_inherited: coverpoint inherited_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_this_inherited: coverpoint this.inherited_value { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + cp_derived: coverpoint derived_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_cross: cross cp_inherited, cp_derived; + endgroup + + function new(); + derived_cg = new; + endfunction + + function void observe(bit [3:0] v); + inherited_value = v; + derived_value = 15 - v; + base_cg.sample(); + derived_cg.sample(); + endfunction +endclass + +// IEEE 1800-2012 8.18 makes protected members visible to subclasses. Combined with +// 8.13 and 19.4, a leaf class covergroup may cover a protected root-class property. +class RootMonitor; + protected bit [3:0] root_value; +endclass + +class MiddleMonitor extends RootMonitor; +endclass + +class LeafMonitor extends MiddleMonitor; + bit [3:0] leaf_value; + + covergroup leaf_cg; + cp_root: coverpoint root_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_leaf: coverpoint leaf_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_cross: cross cp_root, cp_leaf; + endgroup + + function new(); + leaf_cg = new; + endfunction + + function void observe(bit [3:0] v); + root_value = v; + leaf_value = 15 - v; + leaf_cg.sample(); + endfunction +endclass + +class ParameterizedBaseMonitor #( + int WIDTH = 4 +); + bit [WIDTH-1:0] parameterized_value; +endclass + +class ParameterizedDerivedMonitor extends ParameterizedBaseMonitor #(4); + covergroup parameterized_cg; + cp_parameterized: coverpoint parameterized_value { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + endgroup + + function new(); + parameterized_cg = new; + endfunction + + function void observe(bit [3:0] v); + parameterized_value = v; + parameterized_cg.sample(); + endfunction +endclass + +class ThisMonitor; + bit [3:0] current; + + covergroup this_cg with function sample(bit [3:0] sampled_current); + cp_this: coverpoint current iff (sampled_current == current) { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + endgroup + + function new(); + this_cg = new; + endfunction + + function void observe(bit [3:0] v); + current = v; + this_cg.sample(this.current); + endfunction +endclass + +class ThisSampleMonitor; + bit [3:0] sampled_value; + localparam bit [3:0] local_value = 4'ha; + + covergroup this_sample_cg with function sample(bit [3:0] sampled_value); + cp_this_member: coverpoint this.sampled_value { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + cp_this_parameter: coverpoint this.local_value; + cp_this_sample: coverpoint sampled_value iff (sampled_value == this.sampled_value) { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + endgroup + + function new(); + this_sample_cg = new; + endfunction + + function void observe(bit [3:0] v); + this.sampled_value = v; + this_sample_cg.sample(this.sampled_value); + endfunction +endclass + +`ifdef VERILATOR +// IEEE 1800-2012 8.11 explicitly permits 'this' in covergroups embedded within classes. +class ThisHandleMonitor; + bit [3:0] current; + + covergroup this_handle_cg; + cp_this_handle: coverpoint current iff (this == this) { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + endgroup + + function new(); + this_handle_cg = new; + endfunction + + function void observe(bit [3:0] v); + current = v; + this_handle_cg.sample(); + endfunction +endclass +`endif + +class CopyMonitor; + bit [3:0] value; + + covergroup copy_cg; + cp_copy: coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + copy_cg = new; + endfunction + + function void observe(bit [3:0] v); + value = v; + copy_cg.sample(); + endfunction +endclass + +class CloneBaseMonitor; + bit [3:0] base_value; + + covergroup cg; + cp_base: coverpoint base_value; + endgroup + + function new(); + cg = new; + endfunction +endclass + +class CloneDerivedMonitor extends CloneBaseMonitor; + bit [3:0] derived_value; + + covergroup cg; + cp_derived: coverpoint derived_value; + endgroup + + function new(); + cg = new; + endfunction +endclass + +class StaticMonitor; + static bit [3:0] static_value; + bit [3:0] instance_value; + + covergroup static_cg; + cp_static: coverpoint static_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_instance: coverpoint instance_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + static_cg = new; + endfunction + + function void observe(bit [3:0] v); + static_value = v; + instance_value = v; + static_cg.sample(); + endfunction +endclass + +class StaticOnlyMonitor; + static bit [3:0] static_value; + + covergroup static_only_cg with function sample(bit [3:0] sampled_value); + cp_static_only: coverpoint static_value iff (sampled_value == static_value) { + bins lo = {[0 : 7]}; + bins hi = {[8 : 15]}; + } + endgroup + + function new(); + static_only_cg = new; + endfunction + + function void observe(bit [3:0] v); + static_value = v; + static_only_cg.sample(this.static_value); + endfunction +endclass + +class MultipleMonitor; + bit [3:0] first_value; + bit [3:0] second_value; + + covergroup first_cg; + cp_first: coverpoint first_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + covergroup second_cg; + cp_second: coverpoint second_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(); + first_cg = new; + second_cg = new; + endfunction + + function void observe(bit [3:0] first, bit [3:0] second); + first_value = first; + second_value = second; + first_cg.sample(); + second_cg.sample(); + endfunction +endclass + +class NestedContainer; + protected static bit [3:0] static_value; + bit [3:0] value; + + class NestedMonitor; + bit [3:0] local_value; + NestedContainer container; + + covergroup nested_cg; + cp_local: coverpoint local_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + cp_container: coverpoint container.value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + // IEEE 1800-2012 8.23 permits implicit access to static outer-class members. + cp_static: coverpoint static_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};} + endgroup + + function new(NestedContainer container_arg); + container = container_arg; + nested_cg = new; + endfunction + + function void observe(bit [3:0] v); + local_value = v; + container.value = 15 - v; + static_value = v; + nested_cg.sample(); + endfunction + endclass +endclass + +class UnconstructedMonitor; + bit [3:0] local_value; + + covergroup unconstructed_cg; + cp: coverpoint local_value; + cp2: coverpoint local_value[2:0]; + cp_x_cp2: cross cp, cp2; + endgroup + + function new(); + endfunction +endclass + +module t; + Monitor mon; + BranchMonitor branch_a; + BranchMonitor branch_b; + DerivedMonitor derived; + LeafMonitor leaf; + ParameterizedDerivedMonitor parameterized; + ThisMonitor this_mon; + ThisSampleMonitor this_sample_mon; +`ifdef VERILATOR + ThisHandleMonitor this_handle_mon; +`endif + CopyMonitor copy_src; + CopyMonitor copy_dst; + GlobalCgHolder global_src; + GlobalCgHolder global_dst; + CloneDerivedMonitor clone_src; + CloneDerivedMonitor clone_dst; + CloneBaseMonitor clone_base_view; + StaticMonitor static_mon; + StaticOnlyMonitor static_only_mon; + MultipleMonitor multiple_mon; + NestedContainer nested_container; + NestedContainer::NestedMonitor nested_mon; + UnconstructedMonitor unconstructed_mon; + int i; + + initial begin + mon = new; + branch_a = new(1); + branch_b = new(0); + derived = new; + leaf = new; + parameterized = new; + this_mon = new; + this_sample_mon = new; +`ifdef VERILATOR + this_handle_mon = new; +`endif + copy_src = new; + global_src = new; + clone_src = new; + static_mon = new; + static_only_mon = new; + multiple_mon = new; + nested_container = new; + nested_mon = new(nested_container); + unconstructed_mon = new; + `checkd(unconstructed_mon.unconstructed_cg == null, 1); + + for (i = 0; i < 16; ++i) begin + mon.observe(i[3:0], i[7:0] * 17, i[1:0], i[3:0]); + derived.observe(i[3:0]); + leaf.observe(i[3:0]); + parameterized.observe(i[3:0]); + this_mon.observe(i[3:0]); + this_sample_mon.observe(i[3:0]); +`ifdef VERILATOR + this_handle_mon.observe(i[3:0]); +`endif + static_mon.observe(i[3:0]); + static_only_mon.observe(i[3:0]); + multiple_mon.observe(i[3:0], 15 - i[3:0]); + nested_mon.observe(i[3:0]); + end + + for (i = 0; i < 8; ++i) begin + branch_a.observe(i[2:0]); + branch_b.observe(i[2:0]); + end + + copy_src.observe(4'h1); + copy_dst = new copy_src; + `checkd(copy_dst.copy_cg == null, 1); + global_dst = new global_src; + `checkd(global_dst.cg == global_src.cg, 1); + clone_dst = new clone_src; + clone_base_view = clone_dst; + `checkd(clone_dst.cg == null, 1); + `checkd(clone_base_view.cg == null, 1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_covergroup_embedded_nested_bad.out b/test_regress/t/t_covergroup_embedded_nested_bad.out new file mode 100644 index 000000000..22dc53f4d --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_nested_bad.out @@ -0,0 +1,18 @@ +%Error-ENCAPSULATED: t/t_covergroup_embedded_nested_bad.v:42:20: 'local_value' is hidden as 'local' within this context (IEEE 1800-2023 8.18) + : ... note: In instance 't' + 42 | cp: coverpoint local_value; + | ^~~~~~~~~~~ + t/t_covergroup_embedded_nested_bad.v:42:20: ... Location of definition + 37 | local bit [3:0] local_value; + | ^~~~~~~~~~~ + ... For error description see https://verilator.org/warn/ENCAPSULATED?v=latest +%Error: t/t_covergroup_embedded_nested_bad.v:13:22: Non-static member 'outer_value' of an outer class requires an explicit object handle (IEEE 1800-2023 8.23). + : ... note: In instance 't' + 13 | cp: coverpoint outer_value; + | ^~~~~~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: t/t_covergroup_embedded_nested_bad.v:25:39: Non-static member 'outer_value' of an outer class requires an explicit object handle (IEEE 1800-2023 8.23). + : ... note: In instance 't' + 25 | cp: coverpoint inner_value iff (outer_value != 0); + | ^~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_covergroup_embedded_nested_bad.py b/test_regress/t/t_covergroup_embedded_nested_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_nested_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('linter') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_covergroup_embedded_nested_bad.v b/test_regress/t/t_covergroup_embedded_nested_bad.v new file mode 100644 index 000000000..3ddaccf82 --- /dev/null +++ b/test_regress/t/t_covergroup_embedded_nested_bad.v @@ -0,0 +1,60 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +class Outer; + bit [3:0] outer_value; + + class CoverpointInner; + covergroup cg; + cp: coverpoint outer_value; + endgroup + + function new(); + cg = new; + endfunction + endclass + + class IffInner; + bit [3:0] inner_value; + + covergroup cg; + cp: coverpoint inner_value iff (outer_value != 0); + endgroup + + function new(); + cg = new; + endfunction + endclass +endclass + +// IEEE 1800-2012 8.18 does not make local base-class members visible to subclasses, +// even though 8.13 makes non-local inherited members part of the derived class. +class LocalBase; + local bit [3:0] local_value; +endclass + +class LocalDerived extends LocalBase; + covergroup cg; + cp: coverpoint local_value; + endgroup + + function new(); + cg = new; + endfunction +endclass + +module t; + Outer::CoverpointInner coverpoint_inner; + Outer::IffInner iff_inner; + LocalDerived local_derived; + + initial begin + coverpoint_inner = new; + iff_inner = new; + local_derived = new; + end +endmodule diff --git a/test_regress/t/t_covergroup_embedded_unsup.out b/test_regress/t/t_covergroup_embedded_unsup.out index d74a490cf..d40285096 100644 --- a/test_regress/t/t_covergroup_embedded_unsup.out +++ b/test_regress/t/t_covergroup_embedded_unsup.out @@ -1,11 +1,11 @@ -%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:23:34: Unsupported: 'covergroup' coverpoint referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov_trans' +%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:44:23: Unsupported: 'covergroup' coverpoint dereferencing a class handle member; ignoring covergroup '__vlAnonCG_cov_param' : ... note: In instance 't' - 23 | trans_start_addr: coverpoint trans_collected.addr {option.auto_bin_max = 16;} - | ^~~~~~~~~~~~~~~ + 44 | cp: coverpoint st.test; + | ^~~~ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:46:23: Unsupported: 'covergroup' coverpoint dereferencing a class handle member (parameterized covergroup); ignoring covergroup '__vlAnonCG_cov_param' +%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:60:37: Unsupported: 'covergroup' coverpoint dereferencing a class handle member; ignoring covergroup '__vlAnonCG_cov_mixed' : ... note: In instance 't' - 46 | cp: coverpoint st.test; - | ^~~~ + 60 | cp: coverpoint local_value + st.test; + | ^~~~ %Error: Exiting due to diff --git a/test_regress/t/t_covergroup_embedded_unsup.v b/test_regress/t/t_covergroup_embedded_unsup.v index 8a81eb4ab..f02302ae6 100644 --- a/test_regress/t/t_covergroup_embedded_unsup.v +++ b/test_regress/t/t_covergroup_embedded_unsup.v @@ -5,9 +5,7 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -// Test that two currently-unsupported coverpoint reference styles are properly flagged -// as COVIGN: references to containing-class members ; references to covergroup formal -// parameters +// Test that unsupported coverpoint reference styles are properly flagged as COVERIGN. class ubus_transfer; bit [15:0] addr; @@ -53,12 +51,29 @@ class parameterized_monitor; endfunction endclass +class mixed_monitor; + bit [3:0] local_value; + coverage_state cs; + + // The formal-handle guard must still apply when the expression also has an enclosing member. + covergroup cov_mixed(coverage_state st); + cp: coverpoint local_value + st.test; + endgroup + + function new(); + cs = new; + cov_mixed = new(cs); + endfunction +endclass + module t; ubus_master_monitor m; parameterized_monitor p; + mixed_monitor q; initial begin m = new; p = new; + q = new; $write("*-* All Finished *-*\n"); $finish; end