From 0853aa751527162b6ea1c34cc32afce5e4043d93 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 5 Nov 2025 13:14:03 +0100 Subject: [PATCH] Support basic global constraints (#6551) (#6552) --- src/V3AstNodeOther.h | 7 +- src/V3Randomize.cpp | 322 ++++++++++++++++-- .../t/t_constraint_global_arr_unsup.out | 26 ++ .../t/t_constraint_global_arr_unsup.py | 16 + .../t/t_constraint_global_arr_unsup.v | 86 +++++ .../t/t_constraint_global_nested_unsup.out | 6 + .../t/t_constraint_global_nested_unsup.py | 16 + .../t/t_constraint_global_nested_unsup.v | 51 +++ test_regress/t/t_constraint_global_random.py | 21 ++ test_regress/t/t_constraint_global_random.v | 124 +++++++ .../t/t_randomize_method_types_unsup.out | 3 - 11 files changed, 644 insertions(+), 34 deletions(-) create mode 100644 test_regress/t/t_constraint_global_arr_unsup.out create mode 100755 test_regress/t/t_constraint_global_arr_unsup.py create mode 100755 test_regress/t/t_constraint_global_arr_unsup.v create mode 100644 test_regress/t/t_constraint_global_nested_unsup.out create mode 100755 test_regress/t/t_constraint_global_nested_unsup.py create mode 100755 test_regress/t/t_constraint_global_nested_unsup.v create mode 100755 test_regress/t/t_constraint_global_random.py create mode 100755 test_regress/t/t_constraint_global_random.v diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index be568f2d6..8c8b69cca 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -941,6 +941,7 @@ public: void dump(std::ostream& str) const override; void dumpJson(std::ostream& str) const override; string name() const override VL_MT_STABLE { return m_name; } // * = Scope name + void name(const string& name) override { m_name = name; } // * = Scope name bool isGateOptimizable() const override { return false; } bool isPredictOptimizable() const override { return false; } bool maybePointedTo() const override VL_MT_SAFE { return true; } @@ -1871,7 +1872,7 @@ class AstVar final : public AstNode { bool m_ignorePostWrite : 1; // Ignore writes in 'Post' blocks during ordering bool m_ignoreSchedWrite : 1; // Ignore writes in scheduling (for special optimizations) bool m_dfgMultidriven : 1; // Singal is multidriven, used by DFG to avoid repeat processing - + bool m_globalConstrained : 1; // Global constraint per IEEE 1800-2023 18.5.8 void init() { m_ansi = false; m_declTyped = false; @@ -1921,6 +1922,7 @@ class AstVar final : public AstNode { m_ignorePostWrite = false; m_ignoreSchedWrite = false; m_dfgMultidriven = false; + m_globalConstrained = false; } public: @@ -2085,7 +2087,8 @@ public: void setIgnoreSchedWrite() { m_ignoreSchedWrite = true; } bool dfgMultidriven() const { return m_dfgMultidriven; } void setDfgMultidriven() { m_dfgMultidriven = true; } - + void globalConstrained(bool flag) { m_globalConstrained = flag; } + bool globalConstrained() const { return m_globalConstrained; } // METHODS void name(const string& name) override { m_name = name; } void tag(const string& text) override { m_tag = text; } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 04e11b48a..514b402e3 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -54,10 +54,17 @@ VL_DEFINE_DEBUG_FUNCTIONS; enum ClassRandom : uint8_t { NONE, // randomize() is not called IS_RANDOMIZED, // randomize() is called + IS_RANDOMIZED_GLOBAL, // randomize() is called with global constraints IS_RANDOMIZED_INLINE, // randomize() with args is called IS_STD_RANDOMIZED, // std::randomize() is called }; +// ###################################################################### +// Constants for global constraint processing + +static constexpr const char* GLOBAL_CONSTRAINT_SEPARATOR = "__DT__"; +static constexpr const char* BASIC_RANDOMIZE_FUNC_NAME = "__VBasicRand"; + // ###################################################################### // Establishes the target of a rand_mode() call @@ -138,6 +145,9 @@ class RandomizeMarkVisitor final : public VNVisitor { AstNodeModule* m_modp; // Current module AstNodeStmt* m_stmtp = nullptr; // Current statement std::set m_staticRefs; // References to static variables under `with` clauses + AstWith* m_withp = nullptr; // Current 'with' constraint node + std::vector m_clonedConstraints; // List of cloned global constraints + std::unordered_set m_processedVars; // Track by variable instance, not class // METHODS void markMembers(const AstClass* nodep) { @@ -196,18 +206,145 @@ class RandomizeMarkVisitor final : public VNVisitor { staticRefp->classOrPackagep(VN_AS(staticRefp->varp()->user2p(), NodeModule)); } } + void markNestedGlobalConstrainedRecurse(AstNode* nodep) { + if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { + AstVar* const varp = refp->varp(); + if (varp->globalConstrained()) return; + varp->globalConstrained(true); + } else if (const AstMemberSel* const memberSelp = VN_CAST(nodep, MemberSel)) { + if (memberSelp->varp()) { + AstVar* const varp = memberSelp->varp(); + if (varp->globalConstrained()) return; + varp->globalConstrained(true); + } + markNestedGlobalConstrainedRecurse(memberSelp->fromp()); + } + } + + // Build MemberSel chain from variable path + AstNodeExpr* buildMemberSelChain(AstVarRef* rootVarRefp, const std::vector& path) { + AstNodeExpr* exprp = rootVarRefp->cloneTree(false); + for (AstVar* memberVarp : path) { + AstMemberSel* memberSelp + = new AstMemberSel{rootVarRefp->fileline(), exprp, memberVarp}; + memberSelp->user2p(m_classp); + exprp = memberSelp; + } + return exprp; + } + + // Process a single constraint during nested constraint cloning + void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp, + const std::vector& newPath) { + std::string pathPrefix = rootVarRefp->name(); + for (AstVar* pathMemberVarp : newPath) { + pathPrefix += GLOBAL_CONSTRAINT_SEPARATOR + pathMemberVarp->name(); + } + + const std::string newName = pathPrefix + GLOBAL_CONSTRAINT_SEPARATOR + constrp->name(); + + for (const AstConstraint* existingConstrp : m_clonedConstraints) { + if (existingConstrp->name() == newName) { + // Multiple paths lead to same constraint - unsupported pattern + std::string fullPath = rootVarRefp->name(); + for (AstVar* pathVar : newPath) { fullPath += "." + pathVar->name(); } + constrp->v3warn(E_UNSUPPORTED, "Unsupported: One variable '" + << fullPath + << "' cannot have multiple global constraints"); + return; + } + } + + AstConstraint* const cloneConstrp = constrp->cloneTree(false); + cloneConstrp->name(newName); + cloneConstrp->foreach([&](AstVarRef* varRefp) { + AstNodeExpr* const chainp = buildMemberSelChain(rootVarRefp, newPath); + AstMemberSel* const finalSelp + = new AstMemberSel{varRefp->fileline(), chainp, varRefp->varp()}; + finalSelp->user2p(m_classp); + varRefp->replaceWith(finalSelp); + VL_DO_DANGLING(varRefp->deleteTree(), varRefp); + }); + + m_clonedConstraints.push_back(cloneConstrp); + } + + // Clone constraints from nested rand class members + void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp, + const std::vector& pathToClass) { + for (AstNode* memberNodep = classp->membersp(); memberNodep; + memberNodep = memberNodep->nextp()) { + AstVar* const memberVarp = VN_CAST(memberNodep, Var); + if (!memberVarp) continue; + if (!memberVarp->rand().isRandomizable()) continue; + const AstClassRefDType* const memberClassRefp + = VN_CAST(memberVarp->dtypep()->skipRefp(), ClassRefDType); + if (!memberClassRefp || !memberClassRefp->classp()) continue; + + AstClass* nestedClassp = memberClassRefp->classp(); + + std::vector newPath = pathToClass; + newPath.push_back(memberVarp); + // Replace all variable references inside the cloned constraint with proper + // member selections + nestedClassp->foreachMember( + [&](AstClass* const containingClassp, AstConstraint* const constrp) { + processNestedConstraint(constrp, rootVarRefp, newPath); + }); + + cloneNestedConstraintsRecurse(rootVarRefp, nestedClassp, newPath); + } + } + + void cloneNestedConstraints(AstVarRef* rootVarRefp, AstClass* rootClass) { + std::vector emptyPath; + cloneNestedConstraintsRecurse(rootVarRefp, rootClass, emptyPath); + } + + void nameManipulation(AstVarRef* fromp, AstConstraint* cloneCons) { + cloneCons->name(fromp->name() + GLOBAL_CONSTRAINT_SEPARATOR + cloneCons->name()); + cloneCons->foreach([&](AstVarRef* varRefp) { + AstVarRef* const clonedFromp = fromp->cloneTree(false); + AstMemberSel* const varMemberp + = new AstMemberSel{cloneCons->fileline(), clonedFromp, varRefp->varp()}; + varMemberp->user2p(m_classp); + varRefp->replaceWith(varMemberp); + VL_DO_DANGLING(varRefp->deleteTree(), varRefp); + }); + } + + // Process a globally constrained variable by cloning its constraints + void processGlobalConstraint(AstVarRef* varRefp, AstClass* gConsClass) { + AstVar* const objVar = varRefp->varp(); + + // Process per-variable (object instance), not per-class + // This allows multiple objects of the same class (e.g., obj1 and obj2 of type Sub) + if (m_processedVars.insert(objVar).second) { + // Clone constraints from the top-level class (e.g., Level1 for obj_a) + gConsClass->foreachMember([&](AstClass* const classp, AstConstraint* const constrp) { + AstConstraint* const cloneConstrp = constrp->cloneTree(false); + nameManipulation(varRefp, cloneConstrp); + m_clonedConstraints.push_back(cloneConstrp); + }); + + cloneNestedConstraints(varRefp, gConsClass); + } + } // VISITORS void visit(AstClass* nodep) override { VL_RESTORER(m_classp); VL_RESTORER(m_modp); + VL_RESTORER(m_clonedConstraints); m_modp = m_classp = nodep; iterateChildrenConst(nodep); if (nodep->extendsp()) { - // Save pointer to derived class + // Record derived class for inheritance hierarchy tracking const AstClass* const basep = nodep->extendsp()->classp(); m_baseToDerivedMap[basep].insert(nodep); } + for (AstConstraint* const constrp : m_clonedConstraints) m_classp->addStmtsp(constrp); + m_clonedConstraints.clear(); } void visit(AstNodeStmt* nodep) override { VL_RESTORER(m_stmtp); @@ -464,7 +601,51 @@ class RandomizeMarkVisitor final : public VNVisitor { // of type AstLambdaArgRef. They are randomized too. const bool randObject = nodep->fromp()->user1() || VN_IS(nodep->fromp(), LambdaArgRef); nodep->user1(randObject && nodep->varp()->rand().isRandomizable()); - nodep->user2p(m_modp); + + if (m_withp) { + AstNode* backp = m_withp; + while (backp->backp()) { + if (const AstMethodCall* const callp = VN_CAST(backp, MethodCall)) { + AstClassRefDType* classdtype + = VN_AS(callp->fromp()->dtypep()->skipRefp(), ClassRefDType); + nodep->user2p(classdtype->classp()); + break; + } + backp = backp->backp(); + } + } else { + nodep->user2p(m_modp); + } + if (randObject && nodep->varp() + && nodep->varp()->rand().isRandomizable()) { // Process global constraints + if (m_classp && m_classp->user1() == IS_RANDOMIZED) { + m_classp->user1(IS_RANDOMIZED_GLOBAL); + } + // Mark the entire nested chain as participating in global constraints + if (VN_IS(nodep->fromp(), VarRef) || VN_IS(nodep->fromp(), MemberSel)) { + markNestedGlobalConstrainedRecurse(nodep->fromp()); + } else if (VN_IS(nodep->fromp(), ArraySel)) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: " << nodep->prettyTypeName() + << " within a global constraint"); + } + // Global constraint processing algorithm: + // 1. Detect globally constrained object variables in randomized classes + // 2. Clone constraint trees from the constrained object's class + // 3. Rename cloned constraints with object prefix (obj.var format) + // 4. Insert cloned constraints into current class for solver processing + // 5. Use basic randomization for non-constrained variables to avoid recursion + + // Extract and validate components early to avoid repeated type checks + AstVarRef* const varRefp = VN_CAST(nodep->fromp(), VarRef); + if (!varRefp) return; + + const AstClassRefDType* const classRefp + = VN_AS(varRefp->dtypep()->skipRefp(), ClassRefDType); + + if (nodep->user1() && varRefp->varp()->globalConstrained()) { + processGlobalConstraint(varRefp, classRefp->classp()); + } + } } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); @@ -479,6 +660,11 @@ class RandomizeMarkVisitor final : public VNVisitor { nodep->user2p(m_modp); iterateChildrenConst(nodep); } + void visit(AstWith* nodep) override { + VL_RESTORER(m_withp); + m_withp = nodep; + iterateChildrenConst(nodep); + } void visit(AstNodeExpr* nodep) override { iterateChildrenConst(nodep); @@ -519,6 +705,20 @@ class ConstraintExprVisitor final : public VNVisitor { bool m_structSel = false; // Marks when inside structSel // (used to format "%@.%@" for struct arrays) + // 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(); + if (const AstVarRef* const refp = VN_CAST(fromp, VarRef)) { + // Base case: reached root VarRef + return refp->name() + "." + memberSelp->name(); + } else if (const AstMemberSel* const selp = VN_CAST(fromp, MemberSel)) { + // Recursive case: build path from outer levels + return buildMemberPath(selp) + "." + memberSelp->name(); + } + memberSelp->v3fatalSrc("Unexpected node type in MemberSel chain"); + return ""; + } + AstSFormatF* getConstFormat(AstNodeExpr* nodep) { return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false, nodep}; @@ -641,19 +841,30 @@ class ConstraintExprVisitor final : public VNVisitor { CONSTRAINTIGN, "Size constraint combined with element constraint may not work correctly"); } - AstMemberSel* membersel = VN_IS(nodep->backp(), MemberSel) - ? VN_AS(nodep->backp(), MemberSel)->cloneTree(false) - : nullptr; + + // Check if this variable is marked as globally constrained + const bool isGlobalConstrained = nodep->varp()->globalConstrained(); + + AstMemberSel* membersel = nullptr; + std::string smtName; + if (isGlobalConstrained && VN_IS(nodep->backp(), MemberSel)) { + // For global constraints: build complete path from topmost MemberSel + AstNode* topMemberSel = nodep->backp(); + while (VN_IS(topMemberSel->backp(), MemberSel)) { + topMemberSel = topMemberSel->backp(); + } + membersel = VN_AS(topMemberSel, MemberSel)->cloneTree(false); + smtName = buildMemberPath(membersel); + } else { + // No MemberSel: just variable name + smtName = nodep->name(); + } + if (membersel) varp = membersel->varp(); AstNodeModule* const classOrPackagep = nodep->classOrPackagep(); const RandomizeMode randMode = {.asInt = varp->user1()}; if (!randMode.usesMode && editFormat(nodep)) return; - // In SMT just variable name, but we also ensure write_var for the variable - const std::string smtName = membersel - ? membersel->fromp()->name() + "." + membersel->name() - : nodep->name(); // Can be anything unique - VNRelinker relinker; nodep->unlinkFrBack(&relinker); AstNodeExpr* exprp = new AstSFormatF{nodep->fileline(), smtName, false, nullptr}; @@ -666,12 +877,18 @@ class ConstraintExprVisitor final : public VNVisitor { VCMethod::ARRAY_AT, new AstConst{nodep->fileline(), randMode.index}}; atp->dtypeSetUInt32(); exprp = new AstCond{varp->fileline(), atp, exprp, constFormatp}; - } else { + } else if (!membersel || !isGlobalConstrained) { + // Only delete nodep here if it's not a global constraint + // Global constraints need nodep for write_var processing VL_DO_DANGLING(pushDeletep(nodep), nodep); } relinker.relink(exprp); - if (!varp->user3()) { + // For global constraints: always call write_var with full path even if varp->user3() is + // set For normal constraints: only call write_var if varp->user3() is not set + if (!varp->user3() || (membersel && nodep->varp()->globalConstrained())) { + // For global constraints, delete nodep here after processing + if (membersel && isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep); AstCMethodHard* const methodp = new AstCMethodHard{ varp->fileline(), new AstVarRef{varp->fileline(), VN_AS(m_genp->user2p(), NodeModule), m_genp, @@ -693,10 +910,14 @@ class ConstraintExprVisitor final : public VNVisitor { methodp->dtypeSetVoid(); AstClass* const classp = membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class); - AstVarRef* const varRefp - = new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE}; - varRefp->classOrPackagep(classOrPackagep); - membersel ? methodp->addPinsp(membersel) : methodp->addPinsp(varRefp); + if (membersel) { + methodp->addPinsp(membersel); + } else { + AstVarRef* const varRefp + = new AstVarRef{varp->fileline(), classp, varp, VAccess::WRITE}; + varRefp->classOrPackagep(classOrPackagep); + methodp->addPinsp(varRefp); + } AstNodeDType* tmpDtypep = varp->dtypep(); while (VN_IS(tmpDtypep, UnpackArrayDType) || VN_IS(tmpDtypep, DynArrayDType) || VN_IS(tmpDtypep, QueueDType) || VN_IS(tmpDtypep, AssocArrayDType)) @@ -911,8 +1132,21 @@ class ConstraintExprVisitor final : public VNVisitor { editSMT(nodep, nodep->fromp(), indexp); } void visit(AstMemberSel* nodep) override { - if (nodep->user1()) { - nodep->v3warn(CONSTRAINTIGN, "Global constraints ignored (unsupported)"); + if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) { + AstNode* rootNode = nodep->fromp(); + while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel)) + rootNode = selp->fromp(); + // Check if the root variable participates in global constraints + if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) { + AstVar* const constrainedVar = varRefp->varp(); + if (constrainedVar->globalConstrained()) { + // Global constraint - unwrap the MemberSel + iterateChildren(nodep); + nodep->replaceWith(nodep->fromp()->unlinkFrBack()); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } + } } // Handle MemberSel references created by captureRefByThis() if (VN_IS(nodep->fromp(), VarRef) @@ -1943,10 +2177,19 @@ class RandomizeVisitor final : public VNVisitor { return; } AstFunc* const memberFuncp - = V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp()); + = memberVarp->globalConstrained() + ? V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp(), + BASIC_RANDOMIZE_FUNC_NAME) + : V3Randomize::newRandomizeFunc(m_memberMap, classRefp->classp()); AstMethodCall* const callp - = new AstMethodCall{fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE}, - "randomize", nullptr}; + = memberVarp->globalConstrained() + ? new AstMethodCall{fl, + new AstVarRef{fl, classp, memberVarp, + VAccess::WRITE}, + BASIC_RANDOMIZE_FUNC_NAME, nullptr} + : new AstMethodCall{ + fl, new AstVarRef{fl, classp, memberVarp, VAccess::WRITE}, + "randomize", nullptr}; callp->taskp(memberFuncp); callp->dtypeFrom(memberFuncp); AstVarRef* const basicFvarRefReadp = basicFvarRefp->cloneTree(false); @@ -2123,6 +2366,8 @@ class RandomizeVisitor final : public VNVisitor { if (!nodep->user1()) return; // Doesn't need randomize, or already processed UINFO(9, "Define randomize() for " << nodep); nodep->baseMostClassp()->needRNG(true); + + const bool globalConstrained = nodep->user1() == IS_RANDOMIZED_GLOBAL; AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep); AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var); addPrePostCall(nodep, randomizep, "pre_randomize"); @@ -2184,7 +2429,18 @@ class RandomizeVisitor final : public VNVisitor { } AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; - randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp}); + + // For global constraints: call basic randomize first (without global constraints) + if (globalConstrained) { + AstFunc* const basicRandomizep + = V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME); + addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp); + AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr}; + randomizep->addStmtsp(new AstAssign{fl, fvarRefp, basicRandomizeCallp}); + } else { + // For normal classes: use beginValp (standard flow) + randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp}); + } if (AstTask* const resizeAllTaskp = VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) { @@ -2192,15 +2448,23 @@ class RandomizeVisitor final : public VNVisitor { randomizep->addStmtsp(resizeTaskRefp->makeStmt()); } - AstFunc* const basicRandomizep - = V3Randomize::newRandomizeFunc(m_memberMap, nodep, "__Vbasic_randomize"); - addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp); - AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr}; AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false); fvarRefReadp->access(VAccess::READ); - randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false), - new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}}); + // For global constraints: combine with solver result (beginValp) + // For normal classes: call basic randomize after resize + if (globalConstrained) { + randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false), + new AstAnd{fl, fvarRefReadp, beginValp}}); + } else { + AstFunc* const basicRandomizep + = V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME); + addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp); + AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep, nullptr}; + randomizep->addStmtsp( + new AstAssign{fl, fvarRefp->cloneTree(false), + new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}}); + } addPrePostCall(nodep, randomizep, "post_randomize"); nodep->user1(false); } @@ -2417,7 +2681,7 @@ class RandomizeVisitor final : public VNVisitor { randomizeFuncp->addStmtsp(localGenp); AstFunc* const basicRandomizeFuncp - = V3Randomize::newRandomizeFunc(m_memberMap, classp, "__Vbasic_randomize"); + = V3Randomize::newRandomizeFunc(m_memberMap, classp, BASIC_RANDOMIZE_FUNC_NAME); AstFuncRef* const basicRandomizeFuncCallp = new AstFuncRef{nodep->fileline(), basicRandomizeFuncp, nullptr}; diff --git a/test_regress/t/t_constraint_global_arr_unsup.out b/test_regress/t/t_constraint_global_arr_unsup.out new file mode 100644 index 000000000..60b471e43 --- /dev/null +++ b/test_regress/t/t_constraint_global_arr_unsup.out @@ -0,0 +1,26 @@ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: MEMBERSEL 'm_x' within a global constraint + : ... note: In instance 't_constraint_global_arr_unsup' + 41 | m_mid.m_arr[0].m_x == 200; + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: MEMBERSEL 'm_y' within a global constraint + : ... note: In instance 't_constraint_global_arr_unsup' + 42 | m_mid.m_arr[0].m_y == 201; + | ^~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:18: Unsupported: MEMBERSEL 'm_obj' within a global constraint + : ... note: In instance 't_constraint_global_arr_unsup' + 47 | m_mid_arr[0].m_obj.m_x == 300; + | ^~~~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:18: Unsupported: MEMBERSEL 'm_obj' within a global constraint + : ... note: In instance 't_constraint_global_arr_unsup' + 48 | m_mid_arr[0].m_obj.m_y == 301; + | ^~~~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:18: Unsupported: MEMBERSEL 'm_arr' within a global constraint + : ... note: In instance 't_constraint_global_arr_unsup' + 53 | m_mid_arr[1].m_arr[2].m_y == 400; + | ^~~~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: MEMBERSEL 'm_y' within a global constraint + : ... note: In instance 't_constraint_global_arr_unsup' + 53 | m_mid_arr[1].m_arr[2].m_y == 400; + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_global_arr_unsup.py b/test_regress/t/t_constraint_global_arr_unsup.py new file mode 100755 index 000000000..e30916148 --- /dev/null +++ b/test_regress/t/t_constraint_global_arr_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_global_arr_unsup.v b/test_regress/t/t_constraint_global_arr_unsup.v new file mode 100755 index 000000000..991334e27 --- /dev/null +++ b/test_regress/t/t_constraint_global_arr_unsup.v @@ -0,0 +1,86 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by PlanV GmbH. +// SPDX-License-Identifier: CC0-1.0 + +/* verilator lint_off WIDTHTRUNC */ +class Inner; + rand int m_x; + rand int m_y; +endclass + +class Middle; + rand Inner m_obj; + rand Inner m_arr[3]; +endclass + +class Outer; + rand Middle m_mid; + rand Middle m_mid_arr[2]; + + function new(); + m_mid = new; + m_mid.m_obj = new; + foreach (m_mid.m_arr[i]) m_mid.m_arr[i] = new; + foreach (m_mid_arr[i]) begin + m_mid_arr[i] = new; + m_mid_arr[i].m_obj = new; + foreach (m_mid_arr[i].m_arr[j]) m_mid_arr[i].m_arr[j] = new; + end + endfunction + + // Case 1: Simple nested member access (should work) + constraint c_simple { + m_mid.m_obj.m_x == 100; + m_mid.m_obj.m_y == 101; + } + + // Case 2: Array indexing in the path (may not work) + constraint c_array_index { + m_mid.m_arr[0].m_x == 200; + m_mid.m_arr[0].m_y == 201; + } + + // Case 3: Nested array indexing + constraint c_nested_array { + m_mid_arr[0].m_obj.m_x == 300; + m_mid_arr[0].m_obj.m_y == 301; + } + + // Case 4: Multiple array indices + constraint c_multi_array { + m_mid_arr[1].m_arr[2].m_y == 400; + } +endclass + +module t_constraint_global_arr_unsup; + initial begin + Outer o = new; + if (o.randomize()) begin + $display("Case 1 - Simple: mid.obj.x = %0d (expected 100)", o.m_mid.m_obj.m_x); + $display("Case 1 - Simple: mid.obj.y = %0d (expected 101)", o.m_mid.m_obj.m_y); + $display("Case 2 - Array[0]: mid.arr[0].x = %0d (expected 200)", o.m_mid.m_arr[0].m_x); + $display("Case 2 - Array[0]: mid.arr[0].y = %0d (expected 201)", o.m_mid.m_arr[0].m_y); + $display("Case 3 - Nested[0]: mid_arr[0].obj.x = %0d (expected 300)", o.m_mid_arr[0].m_obj.m_x); + $display("Case 3 - Nested[0]: mid_arr[0].obj.y = %0d (expected 301)", o.m_mid_arr[0].m_obj.m_y); + $display("Case 4 - Multi[1][2]: mid_arr[1].arr[2].y = %0d (expected 400)", o.m_mid_arr[1].m_arr[2].m_y); + + // Check results + if (o.m_mid.m_obj.m_x == 100 && o.m_mid.m_obj.m_y == 101 && + o.m_mid.m_arr[0].m_x == 200 && o.m_mid.m_arr[0].m_y == 201 && + o.m_mid_arr[0].m_obj.m_x == 300 && o.m_mid_arr[0].m_obj.m_y == 301 && + o.m_mid_arr[1].m_arr[2].m_y == 400) begin + $display("*-* All Finished *-*"); + $finish; + end else begin + $display("*-* FAILED *-*"); + $stop; + end + end else begin + $display("*-* FAILED: randomize() returned 0 *-*"); + $stop; + end + end +endmodule +/* verilator lint_off WIDTHTRUNC */ diff --git a/test_regress/t/t_constraint_global_nested_unsup.out b/test_regress/t/t_constraint_global_nested_unsup.out new file mode 100644 index 000000000..db7e02cfb --- /dev/null +++ b/test_regress/t/t_constraint_global_nested_unsup.out @@ -0,0 +1,6 @@ +%Error-UNSUPPORTED: t/t_constraint_global_nested_unsup.v:9:14: Unsupported: One variable 'm_mid.m_inner' cannot have multiple global constraints + : ... note: In instance 't' + 9 | constraint c_inner { m_val inside {[1:10]}; } + | ^~~~~~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error: Exiting due to diff --git a/test_regress/t/t_constraint_global_nested_unsup.py b/test_regress/t/t_constraint_global_nested_unsup.py new file mode 100755 index 000000000..6585af685 --- /dev/null +++ b/test_regress/t/t_constraint_global_nested_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# 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 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_constraint_global_nested_unsup.v b/test_regress/t/t_constraint_global_nested_unsup.v new file mode 100755 index 000000000..903571156 --- /dev/null +++ b/test_regress/t/t_constraint_global_nested_unsup.v @@ -0,0 +1,51 @@ +// DESCRIPTION: Verilator: Test for unsupported multiple global constraints +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by PlanV GmbH. +// SPDX-License-Identifier: CC0-1.0 + + +class Inner; + rand int m_val; + constraint c_inner { m_val inside {[1:10]}; } + function new(); m_val = 0; endfunction +endclass + +class Mid; + rand Inner m_inner; + rand int m_x; + // Mid has global constraint on m_inner.m_val + constraint c_mid_global { + m_x > m_inner.m_val; + m_x inside {[5:15]}; + } + function new(); + m_inner = new(); + m_x = 0; + endfunction +endclass + +class Top; + rand Mid m_mid; + rand int m_y; + // Top also has global constraint on m_mid.m_inner.m_val + constraint c_top_global { + m_y < m_mid.m_inner.m_val; + m_y inside {[1:5]}; + } + function new(); + m_mid = new(); + m_y = 0; + endfunction +endclass + +module t; + Top top; + /* verilator lint_off WIDTHTRUNC */ + initial begin + top = new(); + if (!top.randomize()) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + /* verilator lint_off WIDTHTRUNC */ +endmodule diff --git a/test_regress/t/t_constraint_global_random.py b/test_regress/t/t_constraint_global_random.py new file mode 100755 index 000000000..466368b3d --- /dev/null +++ b/test_regress/t/t_constraint_global_random.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2025 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 + +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_constraint_global_random.v b/test_regress/t/t_constraint_global_random.v new file mode 100755 index 000000000..e0dec8f4c --- /dev/null +++ b/test_regress/t/t_constraint_global_random.v @@ -0,0 +1,124 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2025 by PlanV GmbH. +// SPDX-License-Identifier: CC0-1.0 + +class Inner; + rand int m_val; + constraint c_local { m_val inside {[1:5]}; } + + class NestedInner; + rand int nested_val; + constraint c_nested { nested_val inside {[1:3]}; } + endclass + + rand NestedInner nested_obj; + + function new(); + m_val = 0; + nested_obj = new(); + endfunction +endclass + +class Mid; + int m_limit; + rand int m_x; + rand Inner m_inner; + constraint c_mid { m_x == m_limit; } + function new(int lim); + m_limit = lim; + m_inner = new(); + endfunction +endclass + +class Top; + rand Mid m_m1; + rand Mid m_m2; + rand int m_y; + + constraint c_global { + m_m1.m_inner.m_val < m_m2.m_inner.m_val; + m_y > m_m1.m_x; + m_y < m_m2.m_x; + m_m1.m_inner.m_val + m_m2.m_inner.m_val < 8; + // Global constraint on nested class variable (3-level deep) + m_m1.m_inner.nested_obj.nested_val == 1; + m_m2.m_inner.nested_obj.nested_val == 3; + } + + function new(); + m_m1 = new(3); + m_m2 = new(5); + m_y = 0; + endfunction +endclass + +// Second independent class with global constraints + +class AnotherTop; + rand Mid m_m3; + rand int m_z; + + constraint c_another { + m_z < m_m3.m_x; + } + + function new(); + m_m3 = new(10); + m_z = 0; + endfunction +endclass + +module t_constraint_global_random; + int success; + Top t; + AnotherTop t2; + + initial begin + t = new(); + + // Test 1: Regular randomize() with global constraints + success = t.randomize(); + if (success != 1) $stop; + + if (t.m_m1.m_x != 3 || t.m_m2.m_x != 5) $stop; + if (t.m_m1.m_inner.m_val >= t.m_m2.m_inner.m_val) $stop; + if (t.m_y <= t.m_m1.m_x || t.m_y >= t.m_m2.m_x) $stop; + if (t.m_m1.m_inner.m_val + t.m_m2.m_inner.m_val >= 8) $stop; + if (t.m_m1.m_inner.m_val < 1 || t.m_m1.m_inner.m_val > 5 || + t.m_m2.m_inner.m_val < 1 || t.m_m2.m_inner.m_val > 5) $stop; + + // Verify nested class global constraints (3-level deep: Top -> Mid -> Inner -> NestedInner) + if (t.m_m1.m_inner.nested_obj.nested_val != 1) $stop; + if (t.m_m2.m_inner.nested_obj.nested_val != 3) $stop; + + // Test 2: randomize() with inline constraint on global-constrained members + success = 0; + success = t.randomize() with { + m_m1.m_inner.m_val == 2; + m_m2.m_inner.m_val == 5; + }; + if (success != 1) $stop; + + // Verify inline constraints + if (t.m_m1.m_inner.m_val != 2) $stop; + if (t.m_m2.m_inner.m_val != 5) $stop; + + // Verify global constraints still hold + if (t.m_m1.m_x != 3 || t.m_m2.m_x != 5) $stop; + if (t.m_m1.m_inner.m_val >= t.m_m2.m_inner.m_val) $stop; + if (t.m_y <= t.m_m1.m_x || t.m_y >= t.m_m2.m_x) $stop; + if (t.m_m1.m_inner.m_val + t.m_m2.m_inner.m_val >= 8) $stop; + + // Test 3: Second independent class (tests m_clonedConstraints.clear() bug) + t2 = new(); + success = t2.randomize(); + if (success != 1) $stop; + if (t2.m_z >= t2.m_m3.m_x) $stop; + if (t2.m_m3.m_x != 10) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_randomize_method_types_unsup.out b/test_regress/t/t_randomize_method_types_unsup.out index 7f6b8a170..2b19d4450 100644 --- a/test_regress/t/t_randomize_method_types_unsup.out +++ b/test_regress/t/t_randomize_method_types_unsup.out @@ -7,9 +7,6 @@ : ... note: In instance 't' 27 | q.size < 5; | ^~~~ -%Warning-CONSTRAINTIGN: t/t_randomize_method_types_unsup.v:31:10: Global constraints ignored (unsupported) - 31 | foo.x < y; - | ^ %Error-UNSUPPORTED: t/t_randomize_method_types_unsup.v:15:13: Unsupported: random member variable with the type of the containing class : ... note: In instance 't' 15 | rand Cls cls;