From 048c97f0ae56dddc29fd05870325fcea18bd5c40 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 14 Nov 2025 20:32:01 +0100 Subject: [PATCH] Support `std::randomize(){...}` (#4706) (#6573) --- include/verilated_random.cpp | 10 +- include/verilated_random.h | 8 +- src/V3AstAttr.h | 6 +- src/V3Randomize.cpp | 113 ++++++++++++++++-- src/V3Width.cpp | 7 -- test_regress/t/t_std_randomize_unsup_bad.out | 7 -- test_regress/t/t_std_randomize_unsup_bad.v | 16 --- ...e_unsup_bad.py => t_std_randomize_with.py} | 9 +- test_regress/t/t_std_randomize_with.v | 53 ++++++++ 9 files changed, 179 insertions(+), 50 deletions(-) delete mode 100644 test_regress/t/t_std_randomize_unsup_bad.out delete mode 100644 test_regress/t/t_std_randomize_unsup_bad.v rename test_regress/t/{t_std_randomize_unsup_bad.py => t_std_randomize_with.py} (75%) create mode 100644 test_regress/t/t_std_randomize_with.v diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 8236e34b4..30d13846e 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -505,7 +505,15 @@ void VlRandomizer::hard(std::string&& constraint) { m_constraints.emplace_back(std::move(constraint)); } -void VlRandomizer::clear() { m_constraints.clear(); } +void VlRandomizer::clearConstraints() { + m_constraints.clear(); + // Keep m_vars for class member randomization +} + +void VlRandomizer::clearAll() { + m_constraints.clear(); + m_vars.clear(); +} #ifdef VL_DEBUG void VlRandomizer::dump() const { diff --git a/include/verilated_random.h b/include/verilated_random.h index c7e41e590..bd3a27442 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -197,7 +197,7 @@ public: //============================================================================= // Object holding constraints and variable references. -class VlRandomizer final { +class VlRandomizer VL_NOT_FINAL { // MEMBERS std::vector m_constraints; // Solver-dependent constraints std::map> m_vars; // Solver-dependent @@ -570,7 +570,8 @@ public: } void hard(std::string&& constraint); - void clear(); + void clearConstraints(); + void clearAll(); // Clear both constraints and variables void set_randmode(const VlQueue& randmode) { m_randmodep = &randmode; } #ifdef VL_DEBUG void dump() const; @@ -580,7 +581,7 @@ public: //============================================================================= // Light wrapper for RNG used by std::randomize() to support scope-level randomization. -class VlStdRandomizer final { +class VlStdRandomizer final : public VlRandomizer { // MEMBERS VlRNG m_rng; // Random number generator @@ -594,6 +595,7 @@ public: value = VL_MASK_I(width) & VL_RANDOM_RNG_I(m_rng); return true; } + bool next() { return VlRandomizer::next(m_rng); } }; #endif // Guard diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index 3a3f7bd8f..8e0f8bdc9 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -783,7 +783,8 @@ public: FORK_INIT, FORK_JOIN, RANDOMIZER_BASIC_STD_RANDOMIZATION, - RANDOMIZER_CLEAR, + RANDOMIZER_CLEARCONSTRAINTS, + RANDOMIZER_CLEARALL, RANDOMIZER_HARD, RANDOMIZER_WRITE_VAR, RNG_GET_RANDSTATE, @@ -911,7 +912,8 @@ inline std::ostream& operator<<(std::ostream& os, const VCMethod& rhs) { {FORK_INIT, "init", false}, \ {FORK_JOIN, "join", false}, \ {RANDOMIZER_BASIC_STD_RANDOMIZATION, "basicStdRandomization", false}, \ - {RANDOMIZER_CLEAR, "clear", false}, \ + {RANDOMIZER_CLEARCONSTRAINTS, "clearConstraints", false}, \ + {RANDOMIZER_CLEARALL, "clearAll", false}, \ {RANDOMIZER_HARD, "hard", false}, \ {RANDOMIZER_WRITE_VAR, "write_var", false}, \ {RNG_GET_RANDSTATE, "__Vm_rng.get_randstate", true}, \ diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 514b402e3..50c9d8294 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -144,12 +144,31 @@ class RandomizeMarkVisitor final : public VNVisitor { AstNode* m_constraintExprGenp = nullptr; // Current constraint or constraint if expression AstNodeModule* m_modp; // Current module AstNodeStmt* m_stmtp = nullptr; // Current statement + AstNodeFTaskRef* m_stdRandCallp = nullptr; // Current std::randomize() call + 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 + // Check if a variable is listed in std::randomize() arguments + bool isVarInStdRandomizeArgs(const AstVar* varp) const { + if (!m_inStdWith || !m_stdRandCallp) return false; + + for (AstNode* pinp = m_stdRandCallp->pinsp(); pinp; pinp = pinp->nextp()) { + if (VN_IS(pinp, With)) continue; + const AstArg* const argp = VN_CAST(pinp, Arg); + if (!argp) continue; + const AstNodeExpr* const exprp = argp->exprp(); + if (const AstNodeVarRef* const varrefp = VN_CAST(exprp, NodeVarRef)) { + if (varrefp->varp() == varp) return true; + } else if (const AstMemberSel* const memberselp = VN_CAST(exprp, MemberSel)) { + if (memberselp->varp() == varp) return true; + } + } + return false; + } void markMembers(const AstClass* nodep) { for (const AstClass* classp = nodep; classp; classp = classp->extendsp() ? classp->extendsp()->classp() : nullptr) { @@ -353,6 +372,8 @@ class RandomizeMarkVisitor final : public VNVisitor { if (!nodep->backp()) VL_DO_DANGLING(nodep->deleteTree(), nodep); } void visit(AstNodeFTaskRef* nodep) override { + if (nodep->classOrPackagep() && nodep->classOrPackagep()->name() == "std") + m_stdRandCallp = nodep; iterateChildrenConst(nodep); if (nodep->name() == "rand_mode") { AstMethodCall* const methodCallp = VN_CAST(nodep, MethodCall); @@ -511,6 +532,7 @@ class RandomizeMarkVisitor final : public VNVisitor { markMembers(classp); } if (nodep->classOrPackagep()->name() == "std") { + m_stdRandCallp = nullptr; for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) { AstArg* const argp = VN_CAST(pinp, Arg); if (!argp) continue; @@ -524,6 +546,7 @@ class RandomizeMarkVisitor final : public VNVisitor { AstVarRef* const varrefp = VN_AS(exprp, VarRef); randVarp = varrefp->varp(); exprp = nullptr; + varrefp->user1(true); } UASSERT_OBJ(randVarp, nodep, "No rand variable found"); AstNode* backp = randVarp; @@ -591,16 +614,26 @@ class RandomizeMarkVisitor final : public VNVisitor { if (nodep->varp()->lifetime().isStatic()) m_staticRefs.emplace(nodep); - if (nodep->varp()->rand().isRandomizable()) nodep->user1(true); + // Mark as randomizable if: rand-declared, or listed in std::randomize() args + if (nodep->varp()->rand().isRandomizable() && !(m_inStdWith && m_stdRandCallp)) { + nodep->user1(true); + } else if (isVarInStdRandomizeArgs(nodep->varp())) { + nodep->user1(true); + } } void visit(AstMemberSel* nodep) override { if (!m_constraintExprGenp) return; iterateChildrenConst(nodep); - // Member select are randomized when both object and member are marked as rand. - // Variable references in with clause are converted to member selects and their from() is - // 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()); + const bool randMember = nodep->varp()->rand().isRandomizable(); + const bool inStdWith = m_inStdWith && m_stdRandCallp; + if (randObject && randMember && !inStdWith) { + nodep->user1(true); + } else if (inStdWith && isVarInStdRandomizeArgs(nodep->varp())) { + nodep->user1(true); + // Mark parent object for constraint expression visitor + if (VN_IS(nodep->fromp(), VarRef)) nodep->fromp()->user1(true); + } if (m_withp) { AstNode* backp = m_withp; @@ -663,7 +696,13 @@ class RandomizeMarkVisitor final : public VNVisitor { void visit(AstWith* nodep) override { VL_RESTORER(m_withp); m_withp = nodep; + for (AstNode* pinp = m_stdRandCallp ? m_stdRandCallp->pinsp() : nullptr; pinp; + pinp = pinp->nextp()) { + AstWith* const withp = VN_CAST(pinp, With); + if (withp == nodep) m_inStdWith = true; + } iterateChildrenConst(nodep); + m_inStdWith = false; } void visit(AstNodeExpr* nodep) override { @@ -847,8 +886,8 @@ class ConstraintExprVisitor final : public VNVisitor { AstMemberSel* membersel = nullptr; std::string smtName; - if (isGlobalConstrained && VN_IS(nodep->backp(), MemberSel)) { - // For global constraints: build complete path from topmost MemberSel + if (VN_IS(nodep->backp(), MemberSel)) { + // Build complete path from topmost MemberSel AstNode* topMemberSel = nodep->backp(); while (VN_IS(topMemberSel->backp(), MemberSel)) { topMemberSel = topMemberSel->backp(); @@ -908,8 +947,8 @@ class ConstraintExprVisitor final : public VNVisitor { dimension = 1; } methodp->dtypeSetVoid(); - AstClass* const classp - = membersel ? VN_AS(membersel->user2p(), Class) : VN_AS(varp->user2p(), Class); + AstNodeModule* const classp = membersel ? VN_AS(membersel->user2p(), NodeModule) + : VN_AS(varp->user2p(), NodeModule); if (membersel) { methodp->addPinsp(membersel); } else { @@ -1161,6 +1200,11 @@ class ConstraintExprVisitor final : public VNVisitor { nodep->replaceWith(varRefp); VL_DO_DANGLING(pushDeletep(nodep), nodep); visit(varRefp); + } else if (nodep->user1()) { + iterateChildren(nodep); + nodep->replaceWith(nodep->fromp()->unlinkFrBack()); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; } else { editFormat(nodep); } @@ -1356,9 +1400,8 @@ class CaptureVisitor final : public VNVisitor { newVarp->fileline(fileline); newVarp->varType(VVarType::BLOCKTEMP); newVarp->funcLocal(true); - newVarp->direction(VDirection::INPUT); + newVarp->direction(m_targetp ? VDirection::INPUT : VDirection::REF); newVarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_varCloneMap.emplace(varrefp->varp(), newVarp); varp = newVarp; return true; @@ -1452,6 +1495,7 @@ class CaptureVisitor final : public VNVisitor { m_ignore.emplace(thisRefp); AstMemberSel* const memberSelp = new AstMemberSel{nodep->fileline(), thisRefp, nodep->varp()}; + if (!m_targetp) memberSelp->user1(true); memberSelp->user2p(m_targetp); nodep->replaceWith(memberSelp); VL_DO_DANGLING(pushDeletep(nodep), nodep); @@ -2051,7 +2095,15 @@ class RandomizeVisitor final : public VNVisitor { AstCMethodHard* const clearp = new AstCMethodHard{ fileline, new AstVarRef{fileline, VN_AS(genp->user2p(), NodeModule), genp, VAccess::READWRITE}, - VCMethod::RANDOMIZER_CLEAR}; + VCMethod::RANDOMIZER_CLEARCONSTRAINTS}; + clearp->dtypeSetVoid(); + return clearp->makeStmt(); + } + AstNodeStmt* implementConstraintsClearAll(FileLine* const fileline, AstVar* const genp) { + AstCMethodHard* const clearp = new AstCMethodHard{ + fileline, + new AstVarRef{fileline, VN_AS(genp->user2p(), NodeModule), genp, VAccess::READWRITE}, + VCMethod::RANDOMIZER_CLEARALL}; clearp->dtypeSetVoid(); return clearp->makeStmt(); } @@ -2579,9 +2631,37 @@ class RandomizeVisitor final : public VNVisitor { new AstVarRef{nodep->fileline(), VN_AS(randomizeFuncp->fvarp(), Var), VAccess::WRITE}, new AstConst{nodep->fileline(), AstConst::WidthedValue{}, 32, 1}}); + std::unique_ptr withCapturep; int argn = 0; for (AstNode* pinp = nodep->pinsp(); pinp; pinp = pinp->nextp()) { AstArg* const argp = VN_CAST(pinp, Arg); + AstWith* const withp = VN_CAST(pinp, With); + if (withp) { + FileLine* const fl = nodep->fileline(); + withCapturep + = std::make_unique(withp->exprp(), m_modp, nullptr); + withCapturep->addFunctionArguments(randomizeFuncp); + // Clear old constraints and variables for std::randomize with clause + if (stdrand) { + randomizeFuncp->addStmtsp( + implementConstraintsClearAll(randomizeFuncp->fileline(), stdrand)); + } + AstNode* const capturedTreep = withp->exprp()->unlinkFrBackWithNext(); + randomizeFuncp->addStmtsp(capturedTreep); + { + ConstraintExprVisitor{m_memberMap, capturedTreep, randomizeFuncp, stdrand, + nullptr}; + } + AstCExpr* const solverCallp = new AstCExpr{fl}; + solverCallp->dtypeSetBit(); + solverCallp->add(new AstVarRef{fl, stdrand, VAccess::READWRITE}); + solverCallp->add(".next()"); + AstVar* const fvarp = VN_AS(randomizeFuncp->fvarp(), Var); + AstVarRef* const retvalReadp = new AstVarRef{fl, fvarp, VAccess::READ}; + AstNodeExpr* const andExprp = new AstAnd{fl, retvalReadp, solverCallp}; + AstVarRef* const retvalWritep = new AstVarRef{fl, fvarp, VAccess::WRITE}; + randomizeFuncp->addStmtsp(new AstAssign{fl, retvalWritep, andExprp}); + } if (!argp) continue; AstNodeExpr* exprp = argp->exprp(); @@ -2614,11 +2694,20 @@ class RandomizeVisitor final : public VNVisitor { VN_AS(randomizeFuncp->fvarp(), Var), VAccess::READ}, basicMethodp}}); } + // Remove With nodes from pins as they have been processed + for (AstNode* pinp = nodep->pinsp(); pinp;) { + AstNode* const nextp = pinp->nextp(); + if (VN_IS(pinp, With)) { + VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp); + } + pinp = nextp; + } // Replace the node with a call to that function nodep->name(randomizeFuncp->name()); nodep->taskp(randomizeFuncp); nodep->dtypeFrom(randomizeFuncp->dtypep()); if (VN_IS(m_modp, Class)) nodep->classOrPackagep(m_modp); + if (withCapturep) nodep->addPinsp(withCapturep->getArgs()); UINFOTREE(9, nodep, "", "std::rnd-call"); UINFOTREE(9, randomizeFuncp, "", "std::rnd-func"); return; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index f5ee52f4e..39858ea1f 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -6712,13 +6712,6 @@ class WidthVisitor final : public VNVisitor { for (const AstNode* argp = nodep->pinsp(); argp; argp = argp->nextp()) userIterateAndNext(VN_AS(argp, Arg)->exprp(), WidthVP{SELF, BOTH}.p()); handleStdRandomizeArgs(nodep); // Provided args should be in current scope - if (withp) { - nodep->v3warn(CONSTRAINTIGN, "Unsupported: std::randomize()'s 'with'"); - nodep->replaceWith(new AstConst{nodep->fileline(), 0}); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - VL_DO_DANGLING(pushDeletep(withp), nodep); - return; - } processFTaskRefArgs(nodep); nodep->addPinsp(withp); nodep->didWidth(true); diff --git a/test_regress/t/t_std_randomize_unsup_bad.out b/test_regress/t/t_std_randomize_unsup_bad.out deleted file mode 100644 index b939e9951..000000000 --- a/test_regress/t/t_std_randomize_unsup_bad.out +++ /dev/null @@ -1,7 +0,0 @@ -%Warning-CONSTRAINTIGN: t/t_std_randomize_unsup_bad.v:11:16: Unsupported: std::randomize()'s 'with' - : ... note: In instance 't' - 11 | if (std::randomize(a, b) with { 2 < a; a < 7; b < a; } != 1) $stop; - | ^~~~~~~~~ - ... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest - ... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message. -%Error: Exiting due to diff --git a/test_regress/t/t_std_randomize_unsup_bad.v b/test_regress/t/t_std_randomize_unsup_bad.v deleted file mode 100644 index c848a3e43..000000000 --- a/test_regress/t/t_std_randomize_unsup_bad.v +++ /dev/null @@ -1,16 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2024 by Antmicro Ltd. -// SPDX-License-Identifier: CC0-1.0 - -module t; - initial begin - int a, b; - if (std::randomize(a, b) != 1) $stop; - if (std::randomize(a, b) with { 2 < a; a < 7; b < a; } != 1) $stop; - if (!(2 < a && a < 7 && b < a)) $stop; - $write("-*-* All Finished *-*-\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_std_randomize_unsup_bad.py b/test_regress/t/t_std_randomize_with.py similarity index 75% rename from test_regress/t/t_std_randomize_unsup_bad.py rename to test_regress/t/t_std_randomize_with.py index 31228c9a7..a2b131082 100755 --- a/test_regress/t/t_std_randomize_unsup_bad.py +++ b/test_regress/t/t_std_randomize_with.py @@ -9,8 +9,13 @@ import vltest_bootstrap -test.scenarios('linter') +test.scenarios('simulator') -test.lint(fails=True, 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_std_randomize_with.v b/test_regress/t/t_std_randomize_with.v new file mode 100644 index 000000000..0c457184d --- /dev/null +++ b/test_regress/t/t_std_randomize_with.v @@ -0,0 +1,53 @@ +// 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 external_cl; + int x; + int y; + + function new(); + x = 0; + y = 0; + endfunction +endclass + +module t; + initial begin + int a, b; + int limit = 10; + external_cl obj; + + // Test 1: Basic std::randomize with 'with' clause + if (std::randomize(a, b) with { 2 < a; a < 7; b < a; } != 1) $stop; + if (!(2 < a && a < 7 && b < a)) $stop; + $display("Test 1 passed: a=%0d, b=%0d", a, b); + + // Test 2: Local variable and class member with mutual constraints + obj = new; + if (std::randomize(a, obj.x) with { a > 10; a < 20; obj.x > a; obj.x < a + 5; } != 1) $stop; + if (!(a > 10 && a < 20 && obj.x > a && obj.x < a + 5)) $stop; + $display("Test 2 passed: a=%0d, obj.x=%0d (obj.x between a+1 and a+4)", a, obj.x); + + // Test 3: Reference external variable in constraint + if (std::randomize(a) with { a > 0; a < limit; } != 1) $stop; + if (!(a > 0 && a < limit)) $stop; + $display("Test 3 passed: a=%0d, limit=%0d", a, limit); + + // Test 4: Randomize class member variables + obj = new; + if (std::randomize(obj.x, obj.y) with { obj.x > 5; obj.x < 20; obj.y == obj.x + 1; } != 1) $stop; + if (!(obj.x > 5 && obj.x < 20 && obj.y == obj.x + 1)) $stop; + $display("Test 4 passed: obj.x=%0d, obj.y=%0d", obj.x, obj.y); + + // Test 5: Multiple class members and local variable + if (std::randomize(a, obj.x, obj.y) with { a > 0; a < 5; obj.x > a; obj.y > obj.x; obj.y < a + 10; } != 1) $stop; + if (!(a > 0 && a < 5 && obj.x > a && obj.y > obj.x && obj.y < a + 10)) $stop; + $display("Test 5 passed: a=%0d, obj.x=%0d, obj.y=%0d", a, obj.x, obj.y); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule