From 6cb0a4185787d7745e984f22f1bd8fa6d1bc88bd Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Tue, 13 Aug 2024 20:20:31 +0200 Subject: [PATCH] Support inline random variable control (#5317) Signed-off-by: Krzysztof Bieganski --- src/V3Ast.h | 30 ++ src/V3AstNodeOther.h | 16 +- src/V3AstNodes.cpp | 6 +- src/V3LinkDot.cpp | 50 +++- src/V3ParseImp.h | 4 +- src/V3Randomize.cpp | 281 ++++++++++++++++-- src/V3Task.cpp | 4 +- src/V3Width.cpp | 105 ++++++- test_regress/t/t_randomize_inline_var_ctl.pl | 25 ++ test_regress/t/t_randomize_inline_var_ctl.v | 156 ++++++++++ .../t/t_randomize_inline_var_ctl_bad.out | 25 ++ ...p.pl => t_randomize_inline_var_ctl_bad.pl} | 4 +- .../t/t_randomize_inline_var_ctl_bad.v | 28 ++ .../t/t_randomize_inline_var_ctl_unsup.out | 9 - .../t/t_randomize_inline_var_ctl_unsup_1.out | 14 + .../t/t_randomize_inline_var_ctl_unsup_1.pl | 19 ++ .../t/t_randomize_inline_var_ctl_unsup_1.v | 24 ++ .../t/t_randomize_inline_var_ctl_unsup_2.out | 8 + .../t/t_randomize_inline_var_ctl_unsup_2.pl | 19 ++ ...v => t_randomize_inline_var_ctl_unsup_2.v} | 16 +- 20 files changed, 758 insertions(+), 85 deletions(-) create mode 100755 test_regress/t/t_randomize_inline_var_ctl.pl create mode 100644 test_regress/t/t_randomize_inline_var_ctl.v create mode 100644 test_regress/t/t_randomize_inline_var_ctl_bad.out rename test_regress/t/{t_randomize_inline_var_ctl_unsup.pl => t_randomize_inline_var_ctl_bad.pl} (90%) create mode 100644 test_regress/t/t_randomize_inline_var_ctl_bad.v delete mode 100644 test_regress/t/t_randomize_inline_var_ctl_unsup.out create mode 100644 test_regress/t/t_randomize_inline_var_ctl_unsup_1.out create mode 100755 test_regress/t/t_randomize_inline_var_ctl_unsup_1.pl create mode 100644 test_regress/t/t_randomize_inline_var_ctl_unsup_1.v create mode 100644 test_regress/t/t_randomize_inline_var_ctl_unsup_2.out create mode 100755 test_regress/t/t_randomize_inline_var_ctl_unsup_2.pl rename test_regress/t/{t_randomize_inline_var_ctl_unsup.v => t_randomize_inline_var_ctl_unsup_2.v} (60%) diff --git a/src/V3Ast.h b/src/V3Ast.h index ed34ac0dd..6266debc8 100644 --- a/src/V3Ast.h +++ b/src/V3Ast.h @@ -1680,6 +1680,36 @@ public: bool operator==(const VSelfPointerText& other) const { return *m_strp == *other.m_strp; } }; +// ###################################################################### +// Defines what kind of randomization is done on a variable + +class VRandAttr final { +public: + enum en : uint8_t { + NONE, // Not randomizable + RAND, // Has a rand modifier + RAND_CYCLIC, // Has a randc modifier + RAND_INLINE // Not rand/randc, but used in inline random variable control + }; + enum en m_e; + const char* ascii() const { + static const char* const names[] = {"NONE", "RAND", "RANDC", "RAND-INLINE"}; + return names[m_e]; + } + VRandAttr() + : m_e{NONE} {} + // cppcheck-suppress noExplicitConstructor + constexpr VRandAttr(en _e) + : m_e{_e} {} + constexpr operator en() const { return m_e; } + bool isRandomizable() const { return m_e != NONE; } + bool isRand() const { return m_e == RAND || m_e == RAND_CYCLIC; } + bool isRandC() const { return m_e == RAND_CYCLIC; } +}; +inline std::ostream& operator<<(std::ostream& os, const VRandAttr& rhs) { + return os << rhs.ascii(); +} + //###################################################################### // AstNUser - Generic base class for AST User nodes. // - Also used to allow parameter passing up/down iterate calls diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index c4c9dfd94..ede72a0c3 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -1762,6 +1762,7 @@ class AstVar final : public AstNode { VDirection m_declDirection; // Declared direction input/output etc VLifetime m_lifetime; // Lifetime VVarAttrClocker m_attrClocker; + VRandAttr m_rand; // Randomizability of this variable (rand, randc, etc) int m_pinNum = 0; // For XML, if non-zero the connection pin number bool m_ansi : 1; // Params or pins declared in the module header, rather than the body bool m_declTyped : 1; // Declared as type (for dedup check) @@ -1785,8 +1786,6 @@ class AstVar final : public AstNode { bool m_attrSFormat : 1; // User sformat attribute bool m_attrSplitVar : 1; // declared with split_var metacomment bool m_fileDescr : 1; // File descriptor - bool m_isRand : 1; // Random variable - bool m_isRandC : 1; // Random cyclic variable (isRand also set) bool m_isConst : 1; // Table contains constant data bool m_isContinuously : 1; // Ever assigned continuously (for force/release) bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier @@ -1831,8 +1830,6 @@ class AstVar final : public AstNode { m_attrSFormat = false; m_attrSplitVar = false; m_fileDescr = false; - m_isRand = false; - m_isRandC = false; m_isConst = false; m_isContinuously = false; m_hasStrengthAssignment = false; @@ -1957,6 +1954,7 @@ public: void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSplitVar(bool flag) { m_attrSplitVar = flag; } + void rand(const VRandAttr flag) { m_rand = flag; } void usedClock(bool flag) { m_usedClock = flag; } void usedParam(bool flag) { m_usedParam = flag; } void usedLoopIdx(bool flag) { m_usedLoopIdx = flag; } @@ -1973,11 +1971,6 @@ public: void sc(bool flag) { m_sc = flag; } void scSensitive(bool flag) { m_scSensitive = flag; } void primaryIO(bool flag) { m_primaryIO = flag; } - void isRand(bool flag) { m_isRand = flag; } - void isRandC(bool flag) { - m_isRandC = flag; - if (flag) isRand(true); - } void isConst(bool flag) { m_isConst = flag; } void isContinuously(bool flag) { m_isContinuously = flag; } void isStatic(bool flag) { m_isStatic = flag; } @@ -2066,8 +2059,8 @@ public: bool isSigUserRdPublic() const { return m_sigUserRdPublic; } bool isSigUserRWPublic() const { return m_sigUserRWPublic; } bool isTrace() const { return m_trace; } - bool isRand() const { return m_isRand; } - bool isRandC() const { return m_isRandC; } + bool isRand() const { return m_rand.isRand(); } + bool isRandC() const { return m_rand.isRandC(); } bool isConst() const VL_MT_SAFE { return m_isConst; } bool isStatic() const VL_MT_SAFE { return m_isStatic; } bool isLatched() const { return m_isLatched; } @@ -2084,6 +2077,7 @@ public: bool attrIsolateAssign() const { return m_attrIsolateAssign; } AstIface* sensIfacep() const { return m_sensIfacep; } VVarAttrClocker attrClocker() const { return m_attrClocker; } + VRandAttr rand() const { return m_rand; } string verilogKwd() const override; void lifetime(const VLifetime& flag) { m_lifetime = flag; } VLifetime lifetime() const { return m_lifetime; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 7fe45a45f..6b09148bc 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2395,11 +2395,7 @@ void AstVar::dump(std::ostream& str) const { if (isInternal()) str << " [INTERNAL]"; if (isLatched()) str << " [LATCHED]"; if (isUsedLoopIdx()) str << " [LOOP]"; - if (isRandC()) { - str << " [RANDC]"; - } else if (isRand()) { - str << " [RAND]"; - } + if (rand().isRandomizable()) str << rand(); if (noReset()) str << " [!RST]"; if (attrIsolateAssign()) str << " [aISO]"; if (attrFileDescr()) str << " [aFD]"; diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index fd1f57d90..56a66203d 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -759,6 +759,7 @@ class LinkDotFindVisitor final : public VNVisitor { bool m_explicitNew = false; // Hit a "new" function int m_modBlockNum = 0; // Begin block number in module, 0=none seen int m_modWithNum = 0; // With block number, 0=none seen + int m_modArgNum = 0; // Arg block number for randomize(), 0=none seen // METHODS void makeImplicitNew(AstClass* nodep) { @@ -871,6 +872,7 @@ class LinkDotFindVisitor final : public VNVisitor { VL_RESTORER(m_paramNum); VL_RESTORER(m_modBlockNum); VL_RESTORER(m_modWithNum); + VL_RESTORER(m_modArgNum); if (doit && nodep->user2()) { nodep->v3warn(E_UNSUPPORTED, "Unsupported: Identically recursive module (module instantiates " @@ -897,6 +899,7 @@ class LinkDotFindVisitor final : public VNVisitor { m_paramNum = 0; m_modBlockNum = 0; m_modWithNum = 0; + m_modArgNum = 0; // m_modSymp/m_curSymp for non-packages set by AstCell above this module // Iterate nodep->user2(true); @@ -932,6 +935,7 @@ class LinkDotFindVisitor final : public VNVisitor { VL_RESTORER(m_paramNum); VL_RESTORER(m_modBlockNum); VL_RESTORER(m_modWithNum); + VL_RESTORER(m_modArgNum); { UINFO(4, " Link Class: " << nodep << endl); VSymEnt* const upperSymp = m_curSymp; @@ -945,6 +949,7 @@ class LinkDotFindVisitor final : public VNVisitor { m_paramNum = 0; m_modBlockNum = 0; m_modWithNum = 0; + m_modArgNum = 0; m_explicitNew = false; // m_modSymp/m_curSymp for non-packages set by AstCell above this module // Iterate @@ -2056,8 +2061,10 @@ class LinkDotResolveVisitor final : public VNVisitor { const AstCell* m_cellp = nullptr; // Current cell AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task + AstMethodCall* m_randMethodCallp = nullptr; // Current randomize() method call int m_modportNum = 0; // Uniqueify modport numbers bool m_inSens = false; // True if in senitem + bool m_inWith = false; // True if in with std::map m_ifClassImpNames; // Names imported from interface class std::set m_extendsParam; // Classes that have a parameterized super class // (except the default instances) @@ -2682,6 +2689,21 @@ class LinkDotResolveVisitor final : public VNVisitor { if (m_fromSymp) { foundp = m_fromSymp->findIdFlat(nodep->name()); if (foundp) { + if (!m_inWith) { + UASSERT_OBJ(m_randMethodCallp, nodep, "Expected to be under randomize()"); + // This will start failing once complex expressions are allowed on the LHS + // of randomize() with args + UASSERT_OBJ(VN_IS(m_randMethodCallp->fromp(), VarRef), m_randMethodCallp, + "Expected simple randomize target"); + // A ParseRef is used here so that the dot RHS gets resolved + nodep->replaceWith(new AstMemberSel{ + nodep->fileline(), + new AstParseRef{nodep->fileline(), VParseRefExp::PX_TEXT, + m_randMethodCallp->fromp()->name()}, + VFlagChildDType{}, nodep->name()}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } UINFO(9, " randomize-with fromSym " << foundp->nodep() << endl); if (m_ds.m_dotPos != DP_NONE) m_ds.m_dotPos = DP_MEMBER; AstLambdaArgRef* const lambdaRefp @@ -3152,9 +3174,11 @@ class LinkDotResolveVisitor final : public VNVisitor { // Created here so should already be resolved. VL_RESTORER(m_ds); VL_RESTORER(m_fromSymp); + VL_RESTORER(m_randMethodCallp); { m_ds.init(m_curSymp); if (nodep->name() == "randomize" && nodep->pinsp()) { + m_randMethodCallp = nodep; const AstNodeDType* fromDtp = nodep->fromp()->dtypep(); if (!fromDtp) { if (const AstNodeVarRef* const varRefp = VN_CAST(nodep->fromp(), NodeVarRef)) { @@ -3171,9 +3195,17 @@ class LinkDotResolveVisitor final : public VNVisitor { fromDtp = varRefp->varp()->dtypeSkipRefp()->subDTypep(); } } - if (!fromDtp) - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: 'randomize() with' on complex expressions"); + if (!fromDtp) { + if (VN_IS(nodep->pinsp(), With)) { + nodep->v3warn( + E_UNSUPPORTED, + "Unsupported: 'randomize() with' on complex expressions"); + } else { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Inline random variable control with " + "'randomize()' called on complex expressions"); + } + } } if (m_statep->forPrimary() && isParamedClassRefDType(fromDtp)) { m_ds.m_unresolvedClass = true; @@ -3186,14 +3218,6 @@ class LinkDotResolveVisitor final : public VNVisitor { else 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); } @@ -3323,7 +3347,7 @@ class LinkDotResolveVisitor final : public VNVisitor { } if (m_fromSymp) { VSymEnt* const foundp = m_fromSymp->findIdFlat(nodep->name()); - if (foundp) { + if (foundp && m_inWith) { UINFO(9, " randomize-with fromSym " << foundp->nodep() << endl); AstNodeExpr* argsp = nullptr; if (nodep->pinsp()) { @@ -3577,8 +3601,10 @@ class LinkDotResolveVisitor final : public VNVisitor { UINFO(5, " " << nodep << endl); checkNoDot(nodep); VL_RESTORER(m_curSymp); + VL_RESTORER(m_inWith); { m_ds.m_dotSymp = m_curSymp = m_statep->getNodeSym(nodep); + m_inWith = true; iterateChildren(nodep); } m_ds.m_dotSymp = VL_RESTORER_PREV(m_curSymp); diff --git a/src/V3ParseImp.h b/src/V3ParseImp.h index dd5562d4c..4514dbc31 100644 --- a/src/V3ParseImp.h +++ b/src/V3ParseImp.h @@ -82,8 +82,8 @@ struct VMemberQualifiers final { } void applyToNodes(AstVar* nodesp) const { for (AstVar* nodep = nodesp; nodep; nodep = VN_AS(nodep->nextp(), Var)) { - if (m_rand) nodep->isRand(true); - if (m_randc) nodep->isRandC(true); + if (m_rand) nodep->rand(VRandAttr::RAND); + if (m_randc) nodep->rand(VRandAttr::RAND_CYCLIC); if (m_local) nodep->isHideLocal(true); if (m_protected) nodep->isHideProtected(true); if (m_static) nodep->lifetime(VLifetime::STATIC); diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 319eddfd8..a1feddf34 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -48,6 +48,15 @@ VL_DEFINE_DEBUG_FUNCTIONS; +// ###################################################################### +// Determines if a class is used with randomization + +enum ClassRandom : uint8_t { + NONE, // randomize() is not called + IS_RANDOMIZED, // randomize() is called + IS_RANDOMIZED_INLINE, // randomize() with args is called +}; + // ###################################################################### // Establishes the target of a rand_mode() call @@ -130,18 +139,26 @@ class RandomizeMarkVisitor final : public VNVisitor { void markMembers(const AstClass* nodep) { for (const AstClass* classp = nodep; classp; classp = classp->extendsp() ? classp->extendsp()->classp() : nullptr) { - for (const AstNode* memberp = classp->stmtsp(); memberp; memberp = memberp->nextp()) { - // If member is rand and of class type, mark its class - if (VN_IS(memberp, Var) && VN_AS(memberp, Var)->isRand()) { + for (AstNode* memberp = classp->stmtsp(); memberp; memberp = memberp->nextp()) { + AstVar* const varp = VN_CAST(memberp, Var); + if (!varp) continue; + // If member is randomizable and of class type, mark its class + if (varp->rand().isRandomizable()) { if (const AstClassRefDType* const classRefp - = VN_CAST(memberp->dtypep()->skipRefp(), ClassRefDType)) { + = VN_CAST(varp->dtypep()->skipRefp(), ClassRefDType)) { AstClass* const rclassp = classRefp->classp(); if (!rclassp->user1()) { - rclassp->user1(true); + rclassp->user1(IS_RANDOMIZED); markMembers(rclassp); markDerived(rclassp); } } + // If the class is randomized inline, all members use rand mode + if (nodep->user1() == IS_RANDOMIZED_INLINE) { + VarRandMode randMode = {}; + randMode.usesRandMode = true; + varp->user1(randMode.asInt); + } } } } @@ -150,8 +167,8 @@ class RandomizeMarkVisitor final : public VNVisitor { const auto it = m_baseToDerivedMap.find(nodep); if (it != m_baseToDerivedMap.end()) { for (auto* classp : it->second) { - if (!classp->user1()) { - classp->user1(true); + if (classp->user1() < nodep->user1()) { + classp->user1(nodep->user1()); markMembers(classp); markDerived(classp); } @@ -277,9 +294,42 @@ class RandomizeMarkVisitor final : public VNVisitor { } } if (classp) { - classp->user1(true); + if (!classp->user1()) classp->user1(IS_RANDOMIZED); markMembers(classp); } + for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) { + AstArg* const argp = VN_CAST(pinp, Arg); + if (!argp) continue; + classp->user1(IS_RANDOMIZED_INLINE); + AstNodeExpr* exprp = argp->exprp(); + AstVar* fromVarp = nullptr; // If nodep is a method call, this is its receiver + if (AstMethodCall* methodCallp = VN_CAST(nodep, MethodCall)) { + if (AstMemberSel* const memberSelp = VN_CAST(methodCallp->fromp(), MemberSel)) { + fromVarp = memberSelp->varp(); + } else { + AstVarRef* const varrefp = VN_AS(methodCallp->fromp(), VarRef); + fromVarp = varrefp->varp(); + } + } + while (exprp) { + AstVar* randVarp = nullptr; + if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { + randVarp = memberSelp->varp(); + exprp = memberSelp->fromp(); + } else { + AstVarRef* const varrefp = VN_AS(exprp, VarRef); + randVarp = varrefp->varp(); + exprp = nullptr; + } + if (randVarp == fromVarp) break; + AstNode* backp = randVarp; + while (backp && !VN_IS(backp, Class)) backp = backp->backp(); + VarRandMode randMode = {}; + randMode.usesRandMode = true; + randVarp->user1(randMode.asInt); + VN_AS(backp, Class)->user1(IS_RANDOMIZED_INLINE); + } + } } void visit(AstConstraintExpr* nodep) override { VL_RESTORER(m_constraintExprp); @@ -300,7 +350,7 @@ class RandomizeMarkVisitor final : public VNVisitor { if (nodep->varp()->lifetime().isStatic()) m_staticRefs.emplace(nodep); - if (!nodep->varp()->isRand()) return; + if (!nodep->varp()->rand().isRandomizable()) return; for (AstNode* backp = nodep; backp != m_constraintExprp && !backp->user1(); backp = backp->backp()) backp->user1(true); @@ -308,7 +358,7 @@ class RandomizeMarkVisitor final : public VNVisitor { void visit(AstMemberSel* nodep) override { if (!m_constraintExprp) return; if (VN_IS(nodep->fromp(), LambdaArgRef)) { - if (!nodep->varp()->isRand()) return; + if (!nodep->varp()->rand().isRandomizable()) return; for (AstNode* backp = nodep; backp != m_constraintExprp && !backp->user1(); backp = backp->backp()) backp->user1(true); @@ -974,6 +1024,8 @@ class RandomizeVisitor final : public VNVisitor { // STATE V3UniqueNames m_inlineUniqueNames; // For generating unique function names + V3UniqueNames m_randModeUniqueNames{"__Vrandmode"}; // For generating unique rand mode state + // var names VMemberMap m_memberMap; // Member names cached for fast lookup AstNodeModule* m_modp = nullptr; // Current module const AstNodeFTask* m_ftaskp = nullptr; // Current function/task @@ -1079,8 +1131,8 @@ class RandomizeVisitor final : public VNVisitor { new AstVarRef{fl, randModeModp, randModeVarp, VAccess::READ}); dynarrayNewp->dtypeSetVoid(); AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(classp, "new"), NodeFTask); - fl = classp->fileline(); UASSERT_OBJ(newp, classp, "No new() in class"); + fl = classp->fileline(); newp->addStmtsp(dynarrayNewp->makeStmt()); newp->addStmtsp(makeRandModeInitLoop( fl, new AstVarRef{fl, randModeModp, randModeVarp, VAccess::WRITE}, @@ -1180,8 +1232,7 @@ class RandomizeVisitor final : public VNVisitor { if (basicp->width() > 32) { varp->v3error("Maximum implemented width for randc is 32 bits, " << varp->prettyNameQ() << " is " << basicp->width() << " bits"); - varp->isRandC(false); - varp->isRand(true); + varp->rand(VRandAttr::RAND); return nullptr; } items = 1ULL << basicp->width(); @@ -1286,16 +1337,119 @@ class RandomizeVisitor final : public VNVisitor { clearp->dtypeSetVoid(); return clearp->makeStmt(); } + AstVar* getVarFromRef(AstNodeExpr* const exprp) { + if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { + return memberSelp->varp(); + } else if (AstVarRef* const varrefp = VN_CAST(exprp, VarRef)) { + return varrefp->varp(); + } + exprp->v3fatalSrc("Not a MemberSel nor VarRef"); + return nullptr; // LCOV_EXCL_LINE + } + AstNodeExpr* makeSiblingRefp(AstNodeExpr* const exprp, AstVar* const varp, + const VAccess access) { + if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { + return new AstMemberSel{exprp->fileline(), memberSelp->fromp()->cloneTree(false), + varp}; + } + UASSERT_OBJ(VN_IS(exprp, VarRef), exprp, "Should be a VarRef"); + return new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), Class), varp, access}; + } + AstNodeExpr* getFromp(AstNodeExpr* const exprp) { + if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { + return memberSelp->fromp(); + } else if (AstMethodCall* const methodCallp = VN_CAST(exprp, MethodCall)) { + return methodCallp->fromp(); + } + return nullptr; + } + AstVar* makeTmpRandModeVar(AstNodeExpr* siblingExprp, AstVar* randModeVarp, + AstNode*& storeStmtspr, AstNodeStmt*& restoreStmtspr) { + FileLine* const fl = randModeVarp->fileline(); + AstVar* const randModeTmpVarp + = new AstVar{fl, VVarType::BLOCKTEMP, m_randModeUniqueNames.get(randModeVarp), + randModeVarp->dtypep()}; + randModeTmpVarp->funcLocal(m_ftaskp); + randModeTmpVarp->lifetime(VLifetime::AUTOMATIC); + storeStmtspr = AstNode::addNext( + storeStmtspr, + new AstAssign{fl, new AstVarRef{fl, randModeTmpVarp, VAccess::WRITE}, + makeSiblingRefp(siblingExprp, randModeVarp, VAccess::READ)}); + storeStmtspr = AstNode::addNext( + storeStmtspr, + makeRandModeInitLoop(fl, makeSiblingRefp(siblingExprp, randModeVarp, VAccess::WRITE), + new AstConst{fl, 0}, m_ftaskp)); + restoreStmtspr = AstNode::addNext( + restoreStmtspr, + new AstAssign{fl, makeSiblingRefp(siblingExprp, randModeVarp, VAccess::WRITE), + new AstVarRef{fl, randModeTmpVarp, VAccess::READ}}); + return randModeTmpVarp; + } + // Returns the common prefix of two hierarchical accesses, or nullptr if there is none + // e.g. a.b.c and a.b.d -> a.b + AstNodeExpr* sliceToCommonPrefix(AstNodeExpr* thisp, AstNodeExpr* otherp) { + static std::vector thisHier, otherHier; // Keep around + // to avoid reallocations + thisHier.clear(); + otherHier.clear(); + while (thisp) { + thisHier.push_back(thisp); + thisp = getFromp(thisp); + } + while (otherp) { + otherHier.push_back(otherp); + otherp = getFromp(otherp); + } + AstNodeExpr* commonp = nullptr; + for (auto thisIt = thisHier.rbegin(), otherIt = otherHier.rbegin(); + thisIt != thisHier.rend() && otherIt != otherHier.rend(); ++thisIt, ++otherIt) { + if ((*thisIt)->type() != (*otherIt)->type()) break; + if (AstMemberSel* memberSelp = VN_CAST(*thisIt, MemberSel)) { + AstMemberSel* otherMemberSelp = VN_AS(*otherIt, MemberSel); + if (memberSelp->varp() == otherMemberSelp->varp()) { + commonp = memberSelp; + continue; + } + } else if (AstMethodCall* thisMethodCallp = VN_CAST(*thisIt, MethodCall)) { + AstMethodCall* otherMethodCallp = VN_AS(*otherIt, MethodCall); + if (thisMethodCallp->taskp() == otherMethodCallp->taskp()) { + commonp = thisMethodCallp; + continue; + } + } else if (AstVarRef* firstVarRefp = VN_CAST(*thisIt, VarRef)) { + AstVarRef* secondVarRefp = VN_AS(*otherIt, VarRef); + if (firstVarRefp->varp() == secondVarRefp->varp()) { + commonp = firstVarRefp; + continue; + } + } + break; + } + return commonp; + } - void addBasicRandomizeBody(AstFunc* const basicRandomizep, AstClass* const nodep) { + void addBasicRandomizeBody(AstFunc* const basicRandomizep, AstClass* const nodep, + AstVar* randModeVarp) { FileLine* const fl = nodep->fileline(); AstVar* const basicFvarp = VN_AS(basicRandomizep->fvarp(), Var); AstVarRef* const basicFvarRefp = new AstVarRef{fl, basicFvarp, VAccess::WRITE}; AstConst* const beginBasicValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1}; basicRandomizep->addStmtsp(new AstAssign{fl, basicFvarRefp, beginBasicValp}); - + AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(nodep, "new"), NodeFTask); + UASSERT_OBJ(newp, nodep, "No new() in class"); nodep->foreachMember([&](AstClass* classp, AstVar* memberVarp) { - if (!memberVarp->isRand() || memberVarp->user3()) return; + if (!memberVarp->rand().isRandomizable()) return; + const VarRandMode randMode = {.asInt = memberVarp->user1()}; + if (randMode.usesRandMode && !memberVarp->isRand()) { // Not randomizable by default + AstCMethodHard* atp = new AstCMethodHard{ + nodep->fileline(), + new AstVarRef{fl, VN_AS(randModeVarp->user2p(), NodeModule), randModeVarp, + VAccess::WRITE}, + "at", new AstConst{nodep->fileline(), randMode.index}}; + atp->dtypeSetUInt32(); + newp->addStmtsp(new AstAssign{fl, atp, new AstConst{fl, 0}}); + } + if (memberVarp->user3()) return; // Handled in constraints const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp(); if (VN_IS(dtypep, BasicDType) || VN_IS(dtypep, StructDType)) { AstVar* const randcVarp = newRandcVarsp(memberVarp); @@ -1332,6 +1486,90 @@ class RandomizeVisitor final : public VNVisitor { }); } + // Handle inline random variable control. After this, the randomize() call has no args + void handleRandomizeArgs(AstNodeFTaskRef* const nodep) { + if (!nodep->pinsp()) return; + // This assumes arguments to always be a member sel from nodep->fromp(), if applicable + // e.g. LinkDot transformed a.randomize(b, a.c) -> a.randomize(a.b, a.c) + // Merge pins with common prefixes so that setting their rand mode doesn't interfere + // with each other. + // e.g. a.randomize(a.b, a.c, a.b.d) -> a.randomize(a.b, a.c) + for (AstNode *pinp = nodep->pinsp(), *nextp = nullptr; pinp; pinp = nextp) { + nextp = pinp->nextp(); + AstArg* const argp = VN_CAST(pinp, Arg); + if (!argp) continue; + AstNode* otherNextp = nullptr; + for (AstNode* otherPinp = nextp; otherPinp; otherPinp = otherNextp) { + otherNextp = otherPinp->nextp(); + AstArg* const otherArgp = VN_CAST(otherPinp, Arg); + if (!otherArgp) continue; + if (AstNodeExpr* const prefixp + = sliceToCommonPrefix(argp->exprp(), otherArgp->exprp())) { + if (prefixp == argp->exprp()) { + if (nextp == otherPinp) nextp = nextp->nextp(); + VL_DO_DANGLING(otherPinp->unlinkFrBack()->deleteTree(), otherPinp); + continue; + } + } + if (AstNodeExpr* const prefixp + = sliceToCommonPrefix(otherArgp->exprp(), argp->exprp())) { + if (prefixp == otherArgp->exprp()) { + VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp); + break; + } + } + } + } + // Construct temp vars, and store and restore statements + std::set savedRandModeVarps; + AstVar* tmpVarps = nullptr; + AstNode* storeStmtsp = nullptr; + AstNode* setStmtsp = nullptr; + AstNodeStmt* restoreStmtsp = nullptr; + for (AstNode *pinp = nodep->pinsp(), *nextp = nullptr; pinp; pinp = nextp) { + nextp = pinp->nextp(); + AstArg* const argp = VN_CAST(pinp, Arg); + if (!argp) continue; + AstNodeExpr* exprp = VN_AS(pinp, Arg)->exprp(); + AstNodeExpr* const commonPrefixp = sliceToCommonPrefix(exprp, nodep); + UASSERT_OBJ(commonPrefixp != exprp, nodep, + "Common prefix should be different than pin"); + FileLine* const fl = argp->fileline(); + while (exprp) { + if (commonPrefixp == exprp) break; + AstVar* const randVarp = getVarFromRef(exprp); + AstClass* const classp = VN_AS(randVarp->user2p(), Class); + AstVar* const randModeVarp = getRandModeVar(classp); + if (savedRandModeVarps.find(randModeVarp) == savedRandModeVarps.end()) { + AstVar* const randModeTmpVarp + = makeTmpRandModeVar(exprp, randModeVarp, storeStmtsp, restoreStmtsp); + savedRandModeVarps.insert(randModeVarp); + tmpVarps = AstNode::addNext(tmpVarps, randModeTmpVarp); + } + const VarRandMode randMode = {.asInt = randVarp->user1()}; + AstCMethodHard* atp + = new AstCMethodHard{fl, makeSiblingRefp(exprp, randModeVarp, VAccess::WRITE), + "at", new AstConst{fl, randMode.index}}; + atp->dtypeSetUInt32(); + setStmtsp + = AstNode::addNext(setStmtsp, new AstAssign{fl, atp, new AstConst{fl, 1}}); + exprp = getFromp(exprp); + } + pinp->unlinkFrBack()->deleteTree(); + } + if (tmpVarps) { + UASSERT_OBJ(storeStmtsp && setStmtsp && restoreStmtsp, nodep, "Should have stmts"); + VNRelinker relinker; + m_stmtp->unlinkFrBack(&relinker); + AstNode* const stmtsp = tmpVarps; + stmtsp->addNext(storeStmtsp); + stmtsp->addNext(setStmtsp); + stmtsp->addNext(m_stmtp); + stmtsp->addNext(restoreStmtsp); + relinker.relink(new AstBegin{nodep->fileline(), "", stmtsp, false, true}); + } + } + // VISITORS void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -1411,12 +1649,9 @@ class RandomizeVisitor final : public VNVisitor { AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp}); - AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(nodep, "new"), NodeFTask); - UASSERT_OBJ(newp, nodep, "No new() in class"); - AstFunc* const basicRandomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep, "__Vbasic_randomize"); - addBasicRandomizeBody(basicRandomizep, nodep); + addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp); AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, "__Vbasic_randomize", nullptr}; basicRandomizeCallp->taskp(basicRandomizep); basicRandomizeCallp->dtypep(basicRandomizep->dtypep()); @@ -1539,9 +1774,13 @@ class RandomizeVisitor final : public VNVisitor { return; } + if (nodep->name() != "randomize") return; + + handleRandomizeArgs(nodep); + AstWith* const withp = VN_CAST(nodep->pinsp(), With); - if (!(nodep->name() == "randomize") || !withp) { + if (!withp) { iterateChildren(nodep); return; } diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 31d286d80..ae8a6373b 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -1664,7 +1664,9 @@ V3TaskConnects V3Task::taskConnects(AstNodeFTaskRef* nodep, AstNode* taskStmtsp, reorganize = true; } } else { // By pin number - if (ppinnum >= tpinnum) { + if (nodep->taskp()->prettyName() == "randomize") { + // Arguments to randomize() are special, will be handled in V3Randomize + } else if (ppinnum >= tpinnum) { if (sformatp) { tconnects.emplace_back(sformatp, static_cast(nullptr)); tconnects[ppinnum].second = argp; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 308675510..48513bb32 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3134,8 +3134,10 @@ class WidthVisitor final : public VNVisitor { // Should check types the method requires, but at present we don't do much userIterate(nodep->fromp(), WidthVP{SELF, BOTH}.p()); // Any AstWith is checked later when know types, in methodWithArgument - for (AstArg* argp = VN_CAST(nodep->pinsp(), Arg); argp; argp = VN_AS(argp->nextp(), Arg)) { - if (argp->exprp()) userIterate(argp->exprp(), WidthVP{SELF, BOTH}.p()); + for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) { + if (AstArg* const argp = VN_CAST(pinp, Arg)) { + if (argp->exprp()) userIterate(argp->exprp(), WidthVP{SELF, BOTH}.p()); + } } // Find the fromp dtype - should be a class UASSERT_OBJ(nodep->fromp() && nodep->fromp()->dtypep(), nodep, "Unsized expression"); @@ -3178,13 +3180,16 @@ class WidthVisitor final : public VNVisitor { AstNodeDType* returnDtp, AstNodeDType* indexDtp, AstNodeDType* valueDtp) { UASSERT_OBJ(arbReturn || returnDtp, nodep, "Null return type"); - if (AstWith* const withp = VN_CAST(nodep->pinsp(), With)) { - withp->indexArgRefp()->dtypep(indexDtp); - withp->valueArgRefp()->dtypep(valueDtp); - userIterate(withp, WidthVP{returnDtp, BOTH}.p()); - withp->unlinkFrBack(); - return withp; - } else if (required) { + for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) { + if (AstWith* const withp = VN_CAST(pinp, With)) { + withp->indexArgRefp()->dtypep(indexDtp); + withp->valueArgRefp()->dtypep(valueDtp); + userIterate(withp, WidthVP{returnDtp, BOTH}.p()); + withp->unlinkFrBack(); + return withp; + } + } + if (required) { nodep->v3error("'with' statement is required for ." << nodep->prettyName() << " method"); } @@ -3792,6 +3797,80 @@ class WidthVisitor final : public VNVisitor { nodep->v3error("Member reference from interface to " << nodep->prettyNameQ() << " is not referencing a valid task or function "); } + void handleRandomizeArgs(AstNodeFTaskRef* const nodep, AstClass* const classp) { + bool hasNonNullArgs = false; + AstConst* nullp = nullptr; + for (AstNode *pinp = nodep->pinsp(), *nextp = nullptr; pinp; pinp = nextp) { + nextp = pinp->nextp(); + AstArg* const argp = VN_CAST(pinp, Arg); + if (!argp) continue; + AstVar* randVarp = nullptr; + AstNodeExpr* exprp = argp->exprp(); + if (AstConst* const constp = VN_CAST(exprp, Const)) { + if (constp->num().isNull()) { + nullp = constp; + continue; + } + } + hasNonNullArgs = true; + AstVar* fromVarp = nullptr; // If it's a method call, the leftmost element + // of the dot hierarchy + if (AstMethodCall* methodCallp = VN_CAST(nodep, MethodCall)) { + AstNodeExpr* fromp = methodCallp->fromp(); + while (AstMemberSel* const memberSelp = VN_CAST(fromp, MemberSel)) { + fromp = memberSelp->fromp(); + } + AstVarRef* const varrefp = VN_AS(fromp, VarRef); + fromVarp = varrefp->varp(); + } + if (!VN_IS(exprp, VarRef) && !VN_IS(exprp, MemberSel)) { + argp->v3error("'randomize()' argument must be a variable contained in " + << (fromVarp ? fromVarp->prettyNameQ() : classp->prettyNameQ())); + VL_DO_DANGLING(argp->unlinkFrBack()->deleteTree(), argp); + continue; + } + while (exprp) { + if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { + randVarp = memberSelp->varp(); + exprp = memberSelp->fromp(); + } else { + if (AstVarRef* const varrefp = VN_CAST(exprp, VarRef)) { + randVarp = varrefp->varp(); + } else { + argp->v3warn( + E_UNSUPPORTED, + "Unsupported: Non-variable expression as 'randomize()' argument"); + VL_DO_DANGLING(argp->unlinkFrBack()->deleteTree(), argp); + } + exprp = nullptr; + } + // All variables in the dot hierarchy must be randomizable + if (randVarp && !randVarp->isRand()) randVarp->rand(VRandAttr::RAND_INLINE); + } + if (!argp) continue; // Errored out, bail + // randVarp is now the leftmost element from the dot hierarchy in argp->exprp() + if (randVarp == fromVarp) { + // The passed in variable is MemberSel'ected from the MethodCall target + } else if (classp->existsMember([&](const AstClass*, const AstVar* memberVarp) { + return memberVarp == randVarp; + })) { + // The passed in variable is contained in the method call target + } else { + // Passed in a constant or complex expression, or the above conditions are not + // met + argp->v3error("'randomize()' argument must be a variable contained in " + << (fromVarp ? fromVarp->prettyNameQ() : classp->prettyNameQ())); + VL_DO_DANGLING(argp->unlinkFrBack()->deleteTree(), argp); + } + } + if (nullp) { + if (hasNonNullArgs) { + nullp->v3error("Cannot pass more arguments to 'randomize(null)'"); + } else { + nullp->v3warn(E_UNSUPPORTED, "Unsupported: 'randomize(null)'"); + } + } + } void methodCallClass(AstMethodCall* nodep, AstClassRefDType* adtypep) { // No need to width-resolve the class, as it was done when we did the child AstClass* const first_classp = adtypep->classp(); @@ -3801,9 +3880,9 @@ class WidthVisitor final : public VNVisitor { m_randomizeFromp = nodep->fromp(); withp = methodWithArgument(nodep, false, false, adtypep->findVoidDType(), adtypep->findBitDType(), adtypep); - methodOkArguments(nodep, 0, 0); methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::WRITE); V3Randomize::newRandomizeFunc(m_memberMap, first_classp); + handleRandomizeArgs(nodep, first_classp); } else if (nodep->name() == "srandom") { methodOkArguments(nodep, 1, 1); methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::WRITE); @@ -5942,11 +6021,7 @@ class WidthVisitor final : public VNVisitor { v3Global.rootp()->typeTablep()->addTypesp(adtypep); withp = methodWithArgument(nodep, false, false, adtypep->findVoidDType(), adtypep->findBitDType(), adtypep); - if (nodep->pinsp()) { - nodep->pinsp()->v3warn(CONSTRAINTIGN, - "Inline random variable control (unsupported)"); - nodep->pinsp()->unlinkFrBackWithNext()->deleteTree(); - } + handleRandomizeArgs(nodep, classp); } else if (nodep->name() == "srandom") { nodep->taskp(V3Randomize::newSRandomFunc(m_memberMap, classp)); m_memberMap.clear(); diff --git a/test_regress/t/t_randomize_inline_var_ctl.pl b/test_regress/t/t_randomize_inline_var_ctl.pl new file mode 100755 index 000000000..7b520d284 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl.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 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); + +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_inline_var_ctl.v b/test_regress/t/t_randomize_inline_var_ctl.v new file mode 100644 index 000000000..9bcc55e9f --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl.v @@ -0,0 +1,156 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + rand int zero; + int two; +endclass + +class Bar extends Foo; + rand int one; + static int three; + + function void test; + logic[1:0] ok = '0; + zero = 100; + one = 200; + two = 300; + three = 400; + for (int i = 0; i < 20; i++) begin + void'(randomize(one)); + if (zero != 100) $stop; + if (one != 200) ok[0] = 1; + if (two != 300) $stop; + if (three != 400) $stop; + end + if (!ok[0]) $stop; + ok = '0; + + if (zero.rand_mode() != 1) $stop; + if (one.rand_mode() != 1) $stop; + zero = 500; + one = 600; + two = 700; + three = 800; + one.rand_mode(0); + for (int i = 0; i < 20; i++) begin + void'(randomize(one, two)); + if (zero != 500) $stop; + if (one != 600) ok[0] = 1; + if (two != 700) ok[1] = 1; + if (three != 800) $stop; + end + if (one.rand_mode() != 0) $stop; + one.rand_mode(1); + if (ok != 'b11) $stop; + endfunction +endclass + +class Baz; + int four; + Bar bar; + + function new; + bar = new; + endfunction +endclass + +class Qux; + Baz baz; + + function new; + baz = new; + endfunction +endclass + +class Boo extends Bar; + rand int five; +endclass + +module t; + initial begin + Boo boo = new; + Bar bar = boo; + Qux qux = new; + logic[2:0] ok = '0; + + bar.test; + + bar.zero = 1000; + bar.one = 2000; + bar.two = 3000; + bar.three = 4000; + boo.five = 999999; + for (int i = 0; i < 20; i++) begin + int res = bar.randomize(two); + if (boo.five != 999999) $stop; + end + + bar.zero = 1000; + bar.one = 2000; + bar.two = 3000; + bar.three = 4000; + boo.five = 999999; + for (int i = 0; i < 20; i++) begin + int res = bar.randomize(two) with { two > 3000 && two < 4000; }; + if (bar.zero != 1000) $stop; + if (bar.one != 2000) $stop; + if (!(bar.two > 3000 && bar.two < 4000)) $stop; + if (bar.three != 4000) $stop; + if (boo.five != 999999) $stop; + end + + qux.baz.bar.zero = 5000; + qux.baz.bar.one = 6000; + qux.baz.bar.two = 7000; + qux.baz.bar.three = 8000; + qux.baz.four = 9000; + for (int i = 0; i < 20; i++) begin + void'(qux.randomize(baz)); + if (qux.baz.bar.zero != 5000) $stop; + if (qux.baz.bar.one != 6000) $stop; + if (qux.baz.bar.two != 7000) $stop; + if (qux.baz.bar.three != 8000) $stop; + if (qux.baz.four != 9000) $stop; + end + for (int i = 0; i < 20; i++) begin + void'(qux.randomize(baz.bar)); + if (qux.baz.bar.zero != 5000) ok[0] = 1; + if (qux.baz.bar.one != 6000) ok[1] = 1; + if (qux.baz.bar.two != 7000) $stop; + if (qux.baz.bar.three != 8000) $stop; + if (qux.baz.four != 9000) $stop; + end + if (!ok[0]) $stop; + if (!ok[1]) $stop; + ok = '0; + qux.baz.bar.zero = 10000; + qux.baz.bar.one = 20000; + for (int i = 0; i < 20; i++) begin + void'(qux.randomize(baz.four)); + if (qux.baz.bar.zero != 10000) $stop; + if (qux.baz.bar.one != 20000) $stop; + if (qux.baz.bar.two != 7000) $stop; + if (qux.baz.bar.three != 8000) $stop; + if (qux.baz.four != 9000) ok[0] = 1; + end + if (!ok[0]) $stop; + ok = '0; + qux.baz.four = 30000; + for (int i = 0; i < 20; i++) begin + void'(qux.randomize(baz.bar, qux.baz.bar.one, baz.four)); + if (qux.baz.bar.zero != 10000) ok[0] = 1; + if (qux.baz.bar.one != 20000) ok[1] = 1; + if (qux.baz.bar.two != 7000) $stop; + if (qux.baz.bar.three != 8000) $stop; + if (qux.baz.four != 30000) ok[2] = 1; + end + if (ok != 'b111) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_randomize_inline_var_ctl_bad.out b/test_regress/t/t_randomize_inline_var_ctl_bad.out new file mode 100644 index 000000000..8f68f9667 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_bad.out @@ -0,0 +1,25 @@ +%Error: t/t_randomize_inline_var_ctl_bad.v:12:23: 'randomize()' argument must be a variable contained in 'Foo' + : ... note: In instance 't' + 12 | void'(randomize(y)); + | ^ +%Error: t/t_randomize_inline_var_ctl_bad.v:26:46: 'randomize()' argument must be a variable contained in 'foo' + : ... note: In instance 't' + 26 | void'(foo.randomize(x, foo.x, null, qux.x, bar.y, 0 + 1, x ** 2)); + | ^ +%Error: t/t_randomize_inline_var_ctl_bad.v:26:53: 'randomize()' argument must be a variable contained in 'foo' + : ... note: In instance 't' + 26 | void'(foo.randomize(x, foo.x, null, qux.x, bar.y, 0 + 1, x ** 2)); + | ^ +%Error: t/t_randomize_inline_var_ctl_bad.v:26:59: 'randomize()' argument must be a variable contained in 'foo' + : ... note: In instance 't' + 26 | void'(foo.randomize(x, foo.x, null, qux.x, bar.y, 0 + 1, x ** 2)); + | ^ +%Error: t/t_randomize_inline_var_ctl_bad.v:26:66: 'randomize()' argument must be a variable contained in 'foo' + : ... note: In instance 't' + 26 | void'(foo.randomize(x, foo.x, null, qux.x, bar.y, 0 + 1, x ** 2)); + | ^~ +%Error: t/t_randomize_inline_var_ctl_bad.v:26:37: Cannot pass more arguments to 'randomize(null)' + : ... note: In instance 't' + 26 | void'(foo.randomize(x, foo.x, null, qux.x, bar.y, 0 + 1, x ** 2)); + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup.pl b/test_regress/t/t_randomize_inline_var_ctl_bad.pl similarity index 90% rename from test_regress/t/t_randomize_inline_var_ctl_unsup.pl rename to test_regress/t/t_randomize_inline_var_ctl_bad.pl index 58d9a7818..14a6e3229 100755 --- a/test_regress/t/t_randomize_inline_var_ctl_unsup.pl +++ b/test_regress/t/t_randomize_inline_var_ctl_bad.pl @@ -8,11 +8,11 @@ if (!$::Driver) { use FindBin; exec("$FindBin::Bin/bootstrap.pl", @ARGV, $0); di # Version 2.0. # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -scenarios(linter => 1); +scenarios(simulator => 1); lint( + fails => $Self->{vlt_all}, expect_filename => $Self->{golden_filename}, - fails => 1, ); ok(1); diff --git a/test_regress/t/t_randomize_inline_var_ctl_bad.v b/test_regress/t/t_randomize_inline_var_ctl_bad.v new file mode 100644 index 000000000..5d5f2120f --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_bad.v @@ -0,0 +1,28 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + int x; + + function void test; + int y; + void'(randomize(y)); + endfunction +endclass + +class Bar; + int y; +endclass + +module t; + initial begin + Foo foo = new; + Foo qux = new; + Bar bar = new; + int x; + void'(foo.randomize(x, foo.x, null, qux.x, bar.y, 0 + 1, x ** 2)); + end +endmodule diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup.out b/test_regress/t/t_randomize_inline_var_ctl_unsup.out deleted file mode 100644 index c6f073ca9..000000000 --- a/test_regress/t/t_randomize_inline_var_ctl_unsup.out +++ /dev/null @@ -1,9 +0,0 @@ -%Warning-CONSTRAINTIGN: t/t_randomize_inline_var_ctl_unsup.v:13:23: Inline random variable control (unsupported) - 13 | void'(randomize(one)); - | ^~~ - ... 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_inline_var_ctl_unsup.v:14:23: Inline random variable control (unsupported) - 14 | void'(randomize(two, three)); - | ^~~ -%Error: Exiting due to diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out new file mode 100644 index 000000000..55527bd65 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.out @@ -0,0 +1,14 @@ +%Error-UNSUPPORTED: t/t_randomize_inline_var_ctl_unsup_1.v:20:37: Unsupported: Non-variable expression as 'randomize()' argument + : ... note: In instance 't' + 20 | void'(foo.randomize(Foo::get().x)); + | ^ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_randomize_inline_var_ctl_unsup_1.v:21:34: Unsupported: Non-variable expression as 'randomize()' argument + : ... note: In instance 't' + 21 | void'(foo.randomize(foos[0].x)); + | ^ +%Error-UNSUPPORTED: t/t_randomize_inline_var_ctl_unsup_1.v:22:27: Unsupported: 'randomize(null)' + : ... note: In instance 't' + 22 | void'(foo.randomize(null)); + | ^~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup_1.pl b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.pl new file mode 100755 index 000000000..14a6e3229 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.pl @@ -0,0 +1,19 @@ +#!/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); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup_1.v b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.v new file mode 100644 index 000000000..4f01c058c --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_1.v @@ -0,0 +1,24 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2024 by Antmicro. +// SPDX-License-Identifier: CC0-1.0 + +class Foo; + int x; + + static function Foo get; + Foo foo = new; + return foo; + endfunction +endclass + +module t; + initial begin + Foo foo = Foo::get(); + Foo foos[] = new[1]; + void'(foo.randomize(Foo::get().x)); + void'(foo.randomize(foos[0].x)); + void'(foo.randomize(null)); + end +endmodule diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup_2.out b/test_regress/t/t_randomize_inline_var_ctl_unsup_2.out new file mode 100644 index 000000000..a1be7c312 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_2.out @@ -0,0 +1,8 @@ +%Error-UNSUPPORTED: t/t_randomize_inline_var_ctl_unsup_2.v:17:29: Unsupported: Inline random variable control with 'randomize()' called on complex expressions + 17 | initial void'(Foo::get().randomize(x)); + | ^~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: t/t_randomize_inline_var_ctl_unsup_2.v:17:39: Can't find definition of variable: 'x' + 17 | initial void'(Foo::get().randomize(x)); + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup_2.pl b/test_regress/t/t_randomize_inline_var_ctl_unsup_2.pl new file mode 100755 index 000000000..14a6e3229 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_2.pl @@ -0,0 +1,19 @@ +#!/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); + +lint( + fails => $Self->{vlt_all}, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup.v b/test_regress/t/t_randomize_inline_var_ctl_unsup_2.v similarity index 60% rename from test_regress/t/t_randomize_inline_var_ctl_unsup.v rename to test_regress/t/t_randomize_inline_var_ctl_unsup_2.v index 1f04c76fe..ed5fbe5a5 100644 --- a/test_regress/t/t_randomize_inline_var_ctl_unsup.v +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup_2.v @@ -4,13 +4,15 @@ // any use, without warranty, 2024 by Antmicro. // SPDX-License-Identifier: CC0-1.0 -class Packet; - rand int one; - int two; - static int three; +class Foo; + int x; - function void test; - void'(randomize(one)); - void'(randomize(two, three)); + static function Foo get; + Foo foo = new; + return foo; endfunction endclass + +module t; + initial void'(Foo::get().randomize(x)); +endmodule