From 3b6674386c029fbd5859a38a680eeac875a7eea4 Mon Sep 17 00:00:00 2001 From: Wei-Lun Chiu <60381044+weilun-chiu@users.noreply.github.com> Date: Sat, 17 Jan 2026 09:03:00 -0800 Subject: [PATCH] Support unbounded '$' in inside range expressions (#6935) (#6938) --- docs/CONTRIBUTORS | 1 + docs/guide/warnings.rst | 10 +++ src/V3AstNodes.cpp | 28 ++++++++ src/V3Error.h | 4 +- src/V3Width.cpp | 1 + test_regress/t/t_inside_unbounded.py | 16 +++++ test_regress/t/t_inside_unbounded.v | 80 +++++++++++++++++++++ test_regress/t/t_inside_unbounded_bad.out | 6 ++ test_regress/t/t_inside_unbounded_bad.py | 16 +++++ test_regress/t/t_inside_unbounded_bad.v | 15 ++++ test_regress/t/t_inside_unbounded_nowarn.py | 16 +++++ test_regress/t/t_inside_unbounded_nowarn.v | 16 +++++ 12 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 test_regress/t/t_inside_unbounded.py create mode 100644 test_regress/t/t_inside_unbounded.v create mode 100644 test_regress/t/t_inside_unbounded_bad.out create mode 100644 test_regress/t/t_inside_unbounded_bad.py create mode 100644 test_regress/t/t_inside_unbounded_bad.v create mode 100644 test_regress/t/t_inside_unbounded_nowarn.py create mode 100644 test_regress/t/t_inside_unbounded_nowarn.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index f43f79424..09f9ea2a0 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -258,6 +258,7 @@ Vassilis Papaefstathiou Veripool API Bot Victor Besyakov Vito Gamberini +Wei-Lun Chiu William D. Jones Wilson Snyder Xi Zhang diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index 9f7853543..a0380e505 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -1123,6 +1123,16 @@ List Of Warnings simulate correctly. +.. option:: INSIDETRUE + + Warns that an ``inside`` expression contains a range with unbounded + values on both sides (``[$:$]``), which is always true. This is likely + a coding mistake. + + Ignoring this warning will only suppress the lint check; it will + simulate correctly. + + .. option:: LATCH .. TODO better example diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 0a73c0c22..eaec34c0e 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -356,6 +356,34 @@ const char* AstExecGraph::broken() const { AstNodeExpr* AstInsideRange::newAndFromInside(AstNodeExpr* exprp, AstNodeExpr* lhsp, AstNodeExpr* rhsp) { + const bool lhsUnbounded = VN_IS(lhsp, Unbounded); + const bool rhsUnbounded = VN_IS(rhsp, Unbounded); + + if (lhsUnbounded && rhsUnbounded) { + fileline()->v3warn(INSIDETRUE, + "Unbounded on both sides of inside range [$:$] is always true"); + VL_DO_DANGLING(exprp->deleteTree(), exprp); + VL_DO_DANGLING(lhsp->deleteTree(), lhsp); + VL_DO_DANGLING(rhsp->deleteTree(), rhsp); + return new AstConst{fileline(), AstConst::BitTrue{}}; + } + + if (lhsUnbounded) { + // [$:N] - only check expr <= rhs + // Use exprp directly (not cloned) so ExprStmt side effects are preserved + VL_DO_DANGLING(lhsp->deleteTree(), lhsp); + AstNodeExpr* const bp = new AstLte{fileline(), exprp, rhsp}; + bp->fileline()->modifyWarnOff(V3ErrorCode::CMPCONST, true); + return bp; + } else if (rhsUnbounded) { + // [N:$] - only check expr >= lhs + VL_DO_DANGLING(rhsp->deleteTree(), rhsp); + AstNodeExpr* const ap = new AstGte{fileline(), exprp, lhsp}; + ap->fileline()->modifyWarnOff(V3ErrorCode::UNSIGNED, true); + return ap; + } + + // Normal case: [N:M] - check expr >= lhs && expr <= rhs AstNodeExpr* const ap = new AstGte{fileline(), exprp, lhsp}; AstNodeExpr* lteLhsp; if (const AstExprStmt* const exprStmt = VN_CAST(exprp, ExprStmt)) { diff --git a/src/V3Error.h b/src/V3Error.h index a6284cc52..0f3958243 100644 --- a/src/V3Error.h +++ b/src/V3Error.h @@ -122,6 +122,7 @@ public: INFINITELOOP, // Infinite loop INITIALDLY, // Initial delayed statement INSECURE, // Insecure options + INSIDETRUE, // Inside range is always true LATCH, // Latch detected outside of always_latch block LITENDIAN, // Little endian, renamed to ASCRANGE MINTYPMAXDLY, // Unsupported: min/typ/max delay expressions @@ -224,7 +225,8 @@ public: "ENCAPSULATED", "ENDLABEL", "ENUMITEMWIDTH", "ENUMVALUE", "EOFNEWLINE", "FUNCTIMECTL", "GENCLK", "GENUNNAMED", "HIERBLOCK", "HIERPARAM", "IFDEPTH", "IGNOREDRETURN", "IMPERFECTSCH", "IMPLICIT", "IMPLICITSTATIC", "IMPORTSTAR", "IMPURE", "INCABSPATH", - "INFINITELOOP", "INITIALDLY", "INSECURE", "LATCH", "LITENDIAN", "MINTYPMAXDLY", + "INFINITELOOP", "INITIALDLY", "INSECURE", "INSIDETRUE", "LATCH", "LITENDIAN", + "MINTYPMAXDLY", "MISINDENT", "MODDUP", "MODMISSING", "MULTIDRIVEN", "MULTITOP", "NEWERSTD", "NOEFFECT", "NOLATCH", "NONSTD", "NORETURN", "NULLPORT", "PARAMNODEFAULT", "PINCONNECTEMPTY", "PINMISSING", "PINNOCONNECT", "PINNOTFOUND", "PKGNODECL", "PREPROCZERO", "PROCASSINIT", diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 9545e5e43..c69b9fa6f 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -1624,6 +1624,7 @@ class WidthVisitor final : public VNVisitor { nodep->dtypeSetSigned32(); // Used in int context if (VN_IS(nodep->backp(), IsUnbounded)) return; // Ok, leave if (VN_IS(nodep->backp(), BracketArrayDType)) return; // Ok, leave + if (VN_IS(nodep->backp(), InsideRange)) return; // Ok, leave if (const auto* const varp = VN_CAST(nodep->backp(), Var)) { if (varp->isParam()) return; // Ok, leave } diff --git a/test_regress/t/t_inside_unbounded.py b/test_regress/t/t_inside_unbounded.py new file mode 100644 index 000000000..f2edd7993 --- /dev/null +++ b/test_regress/t/t_inside_unbounded.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile() +test.execute() +test.passes() diff --git a/test_regress/t/t_inside_unbounded.v b/test_regress/t/t_inside_unbounded.v new file mode 100644 index 000000000..788b489b8 --- /dev/null +++ b/test_regress/t/t_inside_unbounded.v @@ -0,0 +1,80 @@ +// DESCRIPTION: Verilator: Test for unbounded '$' in inside range +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026. +// SPDX-License-Identifier: CC0-1.0 + +module t_inside_unbounded; + initial begin + int value; + + // Test [$:100] - should match minimum to 100 + value = 50; + if (!(value inside {[$:100]})) $stop; + + value = 100; + if (!(value inside {[$:100]})) $stop; + + value = 101; + if (value inside {[$:100]}) $stop; // Should NOT match + + // Test [0:$] - should match 0 to maximum + value = 50; + if (!(value inside {[0:$]})) $stop; + + value = 0; + if (!(value inside {[0:$]})) $stop; + + // Test [100:$] - should match 100 to maximum + value = 100; + if (!(value inside {[100:$]})) $stop; + + value = 200; + if (!(value inside {[100:$]})) $stop; + + value = 50; + if (value inside {[100:$]}) $stop; // Should NOT match + + // Test mixed with other ranges + value = 5; + if (!(value inside {[$:10], [90:$]})) $stop; + + value = 95; + if (!(value inside {[$:10], [90:$]})) $stop; + + value = 50; + if (value inside {[$:10], [90:$]}) $stop; // Should NOT match + + // Test with function + if (!(get_value(50) inside {[$:100]})) $stop; + if (!(get_value(50) inside {[0:$]})) $stop; + if (get_value(50) inside {[100:$]}) $stop; // Should NOT match + + // Test with increment + value = 49; + if (!(++value inside {[$:100]})) $stop; // value becomes 50 + if (value != 50) $stop; + + value = -1; + if (!(++value inside {[0:$]})) $stop; // value becomes 0 + if (value != 0) $stop; + + $write("*-* All Finished *-*\n"); + $finish; + end + + function int get_value(int v); + return v; + endfunction + + // Use volatile-like behavior to prevent compile-time optimization + int runtime_val; + function int get_runtime_value(int v); +`ifdef VERILATOR + runtime_val = $c32(v); +`else + runtime_val = v; +`endif + return runtime_val; + endfunction +endmodule diff --git a/test_regress/t/t_inside_unbounded_bad.out b/test_regress/t/t_inside_unbounded_bad.out new file mode 100644 index 000000000..c4968085c --- /dev/null +++ b/test_regress/t/t_inside_unbounded_bad.out @@ -0,0 +1,6 @@ +%Warning-INSIDETRUE: t/t_inside_unbounded_bad.v:12:23: Unbounded on both sides of inside range [$:$] is always true + 12 | if (value inside {[$:$]}) $display("PASS"); + | ^ + ... For warning description see https://verilator.org/warn/INSIDETRUE?v=latest + ... Use "/* verilator lint_off INSIDETRUE */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_inside_unbounded_bad.py b/test_regress/t/t_inside_unbounded_bad.py new file mode 100644 index 000000000..e842d48f7 --- /dev/null +++ b/test_regress/t/t_inside_unbounded_bad.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_inside_unbounded_bad.v b/test_regress/t/t_inside_unbounded_bad.v new file mode 100644 index 000000000..47d9cef70 --- /dev/null +++ b/test_regress/t/t_inside_unbounded_bad.v @@ -0,0 +1,15 @@ +// DESCRIPTION: Verilator: Test for unsupported [$:$] in inside range +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026. +// SPDX-License-Identifier: CC0-1.0 + +module t_inside_unbounded_bad; + initial begin + int value; + value = 50; + // [$:$] should warn - always true + if (value inside {[$:$]}) $display("PASS"); + $finish; + end +endmodule diff --git a/test_regress/t/t_inside_unbounded_nowarn.py b/test_regress/t/t_inside_unbounded_nowarn.py new file mode 100644 index 000000000..3746fc2aa --- /dev/null +++ b/test_regress/t/t_inside_unbounded_nowarn.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# Copyright 2026 by Wilson Snyder. 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-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(v_flags2=['-Wno-INSIDETRUE']) +test.execute() +test.passes() diff --git a/test_regress/t/t_inside_unbounded_nowarn.v b/test_regress/t/t_inside_unbounded_nowarn.v new file mode 100644 index 000000000..8e871b670 --- /dev/null +++ b/test_regress/t/t_inside_unbounded_nowarn.v @@ -0,0 +1,16 @@ +// DESCRIPTION: Verilator: Test for [$:$] with warning suppressed +// +// This file ONLY is placed under the Creative Commons Public Domain, for +// any use, without warranty, 2026. +// SPDX-License-Identifier: CC0-1.0 + +module t_inside_unbounded_nowarn; + initial begin + int value; + value = 50; + // [$:$] is always true - warning suppressed with -Wno-INSIDETRUE + if (!(value inside {[$:$]})) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule