diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 93aba10f9..19817b8d0 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -744,8 +744,12 @@ bool VlRandomizer::parseSolution(std::iostream& os, bool log) { const auto it = m_vars.find(name); if (it == m_vars.end()) continue; const VlRandomVar& varr = *it->second; - if (m_randmodep && !varr.randModeIdxNone()) { - if (!m_randmodep->at(varr.randModeIdx())) continue; + if (!varr.randModeIdxNone()) { + // Static rand vars have their rand_mode in a class-package shared queue, + // not the per-instance one. + const VlQueue* const modep + = m_staticVars.count(name) ? m_static_randmodep : m_randmodep; + if (modep && !modep->at(varr.randModeIdx())) continue; } if (m_disabledVars.count(name)) continue; if (!indices.empty()) { diff --git a/include/verilated_random.h b/include/verilated_random.h index 6ab19a850..e646b6840 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -33,6 +33,7 @@ #include #include #include +#include //============================================================================= @@ -219,6 +220,8 @@ class VlRandomizer VL_NOT_FINAL { std::vector m_unique_arrays; std::map m_unique_array_sizes; const VlQueue* m_randmodep = nullptr; // rand_mode state; + const VlQueue* m_static_randmodep = nullptr; // Static rand_mode state (shared) + std::unordered_set m_staticVars; // Names of static rand vars int m_index = 0; // Internal counter for key generation std::set m_randcVarNames; // Names of randc variables for cyclic tracking std::map> @@ -647,6 +650,10 @@ public: void solveBefore(const std::string& beforeName, const std::string& afterName); // Register solve-before ordering void set_randmode(const VlQueue& randmode) { m_randmodep = &randmode; } + // Shared across all instances; consulted instead of m_randmodep for vars marked via + // mark_var_static(). + void set_static_randmode(const VlQueue& randmode) { m_static_randmodep = &randmode; } + void mark_var_static(const char* const name) { m_staticVars.insert(name); } #ifdef VL_DEBUG void dump() const; #endif diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 25962098e..69c2f909f 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -834,6 +834,8 @@ public: RANDOMIZER_WRITE_VAR, RANDOMIZER_SET_VAR_DISABLED, RANDOMIZER_CLEAR_VAR_DISABLED, + RANDOMIZER_MARK_VAR_STATIC, + RANDOMIZER_SET_STATIC_RANDMODE, RNG_GET_RANDSTATE, RNG_SET_RANDSTATE, SCHED_ANY_TRIGGERED, @@ -985,6 +987,8 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {RANDOMIZER_WRITE_VAR, "write_var", false}, \ {RANDOMIZER_SET_VAR_DISABLED, "set_var_disabled", false}, \ {RANDOMIZER_CLEAR_VAR_DISABLED, "clear_var_disabled", false}, \ + {RANDOMIZER_MARK_VAR_STATIC, "mark_var_static", false}, \ + {RANDOMIZER_SET_STATIC_RANDMODE, "set_static_randmode", false}, \ {RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \ {RNG_SET_RANDSTATE, "__Vm_rng.set_randstate", false}, \ {SCHED_ANY_TRIGGERED, "anyTriggered", false}, \ diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index e02b9e9fb..d12a3c314 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -469,11 +469,6 @@ class RandomizeMarkVisitor final : public VNVisitor { } else if (nodep->argsp() && !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->lifetime().isStatic() - && randModeTarget.receiverp->isRand()) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: 'rand_mode()' on static variable"); - valid = false; } else if (randModeTarget.receiverp && randModeTarget.receiverp->isRand()) { // Called on a rand member variable RandomizeMode randMode = {}; @@ -483,11 +478,6 @@ class RandomizeMarkVisitor final : public VNVisitor { // Called on 'this' or a non-rand class instance randModeTarget.classp->foreachMember([&](AstClass*, AstVar* varp) { if (!varp->isRand()) return; - if (varp->lifetime().isStatic()) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: 'rand_mode()' on static variable: " - << varp->prettyNameQ()); - } RandomizeMode randMode = {}; randMode.usesMode = true; varp->user1(randMode.asInt); @@ -778,6 +768,19 @@ class ConstraintExprVisitor final : public VNVisitor { std::set m_inlineWrittenVars; // Per-instance tracking for inline constraints std::set* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints + // Routes nested sub-objects with static rand vars when the outer class has none. + AstVar* findStaticRandModeVarMember(AstClass* classp) const { + while (true) { + if (AstVar* const varp + = VN_CAST(m_memberMap.findMember(classp, "__Vstaticrandmode"), Var)) { + return varp; + } + AstClassExtends* const extendsp = classp->extendsp(); + if (!extendsp) return nullptr; + classp = extendsp->classp(); + } + } + // Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4") std::string buildMemberPath(const AstMemberSel* const memberSelp) { const AstNode* fromp = memberSelp->fromp(); @@ -1119,9 +1122,19 @@ class ConstraintExprVisitor final : public VNVisitor { AstNodeExpr* constFormatp = membersel ? getConstFormat(membersel->cloneTree(false)) : getConstFormat(nodep); - // Build randmode access: for membersel, use member's class randmode if available + // Static rand vars route through the var's owning class's static array + // (may differ from m_classp when the rand var lives in a sub-object). AstNodeExpr* randModeAccess; - if (membersel) { + const bool varIsStatic = varp->lifetime().isStatic(); + AstClass* const varOwningClassp + = varIsStatic ? VN_CAST(varp->user2p(), Class) : nullptr; + AstVar* const ownerStaticRandModeVarp + = varOwningClassp ? findStaticRandModeVarMember(varOwningClassp) : nullptr; + if (varIsStatic && ownerStaticRandModeVarp) { + randModeAccess = new AstVarRef{ + varp->fileline(), VN_AS(ownerStaticRandModeVarp->user2p(), NodeModule), + ownerStaticRandModeVarp, VAccess::READ}; + } else if (membersel) { AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule); AstVar* const effectiveRandModeVarp = getRandModeVarFromClass(varClassp); if (effectiveRandModeVarp) { @@ -1385,6 +1398,19 @@ class ConstraintExprVisitor final : public VNVisitor { // evaluation), but toggle disabled state so the solver skips // write-back when rand_mode is off. initTaskp->addStmtsp(methodp->makeStmt()); + if (varp->lifetime().isStatic() && randMode.usesMode) { + AstCMethodHard* const markp = new AstCMethodHard{ + varp->fileline(), + new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), + m_genp, VAccess::READWRITE}, + VCMethod::RANDOMIZER_MARK_VAR_STATIC}; + AstNodeExpr* const namep = new AstCExpr{varp->fileline(), AstCExpr::Pure{}, + "\"" + smtName + "\"", varp->width()}; + namep->dtypep(varp->dtypep()); + markp->addPinsp(namep); + markp->dtypeSetVoid(); + initTaskp->addStmtsp(markp->makeStmt()); + } if (isGlobalConstrained && membersel && randMode.usesMode) { AstNodeModule* const varClassp = VN_AS(varp->user2p(), NodeModule); AstVar* const subRandModeVarp = getRandModeVarFromClass(varClassp); @@ -3043,6 +3069,7 @@ class RandomizeVisitor final : public VNVisitor { std::map> m_sizeConstrainedArrays; // Per-class arrays std::map m_staticConstraintModeVars; // Static constraint mode vars per class + std::map m_staticRandModeVars; // Static rand mode vars per class // METHODS // Check if two nodes are semantically equivalent (not pointer equality): @@ -3218,6 +3245,22 @@ class RandomizeVisitor final : public VNVisitor { } return nullptr; } + AstVar* getCreateStaticRandModeVar(AstClass* const classp) { + if (m_staticRandModeVars.count(classp)) return m_staticRandModeVars[classp]; + if (AstClassExtends* const extendsp = classp->extendsp()) { + return getCreateStaticRandModeVar(extendsp->classp()); + } + AstVar* const staticModeVarp = createStaticModeVar(classp, "__Vstaticrandmode"); + m_staticRandModeVars[classp] = staticModeVarp; + return staticModeVarp; + } + AstVar* getStaticRandModeVar(AstClass* const classp) { + if (m_staticRandModeVars.count(classp)) return m_staticRandModeVars[classp]; + if (AstClassExtends* const extendsp = classp->extendsp()) { + return getStaticRandModeVar(extendsp->classp()); + } + return nullptr; + } AstVar* createModeVar(AstClass* const classp, const char* const name) { FileLine* const fl = classp->fileline(); if (!m_dynarrayDtp) { @@ -3260,10 +3303,25 @@ class RandomizeVisitor final : public VNVisitor { setRandModep->dtypeSetVoid(); ftaskp->addStmtsp(setRandModep->makeStmt()); } + static void addSetStaticRandMode(AstNodeFTask* const ftaskp, AstVar* const genp, + AstVar* const staticRandModeVarp) { + FileLine* const fl = ftaskp->fileline(); + AstCMethodHard* const setp = new AstCMethodHard{ + fl, new AstVarRef{fl, VN_AS(genp->user2p(), NodeModule), genp, VAccess::WRITE}, + VCMethod::RANDOMIZER_SET_STATIC_RANDMODE, + new AstVarRef{fl, VN_AS(staticRandModeVarp->user2p(), NodeModule), staticRandModeVarp, + VAccess::READ}}; + setp->dtypeSetVoid(); + ftaskp->addStmtsp(setp->makeStmt()); + } void createRandomizeClassVars(AstNetlist* const netlistp) { - netlistp->foreach([this](AstClass* const classp) { + // Defer init to one emission per root with max descendant count; + // super.new() runs Base ctor before Derived can resize the static array. + std::map rootStaticRandModeCount; + netlistp->foreach([this, &rootStaticRandModeCount](AstClass* const classp) { bool hasConstraints = false; uint32_t randModeCount = 0; + uint32_t staticRandModeCount = 0; uint32_t constraintModeCount = 0; uint32_t staticConstraintModeCount = 0; classp->foreachMember([&](AstClass*, AstNode* memberp) { @@ -3288,14 +3346,23 @@ class RandomizeVisitor final : public VNVisitor { constraintModeCount = constraintMode.index + 1; } } - } else if (VN_IS(memberp, Var)) { + } else if (AstVar* const varp = VN_CAST(memberp, Var)) { RandomizeMode randMode = {.asInt = memberp->user1()}; if (!randMode.usesMode) return; + const bool isStaticVar = varp->lifetime().isStatic(); if (randMode.index == 0) { - randMode.index = randModeCount++; + if (isStaticVar) { + randMode.index = staticRandModeCount++; + } else { + randMode.index = randModeCount++; + } memberp->user1(randMode.asInt); } else { - randModeCount = randMode.index + 1; + if (isStaticVar) { + staticRandModeCount = randMode.index + 1; + } else { + randModeCount = randMode.index + 1; + } } } }); @@ -3309,6 +3376,9 @@ class RandomizeVisitor final : public VNVisitor { if (AstVar* const subVarp = VN_CAST(subMemberp, Var)) { const RandomizeMode rm = {.asInt = subVarp->user1()}; if (!rm.usesMode) return; + // Static rand vars index into their own class's static + // rand mode array, not into the outer __Vrandmode. + if (subVarp->lifetime().isStatic()) return; const uint32_t needed = rm.index + 1; if (needed > randModeCount) randModeCount = needed; } @@ -3335,7 +3405,20 @@ class RandomizeVisitor final : public VNVisitor { AstVar* const staticConstraintModeVarp = getCreateStaticConstraintModeVar(classp); makeStaticModeInit(staticConstraintModeVarp, classp, staticConstraintModeCount); } + if (staticRandModeCount > 0) { + getCreateStaticRandModeVar(classp); + AstClass* rootp = classp; + while (AstClassExtends* const ep = rootp->extendsp()) rootp = ep->classp(); + uint32_t& slot = rootStaticRandModeCount[rootp]; + if (staticRandModeCount > slot) slot = staticRandModeCount; + } }); + for (const auto& kv : rootStaticRandModeCount) emitRootStaticModeInit(kv.first, kv.second); + } + void emitRootStaticModeInit(AstClass* const rootp, const uint32_t count) { + AstVar* const staticRandModeVarp = m_staticRandModeVars[rootp]; + UASSERT_OBJ(staticRandModeVarp, rootp, "Root must have a static rand-mode var"); + makeStaticModeInit(staticRandModeVarp, rootp, count); } void makeModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) { AstNodeModule* const modeVarModp = VN_AS(modeVarp->user2p(), NodeModule); @@ -3423,9 +3506,11 @@ class RandomizeVisitor final : public VNVisitor { new AstAdd{fl, new AstConst{fl, 1}, new AstVarRef{fl, iterVarp, VAccess::READ}}}); return new AstBegin{fl, "", stmtsp, true}; } - static AstNodeStmt* wrapIfRandMode(AstClass* classp, AstVar* const varp, AstNodeStmt* stmtp) { + AstNodeStmt* wrapIfRandMode(AstClass* classp, AstVar* const varp, AstNodeStmt* stmtp) { const RandomizeMode rmode = {.asInt = varp->user1()}; - return VN_AS(wrapIfMode(rmode, getRandModeVarFromClass(classp), stmtp), NodeStmt); + AstVar* const modeVarp = varp->lifetime().isStatic() ? getStaticRandModeVar(classp) + : getRandModeVarFromClass(classp); + return VN_AS(wrapIfMode(rmode, modeVarp, stmtp), NodeStmt); } AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, AstNode* stmtp) { const RandomizeMode rmode = {.asInt = constrp->user1()}; @@ -3794,17 +3879,23 @@ class RandomizeVisitor final : public VNVisitor { exprp->v3fatalSrc("Not a MemberSel nor VarRef"); return nullptr; // LCOV_EXCL_LINE } - AstNodeExpr* makeSiblingRefp(AstNodeExpr* const exprp, AstVar* const varp, - const VAccess access) { + // Build a reference to a rand_mode/constraint_mode dyn-array. + // Static-mode vars live on the class package; V3Scope resolves the VarRef later. + AstNodeExpr* makeModeVarRef(AstNodeExpr* const exprp, AstVar* const modeVarp, + const VAccess access) { + if (modeVarp->lifetime().isStatic()) { + return new AstVarRef{exprp->fileline(), VN_AS(modeVarp->user2p(), NodeModule), + modeVarp, access}; + } if (AstMemberSel* const memberSelp = VN_CAST(exprp, MemberSel)) { - AstMemberSel* const newMemberSelp - = new AstMemberSel{exprp->fileline(), memberSelp->fromp()->cloneTree(false), varp}; - // Set access on all VarRef nodes in the cloned subtree + AstMemberSel* const newMemberSelp = new AstMemberSel{ + exprp->fileline(), memberSelp->fromp()->cloneTree(false), modeVarp}; newMemberSelp->foreach([access](AstVarRef* varrefp) { varrefp->access(access); }); return newMemberSelp; } UASSERT_OBJ(VN_IS(exprp, VarRef), exprp, "Should be a VarRef"); - return new AstVarRef{exprp->fileline(), VN_AS(varp->user2p(), Class), varp, access}; + return new AstVarRef{exprp->fileline(), VN_AS(modeVarp->user2p(), Class), modeVarp, + access}; } // Get or create a size variable for a constrained dynamic/queue/assoc array. // Returns the size variable. Sets wasCreated=true if a new variable was made. @@ -3850,14 +3941,14 @@ class RandomizeVisitor final : public VNVisitor { storeStmtspr = AstNode::addNext( storeStmtspr, new AstAssign{fl, new AstVarRef{fl, randModeTmpVarp, VAccess::WRITE}, - makeSiblingRefp(siblingExprp, randModeVarp, VAccess::READ)}); + makeModeVarRef(siblingExprp, randModeVarp, VAccess::READ)}); storeStmtspr = AstNode::addNext( storeStmtspr, - makeModeSetLoop(fl, makeSiblingRefp(siblingExprp, randModeVarp, VAccess::WRITE), + makeModeSetLoop(fl, makeModeVarRef(siblingExprp, randModeVarp, VAccess::WRITE), new AstConst{fl, 0}, m_ftaskp)); restoreStmtspr = AstNode::addNext( restoreStmtspr, - new AstAssign{fl, makeSiblingRefp(siblingExprp, randModeVarp, VAccess::WRITE), + new AstAssign{fl, makeModeVarRef(siblingExprp, randModeVarp, VAccess::WRITE), new AstVarRef{fl, randModeTmpVarp, VAccess::READ}}); return randModeTmpVarp; } @@ -3980,9 +4071,10 @@ class RandomizeVisitor final : public VNVisitor { // Generate VarRef with classp as module; V3Scope will update varScopep later // when the variable is moved to the class package. if (modeVarp->lifetime().isStatic()) { - // Static mode var - generate VarRef that will be resolved by V3Scope + // Hint owning class so V3Scope resolves from derived call sites. if (fromp) VL_DO_DANGLING(fromp->unlinkFrBack()->deleteTree(), fromp); - return new AstVarRef{fl, classp, modeVarp, VAccess::WRITE}; + return new AstVarRef{fl, VN_AS(modeVarp->user2p(), NodeModule), modeVarp, + VAccess::WRITE}; } else if (classp == m_modp) { // Called on 'this' or a member of 'this' return new AstVarRef{fl, VN_AS(modeVarp->user2p(), NodeModule), modeVarp, @@ -3996,10 +4088,16 @@ class RandomizeVisitor final : public VNVisitor { // Replace the node with an assignment to the mode variable. Called by visit(AstNodeFTaskRef*) void replaceWithModeAssign(AstNodeFTaskRef* const ftaskRefp, AstNode* const receiverp, AstNodeExpr* const lhsp) { + replaceWithModeAssignAndAppend(ftaskRefp, receiverp, lhsp, nullptr); + } + // Append BEFORE swap; backp()/nextp() unreliable after replaceWith. + void replaceWithModeAssignAndAppend(AstNodeFTaskRef* const ftaskRefp, AstNode* const receiverp, + AstNodeExpr* const lhsp, AstNode* const appendStmtp) { FileLine* const fl = ftaskRefp->fileline(); if (ftaskRefp->argsp()) { UASSERT_OBJ(VN_IS(ftaskRefp->backp(), StmtExpr), ftaskRefp, "Should be a statement"); AstNodeExpr* const rhsp = ftaskRefp->argsp()->exprp()->unlinkFrBack(); + AstNode* newStmtp = nullptr; if (receiverp) { // Called on a rand member variable/constraint. Set the variable/constraint's // mode @@ -4008,16 +4106,19 @@ class RandomizeVisitor final : public VNVisitor { AstCMethodHard* const setp = new AstCMethodHard{fl, lhsp, VCMethod::ARRAY_AT_WRITE, new AstConst{fl, rmode.index}}; setp->dtypeSetUInt32(); - m_stmtp->replaceWith(new AstAssign{fl, setp, rhsp}); + newStmtp = new AstAssign{fl, setp, rhsp}; } else { // For rand_mode: Called on 'this' or a non-rand class instance. // For constraint_mode: Called on a class instance. // Set the rand mode of all members - m_stmtp->replaceWith(makeModeSetLoop(fl, lhsp, rhsp, m_ftaskp)); + newStmtp = makeModeSetLoop(fl, lhsp, rhsp, m_ftaskp); } + if (appendStmtp) newStmtp->addNext(appendStmtp); + m_stmtp->replaceWith(newStmtp); pushDeletep(m_stmtp); } else { UASSERT_OBJ(receiverp, ftaskRefp, "Should have receiver"); + UASSERT_OBJ(!appendStmtp, ftaskRefp, "Append path requires arg-form rand_mode"); const RandomizeMode rmode = {.asInt = receiverp->user1()}; UASSERT_OBJ(rmode.usesMode, ftaskRefp, "Failed to set usesMode"); AstCMethodHard* const setp = new AstCMethodHard{fl, lhsp, VCMethod::ARRAY_AT_WRITE, @@ -4106,7 +4207,11 @@ class RandomizeVisitor final : public VNVisitor { if (commonPrefixp == exprp) break; AstVar* const randVarp = getVarFromRef(exprp); AstClass* const classp = VN_AS(randVarp->user2p(), Class); - AstVar* const randModeVarp = getRandModeVarFromClass(classp); + AstVar* const randModeVarp = randVarp->lifetime().isStatic() + ? getStaticRandModeVar(classp) + : getRandModeVarFromClass(classp); + UASSERT_OBJ(randModeVarp, randVarp, + "Rand var with rand_mode must have a mode array"); if (savedRandModeVarps.find(randModeVarp) == savedRandModeVarps.end()) { AstVar* const randModeTmpVarp = makeTmpRandModeVar(exprp, randModeVarp, storeStmtsp, restoreStmtsp); @@ -4115,7 +4220,7 @@ class RandomizeVisitor final : public VNVisitor { } const RandomizeMode randMode = {.asInt = randVarp->user1()}; AstCMethodHard* setp = new AstCMethodHard{ - fl, makeSiblingRefp(exprp, randModeVarp, VAccess::WRITE), + fl, makeModeVarRef(exprp, randModeVarp, VAccess::WRITE), VCMethod::ARRAY_AT_WRITE, new AstConst{fl, randMode.index}}; setp->dtypeSetUInt32(); setStmtsp @@ -4737,6 +4842,12 @@ class RandomizeVisitor final : public VNVisitor { UASSERT_OBJ(newp, randModeClassp, "No new() in class"); addSetRandMode(newp, genp, randModeVarp); } + if (AstVar* const staticRandModeVarp = getStaticRandModeVar(nodep)) { + // Wire the shared static rand_mode queue into the class generator. + AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(nodep, "new"), NodeFTask); + UASSERT_OBJ(newp, nodep, "No new() in class"); + addSetStaticRandMode(newp, genp, staticRandModeVarp); + } } else { beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1}; } @@ -4842,13 +4953,36 @@ class RandomizeVisitor final : public VNVisitor { UASSERT_OBJ(randModeTarget.classp, nodep, "Should have checked in RandomizeMarkVisitor"); AstVar* const receiverp = randModeTarget.receiverp; - AstVar* const randModeVarp = getRandModeVarFromClass(randModeTarget.classp); + const bool isClassLevel = !(receiverp && receiverp->rand().isRand()); + // Class-level rand_mode(N) must also flush the shared static array. + AstNode* classLevelStaticLoopp = nullptr; + if (isClassLevel && nodep->argsp()) { + if (AstVar* const sVarp = getStaticRandModeVar(randModeTarget.classp)) { + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const staticLhsp + = makeModeAssignLhs(fl, randModeTarget.classp, nullptr, sVarp); + AstNodeExpr* const argClonep = nodep->argsp()->exprp()->cloneTreePure(false); + classLevelStaticLoopp = makeModeSetLoop(fl, staticLhsp, argClonep, m_ftaskp); + } + } + const bool receiverIsStaticRand + = receiverp && receiverp->rand().isRand() && receiverp->lifetime().isStatic(); + AstVar* const randModeVarp = receiverIsStaticRand + ? getStaticRandModeVar(randModeTarget.classp) + : getRandModeVarFromClass(randModeTarget.classp); + if (!randModeVarp) { + UASSERT_OBJ(isClassLevel && classLevelStaticLoopp, nodep, + "Per-instance rand_mode var missing without static fallback"); + UASSERT_OBJ(VN_IS(nodep->backp(), StmtExpr), nodep, "Should be a statement"); + m_stmtp->replaceWith(classLevelStaticLoopp); + pushDeletep(m_stmtp); + return; + } AstNodeExpr* const lhsp = makeModeAssignLhs(nodep->fileline(), randModeTarget.classp, randModeTarget.fromp, randModeVarp); - replaceWithModeAssign(nodep, - // If the receiver is not rand, set the rand_mode for all members - receiverp && receiverp->rand().isRand() ? receiverp : nullptr, - lhsp); + replaceWithModeAssignAndAppend( + nodep, receiverp && receiverp->rand().isRand() ? receiverp : nullptr, lhsp, + classLevelStaticLoopp); return; } @@ -5092,6 +5226,11 @@ class RandomizeVisitor final : public VNVisitor { // Set rand mode if present (not needed if classGenp exists and was copied) AstVar* const randModeVarp = getRandModeVarFromClass(classp); if (!classGenp && randModeVarp) addSetRandMode(randomizeFuncp, localGenp, randModeVarp); + if (!classGenp) { + if (AstVar* const sVarp = getStaticRandModeVar(classp)) { + addSetStaticRandMode(randomizeFuncp, localGenp, sVarp); + } + } // Generate constraint setup code and a hardcoded call to the solver AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); diff --git a/test_regress/t/t_randomize_rand_mode_static.py b/test_regress/t/t_randomize_rand_mode_static.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_static.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# 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-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randomize_rand_mode_static.v b/test_regress/t/t_randomize_rand_mode_static.v new file mode 100644 index 000000000..ba479b3a0 --- /dev/null +++ b/test_regress/t/t_randomize_rand_mode_static.v @@ -0,0 +1,296 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +`define check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +// verilog_format: on + +// Tests rand_mode() support for `static rand` class members per IEEE 1800-2023 +// Section 18.5.10 and Section 18.4. Static rand_mode state is shared across all +// instances of a class. + +class Simple; + static rand int sx; + rand int dy; + constraint c { + sx > 0; + sx < 12; + dy > 100; + dy < 200; + } +endclass + +class Base; + static rand bit [3:0] base_sx; + rand bit [3:0] base_dy; + constraint base_c { + base_sx > 0; + base_dy > 0; + } +endclass + +class Derived extends Base; + rand bit [3:0] der_dy; + constraint der_c {der_dy > 0;} +endclass + +// Class with ONLY static rand members; exercises class-level rand_mode() with +// no per-instance mode array. +class StaticOnly; + static rand bit [3:0] sa; + static rand bit [3:0] sb; + constraint c { + sa > 0; + sb > 0; + } +endclass + +// Base with two static rand vars exercises non-zero index in the shared static array. +class BaseTwo; + static rand bit [3:0] base2_a; + static rand bit [3:0] base2_b; + rand bit [3:0] base2_dy; + constraint c { + base2_a > 0; + base2_b > 0; + base2_dy > 0; + } +endclass + +class DerivedTwo extends BaseTwo; + rand bit [3:0] der2_dy; + constraint dc {der2_dy > 0;} +endclass + +// No constraint blocks: inline randomize-with must still flush static rand_mode. +class StaticNoConstraint; + static rand bit [3:0] snc_s; + rand bit [3:0] snc_d; +endclass + +// Base + Derived each declare a static rand var; per-root max-count init must size for both. +class BaseS; + static rand bit [3:0] base_s; + constraint c {base_s > 0;} +endclass + +class DerivedS extends BaseS; + static rand bit [3:0] der_s; + constraint c2 {der_s > 0;} +endclass + +module t; + Simple s1, s2; + Derived d1, d2; + StaticOnly so1, so2; + BaseS bs1; + DerivedS ds1, ds2; + DerivedTwo dt1; + StaticNoConstraint snc1; + int saved_sx, saved_dy, rok; + bit [3:0] saved_base_sx, saved_base_s, saved_der_s, saved_base2_b; + + initial begin + s1 = new; + s2 = new; + + // ---- Test 1: getter on static rand var (initial state is enabled) + `checkd(s1.sx.rand_mode(), 1); + `checkd(s2.sx.rand_mode(), 1); + + // ---- Test 2: randomize() with all rand_modes enabled satisfies constraints + repeat (20) begin + rok = s1.randomize(); + `checkd(rok, 1); + `check_range(Simple::sx, 1, 11); + `check_range(s1.dy, 101, 199); + end + + // ---- Test 3: per-member set on a static rand var is shared across instances + s1.sx.rand_mode(0); + `checkd(s1.sx.rand_mode(), 0); + `checkd(s2.sx.rand_mode(), 0); + + // Non-static dy stays independent on each instance + `checkd(s1.dy.rand_mode(), 1); + `checkd(s2.dy.rand_mode(), 1); + + // ---- Test 4: solver respects rand_mode(0) on static rand var. + // sx is currently a valid value from Test 2; with rand_mode disabled it + // must stay at that value across multiple randomize() calls. + saved_sx = Simple::sx; + repeat (20) begin + rok = s1.randomize(); + `checkd(rok, 1); + `checkd(Simple::sx, saved_sx); + `check_range(s1.dy, 101, 199); + end + + // Re-enable rand_mode for sx via the OTHER instance (sharing test). + s2.sx.rand_mode(1); + `checkd(s1.sx.rand_mode(), 1); + + repeat (20) begin + rok = s1.randomize(); + `checkd(rok, 1); + `check_range(Simple::sx, 1, 11); + `check_range(s1.dy, 101, 199); + end + + // ---- Test 5: class-level obj.rand_mode(0) flushes both per-instance + // and static arrays. Disable everything on s1. + s1.rand_mode(0); + `checkd(s1.sx.rand_mode(), 0); + `checkd(s1.dy.rand_mode(), 0); + // Static sx is shared, so s2 sees it disabled too. + `checkd(s2.sx.rand_mode(), 0); + // Non-static dy on s2 is independent of s1's class-level call. + `checkd(s2.dy.rand_mode(), 1); + + // Re-randomize s1 with everything off - both fields unchanged. + saved_sx = Simple::sx; + saved_dy = s1.dy; + repeat (10) begin + rok = s1.randomize(); + `checkd(rok, 1); + `checkd(Simple::sx, saved_sx); + `checkd(s1.dy, saved_dy); + end + + // Class-level enable resets both arrays to 1. + s1.rand_mode(1); + `checkd(s1.sx.rand_mode(), 1); + `checkd(s1.dy.rand_mode(), 1); + `checkd(s2.sx.rand_mode(), 1); + + // ---- Test 6: inheritance - static rand var declared in base class, + // accessed via a derived-class instance. + d1 = new; + d2 = new; + + `checkd(d1.base_sx.rand_mode(), 1); + + // Randomize first so base_sx satisfies its constraint, then disable it. + rok = d1.randomize(); + `checkd(rok, 1); + if (Base::base_sx == 0) $stop; + + d1.base_sx.rand_mode(0); + `checkd(d1.base_sx.rand_mode(), 0); + `checkd(d2.base_sx.rand_mode(), 0); // Shared via base class + + // Derived class member dy is non-static, independent. + `checkd(d1.der_dy.rand_mode(), 1); + `checkd(d2.der_dy.rand_mode(), 1); + + saved_base_sx = Base::base_sx; + repeat (20) begin + rok = d1.randomize(); + `checkd(rok, 1); + `checkd(Base::base_sx, saved_base_sx); // disabled - unchanged + if (d1.der_dy == 0) $stop; + if (d1.base_dy == 0) $stop; + end + + // ---- Test 7: class-level rand_mode(N) on a class with ONLY static rand members. + so1 = new; + so2 = new; + `checkd(so1.sa.rand_mode(), 1); + `checkd(so1.sb.rand_mode(), 1); + so1.rand_mode(0); // must not crash + `checkd(so1.sa.rand_mode(), 0); + `checkd(so1.sb.rand_mode(), 0); + `checkd(so2.sa.rand_mode(), 0); // shared + `checkd(so2.sb.rand_mode(), 0); // shared + so2.rand_mode(1); + `checkd(so1.sa.rand_mode(), 1); + `checkd(so1.sb.rand_mode(), 1); + + // ---- Test 8: inline obj.randomize(static_var) save/restore. + // The inline form must route through the static rand_mode array, not + // the per-instance one (whose index space is different / smaller). + s1.sx.rand_mode(1); + s1.dy.rand_mode(1); + repeat (10) begin + rok = s1.randomize(sx); // only sx is randomized, dy frozen + `checkd(rok, 1); + `check_range(Simple::sx, 1, 11); + end + // After the inline call, s1.sx.rand_mode() must be back to 1 + // (the save/restore restores the static array). + `checkd(s1.sx.rand_mode(), 1); + `checkd(s2.sx.rand_mode(), 1); // shared - also 1 + + // ---- Test 9: Base AND Derived each declare own static rand var. + // Derived's static array must be sized to fit BOTH base_s and der_s + // even though super.new() runs Base's init first. + ds1 = new; + ds2 = new; + `checkd(ds1.base_s.rand_mode(), 1); + `checkd(ds1.der_s.rand_mode(), 1); + rok = ds1.randomize(); + `checkd(rok, 1); + if (BaseS::base_s == 0) $stop; + if (DerivedS::der_s == 0) $stop; + + // Disable both via per-member call + ds1.base_s.rand_mode(0); + ds1.der_s.rand_mode(0); + `checkd(ds2.base_s.rand_mode(), 0); // shared + `checkd(ds2.der_s.rand_mode(), 0); // shared + saved_base_s = BaseS::base_s; + saved_der_s = DerivedS::der_s; + repeat (10) begin + rok = ds1.randomize(); + `checkd(rok, 1); + `checkd(BaseS::base_s, saved_base_s); + `checkd(DerivedS::der_s, saved_der_s); + end + + // Construct a standalone BaseS AFTER DerivedS already initialized the + // static array; BaseS init must see size != 0 and skip without + // overwriting Derived's prior rand_mode(0) state. + bs1 = new; + `checkd(bs1.base_s.rand_mode(), 0); // still disabled + + // ---- Test 10: two static rand vars in Base; Derived must accumulate inherited indices. + dt1 = new; + `checkd(dt1.base2_a.rand_mode(), 1); + `checkd(dt1.base2_b.rand_mode(), 1); + rok = dt1.randomize(); + `checkd(rok, 1); + if (BaseTwo::base2_a == 0) $stop; + if (BaseTwo::base2_b == 0) $stop; + // Disable second static var to prove its index is reachable in the array. + dt1.base2_b.rand_mode(0); + `checkd(dt1.base2_b.rand_mode(), 0); + `checkd(dt1.base2_a.rand_mode(), 1); // first still enabled + saved_base2_b = BaseTwo::base2_b; + repeat (10) begin + rok = dt1.randomize(); + `checkd(rok, 1); + `checkd(BaseTwo::base2_b, saved_base2_b); // disabled - unchanged + if (BaseTwo::base2_a == 0) $stop; // still randomizing + end + + // ---- Test 11: inline randomize-with on class with static rand and no class-level constraints. + snc1 = new; + snc1.snc_s.rand_mode(1); // ensure static rand-mode array exists + repeat (10) begin + rok = snc1.randomize() with { + snc_d > 5; + snc_d < 13; + }; + `checkd(rok, 1); + `check_range(snc1.snc_d, 6, 12); + end + + $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 index e894388b8..900a92d4e 100644 --- a/test_regress/t/t_randomize_rand_mode_unsup.out +++ b/test_regress/t/t_randomize_rand_mode_unsup.out @@ -1,26 +1,14 @@ -%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:17:20: Unsupported: 'rand_mode()' on dynamic array element +%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:16:20: Unsupported: 'rand_mode()' on dynamic array element : ... note: In instance 't' - 17 | p.m_dyn_arr[0].rand_mode(0); + 16 | 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:18:20: Unsupported: 'rand_mode()' on unpacked array element +%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:17:20: Unsupported: 'rand_mode()' on unpacked array element : ... note: In instance 't' - 18 | p.m_unp_arr[0].rand_mode(0); + 17 | p.m_unp_arr[0].rand_mode(0); | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:19:18: Unsupported: 'rand_mode()' on unpacked struct element +%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:18:18: Unsupported: 'rand_mode()' on unpacked struct element : ... note: In instance 't' - 19 | p.m_struct.y.rand_mode(0); + 18 | p.m_struct.y.rand_mode(0); | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:20:16: Unsupported: 'rand_mode()' on static variable - : ... note: In instance 't' - 20 | p.m_static.rand_mode(0); - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:21:55: Unsupported: 'rand_mode()' on static variable - : ... note: In instance 't' - 21 | $display("p.m_static.rand_mode()=%0d", p.m_static.rand_mode()); - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_randomize_rand_mode_unsup.v:22:7: Unsupported: 'rand_mode()' on static variable: 'm_static' - : ... note: In instance 't' - 22 | p.rand_mode(0); - | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_randomize_rand_mode_unsup.v b/test_regress/t/t_randomize_rand_mode_unsup.v index 0e66b1c98..df4a88275 100644 --- a/test_regress/t/t_randomize_rand_mode_unsup.v +++ b/test_regress/t/t_randomize_rand_mode_unsup.v @@ -8,7 +8,6 @@ class Packet; rand int m_dyn_arr[]; rand int m_unp_arr[10]; rand struct {int y;} m_struct; - static rand int m_static; endclass module t; @@ -17,8 +16,5 @@ module t; p.m_dyn_arr[0].rand_mode(0); p.m_unp_arr[0].rand_mode(0); p.m_struct.y.rand_mode(0); - p.m_static.rand_mode(0); - $display("p.m_static.rand_mode()=%0d", p.m_static.rand_mode()); - p.rand_mode(0); end endmodule