Fix external clock driven coverage capture
This commit is contained in:
parent
007eac25fd
commit
caa3d6512f
|
|
@ -2103,7 +2103,7 @@ public:
|
||||||
VlClassRef() = default;
|
VlClassRef() = default;
|
||||||
// Init with nullptr
|
// Init with nullptr
|
||||||
// cppcheck-suppress noExplicitConstructor
|
// cppcheck-suppress noExplicitConstructor
|
||||||
VlClassRef(VlNull){};
|
VlClassRef(VlNull) {};
|
||||||
template <typename... T_Args>
|
template <typename... T_Args>
|
||||||
VlClassRef(VlDeleter& deleter, T_Args&&... args)
|
VlClassRef(VlDeleter& deleter, T_Args&&... args)
|
||||||
: m_objp{new T_Class} {
|
: m_objp{new T_Class} {
|
||||||
|
|
@ -2158,6 +2158,12 @@ public:
|
||||||
~VlClassRef() { refCountDec(); }
|
~VlClassRef() { refCountDec(); }
|
||||||
|
|
||||||
// METHODS
|
// METHODS
|
||||||
|
/// Create a non-owning ("weak") reference: it does not change the reference count, so the
|
||||||
|
/// pointed-to object must be kept alive by some other (strong) reference for as long as
|
||||||
|
/// this one is used. Dereferencing a weak reference after the object has been freed is
|
||||||
|
/// undefined. Used only for an embedded covergroup's back-pointer to its enclosing
|
||||||
|
/// object, which the enclosing object transitively owns, so the back-pointer is always
|
||||||
|
/// outlived by a strong reference (IEEE 1800-2023 19.4).
|
||||||
static VlClassRef weak(T_Class* objp) {
|
static VlClassRef weak(T_Class* objp) {
|
||||||
VlClassRef ref;
|
VlClassRef ref;
|
||||||
ref.m_objp = objp;
|
ref.m_objp = objp;
|
||||||
|
|
@ -2252,6 +2258,14 @@ static inline bool VL_CAST_DYNAMIC(VlClassRef<T_Lhs> in, VlClassRef<T_Out>& outr
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a non-owning ("weak") reference to an existing object, deducing the class type from
|
||||||
|
// the pointer. See VlClassRef::weak; used for an embedded covergroup's back-pointer to its
|
||||||
|
// enclosing object so the back-pointer does not keep the enclosing object alive.
|
||||||
|
template <typename T_Class>
|
||||||
|
static inline VlClassRef<T_Class> VL_WEAK_REF(T_Class* objp) {
|
||||||
|
return VlClassRef<T_Class>::weak(objp);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T_Lhs>
|
template <typename T_Lhs>
|
||||||
static inline bool VL_CAST_DYNAMIC(VlNull, VlClassRef<T_Lhs>& outr) {
|
static inline bool VL_CAST_DYNAMIC(VlNull, VlClassRef<T_Lhs>& outr) {
|
||||||
outr = VlNull{};
|
outr = VlNull{};
|
||||||
|
|
|
||||||
|
|
@ -73,13 +73,15 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
|
std::vector<BinInfo> m_binInfos; // All bins in current covergroup
|
||||||
|
|
||||||
struct EmbeddedEventTrigger final {
|
struct EmbeddedEventTrigger final {
|
||||||
AstVar* baseVarp;
|
FileLine* eventFl; // Source location of the clocking-event item (for diagnostics)
|
||||||
AstVar* memberVarp;
|
AstVar* baseVarp; // Enclosing-class member naming the event signal
|
||||||
VEdgeType edgeType;
|
AstVar* memberVarp; // Sub-member when the event is 'base.member', else nullptr
|
||||||
AstVar* prevVarp;
|
VEdgeType edgeType; // Edge sensitivity (posedge/negedge/changed)
|
||||||
EmbeddedEventTrigger(AstVar* baseVarp, AstVar* memberVarp, VEdgeType edgeType,
|
AstVar* prevVarp; // Previous-value member; created lazily during install
|
||||||
AstVar* prevVarp)
|
EmbeddedEventTrigger(FileLine* eventFl, AstVar* baseVarp, AstVar* memberVarp,
|
||||||
: baseVarp{baseVarp}
|
VEdgeType edgeType, AstVar* prevVarp)
|
||||||
|
: eventFl{eventFl}
|
||||||
|
, baseVarp{baseVarp}
|
||||||
, memberVarp{memberVarp}
|
, memberVarp{memberVarp}
|
||||||
, edgeType{edgeType}
|
, edgeType{edgeType}
|
||||||
, prevVarp{prevVarp} {}
|
, prevVarp{prevVarp} {}
|
||||||
|
|
@ -1728,6 +1730,10 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
}
|
}
|
||||||
|
|
||||||
AstVar* findEmbeddedCovergroupVar() const {
|
AstVar* findEmbeddedCovergroupVar() const {
|
||||||
|
// Return the enclosing-class member that holds this embedded covergroup instance.
|
||||||
|
// An embedded covergroup declaration declares a single anonymous type and a single
|
||||||
|
// instance variable of that type (IEEE 1800-2023 19.4); a second variable of the same
|
||||||
|
// anonymous type is not expressible, so the first match is the sole instance.
|
||||||
if (!m_enclosingClassp) return nullptr;
|
if (!m_enclosingClassp) return nullptr;
|
||||||
for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) {
|
for (AstNode* itemp = m_enclosingClassp->membersp(); itemp; itemp = itemp->nextp()) {
|
||||||
if (AstVar* const varp = VN_CAST(itemp, Var)) {
|
if (AstVar* const varp = VN_CAST(itemp, Var)) {
|
||||||
|
|
@ -1743,7 +1749,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
|
|
||||||
bool rewriteThisRef(AstThisRef* refp, AstVar* handleVarp) {
|
bool rewriteThisRef(AstThisRef* refp, AstVar* handleVarp) {
|
||||||
if (!m_enclosingClassp) return false;
|
if (!m_enclosingClassp) return false;
|
||||||
const AstClassRefDType* const refDTypep = VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
const AstClassRefDType* const refDTypep
|
||||||
|
= VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
if (!refDTypep || refDTypep->classp() != m_covergroupp) return false;
|
if (!refDTypep || refDTypep->classp() != m_covergroupp) return false;
|
||||||
AstNodeExpr* const newp = newEnclosingHandleRef(refp->fileline(), handleVarp);
|
AstNodeExpr* const newp = newEnclosingHandleRef(refp->fileline(), handleVarp);
|
||||||
refp->replaceWith(newp);
|
refp->replaceWith(newp);
|
||||||
|
|
@ -1825,6 +1832,36 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
return new AstVarRef{fl, varp, VAccess::READ};
|
return new AstVarRef{fl, varp, VAccess::READ};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AstNodeExpr* newEmbeddedVarNonNull(FileLine* fl) {
|
||||||
|
return new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ},
|
||||||
|
new AstConst{fl, AstConst::Null{}}};
|
||||||
|
}
|
||||||
|
|
||||||
|
void installEmbeddedEventFork(AstSenTree* eventp) {
|
||||||
|
// Full clocking-event support for an embedded covergroup (IEEE 1800-2023 19.8.1):
|
||||||
|
// a coverage point is sampled the instant the clocking event takes place, on every
|
||||||
|
// occurrence, regardless of where the event signal is written. The covergroup is a
|
||||||
|
// dynamic per-instance object, so static sensitivity blocks (V3Active) cannot model
|
||||||
|
// this. Instead spawn, in the enclosing constructor right after construction, a
|
||||||
|
// persistent process:
|
||||||
|
// fork forever begin @(event); if (cgvar != null) cgvar.sample(); end join_none
|
||||||
|
// The event SenTree references enclosing-class members, which resolve directly against
|
||||||
|
// 'this' in the constructor. This requires --timing (fork + event control); the caller
|
||||||
|
// only reaches here when timing is enabled. 'eventp' is an unlinked SenTree template
|
||||||
|
// owned here.
|
||||||
|
const std::vector<AstNodeAssign*> constructps = findCovergroupConstructions();
|
||||||
|
for (AstNodeAssign* const constructp : constructps) {
|
||||||
|
FileLine* const fl = constructp->fileline();
|
||||||
|
AstLoop* const loopp = new AstLoop{fl};
|
||||||
|
loopp->addStmtsp(new AstEventControl{fl, eventp->cloneTree(false), nullptr});
|
||||||
|
loopp->addStmtsp(new AstIf{fl, newEmbeddedVarNonNull(fl), newSampleStmt(fl)});
|
||||||
|
AstFork* const forkp = new AstFork{fl, VJoinType::JOIN_NONE};
|
||||||
|
forkp->addForksp(new AstBegin{fl, "", loopp, true});
|
||||||
|
constructp->addNextHere(forkp);
|
||||||
|
}
|
||||||
|
VL_DO_DANGLING(pushDeletep(eventp), eventp);
|
||||||
|
}
|
||||||
|
|
||||||
AstNodeExpr* newEventReadyCondition(FileLine* fl, const EmbeddedEventTrigger& trigger) {
|
AstNodeExpr* newEventReadyCondition(FileLine* fl, const EmbeddedEventTrigger& trigger) {
|
||||||
AstNodeExpr* const curp = newEventRead(fl, trigger);
|
AstNodeExpr* const curp = newEventRead(fl, trigger);
|
||||||
AstNodeExpr* const prevp = newVarRead(fl, trigger.prevVarp);
|
AstNodeExpr* const prevp = newVarRead(fl, trigger.prevVarp);
|
||||||
|
|
@ -1836,16 +1873,15 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
} else {
|
} else {
|
||||||
edgep = new AstNeq{fl, curp, prevp};
|
edgep = new AstNeq{fl, curp, prevp};
|
||||||
}
|
}
|
||||||
return new AstLogAnd{
|
return new AstLogAnd{fl,
|
||||||
fl, new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ},
|
new AstNeq{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ},
|
||||||
new AstConst{fl, AstConst::Null{}}},
|
new AstConst{fl, AstConst::Null{}}},
|
||||||
edgep};
|
edgep};
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNodeStmt* newSampleStmt(FileLine* fl) {
|
AstNodeStmt* newSampleStmt(FileLine* fl) {
|
||||||
AstMethodCall* const callp
|
AstMethodCall* const callp = new AstMethodCall{
|
||||||
= new AstMethodCall{fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, "sample",
|
fl, new AstVarRef{fl, m_embeddedVarp, VAccess::READ}, "sample", nullptr};
|
||||||
nullptr};
|
|
||||||
callp->taskp(m_sampleFuncp);
|
callp->taskp(m_sampleFuncp);
|
||||||
callp->dtypeSetVoid();
|
callp->dtypeSetVoid();
|
||||||
return callp->makeStmt();
|
return callp->makeStmt();
|
||||||
|
|
@ -1871,28 +1907,45 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
AstVar* memberVarp = nullptr;
|
AstVar* memberVarp = nullptr;
|
||||||
UASSERT_OBJ(parseEmbeddedEventExpr(itemp->sensp(), baseVarp, memberVarp), itemp,
|
UASSERT_OBJ(parseEmbeddedEventExpr(itemp->sensp(), baseVarp, memberVarp), itemp,
|
||||||
"Bad embedded covergroup event expression");
|
"Bad embedded covergroup event expression");
|
||||||
FileLine* const fl = itemp->fileline();
|
// Defer the previous-value member to installEmbeddedEventTriggers so it is only
|
||||||
EmbeddedEventTrigger trigger{baseVarp, memberVarp, itemp->edgeType(), nullptr};
|
// created once an in-class assignment to instrument is known to exist.
|
||||||
AstVar* const prevVarp = newEventPrevVar(fl, trigger);
|
triggers.emplace_back(itemp->fileline(), baseVarp, memberVarp, itemp->edgeType(),
|
||||||
m_enclosingClassp->addMembersp(prevVarp);
|
nullptr);
|
||||||
trigger.prevVarp = prevVarp;
|
|
||||||
triggers.push_back(trigger);
|
|
||||||
}
|
}
|
||||||
return triggers;
|
return triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void installEmbeddedEventTriggers(const std::vector<EmbeddedEventTrigger>& triggers) {
|
void installEmbeddedEventTriggers(std::vector<EmbeddedEventTrigger>& triggers) {
|
||||||
if (triggers.empty()) return;
|
// An embedded covergroup is lowered into a sibling class, so a clocking event on an
|
||||||
m_enclosingClassp->foreach([&](AstNodeAssign* asgnp) {
|
// enclosing-class member cannot be placed in a static sensitivity list. Approximate
|
||||||
for (const EmbeddedEventTrigger& trigger : triggers) {
|
// it by sampling immediately after each blocking assignment to the event signal that
|
||||||
if (!isEventLvalue(asgnp->lhsp(), trigger)) continue;
|
// appears textually within the enclosing class. This is only an approximation: it
|
||||||
|
// cannot observe changes made from outside the class (e.g. a testbench poking a public
|
||||||
|
// member) nor model the exact scheduling region of a real clocking event. When the
|
||||||
|
// signal is never assigned inside the class no sampling is possible at all, so warn
|
||||||
|
// rather than silently producing zero coverage (IEEE 1800-2023 19.4).
|
||||||
|
for (EmbeddedEventTrigger& trigger : triggers) {
|
||||||
|
std::vector<AstNodeAssign*> assignps;
|
||||||
|
m_enclosingClassp->foreach([&](AstNodeAssign* asgnp) {
|
||||||
|
if (isEventLvalue(asgnp->lhsp(), trigger)) assignps.push_back(asgnp);
|
||||||
|
});
|
||||||
|
if (assignps.empty()) {
|
||||||
|
trigger.eventFl->v3warn(
|
||||||
|
COVERIGN, "Unsupported: 'covergroup' clocking event signal has no assignment "
|
||||||
|
"within the enclosing class; no coverage sampled");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AstVar* const prevVarp = newEventPrevVar(trigger.eventFl, trigger);
|
||||||
|
m_enclosingClassp->addMembersp(prevVarp);
|
||||||
|
trigger.prevVarp = prevVarp;
|
||||||
|
for (AstNodeAssign* const asgnp : assignps) {
|
||||||
FileLine* const fl = asgnp->fileline();
|
FileLine* const fl = asgnp->fileline();
|
||||||
AstIf* const ifp
|
AstIf* const ifp
|
||||||
= new AstIf{fl, newEventReadyCondition(fl, trigger), newSampleStmt(fl)};
|
= new AstIf{fl, newEventReadyCondition(fl, trigger), newSampleStmt(fl)};
|
||||||
ifp->addNextHere(newPrevUpdate(fl, trigger));
|
ifp->addNextHere(newPrevUpdate(fl, trigger));
|
||||||
asgnp->addNextHere(ifp);
|
asgnp->addNextHere(ifp);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNode* findEnclosingMemberRef() {
|
AstNode* findEnclosingMemberRef() {
|
||||||
|
|
@ -1937,7 +1990,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
// assignments so each constructed instance receives its back-pointer.
|
// assignments so each constructed instance receives its back-pointer.
|
||||||
std::vector<AstNodeAssign*> foundps;
|
std::vector<AstNodeAssign*> foundps;
|
||||||
if (!m_embeddedVarp) return foundps;
|
if (!m_embeddedVarp) return foundps;
|
||||||
AstFunc* const enclosingNewp = VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func);
|
AstFunc* const enclosingNewp
|
||||||
|
= VN_CAST(m_memberMap.findMember(m_enclosingClassp, "new"), Func);
|
||||||
if (!enclosingNewp) return foundps;
|
if (!enclosingNewp) return foundps;
|
||||||
enclosingNewp->foreach([&](AstNodeAssign* asgnp) {
|
enclosingNewp->foreach([&](AstNodeAssign* asgnp) {
|
||||||
const AstNew* const newp = VN_CAST(asgnp->rhsp(), New);
|
const AstNew* const newp = VN_CAST(asgnp->rhsp(), New);
|
||||||
|
|
@ -1992,7 +2046,8 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
rootp->foreach([&](AstThisRef* refp) {
|
rootp->foreach([&](AstThisRef* refp) {
|
||||||
const AstClassRefDType* const refDTypep
|
const AstClassRefDType* const refDTypep
|
||||||
= VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
= VN_CAST(refp->dtypep()->skipRefp(), ClassRefDType);
|
||||||
if (refDTypep && refDTypep->classp() == m_covergroupp) thisRefsToRewrite.push_back(refp);
|
if (refDTypep && refDTypep->classp() == m_covergroupp)
|
||||||
|
thisRefsToRewrite.push_back(refp);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
for (AstCoverpoint* const cpp : m_coverpoints) scan(cpp);
|
for (AstCoverpoint* const cpp : m_coverpoints) scan(cpp);
|
||||||
|
|
@ -2009,8 +2064,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
|
|
||||||
// Commit: add the back-pointer member, rewrite the references, initialize the handle.
|
// Commit: add the back-pointer member, rewrite the references, initialize the handle.
|
||||||
FileLine* const fl = m_covergroupp->fileline();
|
FileLine* const fl = m_covergroupp->fileline();
|
||||||
AstClassRefDType* const enclDTypep
|
AstClassRefDType* const enclDTypep = new AstClassRefDType{fl, m_enclosingClassp, nullptr};
|
||||||
= new AstClassRefDType{fl, m_enclosingClassp, nullptr};
|
|
||||||
v3Global.rootp()->typeTablep()->addTypesp(enclDTypep);
|
v3Global.rootp()->typeTablep()->addTypesp(enclDTypep);
|
||||||
AstVar* const handleVarp
|
AstVar* const handleVarp
|
||||||
= new AstVar{fl, VVarType::MEMBER, "__Vcg_enclosingp", enclDTypep};
|
= new AstVar{fl, VVarType::MEMBER, "__Vcg_enclosingp", enclDTypep};
|
||||||
|
|
@ -2018,23 +2072,21 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
m_covergroupp->addMembersp(handleVarp);
|
m_covergroupp->addMembersp(handleVarp);
|
||||||
|
|
||||||
// Route each enclosing-member reference through the back-pointer: 'm' -> 'h.m'.
|
// Route each enclosing-member reference through the back-pointer: 'm' -> 'h.m'.
|
||||||
for (AstVarRef* const refp : refsToRewrite) {
|
for (AstVarRef* const refp : refsToRewrite) { rewriteVarRef(refp, handleVarp); }
|
||||||
rewriteVarRef(refp, handleVarp);
|
for (AstThisRef* const refp : thisRefsToRewrite) { rewriteThisRef(refp, handleVarp); }
|
||||||
}
|
|
||||||
for (AstThisRef* const refp : thisRefsToRewrite) {
|
|
||||||
rewriteThisRef(refp, handleVarp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the handle right after construction: 'cgvar.__Vcg_enclosingp = this;'.
|
// Initialize the handle right after construction: 'cgvar.__Vcg_enclosingp = this;'.
|
||||||
|
// The back-pointer must be a non-owning (weak) reference so it does not keep the
|
||||||
|
// enclosing object alive (the enclosing object owns the covergroup). There is no AST
|
||||||
|
// node for a weak class-handle literal, so emit the runtime helper VL_WEAK_REF; it
|
||||||
|
// contains no source identifiers, so '--protect' is unaffected.
|
||||||
for (AstNodeAssign* const constructp : constructps) {
|
for (AstNodeAssign* const constructp : constructps) {
|
||||||
FileLine* const cfl = constructp->fileline();
|
FileLine* const cfl = constructp->fileline();
|
||||||
AstMemberSel* const lhsp
|
AstMemberSel* const lhsp
|
||||||
= new AstMemberSel{cfl, constructp->lhsp()->cloneTree(false), handleVarp};
|
= new AstMemberSel{cfl, constructp->lhsp()->cloneTree(false), handleVarp};
|
||||||
lhsp->access(VAccess::WRITE);
|
lhsp->access(VAccess::WRITE);
|
||||||
AstCExpr* const weakThisp = new AstCExpr{cfl};
|
AstCExpr* const weakThisp = new AstCExpr{cfl};
|
||||||
weakThisp->add("decltype(");
|
weakThisp->add("VL_WEAK_REF(this)");
|
||||||
weakThisp->add(lhsp->cloneTree(false));
|
|
||||||
weakThisp->add(")::weak(this)");
|
|
||||||
weakThisp->dtypep(enclDTypep);
|
weakThisp->dtypep(enclDTypep);
|
||||||
constructp->addNextHere(new AstAssign{cfl, lhsp, weakThisp});
|
constructp->addNextHere(new AstAssign{cfl, lhsp, weakThisp});
|
||||||
}
|
}
|
||||||
|
|
@ -2060,6 +2112,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
m_coverCrosses.clear();
|
m_coverCrosses.clear();
|
||||||
m_embeddedVarp = findEmbeddedCovergroupVar();
|
m_embeddedVarp = findEmbeddedCovergroupVar();
|
||||||
std::vector<EmbeddedEventTrigger> embeddedEventTriggers;
|
std::vector<EmbeddedEventTrigger> embeddedEventTriggers;
|
||||||
|
// Clocking event referencing an enclosing member, lowered via a forked sampling
|
||||||
|
// process when --timing is available (full support); null otherwise.
|
||||||
|
AstSenTree* embeddedEventForkp = nullptr;
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -2072,23 +2127,33 @@ 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");
|
||||||
embeddedEventTriggers = collectEmbeddedEventTriggers(cgp);
|
|
||||||
if (!embeddedEventTriggers.empty()) {
|
|
||||||
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
|
||||||
itemp = nextp;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (hasEmbeddedEventExpr(cgp)) {
|
if (hasEmbeddedEventExpr(cgp)) {
|
||||||
cgp->v3warn(COVERIGN, "Unsupported: 'covergroup' clocking event "
|
// The clocking event references a member of the enclosing class, so it
|
||||||
"on complex member expression");
|
// names a per-instance signal that V3Active's static-sensitivity path
|
||||||
hasUnsupportedEvent = true;
|
// cannot express. Under --timing, lower it fully: a forked
|
||||||
|
// 'forever @(event) sample()' process spawned in the enclosing
|
||||||
|
// constructor samples on every occurrence of the event regardless of
|
||||||
|
// where the signal is written (IEEE 1800-2023 19.8.1). Without
|
||||||
|
// --timing, dynamic per-instance event waits are unavailable, so fall
|
||||||
|
// back to a best-effort instrumentation of in-class assignments.
|
||||||
|
if (v3Global.opt.timing().isSetTrue()) {
|
||||||
|
embeddedEventForkp = cgp->eventp()->unlinkFrBack();
|
||||||
|
} else {
|
||||||
|
embeddedEventTriggers = collectEmbeddedEventTriggers(cgp);
|
||||||
|
if (embeddedEventTriggers.empty()) {
|
||||||
|
cgp->v3warn(COVERIGN,
|
||||||
|
"Unsupported: 'covergroup' clocking event on complex "
|
||||||
|
"member expression; use --timing for full support");
|
||||||
|
hasUnsupportedEvent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
||||||
itemp = nextp;
|
itemp = nextp;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Leave cgp in the class membersp so the SenTree stays linked in the AST.
|
// Event references only static/global signals. Leave cgp in the class
|
||||||
// V3Active will find it via membersp, use the event, then delete the
|
// membersp so the SenTree stays linked in the AST. V3Active will find it
|
||||||
// AstCovergroup itself.
|
// via membersp, use the event, then delete the AstCovergroup itself.
|
||||||
UINFO(4, "Keeping covergroup event node for V3Active: " << nodep->name());
|
UINFO(4, "Keeping covergroup event node for V3Active: " << nodep->name());
|
||||||
itemp = nextp;
|
itemp = nextp;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -2139,10 +2204,15 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||||
for (AstCoverCross* crossp : m_coverCrosses) {
|
for (AstCoverCross* crossp : m_coverCrosses) {
|
||||||
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
|
VL_DO_DANGLING(pushDeletep(crossp->unlinkFrBack()), crossp);
|
||||||
}
|
}
|
||||||
|
// The covergroup is ignored, so its sampling process is moot.
|
||||||
|
if (embeddedEventForkp) {
|
||||||
|
VL_DO_DANGLING(pushDeletep(embeddedEventForkp), embeddedEventForkp);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
installEmbeddedEventTriggers(embeddedEventTriggers);
|
installEmbeddedEventTriggers(embeddedEventTriggers);
|
||||||
|
if (embeddedEventForkp) installEmbeddedEventFork(embeddedEventForkp);
|
||||||
|
|
||||||
processCovergroup();
|
processCovergroup();
|
||||||
// Remove lowered coverpoints/crosses from the class - they have been
|
// Remove lowered coverpoints/crosses from the class - they have been
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ def covergroup_coverage_report(test, outfile=None):
|
||||||
return outfile
|
return outfile
|
||||||
|
|
||||||
|
|
||||||
def run(test, *, verilator_flags2=()):
|
def run(test, *, verilator_flags2=(), timing_loop=False):
|
||||||
test.compile(verilator_flags2=['--coverage', *verilator_flags2])
|
test.compile(verilator_flags2=['--coverage', *verilator_flags2], timing_loop=timing_loop)
|
||||||
test.execute()
|
test.execute()
|
||||||
covergroup_coverage_report(test)
|
covergroup_coverage_report(test)
|
||||||
test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename)
|
test.files_identical(test.obj_dir + '/covergroup_report.txt', test.golden_filename)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
__vlAnonCG_chain_cg.cp_val.hi: 4
|
||||||
|
__vlAnonCG_chain_cg.cp_val.lo: 3
|
||||||
|
__vlAnonCG_ext_cg.cp_val.hi: 8
|
||||||
|
__vlAnonCG_ext_cg.cp_val.lo: 0
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
#!/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')
|
||||||
|
|
||||||
|
# --timing enables full embedded-covergroup clocking-event support: the covergroup is
|
||||||
|
# sampled on every occurrence of the event, including events driven from outside the
|
||||||
|
# enclosing class and complex member-chain events.
|
||||||
|
coverage_covergroup_common.run(test, verilator_flags2=['--timing'], timing_loop=True)
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// Full clocking-event support for embedded covergroups (IEEE 1800-2023 19.8.1):
|
||||||
|
// with --timing a covergroup is sampled on every occurrence of its clocking event,
|
||||||
|
// regardless of where the event signal is written. This exercises events driven
|
||||||
|
// from OUTSIDE the enclosing class (impossible to model without --timing) and a
|
||||||
|
// complex member-chain event, both of which the --no-timing path cannot sample.
|
||||||
|
|
||||||
|
class ExtClkMonitor;
|
||||||
|
bit clk; // Clocking-event signal driven only from outside the class
|
||||||
|
bit [3:0] value;
|
||||||
|
|
||||||
|
covergroup ext_cg @(posedge clk);
|
||||||
|
cp_val: coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
|
endgroup
|
||||||
|
|
||||||
|
function new();
|
||||||
|
ext_cg = new;
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class Lvl;
|
||||||
|
bit ev;
|
||||||
|
endclass
|
||||||
|
class Mid;
|
||||||
|
Lvl lvl;
|
||||||
|
endclass
|
||||||
|
|
||||||
|
class ChainMonitor;
|
||||||
|
bit a; // First clocking term, driven externally
|
||||||
|
Mid mid; // Second clocking term reached via a member chain, driven externally
|
||||||
|
bit [3:0] value;
|
||||||
|
|
||||||
|
covergroup chain_cg @(posedge a or posedge mid.lvl.ev);
|
||||||
|
cp_val: coverpoint value {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
|
endgroup
|
||||||
|
|
||||||
|
function new();
|
||||||
|
mid = new;
|
||||||
|
mid.lvl = new;
|
||||||
|
chain_cg = new;
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
module t;
|
||||||
|
ExtClkMonitor extm;
|
||||||
|
ChainMonitor chain;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
extm = new;
|
||||||
|
chain = new;
|
||||||
|
|
||||||
|
// Drive the clocking event from OUTSIDE the class: 8 external posedges, all
|
||||||
|
// sampling the 'hi' range -> cp_value.hi = 8, cp_value.lo = 0.
|
||||||
|
for (i = 0; i < 8; ++i) begin
|
||||||
|
extm.value = 4'h8 | i[3:0];
|
||||||
|
extm.clk = 1'b0;
|
||||||
|
#1;
|
||||||
|
extm.clk = 1'b1;
|
||||||
|
#1;
|
||||||
|
end
|
||||||
|
|
||||||
|
// Complex member-chain event: 4 posedges on 'a' (hi range) and 3 posedges on
|
||||||
|
// 'mid.lvl.ev' (lo range), all driven externally -> cp_value.hi = 4, .lo = 3.
|
||||||
|
for (i = 0; i < 4; ++i) begin
|
||||||
|
chain.value = 4'hC;
|
||||||
|
chain.a = 1'b0;
|
||||||
|
#1;
|
||||||
|
chain.a = 1'b1;
|
||||||
|
#1;
|
||||||
|
end
|
||||||
|
for (i = 0; i < 3; ++i) begin
|
||||||
|
chain.value = 4'h2;
|
||||||
|
chain.mid.lvl.ev = 1'b0;
|
||||||
|
#1;
|
||||||
|
chain.mid.lvl.ev = 1'b1;
|
||||||
|
#1;
|
||||||
|
end
|
||||||
|
|
||||||
|
#1;
|
||||||
|
$write("*-* All Finished *-*\n");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
endmodule
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:13:5: Unsupported: 'covergroup' clocking event on member variable
|
%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:19:18: Unsupported: 'covergroup' coverage construct referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov_nonew'
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
13 | covergroup cov1 @m_z;
|
19 | coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
| ^~~~~~~~~~
|
| ^~~
|
||||||
... For warning description see https://verilator.org/warn/COVERIGN?v=latest
|
... 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.
|
... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message.
|
||||||
|
%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:28:29: Unsupported: 'covergroup' clocking event signal has no assignment within the enclosing class; no coverage sampled
|
||||||
|
28 | covergroup cov_extclk @(posedge clk);
|
||||||
|
| ^~~~~~~
|
||||||
|
%Warning-COVERIGN: t/t_covergroup_member_event_unsup.v:48:5: Unsupported: 'covergroup' clocking event on complex member expression; use --timing for full support
|
||||||
|
: ... note: In instance 't'
|
||||||
|
48 | covergroup cov_cplx @(posedge a or posedge mid.lvl.ev);
|
||||||
|
| ^~~~~~~~~~
|
||||||
%Error: Exiting due to
|
%Error: Exiting due to
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ import vltest_bootstrap
|
||||||
|
|
||||||
test.scenarios('vlt')
|
test.scenarios('vlt')
|
||||||
|
|
||||||
test.lint(expect_filename=test.golden_filename, fails=True)
|
# Without --timing, an embedded covergroup clocked on (or otherwise referencing) an
|
||||||
|
# enclosing-class member cannot be fully supported: dynamic per-instance event waits are
|
||||||
|
# unavailable. Verilator emits a clean COVERIGN warning for each such limitation. With
|
||||||
|
# --timing these same covergroups are fully supported (see t_covergroup_embedded_timing).
|
||||||
|
test.lint(verilator_flags2=['--no-timing'], expect_filename=test.golden_filename, fails=True)
|
||||||
|
|
||||||
test.passes()
|
test.passes()
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,65 @@
|
||||||
// SPDX-FileCopyrightText: 2024 Wilson Snyder
|
// SPDX-FileCopyrightText: 2024 Wilson Snyder
|
||||||
// SPDX-License-Identifier: CC0-1.0
|
// SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
module t (
|
// Embedded covergroups (IEEE 1800-2023 19.4) whose clocking event or coverage
|
||||||
input clk
|
// constructs reference enclosing-class members in ways Verilator cannot fully
|
||||||
);
|
// support. Each must emit a clean COVERIGN warning rather than silently
|
||||||
class Packet;
|
// producing wrong coverage or uncompilable C++.
|
||||||
int m_z;
|
|
||||||
int m_x;
|
module t;
|
||||||
covergroup cov1 @m_z;
|
// Coverpoint references an enclosing member, but the covergroup is never
|
||||||
coverpoint m_x;
|
// constructed (no 'new'), so it cannot be lowered through a back-pointer.
|
||||||
|
class NoConstruct;
|
||||||
|
bit [3:0] m_x;
|
||||||
|
bit m_z;
|
||||||
|
covergroup cov_nonew @m_z;
|
||||||
|
coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
endgroup
|
endgroup
|
||||||
endclass
|
endclass
|
||||||
|
|
||||||
|
// Clocking event on a member that is never assigned inside the class, so no
|
||||||
|
// in-class sampling point exists (e.g. the signal is driven only externally).
|
||||||
|
class ExternalClk;
|
||||||
|
bit clk;
|
||||||
|
bit [3:0] m_x;
|
||||||
|
covergroup cov_extclk @(posedge clk);
|
||||||
|
coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
|
endgroup
|
||||||
|
function new();
|
||||||
|
cov_extclk = new;
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
// Clocking event mixing a member signal with a complex member chain that the
|
||||||
|
// member-event lowering cannot represent.
|
||||||
|
class Lvl;
|
||||||
|
bit ev;
|
||||||
|
endclass
|
||||||
|
class Mid;
|
||||||
|
Lvl lvl;
|
||||||
|
endclass
|
||||||
|
class ComplexEvt;
|
||||||
|
bit a;
|
||||||
|
Mid mid;
|
||||||
|
bit [3:0] m_x;
|
||||||
|
covergroup cov_cplx @(posedge a or posedge mid.lvl.ev);
|
||||||
|
coverpoint m_x {bins lo = {[0 : 7]}; bins hi = {[8 : 15]};}
|
||||||
|
endgroup
|
||||||
|
function new();
|
||||||
|
mid = new;
|
||||||
|
mid.lvl = new;
|
||||||
|
cov_cplx = new;
|
||||||
|
endfunction
|
||||||
|
endclass
|
||||||
|
|
||||||
|
NoConstruct nc;
|
||||||
|
ExternalClk ec;
|
||||||
|
ComplexEvt cx;
|
||||||
|
|
||||||
initial begin
|
initial begin
|
||||||
|
nc = new;
|
||||||
|
ec = new;
|
||||||
|
cx = new;
|
||||||
$write("*-* All Finished *-*\n");
|
$write("*-* All Finished *-*\n");
|
||||||
$finish;
|
$finish;
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,7 @@
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
177 | cross a, b {
|
177 | cross a, b {
|
||||||
| ^
|
| ^
|
||||||
%Warning-COVERIGN: t/t_covergroup_unsup.v:209:5: Unsupported: 'covergroup' clocking event on member variable
|
%Warning-COVERIGN: t/t_covergroup_unsup.v:210:24: Unsupported: 'covergroup' coverage construct referencing enclosing class member; ignoring covergroup '__vlAnonCG_cov1'
|
||||||
: ... note: In instance 't'
|
: ... note: In instance 't'
|
||||||
209 | covergroup cov1 @m_z;
|
210 | cp_x: coverpoint m_x;
|
||||||
| ^~~~~~~~~~
|
| ^~~
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue