diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 8a75ba547..0edc27ec7 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1173,14 +1173,20 @@ class ConstraintExprVisitor final : public VNVisitor { // For global constraints: check shared path-level set // For inline constraints: check per-instance set (each __Vrandwith has own randomizer) - // For class-level constraints: check varp->user3() + // For a class-level member-select (path) reference: key on the full path too, + // so two sub-objects of the same type (c1.x, c2.x) do not collide on the + // shared variable -- otherwise the second write_var is wrongly de-duplicated + // away and the solver constrains an unwritten variable. + // For a plain class-level variable: check varp->user3() const bool alreadyWritten = isGlobalConstrained ? m_writtenVars.count(smtName) > 0 : m_inlineInitTaskp ? m_inlineWrittenVars.count(smtName) > 0 + : membersel ? 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); + if (isGlobalConstrained || (membersel && !m_inlineInitTaskp)) + m_writtenVars.insert(smtName); if (m_inlineInitTaskp) m_inlineWrittenVars.insert(smtName); // For global constraints, delete nodep after processing if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep); @@ -3781,7 +3787,7 @@ class RandomizeVisitor final : public VNVisitor { // Such a class basic-randomizes first and lets the solver override the // constrained leaves afterwards, so a leaf shared with a standalone // randomize() of the sub-object's type is still basic-randomized there. - bool classOwnsGlobalConstraint(AstClass* classp) { + bool classOwnsGlobalConstraint(const AstClass* classp) const { return classp->existsMember([](const AstClass*, const AstConstraint* constrp) { bool owns = false; constrp->foreach([&](const AstMemberSel* memberSelp) { @@ -4887,10 +4893,13 @@ 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. This assumes such an - // owner has no size-constrained arrays of its own (needsSizePhase): a - // global constraint cannot reach an array element, so the two are mutually - // exclusive today and basic randomization stays before the solver. + // 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. AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE}; randomizep->addStmtsp(new AstAssign{ fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp}); @@ -5455,11 +5464,13 @@ public: constrp->foreach([&](AstMemberSel* const memberSelp) { AstVar* const varp = memberSelp->varp(); if (VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) return; - // Only a LOCAL constraint (leaf owned by the constraint's own - // class) flags the leaf as solver-owned. A leaf reached through - // a global constraint is left to basic randomization; the owning - // class basic-randomizes first and the solver overrides it. + // A leaf reached through a global constraint (owned by a class + // other than the constraint's own) is left to basic + // randomization, NOT flagged user3, so a standalone randomize() + // of its type still randomizes it; the owner overrides it via the + // solver (basicFirst path above) instead. if (VN_AS(varp->user2p(), NodeModule) != ownerClassp) return; + // Only a LOCAL constraint flags its leaf as solver-owned/skipped. if (!varp->user3()) varp->user3(true); }); }); diff --git a/test_regress/t/t_constraint_global_subobj_multi.py b/test_regress/t/t_constraint_global_subobj_multi.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_global_subobj_multi.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_constraint_global_subobj_multi.v b/test_regress/t/t_constraint_global_subobj_multi.v new file mode 100644 index 000000000..c3ec47514 --- /dev/null +++ b/test_regress/t/t_constraint_global_subobj_multi.v @@ -0,0 +1,63 @@ +// 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