From 3cf9606ea9d940b142ea1192f94979d7cd725602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Boro=C5=84ski?= <94375110+kboronski-ant@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:18:18 +0200 Subject: [PATCH] Support inline constraints for class randomization methods (#5234) Signed-off-by: Krzysztof Boronski --- src/V3LinkDot.cpp | 1 + src/V3Randomize.cpp | 353 +++++++++++++++--- src/V3Randomize.h | 5 +- src/V3Task.cpp | 1 + src/V3Width.cpp | 6 +- test_regress/t/t_randomize.out | 7 +- .../t/t_randomize_method_types_unsup.v | 3 +- test_regress/t/t_randomize_method_with.pl | 23 ++ test_regress/t/t_randomize_method_with.v | 112 ++++++ .../t/t_randomize_method_with_bad.out | 4 + ...nsup.pl => t_randomize_method_with_bad.pl} | 6 +- test_regress/t/t_randomize_method_with_bad.v | 20 + .../t/t_randomize_method_with_unsup.out | 12 - .../t/t_randomize_method_with_unsup.v | 71 ---- 14 files changed, 470 insertions(+), 154 deletions(-) create mode 100755 test_regress/t/t_randomize_method_with.pl create mode 100644 test_regress/t/t_randomize_method_with.v create mode 100644 test_regress/t/t_randomize_method_with_bad.out rename test_regress/t/{t_randomize_method_with_unsup.pl => t_randomize_method_with_bad.pl} (84%) create mode 100644 test_regress/t/t_randomize_method_with_bad.v delete mode 100644 test_regress/t/t_randomize_method_with_unsup.out delete mode 100644 test_regress/t/t_randomize_method_with_unsup.v diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 1bcada129..7f1e53667 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1839,6 +1839,7 @@ class LinkDotScopeVisitor final : public VNVisitor { symp->fallbackp(m_modSymp); // No recursion, we don't want to pick up variables } + void visit(AstConstraintForeach* nodep) override { iterateChildren(nodep); } void visit(AstWith* nodep) override { VSymEnt* const symp = m_statep->insertBlock(m_modSymp, nodep->name(), nodep, nullptr); symp->fallbackp(m_modSymp); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index d93eeca15..454cd3176 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Expression width calculations +// DESCRIPTION: Verilator: Generate randomization procedures // // Code available from: https://verilator.org // @@ -26,9 +26,18 @@ #include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT +#include "verilatedos.h" + #include "V3Randomize.h" +#include "V3Ast.h" +#include "V3Error.h" +#include "V3FileLine.h" +#include "V3Global.h" #include "V3MemberMap.h" +#include "V3UniqueNames.h" + +#include VL_DEFINE_DEBUG_FUNCTIONS; @@ -48,7 +57,10 @@ class RandomizeMarkVisitor final : public VNVisitorConst { BaseToDerivedMap m_baseToDerivedMap; // Mapping from base classes to classes that extend them AstClass* m_classp = nullptr; // Current class - const AstNode* m_constraintExprp = nullptr; // Current constraint expression + AstNode* m_constraintExprp = nullptr; // Current constraint expression + AstNodeModule* m_modp; // Current module + std::map m_moduleMap; // Variable -> module under which it is + std::set m_staticRefs; // References to static variables under `with` clauses // METHODS void markMembers(const AstClass* nodep) { @@ -87,6 +99,12 @@ class RandomizeMarkVisitor final : public VNVisitorConst { if (p.first->user1()) markDerived(p.first); } } + void setPackageRefs() { + for (AstNodeVarRef* staticRefp : m_staticRefs) { + UINFO(9, "Updated classOrPackage ref for " << staticRefp->name() << endl); + staticRefp->classOrPackagep(m_moduleMap[staticRefp->varp()]); + } + } // VISITORS void visit(AstClass* nodep) override { @@ -130,19 +148,32 @@ class RandomizeMarkVisitor final : public VNVisitorConst { } void visit(AstNodeVarRef* nodep) override { if (!m_constraintExprp) return; + + if (nodep->varp()->lifetime().isStatic()) m_staticRefs.emplace(nodep); + 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; + iterateChildrenConst(nodep); + } + void visit(AstVar* nodep) override { + m_moduleMap.emplace(nodep, m_modp); + iterateChildrenConst(nodep); + } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } public: // CONSTRUCTORS - explicit RandomizeMarkVisitor(AstNetlist* nodep) { + explicit RandomizeMarkVisitor(AstNode* nodep) { iterateConst(nodep); markAllDerived(); + setPackageRefs(); } ~RandomizeMarkVisitor() override = default; }; @@ -152,12 +183,13 @@ public: class ConstraintExprVisitor final : public VNVisitor { // NODE STATE - // AstVar::user4() -> bool. Handled in constraints + // AstVar::user4() -> bool. Handled in constraints // AstNodeExpr::user1() -> bool. Depending on a randomized variable - // VNUser4InUse m_inuser4; (Allocated for use in RandomizeVisitor) + // VNUser4InUse m_inuser4; (Allocated for use in RandomizeVisitor) - AstNodeFTask* const m_taskp; // method to add write_var calls to + AstNodeFTask* const m_taskp; // Method to add write_var calls to AstVar* const m_genp; // VlRandomizer variable of the class + bool m_markDeclaredConstrs; // Mark constraints as aready setup with `write_var`. bool m_wantSingle = false; // Whether to merge constraint expressions with LOGAND bool editFormat(AstNodeExpr* nodep) { @@ -257,12 +289,13 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); if (!varp->user4()) { - varp->user4(true); + varp->user4(m_markDeclaredConstrs); AstCMethodHard* const methodp = new AstCMethodHard{ varp->fileline(), new AstVarRef{varp->fileline(), m_genp, VAccess::READWRITE}, "write_var"}; methodp->dtypeSetVoid(); - methodp->addPinsp(new AstVarRef{varp->fileline(), varp, VAccess::WRITE}); + AstVarRef* varRefp = new AstVarRef{varp->fileline(), varp, VAccess::WRITE}; + methodp->addPinsp(varRefp); methodp->addPinsp(new AstConst{varp->dtypep()->fileline(), AstConst::Unsized64{}, (size_t)varp->width()}); AstNodeExpr* const varnamep @@ -377,13 +410,102 @@ class ConstraintExprVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit ConstraintExprVisitor(AstNode* nodep, AstNodeFTask* taskp, AstVar* genp) + explicit ConstraintExprVisitor(AstNode* nodep, AstNodeFTask* taskp, AstVar* genp, + bool markDeclaredConstrs) : m_taskp(taskp) - , m_genp(genp) { + , m_genp(genp) + , m_markDeclaredConstrs(markDeclaredConstrs) { iterateAndNextNull(nodep); } }; +template +class CaptureFrame final { + TreeNodeType* m_treep; // Original tree + 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; + + bool captureVariable(FileLine* const fileline, AstNodeVarRef* varrefp, AstVar*& varp) { + auto it = m_varCloneMap.find(varrefp->varp()); + if (it == m_varCloneMap.end()) { + AstVar* const newVarp = varrefp->varp()->cloneTree(false); + newVarp->fileline(fileline); + newVarp->varType(VVarType::BLOCKTEMP); + newVarp->funcLocal(true); + newVarp->direction(VDirection::INPUT); + newVarp->lifetime(VLifetime::AUTOMATIC); + + m_varCloneMap.emplace(varrefp->varp(), newVarp); + varp = newVarp; + return true; + } + varp = it->second; + 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); + } + } + +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}); + }); + } + + // PUBLIC METHODS + + TreeNodeType* getTree() const { return m_treep; } + + AstVar* getVar(AstVar* const varp) const { + const auto it = m_varCloneMap.find(varp); + if (it == m_varCloneMap.end()) { return nullptr; } + return it->second; + } + + AstArg* getArgs() const { return m_argsp; } +}; + //###################################################################### // Visitor that defines a randomize method where needed @@ -392,7 +514,8 @@ class RandomizeVisitor final : public VNVisitor { // Cleared on Netlist // AstClass::user1() -> bool. Set true to indicate needs randomize processing // AstEnumDType::user2() -> AstVar*. Pointer to table with enum values - // AstClass::user3() -> AstFunc*. Pointer to randomize() method of a class + // AstConstraint::user2p() -> AstTask*. Pointer to constraint setup procedure + // AstClass::user3p() -> AstFunc*. Pointer to randomize() method of a class // AstVar::user4() -> bool. Handled in constraints // AstClass::user4() -> AstVar*. Constrained randomizer variable // VNUser1InUse m_inuser1; (Allocated for use in RandomizeMarkVisitor) @@ -401,15 +524,17 @@ class RandomizeVisitor final : public VNVisitor { const VNUser4InUse m_inuser4; // STATE + V3UniqueNames m_inlineUniqueNames; // For generating unique function names VMemberMap m_memberMap; // Member names cached for fast lookup AstNodeModule* m_modp = nullptr; // Current module const AstNodeFTask* m_ftaskp = nullptr; // Current function/task size_t m_enumValueTabCount = 0; // Number of tables with enum values created int m_randCaseNum = 0; // Randcase number within a module for var naming std::map m_randcDtypes; // RandC data type deduplication + bool m_inline = false; // Constraints are inline // METHODS - AstVar* enumValueTabp(AstEnumDType* nodep) { + AstVar* enumValueTabp(AstEnumDType* const nodep) { if (nodep->user2p()) return VN_AS(nodep->user2p(), Var); UINFO(9, "Construct Venumvaltab " << nodep << endl); AstNodeArrayDType* const vardtypep = new AstUnpackArrayDType{ @@ -425,6 +550,7 @@ class RandomizeVisitor final : public VNVisitor { varp->valuep(initp); // Add to root, as don't know module we are in, and aids later structure sharing v3Global.rootp()->dollarUnitPkgAddp()->addStmtsp(varp); + UASSERT_OBJ(nodep->itemsp(), nodep, "Enum without items"); for (AstEnumItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), EnumItem)) { @@ -436,7 +562,7 @@ class RandomizeVisitor final : public VNVisitor { return varp; } - AstCDType* findVlRandCDType(FileLine* fl, uint64_t items) { + AstCDType* findVlRandCDType(FileLine* const fl, uint64_t items) { // For 8 items we need to have a 9 item LFSR so items is max count // width(items) = log2(items) + 1 const std::string type = AstCDType::typeToHold(V3Number::log2bQuad(items) + 1); @@ -451,7 +577,7 @@ class RandomizeVisitor final : public VNVisitor { return pair.first->second; } - AstVar* newRandcVarsp(AstVar* varp) { + AstVar* newRandcVarsp(AstVar* const varp) { // If a randc, make a VlRandC object to hold the state if (!varp->isRandC()) return nullptr; uint64_t items = 0; @@ -529,18 +655,19 @@ class RandomizeVisitor final : public VNVisitor { valp}; } } - AstNodeExpr* newRandValue(FileLine* fl, AstVar* randcVarp, AstNodeDType* dtypep) { + AstNodeExpr* newRandValue(FileLine* const fl, AstVar* const randcVarp, + AstNodeDType* const dtypep) { if (randcVarp) { - AstNode* argsp = new AstVarRef{fl, randcVarp, VAccess::READWRITE}; - argsp->addNext(new AstText{fl, ".randomize(__Vm_rng)"}); - AstCExpr* newp = new AstCExpr{fl, argsp}; + AstVarRef* const argsp = new AstVarRef{fl, randcVarp, VAccess::READWRITE}; + argsp->AstNode::addNext(new AstText{fl, ".randomize(__Vm_rng)"}); + AstCExpr* const newp = new AstCExpr{fl, argsp}; newp->dtypep(dtypep); return newp; } else { return new AstRandRNG{fl, dtypep}; } } - void addPrePostCall(AstClass* classp, AstFunc* funcp, const string& name) { + void addPrePostCall(AstClass* const classp, AstFunc* const funcp, const string& name) { if (AstTask* userFuncp = VN_CAST(m_memberMap.findMember(classp, name), Task)) { AstTaskRef* const callp = new AstTaskRef{userFuncp->fileline(), userFuncp->name(), nullptr}; @@ -548,12 +675,18 @@ class RandomizeVisitor final : public VNVisitor { funcp->addStmtsp(callp->makeStmt()); } } - AstTask* newSetupConstraintTask(AstClass* nodep, const std::string& name) { + AstTask* newSetupConstraintTask(AstClass* const nodep, const std::string& name) { AstTask* const taskp = new AstTask{nodep->fileline(), name + "_setup_constraint", nullptr}; taskp->classMethod(true); nodep->addMembersp(taskp); return taskp; } + AstNodeStmt* implementConstraintsClear(FileLine* const fileline, AstVar* const genp) { + AstCMethodHard* const clearp = new AstCMethodHard{ + fileline, new AstVarRef{fileline, genp, VAccess::READWRITE}, "clear"}; + clearp->dtypeSetVoid(); + return clearp->makeStmt(); + } // VISITORS void visit(AstNodeModule* nodep) override { @@ -573,6 +706,7 @@ class RandomizeVisitor final : public VNVisitor { VL_RESTORER(m_randCaseNum); m_modp = nodep; m_randCaseNum = 0; + iterateChildren(nodep); if (!nodep->user1()) return; // Doesn't need randomize, or already processed UINFO(9, "Define randomize() for " << nodep << endl); @@ -596,21 +730,22 @@ class RandomizeVisitor final : public VNVisitor { } } if (m_modp->user4p()) { - AstNode* const argsp = new AstVarRef{nodep->fileline(), VN_AS(m_modp->user4p(), Var), - VAccess::READWRITE}; - argsp->addNext(new AstText{fl, ".next(__Vm_rng)"}); - AstNodeExpr* const solverCallp = new AstCExpr{fl, argsp}; + AstVarRef* const genRefp = new AstVarRef{ + nodep->fileline(), VN_AS(m_modp->user4p(), Var), VAccess::READWRITE}; + genRefp->AstNode::addNext(new AstText{fl, ".next(__Vm_rng)"}); + AstNodeExpr* const solverCallp = new AstCExpr{fl, genRefp}; solverCallp->dtypeSetBit(); beginValp = beginValp ? new AstLogAnd{fl, beginValp, solverCallp} : solverCallp; } if (!beginValp) beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1}; - funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, fvarp, VAccess::WRITE}, beginValp}); + AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; + funcp->addStmtsp(new AstAssign{fl, fvarRefp, beginValp}); for (AstNode* memberp = nodep->stmtsp(); memberp; memberp = memberp->nextp()) { AstVar* const memberVarp = VN_CAST(memberp, Var); if (!memberVarp || !memberVarp->isRand() || memberVarp->user4()) continue; - const AstNodeDType* const dtypep = memberp->dtypep()->skipRefp(); + const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp(); if (VN_IS(dtypep, BasicDType) || VN_IS(dtypep, StructDType)) { AstVar* const randcVarp = newRandcVarsp(memberVarp); AstVarRef* const refp = new AstVarRef{fl, memberVarp, VAccess::WRITE}; @@ -618,38 +753,40 @@ class RandomizeVisitor final : public VNVisitor { funcp->addStmtsp(stmtp); } else if (const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType)) { if (classRefp->classp() == nodep) { - memberp->v3warn( + memberVarp->v3warn( E_UNSUPPORTED, "Unsupported: random member variable with type of a current class"); continue; } - AstVarRef* const refp = new AstVarRef{fl, memberVarp, VAccess::WRITE}; AstFunc* const memberFuncp = V3Randomize::newRandomizeFunc(classRefp->classp()); - AstMethodCall* const callp = new AstMethodCall{fl, refp, "randomize", nullptr}; + AstMethodCall* const callp = new AstMethodCall{ + fl, new AstVarRef{fl, memberVarp, VAccess::WRITE}, "randomize", nullptr}; callp->taskp(memberFuncp); callp->dtypeFrom(memberFuncp); - AstAssign* const assignp = new AstAssign{ - fl, new AstVarRef{fl, fvarp, VAccess::WRITE}, - new AstAnd{fl, new AstVarRef{fl, fvarp, VAccess::READ}, callp}}; + AstVarRef* fvarRefReadp = fvarRefp->cloneTree(false); + fvarRefReadp->access(VAccess::READ); AstIf* const assignIfNotNullp = new AstIf{fl, new AstNeq{fl, new AstVarRef{fl, memberVarp, VAccess::READ}, new AstConst{fl, AstConst::Null{}}}, - assignp}; + new AstAssign{fl, fvarRefp->cloneTree(false), + new AstAnd{fl, fvarRefReadp, callp}}}; funcp->addStmtsp(assignIfNotNullp); } else { - memberp->v3warn(E_UNSUPPORTED, "Unsupported: random member variable with type " - << memberp->dtypep()->prettyDTypeNameQ()); + memberVarp->v3warn(E_UNSUPPORTED, "Unsupported: random member variable with type " + << memberVarp->dtypep()->prettyDTypeNameQ()); } } addPrePostCall(nodep, funcp, "post_randomize"); nodep->user1(false); } void visit(AstConstraint* nodep) override { + if (nodep->user2p()) return; // Already visited AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(m_modp, "new"), NodeFTask); UASSERT_OBJ(newp, m_modp, "No new() in class"); AstFunc* const randomizep = V3Randomize::newRandomizeFunc(VN_AS(m_modp, Class)); AstTask* const taskp = newSetupConstraintTask(VN_AS(m_modp, Class), nodep->name()); + nodep->user2p(taskp); AstTaskRef* const setupTaskRefp = new AstTaskRef{nodep->fileline(), taskp->name(), nullptr}; setupTaskRefp->taskp(taskp); @@ -657,25 +794,20 @@ class RandomizeVisitor final : public VNVisitor { AstVar* genp = VN_AS(m_modp->user4p(), Var); if (!genp) { genp = VN_AS(m_memberMap.findMember(m_modp, "constraint"), Var); - if (!genp) + if (!genp) { genp = new AstVar{nodep->fileline(), VVarType::MEMBER, "constraint", m_modp->findBasicDType(VBasicDTypeKwd::RANDOM_GENERATOR)}; + } VN_AS(m_modp, Class)->addMembersp(genp); m_modp->user4p(genp); } if (!randomizep->stmtsp()) { - AstCMethodHard* const clearp = new AstCMethodHard{ - randomizep->fileline(), - new AstVarRef{randomizep->fileline(), genp, VAccess::READWRITE}, "clear"}; - clearp->dtypeSetVoid(); - randomizep->addStmtsp(clearp->makeStmt()); + randomizep->addStmtsp(implementConstraintsClear(randomizep->fileline(), genp)); } randomizep->addStmtsp(setupTaskRefp->makeStmt()); - - { ConstraintExprVisitor{nodep->itemsp(), newp, genp}; } + { ConstraintExprVisitor{nodep->itemsp(), (m_inline ? taskp : newp), genp, !m_inline}; } if (nodep->itemsp()) taskp->addStmtsp(nodep->itemsp()->unlinkFrBackWithNext()); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstRandCase* nodep) override { // RANDCASE @@ -697,7 +829,7 @@ class RandomizeVisitor final : public VNVisitor { randVarp->noSubst(true); if (m_ftaskp) randVarp->funcLocal(true); AstNodeExpr* sump = new AstConst{fl, AstConst::WidthedValue{}, 64, 0}; - AstNodeIf* firstIfsp + AstNodeIf* const firstIfsp = new AstIf{fl, new AstConst{fl, AstConst::BitFalse{}}, nullptr, nullptr}; AstNodeIf* ifsp = firstIfsp; @@ -708,11 +840,10 @@ class RandomizeVisitor final : public VNVisitor { = new AstAdd{condp->fileline(), sump, new AstExtend{itemp->fileline(), condp, 64}}; AstNode* const stmtsp = itemp->stmtsp() ? itemp->stmtsp()->unlinkFrBackWithNext() : nullptr; + AstVarRef* const randVarRefp = new AstVarRef{fl, randVarp, VAccess::WRITE}; AstNodeIf* const newifp = new AstIf{itemp->fileline(), - new AstLte{condp->fileline(), - new AstVarRef{condp->fileline(), randVarp, VAccess::READ}, - sump->cloneTreePure(true)}, + new AstLte{condp->fileline(), randVarRefp, sump->cloneTreePure(true)}, stmtsp, nullptr}; ifsp->addElsesp(newifp); ifsp = newifp; @@ -724,10 +855,11 @@ class RandomizeVisitor final : public VNVisitor { dispp->fmtp()->timeunit(m_modp->timeunit()); ifsp->addElsesp(dispp); - AstNode* newp = randVarp; + AstNode* const newp = randVarp; AstNodeExpr* randp = new AstRand{fl, nullptr, false}; randp->dtypeSetUInt64(); - newp->addNext(new AstAssign{fl, new AstVarRef{fl, randVarp, VAccess::WRITE}, + AstVarRef* const randVarRefp = new AstVarRef{fl, randVarp, VAccess::WRITE}; + newp->addNext(new AstAssign{fl, randVarRefp, new AstAdd{fl, new AstConst{fl, AstConst::Unsized64{}, 1}, new AstModDiv{fl, randp, sump}}}); newp->addNext(firstIfsp); @@ -735,11 +867,118 @@ class RandomizeVisitor final : public VNVisitor { nodep->replaceWith(newp); VL_DO_DANGLING(pushDeletep(nodep), nodep); } + void visit(AstMethodCall* nodep) override { + AstWith* const withp = VN_CAST(nodep->pinsp(), With); + + if (!(nodep->name() == "randomize") || !withp) { + iterateChildren(nodep); + return; + } + + VL_RESTORER(m_inline); + m_inline = true; + iterateChildren(nodep); + + UASSERT_OBJ(nodep->fromp()->dtypep(), nodep->fromp(), "Object dtype is not linked"); + AstClassRefDType* const classrefdtypep = VN_CAST(nodep->fromp()->dtypep(), ClassRefDType); + if (!classrefdtypep) { + nodep->v3warn(E_UNSUPPORTED, + "Inline constraints are not supported for this node type"); + return; + } + AstClass* const classp = classrefdtypep->classp(); + UASSERT_OBJ(classp, classrefdtypep, "Class type is unlinked to its ref type"); + if (classp->user1()) { + // We need to first ensure that the class randomizer is instantiated if needed + // NOTE: This is safe only because AstClass visit function overwrites all + // nesting-dependent state variables + iterate(classp); + } + + AstVar* const classGenp = VN_CAST(classp->user4p(), Var); + AstVar* const localGenp + = new AstVar{nodep->fileline(), VVarType::BLOCKTEMP, "randomizer", + classp->findBasicDType(VBasicDTypeKwd::RANDOM_GENERATOR)}; + localGenp->funcLocal(true); + + AstFunc* const randomizeFuncp + = V3Randomize::newRandomizeFunc(classp, m_inlineUniqueNames.get(nodep)); + + // 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"); + + // 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())); + } + + // Add constraints clearing code + if (classGenp) { + randomizeFuncp->addStmtsp( + implementConstraintsClear(randomizeFuncp->fileline(), classGenp)); + } + + randomizeFuncp->addStmtsp(localGenp); + + // Copy (derive) class constraints if present + if (classGenp) { + classp->foreach([&](AstConstraint* constrp) { + AstTask* constrSetupFuncp = VN_AS(constrp->user2p(), Task); + UASSERT_OBJ(constrSetupFuncp, constrp, "Constraint not linked to setup procudure"); + auto callp = new AstTaskRef{nodep->fileline(), constrSetupFuncp->name(), nullptr}; + callp->taskp(constrSetupFuncp); + randomizeFuncp->addStmtsp(callp->makeStmt()); + }); + + randomizeFuncp->addStmtsp(new AstAssign{ + nodep->fileline(), new AstVarRef{nodep->fileline(), localGenp, VAccess::WRITE}, + new AstVarRef{nodep->fileline(), classGenp, VAccess::READ}}); + } + + // Generate constraint setup code and a hardcoded call to the solver + randomizeFuncp->addStmtsp(captured.getTree()); + { ConstraintExprVisitor{captured.getTree(), randomizeFuncp, localGenp, !m_inline}; } + + // Call the solver and set return value + AstVarRef* const randNextp + = new AstVarRef{nodep->fileline(), localGenp, VAccess::READWRITE}; + randNextp->AstNode::addNext(new AstText{nodep->fileline(), ".next(__Vm_rng)"}); + AstNodeExpr* const solverCallp = new AstCExpr{nodep->fileline(), randNextp}; + solverCallp->dtypeSetBit(); + randomizeFuncp->addStmtsp(new AstAssign{ + nodep->fileline(), + new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var), VAccess::WRITE}, + solverCallp}); + + // Replace the node with a call to that function + AstMethodCall* const callp + = new AstMethodCall(nodep->fileline(), nodep->fromp()->unlinkFrBack(), + randomizeFuncp->name(), captured.getArgs()); + callp->taskp(randomizeFuncp); + callp->dtypeFrom(randomizeFuncp->dtypep()); + callp->classOrPackagep(classp); + nodep->replaceWith(callp); + UINFO(9, "Added `%s` randomization procedure"); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: // CONSTRUCTORS - explicit RandomizeVisitor(AstNetlist* nodep) { iterate(nodep); } + explicit RandomizeVisitor(AstNetlist* nodep) + : m_inlineUniqueNames("__Vrandwith") { + iterate(nodep); + nodep->foreach([&](AstConstraint* constrp) { + VL_DO_DANGLING(pushDeletep(constrp->unlinkFrBack()), constrp); + }); + } ~RandomizeVisitor() override = default; }; @@ -750,24 +989,26 @@ void V3Randomize::randomizeNetlist(AstNetlist* nodep) { UINFO(2, __FUNCTION__ << ": " << endl); { const RandomizeMarkVisitor markVisitor{nodep}; - RandomizeVisitor{nodep}; + RandomizeVisitor randomizeVisitor{nodep}; } V3Global::dumpCheckGlobalTree("randomize", 0, dumpTreeEitherLevel() >= 3); } -AstFunc* V3Randomize::newRandomizeFunc(AstClass* nodep) { +AstFunc* V3Randomize::newRandomizeFunc(AstClass* nodep, const std::string& name) { VMemberMap memberMap; - AstFunc* funcp = VN_AS(memberMap.findMember(nodep, "randomize"), Func); + AstFunc* funcp = VN_AS(memberMap.findMember(nodep, name), Func); + if (!funcp) { v3Global.useRandomizeMethods(true); AstNodeDType* const dtypep = nodep->findBitDType(32, 32, VSigning::SIGNED); // IEEE says int return of 0/1 - AstVar* const fvarp = new AstVar{nodep->fileline(), VVarType::MEMBER, "randomize", dtypep}; + AstVar* const fvarp = new AstVar{nodep->fileline(), VVarType::MEMBER, name, dtypep}; fvarp->lifetime(VLifetime::AUTOMATIC); fvarp->funcLocal(true); fvarp->funcReturn(true); fvarp->direction(VDirection::OUTPUT); - funcp = new AstFunc{nodep->fileline(), "randomize", nullptr, fvarp}; + nodep->addMembersp(funcp); + funcp = new AstFunc{nodep->fileline(), name, nullptr, fvarp}; funcp->dtypep(dtypep); funcp->classMethod(true); funcp->isVirtual(nodep->isExtended()); diff --git a/src/V3Randomize.h b/src/V3Randomize.h index 24e0fb994..e7359a95d 100644 --- a/src/V3Randomize.h +++ b/src/V3Randomize.h @@ -1,6 +1,6 @@ // -*- mode: C++; c-file-style: "cc-mode" -*- //************************************************************************* -// DESCRIPTION: Verilator: Node attributes/ expression widths +// DESCRIPTION: Verilator: Generate randomization procedures // // Code available from: https://verilator.org // @@ -30,7 +30,8 @@ class V3Randomize final { public: static void randomizeNetlist(AstNetlist* nodep) VL_MT_DISABLED; - static AstFunc* newRandomizeFunc(AstClass* nodep) VL_MT_DISABLED; + static AstFunc* newRandomizeFunc(AstClass* nodep, + const std::string& name = "randomize") VL_MT_DISABLED; static AstFunc* newSRandomFunc(AstClass* nodep) VL_MT_DISABLED; }; diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 214ec9d3e..4baa98adb 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1626,6 +1626,7 @@ V3TaskConnects V3Task::taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp, bool reorganize = false; for (AstNode *nextp, *pinp = nodep->pinsp(); pinp; pinp = nextp) { nextp = pinp->nextp(); + if (VN_IS(pinp, With)) continue; AstArg* const argp = VN_AS(pinp, Arg); UASSERT_OBJ(argp, pinp, "Non-arg under ftask reference"); if (argp->name() != "") { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 5b659e47d..9e5320d85 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -67,6 +67,7 @@ #include "V3Width.h" +#include "V3Ast.h" #include "V3Const.h" #include "V3Error.h" #include "V3Global.h" @@ -3764,11 +3765,8 @@ class WidthVisitor final : public VNVisitor { nodep->dtypeFrom(ftaskp); nodep->classOrPackagep(classp); if (VN_IS(ftaskp, Task)) nodep->dtypeSetVoid(); + if (withp) nodep->addPinsp(withp); processFTaskRefArgs(nodep); - if (withp) { - withp->v3warn(CONSTRAINTIGN, "'with' constraint ignored (unsupported)"); - VL_DO_DANGLING(withp->deleteTree(), withp); - } } return; } else if (nodep->name() == "get_randstate" || nodep->name() == "set_randstate") { diff --git a/test_regress/t/t_randomize.out b/test_regress/t/t_randomize.out index 43bf1959a..5e8720300 100644 --- a/test_regress/t/t_randomize.out +++ b/test_regress/t/t_randomize.out @@ -1,12 +1,9 @@ -%Warning-CONSTRAINTIGN: t/t_randomize.v:66:25: 'with' constraint ignored (unsupported) - 66 | v = p.randomize() with { if_4 == local::if_4; header == 2; }; - | ^~~~ - ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest - ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. %Warning-CONSTRAINTIGN: t/t_randomize.v:37:7: Constraint expression ignored (unsupported) : ... note: In instance 't' 37 | foreach (array[i]) { | ^~~~~~~ + ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest + ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. %Warning-CONSTRAINTIGN: t/t_randomize.v:40:7: Constraint expression ignored (unsupported) : ... note: In instance 't' 40 | unique { array[0], array[1] }; diff --git a/test_regress/t/t_randomize_method_types_unsup.v b/test_regress/t/t_randomize_method_types_unsup.v index 4e24716bc..adc1a7bd0 100644 --- a/test_regress/t/t_randomize_method_types_unsup.v +++ b/test_regress/t/t_randomize_method_types_unsup.v @@ -22,9 +22,10 @@ endclass module t (/*AUTOARG*/); Cls obj; + int res; initial begin obj = new; - obj.randomize(); + res = obj.randomize(); end endmodule diff --git a/test_regress/t/t_randomize_method_with.pl b/test_regress/t/t_randomize_method_with.pl new file mode 100755 index 000000000..1bbe28c31 --- /dev/null +++ b/test_regress/t/t_randomize_method_with.pl @@ -0,0 +1,23 @@ +#!/usr/bin/env perl +if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2024 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); + +compile( + # Ensure we test captures of static variables + verilator_flags2 => ["--fno-inline"], + ); + +execute( + check_finished => 1, + ); + +ok(1); +1; diff --git a/test_regress/t/t_randomize_method_with.v b/test_regress/t/t_randomize_method_with.v new file mode 100644 index 000000000..4521d3601 --- /dev/null +++ b/test_regress/t/t_randomize_method_with.v @@ -0,0 +1,112 @@ +// 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 Boo; + function new(); + boo = 6; + endfunction + + 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; + int x; + + function new(int x); + this.x = x; + endfunction + + constraint constr1_c { b < x; } +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; + // 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; + endfunction + + function bit test_capture_of_callers_derived_var(Foo foo); + boo = 4; + return (foo.randomize() with { a == local::boo; } == 1) && (foo.a == 4); + endfunction + + static function bit test_capture_of_callees_derived_var(Foo foo); + return (foo.randomize() with { a == boo; } == 1) && (foo.a == 6); + endfunction +endclass + +class Baz; + rand int v; +endclass + +module submodule(); + int sub_var = 7; +endmodule + +function automatic int return_2(); + return 2; +endfunction + +module mwith(); + submodule sub1(); + submodule sub2(); + + function automatic int return_3(); + return 3; + endfunction + + initial begin + int c = 30; + Foo foo = new(c); + Baz baz = new; + Bar bar = new; + $display("foo.x = %d", foo.x); + $display("-----------------"); + + repeat (20) begin + if (Bar::test_local_constrdep(foo, 5)) begin + $display("foo.a = %d", foo.a); + $display("foo.b = %d", foo.b); + $display("-----------------"); + + if (!(foo.a inside {2, 3, 5})) $stop; + if (foo.b >= foo.x) $stop; + if (foo.a > c) $stop; + if (foo.a <= 1) $stop; + + sub1.sub_var = foo.a; + end else + $display("Failed to randomize foo with inline constraints"); + end + + // Check capture of a static variable + if (foo.randomize() with { a > sub1.sub_var; } != 1) $stop; + // Check reference to a function + if (foo.randomize() with { a > return_2(); } != 1) $stop; + // Check randomization of class with no constraints + if (baz.randomize() with { v inside {[2:10]}; } != 1) $stop; + // Check randomization with captured non-static variable from different AstNodeModule + if (!bar.test_capture_of_callers_derived_var(foo)) $stop; + // Check randomization with non-captured non-static variable from different AstNodeModule + if (!Bar::test_capture_of_callees_derived_var(foo)) $stop; + + $write("*-* All Finished *-*\n"); + $finish(); + end +endmodule diff --git a/test_regress/t/t_randomize_method_with_bad.out b/test_regress/t/t_randomize_method_with_bad.out new file mode 100644 index 000000000..584cdbd2b --- /dev/null +++ b/test_regress/t/t_randomize_method_with_bad.out @@ -0,0 +1,4 @@ +%Error: t/t_randomize_method_with_bad.v:18:42: Can't find definition of task/function: 'in_mod_function' + 18 | int res = foo.randomize() with { v < in_mod_function(); }; + | ^~~~~~~~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_method_with_unsup.pl b/test_regress/t/t_randomize_method_with_bad.pl similarity index 84% rename from test_regress/t/t_randomize_method_with_unsup.pl rename to test_regress/t/t_randomize_method_with_bad.pl index 882bc2418..f48f2cb92 100755 --- a/test_regress/t/t_randomize_method_with_unsup.pl +++ b/test_regress/t/t_randomize_method_with_bad.pl @@ -2,17 +2,17 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); die; } # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2023 by Wilson Snyder. This program is free software; you +# Copyright 2024 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(linter => 1); +scenarios(simulator => 1); compile( - expect_filename => $Self->{golden_filename}, fails => 1, + expect_filename => $Self->{golden_filename}, ); ok(1); diff --git a/test_regress/t/t_randomize_method_with_bad.v b/test_regress/t/t_randomize_method_with_bad.v new file mode 100644 index 000000000..b831634ce --- /dev/null +++ b/test_regress/t/t_randomize_method_with_bad.v @@ -0,0 +1,20 @@ +// 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 Foo; + rand int unsigned v; +endclass + +module t_randomize_method_with_bad(); + function automatic int unsigned in_mod_function(); + return 5; + endfunction + + initial begin + Foo foo = new; + int res = foo.randomize() with { v < in_mod_function(); }; + end +endmodule diff --git a/test_regress/t/t_randomize_method_with_unsup.out b/test_regress/t/t_randomize_method_with_unsup.out deleted file mode 100644 index d367d0db1..000000000 --- a/test_regress/t/t_randomize_method_with_unsup.out +++ /dev/null @@ -1,12 +0,0 @@ -%Warning-CONSTRAINTIGN: t/t_randomize_method_with_unsup.v:47:40: 'with' constraint ignored (unsupported) - 47 | rand_result = obj.randomize() with { lb <= y && y <= ub; }; - | ^~~~ - ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest - ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. -%Warning-CONSTRAINTIGN: t/t_randomize_method_with_unsup.v:63:37: 'with' constraint ignored (unsupported) - 63 | rand_result = obj.randomize() with { 256 < y && y < 256; }; - | ^~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_method_with_unsup.v:67:37: 'with' constraint ignored (unsupported) - 67 | rand_result = obj.randomize() with { 16 <= z && z <= 32; }; - | ^~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_randomize_method_with_unsup.v b/test_regress/t/t_randomize_method_with_unsup.v deleted file mode 100644 index fb070ce1b..000000000 --- a/test_regress/t/t_randomize_method_with_unsup.v +++ /dev/null @@ -1,71 +0,0 @@ -// 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 - -typedef enum bit[15:0] { - ONE = 3, - TWO = 5, - THREE = 8, - FOUR = 13 -} Enum; - -class Cls; - constraint A { v inside {ONE, THREE}; } - constraint B { w == 5; x inside {1,2} || x inside {4,5}; } - constraint C { z < 3 * 7; z > 5 + 8; } - - rand Enum v; - rand logic[63:0] w; - rand logic[47:0] x; - rand logic[31:0] y; - rand logic[23:0] z; - - function new; - v = ONE; - w = 0; - x = 0; - y = 0; - z = 0; - endfunction - -endclass - -module t (/*AUTOARG*/); - Cls obj; - - initial begin - int rand_result; - int lb, ub; - longint prev_checksum; - $display("===================\nSatisfiable constraints:"); - for (int i = 0; i < 25; i++) begin - obj = new; - lb = 16; - ub = 32; - rand_result = obj.randomize() with { lb <= y && y <= ub; }; - $display("obj.v == %0d", obj.v); - $display("obj.w == %0d", obj.w); - $display("obj.x == %0d", obj.x); - $display("obj.y == %0d", obj.y); - $display("obj.z == %0d", obj.z); - $display("rand_result == %0d", rand_result); - $display("-------------------"); - if (!(obj.v inside {ONE, THREE})) $stop; - if (obj.w != 5) $stop; - if (!(obj.x inside {1,2,4,5})) $stop; - if (obj.y < 16 || obj.y > 32) $stop; - if (obj.z <= 13 || obj.z >= 21) $stop; - if (lb != 16 || ub != 32) $stop; - end - $display("===================\nUnsatisfiable constraints for obj.y:"); - rand_result = obj.randomize() with { 256 < y && y < 256; }; - $display("obj.y == %0d", obj.y); - $display("rand_result == %0d", rand_result); - if (rand_result != 0) $stop; - rand_result = obj.randomize() with { 16 <= z && z <= 32; }; - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule