Support embedded covergroup member references
This commit is contained in:
parent
0ebae43713
commit
007eac25fd
|
|
@ -2086,15 +2086,16 @@ private:
|
||||||
|
|
||||||
// MEMBERS
|
// MEMBERS
|
||||||
T_Class* m_objp = nullptr; // Object pointed to
|
T_Class* m_objp = nullptr; // Object pointed to
|
||||||
|
bool m_weak = false; // Non-owning reference
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
// Increase reference counter with null check
|
// Increase reference counter with null check
|
||||||
void refCountInc() const VL_MT_SAFE {
|
void refCountInc() const VL_MT_SAFE {
|
||||||
if (m_objp) m_objp->refCountInc();
|
if (m_objp && !m_weak) m_objp->refCountInc();
|
||||||
}
|
}
|
||||||
// Decrease reference counter with null check
|
// Decrease reference counter with null check
|
||||||
void refCountDec() const VL_MT_SAFE {
|
void refCountDec() const VL_MT_SAFE {
|
||||||
if (m_objp) m_objp->refCountDec();
|
if (m_objp && !m_weak) m_objp->refCountDec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
@ -2134,58 +2135,73 @@ public:
|
||||||
}
|
}
|
||||||
// cppcheck-suppress noExplicitConstructor
|
// cppcheck-suppress noExplicitConstructor
|
||||||
VlClassRef(const VlClassRef& copied)
|
VlClassRef(const VlClassRef& copied)
|
||||||
: m_objp{copied.m_objp} {
|
: m_objp{copied.m_objp}
|
||||||
|
, m_weak{copied.m_weak} {
|
||||||
refCountInc();
|
refCountInc();
|
||||||
}
|
}
|
||||||
// cppcheck-suppress noExplicitConstructor
|
// cppcheck-suppress noExplicitConstructor
|
||||||
VlClassRef(VlClassRef&& moved)
|
VlClassRef(VlClassRef&& moved)
|
||||||
: m_objp{std::exchange(moved.m_objp, nullptr)} {}
|
: m_objp{std::exchange(moved.m_objp, nullptr)}
|
||||||
|
, m_weak{std::exchange(moved.m_weak, false)} {}
|
||||||
// cppcheck-suppress noExplicitConstructor
|
// cppcheck-suppress noExplicitConstructor
|
||||||
template <typename T_OtherClass>
|
template <typename T_OtherClass>
|
||||||
VlClassRef(const VlClassRef<T_OtherClass>& copied)
|
VlClassRef(const VlClassRef<T_OtherClass>& copied)
|
||||||
: m_objp{copied.m_objp} {
|
: m_objp{copied.m_objp}
|
||||||
|
, m_weak{copied.m_weak} {
|
||||||
refCountInc();
|
refCountInc();
|
||||||
}
|
}
|
||||||
// cppcheck-suppress noExplicitConstructor
|
// cppcheck-suppress noExplicitConstructor
|
||||||
template <typename T_OtherClass>
|
template <typename T_OtherClass>
|
||||||
VlClassRef(VlClassRef<T_OtherClass>&& moved)
|
VlClassRef(VlClassRef<T_OtherClass>&& moved)
|
||||||
: m_objp{std::exchange(moved.m_objp, nullptr)} {}
|
: m_objp{std::exchange(moved.m_objp, nullptr)}
|
||||||
|
, m_weak{std::exchange(moved.m_weak, false)} {}
|
||||||
~VlClassRef() { refCountDec(); }
|
~VlClassRef() { refCountDec(); }
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
static VlClassRef weak(T_Class* objp) {
|
||||||
|
VlClassRef ref;
|
||||||
|
ref.m_objp = objp;
|
||||||
|
ref.m_weak = true;
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
// Copy and move assignments
|
// Copy and move assignments
|
||||||
VlClassRef& operator=(const VlClassRef& copied) {
|
VlClassRef& operator=(const VlClassRef& copied) {
|
||||||
if (m_objp == copied.m_objp) return *this;
|
if (m_objp == copied.m_objp && m_weak == copied.m_weak) return *this;
|
||||||
refCountDec();
|
refCountDec();
|
||||||
m_objp = copied.m_objp;
|
m_objp = copied.m_objp;
|
||||||
|
m_weak = copied.m_weak;
|
||||||
refCountInc();
|
refCountInc();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
VlClassRef& operator=(VlClassRef&& moved) {
|
VlClassRef& operator=(VlClassRef&& moved) {
|
||||||
if (m_objp == moved.m_objp) return *this;
|
if (m_objp == moved.m_objp && m_weak == moved.m_weak) return *this;
|
||||||
refCountDec();
|
refCountDec();
|
||||||
m_objp = std::exchange(moved.m_objp, nullptr);
|
m_objp = std::exchange(moved.m_objp, nullptr);
|
||||||
|
m_weak = std::exchange(moved.m_weak, false);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
template <typename T_OtherClass>
|
template <typename T_OtherClass>
|
||||||
VlClassRef& operator=(const VlClassRef<T_OtherClass>& copied) {
|
VlClassRef& operator=(const VlClassRef<T_OtherClass>& copied) {
|
||||||
if (m_objp == copied.m_objp) return *this;
|
if (m_objp == copied.m_objp && m_weak == copied.m_weak) return *this;
|
||||||
refCountDec();
|
refCountDec();
|
||||||
m_objp = copied.m_objp;
|
m_objp = copied.m_objp;
|
||||||
|
m_weak = copied.m_weak;
|
||||||
refCountInc();
|
refCountInc();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
template <typename T_OtherClass>
|
template <typename T_OtherClass>
|
||||||
VlClassRef& operator=(VlClassRef<T_OtherClass>&& moved) {
|
VlClassRef& operator=(VlClassRef<T_OtherClass>&& moved) {
|
||||||
if (m_objp == moved.m_objp) return *this;
|
if (m_objp == moved.m_objp && m_weak == moved.m_weak) return *this;
|
||||||
refCountDec();
|
refCountDec();
|
||||||
m_objp = std::exchange(moved.m_objp, nullptr);
|
m_objp = std::exchange(moved.m_objp, nullptr);
|
||||||
|
m_weak = std::exchange(moved.m_weak, false);
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
// Assign with nullptr
|
// Assign with nullptr
|
||||||
VlClassRef& operator=(VlNull) {
|
VlClassRef& operator=(VlNull) {
|
||||||
refCountDec();
|
refCountDec();
|
||||||
m_objp = nullptr;
|
m_objp = nullptr;
|
||||||
|
m_weak = false;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
// Dynamic caster
|
// Dynamic caster
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include "V3MemberMap.h"
|
#include "V3MemberMap.h"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||||
|
|
||||||
|
|
@ -44,6 +45,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
|
|
||||||
// STATE
|
// STATE
|
||||||
AstClass* m_covergroupp = nullptr; // Current covergroup being processed
|
AstClass* m_covergroupp = nullptr; // Current covergroup being processed
|
||||||
|
AstClass* m_enclosingClassp = nullptr; // Class lexically enclosing the covergroup (if any)
|
||||||
|
AstVar* m_embeddedVarp = nullptr; // Enclosing class member that owns the embedded covergroup
|
||||||
AstFunc* m_sampleFuncp = nullptr; // Current sample() function
|
AstFunc* m_sampleFuncp = nullptr; // Current sample() function
|
||||||
AstFunc* m_constructorp = nullptr; // Current constructor
|
AstFunc* m_constructorp = nullptr; // Current constructor
|
||||||
std::vector<AstCoverpoint*> m_coverpoints; // Coverpoints in current covergroup
|
std::vector<AstCoverpoint*> m_coverpoints; // Coverpoints in current covergroup
|
||||||
|
|
@ -68,6 +71,20 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
, crossBins{cb} {}
|
, crossBins{cb} {}
|
||||||
};
|
};
|
||||||
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
|
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
|
||||||
|
|
||||||
|
struct EmbeddedEventTrigger final {
|
||||||
|
AstVar* baseVarp;
|
||||||
|
AstVar* memberVarp;
|
||||||
|
VEdgeType edgeType;
|
||||||
|
AstVar* prevVarp;
|
||||||
|
EmbeddedEventTrigger(AstVar* baseVarp, AstVar* memberVarp, VEdgeType edgeType,
|
||||||
|
AstVar* prevVarp)
|
||||||
|
: baseVarp{baseVarp}
|
||||||
|
, memberVarp{memberVarp}
|
||||||
|
, edgeType{edgeType}
|
||||||
|
, prevVarp{prevVarp} {}
|
||||||
|
};
|
||||||
|
|
||||||
std::set<std::string> m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy)
|
std::set<std::string> m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy)
|
||||||
std::vector<AstVar*> m_convCpVars; // VlCoverpoint members of converted coverpoints
|
std::vector<AstVar*> m_convCpVars; // VlCoverpoint members of converted coverpoints
|
||||||
AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type
|
AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type
|
||||||
|
|
@ -1692,9 +1709,195 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// VISITORS
|
// VISITORS
|
||||||
AstNode* findEnclosingMemberRef(AstClass* cgClassp) {
|
void collectOwnVars(std::set<const AstVar*>& ownVars) {
|
||||||
|
for (AstNode* itemp = m_covergroupp->membersp(); itemp; itemp = itemp->nextp()) {
|
||||||
|
if (const AstVar* const varp = VN_CAST(itemp, Var)) ownVars.insert(varp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void collectEnclosingVars(std::set<const AstVar*>& enclosingVars) {
|
||||||
|
if (!m_enclosingClassp) return;
|
||||||
|
m_enclosingClassp->foreachMember(
|
||||||
|
[&](AstClass* const, AstVar* const varp) { enclosingVars.insert(varp); });
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmbeddedCovergroupVar(const AstVar* varp) const {
|
||||||
|
if (!varp || !varp->isClassMember()) return false;
|
||||||
|
const AstClassRefDType* const refp = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
|
return refp && refp->classp() == m_covergroupp;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstVar* findEmbeddedCovergroupVar() const {
|
||||||
|
if (!m_enclosingClassp) return nullptr;
|
||||||
|
for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) {
|
||||||
|
if (AstVar* const varp = VN_CAST(itemp, Var)) {
|
||||||
|
if (isEmbeddedCovergroupVar(varp)) return varp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeExpr* newEnclosingHandleRef(FileLine* fl, AstVar* handleVarp) {
|
||||||
|
return new AstVarRef{fl, handleVarp, VAccess::READ};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool rewriteThisRef(AstThisRef* refp, AstVar* handleVarp) {
|
||||||
|
if (!m_enclosingClassp) return false;
|
||||||
|
const AstClassRefDType* const refDTypep = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
|
if (!refDTypep || refDTypep->classp() != m_covergroupp) return false;
|
||||||
|
AstNodeExpr* const newp = newEnclosingHandleRef(refp->fileline(), handleVarp);
|
||||||
|
refp->replaceWith(newp);
|
||||||
|
VL_DO_DANGLING(pushDeletep(refp), refp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rewriteVarRef(AstVarRef* refp, AstVar* handleVarp) {
|
||||||
|
FileLine* const fl = refp->fileline();
|
||||||
|
AstMemberSel* const selp
|
||||||
|
= new AstMemberSel{fl, newEnclosingHandleRef(fl, handleVarp), refp->varp()};
|
||||||
|
selp->access(refp->access());
|
||||||
|
refp->replaceWith(selp);
|
||||||
|
VL_DO_DANGLING(pushDeletep(refp), refp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool parseEmbeddedEventExpr(AstNodeExpr* exprp, AstVar*& baseVarp, AstVar*& memberVarp) const {
|
||||||
|
if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
|
||||||
|
if (!refp->varp()->isClassMember()) return false;
|
||||||
|
baseVarp = refp->varp();
|
||||||
|
memberVarp = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
AstMemberSel* const selp = VN_CAST(exprp, MemberSel);
|
||||||
|
if (!selp) return false;
|
||||||
|
AstVarRef* const baseRefp = VN_CAST(selp->fromp(), VarRef);
|
||||||
|
if (!baseRefp || !baseRefp->varp()->isClassMember()) return false;
|
||||||
|
baseVarp = baseRefp->varp();
|
||||||
|
memberVarp = selp->varp();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEventLvalue(AstNodeExpr* exprp, const EmbeddedEventTrigger& trigger) const {
|
||||||
|
AstVar* baseVarp = nullptr;
|
||||||
|
AstVar* memberVarp = nullptr;
|
||||||
|
if (!parseEmbeddedEventExpr(exprp, baseVarp, memberVarp)) return false;
|
||||||
|
return baseVarp == trigger.baseVarp && memberVarp == trigger.memberVarp;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeExpr* newEventRead(FileLine* fl, const EmbeddedEventTrigger& trigger) {
|
||||||
|
AstNodeExpr* const basep = new AstVarRef{fl, trigger.baseVarp, VAccess::READ};
|
||||||
|
if (!trigger.memberVarp) return basep;
|
||||||
|
AstMemberSel* const selp = new AstMemberSel{fl, basep, trigger.memberVarp};
|
||||||
|
selp->access(VAccess::READ);
|
||||||
|
return selp;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeDType* eventDTypep(const EmbeddedEventTrigger& trigger) const {
|
||||||
|
if (trigger.memberVarp) return trigger.memberVarp->dtypep();
|
||||||
|
return trigger.baseVarp->dtypep();
|
||||||
|
}
|
||||||
|
|
||||||
|
string eventPrevName(const EmbeddedEventTrigger& trigger) const {
|
||||||
|
string name = "__Vcg_prev_" + m_embeddedVarp->name() + "_" + trigger.baseVarp->name();
|
||||||
|
if (trigger.memberVarp) name += "_" + trigger.memberVarp->name();
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstVar* newEventPrevVar(FileLine* fl, const EmbeddedEventTrigger& trigger) {
|
||||||
|
return new AstVar{fl, VVarType::MEMBER, eventPrevName(trigger), eventDTypep(trigger)};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isEmbeddedEventExpr(AstNodeExpr* exprp) const {
|
||||||
|
AstVar* baseVarp = nullptr;
|
||||||
|
AstVar* memberVarp = nullptr;
|
||||||
|
return parseEmbeddedEventExpr(exprp, baseVarp, memberVarp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasEmbeddedEventExpr(AstCovergroup* cgp) const {
|
||||||
|
if (!m_enclosingClassp || !m_embeddedVarp || !cgp || !cgp->eventp()) return false;
|
||||||
|
for (AstNode* senp = cgp->eventp()->sensesp(); senp; senp = senp->nextp()) {
|
||||||
|
AstSenItem* const itemp = VN_AS(senp, SenItem);
|
||||||
|
if (isEmbeddedEventExpr(itemp->sensp())) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeExpr* newVarRead(FileLine* fl, AstVar* varp) {
|
||||||
|
return new AstVarRef{fl, varp, VAccess::READ};
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeExpr* newEventReadyCondition(FileLine* fl, const EmbeddedEventTrigger& trigger) {
|
||||||
|
AstNodeExpr* const curp = newEventRead(fl, trigger);
|
||||||
|
AstNodeExpr* const prevp = newVarRead(fl, trigger.prevVarp);
|
||||||
|
AstNodeExpr* edgep = nullptr;
|
||||||
|
if (trigger.edgeType == VEdgeType::ET_POSEDGE) {
|
||||||
|
edgep = new AstLogAnd{fl, curp, new AstLogNot{fl, prevp}};
|
||||||
|
} else if (trigger.edgeType == VEdgeType::ET_NEGEDGE) {
|
||||||
|
edgep = new AstLogAnd{fl, new AstLogNot{fl, curp}, prevp};
|
||||||
|
} else {
|
||||||
|
edgep = new AstNeq{fl, curp, prevp};
|
||||||
|
}
|
||||||
|
return new AstLogAnd{
|
||||||
|
fl, new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ},
|
||||||
|
new AstConst{fl, AstConst::Null{}}},
|
||||||
|
edgep};
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeStmt* newSampleStmt(FileLine* fl) {
|
||||||
|
AstMethodCall* const callp
|
||||||
|
= new AstMethodCall{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, "sample",
|
||||||
|
nullptr};
|
||||||
|
callp->taskp(m_sampleFuncp);
|
||||||
|
callp->dtypeSetVoid();
|
||||||
|
return callp->makeStmt();
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNodeStmt* newPrevUpdate(FileLine* fl, const EmbeddedEventTrigger& trigger) {
|
||||||
|
return new AstAssign{fl, new AstVarRef{fl, trigger.prevVarp, VAccess::WRITE},
|
||||||
|
newEventRead(fl, trigger)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EmbeddedEventTrigger> collectEmbeddedEventTriggers(AstCovergroup* cgp) {
|
||||||
|
std::vector<EmbeddedEventTrigger> triggers;
|
||||||
|
if (!m_enclosingClassp || !m_embeddedVarp || !cgp || !cgp->eventp()) return triggers;
|
||||||
|
|
||||||
|
std::vector<AstSenItem*> itemps;
|
||||||
|
for (AstNode* senp = cgp->eventp()->sensesp(); senp; senp = senp->nextp()) {
|
||||||
|
AstSenItem* const itemp = VN_AS(senp, SenItem);
|
||||||
|
if (!isEmbeddedEventExpr(itemp->sensp())) return {};
|
||||||
|
itemps.push_back(itemp);
|
||||||
|
}
|
||||||
|
for (AstSenItem* const itemp : itemps) {
|
||||||
|
AstVar* baseVarp = nullptr;
|
||||||
|
AstVar* memberVarp = nullptr;
|
||||||
|
UASSERT_OBJ(parseEmbeddedEventExpr(itemp->sensp(), baseVarp, memberVarp), itemp,
|
||||||
|
"Bad embedded covergroup event expression");
|
||||||
|
FileLine* const fl = itemp->fileline();
|
||||||
|
EmbeddedEventTrigger trigger{baseVarp, memberVarp, itemp->edgeType(), nullptr};
|
||||||
|
AstVar* const prevVarp = newEventPrevVar(fl, trigger);
|
||||||
|
m_enclosingClassp->addMembersp(prevVarp);
|
||||||
|
trigger.prevVarp = prevVarp;
|
||||||
|
triggers.push_back(trigger);
|
||||||
|
}
|
||||||
|
return triggers;
|
||||||
|
}
|
||||||
|
|
||||||
|
void installEmbeddedEventTriggers(const std::vector<EmbeddedEventTrigger>& triggers) {
|
||||||
|
if (triggers.empty()) return;
|
||||||
|
m_enclosingClassp->foreach([&](AstNodeAssign* asgnp) {
|
||||||
|
for (const EmbeddedEventTrigger& trigger : triggers) {
|
||||||
|
if (!isEventLvalue(asgnp->lhsp(), trigger)) continue;
|
||||||
|
FileLine* const fl = asgnp->fileline();
|
||||||
|
AstIf* const ifp
|
||||||
|
= new AstIf{fl, newEventReadyCondition(fl, trigger), newSampleStmt(fl)};
|
||||||
|
ifp->addNextHere(newPrevUpdate(fl, trigger));
|
||||||
|
asgnp->addNextHere(ifp);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AstNode* findEnclosingMemberRef() {
|
||||||
// An embedded covergroup is lowered into a sibling AstClass that has no handle to
|
// An embedded covergroup is lowered into a sibling AstClass that has no handle to
|
||||||
// the enclosing object. A coverpoint/iff/cross expression that references a
|
// the enclosing object. A coverpoint/iff/cross/event expression that references a
|
||||||
// (non-static) member of the enclosing class therefore emits C++ that accesses the
|
// (non-static) member of the enclosing class therefore emits C++ that accesses the
|
||||||
// member as if it were static ("invalid use of non-static data member"). Detect
|
// member as if it were static ("invalid use of non-static data member"). Detect
|
||||||
// such references so the caller can skip lowering with a clean warning instead of
|
// such references so the caller can skip lowering with a clean warning instead of
|
||||||
|
|
@ -1702,9 +1905,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
// Collect the covergroup class's own member variables (sample/constructor args);
|
// Collect the covergroup class's own member variables (sample/constructor args);
|
||||||
// references to those are legitimate.
|
// references to those are legitimate.
|
||||||
std::set<const AstVar*> ownVars;
|
std::set<const AstVar*> ownVars;
|
||||||
for (AstNode* itemp = cgClassp->membersp(); itemp; itemp = itemp->nextp()) {
|
collectOwnVars(ownVars);
|
||||||
if (const AstVar* const varp = VN_CAST(itemp, Var)) ownVars.insert(varp);
|
|
||||||
}
|
|
||||||
AstNode* offenderp = nullptr;
|
AstNode* offenderp = nullptr;
|
||||||
const auto scan = [&](AstNode* rootp) {
|
const auto scan = [&](AstNode* rootp) {
|
||||||
rootp->foreach([&](AstVarRef* refp) {
|
rootp->foreach([&](AstVarRef* refp) {
|
||||||
|
|
@ -1715,27 +1916,150 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
// legitimate and excluded via ownVars.
|
// legitimate and excluded via ownVars.
|
||||||
if (varp->isClassMember() && !ownVars.count(varp)) offenderp = refp;
|
if (varp->isClassMember() && !ownVars.count(varp)) offenderp = refp;
|
||||||
});
|
});
|
||||||
|
rootp->foreach([&](AstThisRef* refp) {
|
||||||
|
if (offenderp) return;
|
||||||
|
const AstClassRefDType* const refDTypep
|
||||||
|
= VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
|
if (refDTypep && refDTypep->classp() == m_covergroupp) offenderp = refp;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
for (AstCoverpoint* cpp : m_coverpoints) scan(cpp);
|
for (AstCoverpoint* cpp : m_coverpoints) scan(cpp);
|
||||||
for (AstCoverCross* crossp : m_coverCrosses) scan(crossp);
|
for (AstCoverCross* crossp : m_coverCrosses) scan(crossp);
|
||||||
|
for (AstNode* itemp = m_covergroupp->membersp(); itemp; itemp = itemp->nextp()) {
|
||||||
|
if (AstCovergroup* const cgp = VN_CAST(itemp, Covergroup)) scan(cgp);
|
||||||
|
}
|
||||||
return offenderp;
|
return offenderp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<AstNodeAssign*> findCovergroupConstructions() {
|
||||||
|
// IEEE 1800-2023 19.4: an embedded covergroup variable is constructed (and only
|
||||||
|
// constructed) in the enclosing class's new(), as 'cgvar = new'. Return all such
|
||||||
|
// assignments so each constructed instance receives its back-pointer.
|
||||||
|
std::vector<AstNodeAssign*> foundps;
|
||||||
|
if (!m_embeddedVarp) return foundps;
|
||||||
|
AstFunc* const enclosingNewp = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func);
|
||||||
|
if (!enclosingNewp) return foundps;
|
||||||
|
enclosingNewp->foreach([&](AstNodeAssign* asgnp) {
|
||||||
|
const AstNew* const newp = VN_CAST(asgnp->rhsp(), New);
|
||||||
|
if (!newp) return;
|
||||||
|
const AstClassRefDType* const refp = VN_CAST(newp->dtypep(), ClassRefDType);
|
||||||
|
if (!refp || refp->classp() != m_covergroupp) return;
|
||||||
|
const AstVarRef* const lhsRefp = VN_CAST(asgnp->lhsp(), VarRef);
|
||||||
|
if (!lhsRefp || lhsRefp->varp() != m_embeddedVarp) return;
|
||||||
|
foundps.push_back(asgnp);
|
||||||
|
});
|
||||||
|
return foundps;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool 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 true
|
||||||
|
// if the back-pointer was installed; false leaves the COVERIGN safety net to the
|
||||||
|
// caller for anything outside this simple form.
|
||||||
|
if (!m_enclosingClassp) return false; // Covergroup not embedded in a class
|
||||||
|
|
||||||
|
// Collect the covergroup's own members (sample/constructor args, prior generated
|
||||||
|
// members); references to those are legitimate and must not be rewritten.
|
||||||
|
std::set<const AstVar*> ownVars;
|
||||||
|
collectOwnVars(ownVars);
|
||||||
|
// Collect the enclosing class's inherited members; only references to these can be
|
||||||
|
// resolved through the back-pointer.
|
||||||
|
std::set<const AstVar*> enclosingVars;
|
||||||
|
collectEnclosingVars(enclosingVars);
|
||||||
|
|
||||||
|
// Gather the references to rewrite. Bail (keep the safety net) if any
|
||||||
|
// enclosing reference is not a direct member of the enclosing class, so a reference
|
||||||
|
// that cannot be resolved through the back-pointer is never silently rewritten.
|
||||||
|
std::vector<AstVarRef*> refsToRewrite;
|
||||||
|
std::vector<AstThisRef*> thisRefsToRewrite;
|
||||||
|
bool bail = false;
|
||||||
|
const auto scan = [&](AstNode* rootp) {
|
||||||
|
rootp->foreach([&](AstVarRef* refp) {
|
||||||
|
const AstVar* const varp = refp->varp();
|
||||||
|
if (!varp->isClassMember() || ownVars.count(varp)) return;
|
||||||
|
if (enclosingVars.count(varp)) {
|
||||||
|
refsToRewrite.push_back(refp);
|
||||||
|
} else {
|
||||||
|
bail = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rootp->foreach([&](AstThisRef* refp) {
|
||||||
|
const AstClassRefDType* const refDTypep
|
||||||
|
= VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
|
if (refDTypep && refDTypep->classp() == m_covergroupp) thisRefsToRewrite.push_back(refp);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
for (AstCoverpoint* const cpp : m_coverpoints) scan(cpp);
|
||||||
|
for (AstCoverCross* const crossp : m_coverCrosses) scan(crossp);
|
||||||
|
for (AstNode* itemp = m_covergroupp->membersp(); itemp; itemp = itemp->nextp()) {
|
||||||
|
if (AstCovergroup* const cgp = VN_CAST(itemp, Covergroup)) scan(cgp);
|
||||||
|
}
|
||||||
|
if (bail || (refsToRewrite.empty() && thisRefsToRewrite.empty())) return false;
|
||||||
|
|
||||||
|
// The covergroup must be constructed in the enclosing class so the back-pointer can
|
||||||
|
// be initialized; bail if no construction is found.
|
||||||
|
const std::vector<AstNodeAssign*> constructps = findCovergroupConstructions();
|
||||||
|
if (constructps.empty()) return false;
|
||||||
|
|
||||||
|
// 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};
|
||||||
|
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 handle right after construction: 'cgvar.__Vcg_enclosingp = this;'.
|
||||||
|
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 weakThisp = new AstCExpr{cfl};
|
||||||
|
weakThisp->add("decltype(");
|
||||||
|
weakThisp->add(lhsp->cloneTree(false));
|
||||||
|
weakThisp->add(")::weak(this)");
|
||||||
|
weakThisp->dtypep(enclDTypep);
|
||||||
|
constructp->addNextHere(new AstAssign{cfl, lhsp, weakThisp});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void visit(AstClass* nodep) override {
|
void visit(AstClass* nodep) override {
|
||||||
UINFO(9, "Visiting class: " << nodep->name() << " isCovergroup=" << nodep->isCovergroup());
|
UINFO(9, "Visiting class: " << nodep->name() << " isCovergroup=" << nodep->isCovergroup());
|
||||||
if (nodep->isCovergroup()) {
|
if (nodep->isCovergroup()) {
|
||||||
VL_RESTORER(m_covergroupp);
|
VL_RESTORER(m_covergroupp);
|
||||||
|
VL_RESTORER(m_embeddedVarp);
|
||||||
VL_RESTORER(m_sampleFuncp);
|
VL_RESTORER(m_sampleFuncp);
|
||||||
VL_RESTORER(m_constructorp);
|
VL_RESTORER(m_constructorp);
|
||||||
VL_RESTORER(m_coverpoints);
|
VL_RESTORER(m_coverpoints);
|
||||||
VL_RESTORER(m_coverpointMap);
|
VL_RESTORER(m_coverpointMap);
|
||||||
VL_RESTORER(m_coverCrosses);
|
VL_RESTORER(m_coverCrosses);
|
||||||
m_covergroupp = nodep;
|
m_covergroupp = nodep;
|
||||||
|
m_embeddedVarp = nullptr;
|
||||||
m_sampleFuncp = nullptr;
|
m_sampleFuncp = nullptr;
|
||||||
m_constructorp = nullptr;
|
m_constructorp = nullptr;
|
||||||
m_coverpoints.clear();
|
m_coverpoints.clear();
|
||||||
m_coverpointMap.clear();
|
m_coverpointMap.clear();
|
||||||
m_coverCrosses.clear();
|
m_coverCrosses.clear();
|
||||||
|
m_embeddedVarp = findEmbeddedCovergroupVar();
|
||||||
|
std::vector<EmbeddedEventTrigger> embeddedEventTriggers;
|
||||||
|
|
||||||
// Extract and store the clocking event from AstCovergroup node
|
// Extract and store the clocking event from AstCovergroup node
|
||||||
// The parser creates this node to preserve the event information
|
// The parser creates this node to preserve the event information
|
||||||
|
|
@ -1748,33 +2072,26 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
// event exists, so cgp->eventp() is always non-null here.
|
// event exists, so cgp->eventp() is always non-null here.
|
||||||
UASSERT_OBJ(cgp->eventp(), cgp,
|
UASSERT_OBJ(cgp->eventp(), cgp,
|
||||||
"Sentinel AstCovergroup in class must have non-null eventp");
|
"Sentinel AstCovergroup in class must have non-null eventp");
|
||||||
// Check if the clocking event references a member variable (unsupported)
|
embeddedEventTriggers = collectEmbeddedEventTriggers(cgp);
|
||||||
// Clocking events should be on signals/nets, not class members
|
if (!embeddedEventTriggers.empty()) {
|
||||||
bool eventUnsupported = false;
|
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
||||||
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;
|
itemp = nextp;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Remove the AstCovergroup node - either unsupported event or no event
|
if (hasEmbeddedEventExpr(cgp)) {
|
||||||
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
cgp->v3warn(COVERIGN, "Unsupported: 'covergroup' clocking event "
|
||||||
|
"on complex member expression");
|
||||||
|
hasUnsupportedEvent = true;
|
||||||
|
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
||||||
|
itemp = nextp;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
itemp = nextp;
|
itemp = nextp;
|
||||||
}
|
}
|
||||||
|
|
@ -1804,24 +2121,28 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
|
|
||||||
iterateChildren(nodep);
|
iterateChildren(nodep);
|
||||||
|
|
||||||
// Option B safety net for embedded covergroups: if a coverpoint/iff/cross
|
// Embedded covergroups (IEEE 1800-2023 19.4): coverage constructs may reference
|
||||||
// references a member of the enclosing class, lowering would emit uncompilable
|
// members of the enclosing class. The covergroup is lowered into a sibling
|
||||||
// C++ (no handle to the enclosing instance). Skip this covergroup with a clean
|
// class with no implicit handle to the enclosing instance, so install an
|
||||||
// warning rather than crashing the C++ compile. (Full support - an enclosing
|
// explicit back-pointer and route the references through it. For anything
|
||||||
// back-pointer - is the planned follow-up.)
|
// outside that form, fall back to the COVERIGN safety net:
|
||||||
if (AstNode* const offenderp = findEnclosingMemberRef(nodep)) {
|
// skip the covergroup with a clean warning rather than emitting uncompilable C++.
|
||||||
offenderp->v3warn(COVERIGN,
|
if (AstNode* const offenderp = findEnclosingMemberRef()) {
|
||||||
"Unsupported: 'covergroup' coverpoint referencing enclosing "
|
if (!installEnclosingBackPointer()) {
|
||||||
"class member; ignoring covergroup "
|
offenderp->v3warn(COVERIGN,
|
||||||
<< nodep->prettyNameQ());
|
"Unsupported: 'covergroup' coverage construct referencing "
|
||||||
for (AstCoverpoint* cpp : m_coverpoints) {
|
"enclosing class member; ignoring covergroup "
|
||||||
VL_DO_DANGLING(pushDeletep(cpp->unlinkFrBack()), cpp);
|
<< 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);
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
for (AstCoverCross* crossp : m_coverCrosses) {
|
|
||||||
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
installEmbeddedEventTriggers(embeddedEventTriggers);
|
||||||
|
|
||||||
processCovergroup();
|
processCovergroup();
|
||||||
// Remove lowered coverpoints/crosses from the class - they have been
|
// Remove lowered coverpoints/crosses from the class - they have been
|
||||||
|
|
@ -1833,6 +2154,10 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
|
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
|
||||||
}
|
}
|
||||||
} else {
|
} 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);
|
iterateChildren(nodep);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -249,8 +249,37 @@ class EmitCHeader final : public EmitCConstInit {
|
||||||
if (const AstClass* const classp = VN_CAST(modp, Class)) {
|
if (const AstClass* const classp = VN_CAST(modp, Class)) {
|
||||||
if (!classp->isInterfaceClass() && !classp->isVirtual()) {
|
if (!classp->isInterfaceClass() && !classp->isVirtual()) {
|
||||||
decorateFirst(first, section);
|
decorateFirst(first, section);
|
||||||
putns(classp, "VlClass* clone() const { return new "
|
std::vector<const AstVar*> embeddedCovergroupVars;
|
||||||
+ EmitCUtil::prefixNameProtect(classp) + "(*this); }\n");
|
std::vector<AstClass*> classes;
|
||||||
|
for (AstClass* scanClassp = const_cast<AstClass*>(classp); scanClassp;
|
||||||
|
scanClassp = scanClassp->extendsp() ? scanClassp->extendsp()->classp()
|
||||||
|
: nullptr) {
|
||||||
|
classes.push_back(scanClassp);
|
||||||
|
}
|
||||||
|
for (auto it = classes.rbegin(); it != classes.rend(); ++it) {
|
||||||
|
AstClass* const memberClassp = *it;
|
||||||
|
for (AstNode* stmtp = memberClassp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||||
|
AstVar* const varp = VN_CAST(stmtp, Var);
|
||||||
|
if (!varp) continue;
|
||||||
|
const AstClassRefDType* const refp
|
||||||
|
= VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
|
if (refp && refp->classp()->isCovergroup()) {
|
||||||
|
embeddedCovergroupVars.push_back(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 AstVar* const varp : embeddedCovergroupVars) {
|
||||||
|
puts("clonep->" + varp->nameProtect() + " = VlNull{}; ");
|
||||||
|
}
|
||||||
|
puts("return clonep; }\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3431,6 +3431,16 @@ class LinkDotResolveVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
return classSymp;
|
return classSymp;
|
||||||
}
|
}
|
||||||
|
VSymEnt* enclosingClassSympForCovergroup(VSymEnt* classSymp) {
|
||||||
|
if (!classSymp) return nullptr;
|
||||||
|
const AstClass* const classp = VN_CAST(classSymp->nodep(), Class);
|
||||||
|
if (!classp || !classp->isCovergroup()) return nullptr;
|
||||||
|
VSymEnt* parentSymp = classSymp->parentp();
|
||||||
|
while (parentSymp && !VN_IS(parentSymp->nodep(), Class)) {
|
||||||
|
parentSymp = parentSymp->parentp();
|
||||||
|
}
|
||||||
|
return parentSymp;
|
||||||
|
}
|
||||||
void importDerivedClass(AstClass* derivedClassp, VSymEnt* baseSymp, AstClass* baseClassp) {
|
void importDerivedClass(AstClass* derivedClassp, VSymEnt* baseSymp, AstClass* baseClassp) {
|
||||||
// Also used for standard 'extends' from a base class
|
// Also used for standard 'extends' from a base class
|
||||||
UINFO(8, indent() << "importDerivedClass to " << derivedClassp << " from " << baseClassp);
|
UINFO(8, indent() << "importDerivedClass to " << derivedClassp << " from " << baseClassp);
|
||||||
|
|
@ -4307,6 +4317,14 @@ class LinkDotResolveVisitor final : public VNVisitor {
|
||||||
} else {
|
} else {
|
||||||
foundp = m_ds.m_dotSymp->findIdFlat(nodep->name());
|
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") {
|
||||||
|
if (VSymEnt* const parentClassSymp
|
||||||
|
= enclosingClassSympForCovergroup(m_ds.m_dotSymp)) {
|
||||||
|
foundp = parentClassSymp->findIdFallback(nodep->name());
|
||||||
|
if (foundp) m_ds.m_dotSymp = parentClassSymp;
|
||||||
|
}
|
||||||
|
}
|
||||||
// If not found in modport, check interface fallback for parameters and typedefs.
|
// 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).
|
// Parameters and typedefs are always visible through a modport (IEEE 1800-2023 25.5).
|
||||||
// This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef).
|
// This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef).
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
__vlAnonCG_branch_cg.cp_branch.hi: 8
|
||||||
|
__vlAnonCG_branch_cg.cp_branch.lo: 8
|
||||||
|
__vlAnonCG_clock_cg.cp_clocked.hi: 8
|
||||||
|
__vlAnonCG_clock_cg.cp_clocked.lo: 8
|
||||||
|
__vlAnonCG_copy_cg.cp_copy.hi: 0
|
||||||
|
__vlAnonCG_copy_cg.cp_copy.lo: 1
|
||||||
|
__vlAnonCG_derived_cg.cp_inherited.hi: 8
|
||||||
|
__vlAnonCG_derived_cg.cp_inherited.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_this_cg.cp_this.hi: 8
|
||||||
|
__vlAnonCG_this_cg.cp_this.lo: 8
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
|
|
||||||
import vltest_bootstrap
|
import vltest_bootstrap
|
||||||
|
|
||||||
|
import coverage_covergroup_common
|
||||||
|
|
||||||
test.scenarios('vlt')
|
test.scenarios('vlt')
|
||||||
|
|
||||||
test.lint(expect_filename=test.golden_filename, fails=True)
|
coverage_covergroup_common.run(test)
|
||||||
|
|
||||||
test.passes()
|
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
// 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; 8.11 also allows 'this' within an embedded covergroup.
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
class BaseMonitor;
|
||||||
|
bit [3:0] inherited_value;
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class DerivedMonitor extends BaseMonitor;
|
||||||
|
covergroup derived_cg;
|
||||||
|
cp_inherited: coverpoint inherited_value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
|
endgroup
|
||||||
|
|
||||||
|
function new();
|
||||||
|
derived_cg = new;
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function void observe(bit [3:0] v);
|
||||||
|
inherited_value = v;
|
||||||
|
derived_cg.sample();
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class ThisMonitor;
|
||||||
|
bit [3:0] current;
|
||||||
|
|
||||||
|
covergroup this_cg;
|
||||||
|
cp_this: coverpoint this.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();
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class ClockEvent;
|
||||||
|
bit clk;
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class ClockMonitor;
|
||||||
|
ClockEvent ev;
|
||||||
|
bit [3:0] sampled;
|
||||||
|
|
||||||
|
covergroup clock_cg @(posedge ev.clk);
|
||||||
|
cp_clocked: coverpoint sampled {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
|
endgroup
|
||||||
|
|
||||||
|
function new();
|
||||||
|
ev = new;
|
||||||
|
ev.clk = 0;
|
||||||
|
clock_cg = new;
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function void observe(bit [3:0] v);
|
||||||
|
sampled = v;
|
||||||
|
ev.clk = 0;
|
||||||
|
ev.clk = 1;
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
module t;
|
||||||
|
Monitor mon;
|
||||||
|
BranchMonitor branch_a;
|
||||||
|
BranchMonitor branch_b;
|
||||||
|
DerivedMonitor derived;
|
||||||
|
ThisMonitor this_mon;
|
||||||
|
ClockMonitor clock_mon;
|
||||||
|
CopyMonitor copy_src;
|
||||||
|
CopyMonitor copy_dst;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
mon = new;
|
||||||
|
branch_a = new(1);
|
||||||
|
branch_b = new(0);
|
||||||
|
derived = new;
|
||||||
|
this_mon = new;
|
||||||
|
clock_mon = new;
|
||||||
|
copy_src = new;
|
||||||
|
|
||||||
|
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]);
|
||||||
|
this_mon.observe(i[3:0]);
|
||||||
|
clock_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);
|
||||||
|
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
%Warning-COVERIGN: t/t_covergroup_embedded_unsup.v:27:34: Unsupported: 'covergroup' coverpoint referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov_trans'
|
|
||||||
: ... note: In instance 't'
|
|
||||||
27 | trans_start_addr: coverpoint trans_collected.addr {option.auto_bin_max = 16;}
|
|
||||||
| ^~~~~~~~~~~~~~~
|
|
||||||
... 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.
|
|
||||||
%Error: Exiting due to
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
// 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
|
|
||||||
|
|
||||||
// Test the graceful-degradation safety net for embedded covergroups (the dominant
|
|
||||||
// UVM pattern: a covergroup declared inside a class whose coverpoints reference the
|
|
||||||
// enclosing object's members). Such a covergroup is lowered into a sibling class
|
|
||||||
// with no handle to the enclosing instance, so emitting it would produce
|
|
||||||
// uncompilable C++ ("invalid use of non-static data member"). Until the enclosing
|
|
||||||
// back-pointer feature exists, Verilator must emit a clean COVERIGN warning and skip
|
|
||||||
// lowering the covergroup, rather than crashing the C++ compile.
|
|
||||||
|
|
||||||
class ubus_transfer;
|
|
||||||
bit [15:0] addr;
|
|
||||||
bit read_write;
|
|
||||||
endclass
|
|
||||||
|
|
||||||
class ubus_master_monitor;
|
|
||||||
ubus_transfer trans_collected;
|
|
||||||
|
|
||||||
// Coverpoints reference 'trans_collected', a member of the enclosing class.
|
|
||||||
// A cross is included so the safety-net cleanup also exercises cross removal.
|
|
||||||
covergroup cov_trans;
|
|
||||||
trans_start_addr: coverpoint trans_collected.addr {option.auto_bin_max = 16;}
|
|
||||||
trans_dir: coverpoint trans_collected.read_write;
|
|
||||||
trans_addr_x_dir : cross trans_start_addr, trans_dir;
|
|
||||||
endgroup
|
|
||||||
|
|
||||||
function new();
|
|
||||||
trans_collected = new;
|
|
||||||
cov_trans = new;
|
|
||||||
endfunction
|
|
||||||
endclass
|
|
||||||
|
|
||||||
module t;
|
|
||||||
ubus_master_monitor m;
|
|
||||||
initial begin
|
|
||||||
m = new;
|
|
||||||
$write("*-* All Finished *-*\n");
|
|
||||||
$finish;
|
|
||||||
end
|
|
||||||
endmodule
|
|
||||||
Loading…
Reference in New Issue