Handle multiple same-type sub-objects under a global constraint

This commit is contained in:
Yilou Wang 2026-06-25 11:44:04 +02:00
parent d860210111
commit 79fcd07194
3 changed files with 106 additions and 11 deletions

View File

@ -1173,14 +1173,20 @@ class ConstraintExprVisitor final : public VNVisitor {
// For global constraints: check shared path-level set // For global constraints: check shared path-level set
// For inline constraints: check per-instance set (each __Vrandwith has own randomizer) // 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 const bool alreadyWritten = isGlobalConstrained ? m_writtenVars.count(smtName) > 0
: m_inlineInitTaskp ? m_inlineWrittenVars.count(smtName) > 0 : m_inlineInitTaskp ? m_inlineWrittenVars.count(smtName) > 0
: membersel ? m_writtenVars.count(smtName) > 0
: varp->user3(); : varp->user3();
const bool shouldWriteVar = !alreadyWritten; const bool shouldWriteVar = !alreadyWritten;
if (shouldWriteVar) { if (shouldWriteVar) {
// Track this variable path as written // 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); if (m_inlineInitTaskp) m_inlineWrittenVars.insert(smtName);
// For global constraints, delete nodep after processing // For global constraints, delete nodep after processing
if (isGlobalConstrained && !nodep->backp()) VL_DO_DANGLING(pushDeletep(nodep), nodep); 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 // Such a class basic-randomizes first and lets the solver override the
// constrained leaves afterwards, so a leaf shared with a standalone // constrained leaves afterwards, so a leaf shared with a standalone
// randomize() of the sub-object's type is still basic-randomized there. // 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) { return classp->existsMember([](const AstClass*, const AstConstraint* constrp) {
bool owns = false; bool owns = false;
constrp->foreach([&](const AstMemberSel* memberSelp) { constrp->foreach([&](const AstMemberSel* memberSelp) {
@ -4887,10 +4893,13 @@ class RandomizeVisitor final : public VNVisitor {
// basic-randomizes FIRST and lets the solver override the constrained // basic-randomizes FIRST and lets the solver override the constrained
// leaves afterwards. This way a leaf that is only globally constrained // leaves afterwards. This way a leaf that is only globally constrained
// (not user3) is still basic-randomized when its type is randomized // (not user3) is still basic-randomized when its type is randomized
// standalone, while the owner's solver result wins. This assumes such an // standalone, while the owner's solver result wins.
// owner has no size-constrained arrays of its own (needsSizePhase): a // KNOWN LIMITATION: if such an owner also declares its own
// global constraint cannot reach an array element, so the two are mutually // size-constrained array, the size-phase solver call is emitted above
// exclusive today and basic randomization stays before the solver. // (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}; AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
randomizep->addStmtsp(new AstAssign{ randomizep->addStmtsp(new AstAssign{
fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp}); fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp});
@ -5455,11 +5464,13 @@ public:
constrp->foreach([&](AstMemberSel* const memberSelp) { constrp->foreach([&](AstMemberSel* const memberSelp) {
AstVar* const varp = memberSelp->varp(); AstVar* const varp = memberSelp->varp();
if (VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) return; if (VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) return;
// Only a LOCAL constraint (leaf owned by the constraint's own // A leaf reached through a global constraint (owned by a class
// class) flags the leaf as solver-owned. A leaf reached through // other than the constraint's own) is left to basic
// a global constraint is left to basic randomization; the owning // randomization, NOT flagged user3, so a standalone randomize()
// class basic-randomizes first and the solver overrides it. // 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; 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); if (!varp->user3()) varp->user3(true);
}); });
}); });

View File

@ -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()

View File

@ -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