Support dist and solve...before inside foreach constraints (#7245) (#7253)

This commit is contained in:
Yilou Wang 2026-03-13 16:05:18 +01:00 committed by GitHub
parent e0f1f316aa
commit 7cd49a8028
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 272 additions and 111 deletions

View File

@ -722,8 +722,8 @@ void VlRandomizer::clearAll() {
void VlRandomizer::markRandc(const char* name) { m_randcVarNames.insert(name); }
void VlRandomizer::solveBefore(const char* beforeName, const char* afterName) {
m_solveBefore.emplace_back(std::string(beforeName), std::string(afterName));
void VlRandomizer::solveBefore(const std::string& beforeName, const std::string& afterName) {
m_solveBefore.emplace_back(beforeName, afterName);
}
bool VlRandomizer::nextPhased(VlRNG& rngr) {

View File

@ -631,8 +631,8 @@ public:
void clearConstraints();
void clearAll(); // Clear both constraints and variables
void markRandc(const char* name); // Mark variable as randc for cyclic tracking
void solveBefore(const char* beforeName,
const char* afterName); // Register solve-before ordering
void solveBefore(const std::string& beforeName,
const std::string& afterName); // Register solve-before ordering
void set_randmode(const VlQueue<CData>& randmode) { m_randmodep = &randmode; }
#ifdef VL_DEBUG
void dump() const;

View File

@ -767,6 +767,88 @@ class ConstraintExprVisitor final : public VNVisitor {
return "";
}
// Build a C++ expression (as AstNodeExpr) that evaluates to a const char*
// containing the SMT variable name for a solve-before variable reference.
// Handles simple vars, member selects, and array element selects (for foreach).
// Returns nullptr for unsupported expression types.
// Helper: build a dynamic AstCExpr for "baseName[idx]" pattern
AstCExpr* buildArraySelNameExpr(FileLine* fl, const std::string& baseName,
const AstArraySel* selp) {
AstCExpr* const p = new AstCExpr{fl, ""};
p->add("(\""s + baseName + "[\" + std::to_string(");
p->add(selp->bitp()->cloneTreePure(false));
p->add(") + \"]\")");
p->dtypeSetString();
return p;
}
// Helper: get fromp from MemberSel or StructSel
static AstNodeExpr* getSelFromp(AstNodeExpr* exprp) {
if (AstMemberSel* const mp = VN_CAST(exprp, MemberSel)) return mp->fromp();
if (AstStructSel* const sp = VN_CAST(exprp, StructSel)) return sp->fromp();
return nullptr;
}
AstNodeExpr* buildSolveBeforeNameExpr(FileLine* fl, AstNodeExpr* exprp) {
if (const AstVarRef* const varrefp = VN_CAST(exprp, VarRef)) {
AstCExpr* const p = new AstCExpr{fl, AstCExpr::Pure{}, "\"" + varrefp->name() + "\"s"};
p->dtypeSetString();
return p;
}
// Handle MemberSel or StructSel (V3Width converts MemberSel -> StructSel for structs)
if (AstNodeExpr* const selFromp = getSelFromp(exprp)) {
const std::string selName = exprp->name();
// Check if fromp chain contains ArraySel (e.g., cfg[i].w)
if (const AstArraySel* const arrSelp = VN_CAST(selFromp, ArraySel)) {
std::string baseName;
if (const AstVarRef* const vp = VN_CAST(arrSelp->fromp(), VarRef)) {
baseName = vp->name();
} else if (const AstMemberSel* const mp = VN_CAST(arrSelp->fromp(), MemberSel)) {
baseName = buildMemberPath(mp);
}
if (baseName.empty()) return nullptr;
AstCExpr* const p = new AstCExpr{fl, ""};
p->add("(\""s + baseName + "[\" + std::to_string(");
p->add(arrSelp->bitp()->cloneTreePure(false));
p->add(") + \"]." + selName + "\")");
p->dtypeSetString();
return p;
}
// Static member path (obj.field)
if (const AstVarRef* const vp = VN_CAST(selFromp, VarRef)) {
const std::string path = vp->name() + "." + selName;
AstCExpr* const p = new AstCExpr{fl, AstCExpr::Pure{}, "\"" + path + "\"s"};
p->dtypeSetString();
return p;
}
if (VN_IS(selFromp, MemberSel)) {
const std::string path
= buildMemberPath(VN_AS(selFromp, MemberSel)) + "." + selName;
AstCExpr* const p = new AstCExpr{fl, AstCExpr::Pure{}, "\"" + path + "\"s"};
p->dtypeSetString();
return p;
}
return nullptr;
}
if (const AstArraySel* const selp = VN_CAST(exprp, ArraySel)) {
// arr[i] -> dynamic name
std::string baseName;
if (const AstVarRef* const vp = VN_CAST(selp->fromp(), VarRef)) {
baseName = vp->name();
} else if (const AstMemberSel* const mp = VN_CAST(selp->fromp(), MemberSel)) {
baseName = buildMemberPath(mp);
}
if (baseName.empty()) return nullptr;
return buildArraySelNameExpr(fl, baseName, selp);
}
// Packed struct member: SEL(ARRAYSEL(...)) -- bit select on array element.
// Solver registers the whole element, so promote to array element level.
if (const AstSel* const bitSelp = VN_CAST(exprp, Sel)) {
return buildSolveBeforeNameExpr(fl, bitSelp->fromp());
}
return nullptr;
}
AstSFormatF* getConstFormat(AstNodeExpr* nodep) {
return new AstSFormatF{nodep->fileline(), (nodep->width() & 3) ? "#b%b" : "#x%x", false,
nodep};
@ -1876,32 +1958,25 @@ class ConstraintExprVisitor final : public VNVisitor {
AstNodeModule* const genModp = VN_AS(m_genp->user2p(), NodeModule);
for (AstNodeExpr* lhsp = nodep->lhssp(); lhsp; lhsp = VN_CAST(lhsp->nextp(), NodeExpr)) {
const std::string lhsName = extractSolveBeforeVarName(lhsp);
if (lhsName.empty()) {
lhsp->v3warn(CONSTRAINTIGN,
"Unsupported: non-variable expression in solve...before");
AstNodeExpr* const lhsTestp = buildSolveBeforeNameExpr(fl, lhsp);
if (!lhsTestp) {
lhsp->v3fatalSrc("Unexpected expression type in solve...before lhs");
continue;
}
VL_DO_DANGLING(lhsTestp->deleteTree(), lhsTestp);
for (AstNodeExpr* rhsp = nodep->rhssp(); rhsp;
rhsp = VN_CAST(rhsp->nextp(), NodeExpr)) {
const std::string rhsName = extractSolveBeforeVarName(rhsp);
if (rhsName.empty()) {
rhsp->v3warn(CONSTRAINTIGN,
"Unsupported: non-variable expression in solve...before");
AstNodeExpr* const rhsNamep = buildSolveBeforeNameExpr(fl, rhsp);
if (!rhsNamep) {
rhsp->v3fatalSrc("Unexpected expression type in solve...before rhs");
continue;
}
AstCMethodHard* const callp = new AstCMethodHard{
fl, new AstVarRef{fl, genModp, m_genp, VAccess::READWRITE},
VCMethod::RANDOMIZER_SOLVE_BEFORE};
callp->dtypeSetVoid();
AstNodeExpr* const beforeNamep
= new AstCExpr{fl, AstCExpr::Pure{}, "\"" + lhsName + "\""};
beforeNamep->dtypeSetUInt32();
AstNodeExpr* const afterNamep
= new AstCExpr{fl, AstCExpr::Pure{}, "\"" + rhsName + "\""};
afterNamep->dtypeSetUInt32();
callp->addPinsp(beforeNamep);
callp->addPinsp(afterNamep);
callp->addPinsp(buildSolveBeforeNameExpr(fl, lhsp));
callp->addPinsp(rhsNamep);
nodep->addHereThisAsNext(callp->makeStmt());
}
}
@ -3784,6 +3859,12 @@ class RandomizeVisitor final : public VNVisitor {
continue;
}
// Recursively handle ConstraintForeach nodes (dist can be inside foreach)
if (AstConstraintForeach* const cfep = VN_CAST(itemp, ConstraintForeach)) {
if (cfep->bodyp()) lowerDistConstraints(taskp, cfep->bodyp());
continue;
}
AstConstraintExpr* const constrExprp = VN_CAST(itemp, ConstraintExpr);
if (!constrExprp) continue;
AstDist* const distp = VN_CAST(constrExprp->exprp(), Dist);

View File

@ -7843,12 +7843,10 @@ constraint_primary<nodeExprp>: // ==IEEE: constraint_primary
constraint_expressionList<nodep>: // ==IEEE: { constraint_expression }
constraint_expression { $$ = $1; }
| ySOLVE solve_before_list yBEFORE solve_before_list ';'
{ ($<fl>1)->v3warn(CONSTRAINTIGN, "Ignoring unsupported: solve-before only supported as top-level constraint statement");
$$ = nullptr; DEL($2, $4); }
{ $$ = new AstConstraintBefore{$1, $2, $4}; }
| constraint_expressionList constraint_expression { $$ = addNextNull($1, $2); }
| constraint_expressionList ySOLVE solve_before_list yBEFORE solve_before_list ';'
{ ($<fl>2)->v3warn(CONSTRAINTIGN, "Ignoring unsupported: solve-before only supported as top-level constraint statement");
$$ = $1; DEL($3, $5); }
{ $$ = addNextNull($1, new AstConstraintBefore{$<fl>2, $3, $5}); }
;
constraint_expression<nodep>: // ==IEEE: constraint_expression

View File

@ -1,7 +0,0 @@
%Warning-CONSTRAINTIGN: t/t_constraint_solve_before_expr_unsup.v:12:27: Unsupported: non-variable expression in solve...before
: ... note: In instance 't'
12 | constraint c { solve arr[0] before y; }
| ^
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -1,21 +0,0 @@
// 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 Cls;
rand int x;
rand int y;
rand int arr[4];
constraint c { solve arr[0] before y; } // BAD: non-variable expression
endclass
module t;
// verilator lint_off IMPLICITSTATIC
initial begin
Cls c = new;
void'(c.randomize());
end
endmodule

View File

@ -1,12 +0,0 @@
%Warning-CONSTRAINTIGN: t/t_constraint_solve_before_unsup.v:20:7: Ignoring unsupported: solve-before only supported as top-level constraint statement
20 | solve x before data[i];
| ^~~~~
... For warning description see https://verilator.org/warn/CONSTRAINTIGN?v=latest
... Use "/* verilator lint_off CONSTRAINTIGN */" and lint_on around source to disable this message.
%Warning-CONSTRAINTIGN: t/t_constraint_solve_before_unsup.v:29:7: Ignoring unsupported: solve-before only supported as top-level constraint statement
29 | solve x before cfg[i].w, cfg[i].r;
| ^~~~~
%Warning-CONSTRAINTIGN: t/t_constraint_solve_before_unsup.v:30:7: Ignoring unsupported: solve-before only supported as top-level constraint statement
30 | solve cfg[i].l before cfg[i].x;
| ^~~~~
%Error: Exiting due to

View File

@ -1,42 +0,0 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
typedef struct {
rand bit l;
rand bit x;
rand bit w;
rand bit r;
} reg_t;
class Packet;
rand bit [7:0] data[5];
rand bit x;
constraint c_data {
foreach (data[i]) {
solve x before data[i];
data[i] inside {8'h10, 8'h20, 8'h30, 8'h40, 8'h50};
}
}
rand reg_t cfg[];
constraint solves_only_c {
foreach (cfg[i]) {
solve x before cfg[i].w, cfg[i].r;
solve cfg[i].l before cfg[i].x;
}
}
endclass
module t;
Packet p;
initial begin
p = new;
void'(p.randomize());
end
endmodule

View File

@ -9,8 +9,13 @@
import vltest_bootstrap
test.scenarios('linter')
test.scenarios('simulator')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,41 @@
// 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
module t;
class item;
rand int unsigned arr[4];
rand int unsigned wgt_zero;
rand int unsigned wgt_nonzero;
constraint wgt_c {
wgt_zero inside {[1:10]};
wgt_nonzero inside {[1:10]};
}
constraint dist_foreach_c {
foreach (arr[i]) {
arr[i] dist {0 :/ wgt_zero, [1:15] :/ wgt_nonzero};
}
}
endclass
initial begin
static item it = new;
repeat (20) begin
`checkd(it.randomize(), 1);
foreach (it.arr[i]) begin
`checkd(it.arr[i] <= 32'd15, 1);
end
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -4,13 +4,18 @@
# 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: 2024 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.scenarios('simulator')
test.lint(fails=test.vlt_all, expect_filename=test.golden_filename)
if not test.have_solver:
test.skip("No constraint solver installed")
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,113 @@
// 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);
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
// verilog_format: on
typedef struct {
rand bit l;
rand bit x;
rand bit w;
rand bit r;
} reg_t;
typedef struct packed {
bit l;
bit x;
bit w;
bit r;
} preg_t;
module t;
class item;
rand bit [3:0] mode;
rand bit [7:0] data[4];
rand int x;
rand int y;
rand int arr[4];
constraint mode_c {
mode inside {[0:3]};
}
constraint data_c {
foreach (data[i]) {
solve mode before data[i];
if (mode == 0)
data[i] == 8'h00;
else
data[i] inside {[8'd1:8'd255]};
}
}
// Static array index in solve...before (non-foreach)
constraint arr_c {
solve arr[0] before y;
}
endclass
class Packet;
rand bit [7:0] pdata[5];
rand bit px;
rand reg_t cfg[3];
rand preg_t pcfg[3];
constraint c_pdata {
foreach (pdata[i]) {
solve px before pdata[i];
pdata[i] inside {8'h10, 8'h20, 8'h30, 8'h40, 8'h50};
}
}
constraint c_cfg {
foreach (cfg[i]) {
solve px before cfg[i].w, cfg[i].r;
solve cfg[i].l before cfg[i].x;
}
}
constraint c_pcfg {
foreach (pcfg[i]) {
solve px before pcfg[i].w, pcfg[i].r;
solve pcfg[i].l before pcfg[i].x;
}
}
endclass
initial begin
static item it = new;
static Packet pkt = new;
// Test 1: solve...before with conditional constraints
repeat (20) begin
`checkd(it.randomize(), 1);
if (it.mode == 0) begin
foreach (it.data[i]) begin
`checkh(it.data[i], 8'h00);
end
end
end
// Test 2: solve...before with unpacked/packed struct array members
repeat (20) begin
`checkd(pkt.randomize(), 1);
foreach (pkt.pdata[i]) begin
`checkd(pkt.pdata[i] inside {8'h10, 8'h20, 8'h30, 8'h40, 8'h50}, 1);
end
end
// Test 3: solve...before with static array index (non-foreach)
repeat (20) begin
`checkd(it.randomize(), 1);
end
$write("*-* All Finished *-*\n");
$finish;
end
endmodule