Support covergroup/function/task (ref/virtual/array) interface arguments (#8180)

This commit is contained in:
Marco Bartoli
2026-08-22 15:46:07 -04:00
committed by GitHub
parent 063b938f41
commit 0820506d74
39 changed files with 1630 additions and 136 deletions
+37 -7
View File
@@ -1660,7 +1660,7 @@ static const AstNodeDType* computeCastableBase(const AstNodeDType* nodep) {
}
static VCastable computeCastableImp(const AstNodeDType* toDtp, const AstNodeDType* fromDtp,
const AstNode* fromConstp) {
const AstNode* fromConstp, const bool checkIfaceArgCompat) {
const VCastable castable = VCastable::UNSUPPORTED;
toDtp = toDtp->skipRefToEnump();
fromDtp = fromDtp->skipRefToEnump();
@@ -1698,22 +1698,52 @@ static VCastable computeCastableImp(const AstNodeDType* toDtp, const AstNodeDTyp
if (upcast) return VCastable::COMPATIBLE;
if (downcast) return VCastable::DYNAMIC_CLASS;
return VCastable::INCOMPATIBLE;
} else if (const AstIfaceRefDType* const toIfp = VN_CAST(toDtp, IfaceRefDType)) {
} else if (const AstIfaceRefDType* const toIfp
= VN_CAST(checkIfaceArgCompat ? toDtp->elemDTypep(true) : toDtp, IfaceRefDType)) {
// Two interface refs are compatible if they point at the same interface
// module (and modport, if any). Pointer-equality on the dtype isn't
// enough since every cell binding clones the dtype.
const AstIfaceRefDType* const fromIfp = VN_CAST(fromDtp, IfaceRefDType);
if (fromIfp && toIfp->ifaceViaCellp() == fromIfp->ifaceViaCellp()
&& (!toIfp->modportp() || toIfp->modportp() == fromIfp->modportp())) {
// Argument compatibility also requires matching virtualness for ref arguments, while
// input arguments may bind an unqualified or same-modport source to a virtual target.
// Argument compatibility also supports fixed-size arrays of interface references.
const AstIfaceRefDType* const fromIfp
= VN_CAST(checkIfaceArgCompat ? fromDtp->elemDTypep(true) : fromDtp, IfaceRefDType);
if (checkIfaceArgCompat && fromIfp && (toDtp != toIfp || fromDtp != fromIfp)) {
const AstUnpackArrayDType* const toArrayp = VN_CAST(toDtp, UnpackArrayDType);
const AstUnpackArrayDType* const fromArrayp = VN_CAST(fromDtp, UnpackArrayDType);
// IEEE 1800-2023 6.22.2: Equal-sized fixed arrays have equivalent types.
if (!toArrayp || !fromArrayp
|| toArrayp->elementsConst() != fromArrayp->elementsConst()) {
return VCastable::INCOMPATIBLE;
}
return computeCastableImp(toArrayp->subDTypep(), fromArrayp->subDTypep(), nullptr,
checkIfaceArgCompat);
}
if (!fromIfp || toIfp->ifaceViaCellp() != fromIfp->ifaceViaCellp()) {
if (!checkIfaceArgCompat) return castable;
return VCastable::INCOMPATIBLE;
}
const bool sameModport = toIfp->modportp() == fromIfp->modportp();
if (!checkIfaceArgCompat) {
if (!toIfp->modportp() || sameModport) return VCastable::COMPATIBLE;
return castable;
}
if (toIfp->isVirtual() == fromIfp->isVirtual() && sameModport) {
return VCastable::SAMEISH;
}
// An unqualified interface or virtual interface may bind to a modport-qualified
// virtual interface.
if (toIfp->isVirtual() && (!fromIfp->modportp() || sameModport)) {
return VCastable::COMPATIBLE;
}
return VCastable::INCOMPATIBLE;
}
return castable;
}
VCastable AstNode::computeCastable(const AstNodeDType* toDtp, const AstNodeDType* fromDtp,
const AstNode* fromConstp) {
const auto castable = computeCastableImp(toDtp, fromDtp, fromConstp);
const AstNode* fromConstp, const bool checkIfaceArgCompat) {
const auto castable = computeCastableImp(toDtp, fromDtp, fromConstp, checkIfaceArgCompat);
UINFO(9, " castable=" << castable << " for " << toDtp);
UINFO(9, " =?= " << fromDtp);
if (fromConstp) UINFO(9, " const= " << fromConstp);
+1 -1
View File
@@ -809,7 +809,7 @@ public:
static AstBasicDType* findInsertSameDType(AstBasicDType* nodep);
static VCastable computeCastable(const AstNodeDType* toDtp, const AstNodeDType* fromDtp,
const AstNode* fromConstp);
const AstNode* fromConstp, bool checkIfaceArgCompat = false);
static AstNodeDType* getCommonClassTypep(AstNode* nodep1, AstNode* nodep2);
// METHODS - dump and error
+2 -1
View File
@@ -160,7 +160,8 @@ AstElabDisplay::AstElabDisplay(FileLine* fl, VDisplayType dispType, AstNodeExpr*
bool AstVar::sameNode(const AstNode* samep) const {
const AstVar* const asamep = VN_DBG_AS(samep, Var);
return m_name == asamep->m_name && varType() == asamep->varType();
return m_name == asamep->m_name && varType() == asamep->varType()
&& covergroupRefMember() == asamep->covergroupRefMember();
}
AstMatchMasked::AstMatchMasked(FileLine* fl, AstNodeExpr* lhsp, AstVarScope* matchp)
+2 -1
View File
@@ -923,7 +923,8 @@ public:
: ASTGEN_SUPER_IfaceRefDType(fl)
, m_modportFileline{modportFl}
, m_cellName{cellName}
, m_ifaceName{ifaceName} {
, m_ifaceName{ifaceName}
, m_modportName{modport} {
addParamsp(paramsp);
}
ASTGEN_MEMBERS_AstIfaceRefDType;
+4
View File
@@ -2153,6 +2153,7 @@ class AstVar final : public AstNode {
bool m_attrFsmResetArc : 1; // declared with fsm_reset_arc metacomment
bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment
bool m_constPoolEntry : 1; // Constant pool variable
bool m_covergroupRefMember : 1; // Persistent covergroup ref/const ref argument
bool m_fileDescr : 1; // File descriptor
bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration
bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect
@@ -2217,6 +2218,7 @@ class AstVar final : public AstNode {
m_attrFsmResetArc = false;
m_attrFsmArcInclCond = false;
m_constPoolEntry = false;
m_covergroupRefMember = false;
m_fileDescr = false;
m_gotNansiType = false;
m_icoMaybeWritten = false;
@@ -2368,6 +2370,8 @@ public:
void attrFsmArcInclCond(bool flag) { m_attrFsmArcInclCond = flag; }
bool constPoolEntry() const { return m_constPoolEntry; }
void setConstPoolEntry() { m_constPoolEntry = true; }
bool covergroupRefMember() const { return m_covergroupRefMember; }
void covergroupRefMember() { m_covergroupRefMember = true; }
void rand(const VRandAttr flag) { m_rand = flag; }
void usedParam(bool flag) { m_usedParam = flag; }
void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; }
+8 -2
View File
@@ -717,13 +717,17 @@ string AstVar::vlArgType(bool named, bool forReturn, bool forFunc, const string&
asRef = asRef || isDpiOpenArray() || (forFunc && (isWritable() || isRef() || isConstRef()));
if (forFunc && (isReadOnly() || constRef) && asRef) ostatic = ostatic + "const ";
string oname;
if (named) {
if (!namespc.empty()) oname += namespc + "::";
oname += VIdProtect::protectIf(name(), protect());
}
if (forFunc && (isReadOnly() || constRef) && asRef) {
if (VN_IS(dtypep()->skipRefp(), IfaceRefDType)) {
return ostatic + dtypep()->cType("", forFunc, false) + " const &" + oname;
}
ostatic += "const ";
}
return ostatic + dtypep()->cType(oname, forFunc, asRef);
}
@@ -3323,6 +3327,7 @@ int AstVarRef::instrCount() const {
void AstVar::dump(std::ostream& str) const {
this->AstNode::dump(str);
if (constPoolEntry()) str << " [CONSTPOOL]";
if (covergroupRefMember()) str << " [CGREF]";
if (isSc()) str << " [SC]";
if (isPrimaryIO()) str << (isInout() ? " [PIO]" : (isWritable() ? " [PO]" : " [PI]"));
if (isPrimaryClock()) str << " [PCLK]";
@@ -3365,6 +3370,7 @@ void AstVar::dumpJson(std::ostream& str) const {
dumpJsonStrFunc(str, origName);
dumpJsonStrFunc(str, verilogName);
dumpJsonBoolFuncIf(str, constPoolEntry);
dumpJsonBoolFuncIf(str, covergroupRefMember);
dumpJsonBoolFuncIf(str, isSc);
dumpJsonBoolFuncIf(str, isPrimaryIO);
dumpJsonBoolFuncIf(str, isPrimaryClock);
+192 -56
View File
@@ -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
+6
View File
@@ -185,6 +185,12 @@ void EmitCBaseVisitorConst::emitVarDecl(const AstVar* nodep, bool asRef) {
const AstBasicDType* const basicp = nodep->basicp();
const bool refNeedParens = VN_IS(nodep->dtypeSkipRefp(), UnpackArrayDType);
if (nodep->mtaskCacheLineAlign() && !asRef) putns(nodep, "alignas(VL_CACHE_LINE_BYTES) ");
if (nodep->covergroupRefMember()) {
UASSERT_OBJ(!asRef, nodep, "Covergroup reference member emitted as C++ reference");
putns(nodep, nodep->dtypep()->cType("", false, false));
puts(" const* " + nodep->nameProtect() + " = nullptr;\n");
return;
}
const auto emitDeclArrayBrackets = [this](const AstVar* nodep) -> void {
// This isn't very robust and may need cleanup for other data types
+4
View File
@@ -1768,6 +1768,9 @@ public:
void visit(AstVarRef* nodep) override {
const AstVar* const varp = nodep->varp();
const AstNodeModule* const varModp = EmitCParentModule::get(varp);
const bool dereferenceCovergroupRef
= varp->covergroupRefMember() && nodep->access().isReadOrRW();
if (dereferenceCovergroupRef) putns(nodep, "(*");
if (EmitCUtil::isConstPoolMod(varModp)) {
// Reference to constant pool variable
putns(nodep, EmitCUtil::topClassName() + "__ConstPool__");
@@ -1784,6 +1787,7 @@ public:
emitDereference(nodep, nodep->selfPointerProtect(m_useSelfForThis));
}
putns(nodep, nodep->varp()->nameProtect());
if (dereferenceCovergroupRef) puts(")");
}
void visit(AstAddrOfCFunc* nodep) override {
// Note: Can be thought to handle more, but this is all that is needed right now
+1 -1
View File
@@ -118,7 +118,7 @@ class EmitCHeader final : public EmitCConstInit {
// Leftovers, just in case off by one error somewhere above
for (; it != varList.cend(); ++it) emitVarDecl(*it);
} else { // Output as nonanons
for (const auto& pair : varList) emitVarDecl(pair);
for (const AstVar* const varp : varList) emitVarDecl(varp);
}
varList.clear();
+5 -1
View File
@@ -2811,6 +2811,10 @@ private:
// Remember the alias - can't do it yet because we may have additional symbols to be added,
// or maybe an alias of an alias
m_statep->insertScopeAlias(LinkDotState::SAMN_IFTOP, lhsSymp, rhsSymp);
AstVarScope* const lhsVscp = VN_CAST(lhsSymp->nodep(), VarScope);
AstVarScope* const rhsVscp = VN_CAST(rhsSymp->nodep(), VarScope);
UASSERT_OBJ(lhsVscp && rhsVscp, nodep, "Interface alias missing variable scope");
setAliasVarScope(lhsVscp, rhsVscp);
// We have stored the link, we don't need these any more
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
}
@@ -5186,7 +5190,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
checkNoDot(nodep);
iterateChildren(nodep);
AstVarScope* aliasp = LinkDotScopeVisitor::getAliasVarScopep(nodep);
if (aliasp && aliasp != nodep) {
if (aliasp && aliasp != nodep && !nodep->varp()->isIfaceRef()) {
// Aliased variable might still be references from outside,
// eg through the VPI, and is traced, so we need the value to propagate.
// TODO: this means external writes to the LHS (e.g.: through the VPI) don't work
+28 -5
View File
@@ -345,6 +345,11 @@ class LinkParseVisitor final : public VNVisitor {
void visit(AstVar* nodep) override {
cleanFileline(nodep);
UINFO(9, "VAR " << nodep);
const AstClass* const classp = VN_CAST(m_modp, Class);
if (classp && classp->isCovergroup() && nodep->isClassMember() && !nodep->isFuncLocal()
&& (nodep->declDirection().isRef() || nodep->declDirection().isConstRef())) {
nodep->covergroupRefMember();
}
if (nodep->valuep()) nodep->hasUserInit(true);
// IEEE 1800-2023 6.21: for loop variables are automatic. verilog.y is
// responsible for marking those.
@@ -1145,17 +1150,18 @@ class LinkParseVisitor final : public VNVisitor {
iterateChildren(nodep);
}
// Append, for each arg in argsp, an INPUT parameter plus a "this.<member> = <param>"
// Append, for each arg in argsp, a parameter plus a "this.<member> = <param>"
// assignment to funcp. The parameter is a clone of the covergroup member and so shares its
// name; 'this.' on the LHS targets the member, otherwise the same-named local parameter
// shadows it and the assignment self-assigns the parameter, leaving the member unwritten.
// argsp may be null (no args appended).
static void addArgMemberCopies(AstFunc* funcp, AstNode* argsp) {
static void addArgMemberCopies(AstFunc* funcp, AstNode* argsp, bool readOnlyRefs) {
for (AstNode* argp = argsp; argp; argp = argp->nextp()) {
AstVar* const origVarp = VN_AS(argp, Var);
AstVar* const paramp = origVarp->cloneTree(false);
paramp->funcLocal(true);
paramp->direction(VDirection::INPUT);
paramp->direction(origVarp->direction());
if (readOnlyRefs && origVarp->isRef()) paramp->direction(VDirection::CONSTREF);
funcp->addStmtsp(paramp);
AstNodeExpr* const lhsp = new AstDot{
origVarp->fileline(), false, new AstParseRef{origVarp->fileline(), "this"},
@@ -1184,7 +1190,7 @@ class LinkParseVisitor final : public VNVisitor {
// before the coverage body, then re-append the body.
AstNode* const existingBodyp = newFuncp->stmtsp();
if (existingBodyp) existingBodyp->unlinkFrBackWithNext();
addArgMemberCopies(newFuncp, argsp);
addArgMemberCopies(newFuncp, argsp, true);
if (existingBodyp) newFuncp->addStmtsp(existingBodyp);
}
@@ -1213,7 +1219,7 @@ class LinkParseVisitor final : public VNVisitor {
// IEEE: function void sample([arguments])
{
AstFunc* const funcp = new AstFunc{nodep->fileline(), "sample", nullptr, nullptr};
addArgMemberCopies(funcp, sampleArgsp);
addArgMemberCopies(funcp, sampleArgsp, false);
funcp->classMethod(true);
funcp->dtypep(funcp->findVoidDType());
nodep->addMembersp(funcp);
@@ -1299,16 +1305,33 @@ class LinkParseVisitor final : public VNVisitor {
// Convert constructor args to member variables
for (AstNode* argp = nodep->argsp(); argp; argp = argp->nextp()) {
AstVar* const origVarp = VN_AS(argp, Var);
if (origVarp->direction() == VDirection::OUTPUT
|| origVarp->direction() == VDirection::INOUT) {
origVarp->v3error("Covergroup formal arguments cannot be output or inout"
" (IEEE 1800-2012 19.3)");
origVarp->direction(VDirection::INPUT);
}
if ((origVarp->isRef() || origVarp->isConstRef()) && origVarp->valuep()) {
origVarp->v3warn(E_UNSUPPORTED,
"Unsupported: default value on ref or const ref covergroup "
"formal argument");
}
AstVar* const memberp = origVarp->cloneTree(false);
memberp->varType(VVarType::MEMBER);
memberp->funcLocal(false);
memberp->direction(VDirection::NONE);
if (origVarp->isRef() || origVarp->isConstRef()) memberp->noReset(true);
cgClassp->addMembersp(memberp);
}
// Convert sample args to member variables
for (AstNode* argp = nodep->sampleArgsp(); argp; argp = argp->nextp()) {
AstVar* const origVarp = VN_AS(argp, Var);
if (!origVarp->isInput()) {
origVarp->v3error("Covergroup sample formal argument must have input direction "
"(IEEE 1800-2012 19.8.1).");
origVarp->direction(VDirection::INPUT);
}
AstVar* const memberp = origVarp->cloneTree(false);
memberp->varType(VVarType::MEMBER);
memberp->funcLocal(false);
+28 -6
View File
@@ -7566,12 +7566,34 @@ class WidthVisitor final : public VNVisitor {
if (!pinp) continue; // Argument error we'll find later
AstNodeDType* const portDTypep = portp->dtypep()->skipRefToEnump();
const AstNodeDType* const pinDTypep = pinp->dtypep()->skipRefToEnump();
if (portp->direction() == VDirection::REF
&& !similarDTypeRecurse(portDTypep, pinDTypep)) {
pinp->v3error("Ref argument requires matching types;"
<< " port " << portp->prettyNameQ() << " requires "
<< portDTypep->prettyDTypeNameQ() << " but connection is "
<< pinDTypep->prettyDTypeNameQ() << ".");
const AstIfaceRefDType* const portIfacep
= VN_CAST(portDTypep->elemDTypep(true), IfaceRefDType);
const VCastable ifaceCastable
= portIfacep ? AstNode::computeCastable(portDTypep, pinDTypep, pinp,
/* checkIfaceArgCompat */ true)
: VCastable{VCastable::UNSUPPORTED};
const bool matchingRefDTypes = portIfacep
? ifaceCastable == VCastable::SAMEISH
: similarDTypeRecurse(portDTypep, pinDTypep);
const bool pinIsNull = VN_IS(pinp, Const) && VN_AS(pinp, Const)->num().isNull();
if ((portp->isRef() || portp->isConstRef()) && !matchingRefDTypes) {
if (portIfacep) {
pinp->v3error("Ref virtual interface argument "
<< portp->prettyNameQ()
<< " requires the same interface type, parameters, and "
"modport.");
} else {
pinp->v3error("Ref argument requires matching types;"
<< " port " << portp->prettyNameQ() << " requires "
<< portDTypep->prettyDTypeNameQ() << " but connection is "
<< pinDTypep->prettyDTypeNameQ() << ".");
}
} else if (portIfacep && portIfacep->isVirtual() && portp->isInput() && !pinIsNull
&& !ifaceCastable.isAssignable()) {
pinp->v3error("Virtual interface argument "
<< portp->prettyNameQ()
<< " requires a compatible interface type, parameters, and "
"modport.");
} else if (portp->isWritable() && pinp->width() != portp->width()) {
pinp->v3widthWarn(portp->width(), pinp->width(),
"Function output argument "