From 7cd49a802821f05977c88fcaa49d3a21d159f590 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Fri, 13 Mar 2026 16:05:18 +0100 Subject: [PATCH] Support dist and solve...before inside foreach constraints (#7245) (#7253) --- include/verilated_random.cpp | 4 +- include/verilated_random.h | 4 +- src/V3Randomize.cpp | 113 +++++++++++++++--- src/verilog.y | 6 +- .../t_constraint_solve_before_expr_unsup.out | 7 -- .../t/t_constraint_solve_before_expr_unsup.v | 21 ---- .../t/t_constraint_solve_before_unsup.out | 12 -- .../t/t_constraint_solve_before_unsup.v | 42 ------- ...r_unsup.py => t_randomize_dist_foreach.py} | 9 +- test_regress/t/t_randomize_dist_foreach.v | 41 +++++++ ...py => t_randomize_solve_before_foreach.py} | 11 +- .../t/t_randomize_solve_before_foreach.v | 113 ++++++++++++++++++ 12 files changed, 272 insertions(+), 111 deletions(-) delete mode 100644 test_regress/t/t_constraint_solve_before_expr_unsup.out delete mode 100644 test_regress/t/t_constraint_solve_before_expr_unsup.v delete mode 100644 test_regress/t/t_constraint_solve_before_unsup.out delete mode 100644 test_regress/t/t_constraint_solve_before_unsup.v rename test_regress/t/{t_constraint_solve_before_expr_unsup.py => t_randomize_dist_foreach.py} (76%) create mode 100644 test_regress/t/t_randomize_dist_foreach.v rename test_regress/t/{t_constraint_solve_before_unsup.py => t_randomize_solve_before_foreach.py} (68%) create mode 100644 test_regress/t/t_randomize_solve_before_foreach.v diff --git a/include/verilated_random.cpp b/include/verilated_random.cpp index 81f99c021..59fea30a1 100644 --- a/include/verilated_random.cpp +++ b/include/verilated_random.cpp @@ -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) { diff --git a/include/verilated_random.h b/include/verilated_random.h index 3202c8326..c3636e882 100644 --- a/include/verilated_random.h +++ b/include/verilated_random.h @@ -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& randmode) { m_randmodep = &randmode; } #ifdef VL_DEBUG void dump() const; diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index d96a7054c..04fe08943 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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); diff --git a/src/verilog.y b/src/verilog.y index a0f37468e..82db03c53 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -7843,12 +7843,10 @@ constraint_primary: // ==IEEE: constraint_primary constraint_expressionList: // ==IEEE: { constraint_expression } constraint_expression { $$ = $1; } | ySOLVE solve_before_list yBEFORE solve_before_list ';' - { ($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 ';' - { ($2)->v3warn(CONSTRAINTIGN, "Ignoring unsupported: solve-before only supported as top-level constraint statement"); - $$ = $1; DEL($3, $5); } + { $$ = addNextNull($1, new AstConstraintBefore{$2, $3, $5}); } ; constraint_expression: // ==IEEE: constraint_expression diff --git a/test_regress/t/t_constraint_solve_before_expr_unsup.out b/test_regress/t/t_constraint_solve_before_expr_unsup.out deleted file mode 100644 index 41d9d6a4b..000000000 --- a/test_regress/t/t_constraint_solve_before_expr_unsup.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_constraint_solve_before_expr_unsup.v b/test_regress/t/t_constraint_solve_before_expr_unsup.v deleted file mode 100644 index 2bedabd54..000000000 --- a/test_regress/t/t_constraint_solve_before_expr_unsup.v +++ /dev/null @@ -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 diff --git a/test_regress/t/t_constraint_solve_before_unsup.out b/test_regress/t/t_constraint_solve_before_unsup.out deleted file mode 100644 index 90519d765..000000000 --- a/test_regress/t/t_constraint_solve_before_unsup.out +++ /dev/null @@ -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 diff --git a/test_regress/t/t_constraint_solve_before_unsup.v b/test_regress/t/t_constraint_solve_before_unsup.v deleted file mode 100644 index 4d4d2bce7..000000000 --- a/test_regress/t/t_constraint_solve_before_unsup.v +++ /dev/null @@ -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 diff --git a/test_regress/t/t_constraint_solve_before_expr_unsup.py b/test_regress/t/t_randomize_dist_foreach.py similarity index 76% rename from test_regress/t/t_constraint_solve_before_expr_unsup.py rename to test_regress/t/t_randomize_dist_foreach.py index a00127d05..db1adb3f9 100755 --- a/test_regress/t/t_constraint_solve_before_expr_unsup.py +++ b/test_regress/t/t_randomize_dist_foreach.py @@ -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() diff --git a/test_regress/t/t_randomize_dist_foreach.v b/test_regress/t/t_randomize_dist_foreach.v new file mode 100644 index 000000000..344eeac5f --- /dev/null +++ b/test_regress/t/t_randomize_dist_foreach.v @@ -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 diff --git a/test_regress/t/t_constraint_solve_before_unsup.py b/test_regress/t/t_randomize_solve_before_foreach.py similarity index 68% rename from test_regress/t/t_constraint_solve_before_unsup.py rename to test_regress/t/t_randomize_solve_before_foreach.py index b7449248c..db1adb3f9 100755 --- a/test_regress/t/t_constraint_solve_before_unsup.py +++ b/test_regress/t/t_randomize_solve_before_foreach.py @@ -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() diff --git a/test_regress/t/t_randomize_solve_before_foreach.v b/test_regress/t/t_randomize_solve_before_foreach.v new file mode 100644 index 000000000..580de6b30 --- /dev/null +++ b/test_regress/t/t_randomize_solve_before_foreach.v @@ -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