From 00988aed70d0c4737014afc4926bed803947a231 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Wed, 19 Nov 2025 17:08:42 +0100 Subject: [PATCH] Support General Global Constraints (#6709) (#6711) --- src/V3Randomize.cpp | 236 ++++++++---------- .../t/t_constraint_global_arr_unsup.out | 28 +-- ...unsup.py => t_constraint_global_nested.py} | 11 +- ...d_unsup.v => t_constraint_global_nested.v} | 4 + .../t/t_constraint_global_nested_unsup.out | 6 - .../t/t_constraint_global_random_simple.py | 21 ++ .../t/t_constraint_global_random_simple.v | 108 ++++++++ 7 files changed, 264 insertions(+), 150 deletions(-) rename test_regress/t/{t_constraint_global_nested_unsup.py => t_constraint_global_nested.py} (64%) rename test_regress/t/{t_constraint_global_nested_unsup.v => t_constraint_global_nested.v} (84%) delete mode 100644 test_regress/t/t_constraint_global_nested_unsup.out create mode 100755 test_regress/t/t_constraint_global_random_simple.py create mode 100644 test_regress/t/t_constraint_global_random_simple.v diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 50c9d8294..7caaaba7f 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -148,10 +148,20 @@ class RandomizeMarkVisitor final : public VNVisitor { bool m_inStdWith = false; // True when inside a 'with {}' clause 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 + // Mark all rand variables in IS_RANDOMIZED_GLOBAL classes as globalConstrained + void markGlobalConstrainedVars(AstClass* classp) { + for (const AstClass* cp = classp; cp; + cp = cp->extendsp() ? cp->extendsp()->classp() : nullptr) { + for (AstNode* memberp = cp->stmtsp(); memberp; memberp = memberp->nextp()) { + AstVar* const varp = VN_CAST(memberp, Var); + if (!varp) continue; + if (varp->rand().isRandomizable()) varp->globalConstrained(true); + } + } + } // Check if a variable is listed in std::randomize() arguments bool isVarInStdRandomizeArgs(const AstVar* varp) const { if (!m_inStdWith || !m_stdRandCallp) return false; @@ -187,7 +197,8 @@ class RandomizeMarkVisitor final : public VNVisitor { if (classRefp) { AstClass* const rclassp = classRefp->classp(); if (!rclassp->user1()) { - rclassp->user1(IS_RANDOMIZED); + rclassp->user1(IS_RANDOMIZED_GLOBAL); + markGlobalConstrainedVars(rclassp); markMembers(rclassp); markDerived(rclassp); } @@ -254,7 +265,7 @@ class RandomizeMarkVisitor final : public VNVisitor { // Process a single constraint during nested constraint cloning void processNestedConstraint(AstConstraint* const constrp, AstVarRef* rootVarRefp, - const std::vector& newPath) { + const std::vector& newPath, AstClass* targetClassp) { std::string pathPrefix = rootVarRefp->name(); for (AstVar* pathMemberVarp : newPath) { pathPrefix += GLOBAL_CONSTRAINT_SEPARATOR + pathMemberVarp->name(); @@ -262,17 +273,17 @@ class RandomizeMarkVisitor final : public VNVisitor { const std::string newName = pathPrefix + GLOBAL_CONSTRAINT_SEPARATOR + constrp->name(); - for (const AstConstraint* existingConstrp : m_clonedConstraints) { + // Check if this constraint already exists in the target class + bool isDuplicate = false; + targetClassp->foreachMember([&](AstClass* const, AstConstraint* const existingConstrp) { 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; + isDuplicate = true; } - } + }); + if (isDuplicate) return; AstConstraint* const cloneConstrp = constrp->cloneTree(false); cloneConstrp->name(newName); @@ -285,12 +296,16 @@ class RandomizeMarkVisitor final : public VNVisitor { VL_DO_DANGLING(varRefp->deleteTree(), varRefp); }); - m_clonedConstraints.push_back(cloneConstrp); + // Add constraint directly to the target class + targetClassp->addStmtsp(cloneConstrp); + // Immediately iterate to set user1 marks + iterateConst(cloneConstrp); } // Clone constraints from nested rand class members void cloneNestedConstraintsRecurse(AstVarRef* rootVarRefp, AstClass* classp, - const std::vector& pathToClass) { + const std::vector& pathToClass, + AstClass* targetClassp) { for (AstNode* memberNodep = classp->membersp(); memberNodep; memberNodep = memberNodep->nextp()) { AstVar* const memberVarp = VN_CAST(memberNodep, Var); @@ -308,45 +323,9 @@ class RandomizeMarkVisitor final : public VNVisitor { // member selections nestedClassp->foreachMember( [&](AstClass* const containingClassp, AstConstraint* const constrp) { - processNestedConstraint(constrp, rootVarRefp, newPath); + processNestedConstraint(constrp, rootVarRefp, newPath, targetClassp); }); - - 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); + cloneNestedConstraintsRecurse(rootVarRefp, nestedClassp, newPath, targetClassp); } } @@ -354,7 +333,6 @@ class RandomizeMarkVisitor final : public VNVisitor { 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()) { @@ -362,8 +340,6 @@ class RandomizeMarkVisitor final : public VNVisitor { 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); @@ -530,6 +506,26 @@ class RandomizeMarkVisitor final : public VNVisitor { if (classp) { if (!classp->user1()) classp->user1(IS_RANDOMIZED); markMembers(classp); + // Clone constraints from all IS_RANDOMIZED_GLOBAL members + classp->foreachMember([&](AstClass* const, AstVar* const memberVarp) { + if (!memberVarp->rand().isRandomizable()) return; + const AstNodeDType* const dtypep = memberVarp->dtypep()->skipRefp(); + const AstClassRefDType* const classRefp = VN_CAST(dtypep, ClassRefDType); + if (!classRefp || !classRefp->classp()) return; + AstClass* const memberClassp = classRefp->classp(); + if (memberClassp->user1() != IS_RANDOMIZED_GLOBAL) return; + memberVarp->globalConstrained(true); + // Clone constraints from this IS_RANDOMIZED_GLOBAL member class + AstVarRef* rootVarRefp + = new AstVarRef{nodep->fileline(), classp, memberVarp, VAccess::READ}; + std::vector emptyPath; + memberClassp->foreachMember([&](AstClass* const, AstConstraint* const constrp) { + processNestedConstraint(constrp, rootVarRefp, emptyPath, classp); + }); + cloneNestedConstraintsRecurse(rootVarRefp, memberClassp, emptyPath, classp); + // Delete the temporary VarRef created for constraint cloning + VL_DO_DANGLING(rootVarRefp->deleteTree(), rootVarRefp); + }); } if (nodep->classOrPackagep()->name() == "std") { m_stdRandCallp = nullptr; @@ -649,36 +645,6 @@ class RandomizeMarkVisitor final : public VNVisitor { } 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); @@ -743,6 +709,8 @@ class ConstraintExprVisitor final : public VNVisitor { VMemberMap& m_memberMap; // Member names cached for fast lookup bool m_structSel = false; // Marks when inside structSel // (used to format "%@.%@" for struct arrays) + std::set& m_writtenVars; // Track which variable paths have write_var generated + // (shared across all constraints) // Build full path for a MemberSel chain (e.g., "obj.l2.l3.l4") std::string buildMemberPath(const AstMemberSel* const memberSelp) { @@ -916,18 +884,23 @@ 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 if (!membersel || !isGlobalConstrained) { - // Only delete nodep here if it's not a global constraint - // Global constraints need nodep for write_var processing + } else if (!isGlobalConstrained) { + // Non-global constraints: delete nodep immediately VL_DO_DANGLING(pushDeletep(nodep), nodep); } + // else: Global constraints keep nodep alive for write_var processing relinker.relink(exprp); - // 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); + // For global constraints: check if this specific path has been written + // For normal constraints: only call write_var if varp->user3() is not set + const bool alreadyWritten + = isGlobalConstrained ? m_writtenVars.count(smtName) > 0 : varp->user3(); + const bool shouldWriteVar = !alreadyWritten; + if (shouldWriteVar) { + // Track this variable path as written + if (isGlobalConstrained) m_writtenVars.insert(smtName); + // For global constraints, delete nodep after processing + if (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, @@ -981,6 +954,11 @@ class ConstraintExprVisitor final : public VNVisitor { UASSERT_OBJ(initTaskp, classp, "No new() in class"); } initTaskp->addStmtsp(methodp->makeStmt()); + } else { + // Variable already written, clean up cloned membersel if any + if (membersel) VL_DO_DANGLING(membersel->deleteTree(), membersel); + // Delete nodep if it's a global constraint (not deleted yet) + if (isGlobalConstrained) VL_DO_DANGLING(pushDeletep(nodep), nodep); } } void visit(AstCountOnes* nodep) override { @@ -1171,10 +1149,16 @@ class ConstraintExprVisitor final : public VNVisitor { editSMT(nodep, nodep->fromp(), indexp); } void visit(AstMemberSel* nodep) override { + // Check if rootVar is globalConstrained if (nodep->varp()->rand().isRandomizable() && nodep->fromp()) { AstNode* rootNode = nodep->fromp(); while (const AstMemberSel* const selp = VN_CAST(rootNode, MemberSel)) rootNode = selp->fromp(); + // Detect array/assoc array access in global constraints + if (VN_IS(rootNode, ArraySel) || VN_IS(rootNode, AssocSel)) { + nodep->v3warn(E_UNSUPPORTED, + "Unsupported: Array element access in global constraint "); + } // Check if the root variable participates in global constraints if (const AstVarRef* const varRefp = VN_CAST(rootNode, VarRef)) { AstVar* const constrainedVar = varRefp->varp(); @@ -1355,11 +1339,12 @@ public: // CONSTRUCTORS explicit ConstraintExprVisitor(VMemberMap& memberMap, AstNode* nodep, AstNodeFTask* inlineInitTaskp, AstVar* genp, - AstVar* randModeVarp) + AstVar* randModeVarp, std::set& writtenVars) : m_inlineInitTaskp{inlineInitTaskp} , m_genp{genp} , m_randModeVarp{randModeVarp} - , m_memberMap{memberMap} { + , m_memberMap{memberMap} + , m_writtenVars{writtenVars} { iterateAndNextNull(nodep); } }; @@ -1643,6 +1628,7 @@ class RandomizeVisitor final : public VNVisitor { int m_randCaseNum = 0; // Randcase number within a module for var naming std::map m_randcDtypes; // RandC data type deduplication AstConstraint* m_constraintp = nullptr; // Current constraint + std::set m_writtenVars; // Track write_var calls per class to avoid duplicates // METHODS void createRandomGenerator(AstClass* const classp) { @@ -2413,19 +2399,21 @@ class RandomizeVisitor final : public VNVisitor { VL_RESTORER(m_randCaseNum); m_modp = nodep; m_randCaseNum = 0; + m_writtenVars.clear(); // Each class has its own set of written variables iterateChildren(nodep); 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; + FileLine* fl = nodep->fileline(); AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep); AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var); - addPrePostCall(nodep, randomizep, "pre_randomize"); - FileLine* fl = nodep->fileline(); - AstVar* const randModeVarp = getRandModeVar(nodep); + addPrePostCall(nodep, randomizep, "pre_randomize"); + + // Both IS_RANDOMIZED and IS_RANDOMIZED_GLOBAL classes need full constraint support + // IS_RANDOMIZED_GLOBAL classes can be randomized independently AstNodeExpr* beginValp = nullptr; AstVar* genp = getRandomGenerator(nodep); if (genp) { @@ -2451,7 +2439,8 @@ class RandomizeVisitor final : public VNVisitor { resizeAllTaskp->addStmtsp(resizeTaskRefp->makeStmt()); } - ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr, genp, randModeVarp}; + ConstraintExprVisitor{m_memberMap, constrp->itemsp(), nullptr, + genp, randModeVarp, m_writtenVars}; if (constrp->itemsp()) { taskp->addStmtsp(wrapIfConstraintMode( nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext())); @@ -2481,18 +2470,7 @@ class RandomizeVisitor final : public VNVisitor { } AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; - - // 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}); - } + randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp}); if (AstTask* const resizeAllTaskp = VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) { @@ -2503,20 +2481,12 @@ class RandomizeVisitor final : public VNVisitor { AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false); fvarRefReadp->access(VAccess::READ); - // 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}}); - } + 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); } @@ -2649,8 +2619,8 @@ class RandomizeVisitor final : public VNVisitor { AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); randomizeFuncp->addStmtsp(capturedTreep); { - ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, stdrand, - nullptr}; + ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, + stdrand, nullptr, m_writtenVars}; } AstCExpr* const solverCallp = new AstCExpr{fl}; solverCallp->dtypeSetBit(); @@ -2793,8 +2763,8 @@ class RandomizeVisitor final : public VNVisitor { AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); randomizeFuncp->addStmtsp(capturedTreep); { - ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, localGenp, - randModeVarp}; + ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, + localGenp, randModeVarp, m_writtenVars}; } // Call the solver and set return value @@ -2883,6 +2853,18 @@ public: explicit RandomizeVisitor(AstNetlist* nodep) : m_inlineUniqueNames{"__Vrandwith"} { createRandomizeClassVars(nodep); + // Mark variables in global constraints + // These should not be randomized in nested class's __VBasicRand + nodep->foreach([&](AstConstraint* constrp) { + constrp->foreach([&](AstMemberSel* memberSelp) { + // Only mark if this MemberSel was created during constraint cloning + if (memberSelp->user2p()) { + AstVar* const varp = memberSelp->varp(); + if (!varp->user3()) varp->user3(true); + } + }); + }); + iterate(nodep); nodep->foreach([&](AstConstraint* constrp) { VL_DO_DANGLING(pushDeletep(constrp->unlinkFrBack()), constrp); diff --git a/test_regress/t/t_constraint_global_arr_unsup.out b/test_regress/t/t_constraint_global_arr_unsup.out index 60b471e43..636a8f10a 100644 --- a/test_regress/t/t_constraint_global_arr_unsup.out +++ b/test_regress/t/t_constraint_global_arr_unsup.out @@ -1,26 +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' +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:41:20: Unsupported: Array element access in global constraint 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' +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:42:20: Unsupported: Array element access in global constraint 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' +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:24: Unsupported: Array element access in global constraint + 47 | m_mid_arr[0].m_obj.m_x == 300; + | ^~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:47:18: Unsupported: Array element access in global constraint 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' +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:24: Unsupported: Array element access in global constraint + 48 | m_mid_arr[0].m_obj.m_y == 301; + | ^~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:48:18: Unsupported: Array element access in global constraint 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' +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:27: Unsupported: Array element access in global constraint 53 | m_mid_arr[1].m_arr[2].m_y == 400; | ^~~ +%Error-UNSUPPORTED: t/t_constraint_global_arr_unsup.v:53:18: Unsupported: Array element access in global constraint + 53 | m_mid_arr[1].m_arr[2].m_y == 400; + | ^~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_constraint_global_nested_unsup.py b/test_regress/t/t_constraint_global_nested.py similarity index 64% rename from test_regress/t/t_constraint_global_nested_unsup.py rename to test_regress/t/t_constraint_global_nested.py index 6585af685..466368b3d 100755 --- a/test_regress/t/t_constraint_global_nested_unsup.py +++ b/test_regress/t/t_constraint_global_nested.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # DESCRIPTION: Verilator: Verilog Test driver/expect definition # -# Copyright 2024 by Wilson Snyder. This program is free software; you +# 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. @@ -9,8 +9,13 @@ import vltest_bootstrap -test.scenarios('vlt') +test.scenarios('simulator') -test.lint(fails=test.vlt_all, expect_filename=test.golden_filename) +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_nested_unsup.v b/test_regress/t/t_constraint_global_nested.v similarity index 84% rename from test_regress/t/t_constraint_global_nested_unsup.v rename to test_regress/t/t_constraint_global_nested.v index 903571156..ee5d46fc3 100755 --- a/test_regress/t/t_constraint_global_nested_unsup.v +++ b/test_regress/t/t_constraint_global_nested.v @@ -44,6 +44,10 @@ module t; initial begin top = new(); if (!top.randomize()) $stop; + $display("After randomization:"); + $display(" top.m_y = %0d", top.m_y); + $display(" top.m_mid.m_x = %0d", top.m_mid.m_x); + $display(" top.m_mid.m_inner.m_val = %0d", top.m_mid.m_inner.m_val); $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_constraint_global_nested_unsup.out b/test_regress/t/t_constraint_global_nested_unsup.out deleted file mode 100644 index db7e02cfb..000000000 --- a/test_regress/t/t_constraint_global_nested_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%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_random_simple.py b/test_regress/t/t_constraint_global_random_simple.py new file mode 100755 index 000000000..466368b3d --- /dev/null +++ b/test_regress/t/t_constraint_global_random_simple.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_simple.v b/test_regress/t/t_constraint_global_random_simple.v new file mode 100644 index 000000000..21340ec46 --- /dev/null +++ b/test_regress/t/t_constraint_global_random_simple.v @@ -0,0 +1,108 @@ +// 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 + +// Simple test for global constraints with 2-level nesting: Top -> Mid -> Inner + +class Inner; + rand int m_val; + rand int m_rand_in_val; + rand int m_only_topConstrained_val; + constraint c_inner { m_val inside {[1:5]}; } + + function new(); + m_val = 0; + m_rand_in_val = 0; + m_only_topConstrained_val = 0; + endfunction +endclass + +class Mid; + int m_limit; + rand int m_x; + rand Inner m_inner; + rand int m_rand_mid_val; + constraint c_mid { m_x == m_limit; } + + function new(int lim); + m_limit = lim; + m_x = 0; + m_rand_mid_val = 0; + m_inner = new(); + endfunction +endclass + +class Top; + rand Mid m_mid; + rand int m_y; + + constraint c_top { + m_y < m_mid.m_x; // 1-level reference + m_mid.m_inner.m_val < m_y; // 2-level reference + m_mid.m_inner.m_only_topConstrained_val == 5; // Only constrained at top level + } + + function new(); + m_mid = new(10); + m_y = 0; + endfunction +endclass + +module t_constraint_global_random_simple; + int success; + Top t; + + initial begin + t = new(); + + // Test: Regular randomize() with global constraints + success = t.randomize(); + if (success != 1) $stop; + + $display("After randomization:"); + $display(" t.m_y = %0d", t.m_y); + $display(" t.m_mid.m_x = %0d", t.m_mid.m_x); + $display(" t.m_mid.m_inner.m_val = %0d", t.m_mid.m_inner.m_val); + $display(" t.m_mid.m_inner.m_only_topConstrained_val = %0d", t.m_mid.m_inner.m_only_topConstrained_val); + $display(" t.m_mid.m_rand_mid_val = %0d", t.m_mid.m_rand_mid_val); + $display(" t.m_mid.m_inner.m_rand_in_val = %0d", t.m_mid.m_inner.m_rand_in_val); + + // Verify constraints + // 1. c_mid: m_x == m_limit + if (t.m_mid.m_x != 10) begin + $display("ERROR: m_mid.m_x should be 10, got %0d", t.m_mid.m_x); + $stop; + end + + // 2. c_inner: m_val in [1:5] + if (t.m_mid.m_inner.m_val < 1 || t.m_mid.m_inner.m_val > 5) begin + $display("ERROR: m_inner.m_val should be in [1:5], got %0d", t.m_mid.m_inner.m_val); + $stop; + end + + // 3. c_top: m_y < m_mid.m_x (m_y < 10) + if (t.m_y >= t.m_mid.m_x) begin + $display("ERROR: m_y should be < m_mid.m_x, got m_y=%0d, m_x=%0d", t.m_y, t.m_mid.m_x); + $stop; + end + + // 4. c_top: m_mid.m_inner.m_val < m_y + if (t.m_mid.m_inner.m_val >= t.m_y) begin + $display("ERROR: m_inner.m_val should be < m_y, got m_val=%0d, m_y=%0d", + t.m_mid.m_inner.m_val, t.m_y); + $stop; + end + + // 5. c_top: m_mid.m_inner.m_only_topConstrained_val == 5 + if (t.m_mid.m_inner.m_only_topConstrained_val != 5) begin + $display("ERROR: m_only_topConstrained_val should be 5, got %0d", + t.m_mid.m_inner.m_only_topConstrained_val); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule