diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 8bae51d2c..a6ca93d3d 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1675,10 +1675,16 @@ public: // === AstNodeForeach === class AstConstraintForeach final : public AstNodeForeach { // Constraint foreach statement + bool m_soft; // is soft foreach, non-standard extension public: - AstConstraintForeach(FileLine* fl, AstForeachHeader* headerp, AstNode* bodyp) - : ASTGEN_SUPER_ConstraintForeach(fl, headerp, bodyp) {} + AstConstraintForeach(FileLine* fl, AstForeachHeader* headerp, AstNode* bodyp, + bool soft = false) + : ASTGEN_SUPER_ConstraintForeach(fl, headerp, bodyp) + , m_soft{soft} {} ASTGEN_MEMBERS_AstConstraintForeach; + bool isSoft() const { return m_soft; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; }; class AstForeach final : public AstNodeForeach { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 62182331c..c01f657c4 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2307,6 +2307,14 @@ void AstCover::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, isSeqEvent); this->AstNodeCoverOrAssert::dumpJson(str); } +void AstConstraintForeach::dump(std::ostream& str) const { + this->AstNodeForeach::dump(str); + if (isSoft()) str << "[SOFT]"; +} +void AstConstraintForeach::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isSoft); + dumpJsonGen(str); +} void AstClocking::dump(std::ostream& str) const { this->AstNode::dump(str); if (isDefault()) str << " [DEFAULT]"; diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index efc33edee..d032eaabc 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -298,6 +298,12 @@ class LinkParseVisitor final : public VNVisitor { v3Global.useRandomizeMethods(true); iterateChildren(nodep); } + void visit(AstConstraintForeach* nodep) override { + if (nodep->isSoft()) { + nodep->v3warn(NONSTD, "Non-standard soft foreach"); + nodep->foreach([](AstConstraintExpr* exprp) { exprp->isSoft(true); }); + } + } void visit(AstEnumDType* nodep) override { if (nodep->name() == "") { nodep->name(nameFromTypedef(nodep)); // Might still remain "" diff --git a/src/verilog.y b/src/verilog.y index 3b8b66c40..2238b08ab 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -8166,6 +8166,9 @@ constraint_expression: // ==IEEE: constraint_expression // // IEEE says array_identifier here, but dotted accepted in VMM + 1800-2009 | yFOREACH '(' idClassSelForeach ')' constraint_set { $$ = new AstConstraintForeach{$1, $3, $5}; } + // // Non-IEEE extension, soft foreach + | ySOFT yFOREACH '(' idClassSelForeach ')' constraint_set + { $$ = new AstConstraintForeach{$2, $4, $6, true}; } // // soft is 1800-2012 | yDISABLE ySOFT constraint_primary ';' { AstConstraintExpr* const newp = new AstConstraintExpr{$1, $3}; diff --git a/test_regress/t/t_randomize_soft_foreach.py b/test_regress/t/t_randomize_soft_foreach.py new file mode 100755 index 000000000..e200c9159 --- /dev/null +++ b/test_regress/t/t_randomize_soft_foreach.py @@ -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(verilator_flags2=["--Wno-NONSTD"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_randomize_soft_foreach.v b/test_regress/t/t_randomize_soft_foreach.v new file mode 100644 index 000000000..5958576c9 --- /dev/null +++ b/test_regress/t/t_randomize_soft_foreach.v @@ -0,0 +1,94 @@ +// 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 + +// 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 check_range(gotv,minv,maxv) do if ((gotv) < (minv) || (gotv) > (maxv)) begin $write("%%Error: %s:%0d: got=%0d exp=%0d-%0d\n", `__FILE__,`__LINE__, (gotv), (minv), (maxv)); `stop; end while(0); +// verilog_format: on + +class Cls1; + rand int arr[10]; + constraint c_cls { + soft foreach(arr[i]) arr[i] == i; + } +endclass + +class Cls2; + rand int arr[10]; + constraint c_cls { + soft foreach(arr[i]) arr[i] == i; + arr[1] == 10; + } +endclass + +class Cls3; + rand int arr[10]; + constraint c_cls { + soft foreach(arr[i]) arr[i] < i; + arr[5] == 3; + } +endclass + +class Cls4; + rand int arr[10][20]; + rand bit a; + constraint c_cls { + soft foreach(arr[i]) + foreach (arr[i][j]) + soft arr[i][j] == i + j; + arr[5][10] == 2; + } +endclass + +module t; + Cls1 cls1; + Cls2 cls2; + Cls3 cls3; + Cls4 cls4; + int ok; + + initial begin + cls1 = new; + cls2 = new; + cls3 = new; + cls4 = new; + ok = cls1.randomize(); + `checkd(ok, 1); + foreach(cls1.arr[i]) begin + `checkd(cls1.arr[i], i); + end + + ok = cls2.randomize(); + `checkd(ok, 1); + foreach(cls2.arr[i]) begin + if (i != 1) + `checkd(cls2.arr[i], i); + end + `checkd(cls2.arr[1], 10); + + repeat (10) begin + ok = cls3.randomize(); + `checkd(ok, 1); + foreach(cls3.arr[i]) begin + if (cls3.arr[i] >= i) $stop; + end + `checkd(cls3.arr[5], 3); + end + + ok = cls4.randomize(); + `checkd(ok, 1); + foreach(cls4.arr[i, j]) begin + if (i != 5 || j != 10) begin + `checkd(cls4.arr[i][j], i+j); + end + end + `checkd(cls4.arr[5][10], 2); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_randomize_soft_foreach_bad.out b/test_regress/t/t_randomize_soft_foreach_bad.out new file mode 100644 index 000000000..168b4e7fc --- /dev/null +++ b/test_regress/t/t_randomize_soft_foreach_bad.out @@ -0,0 +1,15 @@ +%Warning-NONSTD: t/t_randomize_soft_foreach.v:16:10: Non-standard soft foreach + 16 | soft foreach(arr[i]) arr[i] == i; + | ^~~~~~~ + ... For warning description see https://verilator.org/warn/NONSTD?v=latest + ... Use "/* verilator lint_off NONSTD */" and lint_on around source to disable this message. +%Warning-NONSTD: t/t_randomize_soft_foreach.v:23:10: Non-standard soft foreach + 23 | soft foreach(arr[i]) arr[i] == i; + | ^~~~~~~ +%Warning-NONSTD: t/t_randomize_soft_foreach.v:31:10: Non-standard soft foreach + 31 | soft foreach(arr[i]) arr[i] < i; + | ^~~~~~~ +%Warning-NONSTD: t/t_randomize_soft_foreach.v:40:10: Non-standard soft foreach + 40 | soft foreach(arr[i]) + | ^~~~~~~ +%Error: Exiting due to diff --git a/test_regress/t/t_randomize_soft_foreach_bad.py b/test_regress/t/t_randomize_soft_foreach_bad.py new file mode 100755 index 000000000..4e8516653 --- /dev/null +++ b/test_regress/t/t_randomize_soft_foreach_bad.py @@ -0,0 +1,17 @@ +#!/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('linter') +test.top_filename = 't/t_randomize_soft_foreach.v' + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes()