Fix object randomization skipped by an unrelated global constraint
This commit is contained in:
parent
d84af81a11
commit
d860210111
|
|
@ -3776,6 +3776,25 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
return VN_IS(dtypep, ClassRefDType);
|
return VN_IS(dtypep, ClassRefDType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// True if this class has a constraint reaching into a sub-object (a global
|
||||||
|
// constraint): a member-select chain rooted at a rand class-typed handle.
|
||||||
|
// 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) {
|
||||||
|
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
|
// Get or create __VrandCb_pre/__VrandCb_post task for nested callbacks
|
||||||
AstTask* getCreateNestedCallbackTask(AstClass* classp, const string& suffix) {
|
AstTask* getCreateNestedCallbackTask(AstClass* classp, const string& suffix) {
|
||||||
const string name = "__VrandCb_" + suffix;
|
const string name = "__VrandCb_" + suffix;
|
||||||
|
|
@ -4561,6 +4580,10 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
UINFO(9, "Define randomize() for " << nodep);
|
UINFO(9, "Define randomize() for " << nodep);
|
||||||
nodep->baseMostClassp()->needRNG(true);
|
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();
|
FileLine* fl = nodep->fileline();
|
||||||
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
|
AstFunc* const randomizep = V3Randomize::newRandomizeFunc(m_memberMap, nodep);
|
||||||
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
|
AstVar* const fvarp = VN_AS(randomizep->fvarp(), Var);
|
||||||
|
|
@ -4857,8 +4880,20 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
beginValp = new AstConst{fl, AstConst::WidthedValue{}, 32, 1};
|
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 class that owns a global constraint (basicFirst, detected above)
|
||||||
|
// 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.
|
||||||
AstVarRef* const fvarRefp = new AstVarRef{fl, fvarp, VAccess::WRITE};
|
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 auto sizeArraysIt = m_sizeConstrainedArrays.find(nodep);
|
||||||
const bool needsSizePhase
|
const bool needsSizePhase
|
||||||
|
|
@ -4874,12 +4909,10 @@ class RandomizeVisitor final : public VNVisitor {
|
||||||
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
|
AstVarRef* const fvarRefReadp = fvarRefp->cloneTree(false);
|
||||||
fvarRefReadp->access(VAccess::READ);
|
fvarRefReadp->access(VAccess::READ);
|
||||||
|
|
||||||
AstFunc* const basicRandomizep
|
AstNodeExpr* const secondHalfp
|
||||||
= V3Randomize::newRandomizeFunc(m_memberMap, nodep, BASIC_RANDOMIZE_FUNC_NAME);
|
= basicFirst ? beginValp : new AstFuncRef{fl, basicRandomizep};
|
||||||
addBasicRandomizeBody(basicRandomizep, nodep, randModeVarp);
|
|
||||||
AstFuncRef* const basicRandomizeCallp = new AstFuncRef{fl, basicRandomizep};
|
|
||||||
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
|
randomizep->addStmtsp(new AstAssign{fl, fvarRefp->cloneTree(false),
|
||||||
new AstAnd{fl, fvarRefReadp, basicRandomizeCallp}});
|
new AstAnd{fl, fvarRefReadp, secondHalfp}});
|
||||||
|
|
||||||
// Call nested post_randomize on rand class-type members (IEEE 18.4.1)
|
// Call nested post_randomize on rand class-type members (IEEE 18.4.1)
|
||||||
if (classHasRandClassMembers(nodep)) {
|
if (classHasRandClassMembers(nodep)) {
|
||||||
|
|
@ -5405,15 +5438,30 @@ public:
|
||||||
explicit RandomizeVisitor(AstNetlist* nodep)
|
explicit RandomizeVisitor(AstNetlist* nodep)
|
||||||
: m_inlineUniqueNames{"__Vrandwith"} {
|
: m_inlineUniqueNames{"__Vrandwith"} {
|
||||||
createRandomizeClassVars(nodep);
|
createRandomizeClassVars(nodep);
|
||||||
// Mark variables in global constraints
|
// Mark variables solved by a global constraint so the owning class's
|
||||||
// These should not be randomized in nested class's __VBasicRand
|
// __VBasicRand does not also randomize them. This must run before any
|
||||||
nodep->foreach([&](AstConstraint* constrp) {
|
// class is lowered, otherwise a constrained variable whose class is
|
||||||
constrp->foreach([&](AstMemberSel* memberSelp) {
|
// lowered before its constraint's owner would be basic-randomized too.
|
||||||
// Only mark if this MemberSel was created during constraint cloning
|
// Only constraints whose enclosing class is actually randomized count: a
|
||||||
if (memberSelp->user2p()) {
|
// constraint living in a class that is never randomized through this
|
||||||
|
// object must not flag a shared rand variable, or a standalone
|
||||||
|
// randomize() of the sub-object's type stops randomizing it (issue
|
||||||
|
// #7833). And only the leaf data field is solved -- an intermediate
|
||||||
|
// class-typed sub-object must still be basic-randomized so its own
|
||||||
|
// unconstrained members are 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();
|
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.
|
||||||
|
if (VN_AS(varp->user2p(), NodeModule) != ownerClassp) return;
|
||||||
if (!varp->user3()) varp->user3(true);
|
if (!varp->user3()) varp->user3(true);
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
// 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
|
||||||
|
|
@ -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()
|
||||||
|
|
@ -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;
|
||||||
|
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
|
||||||
Loading…
Reference in New Issue