Fix soft cross-object constraint priority inversion (#7228) (#7233)

Emit nested constraint setup tasks in depth-first order (deepest first)
so outer-scope soft constraints get higher priority per IEEE 18.5.13.
This commit is contained in:
Yilou Wang 2026-03-10 23:02:15 +00:00 committed by GitHub
parent 15c60c83aa
commit 3ba9077726
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 123 additions and 6 deletions

View File

@ -3959,18 +3959,14 @@ class RandomizeVisitor final : public VNVisitor {
AstNodeExpr* beginValp = nullptr;
AstVar* genp = getRandomGenerator(nodep);
if (genp) {
// Phase 1: Process all constraints (create tasks, run ConstraintExprVisitor)
// Setup task refs are NOT added to setupAllTaskp here -- done in phase 2
nodep->foreachMember([&](AstClass* const classp, AstConstraint* const constrp) {
AstTask* taskp = VN_AS(constrp->user2p(), Task);
if (!taskp) {
taskp = newSetupConstraintTask(classp, constrp->name());
constrp->user2p(taskp);
}
AstTaskRef* const setupTaskRefp = new AstTaskRef{constrp->fileline(), taskp};
setupTaskRefp->classOrPackagep(classp);
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(nodep);
setupAllTaskp->addStmtsp(setupTaskRefp->makeStmt());
if (AstTask* const resizeTaskp = VN_CAST(constrp->user3p(), Task)) {
AstTask* const resizeAllTaskp = getCreateAggrResizeTask(nodep);
@ -3989,6 +3985,38 @@ class RandomizeVisitor final : public VNVisitor {
nodep, constrp, constrp->itemsp()->unlinkFrBackWithNext()));
}
});
// Phase 2: Add setup task refs in depth order (deepest first = lowest priority)
// Ensures outer-scope soft constraints override inner-scope (IEEE 18.5.13)
{
// Count nesting depth by GLOBAL_CONSTRAINT_SEPARATOR occurrences
const auto constraintDepth = [](const AstConstraint* constrp) {
int depth = 0;
size_t pos = 0;
while ((pos = constrp->name().find(GLOBAL_CONSTRAINT_SEPARATOR, pos))
!= std::string::npos) {
++depth;
pos += std::strlen(GLOBAL_CONSTRAINT_SEPARATOR);
}
return depth;
};
int maxDepth = 0;
nodep->foreachMember([&](AstClass* const, AstConstraint* const constrp) {
maxDepth = std::max(maxDepth, constraintDepth(constrp));
});
AstTask* const setupAllTaskp = getCreateConstraintSetupFunc(nodep);
for (int d = maxDepth; d >= 0; --d) {
nodep->foreachMember(
[&](AstClass* const classp, AstConstraint* const constrp) {
if (constraintDepth(constrp) != d) return;
AstTask* const taskp = VN_AS(constrp->user2p(), Task);
AstTaskRef* const setupTaskRefp
= new AstTaskRef{constrp->fileline(), taskp};
setupTaskRefp->classOrPackagep(classp);
setupAllTaskp->addStmtsp(setupTaskRefp->makeStmt());
});
}
}
// For derived classes: clone write_var calls from parent's randomize()
if (nodep->extendsp()) {
AstClass* parentClassp = nodep->extendsp()->classp();

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,68 @@
// 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
// verilog_format: off
`define stop $stop
`define checkd(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
// Test: Soft cross-object constraints should respect IEEE 1800-2023 18.5.13
// outer-scope soft has higher priority than inner-scope soft.
class sub_cfg_c;
rand int unsigned timeout;
rand bit enabled;
constraint soft_defaults {
soft timeout == 1000;
soft enabled == 0;
}
endclass
class parent_cfg_c;
rand bit enabled;
rand sub_cfg_c sub_a;
constraint soft_defaults { soft enabled == 0; }
constraint propagate_cons {
if (enabled) { sub_a.enabled == 1; }
}
function new();
sub_a = new();
endfunction
endclass
class top_test_c;
rand parent_cfg_c cfg;
rand sub_cfg_c extra_cfg;
constraint cfg_hard_cons { cfg.enabled == 1; }
constraint cfg_soft_cons {
soft cfg.sub_a.timeout == 5000;
soft extra_cfg.timeout == 9999;
soft extra_cfg.enabled == 1;
}
function new();
cfg = new();
extra_cfg = new();
endfunction
endclass
module t;
initial begin
static top_test_c obj = new();
repeat (10) begin
`checkd(obj.randomize(), 1)
// Two-level cross-object soft: parent's soft overrides child's
`checkd(obj.cfg.sub_a.timeout, 32'd5000)
// One-level cross-object soft: parent's soft overrides child's
`checkd(obj.extra_cfg.timeout, 32'd9999)
`checkd(obj.extra_cfg.enabled, 1'b1)
// Hard constraint propagation still works
`checkd(obj.cfg.enabled, 1'b1)
`checkd(obj.cfg.sub_a.enabled, 1'b1)
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule