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:
+37
-7
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 "
|
||||
|
||||
Reference in New Issue
Block a user