diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 0edc27ec7..bec3fa932 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -4893,13 +4893,9 @@ class RandomizeVisitor final : public VNVisitor { // basic-randomizes FIRST and lets the solver override the constrained // leaves afterwards. This way a leaf that is only globally constrained // (not user3) is still basic-randomized when its type is randomized - // standalone, while the owner's solver result wins. - // KNOWN LIMITATION: if such an owner also declares its own - // size-constrained array, the size-phase solver call is emitted above - // (during the genp block) before these assignments, so basicFirst cannot - // reorder it and the array size constraint is left unsolved. This - // combination cannot be built on master (the #7833 regression blocks it), - // so it is a capability gap, not a regression of working behavior. + // standalone, while the owner's solver result wins. The size-only resize + // fallback below is emitted after the solver call for such owners so an + // owner that also size-constrains its own array still resizes correctly. AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; randomizep->addStmtsp(new AstAssign{ fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp}); @@ -4907,13 +4903,21 @@ class RandomizeVisitor final : public VNVisitor { const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep); const bool needsSizePhase = sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty(); - if (!needsSizePhase) { + // Resize fallback for an array whose size is only solver-determined (size + // constraint but no element constraint, so no two-pass size phase). It must + // run AFTER the solver .next() that computes the size variable, otherwise it + // resizes with a stale value. A basicFirst owner runs the solver LAST (after + // basic randomization), so emit the resize after the second assignment; + // otherwise the solver runs first and the resize belongs right after it. + const auto emitResizeFallback = [&]() { + if (needsSizePhase) return; if (AstTask* const resizeAllTaskp = VN_AS(m_memberMap.findMember(nodep, "__Vresize_constrained_arrays"), Task)) { AstTaskRef* const resizeTaskRefp = new AstTaskRef{fl, resizeAllTaskp}; randomizep->addStmtsp(resizeTaskRefp->makeStmt()); } - } + }; + if (!basicFirst) emitResizeFallback(); AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false); fvarRefReadp->access(VAccess::READ); @@ -4923,6 +4927,8 @@ class RandomizeVisitor final : public VNVisitor { randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false), new AstAnd{fl, fvarRefReadp, secondHalfp}}); + if (basicFirst) emitResizeFallback(); + // Call nested post_randomize on rand class-type members (IEEE 18.4.1) if (classHasRandClassMembers(nodep)) { AstTask* const postTaskp = getCreateNestedCallbackTask(nodep, "post"); diff --git a/test_regress/t/t_constraint_global_subobj_combined.py b/test_regress/t/t_constraint_global_subobj.py similarity index 100% rename from test_regress/t/t_constraint_global_subobj_combined.py rename to test_regress/t/t_constraint_global_subobj.py diff --git a/test_regress/t/t_constraint_global_subobj.v b/test_regress/t/t_constraint_global_subobj.v new file mode 100644 index 000000000..333695f2d --- /dev/null +++ b/test_regress/t/t_constraint_global_subobj.v @@ -0,0 +1,178 @@ +// 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 + +// Scenario 1 (issue #7833 literal): owner SA1 declares a global constraint on a +// sub-object's type but is NEVER randomized; a standalone randomize() of the +// holder must still randomize the nested fields. +class C1; + rand int x; + rand int y; +endclass + +class B1; + rand C1 c; + function new(); + c = new(); + endfunction +endclass + +class SA1; + rand B1 b; + constraint c_foo {b.c.x == 123;} +endclass + +// Scenario 2 (combined): the SAME design randomizes both the constraint owner +// and a standalone holder of the constrained type. +class C2; + rand int x; + rand int y; +endclass + +class B2; + rand C2 c; + function new(); + c = new(); + endfunction +endclass + +class SA2; + rand B2 b; + constraint c_foo {b.c.x == 123;} + function new(); + b = new(); + endfunction +endclass + +// Scenario 3 (multiple same-type sub-objects): two sub-objects of one type are +// each pinned by a global constraint while that type is also randomized +// standalone; the two share one underlying rand variable. +class C3; + rand int x; +endclass + +class M3; + rand C3 c1; + rand C3 c2; + constraint c { + c1.x == 11; + c2.x == 22; + } + function new(); + c1 = new(); + c2 = new(); + endfunction +endclass + +// Scenario 4 (global constraint owner with its own size-constrained array): the +// owner basic-randomizes first, the solver overrides last, and the size-only +// resize fallback still resizes from the solver-determined size. +class C4; + rand int x; +endclass + +class A4; + rand C4 c; + rand int arr[]; + constraint c_sub {c.x == 5;} + constraint c_size {arr.size() == 4;} + function new(); + c = new(); + endfunction +endclass + +module t_constraint_global_subobj; + B1 b1; + SA2 a2; + B2 b2; + M3 m3; + C3 s3; + A4 a4; + int prevx, prevy, p3; + bit varyx, varyy, vary3; + + initial begin + // Scenario 1: SA1 never randomized; standalone B1 must vary both fields. + b1 = new(); + varyx = 0; + varyy = 0; + if (b1.randomize() != 1) $stop; + prevx = b1.c.x; + prevy = b1.c.y; + for (int i = 0; i < 20; i++) begin + if (b1.randomize() != 1) $stop; + if (b1.c.x != prevx) varyx = 1; + if (b1.c.y != prevy) varyy = 1; + prevx = b1.c.x; + prevy = b1.c.y; + end + if (!varyx || !varyy) begin + $display("ERROR: standalone holder fields stuck (varyx=%0d varyy=%0d)", varyx, varyy); + $stop; + end + + // Scenario 2: randomize the owner (constraint applies) and a standalone + // holder (fields random) in the same design. + a2 = new(); + if (a2.randomize() != 1) $stop; + if (a2.b.c.x != 123) begin + $display("ERROR: owner constraint not applied, a2.b.c.x=%0d", a2.b.c.x); + $stop; + end + b2 = new(); + varyx = 0; + if (b2.randomize() != 1) $stop; + prevx = b2.c.x; + for (int i = 0; i < 20; i++) begin + if (b2.randomize() != 1) $stop; + if (b2.c.x != prevx) varyx = 1; + prevx = b2.c.x; + end + if (!varyx) begin + $display("ERROR: standalone holder x stuck while owner also randomized"); + $stop; + end + + // Scenario 3: two same-type sub-objects each pinned, plus standalone vary. + m3 = new(); + if (m3.randomize() != 1) $stop; + if (m3.c1.x != 11) begin + $display("ERROR: m3.c1.x should be 11, got %0d", m3.c1.x); + $stop; + end + if (m3.c2.x != 22) begin + $display("ERROR: m3.c2.x should be 22, got %0d", m3.c2.x); + $stop; + end + s3 = new(); + vary3 = 0; + if (s3.randomize() != 1) $stop; + p3 = s3.x; + for (int i = 0; i < 20; i++) begin + if (s3.randomize() != 1) $stop; + if (s3.x != p3) vary3 = 1; + p3 = s3.x; + end + if (!vary3) begin + $display("ERROR: standalone same-type x stuck"); + $stop; + end + + // Scenario 4: owner with a global constraint AND its own size constraint. + a4 = new(); + if (a4.randomize() != 1) $stop; + if (a4.c.x != 5) begin + $display("ERROR: a4.c.x should be 5, got %0d", a4.c.x); + $stop; + end + if (a4.arr.size() != 4) begin + $display("ERROR: a4.arr.size() should be 4, got %0d", a4.arr.size()); + $stop; + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_constraint_global_subobj_combined.v b/test_regress/t/t_constraint_global_subobj_combined.v deleted file mode 100644 index 8ac36531f..000000000 --- a/test_regress/t/t_constraint_global_subobj_combined.v +++ /dev/null @@ -1,81 +0,0 @@ -// 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 - -class C; - rand int x; - rand int y; -endclass - -class B; - rand C c; - function new(); - c = new(); - endfunction -endclass - -class A; - rand B b; - constraint c_foo {b.c.x == 123;} - function new(); - b = new(); - endfunction -endclass - -module t_constraint_global_subobj_combined; - B b; - A a; - int px, py; - bit xvar, yvar; - - initial begin - // Same compilation randomizes BOTH the sub-object holder B standalone AND the - // global-constraint owner A. A leaf (C::x) is therefore solver-owned for A - // but must be freely randomized for B; both have to work at once (issue - // #7833). - b = new(); - if (b.randomize() != 1) $stop; - px = b.c.x; - py = b.c.y; - xvar = 0; - yvar = 0; - for (int i = 0; i < 20; i++) begin - if (b.randomize() != 1) $stop; - if (b.c.x != px) xvar = 1; - if (b.c.y != py) yvar = 1; - px = b.c.x; - py = b.c.y; - end - if (!xvar) begin - $display("ERROR: standalone b.c.x not randomized (stuck)"); - $stop; - end - if (!yvar) begin - $display("ERROR: standalone b.c.y not randomized (stuck)"); - $stop; - end - - a = new(); - if (a.randomize() != 1) $stop; - py = a.b.c.y; - yvar = 0; - for (int i = 0; i < 20; i++) begin - if (a.randomize() != 1) $stop; - if (a.b.c.x != 123) begin - $display("ERROR: global constraint b.c.x == 123 not applied, got %0d", a.b.c.x); - $stop; - end - if (a.b.c.y != py) yvar = 1; - py = a.b.c.y; - end - if (!yvar) begin - $display("ERROR: a.b.c.y not randomized (stuck)"); - $stop; - end - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_constraint_global_subobj_multi.py b/test_regress/t/t_constraint_global_subobj_multi.py deleted file mode 100755 index db1adb3f9..000000000 --- a/test_regress/t/t_constraint_global_subobj_multi.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/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_constraint_global_subobj_multi.v b/test_regress/t/t_constraint_global_subobj_multi.v deleted file mode 100644 index c3ec47514..000000000 --- a/test_regress/t/t_constraint_global_subobj_multi.v +++ /dev/null @@ -1,63 +0,0 @@ -// 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 - -class C; - rand int x; -endclass - -class M; - rand C c1; - rand C c2; - constraint c { - c1.x == 11; - c2.x == 22; - } - function new(); - c1 = new(); - c2 = new(); - endfunction -endclass - -module t_constraint_global_subobj_multi; - C s; - M m; - int p; - bit vary; - - initial begin - // Type C is randomized standalone AND used as two distinct sub-objects of M, - // each pinned by a global constraint. The two sub-objects share the same - // underlying rand variable, so each must keep its own write_var (issue #7833 - // family). - s = new(); - if (s.randomize() != 1) $stop; - p = s.x; - vary = 0; - for (int i = 0; i < 20; i++) begin - if (s.randomize() != 1) $stop; - if (s.x != p) vary = 1; - p = s.x; - end - if (!vary) begin - $display("ERROR: standalone C.x not randomized (stuck)"); - $stop; - end - - m = new(); - if (m.randomize() != 1) $stop; - if (m.c1.x != 11) begin - $display("ERROR: m.c1.x should be 11, got %0d", m.c1.x); - $stop; - end - if (m.c2.x != 22) begin - $display("ERROR: m.c2.x should be 22, got %0d", m.c2.x); - $stop; - end - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule diff --git a/test_regress/t/t_constraint_global_subobj_standalone.py b/test_regress/t/t_constraint_global_subobj_standalone.py deleted file mode 100755 index db1adb3f9..000000000 --- a/test_regress/t/t_constraint_global_subobj_standalone.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/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_constraint_global_subobj_standalone.v b/test_regress/t/t_constraint_global_subobj_standalone.v deleted file mode 100644 index 9763b62b7..000000000 --- a/test_regress/t/t_constraint_global_subobj_standalone.v +++ /dev/null @@ -1,63 +0,0 @@ -// 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 - -class C; - rand int x; - rand int y; -endclass - -class B; - rand C c; - function new(); - c = new(); - endfunction -endclass - -class A; - rand B b; - constraint c_foo {b.c.x == 123;} - function new(); - b = new(); - endfunction -endclass - -module t_constraint_global_subobj_standalone; - B b; - int px, py; - bit xvar, yvar; - - initial begin - // Issue #7833: a standalone randomize() of B must randomize the nested - // sub-object fields c.x and c.y. Class A holds a global constraint that - // references type C through b.c.x, which used to flag C's fields as solved - // and skip them in B's basic randomization even though A is never - // randomized here. - b = new(); - if (b.randomize() != 1) $stop; - px = b.c.x; - py = b.c.y; - xvar = 0; - yvar = 0; - for (int i = 0; i < 20; i++) begin - if (b.randomize() != 1) $stop; - if (b.c.x != px) xvar = 1; - if (b.c.y != py) yvar = 1; - px = b.c.x; - py = b.c.y; - end - if (!xvar) begin - $display("ERROR: b.c.x not randomized (stuck)"); - $stop; - end - if (!yvar) begin - $display("ERROR: b.c.y not randomized (stuck)"); - $stop; - end - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule