diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 9a0c5e0ce..194de5785 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -3959,18 +3959,14 @@ class RandomizeVisitor final : public VNVisitor { AstNodeExpr* beginValp = nullptr; AstVar* genp = getRandomGenerator(nodep); if (genp) { + // Phase 1: Process all constraints (create tasks, run ConstraintExprVisitor) + // Setup task refs are NOT added to setupAllTaskp here -- done in phase 2 nodep->foreachMember([&](AstClass* const classp, AstConstraint* const constrp) { AstTask* taskp = VN_AS(constrp->user2p(), Task); if (!taskp) { taskp = newSetupConstraintTask(classp, constrp->name()); constrp->user2p(taskp); } - AstTaskRef* const setupTaskRefp = new AstTaskRef{constrp->fileline(), taskp}; - setupTaskRefp->classOrPackagep(classp); - - AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(nodep); - - setupAllTaskp->addStmtsp(setupTaskRefp->makeStmt()); if (AstTask* const resizeTaskp = VN_CAST(constrp->user3p(), Task)) { AstTask* const resizeAllTaskp = getCreateAggrResizeTask(nodep); @@ -3989,6 +3985,38 @@ class RandomizeVisitor final : public VNVisitor { nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext())); } }); + + // Phase 2: Add setup task refs in depth order (deepest first = lowest priority) + // Ensures outer-scope soft constraints override inner-scope (IEEE 18.5.13) + { + // Count nesting depth by GLOBAL_CONSTRAINT_SEPARATOR occurrences + const auto constraintDepth = [](const AstConstraint* constrp) { + int depth = 0; + size_t pos = 0; + while ((pos = constrp->name().find(GLOBAL_CONSTRAINT_SEPARATOR, pos)) + != std::string::npos) { + ++depth; + pos += std::strlen(GLOBAL_CONSTRAINT_SEPARATOR); + } + return depth; + }; + int maxDepth = 0; + nodep->foreachMember([&](AstClass* const, AstConstraint* const constrp) { + maxDepth = std::max(maxDepth, constraintDepth(constrp)); + }); + AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(nodep); + for (int d = maxDepth; d >= 0; --d) { + nodep->foreachMember( + [&](AstClass* const classp, AstConstraint* const constrp) { + if (constraintDepth(constrp) != d) return; + AstTask* const taskp = VN_AS(constrp->user2p(), Task); + AstTaskRef* const setupTaskRefp + = new AstTaskRef{constrp->fileline(), taskp}; + setupTaskRefp->classOrPackagep(classp); + setupAllTaskp->addStmtsp(setupTaskRefp->makeStmt()); + }); + } + } // For derived classes: clone write_var calls from parent's randomize() if (nodep->extendsp()) { AstClass* parentClassp = nodep->extendsp()->classp(); diff --git a/test_regress/t/t_randomize_soft_cross_object.py b/test_regress/t/t_randomize_soft_cross_object.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_randomize_soft_cross_object.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +if not test.have_solver: + test.skip("No constraint solver installed") + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randomize_soft_cross_object.v b/test_regress/t/t_randomize_soft_cross_object.v new file mode 100644 index 000000000..89375bccc --- /dev/null +++ b/test_regress/t/t_randomize_soft_cross_object.v @@ -0,0 +1,68 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +// Test: Soft cross-object constraints should respect IEEE 1800-2023 18.5.13 +// outer-scope soft has higher priority than inner-scope soft. + +class sub_cfg_c; + rand int unsigned timeout; + rand bit enabled; + constraint soft_defaults { + soft timeout == 1000; + soft enabled == 0; + } +endclass + +class parent_cfg_c; + rand bit enabled; + rand sub_cfg_c sub_a; + constraint soft_defaults { soft enabled == 0; } + constraint propagate_cons { + if (enabled) { sub_a.enabled == 1; } + } + function new(); + sub_a = new(); + endfunction +endclass + +class top_test_c; + rand parent_cfg_c cfg; + rand sub_cfg_c extra_cfg; + constraint cfg_hard_cons { cfg.enabled == 1; } + constraint cfg_soft_cons { + soft cfg.sub_a.timeout == 5000; + soft extra_cfg.timeout == 9999; + soft extra_cfg.enabled == 1; + } + function new(); + cfg = new(); + extra_cfg = new(); + endfunction +endclass + +module t; + initial begin + static top_test_c obj = new(); + repeat (10) begin + `checkd(obj.randomize(), 1) + // Two-level cross-object soft: parent's soft overrides child's + `checkd(obj.cfg.sub_a.timeout, 32'd5000) + // One-level cross-object soft: parent's soft overrides child's + `checkd(obj.extra_cfg.timeout, 32'd9999) + `checkd(obj.extra_cfg.enabled, 1'b1) + // Hard constraint propagation still works + `checkd(obj.cfg.enabled, 1'b1) + `checkd(obj.cfg.sub_a.enabled, 1'b1) + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule