Handle multiple same-type sub-objects under a global constraint
This commit is contained in:
parent
d860210111
commit
79fcd07194
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
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
|
||||
Loading…
Reference in New Issue