From 2f5c58b345eca4bf81cace78e9a1d867fa579eef Mon Sep 17 00:00:00 2001 From: Krzysztof Bieganski Date: Wed, 31 Jul 2024 23:30:48 +0200 Subject: [PATCH] Support `rand_mode` (#5273) Signed-off-by: Krzysztof Bieganski --- include/verilated_random.cpp | 6 +- include/verilated_random.h | 19 +- src/V3AstNodes.cpp | 1 + src/V3LinkDot.cpp | 4 +- src/V3Randomize.cpp | 371 +++++++++++++++++- src/V3Width.cpp | 32 +- src/V3WidthCommit.cpp | 5 + .../t/t_randomize_inline_var_ctl_unsup.out | 9 + .../t/t_randomize_inline_var_ctl_unsup.pl | 19 + .../t/t_randomize_inline_var_ctl_unsup.v | 16 + test_regress/t/t_randomize_rand_mode.pl | 1 - test_regress/t/t_randomize_rand_mode.v | 81 ++-- test_regress/t/t_randomize_rand_mode_bad.out | 27 ++ ...rn_bad.pl => t_randomize_rand_mode_bad.pl} | 2 - test_regress/t/t_randomize_rand_mode_bad.v | 28 ++ .../t/t_randomize_rand_mode_constr.pl | 25 ++ test_regress/t/t_randomize_rand_mode_constr.v | 143 +++++++ .../t/t_randomize_rand_mode_unsup.out | 10 + test_regress/t/t_randomize_rand_mode_unsup.pl | 19 + test_regress/t/t_randomize_rand_mode_unsup.v | 18 + .../t/t_randomize_rand_mode_warn_bad.out | 115 ------ 21 files changed, 762 insertions(+), 189 deletions(-) create mode 100644 test_regress/t/t_randomize_inline_var_ctl_unsup.out create mode 100755 test_regress/t/t_randomize_inline_var_ctl_unsup.pl create mode 100644 test_regress/t/t_randomize_inline_var_ctl_unsup.v create mode 100644 test_regress/t/t_randomize_rand_mode_bad.out rename test_regress/t/{t_randomize_rand_mode_warn_bad.pl => t_randomize_rand_mode_bad.pl} (92%) create mode 100644 test_regress/t/t_randomize_rand_mode_bad.v create mode 100755 test_regress/t/t_randomize_rand_mode_constr.pl create mode 100644 test_regress/t/t_randomize_rand_mode_constr.v create mode 100644 test_regress/t/t_randomize_rand_mode_unsup.out create mode 100755 test_regress/t/t_randomize_rand_mode_unsup.pl create mode 100644 test_regress/t/t_randomize_rand_mode_unsup.v delete mode 100644 test_regress/t/t_randomize_rand_mode_warn_bad.out diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index f61f02c5c0..47d2973a57 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -403,8 +403,12 @@ bool VlRandomizer::parseSolution(std::iostream& f) { auto it = m_vars.find(name); if (it == m_vars.end()) continue; + const VlRandomVar& varr = *it->second; + if (m_randmode && !varr.randModeIdxNone()) { + if (!(m_randmode->at(varr.randModeIdx()))) continue; + } - it->second->set(std::move(value)); + varr.set(std::move(value)); } return true; diff --git a/include/verilated_random.h b/include/verilated_random.h index 12b20dea5c..b53e0eb82a 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -38,15 +38,19 @@ class VlRandomVar final : public VlRandomExpr { const char* const m_name; // Variable name void* const m_datap; // Reference to variable data const int m_width; // Variable width in bits + const std::uint32_t m_randModeIdx; // rand_mode index public: - VlRandomVar(const char* name, int width, void* datap) + VlRandomVar(const char* name, int width, void* datap, std::uint32_t randModeIdx) : m_name{name} , m_datap{datap} - , m_width{width} {} + , m_width{width} + , m_randModeIdx{randModeIdx} {} const char* name() const { return m_name; } int width() const { return m_width; } void* datap() const { return m_datap; } + std::uint32_t randModeIdx() const { return m_randModeIdx; } + bool randModeIdxNone() const { return randModeIdx() == std::numeric_limits::max(); } bool set(std::string&&) const; void emit(std::ostream& s) const override; }; @@ -94,8 +98,9 @@ class VlRandomBinOp final : public VlRandomExpr { class VlRandomizer final { // MEMBERS std::vector m_constraints; // Solver-dependent constraints - std::map> - m_vars; // Solver-dependent variables + std::map> m_vars; // Solver-dependent + // variables + const VlQueue* m_randmode; // rand_mode state; // PRIVATE METHODS std::shared_ptr randomConstraint(VlRNG& rngr, int bits); @@ -106,13 +111,15 @@ class VlRandomizer final { // Finds the next solution satisfying the constraints bool next(VlRNG& rngr); template - void write_var(T& var, int width, const char* name) { + void write_var(T& var, int width, const char* name, + std::uint32_t randmodeIdx = std::numeric_limits::max()) { auto it = m_vars.find(name); if (it != m_vars.end()) return; - m_vars[name] = std::make_shared(name, width, &var); + m_vars[name] = std::make_shared(name, width, &var, randmodeIdx); } void hard(std::string&& constraint); void clear(); + void set_randmode(const VlQueue& randmode) { m_randmode = &randmode; } #ifdef VL_DEBUG void dump() const; #endif diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 31ea2684d7..aed3554473 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2834,6 +2834,7 @@ void AstCMethodHard::setPurity() { {"reverse", false}, {"rsort", false}, {"set", false}, + {"set_randmode", false}, {"shuffle", false}, {"size", true}, {"slice", true}, diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index 342173b52d..aa97919755 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -3322,8 +3322,8 @@ class LinkDotResolveVisitor final : public VNVisitor { } else if (VN_IS(nodep, New) && m_statep->forPrearray()) { // Resolved in V3Width } else if (nodep->name() == "randomize" || nodep->name() == "srandom" - || nodep->name() == "get_randstate" - || nodep->name() == "set_randstate") { + || nodep->name() == "get_randstate" || nodep->name() == "set_randstate" + || nodep->name() == "rand_mode") { if (AstClass* const classp = VN_CAST(m_modp, Class)) { nodep->classOrPackagep(classp); } else { diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 514ce533d8..be092fc384 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -41,15 +41,64 @@ VL_DEFINE_DEBUG_FUNCTIONS; +// ###################################################################### +// Establishes the target of a rand_mode() call + +struct RandModeTarget final { + // MEMBERS + AstVar* const receiverp; // Variable that is the target of the rand_mode() call + AstNodeExpr* const fromp; // Expression from which the rand_mode() call originates + AstClass* const classp; // Class in which rand_mode should be set + + // METHODS + static RandModeTarget get(AstNodeExpr* fromp, AstNodeModule* modp) { + if (!fromp) return {nullptr, nullptr, VN_AS(modp, Class)}; + if (AstCMethodHard* const methodHardp = VN_CAST(fromp, CMethodHard)) { + return RandModeTarget::get(methodHardp->fromp(), modp); + } + AstVar* receiverp = nullptr; + if (AstVarRef* const varrefp = VN_CAST(fromp, VarRef)) { + receiverp = varrefp->varp(); + } else if (AstMemberSel* const memberSelp = VN_CAST(fromp, MemberSel)) { + receiverp = memberSelp->varp(); + } + UASSERT_OBJ(receiverp, fromp, "Unknown rand_mode() receiver"); + if (receiverp->isRand()) { + if (AstMemberSel* const memberselp = VN_CAST(fromp, MemberSel)) { + return {receiverp, memberselp->fromp(), + VN_AS(memberselp->fromp()->dtypep()->skipRefp(), ClassRefDType)->classp()}; + } + } else { + AstClassRefDType* const classRefDtp + = VN_CAST(receiverp->dtypep()->skipRefp(), ClassRefDType); + if (classRefDtp) return {receiverp, fromp, classRefDtp->classp()}; + } + return {receiverp, nullptr, VN_AS(modp, Class)}; + } +}; + +// ###################################################################### +// Stores info about a variable's rand_mode state + +union VarRandMode final { + // MEMBERS + struct { + bool usesRandMode : 1; // True if variable uses rand_mode + uint32_t index : 31; // Index of var in rand_mode vector + }; + int asInt; // Representation as int to be stored in nodep->user* +}; + //###################################################################### // Visitor that marks classes needing a randomize() method -class RandomizeMarkVisitor final : public VNVisitorConst { +class RandomizeMarkVisitor final : public VNVisitor { // NODE STATE // Cleared on Netlist // AstClass::user1() -> bool. Set true to indicate needs randomize processing // AstNodeExpr::user1() -> bool. Set true to indicate constraint expression depending on a // randomized variable + // AstVar::user1() -> bool. Set true to indicate needs rand_mode // AstVar::user2p() -> AstNodeModule*. Pointer to containing module const VNUser1InUse m_inuser1; const VNUser2InUse m_inuser2; @@ -61,6 +110,7 @@ class RandomizeMarkVisitor final : public VNVisitorConst { AstClass* m_classp = nullptr; // Current class AstNode* m_constraintExprp = nullptr; // Current constraint expression AstNodeModule* m_modp; // Current module + AstNodeStmt* m_stmtp = nullptr; // Current statement std::set m_staticRefs; // References to static variables under `with` clauses // METHODS @@ -119,8 +169,78 @@ class RandomizeMarkVisitor final : public VNVisitorConst { m_baseToDerivedMap[basep].insert(nodep); } } + void visit(AstNodeStmt* nodep) override { + VL_RESTORER(m_stmtp); + m_stmtp = nodep; + iterateChildren(nodep); + if (!nodep->backp()) VL_DO_DANGLING(nodep->deleteTree(), nodep); + } void visit(AstNodeFTaskRef* nodep) override { iterateChildrenConst(nodep); + if (nodep->name() == "rand_mode") { + AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall); + AstNodeExpr* const fromp = methodCallp ? methodCallp->fromp() : nullptr; + bool valid = true; + if (VN_IS(fromp, ArraySel)) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: 'rand_mode()' on unpacked array element"); + valid = false; + } else if (VN_IS(fromp, Sel)) { + nodep->v3error("Cannot call 'rand_mode()' on packed array element"); + valid = false; + } else if (AstCMethodHard* const methodHardp = VN_CAST(fromp, CMethodHard)) { + if (methodHardp->name() == "at" && VN_IS(fromp, CMethodHard)) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: 'rand_mode()' on dynamic array element"); + valid = false; + } else { + methodHardp->v3fatal("Unknown rand_mode() receiver"); + } + } + if (!nodep->pinsp() && VN_IS(nodep->backp(), StmtExpr)) { + nodep->v3warn( + IGNOREDRETURN, + "Ignoring return value of non-void function (IEEE 1800-2023 13.4.1)"); + valid = false; + } + if (valid) { + const RandModeTarget randModeTarget = RandModeTarget::get(fromp, m_classp); + if ((!randModeTarget.receiverp || !randModeTarget.receiverp->isRand()) + && !nodep->pinsp()) { + nodep->v3error( + "Cannot call 'rand_mode()' as a function on non-random variable"); + valid = false; + } else if (!randModeTarget.classp) { + nodep->v3error("Cannot call 'rand_mode()' on non-random, non-class variable"); + valid = false; + } else if (nodep->pinsp() && !VN_IS(nodep->backp(), StmtExpr)) { + nodep->v3error("'rand_mode()' with arguments cannot be called as a function"); + valid = false; + } else if (randModeTarget.receiverp && randModeTarget.receiverp->isRand()) { + // Called on a rand member variable + VarRandMode randMode = {}; + randMode.usesRandMode = true; + randModeTarget.receiverp->user1(randMode.asInt); + } else { + // Called on 'this' or a non-rand class instance + randModeTarget.classp->foreachMember([&](AstClass*, AstVar* varp) { + if (!varp->isRand()) return; + VarRandMode randMode = {}; + randMode.usesRandMode = true; + varp->user1(randMode.asInt); + }); + } + } + if (!valid) { + if (!nodep->pinsp() && !VN_IS(nodep->backp(), StmtExpr)) { + nodep->replaceWith(new AstConst{nodep->fileline(), 0}); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + } else { + m_stmtp->unlinkFrBack(); + } + } + return; + } if (nodep->name() != "randomize") return; AstClass* classp = m_classp; if (const AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall)) { @@ -192,17 +312,20 @@ class ConstraintExprVisitor final : public VNVisitor { AstNodeFTask* const m_inlineInitTaskp; // Method to add write_var calls to // (may be null, then new() is used) AstVar* const m_genp; // VlRandomizer variable of the class + AstVar* m_randModeVarp; // Relevant randmode state variable bool m_wantSingle = false; // Whether to merge constraint expressions with LOGAND VMemberMap& m_memberMap; // Member names cached for fast lookup + AstSFormatF* getConstFormat(AstNodeExpr* nodep) { + return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false, + nodep}; + } bool editFormat(AstNodeExpr* nodep) { if (nodep->user1()) return false; // Replace computable expression with SMT constant VNRelinker handle; nodep->unlinkFrBack(&handle); - AstSFormatF* const newp = new AstSFormatF{ - nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false, nodep}; - handle.relink(newp); + handle.relink(getConstFormat(nodep)); return true; } void editSMT(AstNodeExpr* nodep, AstNodeExpr* lhsp = nullptr, AstNodeExpr* rhsp = nullptr, @@ -282,14 +405,28 @@ class ConstraintExprVisitor final : public VNVisitor { // VISITORS void visit(AstNodeVarRef* nodep) override { - if (editFormat(nodep)) return; - // In SMT just variable name, but we also ensure write_var for the variable - const std::string smtName = nodep->name(); // Can be anything unique - nodep->replaceWith(new AstSFormatF{nodep->fileline(), smtName, false, nullptr}); - AstVar* const varp = nodep->varp(); + const VarRandMode randMode = {.asInt = varp->user1()}; + if (!randMode.usesRandMode && editFormat(nodep)) return; - VL_DO_DANGLING(pushDeletep(nodep), nodep); + // In SMT just variable name, but we also ensure write_var for the variable + const std::string smtName = nodep->name(); // Can be anything unique + VNRelinker relinker; + nodep->unlinkFrBack(&relinker); + AstNodeExpr* exprp = new AstSFormatF{nodep->fileline(), smtName, false, nullptr}; + if (randMode.usesRandMode) { + AstNodeExpr* constFormatp = getConstFormat(nodep); + AstCMethodHard* const atp = new AstCMethodHard{ + nodep->fileline(), + new AstVarRef{varp->fileline(), VN_AS(m_randModeVarp->user2p(), NodeModule), + m_randModeVarp, VAccess::READ}, + "at", new AstConst{nodep->fileline(), randMode.index}}; + atp->dtypeSetUInt32(); + exprp = new AstCond{varp->fileline(), atp, exprp, constFormatp}; + } else { + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + relinker.relink(exprp); if (!varp->user3()) { AstCMethodHard* const methodp = new AstCMethodHard{ @@ -308,6 +445,10 @@ class ConstraintExprVisitor final : public VNVisitor { = new AstCExpr{varp->fileline(), "\"" + smtName + "\"", varp->width()}; varnamep->dtypep(varp->dtypep()); methodp->addPinsp(varnamep); + if (randMode.usesRandMode) { + methodp->addPinsp( + new AstConst{varp->fileline(), AstConst::Unsized64{}, randMode.index}); + } AstNodeFTask* initTaskp = m_inlineInitTaskp; if (!initTaskp) { varp->user3(true); // Mark as set up in new() @@ -430,9 +571,11 @@ class ConstraintExprVisitor final : public VNVisitor { public: // CONSTRUCTORS explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep, - AstNodeFTask* inlineInitTaskp, AstVar* genp) + AstNodeFTask* inlineInitTaskp, AstVar* genp, + AstVar* randModeVarp) : m_inlineInitTaskp{inlineInitTaskp} , m_genp{genp} + , m_randModeVarp{randModeVarp} , m_memberMap{memberMap} { iterateAndNextNull(nodep); } @@ -538,15 +681,19 @@ class RandomizeVisitor final : public VNVisitor { // AstClass::user2p() -> AstTask*. Pointer to full constraint setup procedure // AstVar::user3() -> bool. Handled in constraints // AstClass::user3p() -> AstVar*. Constrained randomizer variable + // AstClass::user4p() -> AstVar*. Rand mode state variable // VNUser1InUse m_inuser1; (Allocated for use in RandomizeMarkVisitor) // VNUser2InUse m_inuser2; (Allocated for use in RandomizeMarkVisitor) const VNUser3InUse m_inuser3; + 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 + AstNodeStmt* m_stmtp = nullptr; // Current statement + AstDynArrayDType* m_dynarrayDtp = nullptr; // Dynamic array type (for rand mode) 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 @@ -579,14 +726,119 @@ class RandomizeVisitor final : public VNVisitor { classp->user2p(setupAllTaskp); return setupAllTaskp; } + AstVar* getCreateRandModeVar(AstClass* const classp) { + if (classp->user4p()) return VN_AS(classp->user4p(), Var); + if (AstClassExtends* const extendsp = classp->extendsp()) { + return getCreateRandModeVar(extendsp->classp()); + } + FileLine* const fl = classp->fileline(); + if (!m_dynarrayDtp) { + m_dynarrayDtp = new AstDynArrayDType{ + fl, v3Global.rootp()->typeTablep()->findBitDType()->dtypep()}; + m_dynarrayDtp->dtypep(m_dynarrayDtp); + v3Global.rootp()->typeTablep()->addTypesp(m_dynarrayDtp); + } + AstVar* const randModeVarp + = new AstVar{fl, VVarType::MODULETEMP, "__Vrandmode", m_dynarrayDtp}; + + randModeVarp->user2p(classp); + classp->addStmtsp(randModeVarp); + classp->user4p(randModeVarp); + return randModeVarp; + } + AstVar* getRandModeVar(AstClass* const classp) { + if (classp->user4p()) return VN_AS(classp->user4p(), Var); + if (AstClassExtends* const extendsp = classp->extendsp()) { + return getRandModeVar(extendsp->classp()); + } + return nullptr; + } + void addSetRandMode(AstNodeFTask* const ftaskp, AstVar* const genp, + AstVar* const randModeVarp) { + FileLine* const fl = ftaskp->fileline(); + AstCMethodHard* const setRandModep = new AstCMethodHard{ + fl, new AstVarRef{fl, VN_AS(genp->user2p(), NodeModule), genp, VAccess::WRITE}, + "set_randmode", + new AstVarRef{fl, VN_AS(randModeVarp->user2p(), NodeModule), randModeVarp, + VAccess::READ}}; + setRandModep->dtypeSetVoid(); + ftaskp->addStmtsp(setRandModep->makeStmt()); + } void createRandomizeClassVars(AstNetlist* const netlistp) { netlistp->foreach([&](AstClass* const classp) { if (classp->existsMember( [&](const AstClass*, const AstConstraint*) { return true; })) { createRandomGenerator(classp); } + uint32_t randModeCount = 0; + classp->foreachMember([&](AstClass*, AstVar* memberVarp) { + VarRandMode randMode = {.asInt = memberVarp->user1()}; + if (!randMode.usesRandMode) return; + // SystemVerilog only allows single inheritance, so we don't need to worry about + // index overlap. If the index > 0, it's already been set. + if (randMode.index == 0) { + randMode.index = randModeCount++; + memberVarp->user1(randMode.asInt); + } else { + randModeCount = randMode.index + 1; + } + }); + if (randModeCount > 0) { + AstVar* const randModeVarp = getCreateRandModeVar(classp); + AstNodeModule* const randModeModp = VN_AS(randModeVarp->user2p(), NodeModule); + FileLine* fl = randModeVarp->fileline(); + AstCMethodHard* const dynarrayNewp = new AstCMethodHard{ + fl, new AstVarRef{fl, randModeModp, randModeVarp, VAccess::WRITE}, + "renew_copy", new AstConst{fl, randModeCount}}; + dynarrayNewp->addPinsp( + 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"); + newp->addStmtsp(dynarrayNewp->makeStmt()); + newp->addStmtsp(makeRandModeInitLoop( + fl, new AstVarRef{fl, randModeModp, randModeVarp, VAccess::WRITE}, + new AstConst{fl, 1}, true)); + } }); } + static AstNode* makeRandModeInitLoop(FileLine* const fl, AstNodeExpr* const lhsp, + AstNodeExpr* const rhsp, bool inTask) { + AstVar* const iterVarp = new AstVar{fl, VVarType::BLOCKTEMP, "i", lhsp->findUInt32DType()}; + iterVarp->funcLocal(inTask); + iterVarp->lifetime(VLifetime::AUTOMATIC); + AstCMethodHard* const sizep = new AstCMethodHard{fl, lhsp, "size", nullptr}; + sizep->dtypeSetUInt32(); + AstCMethodHard* const atp = new AstCMethodHard{fl, lhsp->cloneTree(false), "at", + new AstVarRef{fl, iterVarp, VAccess::READ}}; + atp->dtypeSetUInt32(); + AstNode* const stmtsp = iterVarp; + stmtsp->addNext( + new AstAssign{fl, new AstVarRef{fl, iterVarp, VAccess::WRITE}, new AstConst{fl, 0}}); + stmtsp->addNext( + new AstWhile{fl, new AstLt{fl, new AstVarRef{fl, iterVarp, VAccess::READ}, sizep}, + new AstAssign{fl, atp, rhsp}, + new AstAssign{fl, new AstVarRef{fl, iterVarp, VAccess::WRITE}, + new AstAdd{fl, new AstConst{fl, 1}, + new AstVarRef{fl, iterVarp, VAccess::READ}}}}); + return new AstBegin{fl, "", stmtsp, false, true}; + } + AstNodeStmt* wrapIfRandMode(AstVar* const varp, AstNodeStmt* stmtp) { + FileLine* const fl = stmtp->fileline(); + const VarRandMode randMode = {.asInt = varp->user1()}; + if (randMode.usesRandMode) { + AstVar* randModeVarp = getRandModeVar(VN_AS(m_modp, Class)); + AstCMethodHard* const atp + = new AstCMethodHard{fl, + new AstVarRef{fl, VN_AS(randModeVarp->user2p(), Class), + randModeVarp, VAccess::READ}, + "at", new AstConst{fl, randMode.index}}; + atp->dtypeSetUInt32(); + return new AstIf{fl, atp, stmtp}; + } + return stmtp; + } AstVar* enumValueTabp(AstEnumDType* const nodep) { if (nodep->user2p()) return VN_AS(nodep->user2p(), Var); UINFO(9, "Construct Venumvaltab " << nodep << endl); @@ -702,10 +954,17 @@ class RandomizeVisitor final : public VNVisitor { valp = newRandValue(fl, randcVarp, (memberp ? memberp->dtypep() : exprp->dtypep())); } - return new AstAssign{fl, - new AstSel{fl, exprp, offset + (memberp ? memberp->lsb() : 0), - memberp ? memberp->width() : exprp->width()}, - valp}; + AstAssign* assignp + = new AstAssign{fl, + new AstSel{fl, exprp, offset + (memberp ? memberp->lsb() : 0), + memberp ? memberp->width() : exprp->width()}, + valp}; + AstVar* varp = nullptr; + exprp->exists([&](const AstVarRef* varrefp) { + if (varrefp->access().isWriteOrRW()) varp = varrefp->varp(); + return varp != nullptr; + }); + return wrapIfRandMode(varp, assignp); } } AstNodeExpr* newRandValue(FileLine* const fl, AstVar* const randcVarp, @@ -780,7 +1039,7 @@ class RandomizeVisitor final : public VNVisitor { new AstConst{fl, AstConst::Null{}}}, new AstAssign{fl, basicFvarRefp->cloneTree(false), new AstAnd{fl, basicFvarRefReadp, callp}}}; - basicRandomizep->addStmtsp(assignIfNotNullp); + basicRandomizep->addStmtsp(wrapIfRandMode(memberVarp, assignIfNotNullp)); } else { memberVarp->v3warn(E_UNSUPPORTED, "Unsupported: random member variable with type " << memberVarp->dtypep()->prettyDTypeNameQ()); @@ -815,6 +1074,7 @@ class RandomizeVisitor final : public VNVisitor { addPrePostCall(nodep, randomizep, "pre_randomize"); FileLine* fl = nodep->fileline(); + AstVar* const randModeVarp = getRandModeVar(nodep); AstNodeExpr* beginValp = nullptr; AstVar* genp = getRandomGenerator(nodep); if (genp) { @@ -833,7 +1093,7 @@ class RandomizeVisitor final : public VNVisitor { setupAllTaskp->addStmtsp(setupTaskRefp->makeStmt()); - ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr, genp}; + ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr, genp, randModeVarp}; if (constrp->itemsp()) taskp->addStmtsp(constrp->itemsp()->unlinkFrBackWithNext()); }); randomizep->addStmtsp(implementConstraintsClear(fl, genp)); @@ -843,13 +1103,22 @@ class RandomizeVisitor final : public VNVisitor { randomizep->addStmtsp(implementConstraintsClear(fl, genp)); randomizep->addStmtsp(setupTaskRefp->makeStmt()); - AstVarRef* genRefp - = new AstVarRef{fl, VN_AS(genp->user2p(), NodeModule), genp, VAccess::READWRITE}; + AstNodeModule* const genModp = VN_AS(genp->user2p(), NodeModule); + AstVarRef* const genRefp = new AstVarRef{fl, genModp, genp, VAccess::READWRITE}; AstNode* const argsp = genRefp; argsp->addNext(new AstText{fl, ".next(__Vm_rng)"}); + AstNodeExpr* const solverCallp = new AstCExpr{fl, argsp}; solverCallp->dtypeSetBit(); beginValp = solverCallp; + + if (randModeVarp) { + AstNodeModule* const randModeClassp = VN_AS(randModeVarp->user2p(), Class); + AstNodeFTask* const newp + = VN_AS(m_memberMap.findMember(randModeClassp, "new"), NodeFTask); + UASSERT_OBJ(newp, randModeClassp, "No new() in class"); + addSetRandMode(newp, genp, randModeVarp); + } } else { beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1}; } @@ -933,6 +1202,58 @@ class RandomizeVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); } void visit(AstNodeFTaskRef* nodep) override { + if (nodep->name() == "rand_mode") { + AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall); + AstNodeExpr* const fromp = methodCallp ? methodCallp->fromp() : nullptr; + const RandModeTarget randModeTarget = RandModeTarget::get(fromp, m_modp); + UASSERT_OBJ(randModeTarget.classp, nodep, + "Should have checked in RandomizeMarkVisitor"); + AstVar* const randModeVarp = getRandModeVar(randModeTarget.classp); + AstNodeExpr* lhsp = nullptr; + if (randModeTarget.classp == m_modp) { + // Called on 'this' or a member of 'this' + lhsp = new AstVarRef{nodep->fileline(), VN_AS(randModeVarp->user2p(), NodeModule), + randModeVarp, VAccess::WRITE}; + } else { + AstMemberSel* const memberselp = new AstMemberSel{ + nodep->fileline(), randModeTarget.fromp->unlinkFrBack(), randModeVarp}; + memberselp->foreach([](AstVarRef* varrefp) { varrefp->access(VAccess::WRITE); }); + lhsp = memberselp; + } + if (nodep->pinsp()) { // Set rand mode + UASSERT_OBJ(VN_IS(nodep->backp(), StmtExpr), nodep, "Should be a statement"); + AstNodeExpr* const rhsp = VN_AS(nodep->pinsp(), Arg)->exprp()->unlinkFrBack(); + if (randModeTarget.receiverp && randModeTarget.receiverp->isRand()) { + // Called on a rand member variable. Set the variable's rand mode + const VarRandMode randMode = {.asInt = randModeTarget.receiverp->user1()}; + UASSERT_OBJ(randMode.usesRandMode, nodep, "Failed to set usesRandMode"); + AstCMethodHard* const atp + = new AstCMethodHard{nodep->fileline(), lhsp, "at", + new AstConst{nodep->fileline(), randMode.index}}; + atp->dtypeSetUInt32(); + m_stmtp->replaceWith(new AstAssign{nodep->fileline(), atp, rhsp}); + } else { + // Called on 'this' or a non-rand class instance. Set the rand mode of all + // members + m_stmtp->replaceWith( + makeRandModeInitLoop(nodep->fileline(), lhsp, rhsp, m_ftaskp)); + } + pushDeletep(m_stmtp); + } else { // Retrieve rand mode + UASSERT_OBJ(randModeTarget.receiverp, nodep, "Should have receiver"); + UASSERT_OBJ(randModeTarget.receiverp->isRand(), nodep, "Should be rand"); + const VarRandMode randMode = {.asInt = randModeTarget.receiverp->user1()}; + UASSERT_OBJ(randMode.usesRandMode, nodep, "Failed to set usesRandMode"); + AstCMethodHard* const atp + = new AstCMethodHard{nodep->fileline(), lhsp, "at", + new AstConst{nodep->fileline(), randMode.index}}; + atp->dtypeSetUInt32(); + nodep->replaceWith(atp); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + } + return; + } + AstWith* const withp = VN_CAST(nodep->pinsp(), With); if (!(nodep->name() == "randomize") || !withp) { @@ -1018,9 +1339,14 @@ class RandomizeVisitor final : public VNVisitor { VAccess::READ}}); } + // Set rand mode if present (not needed if classGenp exists and was copied) + AstVar* const randModeVarp = getRandModeVar(classp); + 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}; + ConstraintExprVisitor{m_memberMap, captured.getTree(), randomizeFuncp, localGenp, + randModeVarp}; // Call the solver and set return value AstVarRef* const randNextp @@ -1042,6 +1368,11 @@ class RandomizeVisitor final : public VNVisitor { UINFO(9, "Added `%s` randomization procedure"); VL_DO_DANGLING(withp->deleteTree(), withp); } + void visit(AstNodeStmt* nodep) override { + VL_RESTORER(m_stmtp); + m_stmtp = nodep; + iterateChildren(nodep); + } void visit(AstNode* nodep) override { iterateChildren(nodep); } public: diff --git a/src/V3Width.cpp b/src/V3Width.cpp index e0816d4a63..3a7856c119 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -2965,6 +2965,12 @@ class WidthVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(nodep), nodep); } bool memberSelClass(AstMemberSel* nodep, AstClassRefDType* adtypep) { + if (nodep->name() == "rand_mode") { + nodep->replaceWith(new AstMethodCall{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "rand_mode", nullptr}); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return true; + } // Returns true if ok // No need to width-resolve the class, as it was done when we did the child AstClass* const first_classp = adtypep->classp(); @@ -3885,12 +3891,14 @@ class WidthVisitor final : public VNVisitor { } } void methodCallRandMode(AstMethodCall* nodep) { - // Method call on constraint (currently hacked as just a var) methodOkArguments(nodep, 0, 1); - nodep->v3warn(CONSTRAINTIGN, "rand_mode ignored (unsupported)"); - // Disables ignored, so we just return "ON" - nodep->replaceWith(new AstConst{nodep->fileline(), AstConst::BitTrue{}}); - VL_DO_DANGLING(pushDeletep(nodep), nodep); + // IEEE 1800-2023 18.8 + if (nodep->pinsp()) { + nodep->dtypep(nodep->findBasicDType(VBasicDTypeKwd::INT)); + } else { + nodep->dtypeSetVoid(); + } + v3Global.useRandomizeMethods(true); } void methodCallUnpack(AstMethodCall* nodep, AstUnpackArrayDType* adtypep) { enum : uint8_t { @@ -6021,9 +6029,13 @@ class WidthVisitor final : public VNVisitor { // Function hasn't been widthed, so make it so. UINFO(5, " FTASKREF " << nodep << endl); AstWith* withp = nullptr; - if (nodep->name() == "randomize" || nodep->name() == "srandom" - || (!nodep->taskp() - && (nodep->name() == "get_randstate" || nodep->name() == "set_randstate"))) { + if (nodep->name() == "rand_mode") { + v3Global.useRandomizeMethods(true); + nodep->dtypep(nodep->findBasicDType(VBasicDTypeKwd::INT)); + } else if (nodep->name() == "randomize" || nodep->name() == "srandom" + || (!nodep->taskp() + && (nodep->name() == "get_randstate" + || nodep->name() == "set_randstate"))) { // TODO perhaps this should move to V3LinkDot AstClass* const classp = VN_CAST(nodep->classOrPackagep(), Class); UASSERT_OBJ(classp, nodep, "Should have failed in V3LinkDot"); @@ -6035,7 +6047,8 @@ class WidthVisitor final : public VNVisitor { withp = methodWithArgument(nodep, false, false, adtypep->findVoidDType(), adtypep->findBitDType(), adtypep); if (nodep->pinsp()) { - nodep->pinsp()->v3warn(CONSTRAINTIGN, "rand_mode ignored (unsupported)"); + nodep->pinsp()->v3warn(CONSTRAINTIGN, + "Inline random variable control (unsupported)"); nodep->pinsp()->unlinkFrBackWithNext()->deleteTree(); } } else if (nodep->name() == "srandom") { @@ -6070,6 +6083,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(false, nodep, "Bad case"); } } + if (nodep->name() == "rand_mode") return; // Handled in V3Randomize UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked"); if (nodep->didWidth()) return; if ((nodep->taskp()->classMethod() && !nodep->taskp()->isStatic()) diff --git a/src/V3WidthCommit.cpp b/src/V3WidthCommit.cpp index ec9589e029..16ae277543 100644 --- a/src/V3WidthCommit.cpp +++ b/src/V3WidthCommit.cpp @@ -79,6 +79,11 @@ class WidthCommitVisitor final : public VNVisitor { // Check local/protected status and complain bool local = false; bool prot = false; + if (!defp) { + // rand_mode() handled in V3Randomize + UASSERT_OBJ(nodep->name() == "rand_mode", nodep, "Only rand_mode() can have no def"); + return; + } if (const auto varp = VN_CAST(defp, Var)) { local = varp->isHideLocal(); prot = varp->isHideProtected(); diff --git a/test_regress/t/t_randomize_inline_var_ctl_unsup.out b/test_regress/t/t_randomize_inline_var_ctl_unsup.out new file mode 100644 index 0000000000..c6f073ca95 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup.out @@ -0,0 +1,9 @@ +%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.pl b/test_regress/t/t_randomize_inline_var_ctl_unsup.pl new file mode 100755 index 0000000000..58d9a78189 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup.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(linter => 1); + +lint( + expect_filename => $Self->{golden_filename}, + fails => 1, + ); + +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.v new file mode 100644 index 0000000000..1f04c76fe9 --- /dev/null +++ b/test_regress/t/t_randomize_inline_var_ctl_unsup.v @@ -0,0 +1,16 @@ +// 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 Packet; + rand int one; + int two; + static int three; + + function void test; + void'(randomize(one)); + void'(randomize(two, three)); + endfunction +endclass diff --git a/test_regress/t/t_randomize_rand_mode.pl b/test_regress/t/t_randomize_rand_mode.pl index 11d21fc86b..e45199a00b 100755 --- a/test_regress/t/t_randomize_rand_mode.pl +++ b/test_regress/t/t_randomize_rand_mode.pl @@ -11,7 +11,6 @@ scenarios(simulator => 1); compile( - verilator_flags2 => ['-Wno-CONSTRAINTIGN'], ); execute( diff --git a/test_regress/t/t_randomize_rand_mode.v b/test_regress/t/t_randomize_rand_mode.v index 1b4f4f12a5..80a0ca50db 100644 --- a/test_regress/t/t_randomize_rand_mode.v +++ b/test_regress/t/t_randomize_rand_mode.v @@ -7,28 +7,50 @@ `define stop $stop `define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); -// TODO Verilator ignores this setting currently, always returning 1 (rand on) -`ifdef VERILATOR - `define checkh_vlt1(gotv,expv) `checkh(gotv,1) -`else - `define checkh_vlt1(gotv,expv) `checkh(gotv,expv) -`endif - -class Packet; +class Base; rand int m_one; - rand int m_two; task test1; m_one.rand_mode(0); - `checkh_vlt1(m_one.rand_mode(), 0); - m_two.rand_mode(0); - `checkh_vlt1(m_two.rand_mode(), 0); + `checkh(m_one.rand_mode(), 0); + verify(0); + + m_one.rand_mode(1); + `checkh(m_one.rand_mode(), 1); + verify(1); + endtask + + task verify(int mode_one); + bit one_ne10 = '0; + int v; + if (m_one.rand_mode() != mode_one) $stop; + for (int i = 0; i < 20; ++i) begin + m_one = 10; + v = randomize(); + if (m_one != 10) one_ne10 = 1'b1; +`ifdef TEST_VERBOSE + $display("one=%0d(rand_mode=%0d)", m_one, mode_one); +`endif + end + if (mode_one != 0 && !one_ne10) $stop; + if (mode_one == 0 && one_ne10) $stop; + endtask + +endclass + +class Packet extends Base; + rand int m_two; + + task test2; + rand_mode(0); + `checkh(m_one.rand_mode(), 0); + `checkh(m_two.rand_mode(), 0); verify(0, 0); m_one.rand_mode(0); - `checkh_vlt1(m_one.rand_mode(), 0); + `checkh(m_one.rand_mode(), 0); m_two.rand_mode(1); - `checkh_vlt1(m_two.rand_mode(), 1); + `checkh(m_two.rand_mode(), 1); verify(0, 1); endtask @@ -36,31 +58,23 @@ class Packet; bit one_ne10 = '0; bit two_ne10 = '0; int v; - // TODO Verilator ignores this setting currently, always returning 1 (rand on) -`ifndef VERILATOR if (m_one.rand_mode() != mode_one) $stop; if (m_two.rand_mode() != mode_two) $stop; -`else - if (m_one.rand_mode() != 1) $stop; - if (m_two.rand_mode() != 1) $stop; -`endif for (int i = 0; i < 20; ++i) begin - m_one = 10; - m_two = 10; - v = randomize(); - if (m_one != 10) one_ne10 = 1'b1; - if (m_two != 10) two_ne10 = 1'b1; + m_one = 10; + m_two = 10; + v = randomize(); + if (m_one != 10) one_ne10 = 1'b1; + if (m_two != 10) two_ne10 = 1'b1; `ifdef TEST_VERBOSE - $display("one=%0d(rand_mode=%0d) two=%0d(rand_mode=%0d)", - m_one, mode_one, m_two, mode_two); + $display("one=%0d(rand_mode=%0d) two=%0d(rand_mode=%0d)", + m_one, mode_one, m_two, mode_two); `endif end if (mode_one != 0 && !one_ne10) $stop; if (mode_two != 0 && !two_ne10) $stop; -`ifndef VERILATOR if (mode_one == 0 && one_ne10) $stop; if (mode_two == 0 && two_ne10) $stop; -`endif endtask endclass @@ -75,6 +89,7 @@ module t (/*AUTOARG*/); p = new; p.test1(); + p.test2(); // IEEE: function void object[.random_variable].rand_mode(bit on_off); // IEEE: function int object.random_variable.rand_mode(); @@ -83,15 +98,15 @@ module t (/*AUTOARG*/); // We call rand_mode here too becuase the parsing is different from that // called from the class itself p.m_one.rand_mode(0); - `checkh_vlt1(p.m_one.rand_mode(), 0); + `checkh(p.m_one.rand_mode(), 0); p.m_two.rand_mode(0); - `checkh_vlt1(p.m_two.rand_mode(), 0); + `checkh(p.m_two.rand_mode(), 0); p.verify(0, 0); p.m_one.rand_mode(0); - `checkh_vlt1(p.m_one.rand_mode(), 0); + `checkh(p.m_one.rand_mode(), 0); p.m_two.rand_mode(1); - `checkh_vlt1(p.m_two.rand_mode(), 1); + `checkh(p.m_two.rand_mode(), 1); p.verify(0, 1); p.rand_mode(1); diff --git a/test_regress/t/t_randomize_rand_mode_bad.out b/test_regress/t/t_randomize_rand_mode_bad.out new file mode 100644 index 0000000000..497b754241 --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_bad.out @@ -0,0 +1,27 @@ +%Error: t/t_randomize_rand_mode_bad.v:22:15: Cannot call 'rand_mode()' on non-random, non-class variable + : ... note: In instance 't' + 22 | p.m_val.rand_mode(0); + | ^~~~~~~~~ +%Error: t/t_randomize_rand_mode_bad.v:23:19: Cannot call 'rand_mode()' on packed array element + : ... note: In instance 't' + 23 | p.m_pack[0].rand_mode(0); + | ^~~~~~~~~ +%Error: t/t_randomize_rand_mode_bad.v:24:39: Cannot call 'rand_mode()' as a function on non-random variable + : ... note: In instance 't' + 24 | $display("p.rand_mode()=%0d", p.rand_mode()); + | ^~~~~~~~~ +%Error: t/t_randomize_rand_mode_bad.v:25:18: 'rand_mode()' with arguments cannot be called as a function + : ... note: In instance 't' + 25 | $display(p.rand_mode(0)); + | ^~~~~~~~~ +%Warning-IGNOREDRETURN: t/t_randomize_rand_mode_bad.v:26:21: Ignoring return value of non-void function (IEEE 1800-2023 13.4.1) + : ... note: In instance 't' + 26 | p.m_other_val.rand_mode(); + | ^~~~~~~~~ + ... For warning description see https://verilator.org/warn/IGNOREDRETURN?v=latest + ... Use "/* verilator lint_off IGNOREDRETURN */" and lint_on around source to disable this message. +%Error: t/t_randomize_rand_mode_bad.v:13:14: Cannot call 'rand_mode()' as a function on non-random variable + : ... note: In instance 't' + 13 | return rand_mode(); + | ^~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_rand_mode_warn_bad.pl b/test_regress/t/t_randomize_rand_mode_bad.pl similarity index 92% rename from test_regress/t/t_randomize_rand_mode_warn_bad.pl rename to test_regress/t/t_randomize_rand_mode_bad.pl index 9c0016ff50..63947fd00e 100755 --- a/test_regress/t/t_randomize_rand_mode_warn_bad.pl +++ b/test_regress/t/t_randomize_rand_mode_bad.pl @@ -10,8 +10,6 @@ scenarios(vlt => 1); -top_filename("t/t_randomize_rand_mode.v"); - lint( fails => 1, expect_filename => $Self->{golden_filename}, diff --git a/test_regress/t/t_randomize_rand_mode_bad.v b/test_regress/t/t_randomize_rand_mode_bad.v new file mode 100644 index 0000000000..6f3b5a0e90 --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_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 Packet; + int m_val; + rand int m_other_val; + rand logic [7:0] m_pack; + + function int get_rand_mode; + return rand_mode(); + endfunction +endclass + +module t; + Packet p; + + initial begin + p = new; + p.m_val.rand_mode(0); + p.m_pack[0].rand_mode(0); + $display("p.rand_mode()=%0d", p.rand_mode()); + $display(p.rand_mode(0)); + p.m_other_val.rand_mode(); + end +endmodule diff --git a/test_regress/t/t_randomize_rand_mode_constr.pl b/test_regress/t/t_randomize_rand_mode_constr.pl new file mode 100755 index 0000000000..7b520d284f --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_constr.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_rand_mode_constr.v b/test_regress/t/t_randomize_rand_mode_constr.v new file mode 100644 index 0000000000..c82db0cc3c --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_constr.v @@ -0,0 +1,143 @@ +// 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 a; + rand int b; +endclass + +class Bar; + rand int x; + rand Foo foo; + + constraint x_gt_0 {x > 0;}; + + function new; + foo = new; + endfunction +endclass + +class Qux extends Bar; + rand int y; + constraint y_gt_x {y > x;}; + constraint y_lt_10 {y < 10;}; + + function void test; + logic ok = 0; + x.rand_mode(1); + if (x.rand_mode == 0) $stop; + y.rand_mode(0); + if (y.rand_mode == 1) $stop; + foo.a.rand_mode(0); + if (foo.a.rand_mode == 1) $stop; + foo.b.rand_mode(1); + if (foo.b.rand_mode == 0) $stop; + for (int i = 0; i < 20; ++i) begin + x = 4; + y = 8; + foo.a = 15; + foo.b = 16; + void'(randomize()); + if (x >= y) $stop; + if (x != 4) ok = 1; + if (y != 8) $stop; + if (foo.a != 15) $stop; + if (foo.b != 16) ok = 1; + end + if (!ok) $stop; + foo.b = 16; + foo.rand_mode(0); + if (foo.rand_mode == 1) $stop; + if (foo.a.rand_mode == 1) $stop; + if (foo.b.rand_mode == 0) $stop; + void'(randomize()); + if (foo.a != 15) $stop; + if (foo.b != 16) $stop; + ok = 0; + foo.rand_mode(1); + if (foo.rand_mode == 0) $stop; + for (int i = 0; i < 20; ++i) begin + foo.a = 23; + foo.b = 42; + void'(randomize()); + if (foo.a != 23) $stop; + if (foo.b != 42) ok = 1; + end + if (!ok) $stop; + endfunction +endclass + +class Baz; + Qux qux; + + function new(); + qux = new; + endfunction + + function void test; + qux.x = 42; + qux.rand_mode(0); + if (qux.x.rand_mode == 1) $stop; + void'(qux.randomize()); + if (qux.x != 42) $stop; + endfunction +endclass + +class Quux; + rand int x; +endclass + +module t; + initial begin + logic ok = 0; + int res; + Baz baz = new; + Qux qux = new; + Quux quux = new; + + baz.test; + qux.test; + + qux.x.rand_mode(0); + if (qux.x.rand_mode == 1) $stop; + qux.y.rand_mode(1); + if (qux.y.rand_mode == 0) $stop; + qux.foo.a.rand_mode(1); + if (qux.foo.a.rand_mode == 0) $stop; + qux.foo.b.rand_mode(0); + if (qux.foo.b.rand_mode == 1) $stop; + for (int i = 0; i < 20; ++i) begin + qux.x = 5; + qux.y = 8; + qux.foo.a = 13; + qux.foo.b = 21; + res = qux.randomize() with {y > 5;}; + if (qux.x >= qux.y) $stop; + if (qux.y <= 5) $stop; + if (qux.x != 5) $stop; + if (qux.y != 8) ok = 1; + if (qux.foo.a != 13) ok = 1; + if (qux.foo.b != 21) $stop; + end + if (!ok) $stop; + + quux.x.rand_mode(0); + quux.x = 1000; + res = quux.randomize() with {x != 1000;}; + if (quux.x != 1000) $stop; + quux.rand_mode(1); + res = quux.randomize() with {x != 1000;}; + if (quux.x == 1000) $stop; + + qux.x = 1024; + qux.y = 512; + qux.rand_mode(0); + if (qux.randomize() == 1) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_randomize_rand_mode_unsup.out b/test_regress/t/t_randomize_rand_mode_unsup.out new file mode 100644 index 0000000000..c76afbc788 --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_unsup.out @@ -0,0 +1,10 @@ +%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:15:22: Unsupported: 'rand_mode()' on dynamic array element + : ... note: In instance 't' + 15 | p.m_dyn_arr[0].rand_mode(0); + | ^~~~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:16:22: Unsupported: 'rand_mode()' on unpacked array element + : ... note: In instance 't' + 16 | p.m_unp_arr[0].rand_mode(0); + | ^~~~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_rand_mode_unsup.pl b/test_regress/t/t_randomize_rand_mode_unsup.pl new file mode 100755 index 0000000000..63947fd00e --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_unsup.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 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(vlt => 1); + +lint( + fails => 1, + expect_filename => $Self->{golden_filename}, + ); + +ok(1); +1; diff --git a/test_regress/t/t_randomize_rand_mode_unsup.v b/test_regress/t/t_randomize_rand_mode_unsup.v new file mode 100644 index 0000000000..8b72243f8c --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_unsup.v @@ -0,0 +1,18 @@ +// 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 Packet; + rand int m_dyn_arr[]; + rand int m_unp_arr[10]; +endclass + +module t; + initial begin + Packet p = new; + p.m_dyn_arr[0].rand_mode(0); + p.m_unp_arr[0].rand_mode(0); + end +endmodule diff --git a/test_regress/t/t_randomize_rand_mode_warn_bad.out b/test_regress/t/t_randomize_rand_mode_warn_bad.out deleted file mode 100644 index 55728b85cf..0000000000 --- a/test_regress/t/t_randomize_rand_mode_warn_bad.out +++ /dev/null @@ -1,115 +0,0 @@ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:22:13: rand_mode ignored (unsupported) - : ... note: In instance 't' - 22 | m_one.rand_mode(0); - | ^~~~~~~~~ - ... 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_rand_mode.v:23:136: rand_mode ignored (unsupported) - : ... note: In instance 't' - 23 | do if ((m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",23, (m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:23:21: rand_mode ignored (unsupported) - : ... note: In instance 't' - 23 | do if ((m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",23, (m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:24:13: rand_mode ignored (unsupported) - : ... note: In instance 't' - 24 | m_two.rand_mode(0); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:25:136: rand_mode ignored (unsupported) - : ... note: In instance 't' - 25 | do if ((m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",25, (m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:25:21: rand_mode ignored (unsupported) - : ... note: In instance 't' - 25 | do if ((m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",25, (m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:44:17: rand_mode ignored (unsupported) - : ... note: In instance 't' - 44 | if (m_one.rand_mode() != 1) $stop; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:45:17: rand_mode ignored (unsupported) - : ... note: In instance 't' - 45 | if (m_two.rand_mode() != 1) $stop; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:28:13: rand_mode ignored (unsupported) - : ... note: In instance 't' - 28 | m_one.rand_mode(0); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:29:136: rand_mode ignored (unsupported) - : ... note: In instance 't' - 29 | do if ((m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",29, (m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:29:21: rand_mode ignored (unsupported) - : ... note: In instance 't' - 29 | do if ((m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",29, (m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:30:13: rand_mode ignored (unsupported) - : ... note: In instance 't' - 30 | m_two.rand_mode(1); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:31:136: rand_mode ignored (unsupported) - : ... note: In instance 't' - 31 | do if ((m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",31, (m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:31:21: rand_mode ignored (unsupported) - : ... note: In instance 't' - 31 | do if ((m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",31, (m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:85:15: rand_mode ignored (unsupported) - : ... note: In instance 't' - 85 | p.m_one.rand_mode(0); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:86:140: rand_mode ignored (unsupported) - : ... note: In instance 't' - 86 | do if ((p.m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",86, (p.m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:86:23: rand_mode ignored (unsupported) - : ... note: In instance 't' - 86 | do if ((p.m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",86, (p.m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:87:15: rand_mode ignored (unsupported) - : ... note: In instance 't' - 87 | p.m_two.rand_mode(0); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:88:140: rand_mode ignored (unsupported) - : ... note: In instance 't' - 88 | do if ((p.m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",88, (p.m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:88:23: rand_mode ignored (unsupported) - : ... note: In instance 't' - 88 | do if ((p.m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",88, (p.m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:91:15: rand_mode ignored (unsupported) - : ... note: In instance 't' - 91 | p.m_one.rand_mode(0); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:92:140: rand_mode ignored (unsupported) - : ... note: In instance 't' - 92 | do if ((p.m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",92, (p.m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:92:23: rand_mode ignored (unsupported) - : ... note: In instance 't' - 92 | do if ((p.m_one.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",92, (p.m_one.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:93:15: rand_mode ignored (unsupported) - : ... note: In instance 't' - 93 | p.m_two.rand_mode(1); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:94:140: rand_mode ignored (unsupported) - : ... note: In instance 't' - 94 | do if ((p.m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",94, (p.m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:94:23: rand_mode ignored (unsupported) - : ... note: In instance 't' - 94 | do if ((p.m_two.rand_mode()) !== (1)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_randomize_rand_mode.v",94, (p.m_two.rand_mode()), (1)); $stop; end while(0);; - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:97:9: rand_mode ignored (unsupported) - : ... note: In instance 't' - 97 | p.rand_mode(1); - | ^~~~~~~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_rand_mode.v:100:9: rand_mode ignored (unsupported) - : ... note: In instance 't' - 100 | p.rand_mode(0); - | ^~~~~~~~~ -%Error: Exiting due to