Fix object randomization skipped by an unrelated global constraint (#7833) (#7838)

Fixes #7833.
This commit is contained in:
Yilou Wang 2026-06-25 15:30:05 +02:00 committed by GitHub
parent b73a897db3
commit f0f1c44dd6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 258 additions and 19 deletions

View File

@ -1171,16 +1171,18 @@ class ConstraintExprVisitor final : public VNVisitor {
// else: Global constraints keep nodep alive for write_var processing
relinker.relink(exprp);
// 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()
// Global / inline / class-level member-select refs key on the full path
// (so same-type sub-objects c1.x, c2.x stay distinct); a plain class-level
// variable keys on 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);
@ -3882,6 +3884,22 @@ class RandomizeVisitor final : public VNVisitor {
return VN_IS(dtypep, ClassRefDType);
});
}
// True if this class owns a global constraint: a member-select chain rooted
// at a rand class-typed handle reaching into a sub-object.
bool classOwnsGlobalConstraint(const AstClass* classp) const {
return classp->existsMember([](const AstClass*, const AstConstraint* constrp) {
bool owns = false;
constrp->foreach([&](const AstMemberSel* memberSelp) {
const AstNode* rootp = memberSelp->fromp();
while (const AstMemberSel* const sp = VN_CAST(rootp, MemberSel))
rootp = sp->fromp();
if (const AstVarRef* const refp = VN_CAST(rootp, VarRef)) {
if (VN_IS(refp->varp()->dtypep()->skipRefp(), ClassRefDType)) owns = true;
}
});
return owns;
});
}
// Get or create __VrandCb_pre/__VrandCb_post task for nested callbacks
AstTask* getCreateNestedCallbackTask(AstClass* classp, const string& suffix) {
const string name = "__VrandCb_" + suffix;
@ -4773,6 +4791,10 @@ class RandomizeVisitor final : public VNVisitor {
UINFO(9, "Define randomize() for " << nodep);
nodep->baseMostClassp()->needRNG(true);
// Detect global-constraint ownership BEFORE the constraint items are
// unlinked into setup tasks below (after that the member-selects are gone).
const bool basicFirst = classOwnsGlobalConstraint(nodep);
FileLine* fl = nodep->fileline();
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
@ -5081,29 +5103,41 @@ class RandomizeVisitor final : public VNVisitor {
beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1};
}
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
// A basicFirst owner basic-randomizes first, then the solver overrides the
// constrained leaves, so a globally-constrained leaf (not user3) is still
// basic-randomized when its type is randomized standalone.
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp, beginValp});
randomizep->addStmtsp(new AstAssign{
fl, fvarRefp, basicFirst ? new AstFuncRef{fl, basicRandomizep} : beginValp});
const auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep);
const bool needsSizePhase
= sizeArraysIt != m_sizeConstrainedArrays.end() && !sizeArraysIt->second.empty();
if (!needsSizePhase) {
// Size-only resize fallback (no element constraint, so no two-pass phase).
// Must run after the solver .next() that sets the size variable; emit it
// after whichever assignment below holds the solver call.
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);
AstFunc* const basicRandomizep
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep};
AstNodeExpr* const secondHalfp
= basicFirst ? beginValp : new AstFuncRef{fl, basicRandomizep};
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
new AstAnd{fl, fvarRefReadp, secondHalfp}});
if (basicFirst) emitResizeFallback();
// Call nested post_randomize on rand class-type members (IEEE 18.4.1)
if (classHasRandClassMembers(nodep)) {
@ -5643,15 +5677,21 @@ public:
explicit RandomizeVisitor(AstNetlist* nodep)
: m_inlineUniqueNames{"__Vrandwith"} {
createRandomizeClassVars(nodep);
// Mark variables in global constraints
// These should not be randomized in nested class's __VBasicRand
nodep->foreach([&](AstConstraint* constrp) {
constrp->foreach([&](AstMemberSel* memberSelp) {
// Only mark if this MemberSel was created during constraint cloning
if (memberSelp->user2p()) {
// Flag local constraint leaves as solver-owned so __VBasicRand skips them.
// Runs before any class is lowered. Only a randomized class counts, and
// only a leaf owned by the constraint's own class: a leaf reached through
// a global constraint stays basic-randomized so a standalone randomize()
// of its type still randomizes it (issue #7833); a class-typed sub-object
// is skipped so its own members are still basic-randomized.
nodep->foreach([&](AstClass* const classp) {
if (!classp->user1()) return;
classp->foreachMember([&](AstClass* const ownerClassp, AstConstraint* const constrp) {
constrp->foreach([&](AstMemberSel* const memberSelp) {
AstVar* const varp = memberSelp->varp();
if (VN_IS(varp->dtypep()->skipRefp(), ClassRefDType)) return;
if (VN_AS(varp->user2p(), NodeModule) != ownerClassp) return;
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,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