diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 5e4057447..5538298e5 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -1603,6 +1603,9 @@ class AstLambdaArgRef final : public AstNodeExpr { // Lambda argument usage // These are not AstVarRefs because we need to be able to delete/clone lambdas during // optimizations and AstVar's are painful to remove. + ASTGEN_MEMBERS_AstLambdaArgRef; + +private: string m_name; // Name of variable bool m_index; // Index, not value @@ -1611,7 +1614,6 @@ public: : ASTGEN_SUPER_LambdaArgRef(fl) , m_name{name} , m_index(index) {} - ASTGEN_MEMBERS_AstLambdaArgRef; bool same(const AstNode* /*samep*/) const override { return true; } string emitVerilog() override { return name(); } string emitC() override { V3ERROR_NA_RETURN(""); } @@ -2216,6 +2218,10 @@ public: : ASTGEN_SUPER_ThisRef(fl) { childDTypep(dtypep); } + AstThisRef(FileLine* fl, AstClassRefDType* dtypep) + : ASTGEN_SUPER_ThisRef(fl) { + this->dtypep(dtypep); + } ASTGEN_MEMBERS_AstThisRef; string emitC() override { return "this"; } string emitVerilog() override { return "this"; } diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index aa9791975..0617d46cc 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -65,6 +65,7 @@ #include "V3LinkDot.h" +#include "V3Global.h" #include "V3Graph.h" #include "V3MemberMap.h" #include "V3Parse.h" @@ -2046,6 +2047,7 @@ class LinkDotResolveVisitor final : public VNVisitor { VSymEnt* m_curSymp = nullptr; // SymEnt for current lookup point VSymEnt* m_modSymp = nullptr; // SymEnt for current module VSymEnt* m_pinSymp = nullptr; // SymEnt for pin lookups + VSymEnt* m_fromSymp = nullptr; // SymEnt for randomize lookups const AstCell* m_cellp = nullptr; // Current cell AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task @@ -2465,6 +2467,7 @@ class LinkDotResolveVisitor final : public VNVisitor { UINFO(8, " " << nodep << endl); const DotStates lastStates = m_ds; const bool start = (m_ds.m_dotPos == DP_NONE); // Save, as m_dotp will be changed + VL_RESTORER(m_fromSymp); { if (start) { // Starting dot sequence if (debug() >= 9) nodep->dumpTree("- dot-in: "); @@ -2580,6 +2583,7 @@ class LinkDotResolveVisitor final : public VNVisitor { "ParseRefs should no longer exist"); const DotStates lastStates = m_ds; const bool start = (m_ds.m_dotPos == DP_NONE); // Save, as m_dotp will be changed + if (start) { m_ds.init(m_curSymp); // Note m_ds.m_dot remains nullptr; this is a reference not under a dot @@ -2648,12 +2652,7 @@ class LinkDotResolveVisitor final : public VNVisitor { classOrPackagep = cpackagerefp->classOrPackagep(); UASSERT_OBJ(classOrPackagep, m_ds.m_dotp->lhsp(), "Bad package link"); if (cpackagerefp->name() == "local::") { - if (m_pinSymp) { - m_ds.m_dotSymp = m_curSymp->fallbackp(); - } else { - nodep->v3error("Illegal 'local::' outside 'randomize() with'"); - m_ds.m_dotErr = true; - } + m_fromSymp = nullptr; } else { m_ds.m_dotSymp = m_statep->getNodeSym(classOrPackagep); } @@ -2675,6 +2674,19 @@ class LinkDotResolveVisitor final : public VNVisitor { VSymEnt* foundp; string baddot; VSymEnt* okSymp = nullptr; + if (m_fromSymp) { + foundp = m_fromSymp->findIdFlat(nodep->name()); + if (foundp) { + UINFO(9, " randomize-with fromSym " << foundp->nodep() << endl); + if (m_ds.m_dotPos != DP_NONE) m_ds.m_dotPos = DP_MEMBER; + AstLambdaArgRef* const lambdaRefp + = new AstLambdaArgRef{nodep->fileline(), "item", false}; + nodep->replaceWith(new AstMemberSel{nodep->fileline(), lambdaRefp, + VFlagChildDType{}, nodep->name()}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } if (allowScope) { foundp = m_statep->findDotted(nodep->fileline(), m_ds.m_dotSymp, nodep->name(), baddot, okSymp); // Maybe nullptr @@ -2730,8 +2742,12 @@ class LinkDotResolveVisitor final : public VNVisitor { } } } else if (allowFTask && VN_IS(foundp->nodep(), NodeFTask)) { - AstTaskRef* const taskrefp - = new AstTaskRef{nodep->fileline(), nodep->name(), nullptr}; + AstNodeFTaskRef* taskrefp; + if (VN_IS(foundp->nodep(), Task)) { + taskrefp = new AstTaskRef{nodep->fileline(), nodep->name(), nullptr}; + } else { + taskrefp = new AstFuncRef{nodep->fileline(), nodep->name(), nullptr}; + } nodep->replaceWith(taskrefp); VL_DO_DANGLING(nodep->deleteTree(), nodep); if (start) m_ds = lastStates; @@ -2979,6 +2995,12 @@ class LinkDotResolveVisitor final : public VNVisitor { UINFO(4, "(Backto) Link ClassOrPackageRef: " << nodep << endl); iterateChildren(nodep); + if (nodep->name() == "local::") { + if (!m_fromSymp) { + nodep->v3error("Illegal 'local::' outside 'randomize() with'"); + m_ds.m_dotErr = true; + } + } AstClass* const modClassp = VN_CAST(m_modp, Class); if (m_statep->forPrimary() && refClassp && !nodep->paramsp() && nodep->classOrPackagep()->hasGParam() @@ -3124,10 +3146,10 @@ class LinkDotResolveVisitor final : public VNVisitor { void visit(AstMethodCall* nodep) override { // Created here so should already be resolved. VL_RESTORER(m_ds); - VL_RESTORER(m_pinSymp); + VL_RESTORER(m_fromSymp); { m_ds.init(m_curSymp); - if (nodep->name() == "randomize" && VN_IS(nodep->pinsp(), With)) { + if (nodep->name() == "randomize" && nodep->pinsp()) { const AstNodeDType* fromDtp = nodep->fromp()->dtypep(); if (!fromDtp) { if (const AstNodeVarRef* const varRefp = VN_CAST(nodep->fromp(), NodeVarRef)) { @@ -3157,8 +3179,16 @@ class LinkDotResolveVisitor final : public VNVisitor { nodep->v3error("'randomize() with' on a non-class-instance " << fromDtp->prettyNameQ()); else - m_pinSymp = m_statep->getNodeSym(classDtp->classp()); + m_fromSymp = m_statep->getNodeSym(classDtp->classp()); } + AstNode* pinsp = nodep->pinsp(); + if (VN_IS(pinsp, With)) { + iterate(pinsp); + pinsp = pinsp->nextp(); + } + m_fromSymp = nullptr; + iterateAndNextNull(pinsp); + return; } iterateChildren(nodep); } @@ -3191,6 +3221,8 @@ class LinkDotResolveVisitor final : public VNVisitor { } } + VL_RESTORER(m_fromSymp); + bool staticAccess = false; if (m_ds.m_unresolvedClass) { // Unable to link before V3Param @@ -3210,12 +3242,12 @@ class LinkDotResolveVisitor final : public VNVisitor { staticAccess = true; AstClassOrPackageRef* const cpackagerefp = VN_AS(m_ds.m_dotp->lhsp(), ClassOrPackageRef); - if (cpackagerefp->name() == "local") { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: " << AstNode::prettyNameQ(cpackagerefp->name())); - } UASSERT_OBJ(cpackagerefp->classOrPackagep(), m_ds.m_dotp->lhsp(), "Bad package link"); - nodep->classOrPackagep(cpackagerefp->classOrPackagep()); + if (cpackagerefp->name() == "local::") { + m_fromSymp = nullptr; + } else { + nodep->classOrPackagep(cpackagerefp->classOrPackagep()); + } // Class/package :: HERE function() . method_called_on_function_return_value() m_ds.m_dotPos = DP_MEMBER; m_ds.m_dotp = nullptr; @@ -3284,6 +3316,24 @@ class LinkDotResolveVisitor final : public VNVisitor { dotSymp = m_statep->findDotted(nodep->fileline(), dotSymp, nodep->dotted(), baddot, okSymp); // Maybe nullptr } + if (m_fromSymp) { + VSymEnt* const foundp = m_fromSymp->findIdFlat(nodep->name()); + if (foundp) { + UINFO(9, " randomize-with fromSym " << foundp->nodep() << endl); + AstNodeExpr* argsp = nullptr; + if (nodep->pinsp()) { + iterateAndNextNull(nodep->pinsp()); + argsp = nodep->pinsp()->unlinkFrBackWithNext(); + } + if (m_ds.m_dotPos != DP_NONE) m_ds.m_dotPos = DP_MEMBER; + AstNode* const newp = new AstMethodCall{ + nodep->fileline(), new AstLambdaArgRef{nodep->fileline(), "item", false}, + VFlagChildDType{}, nodep->name(), argsp}; + nodep->replaceWith(newp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } VSymEnt* const foundp = m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot); AstNodeFTask* const taskp = foundp ? VN_CAST(foundp->nodep(), NodeFTask) : nullptr; // Maybe nullptr @@ -3524,7 +3574,6 @@ class LinkDotResolveVisitor final : public VNVisitor { VL_RESTORER(m_curSymp); { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); - if (m_pinSymp) m_curSymp->importFromClass(m_statep->symsp(), m_pinSymp); iterateChildren(nodep); } m_ds.m_dotSymp = VL_RESTORER_PREV(m_curSymp); diff --git a/src/V3LinkLValue.cpp b/src/V3LinkLValue.cpp index b13d022a0..774434ec2 100644 --- a/src/V3LinkLValue.cpp +++ b/src/V3LinkLValue.cpp @@ -36,12 +36,14 @@ class LinkLValueVisitor final : public VNVisitor { bool m_setContinuously = false; // Set that var has some continuous assignment bool m_setStrengthSpecified = false; // Set that var has assignment with strength specified. bool m_setForcedByCode = false; // Set that var is the target of an AstAssignForce/AstRelease + bool m_setIfRand = false; // Update VarRefs if var declared as rand VAccess m_setRefLvalue; // Set VarRefs to lvalues for pin assignments // VISITs // Result handing void visit(AstNodeVarRef* nodep) override { // VarRef: LValue its reference + if (m_setIfRand && !(nodep->varp() && nodep->varp()->isRand())) return; if (m_setRefLvalue != VAccess::NOCHANGE) nodep->access(m_setRefLvalue); if (nodep->varp() && nodep->access().isWriteOrRW()) { if (m_setContinuously) { @@ -321,6 +323,11 @@ class LinkLValueVisitor final : public VNVisitor { } } } + void visit(AstConstraint* nodep) override { + VL_RESTORER(m_setIfRand); + m_setIfRand = true; + iterateChildren(nodep); + } void visit(AstNode* nodep) override { iterateChildren(nodep); } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index e45129bdd..d5a987152 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -20,7 +20,12 @@ // Mark all classes that inherit from previously marked classed // Mark all classes whose instances are randomized member variables of marked classes // Each marked class: -// define a virtual randomize() method that randomizes its random variables +// * define a virtual randomize() method that randomizes its random variables +// Each call to randomize(): +// * define __Vrandwith### functions for randomize() calls with inline constraints and +// put then into randomized classes +// * replace calls to randomize() that use inline constraints with calls to __Vrandwith### +// functions // //************************************************************************* @@ -37,6 +42,8 @@ #include "V3MemberMap.h" #include "V3UniqueNames.h" +#include +#include #include VL_DEFINE_DEBUG_FUNCTIONS; @@ -278,6 +285,15 @@ class RandomizeMarkVisitor final : public VNVisitor { backp = backp->backp()) backp->user1(true); } + void visit(AstMemberSel* nodep) override { + if (!m_constraintExprp) return; + if (VN_IS(nodep->fromp(), LambdaArgRef)) { + if (!nodep->varp()->isRand()) return; + for (AstNode* backp = nodep; backp != m_constraintExprp && !backp->user1(); + backp = backp->backp()) + backp->user1(true); + } + } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); m_modp = nodep; @@ -406,6 +422,7 @@ class ConstraintExprVisitor final : public VNVisitor { // VISITORS void visit(AstNodeVarRef* nodep) override { AstVar* const varp = nodep->varp(); + AstNodeModule* const classOrPackagep = nodep->classOrPackagep(); const VarRandMode randMode = {.asInt = varp->user1()}; if (!randMode.usesRandMode && editFormat(nodep)) return; @@ -438,6 +455,7 @@ class ConstraintExprVisitor final : public VNVisitor { AstClass* const classp = VN_AS(varp->user2p(), Class); AstVarRef* const varRefp = new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE}; + varRefp->classOrPackagep(classOrPackagep); methodp->addPinsp(varRefp); methodp->addPinsp(new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{}, (size_t)varp->width()}); @@ -603,13 +621,92 @@ public: } }; -template -class CaptureFrame final { - TreeNodeType* m_treep; // Original tree +class ClassLookupHelper final { + const std::set + m_visibleModules; // Modules directly reachale from our lookup point + std::map + m_classMap; // Memoized mapping between nodes and modules that define them + + // BFS search + template + static void foreachSuperClass(AstClass* classp, Action action) { + std::queue classes; + classes.push(classp); + while (!classes.empty()) { + classp = classes.front(); + classes.pop(); + for (AstClassExtends* extendsp = classp->extendsp(); extendsp; + extendsp = VN_AS(extendsp->nextp(), ClassExtends)) { + AstClass* const superClassp + = VN_AS(extendsp->childDTypep(), ClassRefDType)->classp(); + action(superClassp); + classes.push(superClassp); + } + } + } + + static std::set initVisibleModules(AstClass* classp) { + std::set visibleModules = {classp}; + std::vector symLookupOrder = {classp}; + foreachSuperClass(classp, + [&](AstClass* superclassp) { visibleModules.emplace(superclassp); }); + return visibleModules; + } + +public: + bool moduleInClassHierarchy(AstNodeModule* modp) const { + return m_visibleModules.count(modp) != 0; + } + + AstNodeModule* findDeclaringModule(AstNode* nodep, bool classHierarchyOnly = true) { + auto it = m_classMap.find(nodep); + if (it != m_classMap.end()) return it->second; + for (AstNode* backp = nodep; backp; backp = backp->backp()) { + AstNodeModule* const modp = VN_CAST(backp, NodeModule); + if (modp) { + m_classMap.emplace(nodep, modp); + if (classHierarchyOnly) + UASSERT_OBJ(moduleInClassHierarchy(modp), nodep, + "Node does not belong to class"); + return modp; + } + } + return nullptr; + } + + ClassLookupHelper(AstClass* classp) + : m_visibleModules(initVisibleModules(classp)) {} +}; + +enum class CaptureMode : uint8_t { + CAP_NO = 0x0, + CAP_VALUE = 0x01, + CAP_THIS = 0x02, + CAP_F_SET_CLASSORPACKAGEP = 0x4, + CAP_F_XREF = 0x8 +}; +CaptureMode operator|(CaptureMode a, CaptureMode b) { + return static_cast(static_cast(a) | static_cast(b)); +} +CaptureMode operator&(CaptureMode a, CaptureMode b) { + return static_cast(static_cast(a) & static_cast(b)); +} +CaptureMode mode(CaptureMode a) { return a & static_cast(0x3); } +bool hasFlags(CaptureMode a, CaptureMode flags) { + return ((static_cast(a) & 0xc & static_cast(flags)) + == static_cast(flags)); +} + +class CaptureVisitor final : public VNVisitor { AstArg* m_argsp; // Original references turned into arguments - AstNodeModule* m_myModulep; // Module for which static references will stay uncaptured. - // Map original var nodes to their clones - std::map m_varCloneMap; + AstNodeModule* m_callerp; // Module of the outer context (for capturing `this`) + AstClass* m_classp; // Module of inner context (for symbol lookup) + std::map m_varCloneMap; // Map original var nodes to their clones + std::set m_ignore; // Nodes to ignore for capturing + ClassLookupHelper m_lookup; // Util for class lookup + AstVar* m_thisp = nullptr; // Variable for outer context's object, if necessary + + // METHODS bool captureVariable(FileLine* const fileline, AstNodeVarRef* varrefp, AstVar*& varp) { auto it = m_varCloneMap.find(varrefp->varp()); @@ -629,57 +726,30 @@ class CaptureFrame final { return false; } - template - static void foreachSuperClass(AstClass* classp, Action action) { - for (AstClassExtends* extendsp = classp->extendsp(); extendsp; - extendsp = VN_AS(extendsp->nextp(), ClassExtends)) { - AstClass* const superclassp = VN_AS(extendsp->childDTypep(), ClassRefDType)->classp(); - action(superclassp); - foreachSuperClass(superclassp, action); - } + template + void fixupClassOrPackage(AstNode* memberp, NodeT refp) { + AstNodeModule* const declClassp = m_lookup.findDeclaringModule(memberp, false); + if (declClassp != m_classp) refp->classOrPackagep(declClassp); } -public: - explicit CaptureFrame(TreeNodeType* const nodep, AstNodeModule* const myModulep, - const bool clone = true, VNRelinker* const linkerp = nullptr) - : m_treep(clone ? nodep->cloneTree(true) : nodep->unlinkFrBackWithNext(linkerp)) - , m_argsp(nullptr) - , m_myModulep(myModulep) { - - std::set visibleModules = {myModulep}; - if (AstClass* classp = VN_CAST(m_myModulep, Class)) { - foreachSuperClass(classp, - [&](AstClass* superclassp) { visibleModules.emplace(superclassp); }); - } - m_treep->foreachAndNext([&](AstNodeVarRef* varrefp) { - UASSERT_OBJ(varrefp->varp(), varrefp, "Variable unlinked"); - if (!varrefp->varp()->isFuncLocal() && !VN_IS(varrefp, VarXRef) - && (visibleModules.count(varrefp->classOrPackagep()))) - return; - AstVar* newVarp; - bool newCapture = captureVariable(varrefp->fileline(), varrefp, newVarp /*ref*/); - AstNodeVarRef* const newVarRefp = newCapture ? varrefp->cloneTree(false) : nullptr; - if (!varrefp->varp()->lifetime().isStatic() || varrefp->classOrPackagep()) { - // Keeping classOrPackagep will cause a broken link after inlining - varrefp->classOrPackagep(nullptr); // AstScope will figure this out - } - varrefp->varp(newVarp); - if (!newCapture) return; - if (VN_IS(varrefp, VarXRef)) { - AstVarRef* const notXVarRefp - = new AstVarRef{varrefp->fileline(), newVarp, VAccess::READ}; - notXVarRefp->classOrPackagep(varrefp->classOrPackagep()); - varrefp->replaceWith(notXVarRefp); - varrefp->deleteTree(); - varrefp = notXVarRefp; - } - m_argsp = AstNode::addNext(m_argsp, new AstArg{varrefp->fileline(), "", newVarRefp}); - }); + template + bool isReferenceToInnerMember(NodeT nodep) { + return VN_IS(nodep->fromp(), LambdaArgRef); } - // PUBLIC METHODS - - TreeNodeType* getTree() const { return m_treep; } + AstVar* importThisp(FileLine* fl) { + if (!m_thisp) { + AstClassRefDType* const refDTypep + = new AstClassRefDType{fl, VN_AS(m_callerp, Class), nullptr}; + v3Global.rootp()->typeTablep()->addTypesp(refDTypep); + m_thisp = new AstVar{fl, VVarType::BLOCKTEMP, "__Vthis", refDTypep}; + m_thisp->funcLocal(true); + m_thisp->lifetime(VLifetime::AUTOMATIC); + m_thisp->direction(VDirection::INPUT); + m_argsp = AstNode::addNext(m_argsp, new AstArg{fl, "", new AstThisRef{fl, refDTypep}}); + } + return m_thisp; + } AstVar* getVar(AstVar* const varp) const { const auto it = m_varCloneMap.find(varp); @@ -687,7 +757,164 @@ public: return it->second; } + CaptureMode getVarRefCaptureMode(AstNodeVarRef* varRefp) { + AstNodeModule* const modp = m_lookup.findDeclaringModule(varRefp->varp(), false); + + const bool callerIsClass = VN_IS(m_callerp, Class); + const bool refIsXref = VN_IS(varRefp, VarXRef); + const bool varIsFuncLocal = varRefp->varp()->isFuncLocal(); + const bool varHasAutomaticLifetime = varRefp->varp()->lifetime().isAutomatic(); + const bool varIsDeclaredInCaller = modp == m_callerp; + const bool varIsFieldOfCaller = modp ? m_lookup.moduleInClassHierarchy(modp) : false; + + if (refIsXref) return CaptureMode::CAP_VALUE | CaptureMode::CAP_F_XREF; + if (varIsFuncLocal && varHasAutomaticLifetime) return CaptureMode::CAP_VALUE; + // Static var in function (will not be inlined, because it's in class) + if (callerIsClass && varIsFuncLocal) return CaptureMode::CAP_VALUE; + if (callerIsClass && varIsDeclaredInCaller) return CaptureMode::CAP_THIS; + if (callerIsClass && varIsFieldOfCaller) return CaptureMode::CAP_THIS; + UASSERT_OBJ(!callerIsClass, varRefp, "Invalid reference?"); + return CaptureMode::CAP_VALUE; + } + + void captureRefByValue(AstNodeVarRef* nodep, CaptureMode capModeFlags) { + AstVar* newVarp; + bool newCapture = captureVariable(nodep->fileline(), nodep, newVarp /*ref*/); + AstNodeVarRef* const newVarRefp = newCapture ? nodep->cloneTree(false) : nullptr; + if (!hasFlags(capModeFlags, CaptureMode::CAP_F_SET_CLASSORPACKAGEP)) { + // Keeping classOrPackagep will cause a broken link after inlining + nodep->classOrPackagep(nullptr); // AstScope will figure this out + } + nodep->varp(newVarp); + if (!newCapture) return; + if (hasFlags(capModeFlags, CaptureMode::CAP_F_XREF)) { + AstVarRef* const notXVarRefp + = new AstVarRef{nodep->fileline(), newVarp, VAccess::READ}; + notXVarRefp->classOrPackagep(nodep->classOrPackagep()); + nodep->replaceWith(notXVarRefp); + nodep->deleteTree(); + nodep = notXVarRefp; + } + m_ignore.emplace(nodep); + m_argsp = AstNode::addNext(m_argsp, new AstArg{nodep->fileline(), "", newVarRefp}); + } + + void captureRefByThis(AstNodeVarRef* nodep, CaptureMode capModeFlags) { + AstVar* const thisp = importThisp(nodep->fileline()); + AstVarRef* const thisRefp = new AstVarRef{nodep->fileline(), thisp, nodep->access()}; + m_ignore.emplace(thisRefp); + AstMemberSel* const memberSelp + = new AstMemberSel(nodep->fileline(), thisRefp, nodep->varp()); + nodep->replaceWith(memberSelp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + m_ignore.emplace(memberSelp); + } + + // VISITORS + + void visit(AstNodeVarRef* nodep) override { + if (m_ignore.count(nodep)) return; + m_ignore.emplace(nodep); + UASSERT_OBJ(nodep->varp(), nodep, "Variable unlinked"); + CaptureMode capMode = getVarRefCaptureMode(nodep); + if (mode(capMode) == CaptureMode::CAP_NO) return; + if (mode(capMode) == CaptureMode::CAP_VALUE) captureRefByValue(nodep, capMode); + if (mode(capMode) == CaptureMode::CAP_THIS) captureRefByThis(nodep, capMode); + } + void visit(AstNodeFTaskRef* nodep) override { + if (m_ignore.count(nodep)) { + iterateChildren(nodep); + return; + } + m_ignore.emplace(nodep); + UASSERT_OBJ(nodep->taskp(), nodep, "Task unlinked"); + // We assume that constraint targets are not referenced this way. + if (VN_IS(nodep, MethodCall) || VN_IS(nodep, New)) { + m_ignore.emplace(nodep); + iterateChildren(nodep); + return; + } + AstClass* classp = VN_CAST(m_lookup.findDeclaringModule(nodep->taskp(), false), Class); + if ((classp == m_callerp) && VN_IS(m_callerp, Class)) { + AstNodeExpr* const pinsp = nodep->pinsp(); + if (pinsp) pinsp->unlinkFrBack(); + AstVar* const thisp = importThisp(nodep->fileline()); + AstVarRef* const thisRefp = new AstVarRef{ + nodep->fileline(), thisp, nodep->isPure() ? VAccess::READ : VAccess::READWRITE}; + m_ignore.emplace(thisRefp); + AstMethodCall* const methodCallp + = new AstMethodCall{nodep->fileline(), thisRefp, thisp->name(), pinsp}; + methodCallp->taskp(nodep->taskp()); + methodCallp->dtypep(nodep->dtypep()); + nodep->replaceWith(methodCallp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + m_ignore.emplace(methodCallp); + } + } + void visit(AstMemberSel* nodep) override { + if (!isReferenceToInnerMember(nodep)) { + iterateChildren(nodep); + return; + } + AstVarRef* const varRefp + = new AstVarRef(nodep->fileline(), nodep->varp(), nodep->access()); + fixupClassOrPackage(nodep->varp(), varRefp); + varRefp->user1(nodep->user1()); + nodep->replaceWith(varRefp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + m_ignore.emplace(varRefp); + } + void visit(AstMethodCall* nodep) override { + if (!isReferenceToInnerMember(nodep) || m_ignore.count(nodep)) { + iterateChildren(nodep); + return; + } + AstNodeExpr* const pinsp + = nodep->pinsp() ? nodep->pinsp()->unlinkFrBackWithNext() : nullptr; + AstNodeFTaskRef* taskRefp = nullptr; + if (VN_IS(nodep->taskp(), Task)) + taskRefp = new AstTaskRef{nodep->fileline(), nodep->name(), pinsp}; + else if (VN_IS(nodep->taskp(), Func)) + taskRefp = new AstFuncRef{nodep->fileline(), nodep->name(), pinsp}; + UASSERT_OBJ(taskRefp, nodep, "Node needs to point to regular method"); + taskRefp->taskp(nodep->taskp()); + taskRefp->dtypep(nodep->dtypep()); + fixupClassOrPackage(nodep->taskp(), taskRefp); + taskRefp->user1(nodep->user1()); + nodep->replaceWith(taskRefp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + m_ignore.emplace(taskRefp); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit CaptureVisitor(AstNode* const nodep, AstNodeModule* callerp, AstClass* const classp, + const bool clone = true, VNRelinker* const linkerp = nullptr) + : m_argsp(nullptr) + , m_callerp(callerp) + , m_classp(classp) + , m_lookup(classp) { + iterateAndNextNull(nodep); + } + + // PUBLIC METHODS + AstArg* getArgs() const { return m_argsp; } + + void addFunctionArguments(AstNodeFTask* funcp) const { + for (AstArg* argp = getArgs(); argp; argp = VN_AS(argp->nextp(), Arg)) { + if (AstNodeVarRef* varrefp = VN_CAST(argp->exprp(), NodeVarRef)) { + if ((varrefp->classOrPackagep() == m_callerp) || VN_IS(varrefp, VarXRef)) { + // Keeping classOrPackagep will cause a broken link after inlining + varrefp->classOrPackagep(nullptr); + } + funcp->addStmtsp(getVar(varrefp->varp())); + } else { + UASSERT_OBJ(VN_IS(argp->exprp(), ThisRef), argp->exprp(), "Wrong arg expression"); + funcp->addStmtsp(m_thisp); + } + } + } }; //###################################################################### @@ -1315,23 +1542,14 @@ class RandomizeVisitor final : public VNVisitor { classp->findBasicDType(VBasicDTypeKwd::RANDOM_GENERATOR)}; localGenp->funcLocal(true); - AstFunc* const randomizeFuncp - = V3Randomize::newRandomizeFunc(m_memberMap, classp, m_inlineUniqueNames.get(nodep)); + AstFunc* const randomizeFuncp = V3Randomize::newRandomizeFunc( + m_memberMap, classp, m_inlineUniqueNames.get(nodep), false); // Detach the expression and prepare variable copies - const CaptureFrame captured{withp->exprp(), classp, false}; - UASSERT_OBJ(VN_IS(captured.getTree(), ConstraintExpr), captured.getTree(), - "Wrong expr type"); + const CaptureVisitor captured{withp->exprp(), m_modp, classp, false}; // Add function arguments - for (AstArg* argp = captured.getArgs(); argp; argp = VN_AS(argp->nextp(), Arg)) { - AstNodeVarRef* varrefp = VN_AS(argp->exprp(), NodeVarRef); - if ((varrefp->classOrPackagep() == m_modp) || VN_IS(varrefp, VarXRef)) { - // Keeping classOrPackagep will cause a broken link after inlining - varrefp->classOrPackagep(nullptr); - } - randomizeFuncp->addStmtsp(captured.getVar(varrefp->varp())); - } + captured.addFunctionArguments(randomizeFuncp); // Add constraints clearing code if (classGenp) { @@ -1366,9 +1584,12 @@ class RandomizeVisitor final : public VNVisitor { if (!classGenp && randModeVarp) addSetRandMode(randomizeFuncp, localGenp, randModeVarp); // Generate constraint setup code and a hardcoded call to the solver - randomizeFuncp->addStmtsp(captured.getTree()); - ConstraintExprVisitor{m_memberMap, captured.getTree(), randomizeFuncp, localGenp, - randModeVarp}; + AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); + randomizeFuncp->addStmtsp(capturedTreep); + { + ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, localGenp, + randModeVarp}; + } // Call the solver and set return value AstVarRef* const randNextp @@ -1423,7 +1644,7 @@ void V3Randomize::randomizeNetlist(AstNetlist* nodep) { } AstFunc* V3Randomize::newRandomizeFunc(VMemberMap& memberMap, AstClass* nodep, - const std::string& name) { + const std::string& name, bool allowVirtual) { AstFunc* funcp = VN_AS(memberMap.findMember(nodep, name), Func); if (!funcp) { v3Global.useRandomizeMethods(true); @@ -1438,7 +1659,7 @@ AstFunc* V3Randomize::newRandomizeFunc(VMemberMap& memberMap, AstClass* nodep, funcp = new AstFunc{nodep->fileline(), name, nullptr, fvarp}; funcp->dtypep(dtypep); funcp->classMethod(true); - funcp->isVirtual(nodep->isExtended()); + funcp->isVirtual(allowVirtual && nodep->isExtended()); nodep->addMembersp(funcp); memberMap.insert(nodep, funcp); AstClass* const basep = nodep->baseMostClassp(); diff --git a/src/V3Randomize.h b/src/V3Randomize.h index eeb2077b0..596fb9b77 100644 --- a/src/V3Randomize.h +++ b/src/V3Randomize.h @@ -33,7 +33,8 @@ public: static void randomizeNetlist(AstNetlist* nodep) VL_MT_DISABLED; static AstFunc* newRandomizeFunc(VMemberMap& memberMap, AstClass* nodep, - const std::string& name = "randomize") VL_MT_DISABLED; + const std::string& name = "randomize", + bool allowVirtual = true) VL_MT_DISABLED; static AstFunc* newSRandomFunc(VMemberMap& memberMap, AstClass* nodep) VL_MT_DISABLED; }; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 66289b0e3..fd0a85b09 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -221,6 +221,7 @@ class WidthVisitor final : public VNVisitor { const AstWith* m_withp = nullptr; // Current 'with' statement const AstFunc* m_funcp = nullptr; // Current function const AstAttrOf* m_attrp = nullptr; // Current attribute + const AstNodeExpr* m_randomizeFromp = nullptr; // Current randomize method call fromp const bool m_paramsOnly; // Computing parameter value; limit operation const bool m_doGenerate; // Do errors later inside generate statement int m_dtTables = 0; // Number of created data type tables @@ -2993,6 +2994,8 @@ class WidthVisitor final : public VNVisitor { nodep->dtypep(foundp->dtypep()); nodep->varp(varp); nodep->didWidth(true); + if (nodep->fromp()->sameTree(m_randomizeFromp) && varp->isRand()) // null-safe + V3LinkLValue::linkLValueSet(nodep); return true; } if (AstEnumItemRef* const adfoundp = VN_CAST(foundp, EnumItemRef)) { @@ -3791,6 +3794,8 @@ class WidthVisitor final : public VNVisitor { AstClass* const first_classp = adtypep->classp(); AstWith* withp = nullptr; if (nodep->name() == "randomize") { + VL_RESTORER(m_randomizeFromp); + m_randomizeFromp = nodep->fromp(); withp = methodWithArgument(nodep, false, false, adtypep->findVoidDType(), adtypep->findBitDType(), adtypep); methodOkArguments(nodep, 0, 0); diff --git a/test_regress/t/t_package_local_bad.out b/test_regress/t/t_package_local_bad.out index d92fb60d7..8cad1cb2e 100644 --- a/test_regress/t/t_package_local_bad.out +++ b/test_regress/t/t_package_local_bad.out @@ -1,6 +1,6 @@ -%Error: t/t_package_local_bad.v:9:23: Illegal 'local::' outside 'randomize() with' +%Error: t/t_package_local_bad.v:9:16: Illegal 'local::' outside 'randomize() with' 9 | $display(local::x); - | ^ + | ^~~~~ %Error: t/t_package_local_bad.v:9:23: Can't find definition of scope/variable/func: 'x' 9 | $display(local::x); | ^ diff --git a/test_regress/t/t_randomize_method_with.v b/test_regress/t/t_randomize_method_with.v index 34e06814e..616864d7b 100644 --- a/test_regress/t/t_randomize_method_with.v +++ b/test_regress/t/t_randomize_method_with.v @@ -26,14 +26,6 @@ class Boo; int unsigned boo; endclass -class Boo2; - function new(); - boo = 6; - endfunction - - int unsigned boo; -endclass - class Foo extends Boo; rand int unsigned a; rand int unsigned b; @@ -52,7 +44,7 @@ endclass // Current AstWith representation makes VARs of caller indistinguishable from VARs of randomized // object if both the caller and callee are the same module, but different instances. // That's why for the purpose of this test, the caller derives a different class -class Bar extends Boo2; +class Bar extends Boo; // Give the local variables a different scope by defining the functino under Bar static function bit test_local_constrdep(Foo foo, int c); return foo.randomize() with { a <= c; a > 1; x % a == 0; } == 1; @@ -60,10 +52,17 @@ class Bar extends Boo2; function bit test_capture_of_callers_derived_var(Foo foo); boo = 4; + foo.a = 3; return (foo.randomize() with { a == local::boo; } == 1) && (foo.a == 4); endfunction static function bit test_capture_of_callees_derived_var(Foo foo); + foo.a = 5; + return (foo.randomize() with { a == boo; } == 1) && (foo.a == 6); + endfunction + + static function bit test_capture_of_local_qualifier(Foo foo); + foo.a = 5; return (foo.randomize() with { a == boo; } == 1) && (foo.a == 6); endfunction endclass diff --git a/test_regress/t/t_randomize_method_with_scoping.pl b/test_regress/t/t_randomize_method_with_scoping.pl new file mode 100755 index 000000000..c4618c2fd --- /dev/null +++ b/test_regress/t/t_randomize_method_with_scoping.pl @@ -0,0 +1,25 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2019 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +scenarios(simulator => 1); + +if (!$Self->have_solver) { + skip("No constraint solver installed"); +} else { + compile( + ); + + execute( + check_finished => 1, + ); +} + +ok(1); +1; diff --git a/test_regress/t/t_randomize_method_with_scoping.v b/test_regress/t/t_randomize_method_with_scoping.v new file mode 100644 index 000000000..2afd20c98 --- /dev/null +++ b/test_regress/t/t_randomize_method_with_scoping.v @@ -0,0 +1,101 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro Ltd. +// SPDX-License-Identifier: CC0-1.0 + +class c1; + rand int c1_f; +endclass +class c2; + rand int c2_f; +endclass + +class Cls; + rand int x; + rand enum { + ONE_Y, + TWO_Y + } y; + virtual function int get_x(); + return x; + endfunction +endclass +class SubA extends Cls; + c1 e = new; + rand enum { + AMBIG, + ONE_A, + TWO_A + } en; + function c1 get_c(); + return e; + endfunction + function int op(int v); + return v + 1; + endfunction +endclass +class SubB extends Cls; + c2 e = new; + rand enum { + AMBIG, + ONE_B, + TWO_B + } en; + SubA f = new; + function c2 get_c(); + return e; + endfunction + function int op(int v); + return v - 1; + endfunction + function int doit; + // access ambiguous names so width complains if we miss something + doit = 1; + + f.x = 4; + x = 5; + doit = f.randomize() with { x == local::x; }; + if (f.x != x) $stop; + + doit &= f.randomize() with { e.c1_f == local::e.c2_f; }; + doit &= f.randomize() with { get_x() == local::get_x(); }; + doit &= f.randomize() with { get_c().c1_f == local::get_c().c2_f; }; + doit &= f.randomize() with { (get_c).c1_f == (local::get_c).c2_f; }; + + f.y = ONE_Y; + y = TWO_Y; + doit &= f.randomize() with { y == local::y; }; + if (f.y != y) $stop; + + f.en = SubA::ONE_A; + doit &= f.randomize() with { en == AMBIG; }; + if (doit != 1) $stop; + if (f.en != SubA::AMBIG) $stop; + + f.en = SubA::ONE_A; + doit &= f.randomize() with { en == ONE_A; }; + doit &= f.randomize() with { local::en == local::AMBIG; }; + en = ONE_B; + doit &= f.randomize() with { local::en == ONE_B; }; + + doit &= f.randomize() with { x == local::op(op(0)); }; + if (f.x != 0) $stop; + doit &= f.randomize() with { x == op(local::op(1)); }; + if (f.x != 1) $stop; + doit &= f.randomize() with { x == local::op(op(local::op(op(0)))); }; + if (f.x != 0) $stop; + doit &= f.randomize() with { x == op(local::op(op(local::op(1)))); }; + if (f.x != 1) $stop; + endfunction +endclass + +module t (/*AUTOARG*/); + SubB obj = new; + + initial begin + if (obj.doit != 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule