diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 8b34004c5..0c8c309ce 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -514,11 +514,6 @@ class RandomizeMarkVisitor final : public VNVisitor { = VN_CAST(methodCallp->fromp(), ConstraintRef)) { constrp = constrRefp->constrp(); if (constrRefp->fromp()) classp = VN_AS(constrRefp->classOrPackagep(), Class); - if (constrp->isStatic()) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: 'constraint_mode()' on static constraint"); - valid = false; - } } else if (AstClassRefDType* classRefDtp = VN_CAST(methodCallp->fromp()->dtypep()->skipRefp(), ClassRefDType)) { classp = classRefDtp->classp(); @@ -538,11 +533,6 @@ class RandomizeMarkVisitor final : public VNVisitor { constrp->user1(constraintMode.asInt); } else { classp->foreachMember([=](AstClass*, AstConstraint* constrp) { - if (constrp->isStatic()) { - nodep->v3warn(E_UNSUPPORTED, - "Unsupported: 'constraint_mode()' on static constraint: " - << constrp->prettyNameQ()); - } constrp->user1(constraintMode.asInt); }); } @@ -1834,6 +1824,8 @@ class RandomizeVisitor final : public VNVisitor { 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 + std::map + m_staticConstraintModeVars; // Static constraint mode vars per class // METHODS // Check if two nodes are semantically equivalent (not pointer equality): @@ -1947,6 +1939,24 @@ class RandomizeVisitor final : public VNVisitor { } return nullptr; } + AstVar* getCreateStaticConstraintModeVar(AstClass* const classp) { + auto it = m_staticConstraintModeVars.find(classp); + if (it != m_staticConstraintModeVars.end()) return it->second; + if (AstClassExtends* const extendsp = classp->extendsp()) { + return getCreateStaticConstraintModeVar(extendsp->classp()); + } + AstVar* const staticModeVarp = createStaticModeVar(classp, "__Vstaticconstraintmode"); + m_staticConstraintModeVars[classp] = staticModeVarp; + return staticModeVarp; + } + AstVar* getStaticConstraintModeVar(AstClass* const classp) { + auto it = m_staticConstraintModeVars.find(classp); + if (it != m_staticConstraintModeVars.end()) return it->second; + if (AstClassExtends* const extendsp = classp->extendsp()) { + return getStaticConstraintModeVar(extendsp->classp()); + } + return nullptr; + } AstVar* createModeVar(AstClass* const classp, const char* const name) { FileLine* const fl = classp->fileline(); if (!m_dynarrayDtp) { @@ -1960,6 +1970,24 @@ class RandomizeVisitor final : public VNVisitor { classp->addStmtsp(modeVarp); return modeVarp; } + AstVar* createStaticModeVar(AstClass* const classp, const char* const name) { + // Create a static variable that will be shared across all instances. + // By setting lifetime to STATIC_EXPLICIT, V3Class will move this to the class package. + FileLine* const fl = classp->fileline(); + if (!m_dynarrayDtp) { + m_dynarrayDtp = new AstDynArrayDType{ + fl, v3Global.rootp()->typeTablep()->findBitDType()->dtypep()}; + m_dynarrayDtp->dtypep(m_dynarrayDtp); + v3Global.rootp()->typeTablep()->addTypesp(m_dynarrayDtp); + } + AstVar* const modeVarp = new AstVar{fl, VVarType::MODULETEMP, name, m_dynarrayDtp}; + modeVarp->lifetime(VLifetime::STATIC_EXPLICIT); + // Note: user2p is set to classp here. V3Scope will later update varScopep + // to point to the package scope when the variable is moved by V3Class. + modeVarp->user2p(classp); + classp->addStmtsp(modeVarp); + return modeVarp; + } static void addSetRandMode(AstNodeFTask* const ftaskp, AstVar* const genp, AstVar* const randModeVarp) { FileLine* const fl = ftaskp->fileline(); @@ -1976,18 +2004,28 @@ class RandomizeVisitor final : public VNVisitor { bool hasConstraints = false; uint32_t randModeCount = 0; uint32_t constraintModeCount = 0; + uint32_t staticConstraintModeCount = 0; classp->foreachMember([&](AstClass*, AstNode* memberp) { // SystemVerilog only allows single inheritance, so we don't need to worry about // index overlap. If the index > 0, it's already been set. - if (VN_IS(memberp, Constraint)) { + if (AstConstraint* const constrp = VN_CAST(memberp, Constraint)) { hasConstraints = true; RandomizeMode constraintMode = {.asInt = memberp->user1()}; if (!constraintMode.usesMode) return; if (constraintMode.index == 0) { - constraintMode.index = constraintModeCount++; + // Use separate index counters for static vs non-static constraints + if (constrp->isStatic()) { + constraintMode.index = staticConstraintModeCount++; + } else { + constraintMode.index = constraintModeCount++; + } memberp->user1(constraintMode.asInt); } else { - constraintModeCount = constraintMode.index + 1; + if (constrp->isStatic()) { + staticConstraintModeCount = constraintMode.index + 1; + } else { + constraintModeCount = constraintMode.index + 1; + } } } else if (VN_IS(memberp, Var)) { RandomizeMode randMode = {.asInt = memberp->user1()}; @@ -2009,6 +2047,10 @@ class RandomizeVisitor final : public VNVisitor { AstVar* const constraintModeVarp = getCreateConstraintModeVar(classp); makeModeInit(constraintModeVarp, classp, constraintModeCount); } + if (staticConstraintModeCount > 0) { + AstVar* const staticConstraintModeVarp = getCreateStaticConstraintModeVar(classp); + makeStaticModeInit(staticConstraintModeVarp, classp, staticConstraintModeCount); + } }); } void makeModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) { @@ -2025,6 +2067,37 @@ class RandomizeVisitor final : public VNVisitor { new AstVarRef{fl, modeVarModp, modeVarp, VAccess::WRITE}, new AstConst{fl, 1}, true)); } + void makeStaticModeInit(AstVar* modeVarp, AstClass* classp, uint32_t modeCount) { + // For static constraint mode, we need lazy initialization since it's shared across + // instances. Generate: if (size() == 0) { resize(N); set all to 1; } + AstNodeModule* const modeVarModp = VN_AS(modeVarp->user2p(), NodeModule); + FileLine* fl = modeVarp->fileline(); + + // Build the condition: size() == 0 + AstCMethodHard* const sizep + = new AstCMethodHard{fl, new AstVarRef{fl, modeVarModp, modeVarp, VAccess::READ}, + VCMethod::DYN_SIZE, nullptr}; + sizep->dtypeSetUInt32(); + AstEq* const condp = new AstEq{fl, sizep, new AstConst{fl, 0}}; + + // Build the then-block: resize and set all to 1 + AstCMethodHard* const dynarrayNewp + = new AstCMethodHard{fl, new AstVarRef{fl, modeVarModp, modeVarp, VAccess::WRITE}, + VCMethod::DYN_RESIZE, new AstConst{fl, modeCount}}; + dynarrayNewp->dtypeSetVoid(); + AstNode* const thenStmtsp = dynarrayNewp->makeStmt(); + thenStmtsp->addNext( + makeModeSetLoop(fl, new AstVarRef{fl, modeVarModp, modeVarp, VAccess::WRITE}, + new AstConst{fl, 1}, true)); + + // Build the if statement + AstIf* const ifp = new AstIf{fl, condp, thenStmtsp}; + + // Add to new() constructor + AstNodeFTask* const newp = VN_AS(m_memberMap.findMember(classp, "new"), NodeFTask); + UASSERT_OBJ(newp, classp, "No new() in class"); + newp->addStmtsp(ifp); + } static AstNode* makeModeSetLoop(FileLine* const fl, AstNodeExpr* const lhsp, AstNodeExpr* const rhsp, bool inTask) { AstVar* const iterVarp = new AstVar{fl, VVarType::BLOCKTEMP, "i", lhsp->findUInt32DType()}; @@ -2054,17 +2127,20 @@ class RandomizeVisitor final : public VNVisitor { const RandomizeMode rmode = {.asInt = varp->user1()}; return VN_AS(wrapIfMode(rmode, getRandModeVar(classp), stmtp), NodeStmt); } - static AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, - AstNode* stmtp) { + AstNode* wrapIfConstraintMode(AstClass* classp, AstConstraint* const constrp, AstNode* stmtp) { const RandomizeMode rmode = {.asInt = constrp->user1()}; - return wrapIfMode(rmode, getConstraintModeVar(classp), stmtp); + AstVar* const modeVarp = constrp->isStatic() ? getStaticConstraintModeVar(classp) + : getConstraintModeVar(classp); + return wrapIfMode(rmode, modeVarp, stmtp); } static AstNode* wrapIfMode(const RandomizeMode mode, AstVar* modeVarp, AstNode* stmtp) { FileLine* const fl = stmtp->fileline(); if (mode.usesMode) { - AstCMethodHard* const atp = new AstCMethodHard{ - fl, new AstVarRef{fl, VN_AS(modeVarp->user2p(), Class), modeVarp, VAccess::READ}, - VCMethod::ARRAY_AT, new AstConst{fl, mode.index}}; + // user2p can be either AstClass or AstClassPackage (for static constraints) + AstNodeModule* const modp = VN_AS(modeVarp->user2p(), NodeModule); + AstCMethodHard* const atp + = new AstCMethodHard{fl, new AstVarRef{fl, modp, modeVarp, VAccess::READ}, + VCMethod::ARRAY_AT, new AstConst{fl, mode.index}}; atp->dtypeSetUInt32(); return new AstIf{fl, atp, stmtp}; } @@ -2492,7 +2568,15 @@ class RandomizeVisitor final : public VNVisitor { // Creates a lvalue reference to the randomize mode var. Called by visit(AstNodeFTaskRef*) AstNodeExpr* makeModeAssignLhs(FileLine* const fl, AstClass* const classp, AstNodeExpr* const fromp, AstVar* const modeVarp) { - if (classp == m_modp) { + // For static constraint mode vars, always use VarRef (not MemberSel). + // At this point V3Class hasn't run yet, so user2p may be nullptr. + // 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 + if (fromp) VL_DO_DANGLING(fromp->unlinkFrBack()->deleteTree(), fromp); + return new AstVarRef{fl, classp, 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, VAccess::WRITE}; @@ -2848,7 +2932,10 @@ class RandomizeVisitor final : public VNVisitor { classp = VN_AS(fromp->dtypep()->skipRefp(), ClassRefDType)->classp(); } UASSERT_OBJ(classp, nodep, "Failed to find class"); - AstVar* const constraintModeVarp = getConstraintModeVar(classp); + // Use correct mode variable based on whether constraint is static + AstVar* const constraintModeVarp = (constrp && constrp->isStatic()) + ? getStaticConstraintModeVar(classp) + : getConstraintModeVar(classp); AstNodeExpr* const lhsp = makeModeAssignLhs(nodep->fileline(), classp, fromp, constraintModeVarp); replaceWithModeAssign(nodep, constrp, lhsp); diff --git a/test_regress/t/t_constraint_mode_unsup.py b/test_regress/t/t_constraint_mode_static.py similarity index 68% rename from test_regress/t/t_constraint_mode_unsup.py rename to test_regress/t/t_constraint_mode_static.py index b7449248c..db1adb3f9 100755 --- a/test_regress/t/t_constraint_mode_unsup.py +++ b/test_regress/t/t_constraint_mode_static.py @@ -4,13 +4,18 @@ # 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: 2024 Wilson Snyder +# SPDX-FileCopyrightText: 2026 Wilson Snyder # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 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_mode_static.v b/test_regress/t/t_constraint_mode_static.v new file mode 100644 index 000000000..48fde24b8 --- /dev/null +++ b/test_regress/t/t_constraint_mode_static.v @@ -0,0 +1,70 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026 by PlanV GmbH. +// SPDX-License-Identifier: CC0-1.0 + +// Test static constraint_mode() support per IEEE 1800-2017 Section 18.4, 18.8 +// Static constraint mode should be shared across all instances. + +class StaticConstraintTest; + rand bit [7:0] value; + + // Static constraint - shared across all instances + static constraint static_con { + value inside {[10:15]}; + } + + // Non-static constraint for comparison + constraint instance_con { + value > 5; + } +endclass + +module t; + StaticConstraintTest obj1, obj2; + + initial begin + obj1 = new(); + obj2 = new(); + + // Test 1: Verify static constraint_mode getter works + if (obj1.static_con.constraint_mode() != 1) $stop; + if (obj2.static_con.constraint_mode() != 1) $stop; + + // Test 2: Disable static constraint on one instance + obj1.static_con.constraint_mode(0); + + // Verify the state is shared across all instances + if (obj1.static_con.constraint_mode() != 0) $stop; + if (obj2.static_con.constraint_mode() != 0) $stop; + + // Test 3: Re-enable static constraint via different instance + obj2.static_con.constraint_mode(1); + + // Verify state is updated for all instances + if (obj1.static_con.constraint_mode() != 1) $stop; + if (obj2.static_con.constraint_mode() != 1) $stop; + + // Test 4: Verify randomization respects constraint mode when enabled + obj1.static_con.constraint_mode(1); + obj1.instance_con.constraint_mode(1); + for (int i = 0; i < 10; i++) begin + void'(obj1.randomize()); + if (!(obj1.value inside {[10:15]})) $stop; + if (!(obj1.value > 5)) $stop; + end + + // Test 5: Disable static constraint and verify randomization changes + obj1.static_con.constraint_mode(0); + // With static_con disabled, value only needs to be > 5 + for (int i = 0; i < 10; i++) begin + obj1.value = 1; // Reset to low value + void'(obj1.randomize()); + if (!(obj1.value > 5)) $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_mode_unsup.out b/test_regress/t/t_constraint_mode_unsup.out deleted file mode 100644 index 9a82c86f1..000000000 --- a/test_regress/t/t_constraint_mode_unsup.out +++ /dev/null @@ -1,14 +0,0 @@ -%Error-UNSUPPORTED: t/t_constraint_mode_unsup.v:17:55: Unsupported: 'constraint_mode()' on static constraint - : ... note: In instance 't' - 17 | $display("p.cons.constraint_mode()=%0d", p.cons.constraint_mode()); - | ^~~~~~~~~~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_constraint_mode_unsup.v:18:14: Unsupported: 'constraint_mode()' on static constraint - : ... note: In instance 't' - 18 | p.cons.constraint_mode(0); - | ^~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_constraint_mode_unsup.v:19:9: Unsupported: 'constraint_mode()' on static constraint: 'cons' - : ... note: In instance 't' - 19 | p.constraint_mode(0); - | ^~~~~~~~~~~~~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_constraint_mode_unsup.v b/test_regress/t/t_constraint_mode_unsup.v deleted file mode 100644 index b74e7ee4f..000000000 --- a/test_regress/t/t_constraint_mode_unsup.v +++ /dev/null @@ -1,21 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -class Packet; - int m_one; - static constraint cons { m_one > 0 && m_one < 2; } -endclass - -module t; - Packet p; - - initial begin - p = new; - $display("p.cons.constraint_mode()=%0d", p.cons.constraint_mode()); - p.cons.constraint_mode(0); - p.constraint_mode(0); - end -endmodule