mirror of
https://github.com/verilator/verilator.git
synced 2026-10-06 10:03:44 +02:00
Support covergroup/function/task (ref/virtual/array) interface arguments (#8180)
This commit is contained in:
+192
-56
@@ -35,6 +35,89 @@
|
||||
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
//######################################################################
|
||||
// Covergroup expression validation visitor
|
||||
|
||||
class CovergroupExprValidVisitor final : public VNVisitor {
|
||||
const std::set<const AstVar*>& m_sampleMembers;
|
||||
const std::set<const AstVar*>& m_constructorRefMembers;
|
||||
bool m_inCoverageExpression = false;
|
||||
bool m_sampleFormalAllowed = false;
|
||||
|
||||
void scanSampleExpression(AstNode* nodep) {
|
||||
if (!nodep) return;
|
||||
VL_RESTORER(m_sampleFormalAllowed);
|
||||
m_sampleFormalAllowed = true;
|
||||
iterateAndNextNull(nodep);
|
||||
}
|
||||
|
||||
void scanCoverageExpression(AstNode* nodep) {
|
||||
if (!nodep) return;
|
||||
VL_RESTORER(m_inCoverageExpression);
|
||||
m_inCoverageExpression = true;
|
||||
iterateAndNextNull(nodep);
|
||||
}
|
||||
|
||||
void visit(AstCoverpoint* nodep) override {
|
||||
scanSampleExpression(nodep->exprp());
|
||||
scanSampleExpression(nodep->iffp());
|
||||
iterateAndNextNull(nodep->binsp());
|
||||
iterateAndNextNull(nodep->optionsp());
|
||||
}
|
||||
void visit(AstCoverCross* nodep) override {
|
||||
iterateAndNextNull(nodep->itemsp());
|
||||
iterateAndNextNull(nodep->optionsp());
|
||||
iterateAndNextNull(nodep->rawBodyp());
|
||||
}
|
||||
void visit(AstCoverBin* nodep) override {
|
||||
scanCoverageExpression(nodep->rangesp());
|
||||
scanSampleExpression(nodep->iffp());
|
||||
scanCoverageExpression(nodep->arraySizep());
|
||||
scanCoverageExpression(nodep->transp());
|
||||
}
|
||||
void visit(AstVarRef* nodep) override {
|
||||
if (!m_sampleFormalAllowed && m_sampleMembers.count(nodep->varp())) {
|
||||
nodep->v3error("Covergroup sample formal argument "
|
||||
<< nodep->varp()->prettyNameQ()
|
||||
<< " may only be used in a coverpoint or conditional guard "
|
||||
"expression (IEEE 1800-2012 19.8.1).");
|
||||
}
|
||||
if (m_inCoverageExpression && m_constructorRefMembers.count(nodep->varp())) {
|
||||
nodep->v3error("Ref covergroup constructor formal argument "
|
||||
<< nodep->varp()->prettyNameQ()
|
||||
<< " may not be used in a covergroup expression "
|
||||
"(IEEE 1800-2012 19.5).");
|
||||
}
|
||||
}
|
||||
void visit(AstNodeFTaskRef* nodep) override {
|
||||
if (m_inCoverageExpression && nodep->taskp()) {
|
||||
bool invalidDirection = false;
|
||||
for (AstNode* stmtp = nodep->taskp()->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
const AstVar* const varp = VN_CAST(stmtp, Var);
|
||||
if (varp && varp->isIO() && varp->isWritable()) {
|
||||
invalidDirection = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (invalidDirection) {
|
||||
nodep->v3error("Function " << nodep->taskp()->prettyNameQ()
|
||||
<< " called in a covergroup expression has an "
|
||||
"output, inout, or non-const ref argument "
|
||||
"(IEEE 1800-2012 19.5).");
|
||||
}
|
||||
}
|
||||
iterateChildren(nodep);
|
||||
}
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
CovergroupExprValidVisitor(const std::set<const AstVar*>& sampleMembers,
|
||||
const std::set<const AstVar*>& constructorRefMembers)
|
||||
: m_sampleMembers{sampleMembers}
|
||||
, m_constructorRefMembers{constructorRefMembers} {}
|
||||
void scan(AstNode* nodep) { iterate(nodep); }
|
||||
};
|
||||
|
||||
//######################################################################
|
||||
// Functional coverage visitor
|
||||
|
||||
@@ -484,7 +567,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
UINFO(4, " Generating code for coverpoint: " << coverpointp->name());
|
||||
|
||||
// Get the coverpoint expression
|
||||
AstNodeExpr* const exprp = coverpointp->exprp();
|
||||
AstNodeExpr* exprp = coverpointp->exprp();
|
||||
|
||||
// Expand automatic bins before processing
|
||||
expandAutomaticBins(coverpointp, exprp);
|
||||
@@ -498,6 +581,18 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
// Create implicit automatic bins if no regular bins exist
|
||||
createImplicitAutoBins(coverpointp, exprp, autoBinMax);
|
||||
|
||||
AstVar* const valueVarp = new AstVar{
|
||||
coverpointp->fileline(), VVarType::BLOCKTEMP,
|
||||
"__VcpValue_" + sanitizeGeneratedName(coverpointp->name()), exprp->dtypep()};
|
||||
valueVarp->funcLocal(true);
|
||||
m_sampleFuncp->addStmtsp(valueVarp);
|
||||
exprp->unlinkFrBack();
|
||||
m_sampleFuncp->addStmtsp(new AstAssign{
|
||||
coverpointp->fileline(),
|
||||
new AstVarRef{coverpointp->fileline(), valueVarp, VAccess::WRITE}, exprp});
|
||||
coverpointp->exprp(new AstVarRef{coverpointp->fileline(), valueVarp, VAccess::READ});
|
||||
exprp = coverpointp->exprp();
|
||||
|
||||
// Every coverpoint routes through the VlCoverpoint runtime. Transition coverpoints are
|
||||
// included: their per-value matching is still generated as a state machine in sample()
|
||||
// (see generateCoverpoint), but the bin hit is recorded in the runtime bin
|
||||
@@ -1358,10 +1453,12 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
} else if (!boundp) {
|
||||
irp->v3error("Non-constant expression in bin range; "
|
||||
"range bounds must be constants");
|
||||
if (fullCondp) VL_DO_DANGLING(pushDeletep(fullCondp), fullCondp);
|
||||
return nullptr;
|
||||
} else if (boundp->num().isFourState()) {
|
||||
irp->v3error("Four-state (x/z) value in bin range bound; "
|
||||
"range bounds must be two-state constants");
|
||||
if (fullCondp) VL_DO_DANGLING(pushDeletep(fullCondp), fullCondp);
|
||||
return nullptr;
|
||||
} else {
|
||||
rangeCondp = makeOpenRangeCondition(irp->fileline(), exprp, boundp,
|
||||
@@ -1370,10 +1467,12 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
} else if (!minConstp || !maxConstp) {
|
||||
irp->v3error("Non-constant expression in bin range; "
|
||||
"range bounds must be constants");
|
||||
if (fullCondp) VL_DO_DANGLING(pushDeletep(fullCondp), fullCondp);
|
||||
return nullptr;
|
||||
} else if (minConstp->num().isFourState() || maxConstp->num().isFourState()) {
|
||||
irp->v3error("Four-state (x/z) value in bin range bound; "
|
||||
"range bounds must be two-state constants");
|
||||
if (fullCondp) VL_DO_DANGLING(pushDeletep(fullCondp), fullCondp);
|
||||
return nullptr;
|
||||
} else if (minConstp->toUQuad() == maxConstp->toUQuad()) {
|
||||
// Single value
|
||||
@@ -1399,6 +1498,7 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
} else {
|
||||
currRangep->v3error(
|
||||
"Non-constant expression in bin range; values must be constants");
|
||||
if (fullCondp) VL_DO_DANGLING(pushDeletep(fullCondp), fullCondp);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1607,6 +1707,11 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
return found;
|
||||
}
|
||||
|
||||
static bool hasInstanceMemberEventRef(const AstCovergroup* cgp) {
|
||||
return cgp->eventp()->exists(
|
||||
[](const AstVarRef* refp) { return isEnclosingInstanceVar(refp->varp()); });
|
||||
}
|
||||
|
||||
static bool parseEmbeddedEventExpr(AstNodeExpr* exprp, AstVar*& baseVarp,
|
||||
AstVar*& memberVarp) {
|
||||
if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
|
||||
@@ -1758,62 +1863,90 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
}
|
||||
|
||||
class FormalRefVisitor final : public VNVisitor {
|
||||
const std::set<const AstVar*>& m_constructorArgs;
|
||||
const std::map<const AstVar*, AstVar*>& m_replacements;
|
||||
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) return;
|
||||
const auto it = m_replacements.find(nodep->varp());
|
||||
if (it != m_replacements.end()) {
|
||||
nodep->varp(it->second);
|
||||
} else if (m_constructorArgs.count(nodep->varp())) {
|
||||
m_offenderp = m_memberSelp;
|
||||
}
|
||||
}
|
||||
void visit(AstNode* nodep) override {
|
||||
if (!m_offenderp) iterateChildren(nodep);
|
||||
if (it == m_replacements.end()) return;
|
||||
nodep->varp(it->second);
|
||||
}
|
||||
void visit(AstNode* nodep) override { iterateChildren(nodep); }
|
||||
|
||||
public:
|
||||
FormalRefVisitor(const std::set<const AstVar*>& constructorArgs,
|
||||
const std::map<const AstVar*, AstVar*>& replacements)
|
||||
: m_constructorArgs{constructorArgs}
|
||||
, m_replacements{replacements} {}
|
||||
void scan(AstNode* nodep) {
|
||||
if (nodep && !m_offenderp) iterate(nodep);
|
||||
}
|
||||
AstNode* offenderp() const { return m_offenderp; }
|
||||
explicit FormalRefVisitor(const std::map<const AstVar*, AstVar*>& replacements)
|
||||
: m_replacements{replacements} {}
|
||||
void scan(AstNode* nodep) { iterate(nodep); }
|
||||
};
|
||||
|
||||
AstNode* findUnsupportedFormalRef() {
|
||||
std::set<const AstVar*> constructorArgs;
|
||||
std::map<const AstVar*, AstVar*> replacements;
|
||||
void validateCovergroupExpressions() {
|
||||
std::set<const AstVar*> sampleMembers;
|
||||
std::set<const AstVar*> constructorRefMembers;
|
||||
for (AstNode* stmtp = m_constructorp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
AstVar* const varp = VN_CAST(stmtp, Var);
|
||||
if (!varp || !varp->isIO()) continue;
|
||||
constructorArgs.insert(varp);
|
||||
if (!VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) continue;
|
||||
AstVar* const memberp
|
||||
const AstVar* const varp = VN_CAST(stmtp, Var);
|
||||
if (!varp || !varp->isIO() || (!varp->isRef() && !varp->isConstRef())) continue;
|
||||
const AstVar* const memberp
|
||||
= VN_CAST(m_memberMap.findMember(m_covergroupp, varp->name()), Var);
|
||||
UASSERT_OBJ(memberp && memberp->isClassMember(), varp,
|
||||
"Covergroup constructor argument missing persistent member");
|
||||
replacements.emplace(varp, memberp);
|
||||
constructorRefMembers.insert(varp);
|
||||
constructorRefMembers.insert(memberp);
|
||||
}
|
||||
FormalRefVisitor visitor{constructorArgs, replacements};
|
||||
for (AstCoverpoint* const cpp : m_coverpoints) {
|
||||
visitor.scan(cpp->exprp());
|
||||
visitor.scan(cpp->iffp());
|
||||
for (AstNode* stmtp = m_sampleFuncp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
const AstVar* const varp = VN_CAST(stmtp, Var);
|
||||
if (!varp || !varp->isIO()) continue;
|
||||
const AstVar* const memberp
|
||||
= VN_CAST(m_memberMap.findMember(m_covergroupp, varp->name()), Var);
|
||||
UASSERT_OBJ(memberp && memberp->isClassMember(), varp,
|
||||
"Covergroup sample argument missing persistent member");
|
||||
sampleMembers.insert(memberp);
|
||||
}
|
||||
for (AstCoverCross* const crossp : m_coverCrosses) visitor.scan(crossp->iffp());
|
||||
return visitor.offenderp();
|
||||
CovergroupExprValidVisitor{sampleMembers, constructorRefMembers}.scan(m_constructorp);
|
||||
}
|
||||
|
||||
void rebindFormalRefs() {
|
||||
std::map<const AstVar*, AstVar*> replacements;
|
||||
for (AstNode* stmtp = m_constructorp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
|
||||
if (const AstVar* const varp = VN_CAST(stmtp, Var)) {
|
||||
if (!varp->isIO()) continue;
|
||||
AstVar* const memberp
|
||||
= VN_CAST(m_memberMap.findMember(m_covergroupp, varp->name()), Var);
|
||||
UASSERT_OBJ(memberp && memberp->isClassMember(), varp,
|
||||
"Covergroup constructor argument missing persistent member");
|
||||
replacements.emplace(varp, memberp);
|
||||
}
|
||||
}
|
||||
size_t expectedBindings = 0;
|
||||
for (const auto& pair : replacements) {
|
||||
if (pair.first->isRef() || pair.first->isConstRef()) ++expectedBindings;
|
||||
}
|
||||
size_t rewrittenBindings = 0;
|
||||
for (AstNode* stmtp = m_constructorp->stmtsp(); stmtp;) {
|
||||
AstNode* const nextp = stmtp->nextp();
|
||||
if (AstAssign* const assignp = VN_CAST(stmtp, Assign)) {
|
||||
AstVarRef* const lhsp = VN_CAST(assignp->lhsp(), VarRef);
|
||||
AstVarRef* const rhsp = VN_CAST(assignp->rhsp(), VarRef);
|
||||
if (lhsp && rhsp
|
||||
&& (lhsp->varp()->declDirection() == VDirection::REF
|
||||
|| lhsp->varp()->declDirection() == VDirection::CONSTREF)) {
|
||||
const auto it = replacements.find(rhsp->varp());
|
||||
UASSERT_OBJ(it != replacements.end() && it->second == lhsp->varp(), assignp,
|
||||
"Unexpected covergroup reference binding assignment");
|
||||
AstCExpr* const bindp = new AstCExpr{assignp->fileline(), ""};
|
||||
bindp->add(lhsp->unlinkFrBack());
|
||||
bindp->add(" = &");
|
||||
bindp->add(rhsp->unlinkFrBack());
|
||||
assignp->replaceWith(bindp->makeStmt());
|
||||
pushDeletep(assignp);
|
||||
++rewrittenBindings;
|
||||
}
|
||||
}
|
||||
stmtp = nextp;
|
||||
}
|
||||
UASSERT_OBJ(rewrittenBindings == expectedBindings, m_constructorp,
|
||||
"Covergroup reference argument missing binding");
|
||||
FormalRefVisitor visitor{replacements};
|
||||
for (AstCoverpoint* const cpp : m_coverpoints) visitor.scan(cpp);
|
||||
for (AstCoverCross* const crossp : m_coverCrosses) visitor.scan(crossp);
|
||||
}
|
||||
|
||||
AstVarRef* installEnclosingBackPointer(const std::vector<AstNodeAssign*>& constructps) {
|
||||
@@ -1952,6 +2085,14 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
itemp = nextp;
|
||||
continue;
|
||||
}
|
||||
if (hasInstanceMemberEventRef(cgp)) {
|
||||
cgp->v3warn(COVERIGN, "Unsupported: 'covergroup' clocking event "
|
||||
"on member variable");
|
||||
hasUnsupportedEvent = true;
|
||||
VL_DO_DANGLING(pushDeletep(cgp->unlinkFrBack()), cgp);
|
||||
itemp = nextp;
|
||||
continue;
|
||||
}
|
||||
// V3Active handles events that do not depend on an enclosing instance.
|
||||
UINFO(4, "Keeping covergroup event node for V3Active: " << nodep->name());
|
||||
itemp = nextp;
|
||||
@@ -1960,14 +2101,6 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
itemp = nextp;
|
||||
}
|
||||
|
||||
// If covergroup has unsupported clocking event, skip processing it
|
||||
// but still clean up coverpoints so they don't reach downstream passes
|
||||
if (hasUnsupportedEvent) {
|
||||
iterateChildren(nodep);
|
||||
deleteCoverageItems();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the sample() method and constructor
|
||||
m_sampleFuncp = VN_CAST(m_memberMap.findMember(nodep, "sample"), Func);
|
||||
// V3LinkParse always synthesizes a sample() method for every covergroup, and the
|
||||
@@ -1978,12 +2111,12 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
UINFO(9, "Found sample() method: " << (m_sampleFuncp ? "yes" : "no"));
|
||||
UINFO(9, "Found constructor: " << (m_constructorp ? "yes" : "no"));
|
||||
|
||||
iterateChildren(nodep);
|
||||
|
||||
if (AstNode* const offenderp = findUnsupportedFormalRef()) {
|
||||
offenderp->v3warn(COVERIGN, "Unsupported: 'covergroup' coverpoint dereferencing a "
|
||||
"non-class constructor argument; ignoring covergroup "
|
||||
<< nodep->prettyNameQ());
|
||||
// If covergroup has unsupported clocking event, skip processing it
|
||||
// but still clean up coverpoints so they don't reach downstream passes
|
||||
if (hasUnsupportedEvent) {
|
||||
iterateChildren(nodep);
|
||||
validateCovergroupExpressions();
|
||||
rebindFormalRefs();
|
||||
deleteCoverageItems();
|
||||
if (embeddedEventForkp) {
|
||||
VL_DO_DANGLING(pushDeletep(embeddedEventForkp), embeddedEventForkp);
|
||||
@@ -1991,6 +2124,9 @@ class FunctionalCoverageVisitor final : public VNVisitor {
|
||||
return;
|
||||
}
|
||||
|
||||
iterateChildren(nodep);
|
||||
validateCovergroupExpressions();
|
||||
rebindFormalRefs();
|
||||
const std::vector<AstNodeAssign*> constructps = findCovergroupConstructions();
|
||||
|
||||
// Embedded covergroups (IEEE 1800-2023 19.4): coverpoints, iff expressions, and
|
||||
|
||||
Reference in New Issue
Block a user