From 4f134c514be06c617a1a9afe44c99016d645354e Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 9 Jun 2026 09:53:32 +0100 Subject: [PATCH 01/46] Testing: Rename UDP test to fix typo --- test_regress/t/{t_upd_nonsequential.py => t_udp_nonsequential.py} | 0 test_regress/t/{t_upd_nonsequential.v => t_udp_nonsequential.v} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test_regress/t/{t_upd_nonsequential.py => t_udp_nonsequential.py} (100%) rename test_regress/t/{t_upd_nonsequential.v => t_udp_nonsequential.v} (100%) diff --git a/test_regress/t/t_upd_nonsequential.py b/test_regress/t/t_udp_nonsequential.py similarity index 100% rename from test_regress/t/t_upd_nonsequential.py rename to test_regress/t/t_udp_nonsequential.py diff --git a/test_regress/t/t_upd_nonsequential.v b/test_regress/t/t_udp_nonsequential.v similarity index 100% rename from test_regress/t/t_upd_nonsequential.v rename to test_regress/t/t_udp_nonsequential.v From de0236be2fe8b654d14a6ed40ef8b6dc2ca9a4b8 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Tue, 9 Jun 2026 13:01:16 +0100 Subject: [PATCH 02/46] Tests: Fix race condition in t_udp_nonsequential --- test_regress/t/t_udp_nonsequential.v | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_regress/t/t_udp_nonsequential.v b/test_regress/t/t_udp_nonsequential.v index c9496ea84..91c538e39 100644 --- a/test_regress/t/t_udp_nonsequential.v +++ b/test_regress/t/t_udp_nonsequential.v @@ -23,22 +23,22 @@ module t ( sel = 0; end else if (cycle == 1) begin + if (z != 0) $stop; a = 1; b = 1; sel = 0; - if (z != 0) $stop; end else if (cycle == 2) begin + if (z != 1) $stop; a = 0; b = 1; sel = 0; - if (z != 1) $stop; end else if (cycle == 3) begin + if (z != 0) $stop; a = 1; b = 0; sel = 0; - if (z != 0) $stop; end else if (cycle == 4) begin if (z != 1) $stop; From d1319cf81ea07f376ecefddf47240f2b97c80a87 Mon Sep 17 00:00:00 2001 From: Yilin Li <60502081+AllinLeeYL@users.noreply.github.com> Date: Wed, 10 Jun 2026 15:38:41 +0200 Subject: [PATCH 03/46] Fix dpi export pointers (#7742) (#7751) Fixes #7742. --- docs/CONTRIBUTORS | 1 + src/V3EmitCFunc.h | 10 ++++++-- test_regress/t/t_dpi_export_unpack.py | 20 ++++++++++++++++ test_regress/t/t_dpi_export_unpack.v | 33 +++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_dpi_export_unpack.py create mode 100644 test_regress/t/t_dpi_export_unpack.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 880b5608a..ef3746a76 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -325,3 +325,4 @@ Yogish Sekhar 24bit-xjkp Zubin Jain Muzaffer Kal +Yilin Li diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index c05c46afe..6eedcbc69 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -573,8 +573,14 @@ public: + "\", " + std::to_string(nodep->fileline()->lineno()) + ")->"), memberVarp, resetp->constructing()); } else { - AstVar* const varp = VN_AS(fromp, NodeVarRef)->varp(); - emitVarReset("", varp, resetp->constructing()); + AstNodeVarRef* const fromVarRefp = VN_AS(fromp, NodeVarRef); + AstVar* const varp = fromVarRefp->varp(); + const string prefix + = fromVarRefp->selfPointer().isEmpty() + ? "" + : dereferenceString( + VN_AS(fromp, NodeVarRef)->selfPointerProtect(m_useSelfForThis)); + emitVarReset(prefix, varp, resetp->constructing()); } return; } diff --git a/test_regress/t/t_dpi_export_unpack.py b/test_regress/t/t_dpi_export_unpack.py new file mode 100755 index 000000000..b365ee618 --- /dev/null +++ b/test_regress/t/t_dpi_export_unpack.py @@ -0,0 +1,20 @@ +#!/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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +# 9-Jun-2026: Modifications for this test contributed by Yilin Li. + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_dpi_export_unpack.v b/test_regress/t/t_dpi_export_unpack.v new file mode 100644 index 000000000..91cc32577 --- /dev/null +++ b/test_regress/t/t_dpi_export_unpack.v @@ -0,0 +1,33 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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: 2020 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// 9-Jun-2026: Modifications for this test contributed by Yilin Li. + +export "DPI-C" task readHEX; +export "DPI-C" task loadHEX; + +task readHEX; + input string file; + output logic [7:0] stimuli[32'h00010000]; + $readmemh(file, stimuli); +endtask + +task loadHEX; + input string file; + logic [7:0] stimuli[32'h00010000]; + readHEX(file, stimuli); +endtask + +module tb (); + + logic [7:0] result[32'h00010000]; + initial begin + loadHEX("dummy"); + end + +endmodule From 75993ca9ea414f9fc2b9333d0c7f2f795c572688 Mon Sep 17 00:00:00 2001 From: pawelktk Date: Wed, 10 Jun 2026 15:39:43 +0200 Subject: [PATCH 04/46] Support assoc array methods with wide value types (#7680) --- include/verilated_types.h | 38 ++++++++++++--- src/V3Width.cpp | 6 +++ test_regress/t/t_assoc_method.v | 81 +++++++++++++++++++++++++++++++- test_regress/t/t_assoc_unsup.out | 78 ++++++++++++++++++++++++++++++ test_regress/t/t_assoc_unsup.py | 16 +++++++ test_regress/t/t_assoc_unsup.v | 50 ++++++++++++++++++++ 6 files changed, 261 insertions(+), 8 deletions(-) create mode 100644 test_regress/t/t_assoc_unsup.out create mode 100755 test_regress/t/t_assoc_unsup.py create mode 100644 test_regress/t/t_assoc_unsup.v diff --git a/include/verilated_types.h b/include/verilated_types.h index c2502bcdf..40847b65e 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -97,6 +97,28 @@ struct VlWide final { bool operator!=(const VlWide& that) const VL_PURE { return !(*this == that); } EData& operator[](size_t index) VL_MT_SAFE { return m_storage[index]; } const EData& operator[](size_t index) const VL_MT_SAFE { return m_storage[index]; } + VlWide& operator&=(const VlWide& rhs) { + VL_AND_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator|=(const VlWide& rhs) { + VL_OR_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator^=(const VlWide& rhs) { + VL_XOR_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator+=(const VlWide& rhs) { + VL_ADD_W(N_Words, *this, *this, rhs); + return *this; + } + VlWide& operator*=(const VlWide& rhs) { + VlWide out{}; + VL_MUL_W(N_Words, out, *this, rhs); + for (size_t i = 0; i < N_Words; ++i) m_storage[i] = out.m_storage[i]; + return *this; + } // METHODS EData& at(size_t index) VL_MT_SAFE { return m_storage[index]; } @@ -1280,7 +1302,7 @@ public: } T_Value r_sum() const { - T_Value out(0); // Type must have assignment operator + T_Value out = T_Value{}; for (const auto& i : m_map) out += i.second; return out; } @@ -1291,8 +1313,9 @@ public: return out; } T_Value r_product() const { - if (m_map.empty()) return T_Value(0); // The big three do it this way - T_Value out = T_Value(1); + // The big three return 0 when assoc array is empty + if (m_map.empty()) return T_Value{}; + T_Value out = T_Value{1}; for (const auto& i : m_map) out *= i.second; return out; } @@ -1304,8 +1327,9 @@ public: return out; } T_Value r_and() const { - if (m_map.empty()) return T_Value(0); // The big three do it this way - T_Value out = ~T_Value(0); + // The big three return 0 when assoc array is empty + if (m_map.empty()) return T_Value{}; + T_Value out = m_map.cbegin()->second; for (const auto& i : m_map) out &= i.second; return out; } @@ -1317,7 +1341,7 @@ public: return out; } T_Value r_or() const { - T_Value out = T_Value(0); + T_Value out = T_Value{}; for (const auto& i : m_map) out |= i.second; return out; } @@ -1328,7 +1352,7 @@ public: return out; } T_Value r_xor() const { - T_Value out = T_Value(0); + T_Value out = T_Value{}; for (const auto& i : m_map) out ^= i.second; return out; } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 5a24539b2..861714e34 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -4352,6 +4352,12 @@ class WidthVisitor final : public VNVisitor { } void methodCallAssoc(AstMethodCall* nodep, AstAssocArrayDType* adtypep) { AstCMethodHard* newp = nullptr; + if (nodep->withp() && adtypep->subDTypep()->isWide()) { + nodep->v3warn( + E_UNSUPPORTED, + "Unsupported: `with` clause on assoc arrays with wide value types in method '" + << nodep->prettyName() << "'"); + } if (nodep->name() == "num" // function int num() || nodep->name() == "size") { methodOkArguments(nodep, 0, 0); diff --git a/test_regress/t/t_assoc_method.v b/test_regress/t/t_assoc_method.v index 3a7ecdfde..13ab0411f 100644 --- a/test_regress/t/t_assoc_method.v +++ b/test_regress/t/t_assoc_method.v @@ -22,15 +22,23 @@ module t; int qe[int]; // Empty int qv[$]; // Value returns int qi[$]; // Index returns + bit[229:0] qw[int]; // Wide values + bit[229:0] qwe[int]; // Wide values - empty + bit[229:0] qwv[$]; // Wide values - Value returns + int qwi[$]; // Wide values - Index returns point points_q[int]; point points_qe[int]; // Empty points point points_qv[$]; int i; bit b; + bit[229:0] w; q = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3}; `checkp(q, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}"); + qw = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3}; + `checkp(qw, "'{'ha:'h1, 'hb:'h2, 'hc:'h2, 'hd:'h4, 'he:'h3}"); + // NOT tested: with ... selectors //q.sort; // Not legal on assoc - see t_assoc_meth_bad @@ -43,16 +51,34 @@ module t; `checkp(qv, "'{'h1, 'h2, 'h4, 'h3}"); qv = qe.unique; `checkp(qv, "'{}"); + + qwv = qw.unique; + `checkp(qwv, "'{'h1, 'h2, 'h4, 'h3}"); + qwv = qwe.unique; + `checkp(qwv, "'{}"); + qi = q.unique_index; qi.sort; `checkp(qi, "'{'ha, 'hb, 'hd, 'he}"); qi = qe.unique_index; `checkp(qi, "'{}"); + qwi = qw.unique_index; + qwi.sort; + `checkp(qwi, "'{'ha, 'hb, 'hd, 'he}"); + qwi = qwe.unique_index; + `checkp(qwi, "'{}"); + points_q[0] = point'{1, 2}; points_q[1] = point'{2, 4}; points_q[5] = point'{1, 4}; + qi = points_qe.unique_index(); + `checkp(qi, "'{}"); + + qi = points_q.unique_index(); + `checkh(qi.size, 3); + points_qv = points_q.unique(p) with (p.x); `checkh(points_qv.size, 2); qi = points_q.unique_index (p) with (p.x + p.y); @@ -113,6 +139,10 @@ module t; qv = q.min; `checkp(qv, "'{'h1}"); + + qwv = qw.min; + `checkp(qwv, "'{'h1}"); + points_qv = points_q.min(p) with (p.x + p.y); if (points_qv[0].x != 1 || points_qv[0].y != 2) $stop; @@ -130,8 +160,13 @@ module t; qv = qe.max(x) with (x + 1); `checkp(qv, "'{}"); - // Reduction methods + // Wide + qwv = qwe.min; + `checkp(qwv, "'{}"); + qwv = qwe.max; + `checkp(qwv, "'{}"); + // Reduction methods i = q.sum; `checkh(i, 32'hc); i = q.sum with (item + 1); @@ -141,6 +176,12 @@ module t; i = q.product with (item + 1); `checkh(i, 32'h168); + // Wide + w = qw.sum; + `checkh(w, 230'hc); + w = qw.product; + `checkh(w, 230'h30); + i = qe.sum; `checkh(i, 32'h0); i = qe.sum with (item + 1); @@ -150,6 +191,12 @@ module t; i = qe.product with (item + 1); `checkh(i, 32'h0); + // Wide + w = qwe.sum; + `checkh(w, 230'h0); + w = qwe.product; + `checkh(w, 230'h0); + q = '{10: 32'b1100, 11: 32'b1010}; i = q.and; `checkh(i, 32'b1000); @@ -164,6 +211,14 @@ module t; i = q.xor with (item + 1); `checkh(i, 32'b0110); + qw = '{10: 230'b1100, 11: 230'b1010}; + w = qw.and; + `checkh(w, 230'b1000); + w = qw.or; + `checkh(w, 230'b1110); + w = qw.xor; + `checkh(w, 230'b0110); + i = qe.and; `checkh(i, 32'b0); i = qe.and with (item + 1); @@ -177,6 +232,14 @@ module t; i = qe.xor with (item + 1); `checkh(i, 32'b0); + // Wide + w = qwe.and; + `checkh(w, 230'b0); + w = qwe.or; + `checkh(w, 230'b0); + w = qwe.xor; + `checkh(w, 230'b0); + i = q.and(); `checkh(i, 32'b1000); i = q.and() with (item + 1); @@ -190,6 +253,14 @@ module t; i = q.xor() with (item + 1); `checkh(i, 32'b0110); + // Wide + w = qw.and(); + `checkh(w, 230'b1000); + w = qw.or(); + `checkh(w, 230'b1110); + w = qw.xor(); + `checkh(w, 230'b0110); + i = qe.and(); `checkh(i, 32'b0); i = qe.or(); @@ -197,6 +268,14 @@ module t; i = qe.xor(); `checkh(i, 32'b0); + // Wide + w = qwe.and(); + `checkh(w, 230'b0); + w = qwe.or(); + `checkh(w, 230'b0); + w = qwe.xor(); + `checkh(w, 230'b0); + q = '{10: 1, 11: 2}; qe = '{10: 1, 11: 2}; `checkh(q == qe, 1'b1); diff --git a/test_regress/t/t_assoc_unsup.out b/test_regress/t/t_assoc_unsup.out new file mode 100644 index 000000000..0895427a3 --- /dev/null +++ b/test_regress/t/t_assoc_unsup.out @@ -0,0 +1,78 @@ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:17:15: Unsupported: `with` clause on assoc arrays with wide value types in method 'min' + : ... note: In instance 't' + 17 | qwv = qwe.min(x) with (x + 1); + | ^~~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_assoc_unsup.v:18:15: Unsupported: `with` clause on assoc arrays with wide value types in method 'max' + : ... note: In instance 't' + 18 | qwv = qwe.max(x) with (x + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:20:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'sum' + : ... note: In instance 't' + 20 | w = qw.sum with (item + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:21:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'product' + : ... note: In instance 't' + 21 | w = qw.product with (item + 1); + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:23:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'sum' + : ... note: In instance 't' + 23 | w = qwe.sum with (item + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:24:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'product' + : ... note: In instance 't' + 24 | w = qwe.product with (item + 1); + | ^~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:27:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'and' + : ... note: In instance 't' + 27 | w = qw.and with (item + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:28:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'or' + : ... note: In instance 't' + 28 | w = qw.or with (item + 1); + | ^~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:29:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'xor' + : ... note: In instance 't' + 29 | w = qw.xor with (item + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:31:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'and' + : ... note: In instance 't' + 31 | w = qw.and() with (item + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:32:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'or' + : ... note: In instance 't' + 32 | w = qw.or() with (item + 1); + | ^~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:33:12: Unsupported: `with` clause on assoc arrays with wide value types in method 'xor' + : ... note: In instance 't' + 33 | w = qw.xor() with (item + 1); + | ^~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:35:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'find' + : ... note: In instance 't' + 35 | qwv = qw.find with (item == 2); + | ^~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:36:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_first' + : ... note: In instance 't' + 36 | qwv = qw.find_first with (item == 2); + | ^~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:37:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_last' + : ... note: In instance 't' + 37 | qwv = qw.find_last with (item == 2); + | ^~~~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:39:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_index' + : ... note: In instance 't' + 39 | qi = qw.find_index with (item == 2); + | ^~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:40:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_first_index' + : ... note: In instance 't' + 40 | qi = qw.find_first_index with (item == 2); + | ^~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:41:13: Unsupported: `with` clause on assoc arrays with wide value types in method 'find_last_index' + : ... note: In instance 't' + 41 | qi = qw.find_last_index with (item == 2); + | ^~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assoc_unsup.v:45:14: Unsupported: `with` clause on assoc arrays with wide value types in method 'map' + : ... note: In instance 't' + 45 | qwv = qw.map(el) with (el / 100); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_assoc_unsup.py b/test_regress/t/t_assoc_unsup.py new file mode 100755 index 000000000..30986489e --- /dev/null +++ b/test_regress/t/t_assoc_unsup.py @@ -0,0 +1,16 @@ +#!/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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios("linter") + +test.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_assoc_unsup.v b/test_regress/t/t_assoc_unsup.v new file mode 100644 index 000000000..848bf0bf8 --- /dev/null +++ b/test_regress/t/t_assoc_unsup.v @@ -0,0 +1,50 @@ +// 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 + +module t; + initial begin + bit[229:0] qw[int]; // Wide values + bit[229:0] qwe[int]; // Wide values - empty + bit[229:0] qwv[$]; // Wide values - Value returns + int qi[$]; // Index returns + bit[229:0] w; + + qw = '{10: 1, 11: 2, 12: 2, 13: 4, 14: 3}; + + qwv = qwe.min(x) with (x + 1); + qwv = qwe.max(x) with (x + 1); + + w = qw.sum with (item + 1); + w = qw.product with (item + 1); + + w = qwe.sum with (item + 1); + w = qwe.product with (item + 1); + + qw = '{10: 230'b1100, 11: 230'b1010}; + w = qw.and with (item + 1); + w = qw.or with (item + 1); + w = qw.xor with (item + 1); + + w = qw.and() with (item + 1); + w = qw.or() with (item + 1); + w = qw.xor() with (item + 1); + + qwv = qw.find with (item == 2); + qwv = qw.find_first with (item == 2); + qwv = qw.find_last with (item == 2); + + qi = qw.find_index with (item == 2); + qi = qw.find_first_index with (item == 2); + qi = qw.find_last_index with (item == 2); + + // Map method (IEEE 1800-2023 7.12.5) + qw = '{1: 100, 2: 200, 3: 300}; + qwv = qw.map(el) with (el / 100); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From d84af81a11cab7ba8d6b5e42a27bf53b4e797787 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Wed, 10 Jun 2026 15:59:44 +0100 Subject: [PATCH 05/46] Optimize Dfg with relaxed live variable analysis (#7739) Relax the live variable analysis performed by Dfg to bail on fewer cases. This analysis was already conservative (meaning it might think variables are live when they are not), which is good enough for Dfg use. This change in particular enables synthesizing more complex logic involving arrays, e.g. those introduce by V3Table creating lookup tables. --- src/V3AstNodeOther.h | 1 + src/V3CfgLiveVariables.cpp | 32 +++++++------------ src/V3Dfg.cpp | 9 ++++-- test_regress/t/t_case_huge.py | 3 +- test_regress/t/t_dfg_synthesis.v | 9 ++++++ .../t_gate_inline_wide_noexclude_arraysel.py | 2 +- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 903130658..f3692d05e 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -987,6 +987,7 @@ public: bool maybePointedTo() const override VL_MT_SAFE { return true; } void cloneRelink() override { V3ERROR_NA; } // Not cloneable AstModule* modp() const { return m_modp; } + AstScope* scopep() const { return m_scopep; } // Find a table (unpacked array) within the constant pool which is initialized with the // given value, or create one if one does not already exists. The returned VarScope *might* diff --git a/src/V3CfgLiveVariables.cpp b/src/V3CfgLiveVariables.cpp index 79033af9d..162e0837d 100644 --- a/src/V3CfgLiveVariables.cpp +++ b/src/V3CfgLiveVariables.cpp @@ -51,28 +51,18 @@ class CfgLiveVariables final : VNVisitorConst { // METHODS - // This is to match DFG, but can be extended independently - eventually should handle all - static bool isSupportedPackedDType(const AstNodeDType* dtypep) { - dtypep = dtypep->skipRefp(); + static bool isPacked(const AstVarScope* vscp) { + AstNodeDType* const dtypep = vscp->dtypep()->skipRefp(); if (const AstBasicDType* const typep = VN_CAST(dtypep, BasicDType)) { return typep->keyword().isIntNumeric(); } - if (const AstPackArrayDType* const typep = VN_CAST(dtypep, PackArrayDType)) { - return isSupportedPackedDType(typep->subDTypep()); - } + if (VN_IS(dtypep, PackArrayDType)) return true; if (const AstNodeUOrStructDType* const typep = VN_CAST(dtypep, NodeUOrStructDType)) { return typep->packed(); } return false; } - // Check and return if variable is incompatible - bool incompatible(AstVarScope* vscp) { - if (!isSupportedPackedDType(vscp->dtypep())) return true; - if (vscp->varp()->ignoreSchedWrite()) return true; - return false; - } - void updateGen(AstNode* nodep) { UASSERT_OBJ(!m_abort, nodep, "Doing useless work"); m_abort = nodep->exists([&](const AstVarRef* refp) { @@ -80,8 +70,6 @@ class CfgLiveVariables final : VNVisitorConst { if (refp->access().isWriteOnly()) return false; // Grab referenced variable AstVarScope* const vscp = refp->varScopep(); - // Bail if not a compatible type - if (incompatible(vscp)) return true; // Add to gen set, if not killed - assume whole variable read if (m_currp->m_kill.count(vscp)) return false; m_currp->m_gen.emplace(vscp); @@ -96,11 +84,13 @@ class CfgLiveVariables final : VNVisitorConst { if (refp->access().isReadOnly()) return false; // Grab referenced variable AstVarScope* const vscp = refp->varScopep(); - // Bail if not a compatible type - if (incompatible(vscp)) return true; - // If whole written, add to kill set - if (refp->nextp()) return false; - if (VN_IS(refp->abovep(), Sel)) return false; + // Safety case: bail on variables with unusual semantics + if (vscp->varp()->ignoreSchedWrite()) return true; + // If not a packed variable, assume it's not a whole update, don't add to kill set + if (!isPacked(vscp)) return false; + // If packed, and updated partially, don't add to kill set + if (refp->backp()->nextp() != refp && VN_IS(refp->abovep(), Sel)) return false; + // Whole variable is writen, add to kill set m_currp->m_kill.emplace(vscp); return false; }); @@ -198,7 +188,7 @@ public: CfgLiveVariables analysis{cfg}; // If failed, return nullptr if (analysis.m_abort) return nullptr; - // Gather variables live in to the entry blockstd::unique_ptr> + // Gather variables live in to the entry block const std::unordered_set& lin = analysis.m_blockState[cfg.enter()].m_liveIn; std::vector* const resultp = new std::vector{lin.begin(), lin.end()}; diff --git a/src/V3Dfg.cpp b/src/V3Dfg.cpp index 273d1dcff..637cf184f 100644 --- a/src/V3Dfg.cpp +++ b/src/V3Dfg.cpp @@ -906,17 +906,20 @@ AstScope* DfgVertex::scopep(ScopeCache& cache, bool tryResultVar) VL_MT_DISABLED } } + AstScope* const rootp = v3Global.rootp()->topScopep()->scopep(); + AstScope* const constPoolp = v3Global.rootp()->constPoolp()->scopep(); + // Note: the recursive invocation can cause a re-hash but that will not invalidate references AstScope*& resultr = cache[this]; if (!resultr) { // Mark to prevent infinite recursion on circular graphs - should never be called on such resultr = reinterpret_cast(1); - // Find scope based on sources, falling back on the root scope - AstScope* const rootp = v3Global.rootp()->topScopep()->scopep(); + // Find scope based on sources, falling back on the root scope, + // also make sure it's not the constant pool scope, which is special. AstScope* foundp = nullptr; foreachSource([&](DfgVertex& src) { AstScope* const scp = src.scopep(cache, true); - if (scp != rootp) { + if (scp != rootp && scp != constPoolp) { foundp = scp; return true; } diff --git a/test_regress/t/t_case_huge.py b/test_regress/t/t_case_huge.py index 81fcffbad..0ac31a2f8 100755 --- a/test_regress/t/t_case_huge.py +++ b/test_regress/t/t_case_huge.py @@ -11,7 +11,8 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile(verilator_flags2=["--stats"]) +# This tests combining CFuncs, but Dfg would inline the submodule, disabling +test.compile(verilator_flags2=["--stats", "-fno-dfg"]) test.execute() diff --git a/test_regress/t/t_dfg_synthesis.v b/test_regress/t/t_dfg_synthesis.v index 9076b4101..c13d4f47c 100644 --- a/test_regress/t/t_dfg_synthesis.v +++ b/test_regress/t/t_dfg_synthesis.v @@ -577,4 +577,13 @@ module t ( end `signal(VIA_CRESET, via_creset); + logic [1:0] LUT [2] = '{0: 2'b11, 1: 2'b10}; + logic [1:0] array_read; + always_comb begin + array_read = 2'd0; + array_read += LUT[rand_a[0]]; + array_read += LUT[rand_a[1]]; + end + `signal(ARRAY_READ, array_read); + endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py index ccbd8c7e7..8c99e4b33 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py @@ -11,7 +11,7 @@ import vltest_bootstrap test.scenarios('vlt') -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) +test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-dfg']) test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) From dc1e9a8ec65d5549f8b029e724c27051bf517981 Mon Sep 17 00:00:00 2001 From: Tracy Narine <76846523+tmnarine@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:18:48 -0400 Subject: [PATCH 06/46] Internals: Fix gcc bison warnings (#7719) (#7756) Fixes #7719. --- src/bisonpre | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/bisonpre b/src/bisonpre index 9be647f39..36db4b48f 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -145,6 +145,25 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No out.append(line) lines = out out = [] + # Code updates for the .c + if outname.endswith(".c"): + for line in lines: + modify = "YYSTACK_FREE (yyss);" in line and "if (yyss != yyssa)" in out[-1] + ifdefined = "#if defined __GNUC__ && ! defined __ICC\n" + endif = "#endif\n" + if modify: + out.insert(-1, ifdefined) + out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") + out.insert(-1, + " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") + out.insert(-1, endif) + out.append(line) + if modify: + out.append(ifdefined) + out.append(" _Pragma (\"GCC diagnostic pop\")\n") + out.append(endif) + lines = out + out = [] with open(outname, "w", encoding="utf-8") as fh: tmpy = re.escape(tmp_prefix() + ".y") From 48008a2fc96a94caa498c0038b4b7ecbe8dccded Mon Sep 17 00:00:00 2001 From: github action Date: Wed, 10 Jun 2026 16:19:51 +0000 Subject: [PATCH 07/46] Apply 'make format' [ci skip] --- src/bisonpre | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bisonpre b/src/bisonpre index 36db4b48f..5db061a5a 100755 --- a/src/bisonpre +++ b/src/bisonpre @@ -154,7 +154,8 @@ def clean_output(filename: str, outname: str, is_output: bool, is_c: bool) -> No if modify: out.insert(-1, ifdefined) out.insert(-1, " _Pragma (\"GCC diagnostic push\")\n") - out.insert(-1, + out.insert( + -1, " _Pragma (\"GCC diagnostic ignored \\\"-Wfree-nonheap-object\\\"\")\n") out.insert(-1, endif) out.append(line) From e2e1bfe8dd96bbf1437dae3fffacdcec61c95515 Mon Sep 17 00:00:00 2001 From: Shashvat Date: Wed, 10 Jun 2026 23:18:38 +0530 Subject: [PATCH 08/46] Internals: Remove unused V3TSP (#7194) (#7757) --- docs/CONTRIBUTORS | 1 + src/CMakeLists.txt | 2 - src/Makefile_obj.in | 1 - src/V3EmitCFunc.cpp | 2 - src/V3TSP.cpp | 701 ---------------------------------- src/V3TSP.h | 57 --- src/Verilator.cpp | 2 - src/cppcheck-suppressions.txt | 2 - 8 files changed, 1 insertion(+), 767 deletions(-) delete mode 100644 src/V3TSP.cpp delete mode 100644 src/V3TSP.h diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index ef3746a76..dae170a70 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -326,3 +326,4 @@ Yogish Sekhar Zubin Jain Muzaffer Kal Yilin Li +Shashvat Prabhu diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad2178203..b4f4be9aa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -183,7 +183,6 @@ set(HEADERS V3String.h V3Subst.h V3SymTable.h - V3TSP.h V3Table.h V3Task.h V3ThreadPool.h @@ -358,7 +357,6 @@ set(COMMON_SOURCES V3StatsReport.cpp V3String.cpp V3Subst.cpp - V3TSP.cpp V3Table.cpp V3Task.cpp V3ThreadPool.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index 08c8899fc..dcd78a431 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -337,7 +337,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ - V3TSP.o \ V3Table.o \ V3Task.o \ V3Timing.o \ diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index 8eb82b31f..11f2ac14a 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -18,8 +18,6 @@ #include "V3EmitCFunc.h" -#include "V3TSP.h" - #include #include diff --git a/src/V3TSP.cpp b/src/V3TSP.cpp deleted file mode 100644 index 4244a5676..000000000 --- a/src/V3TSP.cpp +++ /dev/null @@ -1,701 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// ISSUES: This isn't exactly Christofides algorithm; see the TODO -// in perfectMatching(). True minimum-weight perfect matching -// would produce a better result. How much better is TBD. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// 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: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3TSP.h" - -#include "V3File.h" -#include "V3Graph.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Support classes - -namespace V3TSP { -static uint32_t s_edgeIdNext = 0; - -static void selfTestStates(); -static void selfTestString(); -} // namespace V3TSP - -// Vertex that tracks a per-vertex key -template -class TspVertexTmpl final : public V3GraphVertex { - VL_RTTI_IMPL(TspVertexTmpl, V3GraphVertex) -private: - const T_Key m_key; - -public: - TspVertexTmpl(V3Graph* graphp, const T_Key& k) - : V3GraphVertex{graphp} - , m_key{k} {} - ~TspVertexTmpl() override = default; - const T_Key& key() const { return m_key; } - -private: - VL_UNCOPYABLE(TspVertexTmpl); -}; - -// TspGraphTmpl represents a complete graph, templatized to work with -// different T_Key types. -template -class TspGraphTmpl final : public V3Graph { -public: - // TYPES - using Vertex = TspVertexTmpl; - - enum VertexState : uint32_t { CLEAR = 0, MST_VISITED = 1, UNMATCHED_ODD = 2 }; - - // MEMBERS - std::unordered_map m_vertices; // T_Key to Vertex lookup map - - // CONSTRUCTORS - TspGraphTmpl() - : V3Graph{} {} - ~TspGraphTmpl() override = default; - - // METHODS - void addVertex(const T_Key& key) { - const bool newEntry = m_vertices.emplace(key, new Vertex{this, key}).second; - UASSERT(newEntry, "Vertex already exists with same key"); - } - - // For purposes of TSP, we are using non-directional graphs. - // Map that onto the normally-directional V3Graph by creating - // a matched pairs of opposite-directional edges to represent - // each non-directional edge: - void addEdge(const T_Key& from, const T_Key& to, int cost) { -#if VL_DEBUG // Hot, so only in debug - UASSERT(from != to, "Adding edge would form a loop"); - UASSERT(cost >= 0, "Negative weight edge"); -#endif - Vertex* const fp = findVertex(from); - Vertex* const tp = findVertex(to); - - // No need to dedup edges. - // The only time we may create duplicate edges is when - // combining the MST with the perfect-matched pairs, - // and in that case, we want to permit duplicate edges. - const uint32_t edgeId = ++V3TSP::s_edgeIdNext; - - // We want to be able to compare edges quickly for a total - // ordering, so pre-compute a sorting key and store it in - // the edge user field. We also want easy access to the 'id' - // which uniquely identifies a single bidir edge. Luckily we - // can do both efficiently. - // cppcheck-suppress badBitmaskCheck - const uint64_t userValue = (static_cast(cost) << 32) | edgeId; - (new V3GraphEdge{this, fp, tp, cost})->user(userValue); - (new V3GraphEdge{this, tp, fp, cost})->user(userValue); - } - - static uint32_t getEdgeId(const V3GraphEdge* edgep) { - return static_cast(edgep->user()); - } - - // cppcheck-suppress duplInheritedMember - bool empty() const { return m_vertices.empty(); } - - const std::list keysToVertexList(const std::vector& odds) { - std::list vertices; - for (unsigned i = 0; i < odds.size(); ++i) vertices.push_back(findVertex(odds.at(i))); - return vertices; - } - -private: - // We will keep sorted lists of edges as vectors - using EdgeList = std::vector; - - static bool edgeCmp(const V3GraphEdge* ap, const V3GraphEdge* bp) { - // We pre-computed these when adding the edge to sort first by cost, then by identity - return ap->user() > bp->user(); - } - - struct EdgeListCmp final { - bool operator()(const EdgeList* ap, const EdgeList* bp) const { - // Compare heads - return edgeCmp(bp->back(), ap->back()); - } - }; - - static Vertex* castVertexp(V3GraphVertex* vxp) { return static_cast(vxp); } - static const Vertex* castVertexp(const V3GraphVertex* vxp) { - return static_cast(vxp); - } - -public: - // From *this, populate *mstp with the minimum spanning tree. - // *mstp must be initially empty. - void makeMinSpanningTree(TspGraphTmpl* mstp) { - UASSERT(mstp->empty(), "Output graph must start empty"); - - // Use Prim's algorithm to efficiently construct the MST. - - uint32_t vertCount = 0; - for (V3GraphVertex& vtx : vertices()) { - mstp->addVertex(castVertexp(&vtx)->key()); - vertCount++; - } - - // Allocate storage for per vertex edge lists up front. - std::vector allocatedEdgeLists{vertCount}; - - // Index of vertex in visitation order (used for indexing allocatedEdgeLists) - uint32_t vertIdx = 0; - - // We keep pending edges as a sorted set of sorted vectors. This allows us to find the - // lowest cost edge quickly, while also reducing the cost of inserting batches of new - // edges, which is what we need in this algorithm. - std::set pendingEdgeListps; - - const auto visit = [&](V3GraphVertex* vtxp) { -#ifdef VL_DEBUG // Very hot, so only in debug - UASSERT(vtxp->user() == VertexState::CLEAR, "Vertex visited twice"); -#endif - // Mark vertex as visited - vtxp->user(VertexState::MST_VISITED); - // Allocate new edge list - EdgeList* const newEdgesp = &allocatedEdgeLists[vertIdx++]; - // Gather out edges of this vertex - for (V3GraphEdge& edge : vtxp->outEdges()) { - // Don't add edges leading to vertices we already visited. This is a highly - // connected graph, so this greatly reduces the cost of maintaining the pending - // set. - if (edge.top()->user() == VertexState::MST_VISITED) continue; - newEdgesp->push_back(&edge); - } - // If no relevant out edges, then we are done - if (newEdgesp->empty()) return; - // Sort new edge list - std::sort(newEdgesp->begin(), newEdgesp->end(), edgeCmp); - // Add edge list to pending set - pendingEdgeListps.insert(newEdgesp); - }; - - // To start, choose an arbitrary vertex and visit it. - visit(vertices().frontp()); - - // Repeatedly find the least costly edge in the pending set. - // If it connects to an unvisited node, visit that node and update - // the pending edge set. If it connects to an already visited node, - // discard it and repeat again. - while (!pendingEdgeListps.empty()) { - // Grab lowest cost edge list - auto it = pendingEdgeListps.begin(); - - // Grab lowest cost edge - EdgeList* const bestEdgeListp = *it; - const V3GraphEdge* const bestEdgep = bestEdgeListp->back(); - - // Remove the lowest cost edge list. We will remove its lowest cost element, and either - // we are done with (if it had a single element) it in which case it will be discarded, - // or the cost of the new head element might be different, so we will need to re-insert - // it in the right place. In either case, it needs to be removed. - pendingEdgeListps.erase(it); - - // If the lowest cost edge list is not a singleton list, then pop the lowest cost - // edge and re-insert the remaining edge list into the pending set. - if (bestEdgeListp->size() > 1) { - bestEdgeListp->pop_back(); - pendingEdgeListps.insert(bestEdgeListp); - } - - // Grab the target vertex - Vertex* const neighborp = castVertexp(bestEdgep->top()); - - // If the neighbour is not yet visited - if (neighborp->user() == VertexState::CLEAR) { - // Visit it - visit(neighborp); - - // Create the edge in our output MST graph - Vertex* const from_vertexp = castVertexp(bestEdgep->fromp()); - mstp->addEdge(from_vertexp->key(), neighborp->key(), bestEdgep->weight()); - -#if VL_DEBUG // Very hot loop, so only in debug - UASSERT(from_vertexp->user() == MST_VISITED, - "bestEdgep->fromp() should be already seen"); -#endif - } - } - - UASSERT(vertIdx == vertCount, "Should have visited all vertices"); - } - - // Populate *outp with a minimal perfect matching of *this. - // *outp must be initially empty. - void perfectMatching(const std::vector& oddKeys, TspGraphTmpl* outp) { - UASSERT(outp->empty(), "Output graph must start empty"); - - const std::list& odds = keysToVertexList(oddKeys); - UASSERT(odds.size() % 2 == 0, "number of odd-order nodes should be even"); - - for (Vertex* const vtxp : odds) { - outp->addVertex(vtxp->key()); - vtxp->user(VertexState::UNMATCHED_ODD); - } - - // TODO: The true Christofides algorithm calls for minimum-weight - // perfect matching. Instead, we have a simple greedy algorithm - // which might get close to the minimum, maybe, with luck? - // - // TODO: Revisit this. It's possible to compute the true minimum in - // N*N*log(N) time using variants of the Blossom algorithm. - // Implementing Blossom looks hard, maybe we can use an existing - // open source implementation -- for example the "LEMON" library - // which has a TSP solver. - - // ----- - - // Gather and sort all edges. We use a vector then sort, because this is faster than a - // sorted set. Reuse the comparator from Prim's routine (note it a 'greater', not a - // 'lesser' comparator). The logic is the same here. - // - // Note that there are two V3GraphEdge's representing a single bidir edge. While we could - // just add both to the pending list and get the same result, we will only add one (based - // on fast pointer comparison - this still yields deterministic results), in order to - // reduce the size of the working set. - std::vector pendingEdges; - - for (Vertex* const fromp : odds) { - for (V3GraphEdge& edge : fromp->outEdges()) { - Vertex* const top = castVertexp(edge.top()); - // There are two edges (in both directions) between these two vertices. Keep one. - if (fromp > top) continue; - // We only care about edges between the odd-order vertices - if (top->user() != VertexState::UNMATCHED_ODD) continue; - // Add to candidate list - pendingEdges.push_back(&edge); - } - } - - // Sort reverse iterators. This yields ascending order with a 'greater' comparator. - std::sort(pendingEdges.rbegin(), pendingEdges.rend(), edgeCmp); - - // Iterate over all edges, in order from low to high cost. - // For any edge whose ends are both odd-order vertices which - // haven't been matched yet, match them. - for (V3GraphEdge* const edgep : pendingEdges) { - Vertex* const fromp = castVertexp(edgep->fromp()); - Vertex* const top = castVertexp(edgep->top()); - if (fromp->user() == VertexState::UNMATCHED_ODD - && top->user() == VertexState::UNMATCHED_ODD) { - outp->addEdge(fromp->key(), top->key(), edgep->weight()); - fromp->user(VertexState::CLEAR); - top->user(VertexState::CLEAR); - } - } - } - - void combineGraph(const TspGraphTmpl& g) { - std::unordered_set edges_done; - for (const V3GraphVertex& vtx : g.vertices()) { - const Vertex* const fromp = castVertexp(&vtx); - for (const V3GraphEdge& edge : fromp->outEdges()) { - const Vertex* const top = castVertexp(edge.top()); - if (edges_done.insert(getEdgeId(&edge)).second) { - addEdge(fromp->key(), top->key(), edge.weight()); - } - } - } - } - - void findEulerTourRecurse(std::unordered_set* markedEdgesp, Vertex* startp, - std::vector* sortedOutp) { - Vertex* cur_vertexp = startp; - - // Go on a random tour. Fun! - std::vector tour; - do { - UINFO(6, "Adding " << cur_vertexp->key() << " to tour."); - tour.push_back(cur_vertexp); - - // Look for an arbitrary edge we've not yet marked - for (V3GraphEdge& edge : cur_vertexp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - // This edge is not yet marked, so follow it. - markedEdgesp->insert(edgeId); - Vertex* const neighborp = castVertexp(edge.top()); - UINFO(6, "following edge " << edgeId << " from " << cur_vertexp->key() - << " to " << neighborp->key()); - cur_vertexp = neighborp; - goto found; - } - } - v3fatalSrc("No unmarked edges found in tour"); - found:; - } while (cur_vertexp != startp); - UINFO(6, "stopped, got back to start of tour @ " << cur_vertexp->key()); - - // Look for nodes on the tour that still have - // un-marked edges. If we find one, recurse. - for (Vertex* vxp : tour) { - bool recursed; - do { - recursed = false; - // Look for an arbitrary edge at vxp we've not yet marked - for (V3GraphEdge& edge : vxp->outEdges()) { - const uint32_t edgeId = getEdgeId(&edge); - if (markedEdgesp->end() == markedEdgesp->find(edgeId)) { - UINFO(6, "Recursing."); - findEulerTourRecurse(markedEdgesp, vxp, sortedOutp); - recursed = true; - goto recursed; - } - } - recursed:; - } while (recursed); - sortedOutp->push_back(vxp->key()); - } - - if (debug() >= 6) { - UINFO(0, "Tour was:"); - for (const Vertex* vxp : tour) std::cout << "- " << vxp->key() << '\n'; - std::cout << "-\n"; - } - } - - void dumpGraph(std::ostream& os, const string& nameComment) const { - // UINFO(0) as controlled by caller - os << "At " << nameComment << ", dumping graph. Keys:\n"; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - os << " " << tspvp->key() << '\n'; - for (const V3GraphEdge& edge : tspvp->outEdges()) { - const Vertex* const neighborp = castVertexp(edge.top()); - os << " has edge " << getEdgeId(&edge) << " to " << neighborp->key() << '\n'; - } - } - } - void dumpGraphFilePrefixed(const string& nameComment) const { - if (dumpLevel()) { - const string filename = v3Global.debugFilename(nameComment) + ".txt"; - const std::unique_ptr logp{V3File::new_ofstream(filename)}; - if (logp->fail()) v3fatal("Can't write file: " << filename); - dumpGraph(*logp, nameComment); - } - } - - void findEulerTour(std::vector* sortedOutp) { - UASSERT(sortedOutp->empty(), "Output graph must start empty"); - if (::dumpGraphLevel() >= 6) dumpDotFilePrefixed("findEulerTour"); - std::unordered_set markedEdges; - // Pick a start node - Vertex* const start_vertexp = castVertexp(vertices().frontp()); - findEulerTourRecurse(&markedEdges, start_vertexp, sortedOutp); - } - - std::vector getOddDegreeKeys() const { - std::vector result; - for (const V3GraphVertex& vtx : vertices()) { - const Vertex* const tspvp = castVertexp(&vtx); - const uint32_t degree = vtx.outEdges().size(); - if (degree & 1) result.push_back(tspvp->key()); - } - return result; - } - -private: - Vertex* findVertex(const T_Key& key) const { - const auto it = m_vertices.find(key); - UASSERT(it != m_vertices.end(), "Vertex not found"); - return it->second; - } - VL_UNCOPYABLE(TspGraphTmpl); -}; - -//###################################################################### -// Main algorithm - -void V3TSP::tspSort(const V3TSP::StateVec& states, V3TSP::StateVec* resultp) VL_MT_SAFE { - UASSERT(resultp->empty(), "Output graph must start empty"); - - // Make this TSP implementation work for graphs of size 0 or 1 - // which, unfortunately, is a special case as the following - // code assumes >= 2 nodes. - if (states.empty()) return; - if (states.size() == 1) { - resultp->push_back(*(states.begin())); - return; - } - - // Build the initial graph from the starting state set. - using Graph = TspGraphTmpl; - Graph graph; - for (const auto& state : states) graph.addVertex(state); - for (V3TSP::StateVec::const_iterator it = states.begin(); it != states.end(); ++it) { - for (V3TSP::StateVec::const_iterator jt = it; jt != states.end(); ++jt) { - if (it == jt) continue; - graph.addEdge(*it, *jt, (*it)->cost(*jt)); - } - } - - // Create the minimum spanning tree - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - // Adds edges to minGraph, the resulting graph will have even number of - // edge counts at every vertex: - minGraph.combineGraph(matching); - - V3TSP::StateVec prelim_result; - minGraph.findEulerTour(&prelim_result); - - UASSERT(prelim_result.size() >= states.size(), "Algorithm size error"); - - // Discard duplicate nodes that the Euler tour might contain. - { - std::unordered_set seen; - for (V3TSP::StateVec::iterator it = prelim_result.begin(); it != prelim_result.end(); - ++it) { - const TspStateBase* const elemp = *it; - const auto itFoundPair = seen.insert(elemp); - if (itFoundPair.second) resultp->push_back(elemp); - } - } - - UASSERT(resultp->size() == states.size(), "Algorithm size error"); - - // Find the most expensive arc and rotate the list so that the most - // expensive arc connects the last and first elements. (Since we're not - // modeling something that actually cycles back, we don't need to pay - // that cost at all.) - { - unsigned max_cost = 0; - unsigned max_cost_idx = 0; - for (unsigned i = 0; i < resultp->size(); ++i) { - const TspStateBase* const ap = (*resultp)[i]; - const TspStateBase* const bp - = (i + 1 == resultp->size()) ? (*resultp)[0] : (*resultp)[i + 1]; - const unsigned cost = ap->cost(bp); - if (cost > max_cost) { - max_cost = cost; - max_cost_idx = i; - } - } - - if (max_cost_idx == resultp->size() - 1) { - // List is already rotated for minimum cost. stop. - return; - } - - V3TSP::StateVec new_result; - unsigned i = max_cost_idx + 1; - UASSERT(i < resultp->size(), "Algorithm size error"); - while (i != max_cost_idx) { - new_result.push_back((*resultp)[i]); - i++; - if (i >= resultp->size()) i = 0; - } - new_result.push_back((*resultp)[i]); - - UASSERT(resultp->size() == new_result.size(), "Algorithm size error"); - *resultp = new_result; - } -} - -//###################################################################### -// Self Tests - -class TspTestState final : public V3TSP::TspStateBase { -public: - TspTestState(unsigned xpos, unsigned ypos) - : m_xpos{xpos} - , m_ypos{ypos} - , m_serial{++s_serialNext} {} - ~TspTestState() override = default; - int cost(const TspStateBase* otherp) const override VL_MT_SAFE { - return cost(dynamic_cast(otherp)); - } - static unsigned diff(unsigned a, unsigned b) VL_PURE { - if (a > b) return a - b; - return b - a; - } - int cost(const TspTestState* otherp) const VL_PURE { - // For test purposes, each TspTestState is merely a point - // on the Cartesian plane; cost is the linear distance - // between two points. - unsigned xabs; - unsigned yabs; - xabs = diff(otherp->m_xpos, m_xpos); - yabs = diff(otherp->m_ypos, m_ypos); - return std::lround(std::sqrt(xabs * xabs + yabs * yabs)); - } - unsigned xpos() const { return m_xpos; } - unsigned ypos() const { return m_ypos; } - - bool operator<(const TspStateBase& other) const override { - return operator<(dynamic_cast(other)); - } - bool operator<(const TspTestState& other) const { return m_serial < other.m_serial; } - -private: - const unsigned m_xpos; - const unsigned m_ypos; - const unsigned m_serial; - static unsigned s_serialNext; -}; - -unsigned TspTestState::s_serialNext = 0; - -void V3TSP::selfTestStates() { - // Linear test -- coords all along the x-axis - { - V3TSP::StateVec states; - const TspTestState s10{10, 0}; - const TspTestState s60{60, 0}; - const TspTestState s20{20, 0}; - const TspTestState s100{100, 0}; - const TspTestState s5{5, 0}; - states.push_back(&s10); - states.push_back(&s60); - states.push_back(&s20); - states.push_back(&s100); - states.push_back(&s5); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&s100); - expect.push_back(&s60); - expect.push_back(&s20); - expect.push_back(&s10); - expect.push_back(&s5); - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << " "; - } - cout << endl; - v3fatalSrc("TSP linear self-test fail. Result (above) did not match expectation."); - } - } - - // Second test. Coords are distributed in 2D space. - // Test that tspSort() will rotate the list for minimum cost. - { - V3TSP::StateVec states; - const TspTestState a{0, 0}; - const TspTestState b{100, 0}; - const TspTestState c{200, 0}; - const TspTestState d{200, 100}; - const TspTestState e{150, 150}; - const TspTestState f{0, 150}; - const TspTestState g{0, 100}; - - states.push_back(&a); - states.push_back(&b); - states.push_back(&c); - states.push_back(&d); - states.push_back(&e); - states.push_back(&f); - states.push_back(&g); - - V3TSP::StateVec result; - tspSort(states, &result); - - V3TSP::StateVec expect; - expect.push_back(&f); - expect.push_back(&g); - expect.push_back(&a); - expect.push_back(&b); - expect.push_back(&c); - expect.push_back(&d); - expect.push_back(&e); - - if (VL_UNCOVERABLE(expect != result)) { - for (V3TSP::StateVec::iterator it = result.begin(); it != result.end(); ++it) { - const TspTestState* const statep = dynamic_cast(*it); - cout << statep->xpos() << "," << statep->ypos() << " "; - } - cout << endl; - v3fatalSrc( - "TSP 2d cycle=false self-test fail. Result (above) did not match expectation."); - } - } -} - -void V3TSP::selfTestString() { - using Graph = TspGraphTmpl; - Graph graph; - graph.addVertex("0"); - graph.addVertex("1"); - graph.addVertex("2"); - graph.addVertex("3"); - - graph.addEdge("0", "1", 3943); - graph.addEdge("0", "2", 3456); - graph.addEdge("0", "3", 4920); - graph.addEdge("1", "2", 2730); - graph.addEdge("1", "3", 8199); - graph.addEdge("2", "3", 4130); - - Graph minGraph; - graph.makeMinSpanningTree(&minGraph); - if (dumpGraphLevel() >= 6) minGraph.dumpGraphFilePrefixed("minGraph"); - - const std::vector oddDegree = minGraph.getOddDegreeKeys(); - Graph matching; - graph.perfectMatching(oddDegree, &matching); - if (dumpGraphLevel() >= 6) matching.dumpGraphFilePrefixed("matching"); - - minGraph.combineGraph(matching); - - std::vector result; - minGraph.findEulerTour(&result); - - std::vector expect; - expect.emplace_back("0"); - expect.emplace_back("2"); - expect.emplace_back("1"); - expect.emplace_back("2"); - expect.emplace_back("3"); - - if (VL_UNCOVERABLE(expect != result)) { - for (const string& i : result) cout << i << " "; - cout << endl; - v3fatalSrc("TSP string self-test fail. Result (above) did not match expectation."); - } -} - -void V3TSP::selfTest() { - selfTestString(); - selfTestStates(); -} diff --git a/src/V3TSP.h b/src/V3TSP.h deleted file mode 100644 index 840f5fa96..000000000 --- a/src/V3TSP.h +++ /dev/null @@ -1,57 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Implementation of Christofides algorithm to -// approximate the solution to the traveling salesman problem. -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// 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: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3TSP_H_ -#define VERILATOR_V3TSP_H_ - -#include "config_build.h" -#include "verilatedos.h" - -#include "V3Error.h" - -#include - -namespace V3TSP { -// Perform a "Traveling Salesman Problem" optimizing sort -// on any type you like -- so long as inherits from TspStateBase. - -class TspStateBase VL_NOT_FINAL { -public: - // This is the cost function that the TSP sort will minimize. - // All costs in V3TSP are int, chosen to match the type of - // V3GraphEdge::weight() which will reflect each edge's cost. - virtual int cost(const TspStateBase* otherp) const VL_MT_SAFE = 0; - - // This operator< must place a meaningless, arbitrary, but - // stable order on all TspStateBase's. It's used only to - // key maps so that iteration is stable, without relying - // on pointer values that could lead to nondeterminism. - virtual bool operator<(const TspStateBase& otherp) const = 0; - - virtual ~TspStateBase() = default; -}; - -using StateVec = std::vector; - -// Given an unsorted set of TspState's, sort them to minimize -// the transition cost for walking the sorted list. -void tspSort(const StateVec& states, StateVec* resultp) VL_MT_SAFE; - -void selfTest() VL_MT_DISABLED; -} // namespace V3TSP - -#endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index 13bb95c6f..f94c8655d 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -103,7 +103,6 @@ #include "V3Stats.h" #include "V3String.h" #include "V3Subst.h" -#include "V3TSP.h" #include "V3Table.h" #include "V3Task.h" #include "V3ThreadPool.h" @@ -734,7 +733,6 @@ static bool verilate(const string& argString) { VHashSha256::selfTest(); VSpellCheck::selfTest(); V3Graph::selfTest(); - V3TSP::selfTest(); V3ScoreboardBase::selfTest(); V3Order::selfTestParallel(); V3ExecGraph::selfTest(); diff --git a/src/cppcheck-suppressions.txt b/src/cppcheck-suppressions.txt index 754ef3c4b..6d9ab321d 100644 --- a/src/cppcheck-suppressions.txt +++ b/src/cppcheck-suppressions.txt @@ -137,8 +137,6 @@ constParameterPointer:src/V3Tristate.cpp constParameterReference:src/V3Tristate.cpp constVariablePointer:src/V3Tristate.cpp constVariableReference:src/V3Tristate.cpp -constVariablePointer:src/V3TSP.cpp -constVariableReference:src/V3TSP.cpp constParameterPointer:src/V3Undriven.cpp constVariablePointer:src/V3Undriven.cpp constParameterPointer:src/V3Unroll.cpp From 901909d3c71014956d54314a00f9b413b9b17a49 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 10:52:43 +0100 Subject: [PATCH 09/46] Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760) Replace 3 DfgCond patterns with 2 more general ones that convert DfgCond with common MSBs/LSBs in both branches into a DfgConcat with a narrower DfgCond. This pattern arises frequently with Dfg synthesis. --- src/V3DfgDataType.h | 1 + src/V3DfgPeephole.cpp | 190 ++++++++++++++++++++++++-------- src/V3DfgPeepholePatterns.h | 5 +- src/V3DfgVertices.h | 1 + test_regress/t/t_dfg_peephole.v | 15 ++- 5 files changed, 162 insertions(+), 50 deletions(-) diff --git a/src/V3DfgDataType.h b/src/V3DfgDataType.h index 5af232b84..3011d254f 100644 --- a/src/V3DfgDataType.h +++ b/src/V3DfgDataType.h @@ -139,6 +139,7 @@ public: // Returns a Packed type of the given width static const DfgDataType& packed(uint32_t width) { + UASSERT(width > 0, "Width must be positive"); // Find or create the right sized packed type const DfgDataType*& entryr = s_packedTypes[width]; if (!entryr) entryr = new DfgDataType{width}; diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index f89285eb7..34c1ac5a6 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1188,6 +1188,102 @@ class V3DfgPeephole final : public DfgVisitor { return {fromp, lsb}; } + // Given a pair of vertices, returns a vertex representing the common LSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonLSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check LSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(matchWidth) != bNum.bitIs0(matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aConstp->width() < bConstp->width() ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the RHS + if (DfgConcat* const catp = ap->cast()) return commonLSBs(catp->rhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonLSBs(ap, catp->rhsp()); + + // If selecting the LSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + if (selp->lsb() == 0) { + DfgVertex* const fromp = selp->fromp(); + const std::pair common = commonLSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common LSBs + return {nullptr, 0}; + } + + // Given a pair of vertices, returns a vertex representing the common MSBs of the two, + // and the number of common LSBs. Returns {nullptr, 0} if no common LSBs are found. + std::pair commonMSBs(DfgVertex* ap, DfgVertex* bp) { + if (ap == bp) return {ap, ap->width()}; + + // If both constants, check MSBs + if (DfgConst* const aConstp = ap->cast()) { + if (DfgConst* const bConstp = bp->cast()) { + const uint32_t aMsb = aConstp->width() - 1; + const uint32_t bMsb = bConstp->width() - 1; + const V3Number& aNum = aConstp->num(); + const V3Number& bNum = bConstp->num(); + // Max match is the shorter constant + const uint32_t maxMatch = std::min(aConstp->width(), bConstp->width()); + // Check all bits + uint32_t matchWidth = 0; + for (; matchWidth < maxMatch; ++matchWidth) { + if (aNum.bitIs0(aMsb - matchWidth) != bNum.bitIs0(bMsb - matchWidth)) break; + } + // Will always return the shorter constant in case it can be used directly + DfgConst* const shorterp = aMsb < bMsb ? aConstp : bConstp; + return {matchWidth ? shorterp : nullptr, matchWidth}; + } + } + + // If Concat, check against the LHS + if (DfgConcat* const catp = ap->cast()) return commonMSBs(catp->lhsp(), bp); + if (DfgConcat* const catp = bp->cast()) return commonMSBs(ap, catp->lhsp()); + + // If selecting the MSBs, check against the source of the Sel + if (DfgSel* const selp = ap->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(fromp, bp); + return {common.first, std::min(common.second, selp->width())}; + } + } + if (DfgSel* const selp = bp->cast()) { + DfgVertex* const fromp = selp->fromp(); + if (selp->msb() == fromp->width() - 1) { + const std::pair common = commonMSBs(ap, fromp); + return {common.first, std::min(common.second, selp->width())}; + } + } + + // Otherwise no common MSBs + return {nullptr, 0}; + } + // VISIT methods void visit(DfgVertex*) override {} @@ -2834,56 +2930,62 @@ class V3DfgPeephole final : public DfgVisitor { } } - if (DfgConcat* const tConcatp = thenp->cast()) { - if (DfgConcat* const eConcatp = elsep->cast()) { - DfgVertex* const tRhsp = tConcatp->rhsp(); - DfgVertex* const tLhsp = tConcatp->lhsp(); - DfgVertex* const eRhsp = eConcatp->rhsp(); - DfgVertex* const eLhsp = eConcatp->lhsp(); - - if (isSame(tRhsp, eRhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_RHS) { - DfgCond* const newCondp - = make(flp, tLhsp->dtype(), condp, tLhsp, eLhsp); - replace(make(vtxp, newCondp, tRhsp)); - return; - } - } - - if (isSame(tLhsp, eLhsp)) { - APPLYING(REPLACE_COND_SAME_CAT_LHS) { - DfgCond* const newCondp - = make(flp, tRhsp->dtype(), condp, tRhsp, eRhsp); - replace(make(vtxp, tLhsp, newCondp)); + if (!thenp->is() && !elsep->is()) { + const std::pair cLSBs = commonLSBs(thenp, elsep); + if (cLSBs.first) { + APPLYING(REPLACE_COND_COMMON_LSBS) { + // Create new RHS + const uint32_t rWidth = cLSBs.second; + DfgVertex* rhsp = cLSBs.first; + if (rWidth != rhsp->width()) { + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + rhsp = make(flp, rDtype, rhsp, 0U); + } + // If it's all the same, just replace + if (rhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(rhsp); return; } + // Create new LHS + const uint32_t lWidth = vtxp->width() - rWidth; + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + DfgVertex* const lThenp = make(flp, lDtype, thenp, rWidth); + DfgVertex* const lElsep = make(flp, lDtype, elsep, rWidth); + DfgVertex* const lhsp = make(flp, lDtype, condp, lThenp, lElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } - if (!tConcatp->hasMultipleSinks()) { - if (DfgConcat* const tRCatp = tConcatp->rhsp()->cast()) { - if (!tRCatp->hasMultipleSinks()) { - if (DfgSel* const tLSelp = tConcatp->lhsp()->cast()) { - if (DfgSel* const tRRSelp = tRCatp->rhsp()->cast()) { - if (tLSelp->lsb() == tRCatp->width() // - && tRRSelp->lsb() == 0 // - && isSame(tLSelp->fromp(), elsep) // - && isSame(tRRSelp->fromp(), elsep)) { - APPLYING(REPLACE_COND_INSERT) { - DfgVertex* const newTp = tRCatp->lhsp(); - DfgVertex* const newEp = make( - flp, newTp->dtype(), elsep, tRRSelp->width()); - DfgCond* const newCp = make(flp, newTp->dtype(), - condp, newTp, newEp); - replace(make( - vtxp, tLSelp, - make(tRCatp, newCp, tRRSelp))); - return; - } - } - } - } + const std::pair cMSBs = commonMSBs(thenp, elsep); + if (cMSBs.first) { + APPLYING(REPLACE_COND_COMMON_MSBS) { + // Create new LHS + const uint32_t lWidth = cMSBs.second; + DfgVertex* lhsp = cMSBs.first; + if (lWidth != lhsp->width()) { + const DfgDataType& lDtype = DfgDataType::packed(lWidth); + lhsp = make(flp, lDtype, lhsp, lhsp->width() - lWidth); } + // If it's all the same, just replace + if (lhsp->dtype() == vtxp->dtype()) { + // Note this branch can only be hit if rules run in the right order, + // it might have missing code coverage after a refactor. + replace(lhsp); + return; + } + // Create new RHS + const uint32_t rWidth = vtxp->width() - lWidth; + const DfgDataType& rDtype = DfgDataType::packed(rWidth); + DfgVertex* const rThenp = make(flp, rDtype, thenp, 0U); + DfgVertex* const rElsep = make(flp, rDtype, elsep, 0U); + DfgVertex* const rhsp = make(flp, rDtype, condp, rThenp, rElsep); + // Replace with concat + replace(make(vtxp, lhsp, rhsp)); + return; } } } diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index d8d295b2d..4da947664 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -112,17 +112,16 @@ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SAME_REP_ON_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_SEL_BOTTOM_AND_ZERO_WITH_SHIFTL) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_CONCAT_ZERO_AND_SEL_TOP_WITH_SHIFTR) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_LSBS) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_COMMON_MSBS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONES_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ONE_ZERO) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_CONST_ZERO_ONES) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_DEC) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INC) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_INSERT) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_OR_THEN_COND_RHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_LHS) \ - _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_CAT_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_ELSE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_SAME_COND_THEN) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, REPLACE_COND_THEN_OR_LHS) \ diff --git a/src/V3DfgVertices.h b/src/V3DfgVertices.h index a3d3adf8e..f8ca8e9c8 100644 --- a/src/V3DfgVertices.h +++ b/src/V3DfgVertices.h @@ -299,6 +299,7 @@ public: void fromp(DfgVertex* vtxp) { srcp(vtxp); } uint32_t lsb() const { return m_lsb; } void lsb(uint32_t value) { m_lsb = value; } + uint32_t msb() const { return m_lsb + width() - 1; } }; class DfgUnitArray final : public DfgVertexUnary { diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 3c9ce7ae8..2d679a8de 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -283,10 +283,20 @@ module t ( `signal(REPLACE_COND_CONST_ZERO_ONAE, rand_a[0] ? 80'b0 : -80'b1); `signal(REPLACE_COND_CAT_LHS_CONST_ONE_ZERO, rand_a[0] ? {8'b1, rand_b[0]} : {8'b0, rand_b[1]}); `signal(REPLACE_COND_CAT_LHS_CONST_ZERO_ONE, rand_a[0] ? {8'b0, rand_b[0]} : {8'b1, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_LHS, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); - `signal(REPLACE_COND_SAME_CAT_RHS, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); `signal(REPLACE_COND_SAM_COND_THEN, rand_a[0] ? (rand_a[0] ? rand_b[1:0] : rand_b[3:2]) : rand_b[5:4]); `signal(REPLACE_COND_SAM_COND_ELSE, rand_a[0] ? rand_b[1:0] : (rand_a[0] ? rand_b[3:2] : rand_b[5:4])); + + `signal(REPLACE_COND_COMMON_MSBS_A, rand_a[0] ? {8'd0, rand_b[0]} : {8'd0, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_B, rand_a[0] ? {8'hf0, rand_b[1:0]} : {9'h1e2, rand_b[1]}); + `signal(REPLACE_COND_COMMON_MSBS_C, rand_a[0] ? {rand_a[63 -: 3] , rand_b[0]} : {rand_a[63 -: 2], rand_b[2:1]}); + `signal(REPLACE_COND_COMMON_LSBS_A, rand_a[0] ? {rand_b[0], 8'd0} : {rand_b[1], 8'd0}); + `signal(REPLACE_COND_COMMON_LSBS_B, rand_a[0] ? {rand_b[2:1], 8'h0f} : {rand_b[1], 9'h08f}); + `signal(REPLACE_COND_COMMON_LSBS_C, rand_a[0] ? {rand_b[0], rand_a[3:0]} : {rand_b[1:0], rand_a[2:0]}); + wire [5:0] tmp_REPLACE_COND_COMMON_LSBS_D = rand_b[5:0]; + wire [5:0] tmp_REPLACE_COND_COMMON_MSBS_D = rand_b[63:58]; + `signal(REPLACE_COND_COMMON_LSBS_D, rand_a[0] ? rand_b[4:0] : tmp_REPLACE_COND_COMMON_LSBS_D[4:0]); + `signal(REPLACE_COND_COMMON_MSBS_D, rand_a[0] ? rand_b[63:59] : tmp_REPLACE_COND_COMMON_MSBS_D[5:1]); + `signal(REMOVE_SHIFTL_ZERO, rand_a << 0); `signal(REPLACE_SHIFTL_OVER, rand_a << 64); `signal(REPLACE_SHIFTL_SEL, rand_a[27:0] << 4); @@ -352,7 +362,6 @@ module t ( `signal(REMOVE_EQ_BIT_1, 1'b1 == rand_a[0]); `signal(REMOVE_NEQ_BIT_0, 1'b0 != rand_a[0]); `signal(REPLACE_NEQ_BIT_1, 1'b1 != rand_a[0]); - `signal(REPLACE_COND_INSERT, rand_a[0] ? {rand_b[63:40], {1'd0, rand_b[38:0]}} : rand_b); `signal(REPLACE_REP_REP, {2{({3{rand_a[0]}})}}); // Operators that should work wiht mismatched widths From 394c9bc9b2ef5c5ecb8350f56d33d56383441018 Mon Sep 17 00:00:00 2001 From: Adam Kostrzewski Date: Thu, 11 Jun 2026 14:37:23 +0200 Subject: [PATCH 10/46] Fix FSM detect unchecked casts and variable redeclaration (#7758) --- docs/CONTRIBUTORS | 1 + src/V3AstNodes.cpp | 6 + src/V3Coverage.cpp | 4 +- src/V3FsmDetect.cpp | 35 ++-- test_regress/t/t_cover_fsm_concat_unsup.out | 6 + test_regress/t/t_cover_fsm_concat_unsup.py | 16 ++ test_regress/t/t_cover_fsm_concat_unsup.v | 18 ++ test_regress/t/t_cover_fsm_sel.out | 99 ++++++++++ test_regress/t/t_cover_fsm_sel.py | 30 +++ test_regress/t/t_cover_fsm_sel.v | 88 +++++++++ test_regress/t/t_cover_fsm_sel_assign.out | 89 +++++++++ test_regress/t/t_cover_fsm_sel_assign.py | 30 +++ test_regress/t/t_cover_fsm_sel_assign.v | 78 ++++++++ .../t/t_cover_fsm_sel_togglevar_unsup.out | 11 ++ .../t/t_cover_fsm_sel_togglevar_unsup.py | 16 ++ .../t/t_cover_fsm_sel_togglevar_unsup.v | 31 ++++ test_regress/t/t_fsm_duplicate.py | 18 ++ test_regress/t/t_fsm_duplicate.v | 173 ++++++++++++++++++ 18 files changed, 736 insertions(+), 13 deletions(-) create mode 100644 test_regress/t/t_cover_fsm_concat_unsup.out create mode 100755 test_regress/t/t_cover_fsm_concat_unsup.py create mode 100644 test_regress/t/t_cover_fsm_concat_unsup.v create mode 100644 test_regress/t/t_cover_fsm_sel.out create mode 100755 test_regress/t/t_cover_fsm_sel.py create mode 100644 test_regress/t/t_cover_fsm_sel.v create mode 100644 test_regress/t/t_cover_fsm_sel_assign.out create mode 100755 test_regress/t/t_cover_fsm_sel_assign.py create mode 100644 test_regress/t/t_cover_fsm_sel_assign.v create mode 100644 test_regress/t/t_cover_fsm_sel_togglevar_unsup.out create mode 100755 test_regress/t/t_cover_fsm_sel_togglevar_unsup.py create mode 100644 test_regress/t/t_cover_fsm_sel_togglevar_unsup.v create mode 100755 test_regress/t/t_fsm_duplicate.py create mode 100644 test_regress/t/t_fsm_duplicate.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index dae170a70..b78b4f101 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -9,6 +9,7 @@ Please see the Verilator manual for 200+ additional contributors. Thanks to all. 404allen404 Adam Bagley +Adam Kostrzewski Adrian Sampson Adrien Le Masle أحمد المحمودي (Ahmed El-Mahmoudy) diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 3bd3f66f6..e98c9bb89 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1322,6 +1322,12 @@ AstNode* AstArraySel::baseFromp(AstNode* nodep, bool overMembers) { } else if (VN_IS(nodep, Sel)) { nodep = VN_AS(nodep, Sel)->fromp(); continue; + } else if (VN_IS(nodep, AssocSel)) { + nodep = VN_AS(nodep, AssocSel)->fromp(); + continue; + } else if (VN_IS(nodep, WildcardSel)) { + nodep = VN_AS(nodep, WildcardSel)->fromp(); + continue; } else if (overMembers && VN_IS(nodep, MemberSel)) { nodep = VN_AS(nodep, MemberSel)->fromp(); continue; diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 63cb65b8a..6d1ff5bc1 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -530,8 +530,10 @@ class CoverageVisitor final : public VNVisitor { newent.cleanup(); } } - } else if (VN_IS(dtypep, QueueDType)) { + } else if (VN_IS(dtypep, QueueDType) || VN_IS(dtypep, AssocArrayDType) + || VN_IS(dtypep, WildcardArrayDType)) { // Not covered + varp->v3warn(COVERIGN, "Coverage ignored for type " << dtypep->prettyTypeName()); } else { dtypep->v3fatalSrc("Unexpected node data type in toggle coverage generation: " << dtypep->prettyTypeName()); diff --git a/src/V3FsmDetect.cpp b/src/V3FsmDetect.cpp index 4a2ccf8af..5ffee9e4c 100644 --- a/src/V3FsmDetect.cpp +++ b/src/V3FsmDetect.cpp @@ -30,6 +30,7 @@ #include "V3Ast.h" #include "V3Control.h" #include "V3Graph.h" +#include "V3UniqueNames.h" #include #include @@ -987,14 +988,23 @@ class FsmDetectVisitor final : public VNVisitor { return assp; } + static AstVarRef* tryExtractVarRef(AstNodeExpr* const exprp) { + AstVarRef* const varp = VN_CAST(AstArraySel::baseFromp(exprp, true), VarRef); + if (!varp) { + exprp->v3warn(COVERIGN, + "Ignoring unsupported: FSM coverage with " << exprp->prettyTypeName()); + return nullptr; + } + return varp; + } + static AstNodeAssign* nodeStateVarAssign(AstNode* nodep, AstVarScope*& stateVscp, AstVarScope*& fromVscp) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, "register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstVarRef* const rhsp = VN_CAST(assp->rhsp(), VarRef); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; stateVscp = lhsp->varScopep(); fromVscp = rhsp->varScopep(); return assp; @@ -1006,11 +1016,9 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& resetValue) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); - UASSERT_OBJ(lhsp, assp, - "conditional register commit lhs should be normalized to a VarRef"); + AstVarRef* const lhsp = tryExtractVarRef(assp->lhsp()); AstCond* const rhsp = VN_CAST(assp->rhsp(), Cond); - if (!rhsp) return nullptr; + if (!rhsp || !lhsp) return nullptr; if (AstVarRef* const elsep = VN_CAST(rhsp->elsep(), VarRef)) { if (constValueStatus(rhsp->thenp(), resetValue) != ConstValueStatus::OK) return nullptr; @@ -1033,7 +1041,7 @@ class FsmDetectVisitor final : public VNVisitor { FsmStateValue& value) { AstNodeAssign* const assp = VN_CAST(nodep, NodeAssign); if (!assp) return nullptr; - AstVarRef* const lhsp = VN_AS(assp->lhsp(), VarRef); + AstVarRef* const lhsp = VN_CAST(AstArraySel::baseFromp(assp->lhsp(), true), VarRef); UASSERT_OBJ(lhsp, assp, "direct constant state assignment lhs should be normalized to a VarRef"); if (constValueStatus(assp->rhsp(), value) != ConstValueStatus::OK) return nullptr; @@ -1220,7 +1228,8 @@ class FsmDetectVisitor final : public VNVisitor { AstVarRef* vrefp = VN_CAST(eqp->lhsp(), VarRef); AstNodeExpr* valuep = eqp->rhsp(); if (!vrefp) { - vrefp = VN_AS(eqp->rhsp(), VarRef); + vrefp = tryExtractVarRef(eqp->rhsp()); + if (!vrefp) { return false; } valuep = eqp->lhsp(); } @@ -2082,6 +2091,7 @@ public: class FsmLowerVisitor final { // STATE - across all visitors const FsmState& m_state; + V3UniqueNames m_fsmBuildNames; // METHODS // Rebuild a state-typed constant using the tracked state variable @@ -2133,8 +2143,8 @@ class FsmLowerVisitor final { AstNodeModule* const modp = scopep->modp(); AstNodeDType* const prevDTypep = scopep->findLogicDType( sampleVscp->width(), sampleVscp->width(), sampleVscp->dtypep()->numeric()); - AstVarScope* const prevVscp - = scopep->createTemp("__Vfsmcov_prev__" + stateVscp->varp()->shortName(), prevDTypep); + const std::string tmpName = m_fsmBuildNames.get(stateVscp->varp()->shortName()); + AstVarScope* const prevVscp = scopep->createTemp(tmpName, prevDTypep); // The saved previous-state temp crosses the scheduler's pre/post split // in the same way as Verilator's built-in NBA shadow variables, so keep // both vars marked as post-life participants for stable MT ordering. @@ -2283,7 +2293,8 @@ public: // concrete coverage instrumentation while the saved scoped pointers are // still valid in the same pass. explicit FsmLowerVisitor(const FsmState& state) - : m_state{state} { + : m_state{state} + , m_fsmBuildNames{"__Vfsmcov_prev"} { for (const DetectedFsm& fsm : m_state.fsms()) { buildOne(*fsm.graphp); } } }; diff --git a/test_regress/t/t_cover_fsm_concat_unsup.out b/test_regress/t/t_cover_fsm_concat_unsup.out new file mode 100644 index 000000000..85da4d440 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.out @@ -0,0 +1,6 @@ +%Warning-COVERIGN: t/t_cover_fsm_concat_unsup.v:12:17: Ignoring unsupported: FSM coverage with CONCAT + 12 | assign c = ({a, b} == 8'h00); + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_concat_unsup.py b/test_regress/t/t_cover_fsm_concat_unsup.py new file mode 100755 index 000000000..4a63bc600 --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage concat as unsupported operation test +# +# 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('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v new file mode 100644 index 000000000..1f9cee77a --- /dev/null +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -0,0 +1,18 @@ +// 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 + +module t ( + input logic[6:0] a, + input logic b, + output logic c +); + assign c = ({a, b} == 8'h00); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out new file mode 100644 index 000000000..4913f5502 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.out @@ -0,0 +1,99 @@ +// // verilator_coverage annotation + // 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 + + package P; + typedef struct packed{ + logic [7:0] vs; + } C; + typedef struct packed{ + C a; int b; + } B; + typedef struct packed{ + B a; + } A; + endpackage + + module t ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000002 P::A a; +%000001 logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + +%000001 logic b; +%000001 logic c; +%000001 logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin +%000003 a.a.a.vs <= a.a.a.vs + 1; +%000003 done <= (a.a.a.vs == 8'h1); +%000002 if (done) begin +%000001 state <= S_DONE; +%000002 end else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel.py b/test_regress/t/t_cover_fsm_sel.py new file mode 100755 index 000000000..9d066e89e --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage array sel test +# +# 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 os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v new file mode 100644 index 000000000..a06275b03 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel.v @@ -0,0 +1,88 @@ +// 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 + +package P; + typedef struct packed{ + logic [7:0] vs; + } C; + typedef struct packed{ + C a; int b; + } B; + typedef struct packed{ + B a; + } A; +endpackage + +module t ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + P::A a; + logic done; + + logic [7:0] va[int]; + logic [7:0] va2d[int][int]; + + logic b; + logic c; + logic d; + + assign b = (a.a.a.vs == 8'h0); + assign c = (va[0] == 8'h0); + assign d = (va2d[0][0] == 8'h0); + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin + a.a.a.vs <= a.a.a.vs + 1; + done <= (a.a.a.vs == 8'h1); + if (done) begin + state <= S_DONE; + end else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out new file mode 100644 index 000000000..c033ed2ef --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -0,0 +1,89 @@ +// // verilator_coverage annotation + // 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 + + module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 + ) ( +%000009 input clk + ); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + +%000001 logic rst; +%000001 logic start; + integer cyc; +%000001 state_t state /*verilator fsm_reset_arc*/; +%000001 logic [1:0] done_arr; + +%000001 logic [W-1:0] a; +%000000 logic [BW-1:0] b; + begin +%000001 logic [D-1:0][W-1:0] s; + begin +%000009 always_ff @(posedge clk) +%000009 s[b] <= a; + end + end + +%000001 initial begin +%000001 rst = 1'b1; +%000001 start = 1'b0; +%000001 cyc = 0; + end + +%000009 always @(posedge clk) begin +%000009 cyc <= cyc + 1; +%000008 if (cyc == 1) rst <= 1'b0; +%000008 if (cyc == 2) start <= 1'b1; +%000008 if (cyc == 3) start <= 1'b0; +%000008 if (cyc == 4) a[0] = 1'b1; +%000008 if (cyc == 8) begin +%000001 $write("*-* All Finished *-*\n"); +%000001 $finish; + end + end + +%000009 always_ff @(posedge clk) begin +%000007 if (rst) begin +%000002 state <= S_IDLE; + end +%000007 else begin +%000007 case (state) + // [FSM coverage] +%000001 // [fsm_arc t.state::ANY->S_IDLE[reset_include]] [reset arc, excluded from %] +%000002 // [fsm_arc t.state::S_DONE->S_DONE] +%000003 // [fsm_arc t.state::S_IDLE->S_IDLE] +%000001 // [fsm_arc t.state::S_IDLE->S_RUN] +%000001 // [fsm_state t.state::S_DONE] +%000000 // [fsm_state t.state::S_ERR] *** UNCOVERED *** +%000000 // [fsm_state t.state::S_IDLE] *** UNCOVERED *** +%000001 // [fsm_state t.state::S_RUN] +%000002 S_IDLE: +%000001 if (start) state <= S_RUN; +%000001 else state <= S_IDLE; +%000003 S_RUN: begin; +%000003 done_arr[0] <= (a[0] == 1'b1); +%000002 if (done_arr[0]) begin +%000001 state <= S_DONE; +%000002 end else begin +%000002 state <= S_RUN; + end + end +%000002 S_DONE: state <= S_DONE; +%000000 default: state <= S_ERR; + endcase + end + end + + endmodule + diff --git a/test_regress/t/t_cover_fsm_sel_assign.py b/test_regress/t/t_cover_fsm_sel_assign.py new file mode 100755 index 000000000..728658c89 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage assignment test +# +# 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 os + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--coverage"]) + +test.execute() + +test.run(cmd=[ + os.environ["VERILATOR_ROOT"] + "/bin/verilator_coverage", + "--annotate", + test.obj_dir + "/annotated", + test.obj_dir + "/coverage.dat", +], + verilator_run=True) + +test.files_identical(test.obj_dir + "/annotated/" + test.name + ".v", test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v new file mode 100644 index 000000000..293fa4692 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -0,0 +1,78 @@ +// 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 + +module t #( + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 +) ( + input clk +); + typedef enum logic [1:0] { + S_IDLE = 2'd0, + S_RUN = 2'd1, + S_DONE = 2'd2, + S_ERR = 2'd3 + } state_t; + + logic rst; + logic start; + integer cyc; + state_t state /*verilator fsm_reset_arc*/; + logic [1:0] done_arr; + + logic [W-1:0] a; + logic [BW-1:0] b; + begin + logic [D-1:0][W-1:0] s; + begin + always_ff @(posedge clk) + s[b] <= a; + end + end + + initial begin + rst = 1'b1; + start = 1'b0; + cyc = 0; + end + + always @(posedge clk) begin + cyc <= cyc + 1; + if (cyc == 1) rst <= 1'b0; + if (cyc == 2) start <= 1'b1; + if (cyc == 3) start <= 1'b0; + if (cyc == 4) a[0] = 1'b1; + if (cyc == 8) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + + always_ff @(posedge clk) begin + if (rst) begin + state <= S_IDLE; + end + else begin + case (state) + S_IDLE: + if (start) state <= S_RUN; + else state <= S_IDLE; + S_RUN: begin; + done_arr[0] <= (a[0] == 1'b1); + if (done_arr[0]) begin + state <= S_DONE; + end else begin + state <= S_RUN; + end + end + S_DONE: state <= S_DONE; + default: state <= S_ERR; + endcase + end + end + +endmodule diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out new file mode 100644 index 000000000..9883741c3 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -0,0 +1,11 @@ +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type ASSOCARRAYDTYPE + : ... note: In instance 't' + 20 | input P::A a, + | ^ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type WILDCARDARRAYDTYPE + : ... note: In instance 't' + 20 | input P::A a, + | ^ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py new file mode 100755 index 000000000..59e566c59 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM coverage ignores associative array selection operators +# +# 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('vlt') + +test.lint(fails=True, expect_filename=test.golden_filename, verilator_flags2=['--coverage']) + +test.passes() diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v new file mode 100644 index 000000000..547a14d85 --- /dev/null +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -0,0 +1,31 @@ +// 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 + +package P; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; int b; + } B; + typedef struct { + B a; + } A; +endpackage +module t ( + input P::A a, + output logic b, + output logic c +); + assign b = (a.a.a.va[0] == 8'h0); + assign c = (a.a.a.vw[0] == 8'h0); + + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_fsm_duplicate.py b/test_regress/t/t_fsm_duplicate.py new file mode 100755 index 000000000..7f4205522 --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: FSM no duplicate variables test +# +# 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') + +test.compile(verilator_flags2=["--coverage -Wno-PINMISSING"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v new file mode 100644 index 000000000..20d7fd62e --- /dev/null +++ b/test_regress/t/t_fsm_duplicate.v @@ -0,0 +1,173 @@ +// 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 + +module rr +#( +) ( + input logic clk, + input logic rst, + input logic [7:0] data, + input logic data_q +); + logic a; + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1, + S2, + S3 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) + if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; + default: dcnt <= dcnt; + endcase +endmodule +module re +#( +) ( + input logic clk, + input logic rst, + output logic o, + input unused0, /* block optimizations */ + input unused1, + input unused2, + input unused3, + input unused4, + input unused5, + input unused6, + input unused7, + input unused8, + input unused9, + input unused10, + input unused11, + input unused12, + input unused13, + input unused14, + input unused15, + input unused16, + input unused17, + input unused18, + input unused19, + input unused20, + input unused21, + input unused22, + input unused23, + input unused24, + input unused25, + input unused26, + input unused27, + input unused28, + input unused29, + input unused30, + input unused31, + input unused32, + input unused33, + input unused34, + input unused35, + input unused36, + input unused37, + input unused38, + input unused39, + input unused40 +); + logic [15:0] dcnt; + typedef enum logic [7:0] { + S0, + S1 + } state_t; + state_t state_d, state_q; + always_ff @(posedge clk or negedge rst) + if (!rst) state_q <= S0; + always_ff @(posedge clk) + unique case (state_q) + S1: o <= dcnt[0]; + default: o <= '0; + endcase + initial begin + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule +module rh +#( +) ( + input logic clk +); + logic [7:0] a; + logic b; + logic c; + logic d; + logic rst; + rr xrr ( + .clk, + .rst(rst), + .data (a), + .data_q (b & c) + ); + re xre ( + .clk, + .rst(rst), + .o (d) + ); +endmodule +module U +#( +) ( + input clk, + input rst +); + rh xrh ( + .clk (clk) + ); +endmodule +module C #( +) ( + input clk, + input rst +); + U U ( + .clk, + .rst + ); +endmodule +module A #( +) ( +); + logic clk; + logic rst; + C c0 ( + .clk, + .rst + ); + C c1 ( + .clk, + .rst + ); +endmodule +module B #( +) ( +); + logic clk; + logic rst; + C xC ( + .clk, + .rst + ); +endmodule +module t #( +) ( +); + B b ( + ); + A a ( + ); +endmodule From c6caa94fe07fbf2be2f366d7a6807b12fac799fe Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Thu, 11 Jun 2026 15:04:06 +0200 Subject: [PATCH 11/46] Fix no-scope internal error on virtual interface method calls (#7759) --- src/V3Task.cpp | 7 +++--- .../t/t_interface_virtual_func_module_call.py | 18 ++++++++++++++ .../t/t_interface_virtual_func_module_call.v | 24 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100755 test_regress/t/t_interface_virtual_func_module_call.py create mode 100644 test_regress/t/t_interface_virtual_func_module_call.v diff --git a/src/V3Task.cpp b/src/V3Task.cpp index 5bae5d0aa..0db8d04d3 100644 --- a/src/V3Task.cpp +++ b/src/V3Task.cpp @@ -248,9 +248,9 @@ private: UASSERT_OBJ(nodep->taskp(), nodep, "Unlinked task"); TaskFTaskVertex* const taskVtxp = getFTaskVertex(nodep->taskp()); new TaskEdge{&m_callGraph, m_curVxp, taskVtxp}; - if (isVirtualIfaceMethodCall(nodep) && isIfaceFTaskScope(getScope(nodep->taskp()))) { - taskVtxp->needsNonInlineCFunc(true); - } + // Virtual-interface method calls dispatch through a runtime handle and + // must not be inlined. + if (isVirtualIfaceMethodCall(nodep)) taskVtxp->needsNonInlineCFunc(true); // Do we have to disable inlining the function? const V3TaskConnects tconnects = V3Task::taskConnects(nodep, nodep->taskp()->stmtsp()); if (!taskVtxp->noInline()) { // Else short-circuit below @@ -1638,6 +1638,7 @@ class TaskVisitor final : public VNVisitor { // Create cloned statements AstNode* beginp; AstCNew* cnewp = nullptr; + // getScope() is safe here: TaskStateVisitor stamped all FTask scopes before this pass. const bool virtualIfaceCall = TaskStateVisitor::isVirtualIfaceMethodCall(nodep) && TaskStateVisitor::isIfaceFTaskScope(m_statep->getScope(nodep->taskp())); diff --git a/test_regress/t/t_interface_virtual_func_module_call.py b/test_regress/t/t_interface_virtual_func_module_call.py new file mode 100755 index 000000000..6fe7d000c --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(verilator_flags2=["--binary"]) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_interface_virtual_func_module_call.v b/test_regress/t/t_interface_virtual_func_module_call.v new file mode 100644 index 000000000..7489bead4 --- /dev/null +++ b/test_regress/t/t_interface_virtual_func_module_call.v @@ -0,0 +1,24 @@ +// 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 + +interface iface; + int cnt = 0; + function void bump(); + cnt++; + endfunction +endinterface + +module t; + iface theIf (); + virtual iface vif; + initial begin + vif = theIf; + vif.bump(); + if (theIf.cnt !== 1) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 4c92c035e7f51b776310f19992654d8c8a93ca2d Mon Sep 17 00:00:00 2001 From: Kornel Uriasz Date: Thu, 11 Jun 2026 15:43:18 +0200 Subject: [PATCH 12/46] Support reduction XOR/AND operations in constraints (#7753) --- docs/CONTRIBUTORS | 1 + src/V3Randomize.cpp | 35 ++++++++ test_regress/t/t_constraint_redops.py | 21 +++++ test_regress/t/t_constraint_redops.v | 123 ++++++++++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100755 test_regress/t/t_constraint_redops.py create mode 100644 test_regress/t/t_constraint_redops.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index b78b4f101..877011165 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -159,6 +159,7 @@ Kefa Chen Keith Colbert Kevin Kiningham Kevin Nygaard +Kornel Uriasz Kritik Bhimani Krzysztof Bieganski Krzysztof Boronski diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 3fcaf5535..929bbf115 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -1509,6 +1509,41 @@ class ConstraintExprVisitor final : public VNVisitor { VL_DO_DANGLING(nodep->deleteTree(), nodep); iterate(sump); } + void visit(AstRedXor* nodep) override { + if (editFormat(nodep)) return; + + // Build popcount expansion: (extract x 1 1) ^ (extract x 2 2) ^ ... + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + + AstNodeExpr* redxorp = new AstSel{fl, argp, 0, 1}; + redxorp->user1(true); + for (int i = 1; i < argp->width(); i++) { + AstSel* const selp = new AstSel{fl, argp->cloneTreePure(false), i, 1}; + selp->user1(true); + + redxorp = new AstXor{fl, redxorp, selp}; + redxorp->user1(true); + } + + nodep->replaceWith(redxorp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(redxorp); + } + void visit(AstRedAnd* nodep) override { + if (editFormat(nodep)) return; + // Convert to (~x == 0) + FileLine* const fl = nodep->fileline(); + AstNodeExpr* const argp = nodep->lhsp()->unlinkFrBack(); + const V3Number numZero{fl, argp->width(), 0}; + AstNodeExpr* const negp = new AstNot{fl, argp}; + negp->user1(true); + AstNodeExpr* const eqp = new AstEq{fl, negp, new AstConst{fl, numZero}}; + eqp->user1(true); + nodep->replaceWith(eqp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + iterate(eqp); + } void visit(AstRedOr* nodep) override { if (editFormat(nodep)) return; // Convert to (x != 0) diff --git a/test_regress/t/t_constraint_redops.py b/test_regress/t/t_constraint_redops.py new file mode 100755 index 000000000..db1adb3f9 --- /dev/null +++ b/test_regress/t/t_constraint_redops.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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v new file mode 100644 index 000000000..e285f8e24 --- /dev/null +++ b/test_regress/t/t_constraint_redops.v @@ -0,0 +1,123 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 by Antmicro +// SPDX-License-Identifier: CC0-1.0 + +// Test case for reducing and in constraint +// 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_rand(cl, field, gotv, expv, count) \ +begin \ + automatic longint prev_result; \ + automatic int ok; \ + if (!bit'(cl.randomize())) $stop; \ + prev_result = longint'(field); \ + `checkd(gotv, expv) \ + repeat(count) begin \ + longint result; \ + if (!bit'(cl.randomize())) $stop; \ + result = longint'(field); \ + `checkd(gotv, expv) \ + if (result != prev_result) ok = 1; \ + prev_result = result; \ + end \ + if (ok != 1) $stop; \ +end +// verilog_format: on + +class test_redops_bitfields #(RANDVAL_BITWIDTH=8); + rand bit [RANDVAL_BITWIDTH-1:0] rand_val; + rand bit redand; + rand bit redxor; + rand bit redor; + + constraint c { + redand == &rand_val; + } + + constraint d { + redxor == ^rand_val; + } + + constraint e { + redor == |rand_val; + } + + function bit calc_redand(); + bit result = 1'b1; + + foreach (rand_val[idx]) begin + result &= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redxor(); + bit result; + + foreach (rand_val[idx]) begin + result ^= rand_val[idx]; + end + + return result; + endfunction + + function bit calc_redor(); + bit result = 1'b0; + + foreach (rand_val[idx]) begin + result |= rand_val[idx]; + end + + return result; + endfunction + + function void verify(); + //`check_rand(this, this.rand_val, this.redand, this.calc_redand(), 20); + `check_rand(this, this.rand_val, this.redxor, this.calc_redxor(), 20); + //`check_rand(this, this.rand_val, this.redor, this.calc_redor(), 20); + endfunction +endclass + +module t; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; + + initial begin + redops_1bit = new(); + redops_1bit.verify(); + + redops_8bit = new(); + redops_8bit.verify(); + + redops_16bit = new(); + redops_16bit.verify(); + + redops_32bit = new(); + redops_32bit.verify(); + + redops_47bit = new(); + redops_47bit.verify(); + + redops_63bit = new(); + redops_63bit.verify(); + + redops_64bit = new(); + redops_64bit.verify(); + + redops_128bit = new(); + redops_128bit.verify(); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From c7a262b05d05d866255545994ba114544c8b8019 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 16:00:30 +0100 Subject: [PATCH 13/46] Optimize bit select removal earlier in Dfg (#7762) Add a simple Dfg pass that removes redundant bit selects early. This can significantly cut down on downstream work and remove some temporary variables introduced during synthesis. --- src/V3DfgContext.h | 21 +++++++ src/V3DfgOptimizer.cpp | 7 +++ src/V3DfgPasses.cpp | 72 ++++++++++++++++++++++++ src/V3DfgPasses.h | 2 + test_regress/t/t_dfg_break_cycles.py | 13 +++-- test_regress/t/t_dfg_break_cycles.v | 6 +- test_regress/t/t_dfg_break_cycles_off.py | 16 ++++++ 7 files changed, 130 insertions(+), 7 deletions(-) create mode 100755 test_regress/t/t_dfg_break_cycles_off.py diff --git a/src/V3DfgContext.h b/src/V3DfgContext.h index dc1b54666..21e7af31a 100644 --- a/src/V3DfgContext.h +++ b/src/V3DfgContext.h @@ -234,6 +234,26 @@ private: addStat("temporaries introduced", m_temporariesIntroduced); } }; +class V3DfgRemoveSelectsContext final : public V3DfgSubContext { + // Only V3DfgContext can create an instance + friend class V3DfgContext; + +public: + // STATE + VDouble0 m_removedFullWidth; // Number of full width selects removed + VDouble0 m_replacedWithSelFromFull; // Number of selects replaced with sel from full driver + VDouble0 m_replacedWithSelFromPart; // Number of selects replaced with sel from partial driver + VDouble0 m_replacedWithPart; // Number of selects replaced with part of driver +private: + V3DfgRemoveSelectsContext() + : V3DfgSubContext{"RemoveSelects"} {} + ~V3DfgRemoveSelectsContext() { + addStat("full width selects removed", m_removedFullWidth); + addStat("replaced with sel from full driver", m_replacedWithSelFromFull); + addStat("replaced with sel from partial driver", m_replacedWithSelFromPart); + addStat("replaced with partial driver", m_replacedWithPart); + } +}; class V3DfgRemoveUnobservableContext final : public V3DfgSubContext { // Only V3DfgContext can create an instance friend class V3DfgContext; @@ -399,6 +419,7 @@ public: V3DfgPeepholeContext m_peepholeContext; V3DfgPushDownSelsContext m_pushDownSelsContext; V3DfgRegularizeContext m_regularizeContext; + V3DfgRemoveSelectsContext m_removeSelectsContext; V3DfgRemoveUnobservableContext m_removeUnobservableContext; V3DfgSynthesisContext m_synthContext; diff --git a/src/V3DfgOptimizer.cpp b/src/V3DfgOptimizer.cpp index b2fa0f9e8..0ad3b8aa9 100644 --- a/src/V3DfgOptimizer.cpp +++ b/src/V3DfgOptimizer.cpp @@ -139,6 +139,13 @@ class DataflowOptimize final { dfg.mergeGraphs(std::move(madeAcyclicComponents)); endOfStage("breakCycles", dfg, cyclicComps); + // Remove redundant selects + V3DfgPasses::removeSelects(dfg, m_ctx.m_removeSelectsContext); + for (std::unique_ptr& compp : cyclicComps) { + V3DfgPasses::removeSelects(*compp, m_ctx.m_removeSelectsContext); + } + endOfStage("removeSelects", dfg, cyclicComps); + // Split the acyclic DFG into [weakly] connected components std::vector> acyclicComps = dfg.splitIntoComponents("acyclic"); UASSERT(dfg.size() == 0, "DfgGraph should have become empty"); diff --git a/src/V3DfgPasses.cpp b/src/V3DfgPasses.cpp index 3e14397c6..afca00589 100644 --- a/src/V3DfgPasses.cpp +++ b/src/V3DfgPasses.cpp @@ -108,6 +108,78 @@ void V3DfgPasses::removeUnobservable(DfgGraph& dfg, V3DfgContext& dfgCtx) { } } +void V3DfgPasses::removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) { + + std::vector selps; + for (DfgVertex& vtx : dfg.opVertices()) { + DfgSel* const selp = vtx.cast(); + if (!selp) continue; + selps.push_back(selp); + } + + for (DfgSel* const selp : selps) { + FileLine* const flp = selp->fileline(); + const DfgDataType& dtype = selp->dtype(); + + // Remove full width selects + if (selp->fromp()->dtype() == dtype) { + ++ctx.m_removedFullWidth; + selp->replaceWith(selp->fromp()); + continue; + } + + // Push selects through synthesis temporaries only + DfgVarPacked* const varp = selp->fromp()->cast(); + if (!varp || !varp->tmpForp()) continue; + DfgVertex* const srcp = varp->srcp(); + if (!srcp) continue; + // Don't inline CReset + if (srcp->is()) continue; + + const uint32_t lsb = selp->lsb(); + const uint32_t msb = lsb + selp->width() - 1; + + // If driven whole, select from the driver + if (!srcp->is()) { + ++ctx.m_replacedWithSelFromFull; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb); + newSelp->fromp(srcp); + selp->replaceWith(newSelp); + continue; + } + + // Otherwise attemt to select from the partial driver + DfgSplicePacked* const splicep = srcp->as(); + DfgVertex* driverp = nullptr; + uint32_t driverLsb = 0; + splicep->foreachDriver([&](DfgVertex& src, const uint32_t dLsb) { + const uint32_t dMsb = dLsb + src.width() - 1; + // If it does not cover the whole searched bit range, move on + if (lsb < dLsb || dMsb < msb) return false; + // Save the driver + driverp = &src; + driverLsb = dLsb; + return true; + }); + if (!driverp) continue; + + // If partial driver is the whole thing we are looking for, just replace with the driver + if (driverp->dtype() == dtype) { + ++ctx.m_replacedWithPart; + selp->replaceWith(driverp); + continue; + } + + // Otherwise create a new select from the partial driver + ++ctx.m_replacedWithSelFromPart; + DfgSel* const newSelp = new DfgSel{dfg, flp, dtype}; + newSelp->lsb(lsb - driverLsb); + newSelp->fromp(driverp); + selp->replaceWith(newSelp); + } +} + void V3DfgPasses::inlineVars(DfgGraph& dfg) { for (DfgVertexVar& vtx : dfg.varVertices()) { // Nothing to inline it into diff --git a/src/V3DfgPasses.h b/src/V3DfgPasses.h index a7d2b04fb..2984c6bd7 100644 --- a/src/V3DfgPasses.h +++ b/src/V3DfgPasses.h @@ -41,6 +41,8 @@ void removeUnobservable(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; // Synthesize DfgLogic vertices into primitive operations. // Removes all DfgLogic (even those that were not synthesized). void synthesize(DfgGraph&, V3DfgContext&) VL_MT_DISABLED; +// Remove redundant selects +void removeSelects(DfgGraph& dfg, V3DfgRemoveSelectsContext& ctx) VL_MT_DISABLED; // Attempt to make the given cyclic graph into an acyclic, or "less cyclic" // equivalent. If the returned pointer is null, then no improvement was // possible on the input graph. Otherwise the returned graph is an improvement diff --git a/test_regress/t/t_dfg_break_cycles.py b/test_regress/t/t_dfg_break_cycles.py index 2c35f08f5..ec6c6b339 100755 --- a/test_regress/t/t_dfg_break_cycles.py +++ b/test_regress/t/t_dfg_break_cycles.py @@ -17,6 +17,8 @@ test.sim_time = 2000000 if not os.path.exists(test.root + "/.git"): test.skip("Not in a git repository") +test.top_filename = "t/t_dfg_break_cycles.v" + # Read expected source lines hit expectedLines = set() @@ -65,7 +67,8 @@ with open(rdFile, 'r', encoding="utf8") as rdFh, \ test.compile(verilator_flags2=[ "--stats", "--build", - "-fno-dfg-break-cycles", + "-fno-dfg" if test.name == "t_dfg_break_cycles" else "-fno-dfg-break-cycles", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_ref", "--prefix", "Vref", @@ -73,8 +76,9 @@ test.compile(verilator_flags2=[ ]) # yapf:disable # Check we got the expected number of circular logic warnings -test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", - r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) +if test.name == "t_dfg_break_cycles": + test.file_grep(test.obj_dir + "/obj_ref/Vref__stats.txt", + r'Warnings, Suppressed UNOPTFLAT\s+(\d+)', nExpectedCycles) # Compile optimized - also builds executable test.compile(verilator_flags2=[ @@ -82,6 +86,7 @@ test.compile(verilator_flags2=[ "--build", "--exe", "-fno-const-before-dfg", + "-fno-gate", "+incdir+" + test.obj_dir, "-Mdir", test.obj_dir + "/obj_opt", "--prefix", "Vopt", @@ -90,7 +95,7 @@ test.compile(verilator_flags2=[ "--debug", "--debugi", "0", "--dumpi-tree", "0", "-CFLAGS \"-I .. -I ../obj_ref\"", "../obj_ref/Vref__ALL.a", - "../../t/" + test.name + ".cpp" + "../../t/t_dfg_break_cycles.cpp" ]) # yapf:disable # Execute test to check equivalence diff --git a/test_regress/t/t_dfg_break_cycles.v b/test_regress/t/t_dfg_break_cycles.v index 5de4c628b..80c5aad04 100644 --- a/test_regress/t/t_dfg_break_cycles.v +++ b/test_regress/t/t_dfg_break_cycles.v @@ -219,14 +219,14 @@ module t ( `signal(ARRAY_1, 3); // UNOPTFLAT assign ARRAY_1 = array_1[0]; - wire [2:0] array_2a [2]; - wire [2:0] array_2b [2]; + wire [2:0] array_2a [2]; // UNOPTFLAT + wire [2:0] array_2b [2]; // UNOPTFLAT assign array_2a[0][0] = rand_a[0]; assign array_2a[0][1] = array_2b[1][0]; assign array_2a[0][2] = array_2b[1][1]; assign array_2a[1] = array_2a[0]; assign array_2b = array_2a; - `signal(ARRAY_2, 3); // UNOPTFLAT + `signal(ARRAY_2, 3); assign ARRAY_2 = array_2a[0]; wire [2:0] array_3 [2]; diff --git a/test_regress/t/t_dfg_break_cycles_off.py b/test_regress/t/t_dfg_break_cycles_off.py new file mode 100755 index 000000000..bf6af753c --- /dev/null +++ b/test_regress/t/t_dfg_break_cycles_off.py @@ -0,0 +1,16 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +import runpy + +test.scenarios('vlt_all') + +runpy.run_path("t/t_dfg_break_cycles.py", globals()) From 0ee5cbf5021dcaea2d82f8a3177d98dc2338eabd Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 15:47:48 +0100 Subject: [PATCH 14/46] CI: replace deprecated app-id with client-id --- .github/workflows/coverage.yml | 2 +- .github/workflows/pages.yml | 2 +- .github/workflows/rtlmeter.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 439143c85..e5f6a61ea 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -197,7 +197,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 4d1523ac2..3718abc11 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -76,7 +76,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} permission-actions: write permission-pull-requests: write diff --git a/.github/workflows/rtlmeter.yml b/.github/workflows/rtlmeter.yml index d2880b476..b36646698 100644 --- a/.github/workflows/rtlmeter.yml +++ b/.github/workflows/rtlmeter.yml @@ -292,7 +292,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator-rtlmeter-results @@ -394,7 +394,7 @@ jobs: id: generate-token uses: actions/create-github-app-token@v3.2.0 with: - app-id: ${{ vars.VERILATOR_CI_ID }} + client-id: ${{ vars.VERILATOR_CI_ID }} private-key: ${{ secrets.VERILATOR_CI_KEY }} owner: verilator repositories: verilator From 0ee25038ac7b089805b9699596baf5afed2f8db3 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Thu, 11 Jun 2026 20:59:18 +0100 Subject: [PATCH 15/46] Optimize V3Gate inlining heuristic (#7716) V3Gate used to inline too many expensive operations. One particularly bad example is inlining `{<<{wide}}` (bit-reverse of a wide signal), which is a single input node, but is quite expensive to compute, which we always used to inline. Change the heuristic to only inline single input nodes if they are not wide, or a cheap wide operation, otherwise treat them the same as multi-input ops and inline them only if they are used no more than once. --- src/V3Gate.cpp | 109 +++++++----------- test_regress/t/t_gate_chained.py | 2 +- .../t/t_gate_inline_wide_exclude_multiple.py | 4 +- .../t_gate_inline_wide_noexclude_arraysel.py | 4 +- .../t/t_gate_inline_wide_noexclude_const.py | 4 +- ..._gate_inline_wide_noexclude_other_scope.py | 18 --- ...t_gate_inline_wide_noexclude_other_scope.v | 34 ------ .../t/t_gate_inline_wide_noexclude_sel.py | 4 +- ...t_gate_inline_wide_noexclude_small_wide.py | 18 --- .../t_gate_inline_wide_noexclude_small_wide.v | 21 ---- .../t/t_gate_inline_wide_noexclude_varref.py | 4 +- 11 files changed, 52 insertions(+), 170 deletions(-) delete mode 100755 test_regress/t/t_gate_inline_wide_noexclude_other_scope.py delete mode 100644 test_regress/t/t_gate_inline_wide_noexclude_other_scope.v delete mode 100755 test_regress/t/t_gate_inline_wide_noexclude_small_wide.py delete mode 100644 test_regress/t/t_gate_inline_wide_noexclude_small_wide.v diff --git a/src/V3Gate.cpp b/src/V3Gate.cpp index 4875ac75c..b96c892f2 100644 --- a/src/V3Gate.cpp +++ b/src/V3Gate.cpp @@ -543,89 +543,62 @@ class GateInline final { // Logic block with pending substitutions are stored in this map, together with their ordinal std::unordered_map m_hasPending; size_t m_statInlined = 0; // Statistic tracking - signals inlined - size_t m_statRefs = 0; // Statistic tracking - size_t m_statExcluded = 0; // Statistic tracking + size_t m_statNotInlined = 0; // Statistic tracking - signals not inlined due to cost + size_t m_statRefs = 0; // Statistic tracking - number of input variable references replaced // METHODS - static bool isCheapWide(const AstNodeExpr* exprp) { + static bool isCheap(const AstNodeExpr* exprp) { + // Constant is cheap + if (VN_IS(exprp, Const)) return true; + // Variable reference is cheap + if (VN_IS(exprp, NodeVarRef)) return true; + // AstSel is cheap if the fromp is cheap, and not a wide needing bit swizzling if (const AstSel* const selp = VN_CAST(exprp, Sel)) { + if (!isCheap(selp->fromp())) return false; + if (!selp->isWide()) return true; + if (!VN_IS(selp->lsbp(), Const)) return false; if (selp->lsbConst() % VL_EDATASIZE != 0) return false; - exprp = selp->fromp(); + return true; } - if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) exprp = aselp->fromp(); - return VN_IS(exprp, Const) || VN_IS(exprp, NodeVarRef); - } - static bool excludedWide(GateVarVertex* const vVtxp, const AstNodeExpr* const rhsp) { - // Handle wides with logic drivers that are too wide for V3Expand. - if (!vVtxp->varScp()->isWide() // - || vVtxp->varScp()->widthWords() <= v3Global.opt.expandLimit() // - || vVtxp->inEmpty() // - || isCheapWide(rhsp)) - return false; - - const GateLogicVertex* const lVtxp - = vVtxp->inEdges().frontp()->fromp()->as(); - - // Exclude from inlining variables READ multiple times. - // To decouple actives thus simplifying scheduling, exclude only those - // VarRefs that are referenced under the same active as they were assigned. - if (const AstActive* const primaryActivep = lVtxp->activep()) { - size_t reads = 0; - for (const V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const lvp = edge.top()->as(); - if (lvp->activep() != primaryActivep) continue; - - reads += edge.weight(); - if (reads > 1) return true; - } + // AstArraySel is cheap if the fromp is cheap + if (const AstArraySel* const aselp = VN_CAST(exprp, ArraySel)) { + return isCheap(aselp->fromp()); } + // Otherwise it is not cheap return false; } bool shouldInline(GateVarVertex* vVtxp, GateLogicVertex* lVtxp, size_t nReads, AstNodeExpr* substp, bool allowMultiIn) { - AstVarScope* const vscp = vVtxp->varScp(); - // Always inline constants if (VN_IS(substp, Const)) return true; - // Don't inline non-constant static initializers + // Don't inline non-constant static initializers - these are scheduled differently if (lVtxp->staticInit()) return false; // Inline simple variable references if (VN_IS(substp, VarRef)) return true; // Only inline arrays if a simple variable or constant - if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) return false; - // Inline constant array selects - if (VN_IS(substp, ArraySel) && nReads <= 1) return true; - - // Don't inline expensive wide operations - if (excludedWide(vVtxp, substp)) { - ++m_statExcluded; - UINFO(9, "Gate inline exclude '" << vVtxp->name() << "'"); - vVtxp->clearReducible("Excluded wide"); // Check once. - return false; - } - - if (nReads == 0) { - // Reads no variables, likely unfolded constant expression - return true; - } else if (nReads == 1) { - // Reads one variable - return true; - } else { - // Reads more two or more variables - if (!allowMultiIn) return false; - // Do it if not used, or used only once, ignoring slow code - int n = 0; - for (V3GraphEdge& edge : vVtxp->outEdges()) { - const GateLogicVertex* const dstVtxp = edge.top()->as(); - // Ignore slow code, or if the destination is not used - if (dstVtxp->slow()) continue; - if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; - n += edge.weight(); - if (n > 1) return false; + if (VN_IS(vVtxp->varScp()->dtypep()->skipRefp(), UnpackArrayDType)) return false; + // Inline if reads no variables - unfolded constant expression, nullary builtin e.g.: $time + if (nReads == 0) return true; + // If it reads one variable, inline if not wide, or if cheap + if (nReads == 1 && (!substp->isWide() || isCheap(substp))) return true; + // Don't inline on first round if reads more than one variable + if (nReads > 1 && !allowMultiIn) return false; + // Reads multiple variables, or is expensive to compute. + // Inline if used only once, ignoring slow code, or dead code that can be deleted. + int n = 0; + for (V3GraphEdge& edge : vVtxp->outEdges()) { + const GateLogicVertex* const dstVtxp = edge.top()->as(); + // Ignore slow code, or if the destination is not used + if (dstVtxp->slow()) continue; + if (dstVtxp->outEmpty() && !dstVtxp->consumed()) continue; + n += edge.weight(); + if (n > 1) { + ++m_statNotInlined; + return false; } - return true; } + return true; } void recordSubstitution(AstVarScope* vscp, AstNodeExpr* substp, AstNode* logicp) { @@ -724,7 +697,7 @@ class GateInline final { if (!okVisitor.varAssigned(vVtxp->varScp())) continue; // Expression we are considering to substitute with - AstNodeExpr* const substp = okVisitor.substitutionp(); + AstNodeExpr* const substp = V3Const::constifyEdit(okVisitor.substitutionp()); // Number of variables read by the substitution const size_t nReads = okVisitor.readVscps().size(); @@ -832,9 +805,9 @@ class GateInline final { } ~GateInline() { - V3Stats::addStat("Optimizations, Gate sigs deleted", m_statInlined); - V3Stats::addStat("Optimizations, Gate inputs replaced", m_statRefs); - V3Stats::addStat("Optimizations, Gate excluded wide expressions", m_statExcluded); + V3Stats::addStat("Optimizations, Gate signals inlined", m_statInlined); + V3Stats::addStat("Optimizations, Gate signals not inlined due to cost", m_statNotInlined); + V3Stats::addStat("Optimizations, Gate reads replaced", m_statRefs); } public: diff --git a/test_regress/t/t_gate_chained.py b/test_regress/t/t_gate_chained.py index 7fa18e3a3..ddf8bef62 100755 --- a/test_regress/t/t_gate_chained.py +++ b/test_regress/t/t_gate_chained.py @@ -48,6 +48,6 @@ test.compile( test.execute() # Must be <<9000 above to prove this worked -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 8550) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 8550) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_exclude_multiple.py b/test_regress/t/t_gate_inline_wide_exclude_multiple.py index 76a8991cf..2bf5b1241 100755 --- a/test_regress/t/t_gate_inline_wide_exclude_multiple.py +++ b/test_regress/t/t_gate_inline_wide_exclude_multiple.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 2) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 4) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py index 8c99e4b33..15370ae7c 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_arraysel.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-dfg']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_const.py b/test_regress/t/t_gate_inline_wide_noexclude_const.py index 5102631f7..cb6511e21 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_const.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_const.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 2) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py deleted file mode 100755 index c20d324db..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/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: 2024 Wilson Snyder -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -import vltest_bootstrap - -test.scenarios('vlt') - -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) - -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) - -test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v b/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v deleted file mode 100644 index c70022d24..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_other_scope.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain -// SPDX-FileCopyrightText: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 256; // Wider than expand limit. - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides referenced in different scope. - wire [N-1:0] wide = N ~^ i; - - sub sub ( - i, - wide, - o - ); -endmodule - -module sub ( - input wire [N-1:0] i, - input wire [N-1:0] wide, - output logic [N-1:0] o -); - initial begin - for (integer n = 0; n < N; ++n) begin - o[n] = i[N-1-n] | wide[N-1-n]; - end - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_sel.py b/test_regress/t/t_gate_inline_wide_noexclude_sel.py index aa65429ab..41d13c55d 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_sel.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_sel.py @@ -13,8 +13,8 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5', '-fno-var-split']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 1) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 1) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 1) test.file_grep(test.stats, r'SplitVar, packed variables split automatically\s+(\d+)', 0) test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py deleted file mode 100755 index c20d324db..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.py +++ /dev/null @@ -1,18 +0,0 @@ -#!/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: 2024 Wilson Snyder -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -import vltest_bootstrap - -test.scenarios('vlt') - -test.lint(verilator_flags2=['--stats', '--expand-limit 5']) - -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) - -test.passes() diff --git a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v b/test_regress/t/t_gate_inline_wide_noexclude_small_wide.v deleted file mode 100644 index 1b1231c4e..000000000 --- a/test_regress/t/t_gate_inline_wide_noexclude_small_wide.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: 2024 Antmicro -// SPDX-License-Identifier: CC0-1.0 - -localparam N = 65; // Wide but narrower than expand limit - -module t ( - input wire [N-1:0] i, - output wire [N-1:0] o -); - - // Do not exclude from inlining wides small enough to be handled by - // V3Expand. - wire [65:0] wide_small = N << i * i / N; - - for (genvar n = 0; n < N; ++n) begin - assign o[n] = i[n] ^ wide_small[n]; - end -endmodule diff --git a/test_regress/t/t_gate_inline_wide_noexclude_varref.py b/test_regress/t/t_gate_inline_wide_noexclude_varref.py index 1d91e6493..282e88b84 100755 --- a/test_regress/t/t_gate_inline_wide_noexclude_varref.py +++ b/test_regress/t/t_gate_inline_wide_noexclude_varref.py @@ -13,7 +13,7 @@ test.scenarios('vlt') test.lint(verilator_flags2=['--stats', '--expand-limit 5']) -test.file_grep(test.stats, r'Optimizations, Gate excluded wide expressions\s+(\d+)', 0) -test.file_grep(test.stats, r'Optimizations, Gate sigs deleted\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals not inlined due to cost\s+(\d+)', 0) +test.file_grep(test.stats, r'Optimizations, Gate signals inlined\s+(\d+)', 0) test.passes() From 4555c8b23ce11072c6430eca084ccb57b6edf634 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 06:38:21 +0100 Subject: [PATCH 16/46] Internals: Cleanup case condition lifting (#7768) V3Begin used to lift impure case expressions. With V3LiftExpr, this is now redundant. --- src/V3Begin.cpp | 32 ++---------------- src/V3Case.cpp | 38 +++------------------- test_regress/t/t_case_call_count.py | 2 +- test_regress/t/t_case_inside_call_count.py | 2 +- 4 files changed, 8 insertions(+), 66 deletions(-) diff --git a/src/V3Begin.cpp b/src/V3Begin.cpp index 36e151053..2ac71c770 100644 --- a/src/V3Begin.cpp +++ b/src/V3Begin.cpp @@ -61,7 +61,6 @@ class BeginVisitor final : public VNVisitor { // NODE STATE // AstCase::user1 -> bool, if already purified - V3UniqueNames m_caseTempNames; // For generating unique temporary variable names used by cases // STATE - across all visitors BeginState* const m_statep; // Current global state @@ -75,7 +74,6 @@ class BeginVisitor final : public VNVisitor { string m_unnamedScope; // Name of begin blocks, including unnamed blocks int m_ifDepth = 0; // Current if depth bool m_keepBegins = false; // True if begins should not be inlined - VDouble0 m_statPurifiedCaseExpr; // Count of purified case expressions // METHODS @@ -132,7 +130,6 @@ class BeginVisitor final : public VNVisitor { } void visit(AstNodeModule* nodep) override { VL_RESTORER(m_modp); - VL_RESTORER(m_caseTempNames); m_modp = nodep; // Rename it (e.g. class under a generate) if (m_unnamedScope != "") { @@ -171,7 +168,6 @@ class BeginVisitor final : public VNVisitor { VL_RESTORER(m_liftedp); VL_RESTORER(m_namedScope); VL_RESTORER(m_unnamedScope); - VL_RESTORER(m_caseTempNames); m_displayScope = dot(m_displayScope, nodep->name()); m_namedScope = ""; m_unnamedScope = ""; @@ -415,29 +411,7 @@ class BeginVisitor final : public VNVisitor { // any BEGINs, but V3Coverage adds them all under the module itself. iterateChildren(nodep); } - void visit(AstCase* nodep) override { - // Introduce temporary variable for AstCase if needed - it is done here and not in V3Case - // because this phase is before V3Scope and V3Case is not. Doing it before V3Scope ensures - // that V3Scope will take care of a scope creation - if (!nodep->exprp()->isPure() && !nodep->user1SetOnce()) { - ++m_statPurifiedCaseExpr; - FileLine* const fl = nodep->exprp()->fileline(); - AstVar* const varp = new AstVar{fl, VVarType::XTEMP, m_caseTempNames.get(nodep), - nodep->exprp()->dtypep()}; - nodep->exprp(new AstExprStmt{fl, - new AstAssign{fl, new AstVarRef{fl, varp, VAccess::WRITE}, - nodep->exprp()->unlinkFrBack()}, - new AstVarRef{fl, varp, VAccess::READ}}); - if (m_ftaskp) { - varp->funcLocal(true); - varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); - } else { - m_modp->stmtsp()->addHereThisAsNext(varp); - } - } - iterateChildren(nodep); - } + void visit(AstCase* nodep) override { iterateChildren(nodep); } // VISITORS - LINT CHECK void visit(AstIf* nodep) override { // not AstNodeIf; other types not covered VL_RESTORER(m_keepBegins); @@ -464,10 +438,8 @@ class BeginVisitor final : public VNVisitor { public: // CONSTRUCTORS BeginVisitor(AstNetlist* nodep, BeginState* statep) - : m_caseTempNames{"__VCase"} - , m_statep{statep} { + : m_statep{statep} { iterate(nodep); - V3Stats::addStatSum("Impure case expressions", m_statPurifiedCaseExpr); } ~BeginVisitor() override = default; }; diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 4eca73e01..6c969c791 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -147,7 +147,6 @@ class CaseVisitor final : public VNVisitor { bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant // For each possible value, the case branch we need std::array m_valueItem; - bool m_needToClearCache = false; // Whether cache needs to be cleared // METHODS //! Determine whether we should check case items are complete @@ -398,14 +397,6 @@ class CaseVisitor final : public VNVisitor { // CASEx(cexpr,.... // -> tree of IF(msb, IF(msb-1, 11, 10) // IF(msb-1, 01, 00)) - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp()->unlinkFrBack(), ExprStmt); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } if (debug() >= 9) { // LCOV_EXCL_START for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { @@ -419,14 +410,7 @@ class CaseVisitor final : public VNVisitor { replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(cexprp, m_caseWidth - 1, 0UL); - if (cexprStmtp) { - cexprStmtp->resultp()->unlinkFrBack()->deleteTree(); - AstIf* const ifp = VN_AS(ifrootp, If); - cexprStmtp->resultp(ifp->condp()->unlinkFrBack()); - ifp->condp(cexprStmtp); - m_needToClearCache = true; - } + AstNode* ifrootp = replaceCaseFastRecurse(nodep->exprp(), m_caseWidth - 1, 0UL); // Case expressions can't be linked twice, so clone them if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); @@ -437,7 +421,6 @@ class CaseVisitor final : public VNVisitor { nodep->unlinkFrBack(); } VL_DO_DANGLING(nodep->deleteTree(), nodep); - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); UINFOTREE(9, ifrootp, "", "_simp"); } @@ -446,14 +429,7 @@ class CaseVisitor final : public VNVisitor { // -> IF((cexpr==icond1),istmts1, // IF((EQ (AND MASK cexpr) (AND MASK icond1) // ,istmts2, istmts3 - AstNodeExpr* cexprp; - AstExprStmt* cexprStmtp = nullptr; - if (nodep->exprp()->isPure()) { - cexprp = nodep->exprp()->unlinkFrBack(); - } else { - cexprStmtp = VN_AS(nodep->exprp(), ExprStmt)->unlinkFrBack(); - cexprp = cexprStmtp->resultp()->cloneTreePure(false); - } + AstNodeExpr* const cexprp = nodep->exprp(); // We'll do this in two stages. First stage, convert the conditions to // the appropriate IF AND terms. UINFOTREE(9, nodep, "", "_comp_IN::"); @@ -518,7 +494,6 @@ class CaseVisitor final : public VNVisitor { itemp->addCondsp(ifexprp); } } - VL_DO_DANGLING(cexprp->deleteTree(), cexprp); if (!hadDefault) { // If there was no default, add a empty one, this greatly simplifies below code // and constant propagation will just eliminate it for us later. @@ -582,12 +557,6 @@ class CaseVisitor final : public VNVisitor { if (grouprootp) { UINFOTREE(9, grouprootp, "", "_new"); nodep->replaceWith(grouprootp); - if (cexprStmtp) { - pushDeletep(cexprStmtp->resultp()->unlinkFrBack()); - cexprStmtp->resultp(grouprootp->condp()->unlinkFrBack()); - grouprootp->condp(cexprStmtp); - m_needToClearCache = true; - } } else { nodep->unlinkFrBack(); } @@ -622,6 +591,8 @@ class CaseVisitor final : public VNVisitor { { CaseLintVisitor{nodep}; } iterateChildren(nodep); UINFOTREE(9, nodep, "", "case_old"); + UASSERT_OBJ(nodep->exprp()->isPure(), nodep, + "Impure case expression should have been removed by V3LiftExpr"); if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { // It's a simple priority encoder or complete statement // we can make a tree of statements to avoid extra comparisons @@ -648,7 +619,6 @@ public: explicit CaseVisitor(AstNetlist* nodep) { for (auto& itr : m_valueItem) itr = nullptr; iterate(nodep); - if (m_needToClearCache) VIsCached::clearCacheTree(); } ~CaseVisitor() override { V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); diff --git a/test_regress/t/t_case_call_count.py b/test_regress/t/t_case_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_call_count.py +++ b/test_regress/t/t_case_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() diff --git a/test_regress/t/t_case_inside_call_count.py b/test_regress/t/t_case_inside_call_count.py index 4116217e8..df91d0e3c 100755 --- a/test_regress/t/t_case_inside_call_count.py +++ b/test_regress/t/t_case_inside_call_count.py @@ -15,6 +15,6 @@ test.compile(verilator_flags2=['--stats']) test.execute() -test.file_grep(test.stats, r'Impure case expressions\s+(\d+)', 2) +test.file_grep(test.stats, r'LiftExpr, lifted calls\s+(\d+)', 3) test.passes() From 60f729639b882fc1d32df7bf194cad6957598fce Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 07:48:36 +0100 Subject: [PATCH 17/46] Fix 'case (_) inside' with x wildcards (#7766) Found by inspection, case inside used to threat 'x' as a value, not as a wildcard. Per the standard it should behave as '==?' which treats both 'x' and 'z' as wildcards. --- src/V3Case.cpp | 4 +-- test_regress/t/t_case_inside_with_x.py | 18 +++++++++++++ test_regress/t/t_case_inside_with_x.v | 35 ++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) create mode 100755 test_regress/t/t_case_inside_with_x.py create mode 100644 test_regress/t/t_case_inside_with_x.v diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 6c969c791..1431c8ddf 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -576,8 +576,8 @@ class CaseVisitor final : public VNVisitor { bool neverItem(const AstCase* casep, const AstConst* itemp) { // Xs in case or casez are impossible due to two state simulations - if (casep->casex()) { - } else if (casep->casez() || casep->caseInside()) { + if (casep->casex() || casep->caseInside()) { + } else if (casep->casez()) { if (itemp->num().isAnyX()) return true; } else { if (itemp->num().isFourState()) return true; diff --git a/test_regress/t/t_case_inside_with_x.py b/test_regress/t/t_case_inside_with_x.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v new file mode 100644 index 000000000..51a456c7a --- /dev/null +++ b/test_regress/t/t_case_inside_with_x.v @@ -0,0 +1,35 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module top; + bit clk = 1'b0; + always #1 clk = ~clk; + + logic [2:0] cyc = 3'd0; + int count = 0; + always @(posedge clk) begin + // verilator lint_off CASEWITHX + case (cyc) inside + 3'b000: begin $display("case inside 000"); ++count; end + 3'b001: begin $display("case inside 001"); ++count; end + // Should match z + 3'b01?: begin $display("case inside 01?"); ++count; end + // Should match x + 3'b1xx: begin $display("case inside 1xx"); ++count; end + endcase + // verilator lint_on CASEWITHX + cyc <= cyc + 3'd1; + if (&cyc) begin + `checkh(count, 8); + $finish; + end + end +endmodule From e6ee6dd106f6db8c0b846222cb3ec19cbb8ac1c6 Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Fri, 12 Jun 2026 12:55:06 +0200 Subject: [PATCH 18/46] Fix bounds checks in expressions with read/write references (#7694) --- src/V3Unknown.cpp | 93 +++++++------------ test_regress/t/t_select_bound4.py | 20 ++++ test_regress/t/t_select_bound4.v | 29 ++++++ test_regress/t/t_select_bound_side_effect.py | 20 ++++ test_regress/t/t_select_bound_side_effect.v | 74 +++++++++++++++ test_regress/t/t_select_bound_timing_intra.py | 20 ++++ test_regress/t/t_select_bound_timing_intra.v | 34 +++++++ 7 files changed, 228 insertions(+), 62 deletions(-) create mode 100755 test_regress/t/t_select_bound4.py create mode 100644 test_regress/t/t_select_bound4.v create mode 100755 test_regress/t/t_select_bound_side_effect.py create mode 100644 test_regress/t/t_select_bound_side_effect.v create mode 100755 test_regress/t/t_select_bound_timing_intra.py create mode 100644 test_regress/t/t_select_bound_timing_intra.v diff --git a/src/V3Unknown.cpp b/src/V3Unknown.cpp index 727e97840..c8851280d 100644 --- a/src/V3Unknown.cpp +++ b/src/V3Unknown.cpp @@ -51,15 +51,14 @@ class UnknownVisitor final : public VNVisitor { static const std::string s_xrandPrefix; // STATE - across all visitors - VDouble0 m_statUnkVars; // Statistic tracking + VDouble0 m_statUnkVars; // Statistic of xrand variable created + VDouble0 m_statElses; // Statistic of else branches created for array selects V3UniqueNames m_lvboundNames; // For generating unique temporary variable names std::unique_ptr m_xrandNames; // For generating unique temporary variable names // STATE - for current visit position (use VL_RESTORER) AstNodeModule* m_modp = nullptr; // Current module AstNodeFTask* m_ftaskp = nullptr; // Current function/task - AstAssignDly* m_assigndlyp = nullptr; // Current assignment - AstNode* m_timingControlp = nullptr; // Current assignment's intra timing control bool m_constXCvt = false; // Convert X's bool m_allowXUnique = true; // Allow unique assignments @@ -69,38 +68,21 @@ class UnknownVisitor final : public VNVisitor { if (m_ftaskp) { varp->funcLocal(true); varp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); - m_ftaskp->stmtsp()->addHereThisAsNext(varp); + if (m_ftaskp->stmtsp()) + m_ftaskp->stmtsp()->addHereThisAsNext(varp); + else + m_ftaskp->addStmtsp(varp); } else { - m_modp->stmtsp()->addHereThisAsNext(varp); + if (m_modp->stmtsp()) + m_modp->stmtsp()->addHereThisAsNext(varp); + else + m_modp->addStmtsp(varp); } } void replaceBoundLvalue(AstNodeExpr* nodep, AstNodeExpr* condp) { // Spec says a out-of-range LHS SEL results in a NOP. - // This is a PITA. We could: - // 1. IF(...) around an ASSIGN, - // but that would break a "foo[TOO_BIG]=$fopen(...)". - // 2. Hack to extend the size of the output structure - // by one bit, and write to that temporary, but never read it. - // That makes there be two widths() and is likely a bug farm. - // 3. Make a special SEL to choose between the real lvalue - // and a temporary NOP register. - // 4. Assign to a temp, then IF that assignment. - // This is suspected to be nicest to later optimizations. - // 4 seems best but breaks later optimizations. 3 was tried, - // but makes a mess in the emitter as lvalue switching is needed. So 4. - // SEL(...) -> temp - // if (COND(LTE(bit<=maxlsb))) ASSIGN(SEL(...)),temp) - const bool needDly = (m_assigndlyp != nullptr); - if (m_assigndlyp) { - // Delayed assignments become normal assignments, - // then the temp created becomes the delayed assignment - AstNode* const newp = new AstAssign{m_assigndlyp->fileline(), - m_assigndlyp->lhsp()->unlinkFrBackWithNext(), - m_assigndlyp->rhsp()->unlinkFrBackWithNext()}; - m_assigndlyp->replaceWith(newp); - VL_DO_CLEAR(pushDeletep(m_assigndlyp), m_assigndlyp = nullptr); - } + // We wrap the expression into IF AstNodeExpr* prep = nodep; // Scan back to put the condlvalue above all selects (IE top of the lvalue) @@ -121,32 +103,35 @@ class UnknownVisitor final : public VNVisitor { // Already exists; rather than IF(a,... IF(b... optimize to IF(a&&b, // Saves us teaching V3Const how to optimize, and it won't be needed again. if (const AstIf* const ifp = VN_AS(prep->user2p(), If)) { - UASSERT_OBJ(!needDly, prep, "Should have already converted to non-delay"); VNRelinker replaceHandle; AstNodeExpr* const earliercondp = ifp->condp()->unlinkFrBack(&replaceHandle); AstNodeExpr* const newp = new AstLogAnd{condp->fileline(), condp, earliercondp}; UINFO(4, "Edit BOUNDLVALUE " << newp); replaceHandle.relink(newp); } else { - AstVar* const varp - = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), prep->dtypep()}; - addVar(varp); AstNode* stmtp = prep->backp(); // Grab above point before we replace 'prep' while (!VN_IS(stmtp, NodeStmt)) stmtp = stmtp->backp(); - - prep->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); - if (m_timingControlp) m_timingControlp->unlinkFrBack(); - AstIf* const newp = new AstIf{ - fl, condp, - (needDly - ? static_cast(new AstAssignDly{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}) - : static_cast(new AstAssign{ - fl, prep, new AstVarRef{fl, varp, VAccess::READ}, m_timingControlp}))}; + VNRelinker replaceHandle; + AstNode* const origStmtp = stmtp->unlinkFrBack(&replaceHandle); + AstNode* elseStmtp = nullptr; + const bool hasSideEffects = origStmtp->exists( + [](AstNode* const np) { return !np->isPure() || np->isTimingControl(); }); + if (hasSideEffects) { + // Copy original statement and replace `prep` with reference to tmp var to make + // sure that side effect will take place + m_statElses++; + elseStmtp = origStmtp->cloneTree(false); + AstVar* const varp = new AstVar{fl, VVarType::MODULETEMP, m_lvboundNames.get(prep), + prep->dtypep()}; + addVar(varp); + AstNode* const prepCopyp = prep->clonep(); + prepCopyp->replaceWith(new AstVarRef{fl, varp, VAccess::WRITE}); + pushDeletep(prepCopyp); + } + AstIf* const newp = new AstIf{fl, condp, origStmtp, elseStmtp}; + replaceHandle.relink(newp); newp->branchPred(VBranchPred::BP_LIKELY); newp->isBoundsCheck(true); - UINFOTREE(9, newp, "", "_new"); - stmtp->addNextHere(newp); prep->user2p(newp); // Save so we may LogAnd it next time } } @@ -208,23 +193,6 @@ class UnknownVisitor final : public VNVisitor { m_ftaskp = nodep; iterateChildren(nodep); } - void visit(AstAssignDly* nodep) override { - VL_RESTORER(m_assigndlyp); - VL_RESTORER(m_timingControlp); - m_assigndlyp = nodep; - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstAssignW* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - VL_DO_DANGLING(iterateChildren(nodep), nodep); // May delete nodep. - } - void visit(AstNodeAssign* nodep) override { - VL_RESTORER(m_timingControlp); - m_timingControlp = nodep->timingControlp(); - iterateChildren(nodep); - } void visit(AstCaseItem* nodep) override { VL_RESTORER(m_constXCvt); m_constXCvt = false; // Avoid losing the X's in casex @@ -584,6 +552,7 @@ public: } ~UnknownVisitor() override { // V3Stats::addStat("Unknowns, variables created", m_statUnkVars); + V3Stats::addStat("Unknowns, else branches created", m_statElses); } }; diff --git a/test_regress/t/t_select_bound4.py b/test_regress/t/t_select_bound4.py new file mode 100755 index 000000000..00e3aae74 --- /dev/null +++ b/test_regress/t/t_select_bound4.py @@ -0,0 +1,20 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 0) + +test.passes() diff --git a/test_regress/t/t_select_bound4.v b/test_regress/t/t_select_bound4.v new file mode 100644 index 000000000..d09a59a52 --- /dev/null +++ b/test_regress/t/t_select_bound4.v @@ -0,0 +1,29 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +module t; + int q[2][$]; + + task automatic pop_q(input int qid, input int expected); + int actual; + actual = q[qid].pop_front(); + if (qid < 2 && actual !== expected) $stop; + endtask + + initial begin + for (int i = 0; i < 4; i++) begin + q[i].push_back(i); + end + + for (int i = 0; i < 4; i++) begin + pop_q(i, i); + end + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_side_effect.py b/test_regress/t/t_select_bound_side_effect.py new file mode 100755 index 000000000..d696b53d9 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.py @@ -0,0 +1,20 @@ +#!/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: 2025 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v new file mode 100644 index 000000000..5fd21bdc8 --- /dev/null +++ b/test_regress/t/t_select_bound_side_effect.v @@ -0,0 +1,74 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`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 + +module t; + int arr[5]; + int x = 0; + int y = 0; + int z = 0; + + task automatic incr(input int i, input int expected); + arr[i] = x++; + if (i < 5) `checkh(arr[i], expected); + endtask + + function int get_y; + y++; + return y; + endfunction + + task automatic assign_side_effect(input int i, input int expected); + arr[i] = get_y(); + if (i < 5) `checkh(arr[i], expected); + endtask + + task automatic add_z(inout int a); + a += z; + z++; + endtask + + task automatic assign_side_effect_inout(input int i, input int expected); + if (i < 5) arr[i] = 1; + add_z(arr[i]); + if (i < 5) `checkh(arr[i], expected); + endtask + + initial begin + incr(0, 0); + incr(7, 0); + incr(4, 2); + + assign_side_effect(3, 1); + assign_side_effect(8, 0); + assign_side_effect(9, 0); + assign_side_effect(3, 4); + + assign_side_effect_inout(3, 1); + assign_side_effect_inout(4, 2); + assign_side_effect_inout(5, 0); + assign_side_effect_inout(1, 4); + + y = 0; + for (int i = 0; i < 10; i++) begin + arr[get_y()] = i; + if (y < 5) `checkh(arr[y], i); + `checkh(y, 2 * i + 1); + arr[get_y() % (i + 1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y % (i + 1)], i); + `checkh(y, 2 * (i + 1)); + end + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule diff --git a/test_regress/t/t_select_bound_timing_intra.py b/test_regress/t/t_select_bound_timing_intra.py new file mode 100755 index 000000000..4fae43d09 --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.py @@ -0,0 +1,20 @@ +#!/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: 2024 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator') + +test.compile(verilator_flags2=["--binary", "--stats"]) + +test.execute() + +test.file_grep(test.stats, r'Unknowns, else branches created\s+(\d+)', 1) + +test.passes() diff --git a/test_regress/t/t_select_bound_timing_intra.v b/test_regress/t/t_select_bound_timing_intra.v new file mode 100644 index 000000000..31d30c48c --- /dev/null +++ b/test_regress/t/t_select_bound_timing_intra.v @@ -0,0 +1,34 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 Antmicro +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +// verilog_format: off +`define stop $stop +`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 + +module t; + int arr[5]; + + task automatic intra(input int i); + time t = $time; + arr[i] = #1 i; + #1; + if (i < 5) `checkh(arr[i], i); + `checkh($time, t + 2); + endtask + + initial begin + intra(0); + intra(7); + intra(4); + intra(1); + + $write("*-* All Finished *-*\n"); + $finish; + end +endmodule From 384a63fade5375c4b18c5319f20928a065b81896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 07:13:57 -0400 Subject: [PATCH 19/46] CI: Bump codecov/codecov-action from 6 to 7 in the everything group (#7738) --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index e5f6a61ea..1601a67d7 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -90,7 +90,7 @@ jobs: find obj_coverage -type f | paste -sd, | sed "s/^/files=/" >> "$GITHUB_OUTPUT" - name: Upload to codecov.io - uses: codecov/codecov-action@v6 + uses: codecov/codecov-action@v7 with: disable_file_fixes: true disable_search: true From 279b425a5775295679d79ef051d581c38ffa5d36 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 14:15:41 +0100 Subject: [PATCH 20/46] Internals: Cleanup V3Case (#7769) --- src/V3Case.cpp | 687 +++++++++++++++++++++++++------------------------ 1 file changed, 348 insertions(+), 339 deletions(-) diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 1431c8ddf..93612026f 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -42,10 +42,6 @@ VL_DEFINE_DEBUG_FUNCTIONS; -#define CASE_OVERLAP_WIDTH 16 // Maximum width we can check for overlaps in -#define CASE_BARF 999999 // Magic width when non-constant -#define CASE_ENCODER_GROUP_DEPTH 8 // Levels of priority to be ORed together in top IF tree - //###################################################################### class CaseLintVisitor final : public VNVisitorConst { @@ -119,21 +115,34 @@ class CaseLintVisitor final : public VNVisitorConst { } void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } -public: // CONSTRUCTORS explicit CaseLintVisitor(AstCase* nodep) { iterateConst(nodep); } explicit CaseLintVisitor(AstGenCase* nodep) { iterateConst(nodep); } ~CaseLintVisitor() override = default; + +public: + static void apply(AstCase* nodep) { CaseLintVisitor{nodep}; } + static void apply(AstGenCase* nodep) { CaseLintVisitor{nodep}; } }; //###################################################################### // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // NODE STATE - // Cleared each Case - // AstIf::user3() -> bool. Set true to indicate clone not needed - const VNUser3InUse m_inuser3; + // Maximum width we can check for overlaps/exhaustiveness + constexpr static int CASE_OVERLAP_WIDTH = 16; + // Maximum number of case values for exhaustive analysis/optimization + constexpr static int CASE_MAX_VALUES = 1 << CASE_OVERLAP_WIDTH; + // Levels of priority to be ORed together in top IF tree + constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; + + // TYPES + // Record for each case value + struct CaseRecord final { + AstCaseItem* itemp; // Case item that covers value + AstConst* constp; // Expression within 'itemp' that covers value (nullptr for default) + AstNode* stmtsp; // Statements of 'itemp' (might be nullptr if case is empty) + }; // STATE VDouble0 m_statCaseFast; // Statistic tracking @@ -141,229 +150,246 @@ class CaseVisitor final : public VNVisitor { const AstNode* m_alwaysp = nullptr; // Always in which case is located // Per-CASE - int m_caseWidth = 0; // Width of valueItems - int m_caseItems = 0; // Number of caseItem unique values - bool m_caseIncomplete = false; // Proven incomplete - bool m_caseNoOverlapsAllCovered = false; // Proven to be synopsys parallel_case compliant - // For each possible value, the case branch we need - std::array m_valueItem; + bool m_caseExhaustive = false; // Proven exhaustive + bool m_caseNoOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array m_value2CaseRecord; // METHODS - //! Determine whether we should check case items are complete - //! @return Enum's dtype if should check, nullptr if shouldn't - const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { + // Determine whether we should check case items are complete + // Returns enum's dtype if should check, nullptr if shouldn't + static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { if (!nodep->uniquePragma() && !nodep->unique0Pragma()) return nullptr; const AstEnumDType* const enumDtp = VN_CAST(nodep->exprp()->dtypep()->skipRefToEnump(), EnumDType); if (!enumDtp) return nullptr; // Case isn't enum const AstBasicDType* const basicp = enumDtp->subDTypep()->basicp(); if (!basicp) return nullptr; // Not simple type (perhaps IEEE illegal) - if (basicp->width() > 32) return nullptr; return enumDtp; } - //! @return True if case items are complete, false if there are uncovered enums - bool checkCaseEnumComplete(const AstCase* const nodep, const AstEnumDType* const dtype) { - const uint32_t numCases = 1UL << m_caseWidth; - for (AstEnumItem* itemp = dtype->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), EnumItem)) { - AstConst* const econstp = VN_AS(itemp->valuep(), Const); - V3Number nummask{itemp, econstp->width()}; + + // Check and warn if case items are not complete over the given enum type. + // Returns true iff the case items cover all enum values/patterns. + bool checkExhaustiveEnum(const AstCase* const nodep, const AstEnumDType* const enump) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (AstEnumItem* eip = enump->itemsp(); eip; eip = VN_AS(eip->nextp(), EnumItem)) { + AstConst* const econstp = VN_AS(eip->valuep(), Const); + V3Number nummask{eip, econstp->width()}; nummask.opBitsNonX(econstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, econstp->width()}; + V3Number numval{eip, econstp->width()}; numval.opBitsOne(econstp->num()); + + const uint32_t mask = nummask.toUInt(); const uint32_t val = numval.toUInt(); + // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Enum item " - << itemp->prettyNameQ() - << " not covered by case\n"); - m_caseIncomplete = true; - return false; // enum has uncovered value by case items - } + if ((i & mask) != val) continue; // This case is not for this enum value + if (m_value2CaseRecord[i].itemp) continue; // Covered case + // Warn unless unique0 case which allows no-match + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Enum item " << eip->prettyNameQ() << " not covered by case"); } + // TODO: warn for all uncovered enum values, not just the first + return false; // enum has uncovered value by case items } } return true; // enum is fully covered } + + // Check and warn if case items are not complete over all possible values. + // Returns true iff the case items cover all values of the case expression. + bool checkExhaustivePacked(AstCase* nodep) { + const uint32_t numCases = 1UL << nodep->exprp()->width(); + for (uint32_t i = 0; i < numCases; ++i) { + if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (!nodep->unique0Pragma()) { + nodep->v3warn(CASEINCOMPLETE, + "Case values incompletely covered (example pattern 0x" << std::hex + << i << ")"); + } + // TODO: warn for more than one uncovered case, not just the first + return false; + } + + // It's an exhaustive case statement + return true; + } + + bool checkExhaustive(AstCase* nodep) { + if (const AstEnumDType* const enump = getEnumCompletionCheckDType(nodep)) { + return checkExhaustiveEnum(nodep, enump); + } + return checkExhaustivePacked(nodep); + } + bool isCaseTreeFast(AstCase* nodep) { - int width = 0; - bool opaque = false; - m_caseItems = 0; - m_caseNoOverlapsAllCovered = true; - for (AstCaseItem* itemp = nodep->itemsp(); itemp; - itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - if (icondp->width() > width) width = icondp->width(); - if (icondp->isDouble()) opaque = true; - if (!VN_IS(icondp, Const)) width = CASE_BARF; // Can't parse; not a constant - m_caseItems++; + m_caseExhaustive = true; // TODO: we haven't proven this yet, but is as was before + m_caseNoOverlaps = false; + + AstNode* const caseExprp = nodep->exprp(); + if (caseExprp->isDouble() || caseExprp->isString()) return false; + + const int caseWidth = caseExprp->width(); + if (!caseWidth) return false; + if (caseWidth > CASE_OVERLAP_WIDTH) return false; + + int caseConditions = 0; + + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Can't do anything with non-constants + if (!VN_IS(condp, Const)) return false; + // Count conditions + ++caseConditions; } } - m_caseWidth = width; - if (width == 0 || width > CASE_OVERLAP_WIDTH || opaque) { - m_caseNoOverlapsAllCovered = false; - return false; // Too wide for analysis - } + UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << m_caseWidth; + const uint32_t numCases = 1UL << caseWidth; // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) m_valueItem[i] = nullptr; + for (uint32_t i = 0; i < numCases; ++i) { + m_value2CaseRecord[i].itemp = nullptr; + m_value2CaseRecord[i].constp = nullptr; + m_value2CaseRecord[i].stmtsp = nullptr; + } // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; bool reportedSubcase = false; - bool hasDefaultCase = false; - std::map caseItemMap; // case condition -> case item + bool hasDefault = false; + m_caseNoOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - for (AstNode* icondp = itemp->condsp(); icondp; icondp = icondp->nextp()) { - // UINFOTREE(9, icondp, "", "caseitem"); - AstConst* const iconstp = VN_AS(icondp, Const); - UASSERT_OBJ(iconstp, nodep, "above 'can't parse' should have caught this"); - if (neverItem(nodep, iconstp)) { - // X in casez can't ever be executed + + // Default case + if (itemp->isDefault()) { + // Default was moved to be the last item by V3LinkDot. Fill remaining cases + for (uint32_t i = 0; i < numCases; ++i) { + CaseRecord& caseRecord = m_value2CaseRecord[i]; + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.stmtsp = itemp->stmtsp(); + } + } + hasDefault = true; + continue; + } + + for (AstConst* iconstp = VN_AS(itemp->condsp(), Const); iconstp; + iconstp = VN_AS(iconstp->nextp(), Const)) { + // Some items can never match due to 2-state simulation + if (neverItem(nodep, iconstp)) continue; + + V3Number nummask{itemp, iconstp->width()}; + nummask.opBitsNonX(iconstp->num()); + V3Number numval{itemp, iconstp->width()}; + numval.opBitsOne(iconstp->num()); + + const uint32_t mask = nummask.toUInt(); + const uint32_t val = numval.toUInt(); + + uint32_t firstOverlap = 0; + const AstConst* overlappedCondp = nullptr; + bool foundHit = false; + for (uint32_t i = 0; i < numCases; ++i) { + if ((i & mask) != val) continue; + + CaseRecord& caseRecord = m_value2CaseRecord[i]; + + // If this is the first case that covers this value, record it + if (!caseRecord.itemp) { + caseRecord.itemp = itemp; + caseRecord.constp = iconstp; + caseRecord.stmtsp = itemp->stmtsp(); + foundHit = true; + continue; + } + + // Otherwise record the first overlapping case, + // but overlap within the same CaseItem is legal + if (!overlappedCondp && caseRecord.itemp != itemp) { + firstOverlap = i; + overlappedCondp = caseRecord.constp; + m_caseNoOverlaps = false; + } + } + if (!nodep->priorityPragma()) { + // If this case statement doesn't have the priority + // keyword, we want to warn on any overlap. + if (!reportedOverlap && overlappedCondp) { + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlap + << ")"; + } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << overlappedCondp->warnOther() + << "... Location of overlapping condition\n" + << overlappedCondp->warnContextSecondary()); + reportedOverlap = true; + } } else { - const bool isCondWildcard = iconstp->num().isAnyXZ(); - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - const uint32_t mask = nummask.toUInt(); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - const uint32_t val = numval.toUInt(); - - uint32_t firstOverlap = 0; - const AstNode* overlappedCondp = nullptr; - bool foundHit = false; - for (uint32_t i = 0; i < numCases; ++i) { - if ((i & mask) == val) { - if (!m_valueItem[i]) { - m_valueItem[i] = icondp; - caseItemMap[icondp] = itemp; - foundHit = true; - } else if (!overlappedCondp) { - // Overlapping case item expressions in the - // same case item are legal - if (caseItemMap[m_valueItem[i]] != itemp) { - firstOverlap = i; - overlappedCondp = m_valueItem[i]; - m_caseNoOverlapsAllCovered = false; - } - } - } - } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { - std::ostringstream examplePattern; - if (isCondWildcard) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlap << ")"; - } - icondp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); - reportedOverlap = true; - } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - icondp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << icondp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } - } - } - } - // Defaults were moved to last in the caseitem list by V3LinkDot - if (itemp->isDefault()) { // Case statement's default... Fill the table - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) m_valueItem[i] = itemp; - } - caseItemMap[itemp] = itemp; - hasDefaultCase = true; - } - } - if (!hasDefaultCase) { - const AstEnumDType* const dtype = getEnumCompletionCheckDType(nodep); - if (dtype) { - if (!checkCaseEnumComplete(nodep, dtype)) { - // checkCaseEnumComplete has already warned of incompletion - m_caseNoOverlapsAllCovered = false; - return false; - } - } else { - for (uint32_t i = 0; i < numCases; ++i) { - if (!m_valueItem[i]) { // has uncovered case - if (!nodep->unique0Pragma()) - nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered " - "(example pattern 0x" - << std::hex << i << ")"); - m_caseIncomplete = true; - m_caseNoOverlapsAllCovered = false; - return false; + // If this is a priority case, we only want to complain + // if every possible value for this item is already hit + // by some other item. This is true if foundHit is + // false. + if (!reportedSubcase && !foundHit) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << overlappedCondp->warnOther() + << "... Location of previous condition\n" + << overlappedCondp->warnContextPrimary()); + reportedSubcase = true; } } } } - if (m_caseItems <= 3 + // If there was no default, check exhaustiveness + m_caseExhaustive = hasDefault || checkExhaustive(nodep); + if (!m_caseExhaustive) { + m_caseNoOverlaps = false; + return false; + } + + if (caseConditions <= 3 // Avoid e.g. priority expanders from going crazy in expansion - || (m_caseWidth >= 8 && (m_caseItems <= (m_caseWidth + 1)))) { + || (caseWidth >= 8 && (caseConditions <= (caseWidth + 1)))) { return false; // Not worth simplifying } - // Convert valueItem from AstCaseItem* to the expression - // Not done earlier, as we may now have a nullptr because it's just a ";" NOP branch - for (uint32_t i = 0; i < numCases; ++i) { - if (AstNode* const condp = m_valueItem[i]) { - const AstCaseItem* const caseItemp = caseItemMap[condp]; - UASSERT_OBJ(caseItemp, condp, "caseItemp should exist"); - m_valueItem[i] = caseItemp->stmtsp(); - } - } return true; // All is fine } + // TODO: should return AstNodeStmt after #6280 AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { - if (msb < 0) { - // There's no space for a IF. We know upperValue is thus down to a specific - // exact value, so just return the tree value - // Note can't clone here, as we're going to check for equivalence above - AstNode* const foundp = m_valueItem[upperValue]; - return foundp; - } else { - // Make left and right subtrees - // cexpr[msb:lsb] == 1 - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue); - AstNode* tree1p = replaceCaseFastRecurse( - cexprp, msb - 1, upperValue | (1UL << static_cast(msb))); + // Base case: If reached the last bit, upperValue equals an exact value, just return + // the statements from that CaseItem. Note: Not cloning here as the caller will do + // an identity check. + if (msb < 0) return m_value2CaseRecord[upperValue].stmtsp; - if (tree0p == tree1p) { - // Same logic on both sides, so we can just return one of 'em - return tree0p; - } - // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + // Recursive case: + // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively + const uint32_t upperValue0 = upperValue; + const uint32_t upperValue1 = upperValue | (1UL << msb); + AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue1); + + // If same logic on both sides, we can just return one of them + if (tree0p == tree1p) return tree0p; + + // We could have a "checkerboard" with A B A B, we can use the same IF on both edges + { bool same = true; - for (uint32_t a = upperValue, b = (upperValue | (1UL << msb)); - a < (upperValue | (1UL << msb)); a++, b++) { - if (m_valueItem[a] != m_valueItem[b]) { + for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { + if (m_value2CaseRecord[a].stmtsp != m_value2CaseRecord[b].stmtsp) { same = false; break; } @@ -372,137 +398,120 @@ class CaseVisitor final : public VNVisitor { VL_DO_DANGLING(tree1p->deleteTree(), tree1p); return tree0p; } - - // Must have differing logic, so make a selection - - // Case expressions can't be linked twice, so clone them - if (tree0p && !tree0p->user3()) tree0p = tree0p->cloneTree(true); - if (tree1p && !tree1p->user3()) tree1p = tree1p->cloneTree(true); - - // Alternate scheme if we ever do multiple bits at a time: - // V3Number nummask (cexprp, cexprp->width(), (1UL<fileline(), cexprp->cloneTreePure(false), - // new AstConst(cexprp->fileline(), nummask)); - AstNodeExpr* const and1p - = new AstSel{cexprp->fileline(), cexprp->cloneTreePure(false), msb, 1}; - AstNodeExpr* const eqp - = new AstNeq{cexprp->fileline(), new AstConst{cexprp->fileline(), 0}, and1p}; - AstIf* const ifp = new AstIf{cexprp->fileline(), eqp, tree1p, tree0p}; - ifp->user3(1); // So we don't bother to clone it - return ifp; } + + // Must have differing logic. Test the bit and convert to an If. + + // Clone if needed + if (tree0p && tree0p->backp()) tree0p = tree0p->cloneTree(true); + if (tree1p && tree1p->backp()) tree1p = tree1p->cloneTree(true); + // Create the If statement + FileLine* const flp = cexprp->fileline(); + AstNodeExpr* const condp = new AstSel{flp, cexprp->cloneTreePure(false), msb, 1}; + AstIf* const ifp = new AstIf{flp, condp, tree1p, tree0p}; + return ifp; } - void replaceCaseFast(AstCase* nodep) { + // TODO: should return AstNodeStmt after #6280 + AstNode* replaceCaseFast(AstCase* nodep) { // CASEx(cexpr,.... // -> tree of IF(msb, IF(msb-1, 11, 10) // IF(msb-1, 01, 00)) - - if (debug() >= 9) { // LCOV_EXCL_START - for (uint32_t i = 0; i < (1UL << m_caseWidth); ++i) { - if (const AstNode* const itemp = m_valueItem[i]) { - UINFO(9, "Value " << std::hex << i << " " << itemp); - } - } - } // LCOV_EXCL_STOP - - // Handle any assertions - replaceCaseParallel(nodep, m_caseNoOverlapsAllCovered); - - AstNode::user3ClearTree(); - AstNode* ifrootp = replaceCaseFastRecurse(nodep->exprp(), m_caseWidth - 1, 0UL); - - // Case expressions can't be linked twice, so clone them - if (ifrootp && !ifrootp->user3()) ifrootp = ifrootp->cloneTree(true); - - if (ifrootp) { - nodep->replaceWith(ifrootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); - UINFOTREE(9, ifrootp, "", "_simp"); + const int caseWidth = nodep->exprp()->width(); + AstNode* const ifrootp = replaceCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } - void replaceCaseComplicated(AstCase* nodep) { + // TODO: should return AstNodeStmt after #6280 + AstNode* replaceCaseComplicated(AstCase* nodep) { // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) // -> IF((cexpr==icond1),istmts1, // IF((EQ (AND MASK cexpr) (AND MASK icond1) // ,istmts2, istmts3 - AstNodeExpr* const cexprp = nodep->exprp(); - // We'll do this in two stages. First stage, convert the conditions to - // the appropriate IF AND terms. - UINFOTREE(9, nodep, "", "_comp_IN::"); - bool hadDefault = false; + + // We'll do this in two stages. + // First stage, convert the conditions to the appropriate IF AND terms. + bool hasDefault = false; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - if (!itemp->condsp()) { - // Default clause. Just make true, we'll optimize it away later - itemp->addCondsp(new AstConst{itemp->fileline(), AstConst::BitTrue{}}); - hadDefault = true; - } else { - // Expressioned clause - AstNodeExpr* icondNextp = nullptr; - AstNodeExpr* ifexprp = nullptr; // If expression to test - for (AstNodeExpr* icondp = itemp->condsp(); icondp; icondp = icondNextp) { - icondNextp = VN_AS(icondp->nextp(), NodeExpr); - icondp->unlinkFrBack(); - AstNodeExpr* condp = nullptr; // Default is to use and1p/and2p - AstConst* const iconstp = VN_CAST(icondp, Const); - if (iconstp && neverItem(nodep, iconstp)) { - // X in casez can't ever be executed - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - // For simplicity, make expression that is not equal, and let later - // optimizations remove it - condp = new AstConst{itemp->fileline(), AstConst::BitFalse{}}; - } else if (AstInsideRange* const irangep = VN_CAST(icondp, InsideRange)) { - // Similar logic in V3Width::visit(AstInside) - condp = irangep->newAndFromInside(cexprp->cloneTreePure(true), - irangep->lhsp()->unlinkFrBack(), - irangep->rhsp()->unlinkFrBack()); - VL_DO_DANGLING2(icondp->deleteTree(), icondp, irangep); - } else if (iconstp && iconstp->num().isFourState() - && (nodep->casex() || nodep->casez() || nodep->caseInside())) { - V3Number nummask{itemp, iconstp->width()}; - nummask.opBitsNonX(iconstp->num()); - V3Number numval{itemp, iconstp->width()}; - numval.opBitsOne(iconstp->num()); - AstNodeExpr* const and1p - = new AstAnd{itemp->fileline(), cexprp->cloneTreePure(false), - new AstConst{itemp->fileline(), nummask}}; - AstNodeExpr* const and2p = new AstAnd{ - itemp->fileline(), new AstConst{itemp->fileline(), numval}, - new AstConst{itemp->fileline(), nummask}}; - VL_DO_DANGLING(icondp->deleteTree(), icondp); - VL_DANGLING(iconstp); - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } else { - // Not a caseX mask, we can build CASEEQ(cexpr icond) - AstNodeExpr* const and1p = cexprp->cloneTreePure(false); - AstNodeExpr* const and2p = icondp; - condp = AstEq::newTyped(itemp->fileline(), and1p, and2p); - } - if (!ifexprp) { - ifexprp = condp; - } else { - ifexprp = new AstLogOr{itemp->fileline(), ifexprp, condp}; - } - } - // Replace expression in tree - itemp->addCondsp(ifexprp); + FileLine* const flp = itemp->fileline(); + + // Default clause. Just make true, we'll optimize it away later + if (itemp->isDefault()) { + itemp->addCondsp(new AstConst{flp, AstConst::BitTrue{}}); + hasDefault = true; + continue; } + + // Regular clause. Construct the condition expression. + AstNodeExpr* newCondp = nullptr; + for (AstNodeExpr *itemExprp = itemp->condsp(), *nextp; itemExprp; itemExprp = nextp) { + nextp = VN_AS(itemExprp->nextp(), NodeExpr); + itemExprp->unlinkFrBack(); + + // If case never matches, ignore it + if (neverItem(nodep, itemExprp)) { + VL_DO_DANGLING(itemExprp->deleteTree(), itemExprp); + continue; + } + + // Compute the term to add to the condition expression + AstNodeExpr* const termp = [&]() -> AstNodeExpr* { + // Will need a copy of the caseExpr regardless + AstNodeExpr* const caseExprp = nodep->exprp()->cloneTreePure(false); + + // InsideRange: Similar logic in V3Width::visit(AstInside) + if (AstInsideRange* const itemRangep = VN_CAST(itemExprp, InsideRange)) { + AstNodeExpr* const resultp = itemRangep->newAndFromInside( // + caseExprp, // + itemRangep->lhsp()->unlinkFrBack(), + itemRangep->rhsp()->unlinkFrBack()); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemRangep); + return resultp; + } + + // Check if we need to perform a wildcard match, this needs masking + if (AstConst* const itemConstp = VN_CAST(itemExprp, Const)) { + // TODO: 4-state will need to fix this + if (itemConstp->num().isFourState() + && (nodep->casex() || nodep->casez() || nodep->caseInside())) { + // Wildcard match, make 'caseExpr' & 'mask' == 'itemExpr' & 'mask' + V3Number numMask{itemp, itemConstp->width()}; + numMask.opBitsNonX(itemConstp->num()); + V3Number numOne{itemp, itemConstp->width()}; + numOne.opBitsOne(itemConstp->num()); + V3Number numRhs{itemp, itemConstp->width()}; + numRhs.opAnd(numOne, numMask); + VL_DO_DANGLING2(itemExprp->deleteTree(), itemExprp, itemConstp); + return AstEq::newTyped( + flp, // + new AstConst{flp, numRhs}, + new AstAnd{flp, caseExprp, new AstConst{flp, numMask}}); + } + } + + // Regular case, use simple equality comparison + return AstEq::newTyped(flp, caseExprp, itemExprp); + }(); + + // 'Or' new term with previous terms + newCondp = newCondp ? new AstLogOr{flp, newCondp, termp} : termp; + } + // Replace expression in tree. Needs to be non-null, so add a constant false if needed + if (!newCondp) newCondp = new AstConst{flp, AstConst::BitFalse{}}; + itemp->addCondsp(newCondp); } - if (!hadDefault) { - // If there was no default, add a empty one, this greatly simplifies below code - // and constant propagation will just eliminate it for us later. + + // If there was no default, add a empty one, this greatly simplifies below code + // and constant propagation will just eliminate it for us later. + if (!hasDefault) { nodep->addItemsp(new AstCaseItem{ nodep->fileline(), new AstConst{nodep->fileline(), AstConst::BitTrue{}}, nullptr}); } - UINFOTREE(9, nodep, "", "_comp_COND"); + // Now build the IF statement tree - // The tree can be quite huge. Pull ever group of 8 out, and make a OR tree. + // The tree can be quite huge. Pull every group of 8 out, and make a OR tree. // This reduces the depth for the bottom elements, at the cost of // some of the top elements. If we ever have profiling data, we // should pull out the most common item from here and instead make @@ -513,8 +522,11 @@ class CaseVisitor final : public VNVisitor { AstIf* itemnextp = nullptr; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { - AstNode* const istmtsp = itemp->stmtsp(); // Maybe null -- no action. + + // Grab the statements from this item. May be empty. + AstNode* const istmtsp = itemp->stmtsp(); if (istmtsp) istmtsp->unlinkFrBackWithNext(); + // Expressioned clause AstNodeExpr* const ifexprp = itemp->condsp()->unlinkFrBack(); { // Prepare for next group @@ -550,61 +562,61 @@ class CaseVisitor final : public VNVisitor { itemnextp = newp; } } - UINFOTREE(9, nodep, "", "_comp_TREE"); - // Handle any assertions - replaceCaseParallel(nodep, false); - // Replace the CASE... with IF... - if (grouprootp) { - UINFOTREE(9, grouprootp, "", "_new"); - nodep->replaceWith(grouprootp); - } else { - nodep->unlinkFrBack(); - } - VL_DO_DANGLING(nodep->deleteTree(), nodep); + return grouprootp; } - void replaceCaseParallel(AstCase* nodep, bool noOverlapsAllCovered) { - // Take the notParallelp tree under the case statement created by V3Assert - // If the statement was proven to have no overlaps and all cases - // covered, we're done with it. - // Else, convert to a normal statement parallel with the case statement. - if (nodep->notParallelp() && !noOverlapsAllCovered) { - AstNode* const parp = nodep->notParallelp()->unlinkFrBackWithNext(); - nodep->addNextHere(parp); - } - } - - bool neverItem(const AstCase* casep, const AstConst* itemp) { + bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; // Xs in case or casez are impossible due to two state simulations - if (casep->casex() || casep->caseInside()) { - } else if (casep->casez()) { - if (itemp->num().isAnyX()) return true; - } else { - if (itemp->num().isFourState()) return true; - } - return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); } // VISITORS void visit(AstCase* nodep) override { - VL_RESTORER(m_caseIncomplete); - { CaseLintVisitor{nodep}; } - iterateChildren(nodep); - UINFOTREE(9, nodep, "", "case_old"); UASSERT_OBJ(nodep->exprp()->isPure(), nodep, "Impure case expression should have been removed by V3LiftExpr"); + + CaseLintVisitor::apply(nodep); + + // Convert any children first + iterateChildren(nodep); + + // Convert the case statement + AstNode* replacementp = nullptr; if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { // It's a simple priority encoder or complete statement // we can make a tree of statements to avoid extra comparisons ++m_statCaseFast; - VL_DO_DANGLING(replaceCaseFast(nodep), nodep); + replacementp = replaceCaseFast(nodep); } else { - // If a case statement is whole, presume signals involved aren't forming a latch - if (m_alwaysp && !m_caseIncomplete) + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && m_caseExhaustive) { m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } ++m_statCaseSlow; - VL_DO_DANGLING(replaceCaseComplicated(nodep), nodep); + m_caseExhaustive = false; + m_caseNoOverlaps = false; + replacementp = replaceCaseComplicated(nodep); } + + // Take the notParallelp tree under the case statement created by V3Assert + // If the statement was proven to have no overlaps and all cases covered, + // it can be removed. Otherwise insert the assertion after the case statement. + if (nodep->notParallelp() && (!m_caseExhaustive || !m_caseNoOverlaps)) { + nodep->addNextHere(nodep->notParallelp()->unlinkFrBackWithNext()); + } + + // Replace/remove the case statement + if (replacementp) { + nodep->replaceWith(replacementp); + } else { + nodep->unlinkFrBack(); + } + VL_DO_DANGLING(nodep->deleteTree(), nodep); } //-------------------- void visit(AstAlways* nodep) override { @@ -616,10 +628,7 @@ class CaseVisitor final : public VNVisitor { public: // CONSTRUCTORS - explicit CaseVisitor(AstNetlist* nodep) { - for (auto& itr : m_valueItem) itr = nullptr; - iterate(nodep); - } + explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); @@ -636,5 +645,5 @@ void V3Case::caseAll(AstNetlist* nodep) { } void V3Case::caseLint(AstGenCase* nodep) { UINFO(4, __FUNCTION__ << ": "); - { CaseLintVisitor{nodep}; } + CaseLintVisitor::apply(nodep); } From e0c4c995b9ce0abf099b3ad0b43a39a3d7d289e7 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Fri, 12 Jun 2026 12:22:18 +0100 Subject: [PATCH 21/46] Fix crash on overlapping priority case --- src/V3Case.cpp | 66 ++++++++++++----------- test_regress/t/t_case_priority_overlap.py | 18 +++++++ test_regress/t/t_case_priority_overlap.v | 56 +++++++++++++++++++ 3 files changed, 108 insertions(+), 32 deletions(-) create mode 100755 test_regress/t/t_case_priority_overlap.py create mode 100644 test_regress/t/t_case_priority_overlap.v diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 93612026f..9fc489c4d 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -290,9 +290,9 @@ class CaseVisitor final : public VNVisitor { const uint32_t mask = nummask.toUInt(); const uint32_t val = numval.toUInt(); - uint32_t firstOverlap = 0; - const AstConst* overlappedCondp = nullptr; - bool foundHit = false; + bool foundNewCase = false; + const AstConst* firstOverlapConstp = nullptr; + uint32_t firstOverlapValue = 0; for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != val) continue; @@ -303,51 +303,53 @@ class CaseVisitor final : public VNVisitor { caseRecord.itemp = itemp; caseRecord.constp = iconstp; caseRecord.stmtsp = itemp->stmtsp(); - foundHit = true; + foundNewCase = true; continue; } - // Otherwise record the first overlapping case, - // but overlap within the same CaseItem is legal - if (!overlappedCondp && caseRecord.itemp != itemp) { - firstOverlap = i; - overlappedCondp = caseRecord.constp; + // There is an overlap. If it's within the same CaseItem, it is legal + if (caseRecord.itemp == itemp) continue; + + // Otherwise record the first overlapping case + if (!firstOverlapConstp) { + firstOverlapConstp = caseRecord.constp; + firstOverlapValue = i; m_caseNoOverlaps = false; } } - if (!nodep->priorityPragma()) { - // If this case statement doesn't have the priority - // keyword, we want to warn on any overlap. - if (!reportedOverlap && overlappedCondp) { + if (nodep->priorityPragma()) { + // If this is a priority case, we only want to complain if every possible value + // for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only covering + // item is this item itself, which is legal overlap within one item. + if (!reportedSubcase && !foundNewCase && firstOverlapConstp) { + iconstp->v3warn(CASEOVERLAP, + "Case item ignored: every matching value is covered " + "by an earlier condition\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of previous condition\n" + << firstOverlapConstp->warnContextPrimary()); + reportedSubcase = true; + } + } else { + // If this case statement doesn't have the priority keyword, + // we want to warn on any overlap. + if (!reportedOverlap && firstOverlapConstp) { std::ostringstream examplePattern; if (iconstp->num().isAnyXZ()) { - examplePattern << " (example pattern 0x" << std::hex << firstOverlap - << ")"; + examplePattern << " (example pattern 0x" << std::hex + << firstOverlapValue << ")"; } iconstp->v3warn(CASEOVERLAP, "Case conditions overlap" << examplePattern.str() << "\n" << iconstp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() + << firstOverlapConstp->warnOther() << "... Location of overlapping condition\n" - << overlappedCondp->warnContextSecondary()); + << firstOverlapConstp->warnContextSecondary()); reportedOverlap = true; } - } else { - // If this is a priority case, we only want to complain - // if every possible value for this item is already hit - // by some other item. This is true if foundHit is - // false. - if (!reportedSubcase && !foundHit) { - iconstp->v3warn(CASEOVERLAP, - "Case item ignored: every matching value is covered " - "by an earlier condition\n" - << iconstp->warnContextPrimary() << '\n' - << overlappedCondp->warnOther() - << "... Location of previous condition\n" - << overlappedCondp->warnContextPrimary()); - reportedSubcase = true; - } } } } diff --git a/test_regress/t/t_case_priority_overlap.py b/test_regress/t/t_case_priority_overlap.py new file mode 100755 index 000000000..46d1fe4c0 --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v new file mode 100644 index 000000000..2de27080a --- /dev/null +++ b/test_regress/t/t_case_priority_overlap.v @@ -0,0 +1,56 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Regression: a `priority` case item whose later condition (INST_B) is fully +// subsumed by an earlier condition (INST_A) of the *same* item. Overlap within +// a single case item is legal, but this previously crashed V3Case with a null +// pointer dereference: the priority "case item ignored" CASEOVERLAP diagnostic +// dereferenced 'overlappedCondp', which is null when the only covering item is +// the item itself. Must compile cleanly and simulate correctly. + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + + logic clk = 1'b0; + always #5 clk = ~clk; + + logic [1:0] in; + logic [1:0] out; + + always_comb begin + priority casez (in) + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: out = 2'b10; + 2'b0?: out = 2'b01; + endcase + end + + initial begin + in = 2'b00; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b01; + @(posedge clk); + `checkh(out, 2'b01); + + in = 2'b10; + @(posedge clk); + `checkh(out, 2'b10); + + in = 2'b11; + @(posedge clk); + `checkh(out, 2'b10); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule From dab6889f1e3cdc1bd1c7f0e3c3b0957ac0322ef3 Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Fri, 12 Jun 2026 16:40:38 +0200 Subject: [PATCH 22/46] Support assert property 'default disable iff` (#4848) (#7723) --- src/V3Assert.cpp | 73 ++++++ src/V3Assert.h | 5 + src/V3AssertNfa.cpp | 10 +- src/V3AssertPre.cpp | 14 +- src/V3AstNodeOther.h | 6 + src/Verilator.cpp | 1 + src/verilog.y | 10 +- test_regress/t/t_assert_iff_clk.py | 18 ++ test_regress/t/t_assert_iff_clk.v | 48 ++++ test_regress/t/t_assert_iff_clk_unsup.out | 6 - test_regress/t/t_assert_iff_clk_unsup.v | 22 -- .../t/t_default_disable_iff_gen_multi_bad.out | 6 + ...=> t_default_disable_iff_gen_multi_bad.py} | 6 +- .../t/t_default_disable_iff_gen_multi_bad.v | 20 ++ test_regress/t/t_default_disable_iff_scope.py | 18 ++ test_regress/t/t_default_disable_iff_scope.v | 230 ++++++++++++++++++ 16 files changed, 444 insertions(+), 49 deletions(-) create mode 100755 test_regress/t/t_assert_iff_clk.py create mode 100644 test_regress/t/t_assert_iff_clk.v delete mode 100644 test_regress/t/t_assert_iff_clk_unsup.out delete mode 100644 test_regress/t/t_assert_iff_clk_unsup.v create mode 100644 test_regress/t/t_default_disable_iff_gen_multi_bad.out rename test_regress/t/{t_assert_iff_clk_unsup.py => t_default_disable_iff_gen_multi_bad.py} (70%) create mode 100644 test_regress/t/t_default_disable_iff_gen_multi_bad.v create mode 100755 test_regress/t/t_default_disable_iff_scope.py create mode 100644 test_regress/t/t_default_disable_iff_scope.v diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 0ccf162fa..e785973f1 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -23,6 +23,79 @@ VL_DEFINE_DEBUG_FUNCTIONS; +namespace { + +class DefaultDisableLocalVisitor final : public VNVisitor { + // STATE + AstNode* m_scopep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_scopep); + m_scopep = nodep; + nodep->defaultDisablep(nullptr); + iterateChildren(nodep); + } + void visit(AstDefaultDisable* nodep) override { + UASSERT_OBJ(nodep, m_scopep, + "default disable iff must be inside a module or generate block"); + AstDefaultDisable* defaultp = nullptr; + if (const AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + defaultp = modp->defaultDisablep(); + } else { + defaultp = VN_AS(m_scopep, GenBlock)->defaultDisablep(); + } + if (VL_UNLIKELY(defaultp)) { + nodep->v3error("Only one 'default disable iff' allowed per " + << (VN_IS(m_scopep, NodeModule) ? "module" : "generate block") + << " (IEEE 1800-2023 16.15)"); + } else if (AstNodeModule* const modp = VN_CAST(m_scopep, NodeModule)) { + modp->defaultDisablep(nodep); + } else { + VN_AS(m_scopep, GenBlock)->defaultDisablep(nodep); + } + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisableLocalVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +class DefaultDisablePropagateVisitor final : public VNVisitor { + // STATE + AstDefaultDisable* m_defaultDisablep = nullptr; + + // VISITORS + void visit(AstNodeModule* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + if (!nodep->defaultDisablep()) nodep->defaultDisablep(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } + void visit(AstNode* nodep) override { iterateChildren(nodep); } + +public: + explicit DefaultDisablePropagateVisitor(AstNetlist* nodep) { iterate(nodep); } +}; + +} // namespace + +void V3AssertCommon::collectDefaultDisable(AstNetlist* nodep) { + { DefaultDisableLocalVisitor{nodep}; } + { DefaultDisablePropagateVisitor{nodep}; } +} + //###################################################################### // AssertDeFutureVisitor // If any AstFuture, then move all non-future varrefs to be one cycle behind, diff --git a/src/V3Assert.h b/src/V3Assert.h index dbaac13bf..f144688b6 100644 --- a/src/V3Assert.h +++ b/src/V3Assert.h @@ -24,6 +24,11 @@ //============================================================================ +class V3AssertCommon final { +public: + static void collectDefaultDisable(AstNetlist* nodep) VL_MT_DISABLED; +}; + class V3Assert final { public: static void assertAll(AstNetlist* nodep) VL_MT_DISABLED; diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index bf50e97a1..8db3e7761 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -26,6 +26,7 @@ #include "V3AssertNfa.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Graph.h" #include "V3Task.h" @@ -2301,7 +2302,7 @@ class AssertNfaVisitor final : public VNVisitor { VL_RESTORER(m_defaultDisablep); m_modp = nodep; m_defaultClockingp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); SvaNfaLowering lowering{nodep}; m_loweringp = &lowering; iterateChildren(nodep); @@ -2310,9 +2311,12 @@ class AssertNfaVisitor final : public VNVisitor { if (nodep->isDefault() && !m_defaultClockingp) m_defaultClockingp = nodep; iterateChildren(nodep); } - void visit(AstDefaultDisable* nodep) override { - if (!m_defaultDisablep) m_defaultDisablep = nodep; + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); } + void visit(AstDefaultDisable* nodep) override {} void visit(AstAssert* nodep) override { processAssertion(nodep); } void visit(AstCover* nodep) override { processAssertion(nodep); } void visit(AstRestrict* nodep) override { diff --git a/src/V3AssertPre.cpp b/src/V3AssertPre.cpp index c61299a66..05fb804fc 100644 --- a/src/V3AssertPre.cpp +++ b/src/V3AssertPre.cpp @@ -23,6 +23,7 @@ #include "V3AssertPre.h" +#include "V3Assert.h" #include "V3Const.h" #include "V3Task.h" #include "V3UniqueNames.h" @@ -1438,12 +1439,6 @@ private: } void visit(AstDefaultDisable* nodep) override { - if (m_defaultDisablep) { - nodep->v3error("Only one 'default disable iff' allowed per module" - " (IEEE 1800-2023 16.15)"); - } else { - m_defaultDisablep = nodep; - } VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); } void visit(AstInferredDisable* nodep) override { @@ -1565,10 +1560,15 @@ private: VL_RESTORER(m_modp); m_defaultClockingp = nullptr; m_defaultClkEvtVarp = nullptr; - m_defaultDisablep = nullptr; + m_defaultDisablep = nodep->defaultDisablep(); m_modp = nodep; iterateChildren(nodep); } + void visit(AstGenBlock* nodep) override { + VL_RESTORER(m_defaultDisablep); + m_defaultDisablep = nodep->defaultDisablep(); + iterateChildren(nodep); + } void visit(AstProperty* nodep) override { // The body will be visited when will be substituted in place of property reference // (AstFuncRef) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index f3692d05e..1e17d672c 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -301,6 +301,7 @@ class AstNodeModule VL_NOT_FINAL : public AstNode { VLifetime m_lifetime; // Lifetime VTimescale m_timeunit; // Global time unit VOptionBool m_unconnectedDrive; // State of `unconnected_drive + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope bool m_modPublic : 1; // Module has public references bool m_modTrace : 1; // Tracing this module @@ -351,6 +352,8 @@ public: string origName() const override { return m_origName; } string someInstanceName() const VL_MT_SAFE { return m_someInstanceName; } void someInstanceName(const string& name) { m_someInstanceName = name; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } bool inLibrary() const { return m_inLibrary; } void inLibrary(bool flag) { m_inLibrary = flag; } void depth(int value) { m_depth = value; } @@ -2810,6 +2813,7 @@ class AstGenBlock final : public AstNodeGen { std::string m_name; // Name of block const bool m_unnamed; // Originally unnamed (name change does not affect this) const bool m_implied; // Not inserted by user + AstDefaultDisable* m_defaultDisablep = nullptr; // Default disable iff in this scope public: AstGenBlock(FileLine* fl, const string& name, AstNode* itemsp, bool implied) @@ -2826,6 +2830,8 @@ public: void name(const std::string& name) override { m_name = name; } bool unnamed() const { return m_unnamed; } bool implied() const { return m_implied; } + AstDefaultDisable* defaultDisablep() const { return m_defaultDisablep; } + void defaultDisablep(AstDefaultDisable* nodep) { m_defaultDisablep = nodep; } }; class AstGenCase final : public AstNodeGen { // Generate 'case' diff --git a/src/Verilator.cpp b/src/Verilator.cpp index f94c8655d..c30f8b28c 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -260,6 +260,7 @@ static void process() { // Assertion insertion // After we've added block coverage, but before other nasty transforms + V3AssertCommon::collectDefaultDisable(v3Global.rootp()); V3AssertNfa::assertNfaAll(v3Global.rootp()); // V3AssertProp removed: NFA subsumes multi-cycle property lowering. // Unsupported constructs fall through to V3AssertPre. diff --git a/src/verilog.y b/src/verilog.y index 663e8a206..510545736 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6664,14 +6664,8 @@ property_spec: // IEEE: property_spec { $$ = new AstPropSpec{$1, $3, nullptr, $5}; } | '@' senitemVar pexpr { $$ = new AstPropSpec{$1, $2, nullptr, $3}; } - // // Disable applied after the event occurs, - // // so no existing AST can represent this - | yDISABLE yIFF '(' expr ')' '@' '(' senitemEdge ')' pexpr - { $$ = new AstPropSpec{$1, $8, nullptr, new AstLogOr{$1, $4, $10}}; - BBUNSUP($1, "Unsupported: property '(disable iff (...) @ (...)'\n" - + $1->warnMore() - + "... Suggest use property '(@(...) disable iff (...))'"); } - //UNSUP remove above + | yDISABLE yIFF '(' expr ')' '@' '(' senitem ')' pexpr + { $$ = new AstPropSpec{$1, $8, $4, $10}; } | yDISABLE yIFF '(' expr ')' pexpr { $$ = new AstPropSpec{$4->fileline(), nullptr, $4, $6}; } | pexpr { $$ = new AstPropSpec{$1->fileline(), nullptr, nullptr, $1}; } ; diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_assert_iff_clk.v b/test_regress/t/t_assert_iff_clk.v new file mode 100644 index 000000000..3dfb3458b --- /dev/null +++ b/test_regress/t/t_assert_iff_clk.v @@ -0,0 +1,48 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Antmicro Ltd +// SPDX-License-Identifier: CC0-1.0 + +module t ( + input clk +); + + int cyc; + logic rst = 1'b1; + logic x = 1'b1; + int issue_fail; + int pre_fail; + int post_fail; + int pre_temporal_fail; + int post_temporal_fail; + + a_issue: assert property (disable iff(rst !== 1'b0) @(posedge clk) !x) + else issue_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 0) + else pre_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 0) + else post_fail++; + + assert property (disable iff (cyc < 5) @(posedge clk) 1 ##1 0) + else pre_temporal_fail++; + + assert property (@(posedge clk) disable iff (cyc < 5) 1 ##1 0) + else post_temporal_fail++; + + always @(posedge clk) begin + cyc <= cyc + 1; + rst <= cyc < 4; + x <= cyc < 4; + if (cyc == 12) begin + if (issue_fail != 0) $stop; + if (pre_fail != post_fail) $stop; + if (pre_temporal_fail != post_temporal_fail) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_assert_iff_clk_unsup.out b/test_regress/t/t_assert_iff_clk_unsup.out deleted file mode 100644 index 3ff98273e..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_assert_iff_clk_unsup.v:20:20: Unsupported: property '(disable iff (...) @ (...)' - : ... Suggest use property '(@(...) disable iff (...))' - 20 | assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - | ^~~~~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.v b/test_regress/t/t_assert_iff_clk_unsup.v deleted file mode 100644 index efe917a04..000000000 --- a/test_regress/t/t_assert_iff_clk_unsup.v +++ /dev/null @@ -1,22 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2022 Antmicro Ltd -// SPDX-License-Identifier: CC0-1.0 - -module t ( - input clk -); - - input clk; - int cyc = 0; - logic val = 0; - - always @(posedge clk) begin - cyc <= cyc + 1; - val = ~val; - end - - assert property (disable iff (cyc < 5) @(posedge clk) cyc >= 5); - -endmodule diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.out b/test_regress/t/t_default_disable_iff_gen_multi_bad.out new file mode 100644 index 000000000..ed0df293e --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.out @@ -0,0 +1,6 @@ +%Error: t/t_default_disable_iff_gen_multi_bad.v:15:7: Only one 'default disable iff' allowed per generate block (IEEE 1800-2023 16.15) + : ... note: In instance 't' + 15 | default disable iff (cyc < 7); + | ^~~~~~~ + ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. +%Error: Exiting due to diff --git a/test_regress/t/t_assert_iff_clk_unsup.py b/test_regress/t/t_default_disable_iff_gen_multi_bad.py similarity index 70% rename from test_regress/t/t_assert_iff_clk_unsup.py rename to test_regress/t/t_default_disable_iff_gen_multi_bad.py index b5718946c..38cf36b43 100755 --- a/test_regress/t/t_assert_iff_clk_unsup.py +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.py @@ -4,13 +4,13 @@ # 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('linter') -test.lint(expect_filename=test.golden_filename, verilator_flags2=['--assert'], fails=True) +test.lint(fails=True, expect_filename=test.golden_filename) test.passes() diff --git a/test_regress/t/t_default_disable_iff_gen_multi_bad.v b/test_regress/t/t_default_disable_iff_gen_multi_bad.v new file mode 100644 index 000000000..61e69830c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_gen_multi_bad.v @@ -0,0 +1,20 @@ +// 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 + +module t(input clk); + int cyc; + + default disable iff (cyc < 3); + + generate + begin : g + default disable iff (cyc < 5); + default disable iff (cyc < 7); + + assert property (@(posedge clk) 0); + end + endgenerate +endmodule diff --git a/test_regress/t/t_default_disable_iff_scope.py b/test_regress/t/t_default_disable_iff_scope.py new file mode 100755 index 000000000..35e44000c --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_default_disable_iff_scope.v b/test_regress/t/t_default_disable_iff_scope.v new file mode 100644 index 000000000..6cdabba82 --- /dev/null +++ b/test_regress/t/t_default_disable_iff_scope.v @@ -0,0 +1,230 @@ +// 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 (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + bit clk; + int cyc; + + always #1 clk = !clk; + + bit rst_mod; + int before_mod_false; + int before_mod_gen_false; + int before_gen_default_false; + int before_gen_child_default_false; + int prog_false_count; + + always_comb rst_mod = (cyc < 3); + + // The default declaration appears later in the module, but still applies here. + assert property (@(posedge clk) 0) + else before_mod_false++; + + generate + begin : g_before_mod_default + // Inherits the module default declaration that appears later in this module. + assert property (@(posedge clk) 0) + else before_mod_gen_false++; + end + + begin : g_before_local_default + // The generate-local default declaration appears later in the block. + assert property (@(posedge clk) 0) + else before_gen_default_false++; + + begin : g_child_inherit + // Inherits the generate-local default declaration that appears later. + assert property (@(posedge clk) 0) + else before_gen_child_default_false++; + end + default disable iff (cyc < 7); + end + endgenerate + + default disable iff (rst_mod); + + generate + begin : g_override + bit rst_gen; + int override_false; + + always_comb rst_gen = (cyc < 5); + + default disable iff (rst_gen); + + assert property (@(posedge clk) 0) + else override_false++; + end + + begin : g_inherit + bit rst_mod; + int inherit_false; + + always_comb rst_mod = (cyc < 8); + + // Inherits the module default, whose rst_mod was resolved in module scope. + assert property (@(posedge clk) 0) + else inherit_false++; + end + endgenerate + + if_scope u_if (.clk(clk), .cyc(cyc)); + p_scope u_prog (.clk(clk), .cyc(cyc), .false_count(prog_false_count)); + examples_with_default_count u_with (.clk(clk), .cyc(cyc)); + examples_without_default_count u_without (.clk(clk), .cyc(cyc)); + examples_with_default u_ieee_with (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0), .rst1(1'b0)); + examples_without_default u_ieee_without (.a(1'b0), .b(1'b0), .clk(clk), .rst(1'b0)); + m_override u_m_override (.clk(clk), .cyc(cyc)); + + // The disable iff expression is unsampled, so same-edge updates race in MT simulation. + // Change clk on negedge while the properties are sampled on posedge to avoid races. + always @(negedge clk) begin + cyc++; + if (cyc == 12) begin + `checkd(before_mod_false, 9); + `checkd(before_mod_gen_false, 9); + `checkd(before_gen_default_false, 5); + `checkd(before_gen_child_default_false, 5); + `checkd(g_inherit.inherit_false, 9); + `checkd(g_override.override_false, 7); + `checkd(u_if.false_count, 6); + `checkd(u_if.g_inherit.false_count, 6); + `checkd(prog_false_count, 4); + `checkd(u_with.explicit_assert_false, 5); + `checkd(u_with.explicit_property_false, 5); + `checkd(u_with.inferred_default_false, 9); + `checkd(u_without.explicit_assert_false, 9); + `checkd(u_without.explicit_property_false, 9); + `checkd(u_m_override.false_count, 7); + `checkd(u_m_override.g_inherit_from_module.false_count, 7); + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule + +module examples_with_default (input logic a, b, clk, rst, rst1); +default disable iff rst; +property p1; +disable iff (rst1) a |=> b; +endproperty +// Disable condition is rst1 - explicitly specified within a1 +a1 : assert property (@(posedge clk) disable iff (rst1) a |=> b); +// Disable condition is rst1 - explicitly specified within p1 +a2 : assert property (@(posedge clk) p1); +// Disable condition is rst - no explicit specification, inferred from +// default disable iff declaration +a3 : assert property (@(posedge clk) a |=> b); +// Disable condition is 1'b0. This is the only way to +// cancel the effect of default disable. +a4 : assert property (@(posedge clk) disable iff (1'b0) a |=> b); +endmodule + +module examples_without_default (input logic a, b, clk, rst); +property p2; +disable iff (rst) a |=> b; +endproperty +// Disable condition is rst - explicitly specified within a5 +a5 : assert property (@(posedge clk) disable iff (rst) a |=> b); +// Disable condition is rst - explicitly specified within p2 +a6 : assert property (@ (posedge clk) p2); +// No disable condition +a7 : assert property (@ (posedge clk) a |=> b); +endmodule + +module examples_with_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + int inferred_default_false; + + default disable iff (cyc < 3); + + property p1; + disable iff (cyc < 7) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 7) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p1) + else explicit_property_false++; + + // Disable condition is inferred from the default. + assert property (@(posedge clk) 0) + else inferred_default_false++; + +endmodule + +module examples_without_default_count(input bit clk, input int cyc); + int explicit_assert_false; + int explicit_property_false; + + property p2; + disable iff (cyc < 3) 0; + endproperty + + // Disable condition is explicit in the assertion. + assert property (@(posedge clk) disable iff (cyc < 3) 0) + else explicit_assert_false++; + + // Disable condition is explicit in the property. + assert property (@(posedge clk) p2) + else explicit_property_false++; + +endmodule + +module m_override(input bit clk, input int cyc); + bit rst2; + int false_count; + + always_comb rst2 = (cyc < 5); + default disable iff (rst2); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit_from_module + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endmodule + +interface if_scope(input bit clk, input int cyc); + bit rst_if; + int false_count; + + always_comb rst_if = (cyc < 6); + default disable iff (rst_if); + + assert property (@(posedge clk) 0) + else false_count++; + + generate + begin : g_inherit + int false_count; + assert property (@(posedge clk) 0) + else false_count++; + end + endgenerate +endinterface + +program p_scope(input bit clk, input int cyc, + output int false_count); + default disable iff (cyc < 8); + + assert property (@(posedge clk) 0) + else false_count++; +endprogram From 748e48f88144e527f29d5f65385c1c29acbf59d3 Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Fri, 12 Jun 2026 10:41:56 -0400 Subject: [PATCH 23/46] Fix s_eventually in parameterized interfaces (#7741) --- src/V3Assert.cpp | 2 ++ .../t/t_property_s_eventually_iface_param.py | 18 ++++++++++ .../t/t_property_s_eventually_iface_param.v | 36 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100755 test_regress/t/t_property_s_eventually_iface_param.py create mode 100644 test_regress/t/t_property_s_eventually_iface_param.v diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index e785973f1..160121c0b 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -1080,10 +1080,12 @@ class AssertVisitor final : public VNVisitor { VL_RESTORER(m_modPastNum); VL_RESTORER(m_modStrobeNum); VL_RESTORER(m_modExpr2Sen2DelayedAlwaysp); + VL_RESTORER(m_finalp); m_modp = nodep; m_modPastNum = 0; m_modStrobeNum = 0; m_modExpr2Sen2DelayedAlwaysp.clear(); + m_finalp = nullptr; iterateChildren(nodep); } void visit(AstNodeProcedure* nodep) override { diff --git a/test_regress/t/t_property_s_eventually_iface_param.py b/test_regress/t/t_property_s_eventually_iface_param.py new file mode 100755 index 000000000..1ddad07d5 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(timing_loop=True, verilator_flags2=['--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v new file mode 100644 index 000000000..8d44f6373 --- /dev/null +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -0,0 +1,36 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +interface iface_if #(parameter int W = 8) (input bit clk); + logic [W-1:0] sig = 0; + int passed = 0; + assert property (@(posedge clk) s_eventually (sig == 1)) passed++; +endinterface + +module t; + bit clk = 0; + initial forever #1 clk = ~clk; + int cyc = 0; + + // Two distinct specializations: V3Param clones the interface into two + // modules, each with its own s_eventually tracking. The generated final + // block must stay per-module. + iface_if #(.W(4)) a(.clk(clk)); + iface_if #(.W(8)) b(.clk(clk)); + + always @(posedge clk) begin + ++cyc; + if (cyc == 2) begin + a.sig <= 1; + b.sig <= 1; + end + if (cyc == 5) begin + if (a.passed == 0 || b.passed == 0) $stop; + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule From 5831cc8d4667dc90bf4918270df9ef48ad01fee3 Mon Sep 17 00:00:00 2001 From: Marco Bartoli Date: Fri, 12 Jun 2026 16:42:32 +0200 Subject: [PATCH 24/46] Fix timed nested fork block with disable (#6720) (#7743) Fixes #6720. --- src/V3LinkJump.cpp | 3 + test_regress/t/t_disable_fork_nested.out | 3 + test_regress/t/t_disable_fork_nested.py | 18 +++++ test_regress/t/t_disable_fork_nested.v | 86 ++++++++++++++++++++++++ 4 files changed, 110 insertions(+) create mode 100644 test_regress/t/t_disable_fork_nested.out create mode 100755 test_regress/t/t_disable_fork_nested.py create mode 100644 test_regress/t/t_disable_fork_nested.v diff --git a/src/V3LinkJump.cpp b/src/V3LinkJump.cpp index 21e3cdad9..70115033f 100644 --- a/src/V3LinkJump.cpp +++ b/src/V3LinkJump.cpp @@ -262,6 +262,9 @@ class LinkJumpVisitor final : public VNVisitor { if (it != m_beginDisableBegins.end()) return it->second; AstBegin* const beginBodyp = new AstBegin{fl, "", nullptr, false}; + // Disable-by-name rewrites kill this detached block-body process, so mark it as process + // backed to ensure fork/join kill-accounting hooks are always emitted. + beginBodyp->setNeedProcess(); if (beginp->stmtsp()) beginBodyp->addStmtsp(beginp->stmtsp()->unlinkFrBackWithNext()); AstFork* const forkp = new AstFork{fl, VJoinType::JOIN}; diff --git a/test_regress/t/t_disable_fork_nested.out b/test_regress/t/t_disable_fork_nested.out new file mode 100644 index 000000000..dce504813 --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.out @@ -0,0 +1,3 @@ +Fast clock (200ns half-period): o_counter=7 +Slow clock (5400ns half-period): o_counter=3 +*-* All Finished *-* diff --git a/test_regress/t/t_disable_fork_nested.py b/test_regress/t/t_disable_fork_nested.py new file mode 100755 index 000000000..b716266af --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(verilator_flags2=["--binary", "-Wno-ZERODLY"]) + +test.execute(expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v new file mode 100644 index 000000000..ad6a4d51e --- /dev/null +++ b/test_regress/t/t_disable_fork_nested.v @@ -0,0 +1,86 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// Clock-period detector exercising a named-block `disable` of a block whose +// body contains a nested fork..join. Disabling such a block from a sibling +// process must release the enclosing fork..join so the surrounding `always` +// block keeps iterating. + +module disable_fork ( + input logic i_clk, + output logic [2:0] o_counter +); + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period + + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; + + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); + clk_re <= 1'b0; + #1 disable check4; + if (counter < 3'b111) counter <= counter + 3'b001; + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end + + assign o_counter = counter; +endmodule + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +// verilog_format: on + +module t; + logic clk; + logic [2:0] counter; + + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle + + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end + + disable_fork a_inst(.i_clk(clk), .o_counter(counter)); +endmodule From e03fa6c7839ca604fba57942d48363a26eeb7f00 Mon Sep 17 00:00:00 2001 From: Matthew Ballance Date: Fri, 12 Jun 2026 08:40:48 -0700 Subject: [PATCH 25/46] Support covergroup runtime model Phase A1 (#7728) --- include/verilated_cov_model.h | 63 ++++ include/verilated_covergroup.cpp | 78 +++++ include/verilated_covergroup.h | 144 +++++++++ src/V3AstAttr.h | 13 + src/V3Covergroup.cpp | 279 +++++++++++++++--- src/V3EmitCHeaders.cpp | 1 + src/V3EmitCModel.cpp | 1 + src/V3Global.cpp | 1 + test_regress/t/t_covergroup_array_bins.out | 14 +- test_regress/t/t_covergroup_cross.out | 19 ++ test_regress/t/t_covergroup_cross.v | 36 +++ test_regress/t/t_covergroup_default_bins.out | 6 +- test_regress/t/t_covergroup_default_bins.v | 12 +- test_regress/t/t_covergroup_iff.out | 6 +- test_regress/t/t_covergroup_iff.v | 2 +- test_regress/t/t_covergroup_ignore_bins.out | 6 +- test_regress/t/t_covergroup_illegal_bins.out | 4 +- test_regress/t/t_covergroup_trans.out | 2 + test_regress/t/t_covergroup_trans.v | 16 + .../t/t_vlcov_covergroup.annotate.out | 86 +++++- 20 files changed, 720 insertions(+), 69 deletions(-) create mode 100644 include/verilated_cov_model.h create mode 100644 include/verilated_covergroup.cpp create mode 100644 include/verilated_covergroup.h diff --git a/include/verilated_cov_model.h b/include/verilated_cov_model.h new file mode 100644 index 000000000..519fd214c --- /dev/null +++ b/include/verilated_cov_model.h @@ -0,0 +1,63 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// 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-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage model interfaces +/// +/// Defines interface classes to runtime covergroup coverage-collection classes. +/// These are used to query coverage achievement at runtime, and (future) +/// when writing coverage to the coverage database. +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COV_MODEL_H_ +#define VERILATOR_VERILATED_COV_MODEL_H_ + +#include "verilatedos.h" + +#include +#include + +// Per-bin classification. A bin's kind is which set it lives in (structural), +// not a per-bin field. Only Normal feeds coverage(); the rest are recorded. +// Enumerators are 'KIND_'-prefixed because the bare LRM terms collide with +// macros (e.g. IGNORE), which the preprocessor would expand. +enum class VlCovBinKind : uint8_t { + KIND_NORMAL = 0, // Base coverage-collecting bin + KIND_DEFAULT = 1, // Bin declared with 'default' range (which is excluded per LRM) + KIND_IGNORE = 2, // Ignore bin + KIND_ILLEGAL = 3 // Illegal bin +}; + +//============================================================================= +// VlCoverpointIf +/// Read-side view of a coverpoint. The writer queries bins by index; the +/// implementor computes names/kinds on demand. Bounded bin count, so random +/// access by index is the primary usage. + +class VlCoverpointIf VL_NOT_FINAL { +public: + // CONSTRUCTORS + virtual ~VlCoverpointIf() = default; + + // METHODS + // All bins, across every set; index range [0, binCount()) + virtual int binCount() const = 0; + // Bin name in declaration order (e.g. "myBin" or "b[3]") + virtual std::string binName(int i) const = 0; + virtual VlCovBinKind binKind(int i) const = 0; + // Bins covered / effective total (Normal set only) for the coverage calc + virtual void coverageParts(double& covered, double& total) const = 0; +}; + +#endif // Guard diff --git a/include/verilated_covergroup.cpp b/include/verilated_covergroup.cpp new file mode 100644 index 000000000..6dc7006f1 --- /dev/null +++ b/include/verilated_covergroup.cpp @@ -0,0 +1,78 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// 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-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime implementation +/// +/// Compiled and linked when "verilator --coverage" is used with covergroups. +/// +//============================================================================= + +#include "verilatedos.h" + +#include "verilated_covergroup.h" + +#include "verilated_cov.h" + +void VlCoverpoint::init(const char* hier, uint32_t atLeast, int nBins) { + m_hier = hier; + m_atLeast = atLeast; + m_total = nBins; + m_counts.assign(nBins, 0); +} + +void VlCoverpoint::addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) { + m_namers.emplace_back(set, count, m_nextBase, naming, name, file, line, col); + m_nextBase += count; + if (set == VlCovBinKind::KIND_NORMAL) m_normal += count; +} + +const VlCovNamer& VlCoverpoint::namerFor(int i) const { + // Namers are appended in ascending, contiguous index order covering [0, m_total), + // and i is always a valid bin index, so the matching namer always exists. + for (const VlCovNamer& nm : m_namers) { + if (i < nm.base() + nm.count()) return nm; + } + VL_UNREACHABLE; +} + +std::string VlCoverpoint::binName(int i) const { + const VlCovNamer& nm = namerFor(i); + std::string name = nm.name(); + if (nm.naming() == VlCovBinNaming::Array) name += '[' + std::to_string(i - nm.base()) + ']'; + return name; +} + +void VlCoverpoint::registerBins(VerilatedCovContext* covcontextp, const char* page) { + for (int i = 0; i < binCount(); ++i) { + const VlCovNamer& nm = namerFor(i); + const VlCovBinKind kind = binKind(i); + const std::string binp = binName(i); + const std::string full = m_hier + "." + binp; + const std::string lineStr = std::to_string(nm.line()); + const std::string colStr = std::to_string(nm.col()); + if (kind == VlCovBinKind::KIND_NORMAL) { + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str()); + } else { + const char* const binType = kind == VlCovBinKind::KIND_IGNORE ? "ignore" + : kind == VlCovBinKind::KIND_ILLEGAL ? "illegal" + : "default"; + VL_COVER_INSERT(covcontextp, full.c_str(), &m_counts[i], "page", page, "filename", + nm.file(), "lineno", lineStr.c_str(), "column", colStr.c_str(), "bin", + binp.c_str(), "bin_type", binType); + } + } +} diff --git a/include/verilated_covergroup.h b/include/verilated_covergroup.h new file mode 100644 index 000000000..1ffc98ff7 --- /dev/null +++ b/include/verilated_covergroup.h @@ -0,0 +1,144 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +//============================================================================= +// +// Code available from: https://verilator.org +// +// 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-2026 Wilson Snyder +// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +// +//============================================================================= +/// +/// \file +/// \brief Verilated functional-coverage collection runtime +/// +/// VlCoverpoint owns per-instance bin-count storage for one coverpoint, +/// computes coverage, builds bin names on demand, and registers bins with the +/// coverage database. It implements the VlCoverpointIf read interface. +/// +/// Generated covergroup code holds one VlCoverpoint per coverpoint, configures +/// it in the constructor (init + add*Namer), increments bins from sample(), +/// and registers via registerBins(). +/// +//============================================================================= + +#ifndef VERILATOR_VERILATED_COVERGROUP_H_ +#define VERILATOR_VERILATED_COVERGROUP_H_ + +#include "verilatedos.h" + +#include "verilated_cov_model.h" + +#include +#include +#include + +class VerilatedCovContext; + +// How a namer builds the names of the bins it covers. +enum class VlCovBinNaming : uint8_t { + Single, // "" one bin + Array, // "[i]" bins b[N] value array +}; + +// Specifies the naming scheme for a range of bins, allowing the +// specific name to be computed on-demand. +// All name strings are borrowed literals from the generated code. +class VlCovNamer final { + // MEMBERS + VlCovBinKind m_set; // which set the bins belong to + int m_count; // bins this namer covers (1 for Single) + int m_base; // first bin index (declaration order), assigned on append + VlCovBinNaming m_naming; // how bin names are built + const char* m_name; // bin name (Single) or array base name (Array) + const char* m_file; // declaration file + int m_line; // declaration line + int m_col; // declaration column + +public: + // CONSTRUCTORS + VlCovNamer(VlCovBinKind set, int count, int base, VlCovBinNaming naming, const char* name, + const char* file, int line, int col) + : m_set{set} + , m_count{count} + , m_base{base} + , m_naming{naming} + , m_name{name} + , m_file{file} + , m_line{line} + , m_col{col} {} + + // METHODS + VlCovBinKind set() const { return m_set; } + int count() const { return m_count; } + int base() const { return m_base; } + VlCovBinNaming naming() const { return m_naming; } + const char* name() const { return m_name; } + const char* file() const { return m_file; } + int line() const { return m_line; } + int col() const { return m_col; } +}; + +//============================================================================= +// VlCoverpoint +/// Per-instance coverpoint runtime. Bins are stored in declaration order; a +/// bin's set/name come from the owning namer. coverage() is computed on demand +/// by scanning bin counts, keeping the sample() hot path a plain counter bump. + +class VlCoverpoint final : public VlCoverpointIf { + // MEMBERS + std::string m_hier; // "covergroup.coverpoint" + uint32_t m_atLeast = 1; // option.at_least (coverpoint-wide) + int m_total = 0; // bins across all sets + int m_normal = 0; // Normal bins (coverage denominator) + int m_nextBase = 0; // running append cursor + std::vector m_counts; // [m_total], one per bin + std::vector m_namers; // appended in declaration order + + // PRIVATE METHODS + const VlCovNamer& namerFor(int i) const; // obtain the bin-specific name producer + void addNamer(VlCovBinKind set, int count, VlCovBinNaming naming, const char* name, + const char* file, int line, int col); + +public: + // CONSTRUCTORS + VlCoverpoint() = default; + + // METHODS + // ---- configuration (from generated constructor) ---- + void init(const char* hier, uint32_t atLeast, int nBins); + void addSingleNamer(VlCovBinKind set, const char* name, const char* file, int line, int col) { + addNamer(set, 1, VlCovBinNaming::Single, name, file, line, col); + } + void addArrayNamer(VlCovBinKind set, int count, const char* name, const char* file, int line, + int col) { + addNamer(set, count, VlCovBinNaming::Array, name, file, line, col); + } + void registerBins(VerilatedCovContext* covcontextp, const char* page); + + // ---- hot path (from generated sample()) ---- + void incrementBin(int i) { ++m_counts[i]; } // Normal bin: count only + void recordHit(int i) { ++m_counts[i]; } // Ignore/Illegal/Default: count only + + // ---- VlCoverpointIf ---- + int binCount() const override { return m_total; } + std::string binName(int i) const override; + VlCovBinKind binKind(int i) const override { return namerFor(i).set(); } + void coverageParts(double& covered, double& total) const override { + // Count Normal bins that reached option.at_least on demand, so the hot + // path (incrementBin) stays a plain counter bump. + int numCovered = 0; + for (const VlCovNamer& nm : m_namers) { + if (nm.set() != VlCovBinKind::KIND_NORMAL) continue; + for (int i = nm.base(); i < nm.base() + nm.count(); ++i) { + if (m_counts[i] >= m_atLeast) ++numCovered; + } + } + covered = numCovered; + total = m_normal; + } +}; + +#endif // Guard diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index c12627886..dd1754a9d 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -1191,6 +1191,19 @@ public: = {"user", "array", "auto", "ignore", "illegal", "default", "wildcard", "transition"}; return names[m_e]; } + // VlCovBinKind enumerator naming the bin's set + const char* binSetEnum() const { + switch (m_e) { + case BINS_IGNORE: return "VlCovBinKind::KIND_IGNORE"; + case BINS_ILLEGAL: return "VlCovBinKind::KIND_ILLEGAL"; + case BINS_DEFAULT: return "VlCovBinKind::KIND_DEFAULT"; + default: return "VlCovBinKind::KIND_NORMAL"; + } + } + // Normal bins (feed coverage) are anything but ignore/illegal/default + bool binIsNormal() const { + return m_e != BINS_IGNORE && m_e != BINS_ILLEGAL && m_e != BINS_DEFAULT; + } }; constexpr bool operator==(const VCoverBinsType& lhs, VCoverBinsType::en rhs) { return lhs.m_e == rhs; diff --git a/src/V3Covergroup.cpp b/src/V3Covergroup.cpp index 727b5689c..0d43e9f60 100644 --- a/src/V3Covergroup.cpp +++ b/src/V3Covergroup.cpp @@ -68,6 +68,9 @@ class FunctionalCoverageVisitor final : public VNVisitor { , crossBins{cb} {} }; std::vector m_binInfos; // All bins in current covergroup + std::set m_crossedCpNames; // Coverpoints referenced by a cross (kept legacy) + std::vector m_convCpVars; // VlCoverpoint members of converted coverpoints + AstCDType* m_vlCoverpointDTypep = nullptr; // Shared "VlCoverpoint" C++ member type VMemberMap m_memberMap; // Member names cached for fast lookup @@ -88,6 +91,17 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Clear bin info for this covergroup (deleting any orphaned cross pseudo-bins) clearBinInfos(); + // Coverpoints referenced by a cross keep the legacy per-bin-member path (the cross + // reads those members); collect their names before they are consumed by the cross. + m_crossedCpNames.clear(); + m_convCpVars.clear(); + for (AstCoverCross* crossp : m_coverCrosses) { + for (AstNode* itemp = crossp->itemsp(); itemp; itemp = itemp->nextp()) { + if (const AstCoverpointRef* const refp = VN_CAST(itemp, CoverpointRef)) + m_crossedCpNames.insert(refp->name()); + } + } + // For each coverpoint, generate sampling code for (AstCoverpoint* cpp : m_coverpoints) generateCoverpointCode(cpp); @@ -504,6 +518,13 @@ class FunctionalCoverageVisitor final : public VNVisitor { // Create implicit automatic bins if no regular bins exist createImplicitAutoBins(coverpointp, exprp, autoBinMax); + // Eligible coverpoints route through the VlCoverpoint runtime; the rest (cross-fed or + // transition-bearing) keep the legacy per-bin-member path below. + if (coverpointConvertible(coverpointp)) { + generateConvertedCoverpoint(coverpointp, exprp, atLeastValue); + return; + } + // Generate member variables and matching code for each bin // Process in two passes: first non-default bins, then default bins std::vector defaultBins; @@ -595,47 +616,179 @@ class FunctionalCoverageVisitor final : public VNVisitor { UINFO(4, " Successfully added if statement for bin: " << binp->name()); } + // Build the condition under which a default bin matches: NOT(OR of all normal bins). + AstNodeExpr* buildDefaultCondition(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + FileLine* fl) { + AstNodeExpr* anyBinMatchp = nullptr; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT + || cbinp->binsType() == VCoverBinsType::BINS_IGNORE + || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) + continue; + AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); + UASSERT_OBJ(binCondp, cbinp, + "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); + anyBinMatchp = anyBinMatchp ? new AstOr{fl, anyBinMatchp, binCondp} : binCondp; + } + return anyBinMatchp ? static_cast(new AstNot{fl, anyBinMatchp}) + : static_cast(new AstConst{fl, AstConst::BitTrue{}}); + } + + //==================================================================== + // VlCoverpoint conversion (eligible coverpoints) + + // True if a coverpoint routes through the VlCoverpoint runtime. Cross-fed coverpoints + // (the cross reads their per-bin members) and transition-bearing ones stay legacy. + bool coverpointConvertible(AstCoverpoint* coverpointp) { + if (m_crossedCpNames.count(coverpointp->name())) return false; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + if (VN_AS(binp, CoverBin)->transp()) return false; + } + return true; + } + + // A 'this->m_member' reference for embedding in an AstCStmt + AstVarRef* memberRef(FileLine* fl, AstVar* varp) { + AstVarRef* const refp = new AstVarRef{fl, varp, VAccess::READ}; + refp->selfPointer(VSelfPointerText{VSelfPointerText::This{}}); + return refp; + } + + // Individual equality targets of an array bin (bins b[N] = {values/ranges}), in order. + std::vector extractArrayValues(AstCoverBin* arrayBinp, AstNodeExpr* exprp) { + std::vector values; + for (AstNode* rangep = arrayBinp->rangesp(); rangep; rangep = rangep->nextp()) { + if (AstInsideRange* const irp = VN_CAST(rangep, InsideRange)) { + AstConst* const minp = VN_CAST(V3Const::constifyEdit(irp->lhsp()), Const); + AstConst* const maxp = VN_CAST(V3Const::constifyEdit(irp->rhsp()), Const); + if (!minp || !maxp) { + arrayBinp->v3error("Non-constant expression in array bins range; " + "range bounds must be constants"); + return values; + } + for (int val = minp->toSInt(); val <= maxp->toSInt(); ++val) + values.push_back(new AstConst{irp->fileline(), AstConst::WidthedValue{}, + static_cast(exprp->width()), + static_cast(val)}); + } else { + values.push_back(VN_AS(rangep->cloneTree(false), NodeExpr)); + } + } + return values; + } + + // Emit a 'this->m_cp.addSingleNamer/addArrayNamer(...)' statement for one bin + AstCStmt* makeNamer(AstVar* cpVarp, AstCoverBin* binp, int count) { + FileLine* const fl = binp->fileline(); + AstCStmt* const cs = new AstCStmt{fl}; + cs->add(memberRef(fl, cpVarp)); + const std::string loc = "\"" + std::string{fl->filename()} + "\", " + + std::to_string(fl->lineno()) + ", " + + std::to_string(fl->firstColumn()) + ");"; + if (count < 0) { // single bin + cs->add(".addSingleNamer(" + std::string{binp->binsType().binSetEnum()} + ", \"" + + binp->name() + "\", " + loc); + } else { // value array bin + cs->add(".addArrayNamer(" + std::string{binp->binsType().binSetEnum()} + ", " + + std::to_string(count) + ", \"" + binp->name() + "\", " + loc); + } + return cs; + } + + // Emit 'if (iff && cond) m_cp.incrementBin(idx);' (or recordHit, + illegal action) in sample() + void emitConvHitIf(AstCoverpoint* coverpointp, AstCoverBin* binp, AstVar* cpVarp, int idx, + AstNodeExpr* condp) { + FileLine* const fl = binp->fileline(); + AstCStmt* const hitp = new AstCStmt{fl}; + hitp->add(memberRef(fl, cpVarp)); + hitp->add((binp->binsType().binIsNormal() ? ".incrementBin(" : ".recordHit(") + + std::to_string(idx) + ");"); + AstNode* actionp = hitp; + if (binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { + actionp->addNext(makeIllegalBinAction(fl, "Illegal bin " + binp->prettyNameQ() + + " hit in coverpoint " + + coverpointp->prettyNameQ())); + } + AstNodeExpr* const guardedp = applyCoverpointIffCondition(coverpointp, fl, condp); + UASSERT_OBJ(m_sampleFuncp, binp, "sample() CFunc not set in converted coverpoint"); + m_sampleFuncp->addStmtsp(new AstIf{fl, guardedp, actionp, nullptr}); + } + + // Route an eligible coverpoint through a VlCoverpoint member: emit the member, its + // sample() increments, the constructor configuration (init + namers), and registration. + void generateConvertedCoverpoint(AstCoverpoint* coverpointp, AstNodeExpr* exprp, + int atLeastValue) { + FileLine* const fl = coverpointp->fileline(); + UINFO(4, " Converting coverpoint to VlCoverpoint: " << coverpointp->name()); + + if (!m_vlCoverpointDTypep) { + m_vlCoverpointDTypep = new AstCDType{fl, "VlCoverpoint"}; + v3Global.rootp()->typeTablep()->addTypesp(m_vlCoverpointDTypep); + } + AstVar* const cpVarp = new AstVar{fl, VVarType::MEMBER, "__Vcp_" + coverpointp->name(), + m_vlCoverpointDTypep}; + cpVarp->isStatic(false); + m_covergroupp->addMembersp(cpVarp); + m_convCpVars.push_back(cpVarp); + + // Walk bins (non-default, then default), assigning sequential indices that match the + // namer append order; emit sample increments and collect namer statements. + std::vector namerStmts; + std::vector defaultBins; + int idx = 0; + for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { + AstCoverBin* const cbinp = VN_AS(binp, CoverBin); + if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT) { + defaultBins.push_back(cbinp); + continue; + } + if (cbinp->isArray()) { // value array: bins b[N] = {...} -> b[0]..b[N-1] + std::vector values = extractArrayValues(cbinp, exprp); + namerStmts.push_back(makeNamer(cpVarp, cbinp, static_cast(values.size()))); + for (AstNodeExpr* valuep : values) { + emitConvHitIf(coverpointp, cbinp, cpVarp, idx++, + new AstEq{cbinp->fileline(), exprp->cloneTree(false), valuep}); + } + } else { + namerStmts.push_back(makeNamer(cpVarp, cbinp, -1)); + // buildBinCondition is null for 'ignore_bins = default' (no ranges); the bin + // still gets a reserved slot (recorded, never incremented). + if (AstNodeExpr* const condp = buildBinCondition(cbinp, exprp)) + emitConvHitIf(coverpointp, cbinp, cpVarp, idx, condp); + ++idx; + } + } + for (AstCoverBin* const defBinp : defaultBins) { + namerStmts.push_back(makeNamer(cpVarp, defBinp, -1)); + emitConvHitIf(coverpointp, defBinp, cpVarp, idx++, + buildDefaultCondition(coverpointp, exprp, defBinp->fileline())); + } + + // Constructor: init (allocates), namers, then registration (under --coverage) + const std::string hier = m_covergroupp->name() + "." + coverpointp->name(); + AstCStmt* const initp = new AstCStmt{fl}; + initp->add(memberRef(fl, cpVarp)); + initp->add(".init(\"" + hier + "\", " + std::to_string(atLeastValue) + ", " + + std::to_string(idx) + ");"); + m_constructorp->addStmtsp(initp); + for (AstCStmt* const ns : namerStmts) m_constructorp->addStmtsp(ns); + if (v3Global.opt.coverage()) { + AstCStmt* const regp = new AstCStmt{fl}; + regp->add(memberRef(fl, cpVarp)); + regp->add(".registerBins(vlSymsp->_vm_contextp__->coveragep(), \"v_covergroup/" + + m_covergroupp->name() + "\");"); + m_constructorp->addStmtsp(regp); + } + } + // Generate matching code for default bins // Default bins match when value doesn't match any other explicit bin void generateDefaultBinMatchCode(AstCoverpoint* coverpointp, AstCoverBin* defBinp, AstNodeExpr* exprp, AstVar* hitVarp) { UINFO(4, " Generating default bin match for: " << defBinp->name()); - // Build OR of all non-default, non-ignore bins - AstNodeExpr* anyBinMatchp = nullptr; - - for (AstNode* binp = coverpointp->binsp(); binp; binp = binp->nextp()) { - AstCoverBin* const cbinp = VN_AS(binp, CoverBin); - - // Skip default, ignore, and illegal bins - if (cbinp->binsType() == VCoverBinsType::BINS_DEFAULT - || cbinp->binsType() == VCoverBinsType::BINS_IGNORE - || cbinp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } - - // Build condition for this bin - AstNodeExpr* const binCondp = buildBinCondition(cbinp, exprp); - UASSERT_OBJ(binCondp, cbinp, - "buildBinCondition returned nullptr for non-ignore/non-illegal bin"); - - // OR with previous conditions - if (anyBinMatchp) { - anyBinMatchp = new AstOr{defBinp->fileline(), anyBinMatchp, binCondp}; - } else { - anyBinMatchp = binCondp; - } - } - - // Default matches when NO explicit bin matches - AstNodeExpr* defaultCondp = nullptr; - if (anyBinMatchp) { - // NOT (bin1 OR bin2 OR ... OR binN) - defaultCondp = new AstNot{defBinp->fileline(), anyBinMatchp}; - } else { - // No other bins - default always matches (shouldn't happen in practice) - defaultCondp = new AstConst{defBinp->fileline(), AstConst::BitTrue{}}; - } + AstNodeExpr* defaultCondp = buildDefaultCondition(coverpointp, exprp, defBinp->fileline()); // Apply iff condition if present if (AstNodeExpr* iffp = coverpointp->iffp()) { @@ -1320,15 +1473,50 @@ class FunctionalCoverageVisitor final : public VNVisitor { void generateCoverageMethodBody(AstFunc* funcp) { FileLine* const fl = funcp->fileline(); + AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Count total bins (excluding ignore_bins and illegal_bins) + // Converted coverpoints hold their bins in VlCoverpoint. Combine their contributions + // (via coverageParts) with any remaining legacy cross/cross-fed bins as the same flat + // covered/total ratio the all-legacy path below computes. Normal bins only: ignore, + // illegal, and default are excluded (LRM 19.5). + if (!m_convCpVars.empty()) { + AstCStmt* const headp = new AstCStmt{fl}; + headp->add("double __Vcov = 0.0; double __Vtot = 0.0;"); + funcp->addStmtsp(headp); + for (AstVar* const cpVarp : m_convCpVars) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("{ double __Vc = 0.0; double __Vt = 0.0; "); + cs->add(memberRef(fl, cpVarp)); + cs->add(".coverageParts(__Vc, __Vt); __Vcov += __Vc; __Vtot += __Vt; }"); + funcp->addStmtsp(cs); + } + int legacyRegular = 0; + for (const BinInfo& bi : m_binInfos) { + if (!bi.binp->binsType().binIsNormal()) continue; + ++legacyRegular; + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("if ("); + cs->add(memberRef(fl, bi.varp)); + cs->add(" >= " + std::to_string(bi.atLeast) + ") __Vcov += 1.0;"); + funcp->addStmtsp(cs); + } + if (legacyRegular) { + AstCStmt* const cs = new AstCStmt{fl}; + cs->add("__Vtot += " + std::to_string(legacyRegular) + ".0;"); + funcp->addStmtsp(cs); + } + AstCStmt* const retp = new AstCStmt{fl}; + retp->add(new AstVarRef{fl, returnVarp, VAccess::WRITE}); + retp->add(" = (__Vtot != 0.0) ? (100.0 * __Vcov / __Vtot) : 100.0;"); + funcp->addStmtsp(retp); + return; + } + + // Count total bins (Normal only: excludes ignore/illegal/default) int totalBins = 0; for (const BinInfo& bi : m_binInfos) { UINFO(6, " Bin: " << bi.binp->name() << " type=" << bi.binp->binsType().ascii()); - if (bi.binp->binsType() != VCoverBinsType::BINS_IGNORE - && bi.binp->binsType() != VCoverBinsType::BINS_ILLEGAL) { - totalBins++; - } + if (bi.binp->binsType().binIsNormal()) totalBins++; } UINFO(4, " Total regular bins: " << totalBins << " of " << m_binInfos.size()); @@ -1337,7 +1525,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { // No coverage to compute - return 100%. // Any parser-generated initialization of returnVar is overridden by our assignment. UINFO(4, " Empty covergroup, returning 100.0"); - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); funcp->addStmtsp(new AstAssign{fl, new AstVarRef{fl, returnVarp, VAccess::WRITE}, new AstConst{fl, AstConst::RealDouble{}, 100.0}}); UINFO(4, " Added assignment to return 100.0"); @@ -1356,11 +1543,8 @@ class FunctionalCoverageVisitor final : public VNVisitor { // For each regular bin, if count > 0, increment covered_count for (const BinInfo& bi : m_binInfos) { - // Skip ignore_bins and illegal_bins in coverage calculation - if (bi.binp->binsType() == VCoverBinsType::BINS_IGNORE - || bi.binp->binsType() == VCoverBinsType::BINS_ILLEGAL) { - continue; - } + // Skip ignore/illegal/default bins in coverage calculation + if (!bi.binp->binsType().binIsNormal()) continue; // if (bin_count >= at_least) covered_count++; AstIf* ifp = new AstIf{ @@ -1375,9 +1559,6 @@ class FunctionalCoverageVisitor final : public VNVisitor { funcp->addStmtsp(ifp); } - // Find the return variable - AstVar* const returnVarp = VN_AS(funcp->fvarp(), Var); - // Calculate coverage: (covered_count / total_bins) * 100.0 // return_var = (double)covered_count / (double)total_bins * 100.0 diff --git a/src/V3EmitCHeaders.cpp b/src/V3EmitCHeaders.cpp index 1746139b8..524a39222 100644 --- a/src/V3EmitCHeaders.cpp +++ b/src/V3EmitCHeaders.cpp @@ -689,6 +689,7 @@ class EmitCHeader final : public EmitCConstInit { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.usesTiming()) puts("#include \"verilated_timing.h\"\n"); if (v3Global.useRandomizeMethods()) puts("#include \"verilated_random.h\"\n"); if (v3Global.usesForce()) puts("#include \"verilated_force.h\"\n"); diff --git a/src/V3EmitCModel.cpp b/src/V3EmitCModel.cpp index 211eea99f..6ddd53e77 100644 --- a/src/V3EmitCModel.cpp +++ b/src/V3EmitCModel.cpp @@ -70,6 +70,7 @@ class EmitCModel final : public EmitCFunc { if (v3Global.opt.mtasks()) puts("#include \"verilated_threads.h\"\n"); if (v3Global.opt.savable()) puts("#include \"verilated_save.h\"\n"); if (v3Global.opt.coverage()) puts("#include \"verilated_cov.h\"\n"); + if (v3Global.opt.coverage()) puts("#include \"verilated_covergroup.h\"\n"); if (v3Global.dpi()) puts("#include \"svdpi.h\"\n"); // Declare foreign instances up front to make C++ happy diff --git a/src/V3Global.cpp b/src/V3Global.cpp index e0b6f42ea..2e7c25fc7 100644 --- a/src/V3Global.cpp +++ b/src/V3Global.cpp @@ -245,6 +245,7 @@ std::vector V3Global::verilatedCppFiles() { if (v3Global.opt.vpi()) result.emplace_back("verilated_vpi.cpp"); if (v3Global.opt.savable()) result.emplace_back("verilated_save.cpp"); if (v3Global.opt.coverage()) result.emplace_back("verilated_cov.cpp"); + if (v3Global.opt.coverage()) result.emplace_back("verilated_covergroup.cpp"); for (const string& base : v3Global.opt.traceSourceBases()) result.emplace_back(base + "_c.cpp"); if (v3Global.usesProbDist()) result.emplace_back("verilated_probdist.cpp"); diff --git a/test_regress/t/t_covergroup_array_bins.out b/test_regress/t/t_covergroup_array_bins.out index 4c0767563..ccbbbc297 100644 --- a/test_regress/t/t_covergroup_array_bins.out +++ b/test_regress/t/t_covergroup_array_bins.out @@ -1,4 +1,12 @@ cg.data.grouped: 2 -cg.data.values: 3 -cg2.cp.range_arr: 3 -cg3.cp.range_sized: 3 +cg.data.values[0]: 1 +cg.data.values[1]: 1 +cg.data.values[2]: 1 +cg2.cp.range_arr[0]: 1 +cg2.cp.range_arr[1]: 1 +cg2.cp.range_arr[2]: 1 +cg2.cp.range_arr[3]: 0 +cg3.cp.range_sized[0]: 1 +cg3.cp.range_sized[1]: 1 +cg3.cp.range_sized[2]: 1 +cg3.cp.range_sized[3]: 0 diff --git a/test_regress/t/t_covergroup_cross.out b/test_regress/t/t_covergroup_cross.out index 84d9104d6..a7311da80 100644 --- a/test_regress/t/t_covergroup_cross.out +++ b/test_regress/t/t_covergroup_cross.out @@ -65,6 +65,15 @@ cg_at_least.cp_addr.addr0: 1 cg_at_least.cp_addr.addr1: 1 cg_at_least.cp_cmd.read: 1 cg_at_least.cp_cmd.write: 1 +cg_def_cross.axc.a0_x_read [cross]: 1 +cg_def_cross.axc.a0_x_write [cross]: 0 +cg_def_cross.axc.a1_x_read [cross]: 0 +cg_def_cross.axc.a1_x_write [cross]: 0 +cg_def_cross.cp_a.a0: 1 +cg_def_cross.cp_a.a1: 0 +cg_def_cross.cp_a.ad: 1 +cg_def_cross.cp_c.read: 1 +cg_def_cross.cp_c.write: 1 cg_goal.addr_cmd_goal.addr0_x_read [cross]: 1 cg_goal.addr_cmd_goal.addr0_x_write [cross]: 0 cg_goal.addr_cmd_goal.addr1_x_read [cross]: 0 @@ -82,6 +91,16 @@ cg_ignore.cross_ab.a0_x_read [cross]: 1 cg_ignore.cross_ab.a0_x_write [cross]: 1 cg_ignore.cross_ab.a1_x_read [cross]: 1 cg_ignore.cross_ab.a1_x_write [cross]: 1 +cg_mixed.ab.addr0_x_read [cross]: 1 +cg_mixed.ab.addr0_x_write [cross]: 1 +cg_mixed.ab.addr1_x_read [cross]: 1 +cg_mixed.ab.addr1_x_write [cross]: 1 +cg_mixed.cp_addr.addr0: 2 +cg_mixed.cp_addr.addr1: 2 +cg_mixed.cp_cmd.read: 2 +cg_mixed.cp_cmd.write: 2 +cg_mixed.cp_solo.debug: 2 +cg_mixed.cp_solo.normal: 2 cg_range.addr_cmd_range.hi_range_x_read [cross]: 1 cg_range.addr_cmd_range.hi_range_x_write [cross]: 1 cg_range.addr_cmd_range.lo_range_x_read [cross]: 1 diff --git a/test_regress/t/t_covergroup_cross.v b/test_regress/t/t_covergroup_cross.v index 6245baa35..e86f179a4 100644 --- a/test_regress/t/t_covergroup_cross.v +++ b/test_regress/t/t_covergroup_cross.v @@ -99,6 +99,23 @@ module t; cross cp_a, cp_c; // no label: reported under the default cross name endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; + cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} + cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} + cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed + ab: cross cp_addr, cp_cmd; + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; + cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} + cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} + axc: cross cp_a, cp_c; + endgroup + cg2 cg2_inst = new; cg_ignore cg_ignore_inst = new; cg_range cg_range_inst = new; @@ -109,6 +126,8 @@ module t; cg_goal cg_goal_inst = new; cg_unsup_cross_opt cg_unsup_cross_opt_inst = new; cg_unnamed_cross cg_unnamed_cross_inst = new; + cg_mixed cg_mixed_inst = new; + cg_def_cross cg_def_cross_inst = new; initial begin // Sample 2-way: hit all 4 combinations @@ -275,6 +294,23 @@ module t; cg_unnamed_cross_inst.sample(); // a1 x write `checkr(cg_unnamed_cross_inst.get_inst_coverage(), 75.0); + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) + addr = 0; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) + `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 + addr = 0; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) + addr = 1; cmd = 0; mode = 0; + cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) + addr = 1; cmd = 1; mode = 1; + cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) + `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) + mode = 1; + addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read + addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_covergroup_default_bins.out b/test_regress/t/t_covergroup_default_bins.out index 9e4e7a1c6..e034545b8 100644 --- a/test_regress/t/t_covergroup_default_bins.out +++ b/test_regress/t/t_covergroup_default_bins.out @@ -1,11 +1,11 @@ cg.data.high: 1 cg.data.low: 1 -cg.data.other: 2 -cg2.cp_only_default.all: 4 +cg.data.other [default]: 2 +cg2.cp_only_default.all [default]: 4 cg3.data.bad [ignore]: 1 cg3.data.err [illegal]: 0 cg3.data.normal: 2 -cg3.data.other: 2 +cg3.data.other [default]: 2 cg4.cp_idx.auto_0: 1 cg4.cp_idx.auto_1: 1 cg4.cp_idx.auto_2: 1 diff --git a/test_regress/t/t_covergroup_default_bins.v b/test_regress/t/t_covergroup_default_bins.v index f4ba5148c..c97cf8f66 100644 --- a/test_regress/t/t_covergroup_default_bins.v +++ b/test_regress/t/t_covergroup_default_bins.v @@ -157,17 +157,17 @@ module t; // Sample cg3: verify ignore/illegal bins do not contribute to coverage data = 2; cg3_inst.sample(); // hits normal bin - `checkr(cg3_inst.get_inst_coverage(), 50.0); // 1/2 bins hit (normal) + `checkr(cg3_inst.get_inst_coverage(), 100.0); // 1/1: only 'normal' counts (default/ignore/illegal excluded, LRM 19.5) data = 7; cg3_inst.sample(); // hits normal bin again - `checkr(cg3_inst.get_inst_coverage(), 50.0); // no new bins + `checkr(cg3_inst.get_inst_coverage(), 100.0); // no new normal bins data = 255; - cg3_inst.sample(); // ignore_bins value; Verilator counts it toward default bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2: Verilator hits 'other' (default) even for ignore_bins + cg3_inst.sample(); // ignore_bins value; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // note: do not sample 254 (illegal_bins would cause runtime assertion) data = 100; - cg3_inst.sample(); // hits default (other) bin - `checkr(cg3_inst.get_inst_coverage(), 100.0); // 2/2 bins hit + cg3_inst.sample(); // hits default (other) bin; recorded but excluded from coverage + `checkr(cg3_inst.get_inst_coverage(), 100.0); // Sample cg4: auto-bins with one excluded value // idx=2 is in ignore_bins, so auto-bins cover 0, 1, 3 only (3 bins total) diff --git a/test_regress/t/t_covergroup_iff.out b/test_regress/t/t_covergroup_iff.out index 0e28a68cf..69a7711dd 100644 --- a/test_regress/t/t_covergroup_iff.out +++ b/test_regress/t/t_covergroup_iff.out @@ -2,9 +2,11 @@ cg_and.cp_concat.b00: 0 cg_and.cp_concat.b01: 1 cg_and.cp_concat.b10: 0 cg_and.cp_concat.b11: 0 -cg_array_iff.cp.arr: 1 +cg_array_iff.cp.arr[0]: 1 +cg_array_iff.cp.arr[1]: 0 +cg_array_iff.cp.arr[2]: 0 cg_bitw.cp.seven: 1 -cg_default_iff.cp.def: 1 +cg_default_iff.cp.def [default]: 1 cg_default_iff.cp.known: 1 cg_iff.cp_value.disabled_hi: 0 cg_iff.cp_value.disabled_lo: 0 diff --git a/test_regress/t/t_covergroup_iff.v b/test_regress/t/t_covergroup_iff.v index 1816bd327..8bf6e0016 100644 --- a/test_regress/t/t_covergroup_iff.v +++ b/test_regress/t/t_covergroup_iff.v @@ -129,7 +129,7 @@ module t; enable = 1; value = 10; cg2.sample(); // hits 'known' - `checkr(cg2.get_inst_coverage(), 50.0); + `checkr(cg2.get_inst_coverage(), 100.0); // 1/1: only 'known' counts (default excluded, LRM 19.5) value = 99; cg2.sample(); // hits 'def' (default) `checkr(cg2.get_inst_coverage(), 100.0); diff --git a/test_regress/t/t_covergroup_ignore_bins.out b/test_regress/t/t_covergroup_ignore_bins.out index bcd0b72e0..6a1cba188 100644 --- a/test_regress/t/t_covergroup_ignore_bins.out +++ b/test_regress/t/t_covergroup_ignore_bins.out @@ -1,5 +1,7 @@ -cg.data.arr [ignore]: 0 -cg.data.bad [illegal]: 0 +cg.data.arr[0] [ignore]: 0 +cg.data.arr[1] [ignore]: 0 +cg.data.bad[0] [illegal]: 0 +cg.data.bad[1] [illegal]: 0 cg.data.catch_all [ignore]: 0 cg.data.high: 1 cg.data.low: 1 diff --git a/test_regress/t/t_covergroup_illegal_bins.out b/test_regress/t/t_covergroup_illegal_bins.out index d2de75a65..798247e10 100644 --- a/test_regress/t/t_covergroup_illegal_bins.out +++ b/test_regress/t/t_covergroup_illegal_bins.out @@ -2,7 +2,9 @@ cg.data.forbidden [illegal]: 0 cg.data.high: 1 cg.data.low: 1 cg.data.mid: 1 -cg2.cp_arr.bad_arr [illegal]: 0 +cg2.cp_arr.bad_arr[0] [illegal]: 0 +cg2.cp_arr.bad_arr[1] [illegal]: 0 +cg2.cp_arr.bad_arr[2] [illegal]: 0 cg2.cp_arr.ok: 1 cg2.cp_arr.wlib [illegal]: 0 cg2.cp_trans.bad_2step [illegal]: 0 diff --git a/test_regress/t/t_covergroup_trans.out b/test_regress/t/t_covergroup_trans.out index f7621e74a..17e059ede 100644 --- a/test_regress/t/t_covergroup_trans.out +++ b/test_regress/t/t_covergroup_trans.out @@ -7,3 +7,5 @@ cg.cp_trans2.trans2: 1 cg.cp_trans2.trans3: 1 cg.cp_trans3.seq_a: 1 cg.cp_trans3.seq_b: 1 +cg_legacy.cp_mix.tr: 1 +cg_legacy.cp_mix.vals: 1 diff --git a/test_regress/t/t_covergroup_trans.v b/test_regress/t/t_covergroup_trans.v index dea93e6c5..f95561ed2 100644 --- a/test_regress/t/t_covergroup_trans.v +++ b/test_regress/t/t_covergroup_trans.v @@ -39,7 +39,17 @@ module t; } endgroup + // Non-convertible coverpoint (it has a transition bin) that also carries a value-array bin, + // so the legacy array codegen path (which converted coverpoints bypass) is still exercised. + covergroup cg_legacy; + cp_mix: coverpoint state { + bins tr = (0 => 1); + bins vals[] = {2, [4:5]}; // discrete + range elements (both legacy array paths) + } + endgroup + cg cg_inst = new; + cg_legacy cg_legacy_inst = new; initial begin // Drive sequence 0->1->2->3->4 which hits all bins @@ -56,6 +66,12 @@ module t; cg_inst.sample(); // 3=>4: seq_b done `checkr(cg_inst.get_inst_coverage(), 100.0); + // cg_legacy: exercise legacy array codegen (non-convertible coverpoint) + state = 0; cg_legacy_inst.sample(); + state = 1; cg_legacy_inst.sample(); // 0=>1: tr + state = 2; cg_legacy_inst.sample(); // vals + state = 3; cg_legacy_inst.sample(); // vals + $write("*-* All Finished *-*\n"); $finish; end diff --git a/test_regress/t/t_vlcov_covergroup.annotate.out b/test_regress/t/t_vlcov_covergroup.annotate.out index 436973cb7..b8616ad2d 100644 --- a/test_regress/t/t_vlcov_covergroup.annotate.out +++ b/test_regress/t/t_vlcov_covergroup.annotate.out @@ -14,9 +14,9 @@ module t; %000001 logic [1:0] addr; --000001 point: type=toggle comment=addr[0]:0->1 hier=top.t +-000000 point: type=toggle comment=addr[0]:0->1 hier=top.t -000000 point: type=toggle comment=addr[0]:1->0 hier=top.t --000000 point: type=toggle comment=addr[1]:0->1 hier=top.t +-000001 point: type=toggle comment=addr[1]:0->1 hier=top.t -000000 point: type=toggle comment=addr[1]:1->0 hier=top.t %000001 logic cmd; -000001 point: type=toggle comment=cmd:0->1 hier=top.t @@ -278,6 +278,50 @@ // cross: [a1, write] endgroup + // Cross plus an un-crossed coverpoint: get_inst_coverage must combine the converted + // (VlCoverpoint) coverpoint cp_solo with the legacy cross/crossed-coverpoint bins. + covergroup cg_mixed; +%000002 cp_addr: coverpoint addr {bins addr0 = {0}; bins addr1 = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr0 +-000002 point: type=covergroup comment= hier=cg_mixed.cp_addr.addr1 +%000002 cp_cmd: coverpoint cmd {bins read = {0}; bins write = {1};} +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.read +-000002 point: type=covergroup comment= hier=cg_mixed.cp_cmd.write +%000002 cp_solo: coverpoint mode {bins normal = {0}; bins debug = {1};} // not crossed +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.normal +-000002 point: type=covergroup comment= hier=cg_mixed.cp_solo.debug +%000001 ab: cross cp_addr, cp_cmd; +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_read + // cross: [addr0, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr0_x_write + // cross: [addr0, write] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_read + // cross: [addr1, read] +-000001 point: type=covergroup comment= hier=cg_mixed.ab.addr1_x_write + // cross: [addr1, write] + endgroup + + // Crossed (hence non-convertible) coverpoint that also has a default bin: exercises the + // legacy default-bin codegen path that converted coverpoints bypass. + covergroup cg_def_cross; +%000001 cp_a: coverpoint addr iff (mode) {bins a0 = {0}; bins a1 = {1}; bins ad = default;} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.a0 +-000000 point: type=covergroup comment= hier=cg_def_cross.cp_a.a1 +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_a.ad +%000001 cp_c: coverpoint cmd {bins read = {0}; bins write = {1};} +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.read +-000001 point: type=covergroup comment= hier=cg_def_cross.cp_c.write +%000001 axc: cross cp_a, cp_c; +-000001 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_read + // cross: [a0, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a0_x_write + // cross: [a0, write] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_read + // cross: [a1, read] +-000000 point: type=covergroup comment= hier=cg_def_cross.axc.a1_x_write + // cross: [a1, write] + endgroup + %000001 cg2 cg2_inst = new; -000001 point: type=line comment=block hier=top.t %000001 cg_ignore cg_ignore_inst = new; @@ -298,6 +342,10 @@ -000001 point: type=line comment=block hier=top.t %000001 cg_unnamed_cross cg_unnamed_cross_inst = new; -000001 point: type=line comment=block hier=top.t +%000001 cg_mixed cg_mixed_inst = new; +-000001 point: type=line comment=block hier=top.t +%000001 cg_def_cross cg_def_cross_inst = new; +-000001 point: type=line comment=block hier=top.t %000001 initial begin -000001 point: type=line comment=block hier=top.t @@ -641,6 +689,40 @@ -000000 point: type=line comment=block hier=top.t -000001 point: type=line comment=else hier=top.t + // Sample cg_mixed: 10 bins total (cp_addr 2 + cp_cmd 2 + cp_solo 2 + cross ab 4) +%000001 addr = 0; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, read, solo normal, ab(addr0_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 40.0); // 4/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t +%000001 addr = 0; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr0, write, solo debug, ab(addr0_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 0; mode = 0; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, read, ab(addr1_x_read) +-000001 point: type=line comment=block hier=top.t +%000001 addr = 1; cmd = 1; mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 cg_mixed_inst.sample(); // addr1, write, ab(addr1_x_write) +-000001 point: type=line comment=block hier=top.t +%000001 `checkr(cg_mixed_inst.get_inst_coverage(), 100.0); // 10/10 +-000001 point: type=line comment=block hier=top.t +-000000 point: type=line comment=block hier=top.t +-000001 point: type=line comment=else hier=top.t + + // Sample cg_def_cross (default bin in a crossed coverpoint, gated by iff) +%000001 mode = 1; +-000001 point: type=line comment=block hier=top.t +%000001 addr = 0; cmd = 0; cg_def_cross_inst.sample(); // a0, read +-000001 point: type=line comment=block hier=top.t +%000001 addr = 2; cmd = 1; cg_def_cross_inst.sample(); // ad (default), write +-000001 point: type=line comment=block hier=top.t + %000001 $write("*-* All Finished *-*\n"); -000001 point: type=line comment=block hier=top.t %000001 $finish; From 87d261067405771c731c5422122aca0d9013d3ee Mon Sep 17 00:00:00 2001 From: Nick Brereton <85175726+nbstrike@users.noreply.github.com> Date: Fri, 12 Jun 2026 17:32:01 -0400 Subject: [PATCH 26/46] Support unpacked struct stream (#7767) --- src/V3AstNodeDType.h | 4 + src/V3AstNodes.cpp | 41 +++ src/V3Const.cpp | 138 ++++++- src/V3Width.cpp | 53 ++- test_regress/t/t_stream_unpacked_struct.py | 18 + test_regress/t/t_stream_unpacked_struct.v | 347 ++++++++++++++++++ .../t/t_stream_unpacked_struct_bad.out | 30 ++ .../t/t_stream_unpacked_struct_bad.py | 16 + test_regress/t/t_stream_unpacked_struct_bad.v | 63 ++++ 9 files changed, 691 insertions(+), 19 deletions(-) create mode 100755 test_regress/t/t_stream_unpacked_struct.py create mode 100644 test_regress/t/t_stream_unpacked_struct.v create mode 100644 test_regress/t/t_stream_unpacked_struct_bad.out create mode 100755 test_regress/t/t_stream_unpacked_struct_bad.py create mode 100644 test_regress/t/t_stream_unpacked_struct_bad.v diff --git a/src/V3AstNodeDType.h b/src/V3AstNodeDType.h index 1253c8527..4600911d2 100644 --- a/src/V3AstNodeDType.h +++ b/src/V3AstNodeDType.h @@ -170,6 +170,10 @@ public: void generic(bool flag) { m_generic = flag; } std::pair dimensions(bool includeBasic) const; uint32_t arrayUnpackedElements() const; // 1, or total multiplication of all dimensions + // Fixed aggregate streaming properties + bool isStreamableFixedAggregate() const; + bool containsUnpackedStruct() const; + int widthStream() const; static int uniqueNumInc() { return ++s_uniqueNum; } const char* charIQWN() const { return (isString() ? "N" : isWide() ? "W" : isDouble() ? "D" : isQuad() ? "Q" : "I"); diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index e98c9bb89..746bd4595 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -1259,6 +1259,47 @@ uint32_t AstNodeDType::arrayUnpackedElements() const { return entries; } +bool AstNodeDType::isStreamableFixedAggregate() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->isStreamableFixedAggregate(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) return true; + if (!VN_IS(sdtypep, StructDType)) return false; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + if (!itemp->dtypep()->isStreamableFixedAggregate()) return false; + } + return true; + } + return dtypep->isIntegralOrPacked() || dtypep->isDouble(); +} + +bool AstNodeDType::containsUnpackedStruct() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->containsUnpackedStruct(); + } + const AstStructDType* const sdtypep = VN_CAST(dtypep, StructDType); + return sdtypep && !sdtypep->packed(); +} + +int AstNodeDType::widthStream() const { + const AstNodeDType* const dtypep = skipRefp(); + if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { + return adtypep->subDTypep()->widthStream() * adtypep->elementsConst(); + } else if (const AstNodeUOrStructDType* const sdtypep = VN_CAST(dtypep, NodeUOrStructDType)) { + if (!VN_IS(sdtypep, StructDType) || sdtypep->packed()) return width(); + int width = 0; + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + width += itemp->dtypep()->widthStream(); + } + return width; + } + return dtypep->width(); +} + std::pair AstNodeDType::dimensions(bool includeBasic) const { // How many array dimensions (packed,unpacked) does this Var have? uint32_t packed = 0; diff --git a/src/V3Const.cpp b/src/V3Const.cpp index de2e1d64f..2ff6a0a80 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -973,6 +973,96 @@ class ConstVisitor final : public VNVisitor { return !numv.isNumber() ? numv : V3Number{nodep, nodep->width(), numv}; } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } + + AstStructSel* newStructSel(AstNodeExpr* const fromp, const AstMemberDType* const itemp) { + AstStructSel* const selp = new AstStructSel{fromp->fileline(), fromp, itemp->name()}; + selp->dtypeFrom(itemp->dtypep()); + return selp; + } + + void collectFixedAggregateTerms(AstNodeExpr* const fromp, std::vector& termps, + const bool packReal) { + const AstNodeDType* const dtypep = fromp->dtypep()->skipRefp(); + if (const AstUnpackArrayDType* const unpackDtypep = VN_CAST(dtypep, UnpackArrayDType)) { + const int left = unpackDtypep->left(); + const int right = unpackDtypep->right(); + const int step = left <= right ? 1 : -1; + for (int idx = left;; idx += step) { + AstArraySel* const selp + = new AstArraySel{fromp->fileline(), fromp->cloneTreePure(false), idx}; + collectFixedAggregateTerms(selp, termps, packReal); + if (idx == right) break; + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (const AstNodeUOrStructDType* const sdtypep + = VN_CAST(dtypep, NodeUOrStructDType)) { + if (sdtypep->packed()) { + termps.push_back(fromp); + return; + } + for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; + itemp = VN_AS(itemp->nextp(), MemberDType)) { + collectFixedAggregateTerms(newStructSel(fromp->cloneTreePure(false), itemp), + termps, packReal); + } + VL_DO_DANGLING(pushDeletep(fromp), fromp); + } else if (packReal && dtypep->isDouble()) { + termps.push_back(new AstRealToBits{fromp->fileline(), fromp}); + } else { + termps.push_back(fromp); + } + } + + AstNodeExpr* packFixedAggregate(AstNodeExpr* const fromp) { + std::vector termps; + collectFixedAggregateTerms(fromp, termps, true); + UASSERT(!termps.empty(), "No stream terms"); + AstNodeExpr* resultp = termps[0]; + for (size_t i = 1; i < termps.size(); ++i) { + resultp = new AstConcat{resultp->fileline(), resultp, termps[i]}; + } + return resultp; + } + + void replaceAssignToFixedAggregate(AstNodeAssign* const nodep, AstNodeExpr* const dstp, + AstNodeExpr* srcp) { + const int dstWidth = dstp->dtypep()->widthStream(); + std::vector termps; + collectFixedAggregateTerms(dstp, termps, false); + int srcWidth = srcp->width(); + if (srcWidth < dstWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dstWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + extendp->fileline(), extendp, + new AstConst{extendp->fileline(), static_cast(dstWidth - srcWidth)}, + dstWidth}; + srcWidth = dstWidth; + } + AstNodeAssign* newp = nullptr; + int offset = 0; + for (size_t i = 0; i < termps.size(); ++i) { + AstNodeExpr* const termp = termps[i]; + const int width = termp->dtypep()->widthStream(); + const int lsb = srcWidth - offset - width; + offset += width; + AstNodeExpr* rhsp + = new AstSel{srcp->fileline(), srcp->cloneTreePure(false), lsb, width}; + if (termp->dtypep()->skipRefp()->isDouble()) { + rhsp = new AstBitsToRealD{rhsp->fileline(), rhsp}; + } + AstNodeAssign* const assignp = nodep->cloneType(termp, rhsp); + assignp->dtypeFrom(termp); + newp = AstNode::addNext(newp, assignp); + } + nodep->addNextHere(newp); + VL_DO_DANGLING(pushDeletep(srcp), srcp); + VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); + } + bool operandConst(const AstNode* nodep) { return VN_IS(nodep, Const); } bool operandAsvConst(const AstNode* nodep) { // BIASV(CONST, BIASV(CONST,...)) -> BIASV( BIASV_CONSTED(a,b), ...) @@ -2393,6 +2483,25 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(conp), conp); // Further reduce, either node may have more reductions. return true; + } else if (m_doV && VN_IS(nodep->rhsp(), CvtPackedToArray) + && lowerAsFixedAggregate(nodep->lhsp()->dtypep())) { + AstCvtPackedToArray* const cvtp + = VN_AS(nodep->rhsp(), CvtPackedToArray)->unlinkFrBack(); + AstNodeExpr* srcp = cvtp->fromp()->unlinkFrBack(); + if (lowerAsFixedAggregate(srcp->dtypep())) { + srcp = packFixedAggregate(srcp); + } else if (AstNodeStream* const streamp = VN_CAST(srcp, NodeStream)) { + AstNodeExpr* const streamSrcp = streamp->lhsp(); + if (lowerAsFixedAggregate(streamSrcp->dtypep())) { + AstNodeExpr* const packedp = packFixedAggregate(streamSrcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + } + } + VL_DO_DANGLING(pushDeletep(cvtp), cvtp); + replaceAssignToFixedAggregate(nodep, nodep->lhsp()->unlinkFrBack(), srcp); + return true; } else if (m_doV && VN_IS(nodep->rhsp(), StreamR) && !VN_IS(nodep->lhsp()->dtypep()->skipRefp(), QueueDType)) { // The right-streaming operator on rhs of assignment does not @@ -2402,7 +2511,9 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp()->unlinkFrBack(); AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); const AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); - if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { + if (lowerAsFixedAggregate(srcDTypep)) { + srcp = packFixedAggregate(srcp); + } else if (VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType)) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { int srcElementBits = 0; if (const AstNodeDType* const elemDtp = srcDTypep->subDTypep()) { @@ -2429,6 +2540,19 @@ class ConstVisitor final : public VNVisitor { srcp = new AstShiftL{srcp->fileline(), srcp, new AstConst{srcp->fileline(), offset}, packedBits}; } + if (!VN_IS(dstDTypep, UnpackArrayDType) && !VN_IS(dstDTypep, QueueDType) + && !VN_IS(dstDTypep, DynArrayDType)) { + const int sWidth = srcp->width(); + const int dWidth = nodep->lhsp()->width(); + if (sWidth < dWidth) { + AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; + extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); + srcp = new AstShiftL{ + srcp->fileline(), extendp, + new AstConst{srcp->fileline(), static_cast(dWidth - sWidth)}, + dWidth}; + } + } nodep->rhsp(srcp); VL_DO_DANGLING(pushDeletep(streamp), streamp); // Further reduce, any of the nodes may have more reductions. @@ -2438,7 +2562,7 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* streamp = nodep->lhsp()->unlinkFrBack(); AstNodeExpr* const dstp = VN_AS(streamp, StreamL)->lhsp()->unlinkFrBack(); AstNodeDType* const dstDTypep = dstp->dtypep()->skipRefp(); - AstNodeExpr* const srcp = nodep->rhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); // Handle unpacked/queue/dynarray source -> queue/dynarray dest via // CvtArrayToArray (StreamL reverses, so reverse=true) @@ -2466,6 +2590,7 @@ class ConstVisitor final : public VNVisitor { VL_DO_DANGLING(pushDeletep(streamp), streamp); return true; } + if (lowerAsFixedAggregate(srcDTypep)) srcp = packFixedAggregate(srcp); const int sWidth = srcp->width(); const int dWidth = dstp->width(); // Connect the rhs to the stream operator and update its width @@ -2556,7 +2681,7 @@ class ConstVisitor final : public VNVisitor { } else { // Source narrower than destination: left-justify by shifting left. // The right stream operator packs left-to-right, so remaining - // LSBs are zero-filled (IEEE 1800-2023 11.4.14.2). + // LSBs are zero-filled (IEEE 1800-2023 11.4.14.3). if (!VN_IS(srcp->dtypep()->skipRefp(), QueueDType)) { AstExtend* const extendp = new AstExtend{srcp->fileline(), srcp}; extendp->dtypeSetLogicSized(dWidth, VSigning::UNSIGNED); @@ -2580,6 +2705,13 @@ class ConstVisitor final : public VNVisitor { AstNodeExpr* srcp = streamp->lhsp(); const AstNodeDType* const srcDTypep = srcp->dtypep()->skipRefp(); AstNodeDType* const dstDTypep = nodep->lhsp()->dtypep()->skipRefp(); + if (lowerAsFixedAggregate(srcDTypep)) { + AstNodeExpr* const packedp = packFixedAggregate(srcp->unlinkFrBack()); + streamp->lhsp(packedp); + streamp->dtypeSetLogicUnsized(packedp->width(), packedp->widthMin(), + VSigning::UNSIGNED); + srcp = packedp; + } if ((VN_IS(srcDTypep, QueueDType) || VN_IS(srcDTypep, DynArrayDType) || VN_IS(srcDTypep, UnpackArrayDType))) { if (VN_IS(dstDTypep, QueueDType) || VN_IS(dstDTypep, DynArrayDType)) { diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 861714e34..a323f9336 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -255,13 +255,6 @@ class WidthVisitor final : public VNVisitor { EXTEND_OFF // No extension }; - int widthUnpacked(const AstNodeDType* const dtypep) { - if (const AstUnpackArrayDType* const arrDtypep = VN_CAST(dtypep, UnpackArrayDType)) { - return arrDtypep->subDTypep()->width() * arrDtypep->arrayUnpackedElements(); - } - return dtypep->width(); - } - static void packIfUnpacked(AstNodeExpr* const nodep) { if (AstUnpackArrayDType* const unpackDTypep = VN_CAST(nodep->dtypep(), UnpackArrayDType)) { const int elementsNum = unpackDTypep->arrayUnpackedElements(); @@ -274,6 +267,9 @@ class WidthVisitor final : public VNVisitor { nodep->findLogicDType(unpackBits, unpackMinBits, VSigning::UNSIGNED)}); } } + static bool lowerAsFixedAggregate(const AstNodeDType* const dtypep) { + return dtypep->isStreamableFixedAggregate() && dtypep->containsUnpackedStruct(); + } // When fromp() is a DType (e.g. unlinked RefDType), resolve through // the ref chain; when it's an expression, dtypep() is already resolved. static AstNodeDType* fromDTypep(AstNode* fromp) { @@ -979,7 +975,10 @@ class WidthVisitor final : public VNVisitor { } const AstNodeDType* const lhsDtypep = nodep->lhsp()->dtypep()->skipRefToEnump(); if (VN_IS(lhsDtypep, DynArrayDType) || VN_IS(lhsDtypep, QueueDType) - || VN_IS(lhsDtypep, UnpackArrayDType)) { + || (VN_IS(lhsDtypep, UnpackArrayDType) && lhsDtypep->isStreamableFixedAggregate()) + || (VN_IS(lhsDtypep, NodeUOrStructDType) + && !VN_AS(lhsDtypep, NodeUOrStructDType)->packed() + && lhsDtypep->isStreamableFixedAggregate())) { nodep->dtypeSetStream(); } else if (lhsDtypep->isCompound()) { nodep->v3warn(E_UNSUPPORTED, @@ -6295,14 +6294,14 @@ class WidthVisitor final : public VNVisitor { userIterateAndNext(nodep->rhsp(), WidthVP{nodep->dtypep(), PRELIM}.p()); // // UINFOTREE(1, nodep, "", "assign"); - AstNodeDType* const lhsDTypep + AstNodeDType* lhsDTypep = nodep->lhsp()->dtypep(); // Note we use rhsp for context determined // Check width of stream and wrap if needed if (AstNodeStream* const streamp = VN_CAST(nodep->rhsp(), NodeStream)) { AstNodeDType* const lhsDTypeSkippedRefp = lhsDTypep->skipRefp(); - const int lwidth = widthUnpacked(lhsDTypeSkippedRefp); - const int rwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); + const int lwidth = lhsDTypeSkippedRefp->widthStream(); + const int rwidth = streamp->lhsp()->dtypep()->skipRefp()->widthStream(); if (lwidth != 0 && lwidth < rwidth) { nodep->v3widthWarn(lwidth, rwidth, "Target fixed size variable (" @@ -6314,16 +6313,18 @@ class WidthVisitor final : public VNVisitor { const int queueElementSize = streamp->lhsp()->dtypep()->subDTypep()->width(); UASSERT_OBJ(queueElementSize <= lwidth, nodep, "LHS < RHS"); } - if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType)) { + if (VN_IS(lhsDTypeSkippedRefp, UnpackArrayDType) + || lowerAsFixedAggregate(lhsDTypeSkippedRefp)) { streamp->unlinkFrBack(); nodep->rhsp(new AstCvtPackedToArray{streamp->fileline(), streamp, lhsDTypeSkippedRefp}); } } - if (const AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { + if (AstNodeStream* const streamp = VN_CAST(nodep->lhsp(), NodeStream)) { const AstNodeDType* const rhsDTypep = nodep->rhsp()->dtypep()->skipRefp(); - const int lwidth = widthUnpacked(streamp->lhsp()->dtypep()->skipRefp()); - const int rwidth = widthUnpacked(rhsDTypep); + AstNodeDType* const lhsStreamDTypep = streamp->lhsp()->dtypep()->skipRefp(); + const int lwidth = lhsStreamDTypep->widthStream(); + const int rwidth = rhsDTypep->widthStream(); if (rwidth != 0 && rwidth < lwidth) { nodep->v3widthWarn(lwidth, rwidth, "Stream target requires " @@ -6331,7 +6332,27 @@ class WidthVisitor final : public VNVisitor { << " bits, but source expression only provides " << rwidth << " bits (IEEE 1800-2023 11.4.14.3)"); } - if (VN_IS(rhsDTypep, UnpackArrayDType)) { + if (lowerAsFixedAggregate(lhsStreamDTypep)) { + AstNodeExpr* const streamExprp = nodep->lhsp()->unlinkFrBack(); + AstNodeExpr* const dstp = streamp->lhsp()->unlinkFrBack(); + AstNodeExpr* srcp = nodep->rhsp()->unlinkFrBack(); + if (VN_IS(streamp, StreamL)) { + streamp->lhsp(srcp); + streamp->dtypeSetLogicUnsized(srcp->width(), srcp->widthMin(), + VSigning::UNSIGNED); + srcp = streamExprp; + } else { + if (srcp->width() > lwidth) { + srcp = new AstSel{streamp->fileline(), srcp, srcp->width() - lwidth, + lwidth}; + } + VL_DO_DANGLING(pushDeletep(streamExprp), streamExprp); + } + nodep->lhsp(dstp); + nodep->rhsp(new AstCvtPackedToArray{srcp->fileline(), srcp, lhsStreamDTypep}); + nodep->dtypeFrom(dstp); + lhsDTypep = nodep->lhsp()->dtypep(); + } else if (VN_IS(rhsDTypep, UnpackArrayDType)) { AstNodeExpr* const rhsp = nodep->rhsp()->unlinkFrBack(); nodep->rhsp( new AstCvtArrayToPacked{rhsp->fileline(), rhsp, streamp->dtypep()}); diff --git a/test_regress/t/t_stream_unpacked_struct.py b/test_regress/t/t_stream_unpacked_struct.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v new file mode 100644 index 000000000..509d8a035 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -0,0 +1,347 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +// Ref. to IEEE 1800-2023 11.4.14, A.8.1 + +module t; + + `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); + + typedef struct packed { + logic [3:0] hi; + logic [3:0] lo; + } packed_pair_t; + + typedef union packed { + logic [7:0] byte_data; + packed_pair_t pair; + } packed_union_t; + + typedef struct { + byte a; + logic [3:0] b; + packed_pair_t p; + } simple_t; + + typedef struct { + byte prefix; + byte data[2]; + packed_pair_t p; + } array_t; + + typedef struct { + simple_t inner; + byte tail[2]; + } nested_t; + + typedef struct { + byte prefix; + simple_t items[2]; + byte suffix; + } struct_array_t; + + typedef struct { + byte data[1:0]; + } descending_array_t; + + typedef struct { + byte matrix[2][3]; + } matrix_array_t; + + typedef struct { + logic [1:0][3:0] data[2]; + } mixed_array_t; + + typedef enum logic [2:0] { + E0 = 3'd0, + E5 = 3'd5 + } enum_t; + + typedef struct { + enum_t e; + logic [4:0] x; + } enum_struct_t; + + typedef struct { + byte tag; + real r; + realtime rt; + real ra[2]; + } real_struct_t; + + typedef struct { + packed_union_t u; + byte tail; + } packed_union_struct_t; + + simple_t simple; + simple_t simple_out; + simple_t simple_cont_out; + array_t arrayed; + array_t arrayed_out; + nested_t nested; + nested_t nested_out; + struct_array_t struct_array; + struct_array_t struct_array_out; + descending_array_t descending; + descending_array_t descending_out; + matrix_array_t matrix_array; + matrix_array_t matrix_array_out; + mixed_array_t mixed_array; + mixed_array_t mixed_array_out; + simple_t simple_array[2]; + simple_t simple_array_out[2]; + enum_struct_t enum_struct; + enum_struct_t enum_struct_out; + real_struct_t real_struct; + real_struct_t real_struct_out; + packed_union_struct_t packed_union_struct; + packed_union_struct_t packed_union_struct_out; + + logic [19:0] simple_bits; + logic [31:0] wide_simple_bits; + logic [31:0] array_bits; + logic [35:0] nested_bits; + logic [55:0] struct_array_bits; + logic [15:0] descending_bits; + logic [47:0] matrix_array_bits; + logic [15:0] mixed_array_bits; + logic [39:0] simple_array_bits; + logic [31:0] byteswapped_bits; + logic [7:0] enum_bits; + logic [263:0] real_bits; + logic [15:0] packed_union_bits; + logic [11:0] narrow_bits; + logic [19:0] simple_streaml_src; + byte byte_array_out[2]; + + assign {>>{simple_cont_out}} = 20'habcde; + + initial begin + byteswapped_bits = {<<8{32'h11223344}}; + `checkh(byteswapped_bits, 32'h44332211); + byteswapped_bits = {<>{simple_bits}} = 20'h13579; + `checkh(simple_bits, 20'h13579); + {<<4{simple_bits}} = 20'h12345; + `checkh(simple_bits, 20'h54321); + + simple = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_bits = {>>{simple}}; + `checkh(simple_bits, 20'h12abc); + /* verilator lint_off WIDTHEXPAND */ + wide_simple_bits = {>>{simple}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(wide_simple_bits, 32'h12abc000); + simple_bits = {<<4{simple}}; + `checkh(simple_bits, 20'hcba21); + + {>>{simple_out}} = 20'h345de; + `checkh(simple_out.a, 8'h34); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'hde); + `checkh(simple_cont_out.a, 8'hab); + `checkh(simple_cont_out.b, 4'hc); + `checkh(simple_cont_out.p, 8'hde); + + {<<4{simple_out}} = 20'h789ab; + `checkh(simple_out.a, 8'hba); + `checkh(simple_out.b, 4'h9); + `checkh(simple_out.p, 8'h87); + + simple_out = {>>{20'hfedcb}}; + `checkh(simple_out.a, 8'hfe); + `checkh(simple_out.b, 4'hd); + `checkh(simple_out.p, 8'hcb); + + simple_out = {>>{simple}}; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + {>>{simple_out}} = simple; + `checkh(simple_out.a, 8'h12); + `checkh(simple_out.b, 4'ha); + `checkh(simple_out.p, 8'hbc); + + if ($test$plusargs("t_stream_unpacked_struct_alt")) begin + narrow_bits = 12'h123; + end else begin + narrow_bits = 12'habd; + end + /* verilator lint_off WIDTHEXPAND */ + simple_bits = {>>{narrow_bits}}; + /* verilator lint_on WIDTHEXPAND */ + `checkh(simple_bits, {narrow_bits, 8'h00}); + + simple_out = {>>{narrow_bits}}; + `checkh(simple_out.a, narrow_bits[11:4]); + `checkh(simple_out.b, narrow_bits[3:0]); + `checkh(simple_out.p, 8'h00); + + {>>{simple_out}} = 24'habcdef; + `checkh(simple_out.a, 8'hab); + `checkh(simple_out.b, 4'hc); + `checkh(simple_out.p, 8'hde); + + simple_out = {<<4{20'h13579}}; + `checkh(simple_out.a, 8'h97); + `checkh(simple_out.b, 4'h5); + `checkh(simple_out.p, 8'h31); + + simple_streaml_src = 20'h2468a; + simple_out = {<<4{simple_streaml_src}}; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<4{simple_out}} = simple_streaml_src; + `checkh(simple_out.a, 8'ha8); + `checkh(simple_out.b, 4'h6); + `checkh(simple_out.p, 8'h42); + + {<<8{byte_array_out}} = 16'h1234; + `checkh(byte_array_out[0], 8'h34); + `checkh(byte_array_out[1], 8'h12); + + arrayed = '{8'h11, '{8'h22, 8'h33}, '{4'h4, 4'h5}}; + array_bits = {>>{arrayed}}; + `checkh(array_bits, 32'h11223345); + array_bits = {<<8{arrayed}}; + `checkh(array_bits, 32'h45332211); + + {>>{arrayed_out}} = 32'haabbccdd; + `checkh(arrayed_out.prefix, 8'haa); + `checkh(arrayed_out.data[0], 8'hbb); + `checkh(arrayed_out.data[1], 8'hcc); + `checkh(arrayed_out.p, 8'hdd); + + nested = '{'{8'h12, 4'ha, '{4'hb, 4'hc}}, '{8'h34, 8'h56}}; + nested_bits = {>>{nested}}; + `checkh(nested_bits, 36'h12abc3456); + + {>>{nested_out}} = 36'h6543210fe; + `checkh(nested_out.inner.a, 8'h65); + `checkh(nested_out.inner.b, 4'h4); + `checkh(nested_out.inner.p, 8'h32); + `checkh(nested_out.tail[0], 8'h10); + `checkh(nested_out.tail[1], 8'hfe); + + struct_array.prefix = 8'haa; + struct_array.items[0] = '{8'h12, 4'h3, '{4'h4, 4'h5}}; + struct_array.items[1] = '{8'h67, 4'h8, '{4'h9, 4'ha}}; + struct_array.suffix = 8'hbb; + struct_array_bits = {>>{struct_array}}; + `checkh(struct_array_bits, 56'haa123456789abb); + + {>>{struct_array_out}} = 56'hccdef0123456dd; + `checkh(struct_array_out.prefix, 8'hcc); + `checkh(struct_array_out.items[0].a, 8'hde); + `checkh(struct_array_out.items[0].b, 4'hf); + `checkh(struct_array_out.items[0].p, 8'h01); + `checkh(struct_array_out.items[1].a, 8'h23); + `checkh(struct_array_out.items[1].b, 4'h4); + `checkh(struct_array_out.items[1].p, 8'h56); + `checkh(struct_array_out.suffix, 8'hdd); + + descending = '{data: '{8'hcc, 8'hdd}}; + descending_bits = {>>{descending}}; + `checkh(descending_bits, 16'hccdd); + + {>>{descending_out}} = 16'h1234; + `checkh(descending_out.data[1], 8'h12); + `checkh(descending_out.data[0], 8'h34); + + matrix_array.matrix[0][0] = 8'h11; + matrix_array.matrix[0][1] = 8'h22; + matrix_array.matrix[0][2] = 8'h33; + matrix_array.matrix[1][0] = 8'h44; + matrix_array.matrix[1][1] = 8'h55; + matrix_array.matrix[1][2] = 8'h66; + matrix_array_bits = {>>{matrix_array}}; + `checkh(matrix_array_bits, 48'h112233445566); + + {>>{matrix_array_out}} = 48'haabbccddeeff; + `checkh(matrix_array_out.matrix[0][0], 8'haa); + `checkh(matrix_array_out.matrix[0][1], 8'hbb); + `checkh(matrix_array_out.matrix[0][2], 8'hcc); + `checkh(matrix_array_out.matrix[1][0], 8'hdd); + `checkh(matrix_array_out.matrix[1][1], 8'hee); + `checkh(matrix_array_out.matrix[1][2], 8'hff); + + mixed_array.data[0] = 8'hab; + mixed_array.data[1] = 8'hcd; + mixed_array_bits = {>>{mixed_array}}; + `checkh(mixed_array_bits, 16'habcd); + + {>>{mixed_array_out}} = 16'h1234; + `checkh(mixed_array_out.data[0], 8'h12); + `checkh(mixed_array_out.data[0][1], 4'h1); + `checkh(mixed_array_out.data[0][0], 4'h2); + `checkh(mixed_array_out.data[1], 8'h34); + `checkh(mixed_array_out.data[1][1], 4'h3); + `checkh(mixed_array_out.data[1][0], 4'h4); + + simple_array[0] = '{8'h12, 4'ha, '{4'hb, 4'hc}}; + simple_array[1] = '{8'h34, 4'hd, '{4'he, 4'hf}}; + simple_array_bits = {>>{simple_array}}; + `checkh(simple_array_bits, 40'h12abc34def); + + {>>{simple_array_out}} = 40'h567899abcd; + `checkh(simple_array_out[0].a, 8'h56); + `checkh(simple_array_out[0].b, 4'h7); + `checkh(simple_array_out[0].p, 8'h89); + `checkh(simple_array_out[1].a, 8'h9a); + `checkh(simple_array_out[1].b, 4'hb); + `checkh(simple_array_out[1].p, 8'hcd); + + enum_struct = '{E5, 5'ha}; + enum_bits = {>>{enum_struct}}; + `checkh(enum_bits, 8'haa); + + {>>{enum_struct_out}} = 8'h71; + `checkh(enum_struct_out.e, 3'h3); + `checkh(enum_struct_out.x, 5'h11); + + real_struct.tag = 8'h42; + real_struct.r = 1.0; + real_struct.rt = 3.0; + real_struct.ra[0] = 4.0; + real_struct.ra[1] = 5.0; + real_bits = {>>{real_struct}}; + `checkh(real_bits, + {8'h42, $realtobits(1.0), $realtobits(3.0), $realtobits(4.0), $realtobits(5.0)}); + + {>>{real_struct_out}} + = {8'h99, $realtobits(2.0), $realtobits(6.0), $realtobits(7.0), $realtobits(8.0)}; + `checkh(real_struct_out.tag, 8'h99); + `checkh($realtobits(real_struct_out.r), $realtobits(2.0)); + `checkh($realtobits(real_struct_out.rt), $realtobits(6.0)); + `checkh($realtobits(real_struct_out.ra[0]), $realtobits(7.0)); + `checkh($realtobits(real_struct_out.ra[1]), $realtobits(8.0)); + + packed_union_struct.u.byte_data = 8'hbe; + packed_union_struct.tail = 8'hef; + packed_union_bits = {>>{packed_union_struct}}; + `checkh(packed_union_bits, 16'hbeef); + + {>>{packed_union_struct_out}} = 16'hcafe; + `checkh(packed_union_struct_out.u.byte_data, 8'hca); + `checkh(packed_union_struct_out.tail, 8'hfe); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule diff --git a/test_regress/t/t_stream_unpacked_struct_bad.out b/test_regress/t/t_stream_unpacked_struct_bad.out new file mode 100644 index 000000000..9955e9208 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.out @@ -0,0 +1,30 @@ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:54:12: Unsupported: Stream operation on a variable of a type 'struct{}t.queue_struct_t' + : ... note: In instance 't' + 54 | out = {>>{queue_struct}}; + | ^~ + ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:55:12: Unsupported: Stream operation on a variable of a type 'struct{}t.dynamic_struct_t' + : ... note: In instance 't' + 55 | out = {>>{dynamic_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:56:12: Unsupported: Stream operation on a variable of a type 'struct{}t.associative_struct_t' + : ... note: In instance 't' + 56 | out = {>>{associative_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:57:12: Unsupported: Stream operation on a variable of a type 'struct{}t.union_struct_t' + : ... note: In instance 't' + 57 | out = {>>{union_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:58:12: Unsupported: Stream operation on a variable of a type 'struct{}t.string_struct_t' + : ... note: In instance 't' + 58 | out = {>>{string_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:59:12: Unsupported: Stream operation on a variable of a type 'struct{}t.chandle_struct_t' + : ... note: In instance 't' + 59 | out = {>>{chandle_struct}}; + | ^~ +%Error-UNSUPPORTED: t/t_stream_unpacked_struct_bad.v:60:12: Unsupported: Stream operation on a variable of a type 'struct{}t.event_struct_t' + : ... note: In instance 't' + 60 | out = {>>{event_struct}}; + | ^~ +%Error: Exiting due to diff --git a/test_regress/t/t_stream_unpacked_struct_bad.py b/test_regress/t/t_stream_unpacked_struct_bad.py new file mode 100755 index 000000000..38cf36b43 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.py @@ -0,0 +1,16 @@ +#!/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.lint(fails=True, expect_filename=test.golden_filename) + +test.passes() diff --git a/test_regress/t/t_stream_unpacked_struct_bad.v b/test_regress/t/t_stream_unpacked_struct_bad.v new file mode 100644 index 000000000..2c1e00b97 --- /dev/null +++ b/test_regress/t/t_stream_unpacked_struct_bad.v @@ -0,0 +1,63 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// 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 + +module t; + + typedef struct { + byte bytes[$]; + } queue_struct_t; + + typedef struct { + byte bytes[]; + } dynamic_struct_t; + + typedef struct { + byte bytes[int]; + } associative_struct_t; + + typedef union { + byte a; + logic [15:0] b; + } unpacked_union_t; + + typedef struct { + unpacked_union_t u; + } union_struct_t; + + typedef struct { + string s; + } string_struct_t; + + typedef struct { + chandle c; + } chandle_struct_t; + + typedef struct { + event e; + } event_struct_t; + + logic [255:0] out; + queue_struct_t queue_struct; + dynamic_struct_t dynamic_struct; + associative_struct_t associative_struct; + union_struct_t union_struct; + string_struct_t string_struct; + chandle_struct_t chandle_struct; + event_struct_t event_struct; + + initial begin + out = {>>{queue_struct}}; + out = {>>{dynamic_struct}}; + out = {>>{associative_struct}}; + out = {>>{union_struct}}; + out = {>>{string_struct}}; + out = {>>{chandle_struct}}; + out = {>>{event_struct}}; + end + +endmodule From ba624d7ce16e5c9cfdafd39806807ed3582c1f9f Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 13 Jun 2026 08:45:26 +0100 Subject: [PATCH 27/46] Optimize away proven redundant case statement assertions (#7771) This is still mostly refactoring of V3Case, but with functional changes. Decouple the exhaustiveness/overlap analysis from the decision to convert the case using the fast bitwise testing method. This enables dropping the 'notParallel' assertions for those we can prove exhaustive and unique, even if we decide to convert them using the generic if/else ladder scheme. --- src/V3Case.cpp | 289 +++++++++++++----------- test_regress/t/t_case_unique_overlap.py | 4 +- 2 files changed, 165 insertions(+), 128 deletions(-) diff --git a/src/V3Case.cpp b/src/V3Case.cpp index 9fc489c4d..65386df2a 100644 --- a/src/V3Case.cpp +++ b/src/V3Case.cpp @@ -129,10 +129,8 @@ public: // Case state, as a visitor of each AstNode class CaseVisitor final : public VNVisitor { - // Maximum width we can check for overlaps/exhaustiveness - constexpr static int CASE_OVERLAP_WIDTH = 16; - // Maximum number of case values for exhaustive analysis/optimization - constexpr static int CASE_MAX_VALUES = 1 << CASE_OVERLAP_WIDTH; + // Maximum width for detailed analysis + constexpr static int CASE_DETAILS_MAX_WIDTH = 16; // Levels of priority to be ORed together in top IF tree constexpr static int CASE_ENCODER_GROUP_DEPTH = 8; @@ -145,17 +143,37 @@ class CaseVisitor final : public VNVisitor { }; // STATE - VDouble0 m_statCaseFast; // Statistic tracking - VDouble0 m_statCaseSlow; // Statistic tracking + // Statistics tracking, as a struct so can be passed to 'const' methods + struct Stats final { + VDouble0 caseFast; // Cases using fast bit tree method + VDouble0 caseGeneric; // Cases using generic if/else tree method + VDouble0 provenAssertions; // Assertions proven to hold + } m_stats; const AstNode* m_alwaysp = nullptr; // Always in which case is located - // Per-CASE - bool m_caseExhaustive = false; // Proven exhaustive - bool m_caseNoOverlaps = false; // Proven no overlaps between cases - // Map from value (index) to the CaseRecord that covers this value - std::array m_value2CaseRecord; + // STATE - per AstCase. Update by 'analyzeCase', treat 'const' otherwise + bool m_caseOpaque = false; // Case statement is opaque (non-packed, or non-const conditions) + size_t m_caseNConditions = 0; // Number of conditions in the case statement + bool m_caseDetailsValid = false; // Indicates m_caseDetails is valid + struct final { + bool exhaustive = false; // Proven exhaustive + bool noOverlaps = false; // Proven no overlaps between cases + // Map from value (index) to the CaseRecord that covers this value + std::array records; + } m_caseDetails; // METHODS + + // Xs in case or casez are impossible due to two state simulations. + // Returns true if the item is never possible + static bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { + const AstConst* const constp = VN_CAST(itemExprp, Const); + if (!constp) return false; + if (casep->casex() || casep->caseInside()) return false; + if (casep->casez()) return constp->num().isAnyX(); + return constp->num().isFourState(); + } + // Determine whether we should check case items are complete // Returns enum's dtype if should check, nullptr if shouldn't static const AstEnumDType* getEnumCompletionCheckDType(const AstCase* const nodep) { @@ -185,7 +203,7 @@ class CaseVisitor final : public VNVisitor { // Check all cases to see if they cover this enum value/pattern for (uint32_t i = 0; i < numCases; ++i) { if ((i & mask) != val) continue; // This case is not for this enum value - if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (m_caseDetails.records[i].itemp) continue; // Covered case // Warn unless unique0 case which allows no-match if (!nodep->unique0Pragma()) { nodep->v3warn(CASEINCOMPLETE, @@ -203,7 +221,7 @@ class CaseVisitor final : public VNVisitor { bool checkExhaustivePacked(AstCase* nodep) { const uint32_t numCases = 1UL << nodep->exprp()->width(); for (uint32_t i = 0; i < numCases; ++i) { - if (m_value2CaseRecord[i].itemp) continue; // Covered case + if (m_caseDetails.records[i].itemp) continue; // Covered case if (!nodep->unique0Pragma()) { nodep->v3warn(CASEINCOMPLETE, "Case values incompletely covered (example pattern 0x" << std::hex @@ -224,50 +242,28 @@ class CaseVisitor final : public VNVisitor { return checkExhaustivePacked(nodep); } - bool isCaseTreeFast(AstCase* nodep) { - m_caseExhaustive = true; // TODO: we haven't proven this yet, but is as was before - m_caseNoOverlaps = false; - - AstNode* const caseExprp = nodep->exprp(); - if (caseExprp->isDouble() || caseExprp->isString()) return false; - - const int caseWidth = caseExprp->width(); - if (!caseWidth) return false; - if (caseWidth > CASE_OVERLAP_WIDTH) return false; - - int caseConditions = 0; - - for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { - for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { - // Can't do anything with non-constants - if (!VN_IS(condp, Const)) return false; - // Count conditions - ++caseConditions; - } - } - - UINFO(8, "Simple case statement: " << nodep); - const uint32_t numCases = 1UL << caseWidth; - // Zero list of items for each value - for (uint32_t i = 0; i < numCases; ++i) { - m_value2CaseRecord[i].itemp = nullptr; - m_value2CaseRecord[i].constp = nullptr; - m_value2CaseRecord[i].stmtsp = nullptr; + // Analyze each value in the case statement. Updates 'm_caseDetails' and issues warnings. + void analyzeCaseDetails(AstCase* nodep) { + const uint32_t numValues = 1UL << nodep->exprp()->width(); + // Clear case records + for (uint32_t i = 0; i < numValues; ++i) { + m_caseDetails.records[i].itemp = nullptr; + m_caseDetails.records[i].constp = nullptr; + m_caseDetails.records[i].stmtsp = nullptr; } // Now pick up the values for each assignment // We can cheat and use uint32_t's because we only support narrow case's bool reportedOverlap = false; - bool reportedSubcase = false; bool hasDefault = false; - m_caseNoOverlaps = true; + m_caseDetails.noOverlaps = true; for (AstCaseItem* itemp = nodep->itemsp(); itemp; itemp = VN_AS(itemp->nextp(), CaseItem)) { // Default case if (itemp->isDefault()) { // Default was moved to be the last item by V3LinkDot. Fill remaining cases - for (uint32_t i = 0; i < numCases; ++i) { - CaseRecord& caseRecord = m_value2CaseRecord[i]; + for (uint32_t i = 0; i < numValues; ++i) { + CaseRecord& caseRecord = m_caseDetails.records[i]; if (!caseRecord.itemp) { caseRecord.itemp = itemp; caseRecord.stmtsp = itemp->stmtsp(); @@ -293,10 +289,10 @@ class CaseVisitor final : public VNVisitor { bool foundNewCase = false; const AstConst* firstOverlapConstp = nullptr; uint32_t firstOverlapValue = 0; - for (uint32_t i = 0; i < numCases; ++i) { + for (uint32_t i = 0; i < numValues; ++i) { if ((i & mask) != val) continue; - CaseRecord& caseRecord = m_value2CaseRecord[i]; + CaseRecord& caseRecord = m_caseDetails.records[i]; // If this is the first case that covers this value, record it if (!caseRecord.itemp) { @@ -314,15 +310,21 @@ class CaseVisitor final : public VNVisitor { if (!firstOverlapConstp) { firstOverlapConstp = caseRecord.constp; firstOverlapValue = i; - m_caseNoOverlaps = false; + m_caseDetails.noOverlaps = false; } } + + // Only report first overlap + if (reportedOverlap || !firstOverlapConstp) continue; + + // Report first overlap if (nodep->priorityPragma()) { - // If this is a priority case, we only want to complain if every possible value - // for this item is already hit by some other item. This is true if - // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only covering - // item is this item itself, which is legal overlap within one item. - if (!reportedSubcase && !foundNewCase && firstOverlapConstp) { + // If this is a priority case, we only want to complain if every possible + // value for this item is already hit by some other item. This is true if + // 'foundNewCase' is false. 'firstOverlapConstp' is null when the only + // covering item is this item itself, which is legal overlap within one + // item. + if (!foundNewCase) { iconstp->v3warn(CASEOVERLAP, "Case item ignored: every matching value is covered " "by an earlier condition\n" @@ -330,59 +332,76 @@ class CaseVisitor final : public VNVisitor { << firstOverlapConstp->warnOther() << "... Location of previous condition\n" << firstOverlapConstp->warnContextPrimary()); - reportedSubcase = true; + reportedOverlap = true; } } else { // If this case statement doesn't have the priority keyword, // we want to warn on any overlap. - if (!reportedOverlap && firstOverlapConstp) { - std::ostringstream examplePattern; - if (iconstp->num().isAnyXZ()) { - examplePattern << " (example pattern 0x" << std::hex - << firstOverlapValue << ")"; - } - iconstp->v3warn(CASEOVERLAP, - "Case conditions overlap" - << examplePattern.str() << "\n" - << iconstp->warnContextPrimary() << '\n' - << firstOverlapConstp->warnOther() - << "... Location of overlapping condition\n" - << firstOverlapConstp->warnContextSecondary()); - reportedOverlap = true; + std::ostringstream examplePattern; + if (iconstp->num().isAnyXZ()) { + examplePattern << " (example pattern 0x" << std::hex << firstOverlapValue + << ")"; } + iconstp->v3warn(CASEOVERLAP, + "Case conditions overlap" + << examplePattern.str() << "\n" + << iconstp->warnContextPrimary() << '\n' + << firstOverlapConstp->warnOther() + << "... Location of overlapping condition\n" + << firstOverlapConstp->warnContextSecondary()); + reportedOverlap = true; } } } // If there was no default, check exhaustiveness - m_caseExhaustive = hasDefault || checkExhaustive(nodep); - if (!m_caseExhaustive) { - m_caseNoOverlaps = false; - return false; + m_caseDetails.exhaustive = hasDefault || checkExhaustive(nodep); + // Records now valid + m_caseDetailsValid = true; + } + + // Analyze case statement. Updates 'm_case*' members. Reports warnings. + void analyzeCase(AstCase* nodep) { + // Reset all analysis results + m_caseOpaque = false; + m_caseNConditions = 0; + m_caseDetailsValid = false; + + AstNode* const caseExprp = nodep->exprp(); + + // Mark opaque if not a packed value - TODO: can this be a class? + if (caseExprp->isDouble() || caseExprp->isString()) m_caseOpaque = true; + + // Check each condition expression + for (AstCaseItem* cip = nodep->itemsp(); cip; cip = VN_AS(cip->nextp(), CaseItem)) { + for (AstNode* condp = cip->condsp(); condp; condp = condp->nextp()) { + // Count conditions + ++m_caseNConditions; + // Mark opaque if non-constant condition + if (!VN_IS(condp, Const)) m_caseOpaque = true; + } } - if (caseConditions <= 3 - // Avoid e.g. priority expanders from going crazy in expansion - || (caseWidth >= 8 && (caseConditions <= (caseWidth + 1)))) { - return false; // Not worth simplifying - } + // Nothing else to do if not a packed type, or non-const conditions + if (m_caseOpaque) return; - return true; // All is fine + // If small enough, analyse details + if (caseExprp->width() <= CASE_DETAILS_MAX_WIDTH) analyzeCaseDetails(nodep); } // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) { + AstNode* convertCaseFastRecurse(AstNodeExpr* cexprp, int msb, uint32_t upperValue) const { // Base case: If reached the last bit, upperValue equals an exact value, just return // the statements from that CaseItem. Note: Not cloning here as the caller will do // an identity check. - if (msb < 0) return m_value2CaseRecord[upperValue].stmtsp; + if (msb < 0) return m_caseDetails.records[upperValue].stmtsp; // Recursive case: // Make left and right subtrees assuming cexpr[msb] is 0 and 1 respectively const uint32_t upperValue0 = upperValue; const uint32_t upperValue1 = upperValue | (1UL << msb); - AstNode* tree0p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue0); - AstNode* tree1p = replaceCaseFastRecurse(cexprp, msb - 1, upperValue1); + AstNode* tree0p = convertCaseFastRecurse(cexprp, msb - 1, upperValue0); + AstNode* tree1p = convertCaseFastRecurse(cexprp, msb - 1, upperValue1); // If same logic on both sides, we can just return one of them if (tree0p == tree1p) return tree0p; @@ -391,7 +410,7 @@ class CaseVisitor final : public VNVisitor { { bool same = true; for (uint32_t a = upperValue0, b = upperValue1; a < upperValue1; ++a, ++b) { - if (m_value2CaseRecord[a].stmtsp != m_value2CaseRecord[b].stmtsp) { + if (m_caseDetails.records[a].stmtsp != m_caseDetails.records[b].stmtsp) { same = false; break; } @@ -414,23 +433,23 @@ class CaseVisitor final : public VNVisitor { return ifp; } + // CASEx(cexpr,.... + // -> tree of IF(msb, IF(msb-1, 11, 10) + // IF(msb-1, 01, 00)) // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseFast(AstCase* nodep) { - // CASEx(cexpr,.... - // -> tree of IF(msb, IF(msb-1, 11, 10) - // IF(msb-1, 01, 00)) + AstNode* convertCaseFast(AstCase* nodep) const { const int caseWidth = nodep->exprp()->width(); - AstNode* const ifrootp = replaceCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); + AstNode* const ifrootp = convertCaseFastRecurse(nodep->exprp(), caseWidth - 1, 0UL); return ifrootp && ifrootp->backp() ? ifrootp->cloneTree(true) : ifrootp; } + // Convet case statement using generic if/else tree + // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) + // -> IF((cexpr==icond1),istmts1, + // IF((EQ (AND MASK cexpr) (AND MASK icond1) + // ,istmts2, istmts3 // TODO: should return AstNodeStmt after #6280 - AstNode* replaceCaseComplicated(AstCase* nodep) { - // CASEx(cexpr,ITEM(icond1,istmts1),ITEM(icond2,istmts2),ITEM(default,istmts3)) - // -> IF((cexpr==icond1),istmts1, - // IF((EQ (AND MASK cexpr) (AND MASK icond1) - // ,istmts2, istmts3 - + AstNode* convertCaseGeneric(AstCase* nodep) const { // We'll do this in two stages. // First stage, convert the conditions to the appropriate IF AND terms. bool hasDefault = false; @@ -567,13 +586,38 @@ class CaseVisitor final : public VNVisitor { return grouprootp; } - bool neverItem(const AstCase* casep, const AstNodeExpr* itemExprp) { - const AstConst* const constp = VN_CAST(itemExprp, Const); - if (!constp) return false; - // Xs in case or casez are impossible due to two state simulations - if (casep->casex() || casep->caseInside()) return false; - if (casep->casez()) return constp->num().isAnyX(); - return constp->num().isFourState(); + // Convert the given case statement to a representation not using AstCase + // TODO: should return AstNodeStmt after #6280 + AstNode* convertCase(AstCase* nodep, Stats& stats) const { + // Determine if we should use the fast bitwise branching tree method + const bool useFastBitTree = [&]() { + // Not if disabled + if (!v3Global.opt.fCase()) return false; + // Can't do it without the detailed analysis + if (!m_caseDetailsValid) return false; + // Can't do it if not exhaustive + if (!m_caseDetails.exhaustive) return false; + // Not worth doing if there are few conditions + if (m_caseNConditions <= 3) return false; + // Avoid e.g. priority expanders from going crazy in expansion + const size_t caseWidth = nodep->exprp()->width(); + if (caseWidth >= 8 && m_caseNConditions <= (caseWidth + 1)) return false; + // Otherwise use the bit tree + return true; + }(); + if (useFastBitTree) { + ++stats.caseFast; + return convertCaseFast(nodep); + } + + // Convert using the generic if/else tree method + ++stats.caseGeneric; + // If a case statement is exhaustive, presume signals involved aren't forming a latch + // TODO: this is broken, but it is as was before + if (m_alwaysp && (!m_caseDetailsValid || m_caseDetails.exhaustive)) { + m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); + } + return convertCaseGeneric(nodep); } // VISITORS @@ -586,34 +630,24 @@ class CaseVisitor final : public VNVisitor { // Convert any children first iterateChildren(nodep); - // Convert the case statement - AstNode* replacementp = nullptr; - if (isCaseTreeFast(nodep) && v3Global.opt.fCase()) { - // It's a simple priority encoder or complete statement - // we can make a tree of statements to avoid extra comparisons - ++m_statCaseFast; - replacementp = replaceCaseFast(nodep); - } else { - // If a case statement is exhaustive, presume signals involved aren't forming a latch - // TODO: this is broken, but it is as was before - if (m_alwaysp && m_caseExhaustive) { - m_alwaysp->fileline()->warnOff(V3ErrorCode::LATCH, true); - } - ++m_statCaseSlow; - m_caseExhaustive = false; - m_caseNoOverlaps = false; - replacementp = replaceCaseComplicated(nodep); - } + // Analyze this case statement + analyzeCase(nodep); - // Take the notParallelp tree under the case statement created by V3Assert + // Take the 'notParallelp' statements under the case statement created by V3Assert. // If the statement was proven to have no overlaps and all cases covered, // it can be removed. Otherwise insert the assertion after the case statement. - if (nodep->notParallelp() && (!m_caseExhaustive || !m_caseNoOverlaps)) { - nodep->addNextHere(nodep->notParallelp()->unlinkFrBackWithNext()); + if (AstNode* const stmtp = nodep->notParallelp()) { + stmtp->unlinkFrBackWithNext(); + if (m_caseDetailsValid && m_caseDetails.exhaustive && m_caseDetails.noOverlaps) { + ++m_stats.provenAssertions; + VL_DO_DANGLING(stmtp->deleteTree(), stmtp); + } else { + nodep->addNextHere(stmtp); + } } - // Replace/remove the case statement - if (replacementp) { + // Convert the case statement and replace the original + if (AstNode* const replacementp = convertCase(nodep, m_stats)) { nodep->replaceWith(replacementp); } else { nodep->unlinkFrBack(); @@ -632,8 +666,9 @@ public: // CONSTRUCTORS explicit CaseVisitor(AstNetlist* nodep) { iterate(nodep); } ~CaseVisitor() override { - V3Stats::addStat("Optimizations, Cases parallelized", m_statCaseFast); - V3Stats::addStat("Optimizations, Cases complex", m_statCaseSlow); + V3Stats::addStat("Optimizations, Cases parallelized", m_stats.caseFast); + V3Stats::addStat("Optimizations, Cases complex", m_stats.caseGeneric); + V3Stats::addStat("Optimizations, Cases proven assertions", m_stats.provenAssertions); } }; diff --git a/test_regress/t/t_case_unique_overlap.py b/test_regress/t/t_case_unique_overlap.py index 8a938befd..f3945fcdd 100755 --- a/test_regress/t/t_case_unique_overlap.py +++ b/test_regress/t/t_case_unique_overlap.py @@ -11,8 +11,10 @@ import vltest_bootstrap test.scenarios('simulator') -test.compile() +test.compile(verilator_flags2=['--stats']) test.execute() +test.file_grep(test.stats, r'Optimizations, Cases proven assertions\s+(\d+)', 1) + test.passes() From 7af22422c7a61f3248208351d665825d9a61f97d Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 13 Jun 2026 08:45:46 +0100 Subject: [PATCH 28/46] Optimize table lookups in Dfg (#7772) --- src/V3DfgPeephole.cpp | 20 +++++++++++++++++--- src/V3DfgPeepholePatterns.h | 1 + test_regress/t/t_dfg_peephole.v | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) diff --git a/src/V3DfgPeephole.cpp b/src/V3DfgPeephole.cpp index 34c1ac5a6..3f08f490f 100644 --- a/src/V3DfgPeephole.cpp +++ b/src/V3DfgPeephole.cpp @@ -1885,10 +1885,24 @@ class V3DfgPeephole final : public DfgVisitor { if (!idxp) return; DfgVarArray* const varp = vtxp->fromp()->cast(); if (!varp) return; - if (varp->vscp()->varp()->isForced()) return; - if (varp->vscp()->varp()->isSigUserRWPublic()) return; + AstVar* const astVarp = varp->vscp()->varp(); + if (astVarp->isForced()) return; + if (astVarp->isSigUserRWPublic()) return; DfgVertex* const srcp = varp->srcp(); - if (!srcp) return; + if (!srcp) { + if (vtxp->isPacked()) { + if (AstInitArray* const iap = VN_CAST(astVarp->valuep(), InitArray)) { + if (AstConst* const valp + = VN_CAST(iap->getIndexDefaultedValuep(idxp->toSizeT()), Const)) { + APPLYING(FOLD_ARRAYSEL_TABLE) { + replace(new DfgConst{m_dfg, valp->fileline(), valp->num()}); + return; + } + } + } + } + return; + } if (DfgSpliceArray* const splicep = srcp->cast()) { DfgVertex* const driverp = splicep->driverAt(idxp->toSizeT()); diff --git a/src/V3DfgPeepholePatterns.h b/src/V3DfgPeepholePatterns.h index 4da947664..52d5717fe 100644 --- a/src/V3DfgPeepholePatterns.h +++ b/src/V3DfgPeepholePatterns.h @@ -27,6 +27,7 @@ // Enumeration of each peephole optimization. Must be kept in sorted order (enforced by tests). // clang-format off #define FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION(macro) \ + _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ARRAYSEL_TABLE) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_LHS_OF_RHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_ASSOC_BINARY_RHS_OF_LHS) \ _FOR_EACH_DFG_PEEPHOLE_OPTIMIZATION_APPLY(macro, FOLD_BINARY) \ diff --git a/test_regress/t/t_dfg_peephole.v b/test_regress/t/t_dfg_peephole.v index 2d679a8de..98d9146be 100644 --- a/test_regress/t/t_dfg_peephole.v +++ b/test_regress/t/t_dfg_peephole.v @@ -50,6 +50,9 @@ module t ( assign unitArrayParts[0][1] = rand_a[1]; assign unitArrayParts[0][9] = rand_a[9]; + // Complicated way to write constant 0 that only Dfg can decipher + wire [63:0] convoluted_zero = (({64{rand_a[0]}} & ~{64{rand_a[0]}})); + `signal(FOLD_UNARY_LogNot, !const_a[0]); `signal(FOLD_UNARY_Negate, -const_a); `signal(FOLD_UNARY_Not, ~const_a); @@ -133,6 +136,13 @@ module t ( `signal(FOLD_SEL, const_a[3:1]); + int fold_arraysel_table_one; + ffs ffs_a(convoluted_zero[0] ? 8'hff: 8'd2, fold_arraysel_table_one); + int fold_arraysel_table_two; + ffs ffs_b(convoluted_zero[1] ? 8'hff: 8'd7, fold_arraysel_table_two); + `signal(FOLD_ARRAYSEL_TABLE_ONE, fold_arraysel_table_one); + `signal(FOLD_ARRAYSEL_TABLE_TWO, fold_arraysel_table_two); + `signal(SWAP_CONST_IN_COMMUTATIVE_BINARY, rand_a + const_a); `signal(SWAP_NOT_IN_COMMUTATIVE_BINARY, rand_a + ~rand_a); `signal(SWAP_VAR_IN_COMMUTATIVE_BINARY, rand_b + rand_a); @@ -427,3 +437,25 @@ module t ( assign zero = '0; assign ones = '1; endmodule + +module ffs( + input logic [7:0] i, + output int o +); + // V3Table will convert this + always_comb begin + // verilator lint_off CASEOVERLAP + casez (i) + 8'b1???????: o = 7; + 8'b?1??????: o = 6; + 8'b??1?????: o = 5; + 8'b???1????: o = 4; + 8'b????1???: o = 3; + 8'b?????1??: o = 2; + 8'b??????1?: o = 1; + 8'b???????1: o = 0; + 8'b00000000: o = -1; + endcase + // verilator lint_on CASEOVERLAP + end +endmodule From 64d680c9ba910a0f62576a47b3cc8147b0f4d6f1 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 13 Jun 2026 08:40:36 -0400 Subject: [PATCH 29/46] CI: Ubuntu 26.04 (#7776) --- .github/workflows/build-test.yml | 128 ++++++++++++++++++++----------- 1 file changed, 85 insertions(+), 43 deletions(-) diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 772fc1c95..d9fea2352 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -28,6 +28,38 @@ concurrency: jobs: + build-2604-gcc: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: linux + cc: ${{ matrix.cc }} + dev-asan: ${{ matrix.asan }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + - {os: ubuntu-26.04, cc: gcc, asan: 0} + + build-2604-clang: + name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} + uses: ./.github/workflows/reusable-build.yml + with: + sha: ${{ github.sha }} + os: ${{ matrix.os }} + os-name: linux + cc: ${{ matrix.cc }} + dev-asan: ${{ matrix.asan }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + - {os: ubuntu-26.04, cc: clang, asan: 1} + build-2404-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -58,7 +90,7 @@ jobs: fail-fast: false matrix: include: - - {os: ubuntu-24.04, cc: clang, asan: 1} + - {os: ubuntu-24.04, cc: clang, asan: 0} build-2204-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} @@ -76,22 +108,6 @@ jobs: include: - {os: ubuntu-22.04, cc: gcc, asan: 0} - build-2204-clang: - name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} - uses: ./.github/workflows/reusable-build.yml - with: - sha: ${{ github.sha }} - os: ${{ matrix.os }} - os-name: linux - cc: ${{ matrix.cc }} - dev-asan: ${{ matrix.asan }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - - {os: ubuntu-22.04, cc: clang, asan: 0} - build-osx-gcc: name: Build | ${{ matrix.os }} | ${{ matrix.cc }}${{ matrix.asan && ' | asan' || '' }} uses: ./.github/workflows/reusable-build.yml @@ -160,6 +176,54 @@ jobs: path: ${{ github.workspace }}/repo/verilator.zip name: verilator-win.zip + test-2604-gcc: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + needs: build-2604-gcc + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-gcc.outputs.archive }} + os: ${{ matrix.os }} + cc: ${{ matrix.cc }} + reloc: ${{ matrix.reloc }} + suite: ${{ matrix.suite }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + # Ubuntu 26.04 gcc + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-0} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-1} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-2} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: dist-vlt-3} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-0} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-1} + - {os: ubuntu-26.04, cc: gcc, reloc: 0, suite: vltmt-2} + + test-2604-clang: + name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} + needs: build-2604-clang + uses: ./.github/workflows/reusable-test.yml + with: + archive: ${{ needs.build-2604-clang.outputs.archive }} + os: ${{ matrix.os }} + cc: ${{ matrix.cc }} + reloc: ${{ matrix.reloc }} + suite: ${{ matrix.suite }} + dev-gcov: 0 + strategy: + fail-fast: false + matrix: + include: + # Ubuntu 26.04 clang + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-0} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-1} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-2} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: dist-vlt-3} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-0} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-1} + - {os: ubuntu-26.04, cc: clang, reloc: 0, suite: vltmt-2} + test-2404-gcc: name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} needs: build-2404-gcc @@ -232,30 +296,6 @@ jobs: - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-1} - {os: ubuntu-22.04, cc: gcc, reloc: 0, suite: vltmt-2} - test-2204-clang: - name: Test | ${{ matrix.os }} | ${{ matrix.cc }} | ${{ matrix.reloc && 'reloc | ' || '' }} ${{ matrix.suite }} - needs: build-2204-clang - uses: ./.github/workflows/reusable-test.yml - with: - archive: ${{ needs.build-2204-clang.outputs.archive }} - os: ${{ matrix.os }} - cc: ${{ matrix.cc }} - reloc: ${{ matrix.reloc }} - suite: ${{ matrix.suite }} - dev-gcov: 0 - strategy: - fail-fast: false - matrix: - include: - # Ubuntu 22.04 clang, also test relocation - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-2} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: dist-vlt-3} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-0} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-1} - - {os: ubuntu-22.04, cc: clang, reloc: 1, suite: vltmt-2} - lint-py: name: Lint Python uses: ./.github/workflows/reusable-lint-py.yml @@ -264,17 +304,19 @@ jobs: name: Test suite passed if: always() needs: + - build-2604-gcc + - build-2604-clang - build-2404-gcc - build-2404-clang - build-2204-gcc - - build-2204-clang - build-osx-gcc - build-osx-clang - build-windows + - build-2404-gcc + - build-2404-clang - test-2404-gcc - test-2404-clang - test-2204-gcc - - test-2204-clang - lint-py runs-on: ubuntu-24.04 From df1b1577d96c4c4af3c9cabe18077a2f126417a7 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sat, 13 Jun 2026 19:40:29 +0100 Subject: [PATCH 30/46] Deprecate isolate_assignments attribute (#7774) As per discussion. Remove the unsound V3SplitAs pass. The isolate_assignments attribute/directive is now parsed and ignored in the frontend for compatibility but otherwise have no effect. Fixes #7144 --- docs/guide/control.rst | 4 + docs/guide/extensions.rst | 4 + docs/guide/warnings.rst | 5 - src/CMakeLists.txt | 2 - src/Makefile_obj.in | 1 - src/V3AstAttr.h | 3 +- src/V3AstNodeOther.h | 9 - src/V3AstNodes.cpp | 2 - src/V3Control.cpp | 13 +- src/V3LinkDot.cpp | 1 - src/V3LinkParse.cpp | 4 - src/V3SchedAcyclic.cpp | 3 +- src/V3SplitAs.cpp | 195 -------------------- src/V3SplitAs.h | 32 ---- src/Verilator.cpp | 2 - src/verilog.y | 10 +- test_regress/t/t_unopt_combo_isolate.py | 35 +--- test_regress/t/t_unopt_combo_isolate_vlt.py | 36 +--- test_regress/t/t_unoptflat_simple_2_bad.out | 2 +- test_regress/t/t_vlt_syntax_bad.out | 33 ++-- test_regress/t/t_vlt_syntax_bad.vlt | 2 - 21 files changed, 44 insertions(+), 354 deletions(-) delete mode 100644 src/V3SplitAs.cpp delete mode 100644 src/V3SplitAs.h diff --git a/docs/guide/control.rst b/docs/guide/control.rst index a01396040..c26159d56 100644 --- a/docs/guide/control.rst +++ b/docs/guide/control.rst @@ -187,6 +187,10 @@ The grammar of control commands is as follows: .. option:: isolate_assignments -module "" [-task ""] -var "" + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used to indicate that the assignments to this signal in any blocks should be isolated into new blocks. Same as :option:`/*verilator&32;isolate_assignments*/` metacomment. diff --git a/docs/guide/extensions.rst b/docs/guide/extensions.rst index 0e944860e..4ea636af0 100644 --- a/docs/guide/extensions.rst +++ b/docs/guide/extensions.rst @@ -341,6 +341,10 @@ or "`ifdef`"'s may break other tools. .. option:: /*verilator&32;isolate_assignments*/ + Deprecated and has no effect (ignored). + + In versions before 5.050: + Used after a signal declaration to indicate the assignments to this signal in any blocks should be isolated into new blocks. When large combinatorial block results in a :option:`UNOPTFLAT` warning, attaching diff --git a/docs/guide/warnings.rst b/docs/guide/warnings.rst index d1818afd5..db41d8405 100644 --- a/docs/guide/warnings.rst +++ b/docs/guide/warnings.rst @@ -2366,11 +2366,6 @@ List Of Warnings the conflict. If you run with :vlopt:`--report-unoptflat`, Verilator will suggest possible candidates for :option:`/*verilator&32;split_var*/`. - The UNOPTFLAT warning may also occur where outputs from a block of logic - are independent, but occur in the same always block. To fix this, use - the :option:`/*verilator&32;isolate_assignments*/` metacomment described - above. - Before version 5.000, the UNOPTFLAT warning may also have been due to clock enables, identified from the reported path going through a clock gating instance. To fix these, the clock_enable meta comment was used. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b4f4be9aa..ba8f6a7b7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -175,7 +175,6 @@ set(HEADERS V3Simulate.h V3Slice.h V3Split.h - V3SplitAs.h V3SplitVar.h V3StackCount.h V3Stats.h @@ -350,7 +349,6 @@ set(COMMON_SOURCES V3Scoreboard.cpp V3Slice.cpp V3Split.cpp - V3SplitAs.cpp V3SplitVar.cpp V3StackCount.cpp V3Stats.cpp diff --git a/src/Makefile_obj.in b/src/Makefile_obj.in index dcd78a431..eb3b1d50a 100644 --- a/src/Makefile_obj.in +++ b/src/Makefile_obj.in @@ -333,7 +333,6 @@ RAW_OBJS_PCH_ASTNOMT = \ V3Scoreboard.o \ V3Slice.o \ V3Split.o \ - V3SplitAs.o \ V3SplitVar.o \ V3StackCount.o \ V3Subst.o \ diff --git a/src/V3AstAttr.h b/src/V3AstAttr.h index dd1754a9d..096923300 100644 --- a/src/V3AstAttr.h +++ b/src/V3AstAttr.h @@ -342,7 +342,6 @@ public: VAR_PUBLIC_FLAT, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RD, // V3LinkParse moves to AstVar::sigPublic VAR_PUBLIC_FLAT_RW, // V3LinkParse moves to AstVar::sigPublic - VAR_ISOLATE_ASSIGNMENTS, // V3LinkParse moves to AstVar::attrIsolateAssign VAR_SC_BIGUINT, // V3LinkParse moves to AstVar::attrScBigUint VAR_SC_BV, // V3LinkParse moves to AstVar::attrScBv VAR_SFORMAT, // V3LinkParse moves to AstVar::attrSFormat @@ -364,7 +363,7 @@ public: "TYPEID", "TYPENAME", "VAR_BASE", "VAR_FORCEABLE", "VAR_FSM_ARC_INCLUDE_COND", "VAR_FSM_RESET_ARC", "VAR_FSM_STATE", "VAR_PORT_DTYPE", "VAR_PUBLIC", "VAR_PUBLIC_FLAT", - "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_ISOLATE_ASSIGNMENTS", + "VAR_PUBLIC_FLAT_RD", "VAR_PUBLIC_FLAT_RW", "VAR_SC_BIGUINT", "VAR_SC_BV", "VAR_SFORMAT", "VAR_SPLIT_VAR" }; // clang-format on diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1e17d672c..1308818be 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -97,7 +97,6 @@ class AstNodeFTask VL_NOT_FINAL : public AstNode { string m_ifacePortName; // Interface port name for out-of-block definition (IEEE 25.8) uint64_t m_dpiOpenParent = 0; // DPI import open array, if !=0, how many callees bool m_taskPublic : 1; // Public task - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_classMethod : 1; // Class method bool m_didProto : 1; // Did prototype processing bool m_prototype : 1; // Just a prototype @@ -130,7 +129,6 @@ protected: : AstNode{t, fl} , m_name{name} , m_taskPublic{false} - , m_attrIsolateAssign{false} , m_classMethod{false} , m_didProto{false} , m_prototype{false} @@ -179,8 +177,6 @@ public: uint64_t dpiOpenParent() const { return m_dpiOpenParent; } bool taskPublic() const { return m_taskPublic; } void taskPublic(bool flag) { m_taskPublic = flag; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } bool classMethod() const { return m_classMethod; } void classMethod(bool flag) { m_classMethod = flag; } bool didProto() const { return m_didProto; } @@ -2134,7 +2130,6 @@ class AstVar final : public AstNode { bool m_funcReturn : 1; // Return variable for a function bool m_attrScBv : 1; // User force bit vector attribute bool m_attrScBigUint : 1; // User force sc_biguint attribute - bool m_attrIsolateAssign : 1; // User isolate_assignments attribute bool m_attrSFormat : 1; // User sformat attribute bool m_attrSplitVar : 1; // declared with split_var metacomment bool m_attrFsmState : 1; // declared with fsm_state metacomment @@ -2197,7 +2192,6 @@ class AstVar final : public AstNode { m_funcReturn = false; m_attrScBv = false; m_attrScBigUint = false; - m_attrIsolateAssign = false; m_attrSFormat = false; m_attrSplitVar = false; m_attrFsmState = false; @@ -2346,7 +2340,6 @@ public: void attrFileDescr(bool flag) { m_fileDescr = flag; } void attrScBv(bool flag) { m_attrScBv = flag; } void attrScBigUint(bool flag) { m_attrScBigUint = flag; } - void attrIsolateAssign(bool flag) { m_attrIsolateAssign = flag; } void attrSFormat(bool flag) { m_attrSFormat = flag; } void attrSplitVar(bool flag) { m_attrSplitVar = flag; } void attrFsmState(bool flag) { m_attrFsmState = flag; } @@ -2522,7 +2515,6 @@ public: bool attrFsmRegisterWrapper() const { return m_attrFsmRegisterWrapper; } bool attrFsmResetArc() const { return m_attrFsmResetArc; } bool attrFsmArcInclCond() const { return m_attrFsmArcInclCond; } - bool attrIsolateAssign() const { return m_attrIsolateAssign; } AstIface* sensIfacep() const { return m_sensIfacep; } VRandAttr rand() const { return m_rand; } string verilogKwd() const override; @@ -2534,7 +2526,6 @@ public: // This is getting connected to fromp; keep attributes // Note the method below too if (fromp->attrFileDescr()) attrFileDescr(true); - if (fromp->attrIsolateAssign()) attrIsolateAssign(true); if (fromp->isContinuously()) isContinuously(true); } void propagateWrapAttrFrom(const AstVar* fromp) { diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 746bd4595..2c93b3d58 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -3205,7 +3205,6 @@ void AstVar::dump(std::ostream& str) const { if (noReset()) str << " [!RST]"; if (processQueue()) str << " [PROCQ]"; if (sampled()) str << " [SAMPLED]"; - if (attrIsolateAssign()) str << " [aISO]"; if (attrFsmState()) str << " [aFSMSTATE]"; if (attrFsmResetArc()) str << " [aFSMRESETARC]"; if (attrFsmArcInclCond()) str << " [aFSMARCCOND]"; @@ -3240,7 +3239,6 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, noReset); dumpJsonBoolFuncIf(str, processQueue); dumpJsonBoolFuncIf(str, sampled); - dumpJsonBoolFuncIf(str, attrIsolateAssign); dumpJsonBoolFuncIf(str, attrFsmState); dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); diff --git a/src/V3Control.cpp b/src/V3Control.cpp index e95e3c086..3ac7aa727 100644 --- a/src/V3Control.cpp +++ b/src/V3Control.cpp @@ -182,7 +182,6 @@ class V3ControlFTask final { V3ControlVarResolver m_params; // Parameters in function/task V3ControlVarResolver m_ports; // Ports in function/task V3ControlVarResolver m_vars; // Variables in function/task - bool m_isolate = false; // Isolate function return bool m_noinline = false; // Don't inline function/task bool m_public = false; // Public function/task @@ -190,7 +189,6 @@ public: V3ControlFTask() = default; void update(const V3ControlFTask& f) { // Don't overwrite true with false - if (f.m_isolate) m_isolate = true; if (f.m_noinline) m_noinline = true; if (f.m_public) m_public = true; m_params.update(f.m_params); @@ -203,7 +201,6 @@ public: V3ControlVarResolver& ports() { return m_ports; } V3ControlVarResolver& vars() { return m_vars; } - void setIsolate(bool set) { m_isolate = set; } void setNoInline(bool set) { m_noinline = set; } void setPublic(bool set) { m_public = set; } @@ -212,8 +209,6 @@ public: ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::NO_INLINE_TASK}); if (m_public) ftaskp->addStmtsp(new AstPragma{ftaskp->fileline(), VPragmaType::PUBLIC_TASK}); - // Only functions can have isolate (return value) - if (VN_IS(ftaskp, Func)) ftaskp->attrIsolateAssign(m_isolate); } }; @@ -981,13 +976,7 @@ void V3Control::addVarAttr(FileLine* fl, const string& module, const string& fta // Semantics: Most of the attributes operate on signals if (pattern.empty()) { - if (attr == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - if (ftask.empty()) { - fl->v3error("isolate_assignments only applies to signals or functions/tasks"); - } else { - V3ControlResolver::s().modules().at(module).ftasks().at(ftask).setIsolate(true); - } - } else if (attr == VAttrType::VAR_PUBLIC) { + if (attr == VAttrType::VAR_PUBLIC) { if (ftask.empty()) { // public module, this is the only exception from var here V3ControlResolver::s().modules().at(module).addModulePragma( diff --git a/src/V3LinkDot.cpp b/src/V3LinkDot.cpp index a04b7bbdf..1ada078ba 100644 --- a/src/V3LinkDot.cpp +++ b/src/V3LinkDot.cpp @@ -1727,7 +1727,6 @@ class LinkDotFindVisitor final : public VNVisitor { newvarp->lifetime(VLifetime::AUTOMATIC_EXPLICIT); newvarp->funcReturn(true); newvarp->trace(false); // Not user visible - newvarp->attrIsolateAssign(nodep->attrIsolateAssign()); nodep->fvarp(newvarp); // Explicit insert required, as the var name shadows the upper level's task name m_statep->insertSym(m_curSymp, newvarp->name(), newvarp, nullptr /*classOrPackagep*/); diff --git a/src/V3LinkParse.cpp b/src/V3LinkParse.cpp index e6beb5760..5403d77c2 100644 --- a/src/V3LinkParse.cpp +++ b/src/V3LinkParse.cpp @@ -606,10 +606,6 @@ class LinkParseVisitor final : public VNVisitor { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->sigUserRWPublic(true); VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); - } else if (nodep->attrType() == VAttrType::VAR_ISOLATE_ASSIGNMENTS) { - UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); - m_varp->attrIsolateAssign(true); - VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep); } else if (nodep->attrType() == VAttrType::VAR_SFORMAT) { UASSERT_OBJ(m_varp, nodep, "Attribute not attached to variable"); m_varp->attrSFormat(true); diff --git a/src/V3SchedAcyclic.cpp b/src/V3SchedAcyclic.cpp index 36a9cd096..ad8ba454e 100644 --- a/src/V3SchedAcyclic.cpp +++ b/src/V3SchedAcyclic.cpp @@ -351,8 +351,7 @@ std::string reportLoopVars(FileLine* /*warnFl*/, Graph* graphp, SchedAcyclicVarV if (splittable) { ss << V3Error::warnMore() - << "... Suggest add /*verilator split_var*/ or /*verilator " - "isolate_assignments*/ to appropriate variables above.\n"; + << "... Suggest add /*verilator split_var*/ to appropriate variables above.\n"; } V3Stats::addStat("Scheduling, split_var, candidates", splittable); return ss.str(); diff --git a/src/V3SplitAs.cpp b/src/V3SplitAs.cpp deleted file mode 100644 index 74b72a4dd..000000000 --- a/src/V3SplitAs.cpp +++ /dev/null @@ -1,195 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// 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: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* -// V3SplitAs's Transformations: -// -// Search each ALWAYS for a VARREF lvalue with a /*isolate_assignments*/ attribute -// If found, color statements with both, assignment to that varref, or other assignments. -// Replicate the Always, and remove mis-colored duplicate code. -// -//************************************************************************* - -#include "V3PchAstNoMT.h" // VL_MT_DISABLED_CODE_UNIT - -#include "V3SplitAs.h" - -#include "V3Stats.h" - -VL_DEFINE_DEBUG_FUNCTIONS; - -//###################################################################### -// Find all split variables in a block - -class SplitAsFindVisitor final : public VNVisitorConst { - // STATE - across all visitors - AstVarScope* m_splitVscp = nullptr; // Variable we want to split - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW() && !m_splitVscp && nodep->varp()->attrIsolateAssign()) { - m_splitVscp = nodep->varScopep(); - } - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildrenConst(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsFindVisitor(AstAlways* nodep) { iterateConst(nodep); } - ~SplitAsFindVisitor() override = default; - // METHODS - AstVarScope* splitVscp() const { return m_splitVscp; } -}; - -//###################################################################### -// Remove nodes not containing proper references - -class SplitAsCleanVisitor final : public VNVisitor { - // STATE - across all visitors - const AstVarScope* const m_splitVscp; // Variable we want to split - const bool m_modeMatch; // Remove matching Vscp, else non-matching - // STATE - for current visit position (use VL_RESTORER) - bool m_keepStmt = false; // Current Statement must be preserved - bool m_matches = false; // Statement below has matching lvalue reference - - // METHODS - void visit(AstVarRef* nodep) override { - if (nodep->access().isWriteOrRW()) { - if (nodep->varScopep() == m_splitVscp) { - UINFO(6, " CL VAR " << nodep); - m_matches = true; - } - } - } - void visit(AstNodeStmt* nodep) override { - UINFO(6, " CL STMT " << nodep); - const bool oldKeep = m_keepStmt; - { - VL_RESTORER(m_matches); - m_matches = false; - m_keepStmt = false; - - iterateChildren(nodep); - - if (m_keepStmt || (m_modeMatch ? m_matches : !m_matches)) { - UINFO(6, " Keep STMT " << nodep); - m_keepStmt = true; - } else { - UINFO(6, " Delete STMT " << nodep); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - } - } - // If something below matches, the upper statement remains too. - m_keepStmt = oldKeep || m_keepStmt; - UINFO(9, " upKeep=" << m_keepStmt << " STMT " << nodep); - } - void visit(AstExprStmt* nodep) override { - // A function call inside the splitting assignment - // We need to presume the whole call is preserved (if the upper statement is) - // This will break if the m_splitVscp is a "ref" argument to the function, - // but little we can do. - } - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - SplitAsCleanVisitor(AstAlways* nodep, AstVarScope* vscp, bool modeMatch) - : m_splitVscp{vscp} - , m_modeMatch{modeMatch} { - iterate(nodep); - } - ~SplitAsCleanVisitor() override = default; -}; - -//###################################################################### -// SplitAs class functions - -class SplitAsVisitor final : public VNVisitor { - // NODE STATE - // AstAlways::user() -> bool. True if already processed - const VNUser1InUse m_inuser1; - - // STATE - across all visitors - VDouble0 m_statSplits; // Statistic tracking - - // METHODS - void splitAlways(AstAlways* nodep, AstVarScope* splitVscp) { - UINFOTREE(9, nodep, "", "in"); - // Duplicate it and link in - // Below cloneTree should perhaps be cloneTreePure, but given - // isolate_assignments is required to hit this code, we presume the user - // knows what they are asking for - AstAlways* const newp = nodep->cloneTree(false); - newp->user1(true); // So we don't clone it again - nodep->addNextHere(newp); - { // Delete stuff we don't want in old - const SplitAsCleanVisitor visitor{nodep, splitVscp, false}; - UINFOTREE(9, nodep, "", "out0"); - } - { // Delete stuff we don't want in new - const SplitAsCleanVisitor visitor{newp, splitVscp, true}; - UINFOTREE(9, newp, "", "out1"); - } - } - - void visit(AstAlways* nodep) override { - // Are there any lvalue references below this? - // There could be more than one. So, we process the first one found first. - const AstVarScope* lastSplitVscp = nullptr; - while (!nodep->user1()) { - // Find any splittable variables - const SplitAsFindVisitor visitor{nodep}; - AstVarScope* const splitVscp = visitor.splitVscp(); - // Now isolate the always - if (splitVscp) { - UINFO(3, "Split " << nodep); - UINFO(3, " For " << splitVscp); - // If we did this last time! Something's stuck! - UASSERT_OBJ(splitVscp != lastSplitVscp, nodep, - "Infinite loop in isolate_assignments removal for: " - << splitVscp->prettyNameQ()); - lastSplitVscp = splitVscp; - splitAlways(nodep, splitVscp); - ++m_statSplits; - } else { - nodep->user1(true); - } - } - } - - void visit(AstNodeExpr*) override {} // Accelerate - void visit(AstNode* nodep) override { iterateChildren(nodep); } - -public: - // CONSTRUCTORS - explicit SplitAsVisitor(AstNetlist* nodep) { iterate(nodep); } - ~SplitAsVisitor() override { - V3Stats::addStat("Optimizations, isolate_assignments blocks", m_statSplits); - } -}; - -//###################################################################### -// SplitAs class functions - -void V3SplitAs::splitAsAll(AstNetlist* nodep) { - UINFO(2, __FUNCTION__ << ":"); - { SplitAsVisitor{nodep}; } // Destruct before checking - V3Global::dumpCheckGlobalTree("splitas", 0, dumpTreeEitherLevel() >= 3); -} diff --git a/src/V3SplitAs.h b/src/V3SplitAs.h deleted file mode 100644 index dc5c4cce6..000000000 --- a/src/V3SplitAs.h +++ /dev/null @@ -1,32 +0,0 @@ -// -*- mode: C++; c-file-style: "cc-mode" -*- -//************************************************************************* -// DESCRIPTION: Verilator: Break always into separate statements to reduce temps -// -// Code available from: https://verilator.org -// -//************************************************************************* -// -// 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: 2003-2026 Wilson Snyder -// SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 -// -//************************************************************************* - -#ifndef VERILATOR_V3SPLITAS_H_ -#define VERILATOR_V3SPLITAS_H_ - -#include "config_build.h" -#include "verilatedos.h" - -class AstNetlist; - -//============================================================================ - -class V3SplitAs final { -public: - static void splitAsAll(AstNetlist* nodep) VL_MT_DISABLED; -}; - -#endif // Guard diff --git a/src/Verilator.cpp b/src/Verilator.cpp index c30f8b28c..b37336a52 100644 --- a/src/Verilator.cpp +++ b/src/Verilator.cpp @@ -98,7 +98,6 @@ #include "V3Scoreboard.h" #include "V3Slice.h" #include "V3Split.h" -#include "V3SplitAs.h" #include "V3SplitVar.h" #include "V3Stats.h" #include "V3String.h" @@ -421,7 +420,6 @@ static void process() { // Split single ALWAYS blocks into multiple blocks for better ordering chances if (v3Global.opt.fSplit()) V3Split::splitAll(v3Global.rootp()); - V3SplitAs::splitAsAll(v3Global.rootp()); // Create tracing sample points, before we start eliminating signals if (v3Global.opt.trace()) V3TraceDecl::traceDeclAll(v3Global.rootp()); diff --git a/src/verilog.y b/src/verilog.y index 510545736..d4392a5bc 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -3121,7 +3121,7 @@ sigAttr: | yVL_PUBLIC_FLAT { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RD { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RD}; v3Global.dpi(true); } | yVL_PUBLIC_FLAT_RW attr_event_controlE { $$ = new AstAttrOf{$1, VAttrType::VAR_PUBLIC_FLAT_RW}; v3Global.dpi(true); DEL($2); } - | yVL_ISOLATE_ASSIGNMENTS { $$ = new AstAttrOf{$1, VAttrType::VAR_ISOLATE_ASSIGNMENTS}; } + | yVL_ISOLATE_ASSIGNMENTS { $$ = nullptr; /* Historical, now has no effect */ } | yVL_SC_BIGUINT { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BIGUINT}; } | yVL_SC_BV { $$ = new AstAttrOf{$1, VAttrType::VAR_SC_BV}; } | yVL_SFORMAT { $$ = new AstAttrOf{$1, VAttrType::VAR_SFORMAT}; } @@ -4657,12 +4657,12 @@ task_prototype: // ==IEEE: task_prototype function_declaration: // IEEE: function_declaration + function_body_declaration yFUNCTION dynamic_override_specifiersE lifetimeE funcId funcIsolateE tfGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } | yFUNCTION dynamic_override_specifiersE lifetimeE funcIdNew funcIsolateE tfNewGuts yENDFUNCTION endLabelE - { $$ = $4; $4->attrIsolateAssign($5); $$->addStmtsp($6); + { $$ = $4; $$->addStmtsp($6); $$->baseOverride($2); $$->lifetime($3); GRAMMARP->endLabel($8, $$, $8); } @@ -8553,8 +8553,7 @@ vltVarAttrSpecE: ; vltVarAttrFront: - yVLT_ISOLATE_ASSIGNMENTS { $$ = VAttrType::VAR_ISOLATE_ASSIGNMENTS; } - | yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } + yVLT_FORCEABLE { $$ = VAttrType::VAR_FORCEABLE; } | yVLT_PUBLIC { $$ = VAttrType::VAR_PUBLIC; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT { $$ = VAttrType::VAR_PUBLIC_FLAT; v3Global.dpi(true); } | yVLT_PUBLIC_FLAT_RD { $$ = VAttrType::VAR_PUBLIC_FLAT_RD; v3Global.dpi(true); } @@ -8568,6 +8567,7 @@ vltVarAttrFront: vltVarAttrFrontDeprecated: yVLT_CLOCK_ENABLE { } | yVLT_CLOCKER { } + | yVLT_ISOLATE_ASSIGNMENTS { } | yVLT_NO_CLOCKER { } ; diff --git a/test_regress/t/t_unopt_combo_isolate.py b/test_regress/t/t_unopt_combo_isolate.py index f7b4618d3..3ffd80545 100755 --- a/test_regress/t/t_unopt_combo_isolate.py +++ b/test_regress/t/t_unopt_combo_isolate.py @@ -9,37 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "+define+ISOLATE", "--stats", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,99:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,100:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,112:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,113:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=["+define+ISOLATE"]) test.execute() diff --git a/test_regress/t/t_unopt_combo_isolate_vlt.py b/test_regress/t/t_unopt_combo_isolate_vlt.py index 35e0cc8d2..1a041dac0 100755 --- a/test_regress/t/t_unopt_combo_isolate_vlt.py +++ b/test_regress/t/t_unopt_combo_isolate_vlt.py @@ -9,38 +9,14 @@ import vltest_bootstrap -test.scenarios('simulator') +# Note: This test is historical for the isolate_assignments attribute, which +# was deprecated and has no effect today. This test ensures it still parses +# in SystemVerilog and Verilator control files for backward compatibility. + +test.scenarios('vlt') test.top_filename = "t/t_unopt_combo.v" -out_filename = test.obj_dir + "/V" + test.name + ".tree.json" - -test.compile(verilator_flags2=[ - "--no-json-edit-nums", "--stats", test.t_dir + - "/t_unopt_combo_isolate.vlt", "-fno-dfg", "-fno-lift-expr" -]) - -if test.vlt_all: - test.file_grep(test.stats, r'Optimizations, isolate_assignments blocks\s+4') - test.file_grep( - out_filename, - r'{"type":"VAR","name":"t.b",.*"loc":"\w,23:[^"]*",.*"origName":"b",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__Vfuncout",.*"loc":"\w,104:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__Vfuncout",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vfunc_t.file.get_31_16__0__t_crc",.*"loc":"\w,105:[^"]*",.*"origName":"__Vfunc_t__DOT__file__DOT__get_31_16__0__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_crc",.*"loc":"\w,115:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_crc",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) - test.file_grep( - out_filename, - r'{"type":"VAR","name":"__Vtask_t.file.set_b_d__1__t_c",.*"loc":"\w,116:[^"]*",.*"origName":"__Vtask_t__DOT__file__DOT__set_b_d__1__t_c",.*"attrIsolateAssign":true,.*"dtypeName":"logic"' - ) +test.compile(verilator_flags2=[test.t_dir + "/t_unopt_combo_isolate.vlt"]) test.execute() diff --git a/test_regress/t/t_unoptflat_simple_2_bad.out b/test_regress/t/t_unoptflat_simple_2_bad.out index 8588ac55c..4c8793b04 100644 --- a/test_regress/t/t_unoptflat_simple_2_bad.out +++ b/test_regress/t/t_unoptflat_simple_2_bad.out @@ -10,5 +10,5 @@ t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var ... Candidates with the highest fanout: t/t_unoptflat_simple_2.v:16:14: t.x, width 3, circular fanout 2, can split_var - ... Suggest add /*verilator split_var*/ or /*verilator isolate_assignments*/ to appropriate variables above. + ... Suggest add /*verilator split_var*/ to appropriate variables above. %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.out b/test_regress/t/t_vlt_syntax_bad.out index f1ad706be..65a3941ea 100644 --- a/test_regress/t/t_vlt_syntax_bad.out +++ b/test_regress/t/t_vlt_syntax_bad.out @@ -2,28 +2,25 @@ 9 | public -module "t" @(posedge clk) | ^ ... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance. -%Error: t/t_vlt_syntax_bad.vlt:11:1: isolate_assignments only applies to signals or functions/tasks - 11 | isolate_assignments -module "t" - | ^~~~~~~~~~~~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -match only supported for lint_off - 13 | tracing_off --file "*" -match "nothing" +%Error: t/t_vlt_syntax_bad.vlt:11:1: Argument -match only supported for lint_off + 11 | tracing_off --file "*" -match "nothing" | ^~~~~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:13:1: Argument -scope only supported for tracing_on/off + 13 | lint_off --rule UNOPTFLAT -scope "top*" + | ^~~~~~~~ +%Error: t/t_vlt_syntax_bad.vlt:14:1: Argument -scope only supported for tracing_on/off_off + 14 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 + | ^~~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:15:1: Argument -scope only supported for tracing_on/off - 15 | lint_off --rule UNOPTFLAT -scope "top*" - | ^~~~~~~~ + 15 | lint_on --rule UNOPTFLAT -scope "top*" + | ^~~~~~~ %Error: t/t_vlt_syntax_bad.vlt:16:1: Argument -scope only supported for tracing_on/off_off - 16 | lint_off --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:17:1: Argument -scope only supported for tracing_on/off - 17 | lint_on --rule UNOPTFLAT -scope "top*" + 16 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:18:1: Argument -scope only supported for tracing_on/off_off - 18 | lint_on --rule UNOPTFLAT -scope "top*" -levels 0 - | ^~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:20:1: forceable missing -module - 20 | forceable -module "" -var "net_*" +%Error: t/t_vlt_syntax_bad.vlt:18:1: forceable missing -module + 18 | forceable -module "" -var "net_*" | ^~~~~~~~~ -%Error: t/t_vlt_syntax_bad.vlt:22:1: missing -var - 22 | forceable -module "top" -var "" +%Error: t/t_vlt_syntax_bad.vlt:20:1: missing -var + 20 | forceable -module "top" -var "" | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_vlt_syntax_bad.vlt b/test_regress/t/t_vlt_syntax_bad.vlt index 2ebcefa7c..0a5cdbce0 100644 --- a/test_regress/t/t_vlt_syntax_bad.vlt +++ b/test_regress/t/t_vlt_syntax_bad.vlt @@ -7,8 +7,6 @@ `verilator_config public -module "t" @(posedge clk) -// only signals/functions/tasks -isolate_assignments -module "t" // -match not supported tracing_off --file "*" -match "nothing" // -scope not supported From 44bd8a0c14c44c78f6b073bfc432a9f655f2d529 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 13 Jun 2026 22:07:14 -0400 Subject: [PATCH 31/46] Commentary: Changes update --- Changes | 29 ++++- docs/spelling.txt | 1 + test_regress/t/t_case_inside_with_x.v | 20 +++- test_regress/t/t_case_priority_overlap.v | 5 +- test_regress/t/t_constraint_redops.v | 14 +-- test_regress/t/t_cover_fsm_concat_unsup.v | 6 +- test_regress/t/t_cover_fsm_sel.out | 22 ++-- test_regress/t/t_cover_fsm_sel.v | 22 ++-- test_regress/t/t_cover_fsm_sel_assign.out | 21 ++-- test_regress/t/t_cover_fsm_sel_assign.v | 21 ++-- .../t/t_cover_fsm_sel_togglevar_unsup.out | 12 +- .../t/t_cover_fsm_sel_togglevar_unsup.v | 25 ++-- test_regress/t/t_covergroup_param_bins.v | 6 +- test_regress/t/t_disable_fork_nested.v | 113 +++++++++--------- test_regress/t/t_fsm_duplicate.v | 62 ++++------ .../t/t_property_s_eventually_iface_param.v | 10 +- test_regress/t/t_select_bound_side_effect.v | 12 +- test_regress/t/t_stream_unpacked_struct.v | 3 +- 18 files changed, 217 insertions(+), 187 deletions(-) diff --git a/Changes b/Changes index fcb769eae..ced84ea4e 100644 --- a/Changes +++ b/Changes @@ -15,7 +15,7 @@ Verilator 5.049 devel **Important:** -* Support covergroups, coverpoints, and bins (#784) (#7117). [Matthew Ballance] +* Support covergroups, coverpoints, and bins (#784) (#7117) (#7728). [Matthew Ballance] * Support new FST writer API (#6871) (#6992). [Yu-Sheng Lin] Use of FST may requiring installing liblz4 and/or liblz4-dev packages, see docs/install.rst. @@ -27,9 +27,11 @@ Verilator 5.049 devel * Add `--coverage-per-instance` (#7636). [Yogish Sekhar] * Add NOTREDOP error on reduction and negation operators (#7417) (#7623) (#7624). * Add hierarchy-aware reporting to `verilator_coverage` (#7657). [Yogish Sekhar] +* Deprecate isolate_assignments attribute (#7774) (#7144). [Geza Lore, Testorrent USA, Inc.] * Improve `--coverage-fsm` (#7490) (#7529) (#7561) (#7573) (#7619). [Yogish Sekhar] * Change `+verilator+seed` to default to 1, and 0 to randomly select (#7325) (#7516). [Miguel] * Change JSON to include parameter constant mnemonics for FSM Coverage (#7531). [Yogish Sekhar] +* Support assert property 'default disable iff` (#4848) (#7723). [Artur Bieniek, Antmicro Ltd.] * Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527). [Nick Brereton] * Support weak `until` / `until_with` property operators (#7290) (#7548) (#7685). [Yilou Wang] * Support `s_eventually` (#7291) (#7508). [Bartłomiej Chmiel, Antmicro Ltd.] @@ -59,6 +61,12 @@ Verilator 5.049 devel * Support if/if-else in properties (#7692). [Artur Bieniek, Antmicro Ltd.] * Support process::self().srand() (#7695). [Igor Zaworski, Antmicro Ltd.] * Support MacOS lldb (#7697). [Tracy Narine] +* Support assoc array methods with wide value types (#7680). [pawelktk] +* Support property case (#7721). [Artur Bieniek, Antmicro Ltd.] +* Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] +* Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] +* Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support unpacked struct stream (#7767). [Nick Brereton] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] * Optimize forced signal handling (#7554 partial) (#7572) (#7594) (#7596). [Krzysztof Bieganski, Artur Bieniek, Antmicro Ltd.] @@ -71,8 +79,16 @@ Verilator 5.049 devel * Optimize runtime assertOn() checks (#7707). [Geza Lore, Testorrent USA, Inc.] * Optimize $countones and $onehot in DFG. [Geza Lore, Testorrent USA, Inc.] * Optimize procedural loop unrolling. [Geza Lore, Testorrent USA, Inc.] +* Optimize V3Gate inlining heuristic (#7716). [Geza Lore, Testorrent USA, Inc.] +* Optimize reset in DFG (#7737). [Geza Lore, Testorrent USA, Inc.] +* Optimize DFG with relaxed live variable analysis (#7739). [Geza Lore, Testorrent USA, Inc.] +* Optimize conditional patterns sharing common MBSs/LSBs in DfgPeephole (#7760). [Geza Lore, Testorrent USA, Inc.] +* Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] +* Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] +* Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] +* Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] * Fix segmentation fault when using --trace with --lib-create (#7299) (#7518). [anonkey] * Fix destructive event state before dynamic waits (#7340). [Nick Brereton] * Fix ALWCOMBORDER on variable ordering (#7350) (#7608). [Cookie] @@ -128,6 +144,7 @@ Verilator 5.049 devel * Fix loss of events due to bit shift (#7670). [Artur Bieniek, Antmicro Ltd.] * Fix parameter read through locally-declared interface instance (#7679). [Nick Brereton] * Fix skipping nulls in $sscanf (#7689). +* Fix bounds checks in expressions with read/write references (#7694). [Ryszard Rozak, Antmicro Ltd.] * Fix (const) ref default task argument handling (#7698). [Nick Brereton] * Fix `ref` argument type check for packed arrays with differing range directions (#7700). [Nick Brereton] * Fix ignoring not-found modules with encoded names (#7706). [Igor Zaworski, Antmicro Ltd.] @@ -135,6 +152,16 @@ Verilator 5.049 devel * Fix Makefile action to not write to ${srcdir} (#7715). [Larry Doolittle] * Fix splitting functions containing fork logic (#7717). [Mateusz Gancarz, Antmicro Ltd.] * Fix optimizations of assignments with timing controls (#7718). [Ryszard Rozak, Antmicro Ltd.] +* Fix s_eventually on interface (#7731) (#7733). [Marco Bartoli] +* Fix parameter values in coverage bins widths (#7732) (#7734). [Marco Bartoli] +* Fix configure fall back on dynamic malloc libraries (#7736). [Geza Lore, Testorrent USA, Inc.] +* Fix crash on overlapping priority case. [Geza Lore, Testorrent USA, Inc.] +* Fix s_eventually in parameterized interfaces (#7741). [Nick Brereton] +* Fix dpi export pointers (#7742) (#7751). [Yilin Li] +* Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] +* Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] +* Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] + Verilator 5.048 2026-04-26 diff --git a/docs/spelling.txt b/docs/spelling.txt index a3565cb1d..c41825793 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -924,6 +924,7 @@ localparams localtime logicals longint +lookups lossy lsb lubc diff --git a/test_regress/t/t_case_inside_with_x.v b/test_regress/t/t_case_inside_with_x.v index 51a456c7a..935d779e8 100644 --- a/test_regress/t/t_case_inside_with_x.v +++ b/test_regress/t/t_case_inside_with_x.v @@ -18,12 +18,24 @@ module top; always @(posedge clk) begin // verilator lint_off CASEWITHX case (cyc) inside - 3'b000: begin $display("case inside 000"); ++count; end - 3'b001: begin $display("case inside 001"); ++count; end + 3'b000: begin + $display("case inside 000"); + ++count; + end + 3'b001: begin + $display("case inside 001"); + ++count; + end // Should match z - 3'b01?: begin $display("case inside 01?"); ++count; end + 3'b01?: begin + $display("case inside 01?"); + ++count; + end // Should match x - 3'b1xx: begin $display("case inside 1xx"); ++count; end + 3'b1xx: begin + $display("case inside 1xx"); + ++count; + end endcase // verilator lint_on CASEWITHX cyc <= cyc + 3'd1; diff --git a/test_regress/t/t_case_priority_overlap.v b/test_regress/t/t_case_priority_overlap.v index 2de27080a..9b7e794d6 100644 --- a/test_regress/t/t_case_priority_overlap.v +++ b/test_regress/t/t_case_priority_overlap.v @@ -26,8 +26,9 @@ module t; always_comb begin priority casez (in) - 2'b1?, // fully subsumes 2'b11 below on the same case clause - 2'b11: out = 2'b10; + 2'b1?, // fully subsumes 2'b11 below on the same case clause + 2'b11: + out = 2'b10; 2'b0?: out = 2'b01; endcase end diff --git a/test_regress/t/t_constraint_redops.v b/test_regress/t/t_constraint_redops.v index e285f8e24..5eff12a63 100644 --- a/test_regress/t/t_constraint_redops.v +++ b/test_regress/t/t_constraint_redops.v @@ -83,13 +83,13 @@ class test_redops_bitfields #(RANDVAL_BITWIDTH=8); endclass module t; - test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; - test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(1)) redops_1bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(8)) redops_8bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(16)) redops_16bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(32)) redops_32bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(47)) redops_47bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(63)) redops_63bit; + test_redops_bitfields #(.RANDVAL_BITWIDTH(64)) redops_64bit; test_redops_bitfields #(.RANDVAL_BITWIDTH(128)) redops_128bit; initial begin diff --git a/test_regress/t/t_cover_fsm_concat_unsup.v b/test_regress/t/t_cover_fsm_concat_unsup.v index 1f9cee77a..a7d9a1f84 100644 --- a/test_regress/t/t_cover_fsm_concat_unsup.v +++ b/test_regress/t/t_cover_fsm_concat_unsup.v @@ -5,9 +5,9 @@ // SPDX-License-Identifier: CC0-1.0 module t ( - input logic[6:0] a, - input logic b, - output logic c + input logic [6:0] a, + input logic b, + output logic c ); assign c = ({a, b} == 8'h00); diff --git a/test_regress/t/t_cover_fsm_sel.out b/test_regress/t/t_cover_fsm_sel.out index 4913f5502..a87e0569d 100644 --- a/test_regress/t/t_cover_fsm_sel.out +++ b/test_regress/t/t_cover_fsm_sel.out @@ -6,19 +6,16 @@ // SPDX-License-Identifier: CC0-1.0 package P; - typedef struct packed{ - logic [7:0] vs; - } C; - typedef struct packed{ - C a; int b; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; } B; - typedef struct packed{ - B a; - } A; + typedef struct packed {B a;} A; endpackage module t ( -%000009 input clk +%000009 input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -84,9 +81,10 @@ %000003 a.a.a.vs <= a.a.a.vs + 1; %000003 done <= (a.a.a.vs == 8'h1); %000002 if (done) begin -%000001 state <= S_DONE; -%000002 end else begin -%000002 state <= S_RUN; +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; end end %000002 S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel.v b/test_regress/t/t_cover_fsm_sel.v index a06275b03..732a63553 100644 --- a/test_regress/t/t_cover_fsm_sel.v +++ b/test_regress/t/t_cover_fsm_sel.v @@ -5,19 +5,16 @@ // SPDX-License-Identifier: CC0-1.0 package P; - typedef struct packed{ - logic [7:0] vs; - } C; - typedef struct packed{ - C a; int b; + typedef struct packed {logic [7:0] vs;} C; + typedef struct packed { + C a; + int b; } B; - typedef struct packed{ - B a; - } A; + typedef struct packed {B a;} A; endpackage module t ( - input clk + input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -74,9 +71,10 @@ module t ( a.a.a.vs <= a.a.a.vs + 1; done <= (a.a.a.vs == 8'h1); if (done) begin - state <= S_DONE; - end else begin - state <= S_RUN; + state <= S_DONE; + end + else begin + state <= S_RUN; end end S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel_assign.out b/test_regress/t/t_cover_fsm_sel_assign.out index c033ed2ef..1f0c3983e 100644 --- a/test_regress/t/t_cover_fsm_sel_assign.out +++ b/test_regress/t/t_cover_fsm_sel_assign.out @@ -6,11 +6,11 @@ // SPDX-License-Identifier: CC0-1.0 module t #( - parameter int unsigned W = 16, - parameter int unsigned D = 4, - parameter int unsigned BW = 2 + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 ) ( -%000009 input clk +%000009 input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -30,8 +30,7 @@ begin %000001 logic [D-1:0][W-1:0] s; begin -%000009 always_ff @(posedge clk) -%000009 s[b] <= a; +%000009 always_ff @(posedge clk) s[b] <= a; end end @@ -71,12 +70,14 @@ %000002 S_IDLE: %000001 if (start) state <= S_RUN; %000001 else state <= S_IDLE; -%000003 S_RUN: begin; +%000003 S_RUN: begin + ; %000003 done_arr[0] <= (a[0] == 1'b1); %000002 if (done_arr[0]) begin -%000001 state <= S_DONE; -%000002 end else begin -%000002 state <= S_RUN; +%000001 state <= S_DONE; + end +%000002 else begin +%000002 state <= S_RUN; end end %000002 S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel_assign.v b/test_regress/t/t_cover_fsm_sel_assign.v index 293fa4692..9f3ecca24 100644 --- a/test_regress/t/t_cover_fsm_sel_assign.v +++ b/test_regress/t/t_cover_fsm_sel_assign.v @@ -5,11 +5,11 @@ // SPDX-License-Identifier: CC0-1.0 module t #( - parameter int unsigned W = 16, - parameter int unsigned D = 4, - parameter int unsigned BW = 2 + parameter int unsigned W = 16, + parameter int unsigned D = 4, + parameter int unsigned BW = 2 ) ( - input clk + input clk ); typedef enum logic [1:0] { S_IDLE = 2'd0, @@ -29,8 +29,7 @@ module t #( begin logic [D-1:0][W-1:0] s; begin - always_ff @(posedge clk) - s[b] <= a; + always_ff @(posedge clk) s[b] <= a; end end @@ -61,12 +60,14 @@ module t #( S_IDLE: if (start) state <= S_RUN; else state <= S_IDLE; - S_RUN: begin; + S_RUN: begin + ; done_arr[0] <= (a[0] == 1'b1); if (done_arr[0]) begin - state <= S_DONE; - end else begin - state <= S_RUN; + state <= S_DONE; + end + else begin + state <= S_RUN; end end S_DONE: state <= S_DONE; diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out index 9883741c3..4b76f7298 100644 --- a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.out @@ -1,11 +1,11 @@ -%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type ASSOCARRAYDTYPE +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type ASSOCARRAYDTYPE : ... note: In instance 't' - 20 | input P::A a, - | ^ + 19 | input P::A a, + | ^ ... For warning description see https://verilator.org/warn/COVERIGN?v=latest ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:20:14: Coverage ignored for type WILDCARDARRAYDTYPE +%Warning-COVERIGN: t/t_cover_fsm_sel_togglevar_unsup.v:19:16: Coverage ignored for type WILDCARDARRAYDTYPE : ... note: In instance 't' - 20 | input P::A a, - | ^ + 19 | input P::A a, + | ^ %Error: Exiting due to diff --git a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v index 547a14d85..9be3e9a7a 100644 --- a/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v +++ b/test_regress/t/t_cover_fsm_sel_togglevar_unsup.v @@ -5,21 +5,20 @@ // SPDX-License-Identifier: CC0-1.0 package P; - typedef struct { - logic [7:0] va[int]; - logic [7:0] vw[*]; - } C; - typedef struct { - C a; int b; - } B; - typedef struct { - B a; - } A; + typedef struct { + logic [7:0] va[int]; + logic [7:0] vw[*]; + } C; + typedef struct { + C a; + int b; + } B; + typedef struct {B a;} A; endpackage module t ( - input P::A a, - output logic b, - output logic c + input P::A a, + output logic b, + output logic c ); assign b = (a.a.a.va[0] == 8'h0); assign c = (a.a.a.vw[0] == 8'h0); diff --git a/test_regress/t/t_covergroup_param_bins.v b/test_regress/t/t_covergroup_param_bins.v index ab57a4a62..ece6521cb 100644 --- a/test_regress/t/t_covergroup_param_bins.v +++ b/test_regress/t/t_covergroup_param_bins.v @@ -26,10 +26,10 @@ module t #( covergroup cg; cp: coverpoint value { bins negative = {[PMIN : -1]}; // parameter as range lower bound - bins zero = {0}; + bins zero = {0}; bins positive = {[1 : LMAX]}; // localparam as range upper bound - bins maxv = {LMAX}; // localparam as single value - bins minv = {PMIN}; // parameter as single value + bins maxv = {LMAX}; // localparam as single value + bins minv = {PMIN}; // parameter as single value } endgroup diff --git a/test_regress/t/t_disable_fork_nested.v b/test_regress/t/t_disable_fork_nested.v index ad6a4d51e..de1b2526a 100644 --- a/test_regress/t/t_disable_fork_nested.v +++ b/test_regress/t/t_disable_fork_nested.v @@ -10,45 +10,45 @@ // block keeps iterating. module disable_fork ( - input logic i_clk, + input logic i_clk, output logic [2:0] o_counter ); - time delay1 = 500ns; // min period - time delay2 = 3333ns; // max period + time delay1 = 500ns; // min period + time delay2 = 3333ns; // max period - logic clk_re = 1'b0; // rising edge of the clock - logic [2:0] counter = 3'b000; + logic clk_re = 1'b0; // rising edge of the clock + logic [2:0] counter = 3'b000; - always begin - fork - begin : check1 - #delay1; - #1 disable check2; - fork - begin : check3 - #(delay2 - delay1); - clk_re <= 1'b0; - #1 disable check4; - if (counter < 3'b111) counter <= counter + 3'b001; - end - begin : check4 - @(posedge i_clk); - clk_re <= 1'b1; - counter <= 3'b000; - #1 disable check3; - end - join - end - begin : check2 - @(posedge i_clk); + always begin + fork + begin : check1 + #delay1; + #1 disable check2; + fork + begin : check3 + #(delay2 - delay1); clk_re <= 1'b0; - #1 disable check1; + #1 disable check4; if (counter < 3'b111) counter <= counter + 3'b001; - end - join - end + end + begin : check4 + @(posedge i_clk); + clk_re <= 1'b1; + counter <= 3'b000; + #1 disable check3; + end + join + end + begin : check2 + @(posedge i_clk); + clk_re <= 1'b0; + #1 disable check1; + if (counter < 3'b111) counter <= counter + 3'b001; + end + join + end - assign o_counter = counter; + assign o_counter = counter; endmodule // verilog_format: off @@ -57,30 +57,33 @@ endmodule // verilog_format: on module t; - logic clk; - logic [2:0] counter; + logic clk; + logic [2:0] counter; - task clk_cycle(input time half_period); - clk = 1'b1; - #half_period; - clk = 1'b0; - #half_period; - endtask : clk_cycle + task clk_cycle(input time half_period); + clk = 1'b1; + #half_period; + clk = 1'b0; + #half_period; + endtask : clk_cycle - initial begin - // Fast clock (period below delay1): every edge arrives before the - // min-period timeout, so the counter saturates at its max. - repeat (100) clk_cycle(200ns); - $display("Fast clock (200ns half-period): o_counter=%0d", counter); - `checkh(counter, 3'h7); - // Slow clock (period above delay2): the nested fork path runs, which - // only works if disabling check1 releases the inner fork..join. - repeat (100) clk_cycle(5400ns); - $display("Slow clock (5400ns half-period): o_counter=%0d", counter); - `checkh(counter, 3'h3); - $write("*-* All Finished *-*\n"); - $finish; - end + initial begin + // Fast clock (period below delay1): every edge arrives before the + // min-period timeout, so the counter saturates at its max. + repeat (100) clk_cycle(200ns); + $display("Fast clock (200ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h7); + // Slow clock (period above delay2): the nested fork path runs, which + // only works if disabling check1 releases the inner fork..join. + repeat (100) clk_cycle(5400ns); + $display("Slow clock (5400ns half-period): o_counter=%0d", counter); + `checkh(counter, 3'h3); + $write("*-* All Finished *-*\n"); + $finish; + end - disable_fork a_inst(.i_clk(clk), .o_counter(counter)); + disable_fork a_inst ( + .i_clk(clk), + .o_counter(counter) + ); endmodule diff --git a/test_regress/t/t_fsm_duplicate.v b/test_regress/t/t_fsm_duplicate.v index 20d7fd62e..c0edc5755 100644 --- a/test_regress/t/t_fsm_duplicate.v +++ b/test_regress/t/t_fsm_duplicate.v @@ -4,15 +4,14 @@ // SPDX-FileCopyrightText: 2026 Antmicro // SPDX-License-Identifier: CC0-1.0 -module rr -#( +module rr #( ) ( input logic clk, input logic rst, - input logic [7:0] data, - input logic data_q + input logic [7:0] data, + input logic data_q ); - logic a; + logic a; logic [15:0] dcnt; typedef enum logic [7:0] { S0, @@ -21,23 +20,21 @@ module rr S3 } state_t; state_t state_d, state_q; - always_ff @(posedge clk or negedge rst) - if (!rst) state_q <= S0; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; always_ff @(posedge clk) unique case (state_q) - S1: if (a) dcnt[7:0] <= data; - S2: if (a) dcnt[15:8] <= data; - S3: if (data_q) dcnt <= dcnt - 1; + S1: if (a) dcnt[7:0] <= data; + S2: if (a) dcnt[15:8] <= data; + S3: if (data_q) dcnt <= dcnt - 1; default: dcnt <= dcnt; endcase endmodule -module re -#( +module re #( ) ( input logic clk, input logic rst, output logic o, - input unused0, /* block optimizations */ + input unused0, /* block optimizations */ input unused1, input unused2, input unused3, @@ -85,20 +82,18 @@ module re S1 } state_t; state_t state_d, state_q; - always_ff @(posedge clk or negedge rst) - if (!rst) state_q <= S0; + always_ff @(posedge clk or negedge rst) if (!rst) state_q <= S0; always_ff @(posedge clk) unique case (state_q) S1: o <= dcnt[0]; - default: o <= '0; + default: o <= '0; endcase initial begin $write("*-* All Finished *-*\n"); $finish; end endmodule -module rh -#( +module rh #( ) ( input logic clk ); @@ -110,24 +105,21 @@ module rh rr xrr ( .clk, .rst(rst), - .data (a), - .data_q (b & c) + .data(a), + .data_q(b & c) ); re xre ( .clk, .rst(rst), - .o (d) + .o(d) ); endmodule -module U -#( +module U #( ) ( input clk, input rst ); - rh xrh ( - .clk (clk) - ); + rh xrh (.clk(clk)); endmodule module C #( ) ( @@ -139,9 +131,7 @@ module C #( .rst ); endmodule -module A #( -) ( -); +module A #() (); logic clk; logic rst; C c0 ( @@ -153,9 +143,7 @@ module A #( .rst ); endmodule -module B #( -) ( -); +module B #() (); logic clk; logic rst; C xC ( @@ -163,11 +151,7 @@ module B #( .rst ); endmodule -module t #( -) ( -); - B b ( - ); - A a ( - ); +module t #() (); + B b (); + A a (); endmodule diff --git a/test_regress/t/t_property_s_eventually_iface_param.v b/test_regress/t/t_property_s_eventually_iface_param.v index 8d44f6373..6a76feabf 100644 --- a/test_regress/t/t_property_s_eventually_iface_param.v +++ b/test_regress/t/t_property_s_eventually_iface_param.v @@ -4,7 +4,11 @@ // SPDX-FileCopyrightText: 2026 Wilson Snyder // SPDX-License-Identifier: CC0-1.0 -interface iface_if #(parameter int W = 8) (input bit clk); +interface iface_if #( + parameter int W = 8 +) ( + input bit clk +); logic [W-1:0] sig = 0; int passed = 0; assert property (@(posedge clk) s_eventually (sig == 1)) passed++; @@ -18,8 +22,8 @@ module t; // Two distinct specializations: V3Param clones the interface into two // modules, each with its own s_eventually tracking. The generated final // block must stay per-module. - iface_if #(.W(4)) a(.clk(clk)); - iface_if #(.W(8)) b(.clk(clk)); + iface_if #(.W(4)) a (.clk(clk)); + iface_if #(.W(8)) b (.clk(clk)); always @(posedge clk) begin ++cyc; diff --git a/test_regress/t/t_select_bound_side_effect.v b/test_regress/t/t_select_bound_side_effect.v index 5fd21bdc8..b6a182d04 100644 --- a/test_regress/t/t_select_bound_side_effect.v +++ b/test_regress/t/t_select_bound_side_effect.v @@ -32,10 +32,10 @@ module t; if (i < 5) `checkh(arr[i], expected); endtask - task automatic add_z(inout int a); - a += z; - z++; - endtask + task automatic add_z(inout int a); + a += z; + z++; + endtask task automatic assign_side_effect_inout(input int i, input int expected); if (i < 5) arr[i] = 1; @@ -63,8 +63,8 @@ module t; arr[get_y()] = i; if (y < 5) `checkh(arr[y], i); `checkh(y, 2 * i + 1); - arr[get_y() % (i + 1)] = i; - if (y % (i + 1) < 5) `checkh(arr[y % (i + 1)], i); + arr[get_y()%(i+1)] = i; + if (y % (i + 1) < 5) `checkh(arr[y%(i+1)], i); `checkh(y, 2 * (i + 1)); end diff --git a/test_regress/t/t_stream_unpacked_struct.v b/test_regress/t/t_stream_unpacked_struct.v index 509d8a035..8a169c20e 100644 --- a/test_regress/t/t_stream_unpacked_struct.v +++ b/test_regress/t/t_stream_unpacked_struct.v @@ -177,7 +177,8 @@ module t; if ($test$plusargs("t_stream_unpacked_struct_alt")) begin narrow_bits = 12'h123; - end else begin + end + else begin narrow_bits = 12'habd; end /* verilator lint_off WIDTHEXPAND */ From c6a5255ea0f29e74605f503f1d0ad9ad434b95ac Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sat, 13 Jun 2026 22:07:18 -0400 Subject: [PATCH 32/46] Tests: Disable unstable --vltmt tests (#7779) (#7780) (#7781) --- test_regress/t/t_assert_iff_clk.py | 3 ++- test_regress/t/t_covergroup_clocked_sample.py | 3 ++- test_regress/t/t_stream_queue.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test_regress/t/t_assert_iff_clk.py b/test_regress/t/t_assert_iff_clk.py index 35e44000c..fd4b2dc44 100755 --- a/test_regress/t/t_assert_iff_clk.py +++ b/test_regress/t/t_assert_iff_clk.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7781 unstable with --vltmt +test.scenarios('simulator_st') test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing']) diff --git a/test_regress/t/t_covergroup_clocked_sample.py b/test_regress/t/t_covergroup_clocked_sample.py index 9f6b5465d..be9b453d3 100755 --- a/test_regress/t/t_covergroup_clocked_sample.py +++ b/test_regress/t/t_covergroup_clocked_sample.py @@ -10,6 +10,7 @@ import vltest_bootstrap import coverage_covergroup_common -test.scenarios('vlt_all') +# Issue #7779 unstable with --vltmt +test.scenarios('vlt') coverage_covergroup_common.run(test) diff --git a/test_regress/t/t_stream_queue.py b/test_regress/t/t_stream_queue.py index 3a46a7545..f7785cc52 100755 --- a/test_regress/t/t_stream_queue.py +++ b/test_regress/t/t_stream_queue.py @@ -9,7 +9,8 @@ import vltest_bootstrap -test.scenarios('simulator') +# Issue #7780 unstable with --vltmt +test.scenarios('simulator_st') test.compile(verilator_flags2=["--timing"]) From b973b1df5aa282d1f2877609e615b94892478659 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 14 Jun 2026 13:13:47 +0100 Subject: [PATCH 33/46] Fix hang in assertion optimization (#7707 repair) --- src/V3Assert.cpp | 42 ++++++++++++++++++---------- test_regress/t/t_assert_opt_check.py | 4 +-- test_regress/t/t_assert_opt_check.v | 8 ++++++ 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 160121c0b..77214da14 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -659,6 +659,34 @@ class AssertVisitor final : public VNVisitor { iterateChildren(nodep); } + if (nodep->user2()) { + // Combine consecutive assertOn checks if possible + if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { + if (backp->nextp() == nodep // + && backp->user2() // + && backp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); + nodep->unlinkFrBack(); + VL_DO_DANGLING(pushDeletep(nodep), nodep); + return; + } + } + // Combine nested assertOn checks if possible + if (nodep->thensp() && !nodep->thensp()->nextp() && isEmptyStmt(nodep->elsesp())) { + AstIf* const checkp = VN_CAST(nodep->thensp(), If); + if (checkp // + && checkp->user2() // + && checkp->condp()->sameTree(nodep->condp())) { + ++m_statAssertOnCombined; + nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); + VL_DO_DANGLING(checkp->unlinkFrBack(), checkp); + return; + } + } + return; + } + // Swap assertOn check with single statement 'if' statement to bubble up for combining // Note we can't just swap the conditions as they two Ifs have different flags, // so swapping the Ifs themeselves then swapping back the bodies. @@ -684,20 +712,6 @@ class AssertVisitor final : public VNVisitor { } } } - - // Combine consecutive assertOn checks if possible - if (nodep->user2()) { - if (AstIf* const backp = VN_CAST(nodep->backp(), If)) { - if (backp->nextp() == nodep // - && backp->user2() // - && backp->condp()->sameTree(nodep->condp())) { - ++m_statAssertOnCombined; - backp->addThensp(nodep->thensp()->unlinkFrBackWithNext()); - nodep->unlinkFrBack(); - VL_DO_DANGLING(pushDeletep(nodep), nodep); - } - } - } } //========== Case assertions diff --git a/test_regress/t/t_assert_opt_check.py b/test_regress/t/t_assert_opt_check.py index b7652f1ab..884e2876c 100755 --- a/test_regress/t/t_assert_opt_check.py +++ b/test_regress/t/t_assert_opt_check.py @@ -15,7 +15,7 @@ test.compile(verilator_flags2=['--binary', '--stats']) test.execute(check_finished=True) -test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 2) -test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 11) +test.file_grep(test.stats, r'Assertions, assertOn checks combined\s+(\d+)', 3) +test.file_grep(test.stats, r'Assertions, assertOn checks hoisted\s+(\d+)', 15) test.passes() diff --git a/test_regress/t/t_assert_opt_check.v b/test_regress/t/t_assert_opt_check.v index ccfea327e..7d0f54537 100644 --- a/test_regress/t/t_assert_opt_check.v +++ b/test_regress/t/t_assert_opt_check.v @@ -58,4 +58,12 @@ module t; end end + // Should combine the 2 nested assertOn checks after hoisting + always @(posedge clk) begin + if (assertEnable) begin + // This is an 'assert' with another 'assert' in the fail branch + assert(cntB - 100 == cntA); else assert(cntB == cntA + 100); + end + end + endmodule From 9a0cd289e83737e84ad90ec68206f390d6bdcbc5 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 14 Jun 2026 14:53:40 +0100 Subject: [PATCH 34/46] Fix memory leak in previous patch --- src/V3Assert.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 77214da14..6b3fcb777 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -680,7 +680,7 @@ class AssertVisitor final : public VNVisitor { && checkp->condp()->sameTree(nodep->condp())) { ++m_statAssertOnCombined; nodep->addThensp(checkp->thensp()->unlinkFrBackWithNext()); - VL_DO_DANGLING(checkp->unlinkFrBack(), checkp); + VL_DO_DANGLING(pushDeletep(checkp->unlinkFrBack()), checkp); return; } } From 77e6a21224d76ff2a0335c907c20a25736bbfb9b Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Sun, 14 Jun 2026 16:29:27 +0100 Subject: [PATCH 35/46] Internals: Inline AstVar::isToggleCoverable() Inline into the single call site, remove unnecessary isSc() and isPrimaryIO() guards (these flags are set only in a later pass). No functional change. --- src/V3AstNodeOther.h | 6 ------ src/V3Coverage.cpp | 9 ++++++--- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index 1308818be..b08033033 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2458,12 +2458,6 @@ public: bool isWor() const { return varType().isWor(); } bool isWiredNet() const { return varType().isWiredNet(); } bool isTemp() const { return varType().isTemp(); } - bool isToggleCoverable() const { - return ((isIO() || isSignal()) - && (isIO() || isBitLogic()) - // Wrapper would otherwise duplicate wrapped module's coverage - && !isSc() && !isPrimaryIO() && !isConst() && !isDouble() && !isString()); - } bool isClassMember() const { return varType() == VVarType::MEMBER; } bool isVirtIface() const { if (AstIfaceRefDType* const dtp = VN_CAST(dtypep(), IfaceRefDType)) { diff --git a/src/V3Coverage.cpp b/src/V3Coverage.cpp index 6d1ff5bc1..12bb973dc 100644 --- a/src/V3Coverage.cpp +++ b/src/V3Coverage.cpp @@ -166,10 +166,13 @@ class CoverageVisitor final : public VNVisitor { // METHODS + // Return non-nullptr reason if this variable shouldn't have toggle coverage const char* varIgnoreToggle(const AstVar* nodep) { - // Return true if this shouldn't be traced - // See also similar rule in V3TraceDecl::varIgnoreTrace - if (!nodep->isToggleCoverable()) return "Not relevant signal type"; + const bool cover = nodep->isIO() || (nodep->isSignal() && nodep->isBitLogic()); + if (!cover) return "Not relevant signal"; + if (nodep->isConst()) return "Signal is constant"; + if (nodep->isDouble()) return "Signal is double"; + if (nodep->isString()) return "Signal is string"; if (!v3Global.opt.coverageUnderscore()) { const string prettyName = nodep->prettyName(); if (prettyName[0] == '_') return "Leading underscore"; From 12bcf85d33abad924b49218f9e93f5b09cef03f8 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 14 Jun 2026 09:28:25 -0400 Subject: [PATCH 36/46] Internals: Misc V3Life cleanups. No functional change intended. --- src/V3AstNodeStmt.h | 1 + src/V3AstNodes.cpp | 4 ++++ src/V3Life.cpp | 54 ++++++++++++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index 336da7bae..aed79ddc0 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -59,6 +59,7 @@ protected: public: ASTGEN_MEMBERS_AstNodeAssign; // Clone single node, just get same type back. + void dump(std::ostream& str) const override; virtual AstNodeAssign* cloneType(AstNodeExpr* lhsp, AstNodeExpr* rhsp) = 0; bool hasDType() const override VL_MT_SAFE { return true; } virtual bool cleanRhs() const { return true; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 2c93b3d58..ef8713999 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2771,6 +2771,10 @@ void AstNodeArrayDType::dumpJson(std::ostream& str) const { dumpJsonStr(str, "declRange", cvtToStr(declRange())); dumpJsonGen(str); } +void AstNodeAssign::dump(std::ostream& str) const { + this->AstNode::dump(str); + if (timingControlp()) str << " [TIMING=" << nodeAddr(timingControlp()) << "]"; +} string AstPackArrayDType::prettyDTypeName(bool full) const { std::ostringstream os; if (const auto subp = subDTypep()) os << subp->prettyDTypeName(full); diff --git a/src/V3Life.cpp b/src/V3Life.cpp index 77ac85c87..7b4d5ab1a 100644 --- a/src/V3Life.cpp +++ b/src/V3Life.cpp @@ -60,7 +60,7 @@ public: class LifeVarEntry final { // Last assignment to this varscope, nullptr if no longer relevant - AstNodeStmt* m_assignp = nullptr; + AstNodeStmt* m_assignp = nullptr; // Last simple assignment AstConst* m_constp = nullptr; // Known constant value bool m_isNew = true; // Is just created // First access was a set (and thus block above may have a set that can be deleted @@ -77,6 +77,17 @@ public: m_isNew = false; m_setBeforeUse = setBeforeUse; } + string ascii() const { // LCOV_EXCL_START + std::ostringstream os; + os << "[Life "; + if (m_isNew) os << " isNew"; + if (m_setBeforeUse) os << " setBeforeUse"; + if (m_everSet) os << " everSet"; + if (m_assignp) os << " assignp=" << AstNode::nodeAddr(m_assignp); + if (m_constp) os << " constp=" << AstNode::nodeAddr(m_constp); + os << "]"; + return os.str(); + } // LCOV_EXCL_STOP void simpleAssign(AstNodeStmt* nodep) { // New simple A=.... assignment UASSERT_OBJ(!m_isNew, nodep, "Uninitialized new entry"); @@ -84,7 +95,10 @@ public: m_constp = nullptr; m_everSet = true; if (AstNodeAssign* const assp = VN_CAST(nodep, Assign)) { - if (VN_IS(assp->rhsp(), Const)) m_constp = VN_AS(assp->rhsp(), Const); + if (VN_IS(assp->rhsp(), Const)) { + m_constp = VN_AS(assp->rhsp(), Const); + UINFO(9, "assign-const " << assp->rhsp() << " = " << m_constp); + } } } void complexAssign() { // A[x]=... or some complicated assignment @@ -179,6 +193,7 @@ public: // Aha, variable is constant; substitute in. // We'll later constant propagate UINFO(4, " replaceconst: " << varrefp); + UINFO(9, " replaceval: " << constp); varrefp->replaceWith(constp->cloneTree(false)); m_replacedVref = true; VL_DO_DANGLING(varrefp->deleteTree(), varrefp); @@ -264,7 +279,9 @@ class LifeVisitor final : public VNVisitor { LifeBlock* m_lifep = nullptr; // Current active lifetime map for current scope // METHODS - void setNoopt() { + void setNoopt(const char* reasonp) { + (void)reasonp; + // UINFO(9, "setNoopt " << reasonp); m_noopt = true; m_lifep->clear(); } @@ -310,7 +327,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNodeAssign* nodep) override { if (nodep->isTimingControl() || VN_IS(nodep, AssignForce)) { // V3Life doesn't understand time sense nor force assigns - don't optimize - setNoopt(); + setNoopt("timing|force"); if (nodep->isTimingControl()) m_containsTiming = true; iterateChildren(nodep); return; @@ -325,7 +342,7 @@ class LifeVisitor final : public VNVisitor { // V3Life doesn't understand time sense if (nodep->isTimingControl()) { // Don't optimize - setNoopt(); + setNoopt("assigndly"); m_containsTiming = true; } // Don't treat as normal assign @@ -337,19 +354,19 @@ class LifeVisitor final : public VNVisitor { UINFO(4, " IF " << nodep); // Condition is part of PREVIOUS block iterateAndNextNull(nodep->condp()); - LifeBlock* const prevLifep = m_lifep; - LifeBlock* const ifLifep = new LifeBlock{prevLifep, m_statep}; - LifeBlock* const elseLifep = new LifeBlock{prevLifep, m_statep}; + LifeBlock* const ifLifep = new LifeBlock{m_lifep, m_statep}; + LifeBlock* const elseLifep = new LifeBlock{m_lifep, m_statep}; { + VL_RESTORER(m_lifep); m_lifep = ifLifep; iterateAndNextNull(nodep->thensp()); } { + VL_RESTORER(m_lifep); m_lifep = elseLifep; iterateAndNextNull(nodep->elsesp()); } - m_lifep = prevLifep; - UINFO(4, " join "); + UINFO(4, " join " << nodep); // Find sets on both flows m_lifep->dualBranch(ifLifep, elseLifep); // For the next assignments, clear any variables that were read or written in the block @@ -357,6 +374,7 @@ class LifeVisitor final : public VNVisitor { elseLifep->lifeToAbove(); VL_DO_DANGLING(delete ifLifep, ifLifep); VL_DO_DANGLING(delete elseLifep, elseLifep); + UINFO(4, " if-done " << nodep); } void visit(AstLoop* nodep) override { // Similar problem to AstJumpBlock, don't optimize loop bodies - most are unrolled @@ -366,14 +384,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("loop"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinloop"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstJumpBlock* nodep) override { // As with Loop's we can't predict if a JumpGo will kill us or not @@ -384,14 +402,14 @@ class LifeVisitor final : public VNVisitor { VL_RESTORER(m_noopt); VL_RESTORER(m_lifep); m_lifep = new LifeBlock{m_lifep, m_statep}; - setNoopt(); + setNoopt("jumpblock"); iterateAndNextNull(nodep->stmtsp()); UINFO(4, " joinjump"); // For the next assignments, clear any variables that were read or written in the block m_lifep->lifeToAbove(); VL_DO_DANGLING(delete m_lifep, m_lifep); } - if (m_containsTiming) setNoopt(); + if (m_containsTiming) setNoopt("timing"); } void visit(AstNodeCCall* nodep) override { // UINFO(4, " CCALL " << nodep); @@ -399,7 +417,7 @@ class LifeVisitor final : public VNVisitor { // Enter the function and trace it // else is non-inline or public function we optimize separately if (nodep->funcp()->entryPoint()) { - setNoopt(); + setNoopt("ccall"); } else { m_tracingCall = true; iterate(nodep->funcp()); @@ -409,8 +427,8 @@ class LifeVisitor final : public VNVisitor { // UINFO(4, " CFUNC " << nodep); if (!m_tracingCall && !nodep->entryPoint()) return; m_tracingCall = false; - if (nodep->recursive()) setNoopt(); - if (nodep->noLife()) setNoopt(); + if (nodep->recursive()) setNoopt("recursive"); + if (nodep->noLife()) setNoopt("nolife"); if (nodep->dpiImportPrototype() && !nodep->dpiPure()) { m_sideEffect = true; // If appears on assign RHS, don't ever delete the assignment } @@ -429,7 +447,7 @@ class LifeVisitor final : public VNVisitor { void visit(AstNode* nodep) override { if (nodep->isTimingControl()) { // V3Life doesn't understand time sense - don't optimize - setNoopt(); + setNoopt("timing"); m_containsTiming = true; } iterateChildren(nodep); From df78db0e73a3d49385748e298763f5d21b14b6ed Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 14 Jun 2026 13:01:59 -0400 Subject: [PATCH 37/46] Fix $fflush and autoflush with --threads (#7782). Fixes #7782. --- Changes | 2 +- include/verilated.cpp | 7 +++++++ include/verilated_funcs.h | 5 +++-- src/V3EmitCFunc.h | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Changes b/Changes index ced84ea4e..9877ca716 100644 --- a/Changes +++ b/Changes @@ -161,7 +161,7 @@ Verilator 5.049 devel * Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] * Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] * Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] - +* Fix $fflush and autoflush with --threads (#7782). Verilator 5.048 2026-04-26 diff --git a/include/verilated.cpp b/include/verilated.cpp index 6bf868cad..a7d78f3bd 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -318,6 +318,13 @@ void VL_PRINTF_MT(const char* formatp, ...) VL_MT_SAFE { }}); } +void VL_FFLUSH_MT() VL_MT_SAFE { + va_list ap; + VerilatedThreadMsgQueue::post(VerilatedMsg{[=]() { // + Verilated::runFlushCallbacks(); + }}); +} + template static size_t _vl_snprintf_string(std::string& str, const char* format, snprintf_args_ts... args) VL_MT_SAFE { diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index db4c39e5c..8aebd1e34 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -74,10 +74,8 @@ extern void VL_FATAL_MT(const char* filename, int linenum, const char* hier, extern void VL_WARN_MT(const char* filename, int linenum, const char* hier, const char* msg) VL_MT_SAFE; -// clang-format off /// Print a string, multithread safe. Eventually VL_PRINTF will get called. extern void VL_PRINTF_MT(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; -// clang-format on /// Print a debug message from internals with standard prefix, with printf style format extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; @@ -85,6 +83,9 @@ extern void VL_DBG_MSGF(const char* formatp, ...) VL_ATTR_PRINTF(1) VL_MT_SAFE; /// Print a debug message from string via VL_DBG_MSGF inline void VL_DBG_MSGS(const std::string& str) VL_MT_SAFE { VL_DBG_MSGF("%s", str.c_str()); } +/// Flush stdout +extern void VL_FFLUSH_MT() VL_MT_SAFE; + // EMIT_RULE: VL_RANDOM: oclean=dirty inline IData VL_RANDOM_I() VL_MT_SAFE { return vl_rand64(); } inline QData VL_RANDOM_Q() VL_MT_SAFE { return vl_rand64(); } diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 6eedcbc69..969e2fd0b 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -1134,7 +1134,7 @@ public: } void visit(AstFFlush* nodep) override { if (!nodep->filep()) { - putns(nodep, "Verilated::runFlushCallbacks();\n"); + putns(nodep, "VL_FFLUSH_MT();"); } else { putns(nodep, "VL_FFLUSH_I("); iterateAndNextConstNull(nodep->filep()); From 5ab2bf1ec439fe82c50194d9f9981c6c6e8ed573 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 15 Jun 2026 05:42:00 +0100 Subject: [PATCH 38/46] Optimize input combinational logic by change detection (#7784) When a lot of combinational logic is driven from top level inputs, work can be wasted evaluating that logic if the top level inputs don't change. This change adds an optimization by performing a change detect on the top level inputs, and evaluate 'ico' logic only if the top level input actually changed. This especially helps with --hierarchical/--lib-create which runs the 'ico' of each sub-model in the eval settle loop. This was observed to yield 40%+ run-time speedup on some partitioned designs. The added change detection is cheap, so it is emitted even if the 'ico' region is small, and is on by default. The optimization is only sound if the model itself does not write to the top level inputs (otherwise the 'previous value' variables would be out of sync, which are not updated by internal writes.). If we can detect a top level input is written within the design, then for that input, we fall back on always running the relevant logic. With --vpi we cannot prove safety statically, so --vpi will disable this optimisation unless explicitly enabled. (In which case it's the user's responsibility to not write to top level inputs via the VPI.) --- docs/guide/exe_verilator.rst | 15 ++++ src/V3AstNodeOther.h | 4 ++ src/V3AstNodes.cpp | 3 + src/V3LinkLevel.cpp | 1 + src/V3Options.cpp | 6 ++ src/V3Options.h | 3 + src/V3Sched.cpp | 70 ++++++++++++++++--- src/V3Width.cpp | 1 + test_regress/t/t_constraint_json_only.out | 2 +- test_regress/t/t_flag_expand_limit.py | 2 +- test_regress/t/t_json_only_begin_hier.out | 2 +- test_regress/t/t_json_only_first.out | 14 ++-- test_regress/t/t_json_only_flat.out | 16 ++--- test_regress/t/t_json_only_flat_vlvbound.out | 8 +-- test_regress/t/t_json_only_primary_io.out | 6 +- test_regress/t/t_json_only_tag.out | 2 +- test_regress/t/t_opt_balance_cats.py | 2 +- ...sched_ico_change_detect_input_assigned.cpp | 45 ++++++++++++ ..._sched_ico_change_detect_input_assigned.py | 28 ++++++++ ...t_sched_ico_change_detect_input_assigned.v | 38 ++++++++++ ...ed_ico_change_detect_input_assigned_off.py | 27 +++++++ ...ed_ico_change_detect_input_assigned_vpi.py | 27 +++++++ ...ico_change_detect_input_assigned_vpi_on.py | 27 +++++++ .../t/t_wrapper_context__top0.dat.out | 6 +- .../t/t_wrapper_context__top1.dat.out | 8 +-- 25 files changed, 321 insertions(+), 42 deletions(-) create mode 100644 test_regress/t/t_sched_ico_change_detect_input_assigned.cpp create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned.py create mode 100644 test_regress/t/t_sched_ico_change_detect_input_assigned.v create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned_off.py create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py create mode 100755 test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 705bf3f71..73ed42486 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -737,6 +737,21 @@ Summary: this is not recommended as may cause additional warnings and ordering issues. +.. option:: -fno-ico-change-detect + + Rarely needed. Disable input change detection in the input combinational + ('ico') region. With change detection enabled (the default, unless + :vlopt:`--vpi` is passed), the input combinational logic is evaluated only + when a top level input has actually changed, rather than unconditionally on + the first scheduling iteration. + + The change detection logic assumes a top level input only ever changes + externally between evaluations. The optimization is automatically disabled + for top level input signals that are written within the design. Accesses via + the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` + disables this optimization for all inputs; it may be turned back on by + explicitly passing :vlopt:`-fico-change-detect`. + .. option:: -fno-inline .. option:: -fno-inline-funcs diff --git a/src/V3AstNodeOther.h b/src/V3AstNodeOther.h index b08033033..f9518bf63 100644 --- a/src/V3AstNodeOther.h +++ b/src/V3AstNodeOther.h @@ -2138,6 +2138,7 @@ class AstVar final : public AstNode { bool m_attrFsmArcInclCond : 1; // declared with fsm_arc_include_cond metacomment bool m_fileDescr : 1; // File descriptor bool m_gotNansiType : 1; // Linker saw Non-ANSI type declaration + bool m_icoMaybeWritten : 1; // Design might write this input signal - for ico change detect bool m_isConst : 1; // Table contains constant data bool m_isContinuously : 1; // Ever assigned continuously (for force/release) bool m_hasStrengthAssignment : 1; // Is on LHS of assignment with strength specifier @@ -2200,6 +2201,7 @@ class AstVar final : public AstNode { m_attrFsmArcInclCond = false; m_fileDescr = false; m_gotNansiType = false; + m_icoMaybeWritten = false; m_isConst = false; m_isContinuously = false; m_hasStrengthAssignment = false; @@ -2379,6 +2381,8 @@ public: void hasStrengthAssignment(bool flag) { m_hasStrengthAssignment = flag; } bool hasUserInit() const { return m_hasUserInit; } void hasUserInit(bool flag) { m_hasUserInit = flag; } + void icoMaybeWritten(bool flag) { m_icoMaybeWritten = flag; } + bool icoMaybeWritten() const { return m_icoMaybeWritten; } bool isDpiOpenArray() const VL_MT_SAFE { return m_isDpiOpenArray; } void isDpiOpenArray(bool flag) { m_isDpiOpenArray = flag; } bool isHideLocal() const { return m_isHideLocal; } diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index ef8713999..0707c8d8f 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -630,6 +630,7 @@ void AstVar::combineType(const AstVar* otherp) { varType(otherp->varType()); direction(otherp->direction()); } + if (otherp->icoMaybeWritten()) icoMaybeWritten(true); } void AstVar::combineType(VVarType type) { // These flags get combined with the existing settings of the flags. @@ -3219,6 +3220,7 @@ void AstVar::dump(std::ostream& str) const { str << " [FUNC]"; } if (hasUserInit()) str << " [UINIT]"; + if (icoMaybeWritten()) str << " [ICOMAYBEWRITTEN]"; if (isDpiOpenArray()) str << " [DPIOPENA]"; if (ignorePostWrite()) str << " [IGNPWR]"; if (ignoreSchedWrite()) str << " [IGNWR]"; @@ -3247,6 +3249,7 @@ void AstVar::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, attrFsmResetArc); dumpJsonBoolFuncIf(str, attrFsmArcInclCond); dumpJsonBoolFuncIf(str, attrFileDescr); + dumpJsonBoolFuncIf(str, icoMaybeWritten); dumpJsonBoolFuncIf(str, isDpiOpenArray); dumpJsonBoolFuncIf(str, isFuncReturn); dumpJsonBoolFuncIf(str, isFuncLocal); diff --git a/src/V3LinkLevel.cpp b/src/V3LinkLevel.cpp index 012c87166..066097239 100644 --- a/src/V3LinkLevel.cpp +++ b/src/V3LinkLevel.cpp @@ -291,6 +291,7 @@ void V3LinkLevel::wrapTopCell(AstNetlist* rootp) { varp->sigPublic(true); // User needs to be able to get to it... oldvarp->primaryIO(false); varp->primaryIO(true); + varp->icoMaybeWritten(oldvarp->icoMaybeWritten()); if (varp->isRef() || varp->isConstRef()) { varp->v3warn(E_UNSUPPORTED, "Unsupported: ref/const ref as primary input/output: " diff --git a/src/V3Options.cpp b/src/V3Options.cpp index b8d7a0a29..dd26e7e06 100644 --- a/src/V3Options.cpp +++ b/src/V3Options.cpp @@ -1077,6 +1077,9 @@ void V3Options::notify() VL_MT_DISABLED { // Preprocessor defines based on options used if (timing().isSetTrue()) V3PreShell::defineCmdLine("VERILATOR_TIMING", "1"); + // If VPI is used, and no explicit ico change detect option was passed, disable it by default + if (m_vpi && m_fIcoChangeDetect.isDefault()) m_fIcoChangeDetect.setTrueOrFalse(false); + // === Leave last // Mark options as available m_available = true; @@ -1483,6 +1486,9 @@ void V3Options::parseOptsList(FileLine* fl, const string& optdir, int argc, DECL_OPTION("-ffunc-opt-balance-cat", FOnOff, &m_fFuncBalanceCat); DECL_OPTION("-ffunc-opt-split-cat", FOnOff, &m_fFuncSplitCat); DECL_OPTION("-fgate", FOnOff, &m_fGate); + DECL_OPTION("-fico-change-detect", CbFOnOff, [this](bool flag) { // + m_fIcoChangeDetect.setTrueOrFalse(flag); + }); DECL_OPTION("-finline", FOnOff, &m_fInline); DECL_OPTION("-finline-funcs", FOnOff, &m_fInlineFuncs); DECL_OPTION("-finline-funcs-eager", FOnOff, &m_fInlineFuncsEager); diff --git a/src/V3Options.h b/src/V3Options.h index 729c68f47..56ba51f79 100644 --- a/src/V3Options.h +++ b/src/V3Options.h @@ -410,6 +410,8 @@ private: bool m_fFuncBalanceCat = true; // main switch: -fno-func-balance-cat: expansion of C macros bool m_fFuncSplitCat = true; // main switch: -fno-func-split-cat: expansion of C macros bool m_fGate; // main switch: -fno-gate: gate wire elimination + // main switch: -fno-ico-change-detect: input change detection optimization + VOptionBool m_fIcoChangeDetect{VOptionBool::OPT_DEFAULT_TRUE}; bool m_fInline; // main switch: -fno-inline: module inlining bool m_fInlineFuncs = true; // main switch: -fno-inline-funcs: function inlining bool m_fInlineFuncsEager = true; // main switch: -fno-inline-funcs-eager: don't inline eagerly @@ -745,6 +747,7 @@ public: bool fFuncSplitCat() const { return m_fFuncSplitCat; } bool fFunc() const { return fFuncSplitCat() || fFuncBalanceCat(); } bool fGate() const { return m_fGate; } + VOptionBool fIcoChangeDetect() const { return m_fIcoChangeDetect; } bool fInline() const { return m_fInline; } bool fInlineFuncs() const { return m_fInlineFuncs; } bool fInlineFuncsEager() const { return m_fInlineFuncsEager; } diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 8adc0a52e..217cd3f95 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -529,8 +529,46 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, + entry.m_memberp->name()); } + // Create the input change detect SenTrees. + // If there is a lot of combinationallogic hanging of the top level inputs, we can save + // a lot of work by only evaluating it if an input has actually changed. This in + // paticular helps hierarchical models partitioned across combinaitonal boundaries. + // The change detect itself should be fairly cheap otherwise so alway do it. + // For correctness, don't create a change detect for top level inputs also written + // by the design, as the change detect 'previous value' would get out of sync. + // Also omit a SenTree for types that don't have the required '!=' operator. + // Any signal that does not have an explicit change detect trigger will fall back to + // using the 'first iteration' trigger, same as if this optimization was disabled. + std::unordered_map inp2changedp; + std::vector icoChangeSenTreeps; + if (v3Global.opt.fIcoChangeDetect().isTrue()) { + FileLine* const flp = netlistp->fileline(); + AstScope* const scopep = netlistp->topScopep()->scopep(); + for (AstVarScope* vscp = scopep->varsp(); vscp; vscp = VN_AS(vscp->nextp(), VarScope)) { + // Only for top level ports, assume outputs don't change externally + if (!vscp->varp()->isPrimaryInish()) continue; + // Don't do if written by the design - wouldn't update the change detect 'prev' value + if (vscp->varp()->icoMaybeWritten()) continue; + // Don't do if forceable, as we can't see the actual value - this is belt and braces + if (vscp->varp()->isForced()) continue; + // Can't handle unpacked arrays (they have special types when primary input) + if (VN_IS(vscp->dtypep()->skipRefp(), UnpackArrayDType)) continue; + // Similarly to arrays, can't handle SystemC types + if (vscp->varp()->isSc()) continue; + // Create a sen tree triggered when this input changes + AstSenTree*& senTreepr = inp2changedp[vscp]; + UASSERT_OBJ(!senTreepr, vscp, "Duplicate input change detect trigger"); + AstVarRef* const refp = new AstVarRef{flp, vscp, VAccess::READ}; + AstSenItem* const senItemp = new AstSenItem{flp, VEdgeType::ET_CHANGED, refp}; + senTreepr = new AstSenTree{flp, senItemp}; + icoChangeSenTreeps.push_back(senTreepr); + } + } + V3Stats::addStat("Scheduling, 'ico' change detect triggers", icoChangeSenTreeps.size()); + // Gather the relevant sensitivity expressions and create the trigger kit - const auto& senTreeps = getSenTreesUsedBy({&logic}); + std::vector senTreeps = getSenTreesUsedBy({&logic}); + senTreeps.insert(senTreeps.end(), icoChangeSenTreeps.begin(), icoChangeSenTreeps.end()); const TriggerKit trigKit = TriggerKit::create(netlistp, initFuncp, senExprBuilder, {}, senTreeps, "ico", extraTriggers, false, false); std::ignore = senExprBuilder.getAndClearResults(); @@ -543,14 +581,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Remap sensitivities remapSensitivities(logic, trigKit.mapVec()); + for (auto& pair : inp2changedp) pair.second = trigKit.mapVec().at(pair.second); // Create the inverse map from trigger ref AstSenTree to original AstSenTree V3Order::TrigToSenMap trigToSen; invertAndMergeSenTreeMap(trigToSen, trigKit.mapVec()); - // The trigger top level inputs (first iteration) - AstSenTree* const inputChanged - = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + // The 'first iteration' trigger for top level inputs - lazy constructed only if needed + AstSenTree* firstIterTriggerp = nullptr; // The DPI Export trigger AstSenTree* const dpiExportTriggered @@ -565,9 +603,19 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, netlistp, {&logic}, trigToSen, "ico", false, false, [&](const AstVarScope* vscp, std::vector& out) { AstVar* const varp = vscp->varp(); - if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { - out.push_back(inputChanged); + // If it has an explicit change detect trigger, use that, + // otherwise fall back to using the 'first iteration' trigger + auto it = inp2changedp.find(vscp); + if (it != inp2changedp.end()) { + out.push_back(it->second); + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { + if (!firstIterTriggerp) { + firstIterTriggerp + = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); + } + out.push_back(firstIterTriggerp); } + // Add other triggers if (varp->isWrittenByDpi()) out.push_back(dpiExportTriggered); if (vscp->varp()->sensIfacep() || vscp->varp()->isVirtIface()) { const auto& ifaceTriggered @@ -593,8 +641,14 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, // Work statements: Invoke the 'ico' function util::callVoidFunc(icoFuncp)); - // Add the first iteration trigger to the trigger computation function - trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + // Add the first iteration trigger to the trigger computation function - if used + if (firstIterTriggerp) { + trigKit.addExtraTriggerAssignment(icoLoop.firstIterp, firstIterationTrigger, false); + } + + // Release temporary input change detect SenTrees + for (AstSenTree* const senTreep : icoChangeSenTreeps) senTreep->deleteTree(); + icoChangeSenTreeps.clear(); return icoLoop.stmtsp; } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index a323f9336..f5e55d2a7 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3073,6 +3073,7 @@ class WidthVisitor final : public VNVisitor { UASSERT_OBJ(nodep->dtypep(), nodep, "LHS var should be dtype completed"); } // UINFOTREE(9, nodep, "", "VRout"); + if (nodep->access().isWriteOrRW()) nodep->varp()->icoMaybeWritten(true); if (nodep->access().isWriteOrRW() && nodep->varp()->direction() == VDirection::CONSTREF) { nodep->v3error("Assigning to const ref variable: " << nodep->prettyNameQ()); } else if (nodep->access().isWriteOrRW() && nodep->varp()->isInput() diff --git a/test_regress/t/t_constraint_json_only.out b/test_regress/t/t_constraint_json_only.out index ce4b7e0d1..c18d8a989 100644 --- a/test_regress/t/t_constraint_json_only.out +++ b/test_regress/t/t_constraint_json_only.out @@ -29,7 +29,7 @@ {"type":"VAR","name":"state","addr":"(Z)","loc":"d,17:10,17:15","dtypep":"(M)","origName":"state","verilogName":"state","direction":"NONE","lifetime":"VAUTOMI","varType":"MEMBER","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"FUNC","name":"strings_equal","addr":"(AB)","loc":"d,61:16,61:29","dtypep":"(U)","method":true,"cname":"strings_equal", "fvarp": [ - {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} + {"type":"VAR","name":"strings_equal","addr":"(BB)","loc":"d,61:16,61:29","dtypep":"(U)","origName":"strings_equal","verilogName":"strings_equal","direction":"OUTPUT","noCReset":true,"icoMaybeWritten":true,"isFuncReturn":true,"isFuncLocal":true,"lifetime":"VAUTOM","varType":"VAR","dtypeName":"bit","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []} ],"classOrPackagep": [], "stmtsp": [ {"type":"VAR","name":"a","addr":"(CB)","loc":"d,61:37,61:38","dtypep":"(M)","origName":"a","verilogName":"a","direction":"INPUT","isFuncLocal":true,"lifetime":"VAUTOMI","varType":"PORT","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, diff --git a/test_regress/t/t_flag_expand_limit.py b/test_regress/t/t_flag_expand_limit.py index ec0cc54a6..d9b19b528 100755 --- a/test_regress/t/t_flag_expand_limit.py +++ b/test_regress/t/t_flag_expand_limit.py @@ -13,6 +13,6 @@ test.scenarios('vlt') test.compile(verilator_flags2=['--expand-limit 1 --stats -fno-dfg']) -test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 7) +test.file_grep(test.stats, r'Optimizations, expand limited\s+(\d+)', 29) test.passes() diff --git a/test_regress/t/t_json_only_begin_hier.out b/test_regress/t/t_json_only_begin_hier.out index 8e779675c..68cc2bd11 100644 --- a/test_regress/t/t_json_only_begin_hier.out +++ b/test_regress/t/t_json_only_begin_hier.out @@ -2,7 +2,7 @@ "modulesp": [ {"type":"MODULE","name":"test","addr":"(E)","loc":"d,21:8,21:12","origName":"test","verilogName":"test","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"N","addr":"(F)","loc":"d,22:10,22:11","dtypep":"(G)","origName":"N","verilogName":"N","direction":"NONE","isUsedLoopIdx":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"GENVAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE","addr":"(H)","loc":"d,24:5,24:8","implied":true,"genforp": [],"itemsp": []}, {"type":"GENBLOCK","name":"FOR_GENERATE[0]","addr":"(I)","loc":"d,25:14,25:24","genforp": [], "itemsp": [ diff --git a/test_regress/t/t_json_only_first.out b/test_regress/t/t_json_only_first.out index f32e2e41d..0f60e9c25 100644 --- a/test_regress/t/t_json_only_first.out +++ b/test_regress/t/t_json_only_first.out @@ -2,13 +2,13 @@ "modulesp": [ {"type":"MODULE","name":"t","addr":"(E)","loc":"d,7:8,7:9","origName":"t","verilogName":"t","level":1,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(F)","loc":"d,16:21,16:22","dtypep":"(G)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(H)","loc":"d,14:9,14:12","dtypep":"(I)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(J)","loc":"d,15:15,15:16","dtypep":"(G)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"between","addr":"(K)","loc":"d,18:15,18:22","dtypep":"(G)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"direct_named","addr":"(L)","loc":"d,19:9,19:21","dtypep":"(I)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"computed_named","addr":"(M)","loc":"d,20:9,20:23","dtypep":"(I)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"anonymous_expr","addr":"(N)","loc":"d,21:9,21:23","dtypep":"(I)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"S_IDLE","addr":"(O)","loc":"d,23:26,23:32","dtypep":"(P)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(Q)","loc":"d,23:35,23:40","dtypep":"(R)"} @@ -122,7 +122,7 @@ "stmtsp": [ {"type":"VAR","name":"clk","addr":"(QC)","loc":"d,62:11,62:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(KC)","loc":"d,63:17,63:18","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(NC)","loc":"d,64:23,64:24","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(SC)","loc":"d,67:12,67:13","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(TC)","loc":"d,67:12,67:13","dtypep":"(G)", @@ -142,7 +142,7 @@ ],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(CC)","loc":"d,50:11,50:14","dtypep":"(I)","origName":"clk","verilogName":"clk","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(FC)","loc":"d,51:23,51:24","dtypep":"(G)","origName":"d","verilogName":"d","direction":"INPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(ZB)","loc":"d,52:30,52:31","dtypep":"(G)","origName":"q","verilogName":"q","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"IGNORED","addr":"(AD)","loc":"d,55:14,55:21","dtypep":"(XC)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(BD)","loc":"d,55:24,55:25","dtypep":"(ZC)"} diff --git a/test_regress/t/t_json_only_flat.out b/test_regress/t/t_json_only_flat.out index 727724001..e90bba08b 100644 --- a/test_regress/t/t_json_only_flat.out +++ b/test_regress/t/t_json_only_flat.out @@ -2,16 +2,16 @@ "modulesp": [ {"type":"MODULE","name":"$root","addr":"(F)","loc":"d,7:8,7:9","origName":"$root","verilogName":"$root","level":1,"modPublic":true,"timeunit":"1ps","inlinesp": [], "stmtsp": [ - {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"q","addr":"(G)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"clk","addr":"(I)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"d","addr":"(K)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.q","addr":"(L)","loc":"d,16:21,16:22","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.clk","addr":"(M)","loc":"d,14:9,14:12","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.d","addr":"(N)","loc":"d,15:15,15:16","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.between","addr":"(O)","loc":"d,18:15,18:22","dtypep":"(H)","origName":"between","verilogName":"between","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.direct_named","addr":"(P)","loc":"d,19:9,19:21","dtypep":"(J)","origName":"direct_named","verilogName":"direct_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.computed_named","addr":"(Q)","loc":"d,20:9,20:23","dtypep":"(J)","origName":"computed_named","verilogName":"computed_named","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.anonymous_expr","addr":"(R)","loc":"d,21:9,21:23","dtypep":"(J)","origName":"anonymous_expr","verilogName":"anonymous_expr","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"VAR","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.S_IDLE","addr":"(S)","loc":"d,23:26,23:32","dtypep":"(T)","origName":"S_IDLE","verilogName":"S_IDLE","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"2'h0","addr":"(U)","loc":"d,23:35,23:40","dtypep":"(V)"} @@ -30,14 +30,14 @@ ],"attrsp": []}, {"type":"VAR","name":"t.cell1.clk","addr":"(EB)","loc":"d,50:11,50:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.d","addr":"(FB)","loc":"d,51:23,51:24","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell1.q","addr":"(GB)","loc":"d,52:30,52:31","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell1.IGNORED","addr":"(HB)","loc":"d,55:14,55:21","dtypep":"(BB)","origName":"IGNORED","verilogName":"IGNORED","direction":"NONE","isConst":true,"lifetime":"VSTATICI","varType":"LPARAM","dtypeName":"logic","isParam":true,"hasUserInit":true,"sensIfacep":"UNLINKED","childDTypep": [],"delayp": [], "valuep": [ {"type":"CONST","name":"32'sh1","addr":"(IB)","loc":"d,55:24,55:25","dtypep":"(DB)"} ],"attrsp": []}, {"type":"VAR","name":"t.cell2.clk","addr":"(JB)","loc":"d,62:11,62:14","dtypep":"(J)","origName":"clk","verilogName":"clk","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"t.cell2.d","addr":"(KB)","loc":"d,63:17,63:18","dtypep":"(H)","origName":"d","verilogName":"d","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"t.cell2.q","addr":"(LB)","loc":"d,64:23,64:24","dtypep":"(H)","origName":"q","verilogName":"q","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:9","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(MB)","loc":"d,7:8,7:9","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_flat_vlvbound.out b/test_regress/t/t_json_only_flat_vlvbound.out index ff23ac385..1725da9ef 100644 --- a/test_regress/t/t_json_only_flat_vlvbound.out +++ b/test_regress/t/t_json_only_flat_vlvbound.out @@ -4,12 +4,12 @@ "stmtsp": [ {"type":"VAR","name":"i_a","addr":"(G)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"i_b","addr":"(I)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_a","addr":"(J)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"o_b","addr":"(L)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_a","addr":"(M)","loc":"d,8:24,8:27","dtypep":"(H)","origName":"i_a","verilogName":"i_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"vlvbound_test.i_b","addr":"(N)","loc":"d,9:24,9:27","dtypep":"(H)","origName":"i_b","verilogName":"i_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_a","addr":"(O)","loc":"d,10:24,10:27","dtypep":"(K)","origName":"o_a","verilogName":"o_a","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"vlvbound_test.o_b","addr":"(P)","loc":"d,11:24,11:27","dtypep":"(K)","origName":"o_b","verilogName":"o_b","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"TOPSCOPE","name":"","addr":"(E)","loc":"d,7:8,7:21","senTreesp": [], "scopep": [ {"type":"SCOPE","name":"TOP","addr":"(Q)","loc":"d,7:8,7:21","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(F)", diff --git a/test_regress/t/t_json_only_primary_io.out b/test_regress/t/t_json_only_primary_io.out index 896aa716b..a611f6917 100644 --- a/test_regress/t/t_json_only_primary_io.out +++ b/test_regress/t/t_json_only_primary_io.out @@ -5,8 +5,8 @@ {"type":"VAR","name":"clk","addr":"(F)","loc":"d,13:9,13:12","dtypep":"(G)","origName":"clk","verilogName":"clk","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a1","addr":"(H)","loc":"d,14:9,14:11","dtypep":"(G)","origName":"a1","verilogName":"a1","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(I)","loc":"d,15:9,15:11","dtypep":"(G)","origName":"a2","verilogName":"a2","isPrimaryIO":true,"direction":"INPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready","addr":"(J)","loc":"d,16:10,16:15","dtypep":"(G)","origName":"ready","verilogName":"ready","isPrimaryIO":true,"direction":"OUTPUT","isSigPublic":true,"icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"PORT","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"ready_reg","addr":"(K)","loc":"d,18:8,18:17","dtypep":"(G)","origName":"ready_reg","verilogName":"ready_reg","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"CELL","name":"and_cell","addr":"(L)","loc":"d,20:11,20:19","origName":"and_cell","verilogName":"and_cell","modp":"(M)", "pinsp": [ {"type":"PIN","name":"a1","addr":"(N)","loc":"d,21:8,21:10","svDotName":true,"modVarp":"(O)","modPTypep":"UNLINKED", @@ -37,7 +37,7 @@ "stmtsp": [ {"type":"VAR","name":"a1","addr":"(O)","loc":"d,30:16,30:18","dtypep":"(G)","origName":"a1","verilogName":"a1","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"a2","addr":"(R)","loc":"d,31:16,31:18","dtypep":"(G)","origName":"a2","verilogName":"a2","direction":"INPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"zn","addr":"(U)","loc":"d,32:17,32:19","dtypep":"(G)","origName":"zn","verilogName":"zn","direction":"OUTPUT","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(AB)","loc":"d,34:13,34:14","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(BB)","loc":"d,34:13,34:14","dtypep":"(G)", diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index c0d396689..c7e9999c6 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -9,7 +9,7 @@ {"type":"CELL","name":"itop","addr":"(L)","loc":"d,29:7,29:11","origName":"itop","verilogName":"itop","modp":"(M)","pinsp": [],"paramsp": [],"rangep": [],"intfRefsp": []}, {"type":"VAR","name":"itop","addr":"(N)","loc":"d,29:7,29:11","dtypep":"(O)","origName":"itop__Viftop","verilogName":"itop__Viftop","direction":"NONE","lifetime":"VSTATICI","varType":"IFACEREF","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"VAR","name":"this_struct","addr":"(P)","loc":"d,31:13,31:24","dtypep":"(Q)","origName":"this_struct","verilogName":"this_struct","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, - {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, + {"type":"VAR","name":"dotted","addr":"(R)","loc":"d,33:15,33:21","dtypep":"(S)","origName":"dotted","verilogName":"dotted","direction":"NONE","icoMaybeWritten":true,"lifetime":"VSTATICI","varType":"WIRE","dtypeName":"logic","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []}, {"type":"ALWAYS","name":"","addr":"(T)","loc":"d,33:22,33:23","keyword":"cont_assign","sentreep": [], "stmtsp": [ {"type":"ASSIGNW","name":"","addr":"(U)","loc":"d,33:22,33:23","dtypep":"(S)", diff --git a/test_regress/t/t_opt_balance_cats.py b/test_regress/t/t_opt_balance_cats.py index 577c19985..d5e863848 100755 --- a/test_regress/t/t_opt_balance_cats.py +++ b/test_regress/t/t_opt_balance_cats.py @@ -14,7 +14,7 @@ test.scenarios('vlt') test.compile( verilator_flags2=["--stats", "--build", "--gate-stmts", "10000", "--expand-limit", "128"]) -test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 2) +test.file_grep(test.stats, r'Optimizations, FuncOpt concat trees balanced\s+(\d+)', 3) test.file_grep(test.stats, r'Optimizations, FuncOpt concat splits\s+(\d+)', 67) test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp new file mode 100644 index 000000000..27e7a3d39 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.cpp @@ -0,0 +1,45 @@ +// -*- mode: C++; c-file-style: "cc-mode" -*- +// +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +#include + +#include +#include + +#include VM_PREFIX_INCLUDE + +int main(int argc, char** argv) { + const std::unique_ptr contextp{new VerilatedContext}; + contextp->threads(1); + contextp->commandArgs(argc, argv); + + const std::unique_ptr topp{new VM_PREFIX{contextp.get(), "top"}}; + topp->clk = 0; + topp->i = 0; + topp->eval(); + + while ((contextp->time() < 10000) && !contextp->gotFinish()) { + contextp->timeInc(1); + topp->clk = !topp->clk; + // Always set to the same constant value, so change detection will think it's not changing + topp->i = 500000; + topp->eval(); + if (topp->o != topp->i + 10) { + const std::string msg = "%Error: incorrect output, got: " + std::to_string(topp->o) + + " expected: " + std::to_string(topp->i + 10); + vl_fatal(__FILE__, __LINE__, "main", msg.c_str()); + break; + } + } + + if (!contextp->gotFinish()) { + vl_fatal(__FILE__, __LINE__, "main", "%Error: Timeout; never got a $finish"); + } + topp->final(); + return 0; +} diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.py b/test_regress/t/t_sched_ico_change_detect_input_assigned.py new file mode 100755 index 000000000..c837fa2b9 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.py @@ -0,0 +1,28 @@ +#!/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('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp" + ]) + +test.execute() + +# There should be a change detect for 'clk', but not for 'i' +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v new file mode 100644 index 000000000..0f6f9025d --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -0,0 +1,38 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Wilson Snyder +// SPDX-License-Identifier: CC0-1.0 + +// verilog_format: off +`define stop $stop +`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t( + clk, i, o, cyc +); + + input clk, i; + output o, cyc; + + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; + + // Logic dependent on primary input 'i' + always_comb o = i + 10; + + always @(posedge clk) begin + cyc <= cyc + 1; + // On even cycles, assign 'i' + if (cyc % 2 == 0) i = cyc + 1000; + if (cyc == 99) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end + +endmodule diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py new file mode 100755 index 000000000..b98a8e1b6 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_off.py @@ -0,0 +1,27 @@ +#!/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('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fno-ico-change-detect" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py new file mode 100755 index 000000000..cd22fef43 --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi.py @@ -0,0 +1,27 @@ +#!/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('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 0) + +test.passes() diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py new file mode 100755 index 000000000..afd44e7ce --- /dev/null +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned_vpi_on.py @@ -0,0 +1,27 @@ +#!/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('vlt') + +test.top_filename = "t/t_sched_ico_change_detect_input_assigned.v" + +test.compile(make_top_shell=False, + make_main=False, + verilator_flags2=[ + "--exe", "--stats", "-Wno-ASSIGNIN", + "t/t_sched_ico_change_detect_input_assigned.cpp", "-fico-change-detect", "--vpi" + ]) + +test.execute() + +test.file_grep(test.stats, r"Scheduling, 'ico' change detect triggers\s+(\d+)", 1) + +test.passes() diff --git a/test_regress/t/t_wrapper_context__top0.dat.out b/test_regress/t/t_wrapper_context__top0.dat.out index 07d4800da..ae165bca9 100644 --- a/test_regress/t/t_wrapper_context__top0.dat.out +++ b/test_regress/t/t_wrapper_context__top0.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop0.top' 11 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop0.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop0.top' 10 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 34 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop0.top' 13 C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop0.top' 0 -C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 34 +C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop0.top' 13 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop0.top' 0 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop0.top' 0 C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop0.top' 0 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop0.top' 1 -C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 33 +C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop0.top' 12 diff --git a/test_regress/t/t_wrapper_context__top1.dat.out b/test_regress/t/t_wrapper_context__top1.dat.out index 8ef66f3d0..073c8c4b5 100644 --- a/test_regress/t/t_wrapper_context__top1.dat.out +++ b/test_regress/t/t_wrapper_context__top1.dat.out @@ -139,12 +139,12 @@ C 'ft/t_wrapper_context.vl21n3tlinepagev_line/topoblockS21-24,28,3 C 'ft/t_wrapper_context.vl35n3tlinepagev_line/topoblockS35htop1.top' 6 C 'ft/t_wrapper_context.vl36n5tbranchpagev_branch/topoifS36htop1.top' 1 C 'ft/t_wrapper_context.vl36n6tbranchpagev_branch/topoelseS37htop1.top' 5 -C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 19 -C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 19 +C 'ft/t_wrapper_context.vl39n3tlinepagev_line/topoblockS39-40htop1.top' 8 +C 'ft/t_wrapper_context.vl41n5tbranchpagev_branch/topoifS41htop1.top' 8 C 'ft/t_wrapper_context.vl41n6tbranchpagev_branch/topoelseS47htop1.top' 0 -C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 18 +C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==0) => 0htop1.top' 7 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo((counter >= 32'sh5)==1 && stop==1) => 1htop1.top' 1 C 'ft/t_wrapper_context.vl42n24texprpagev_expr/topo(stop==0) => 0htop1.top' 0 -C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 18 +C 'ft/t_wrapper_context.vl42n8tlinepagev_line/topoelsehtop1.top' 7 C 'ft/t_wrapper_context.vl48n7tbranchpagev_branch/topoifS48-50htop1.top' 0 C 'ft/t_wrapper_context.vl48n8tbranchpagev_branch/topoelsehtop1.top' 0 From a07a980b73cdde207a096e74a797f9cd0cc4da31 Mon Sep 17 00:00:00 2001 From: Geza Lore Date: Mon, 15 Jun 2026 09:17:41 +0100 Subject: [PATCH 39/46] Internals: Do not overload AstVar::isPrimaryIO() for sampled value vars --- src/V3Sampled.cpp | 2 -- src/V3Sched.cpp | 2 +- src/V3SchedReplicate.cpp | 4 ++-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/V3Sampled.cpp b/src/V3Sampled.cpp index 4fb45e772..841b32d55 100644 --- a/src/V3Sampled.cpp +++ b/src/V3Sampled.cpp @@ -51,8 +51,6 @@ class SampledVisitor final : public VNVisitor { AstVar* const newvarp = new AstVar{flp, VVarType::MODULETEMP, newvarname, varp->dtypep()}; m_scopep->modp()->addStmtsp(newvarp); AstVarScope* const newvscp = new AstVarScope{flp, m_scopep, newvarp}; - newvarp->direction(VDirection::INPUT); // Inform V3Sched that it will be driven later - newvarp->primaryIO(true); newvarp->sampled(true); vscp->user1p(newvscp); m_scopep->addVarsp(newvscp); diff --git a/src/V3Sched.cpp b/src/V3Sched.cpp index 217cd3f95..6078fbbf0 100644 --- a/src/V3Sched.cpp +++ b/src/V3Sched.cpp @@ -608,7 +608,7 @@ AstNode* createInputCombLoop(AstNetlist* netlistp, AstCFunc* const initFuncp, auto it = inp2changedp.find(vscp); if (it != inp2changedp.end()) { out.push_back(it->second); - } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic()) { + } else if (varp->isPrimaryInish() || varp->isSigUserRWPublic() || varp->sampled()) { if (!firstIterTriggerp) { firstIterTriggerp = trigKit.newExtraTriggerSenTree(trigKit.vscp(), firstIterationTrigger); diff --git a/src/V3SchedReplicate.cpp b/src/V3SchedReplicate.cpp index 35718b98a..3aa404018 100644 --- a/src/V3SchedReplicate.cpp +++ b/src/V3SchedReplicate.cpp @@ -150,8 +150,8 @@ public: : SchedReplicateVertex{graphp} , m_vscp{vscp} { // Top level inputs are - if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->isWrittenByDpi() - || varp()->sensIfacep() || varp()->isVirtIface()) { + if (varp()->isPrimaryInish() || varp()->isSigUserRWPublic() || varp()->sampled() + || varp()->isWrittenByDpi() || varp()->sensIfacep() || varp()->isVirtIface()) { addDrivingRegions(INPUT); } // Currently we always execute suspendable processes at the beginning of From 969a775ae503a89fe867f87e6d57c326f2792c0b Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 13:33:55 +0200 Subject: [PATCH 40/46] Support assertion control system tasks in classes and interfaces (#7761) --- src/V3Assert.cpp | 6 - test_regress/t/t_assert_ctl_immediate.out | 19 ++- test_regress/t/t_assert_ctl_immediate.v | 144 ++++++++++++++++++++++ test_regress/t/t_assert_ctl_unsup.out | 124 ++++++++----------- test_regress/t/t_assert_ctl_unsup.v | 134 ++------------------ 5 files changed, 218 insertions(+), 209 deletions(-) diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index 6b3fcb777..47c1874f3 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -1001,12 +1001,6 @@ class AssertVisitor final : public VNVisitor { visitAssertionIterate(nodep, nodep->failsp()); } void visit(AstAssertCtl* nodep) override { - if (VN_IS(m_modp, Class) || VN_IS(m_modp, Iface)) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: assertcontrols in classes or interfaces"); - VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep); - return; - } - iterateChildren(nodep); if (!resolveAssertType(nodep)) { diff --git a/test_regress/t/t_assert_ctl_immediate.out b/test_regress/t/t_assert_ctl_immediate.out index a10fd21c4..58b76b5f2 100644 --- a/test_regress/t/t_assert_ctl_immediate.out +++ b/test_regress/t/t_assert_ctl_immediate.out @@ -1,6 +1,15 @@ -[0] %Error: t_assert_ctl_immediate.v:51: Assertion failed in top.t.module_with_assertctl: 'assert' failed. --Info: t/t_assert_ctl_immediate.v:51: Verilog $stop, ignored due to +verilator+error+limit -[0] %Error: t_assert_ctl_immediate.v:57: Assertion failed in top.t.module_with_assertctl: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. -[0] %Error: t_assert_ctl_immediate.v:45: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:52: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +-Info: t/t_assert_ctl_immediate.v:52: Verilog $stop, ignored due to +verilator+error+limit +[0] %Error: t_assert_ctl_immediate.v:58: Assertion failed in top.t.module_with_assertctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:46: Assertion failed in top.t.module_with_assertctl.f_assert: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:159: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:132: Assertion failed in top.t.module_with_method_ctl.Ctl.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:169: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:93: Assertion failed in top.t.module_with_method_ctl.iface.check_positive: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:180: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:186: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:195: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:199: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. +[0] %Error: t_assert_ctl_immediate.v:203: Assertion failed in top.t.module_with_method_ctl: 'assert' failed. *-* All Finished *-* diff --git a/test_regress/t/t_assert_ctl_immediate.v b/test_regress/t/t_assert_ctl_immediate.v index 6c5848aaf..cc9d22b1b 100644 --- a/test_regress/t/t_assert_ctl_immediate.v +++ b/test_regress/t/t_assert_ctl_immediate.v @@ -10,6 +10,7 @@ module t ( module_with_assert module_with_assert (clk); module_with_assertctl module_with_assertctl (clk); + module_with_method_ctl module_with_method_ctl (); always @(posedge clk) begin assert (0); @@ -63,3 +64,146 @@ module module_with_assertctl ( f_assert(); end endmodule + +// Assertion control invoked from class methods and interface functions, in a +// single sub-module with a single initial block so the golden output order is +// stable across --fno-inline (otherwise parallel sub-module initials are +// interleaved differently by the inliner). +// +// Covers: +// - class task $assertoff / $asserton +// - class static method $assertcontrol(Off=4 / On=3), IEEE 1800-2023 Table 20-5 +// - $assertkill from a class method +// - assert that lives inside a class method (obeys context-global gating) +// - interface function $assertoff / $asserton +// - assert inside an interface function +// - virtual interface dispatch to an assertion-control function +// - interface class (AstClass with isInterfaceClass) via a concrete impl +// - multiple instances of each thing: OFF via one instance, ON via another +// (assertion control is global per-context, IEEE 1800-2023 20.11) + +interface AssertCtlIf; + function void suppress(); + $assertoff; + endfunction + function void enable(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction +endinterface + +// verilog_format: off (verible-verilog-format mangles `pure virtual function`) +interface class IAssertCtl; + pure virtual function void suppress(); + pure virtual function void enable(); +endclass +// verilog_format: on + +class IAssertCtlImpl implements IAssertCtl; + virtual function void suppress(); + $assertoff; + endfunction + virtual function void enable(); + $asserton; + endfunction +endclass + +module module_with_method_ctl; + class Ctl; + virtual AssertCtlIf vif; + static function void off_all(); + $assertcontrol(4); + endfunction + static function void on_all(); + $asserton; + endfunction + function void kill_all(); + $assertkill; + endfunction + function void inst_off(); + $assertoff; + endfunction + function void inst_on(); + $asserton; + endfunction + function void check_positive(int v); + assert (v > 0); + endfunction + function void vif_suppress(); + vif.suppress(); + endfunction + function void vif_enable(); + vif.enable(); + endfunction + endclass + + Ctl c; + Ctl c2; + AssertCtlIf iface (); + AssertCtlIf iface2 (); + IAssertCtlImpl impl; + IAssertCtlImpl impl2; + + initial begin + c = new; + c2 = new; + impl = new; + impl2 = new; + + // --- class method coverage --- + Ctl::off_all(); + assert (0); // gated via class static -> no fire + Ctl::on_all(); + assert (0); // fires + Ctl::off_all(); + c.check_positive(-1); // assert inside class method, gated -> no fire + Ctl::on_all(); + c.check_positive(-2); // assert inside class method, fires + + // --- interface function coverage --- + iface.suppress(); + assert (0); // gated via iface fn -> no fire + iface.enable(); + assert (0); // fires + iface.suppress(); + iface.check_positive(-1); // assert inside iface fn, gated -> no fire + iface.enable(); + iface.check_positive(-2); // assert inside iface fn, fires + + // --- virtual interface dispatch coverage --- + c.vif = iface; + c.vif_suppress(); + assert (0); // gated via virtual interface dispatch -> no fire + c.vif_enable(); + assert (0); // fires + + // --- interface class via concrete impl --- + impl.suppress(); + assert (0); // gated via interface-class impl -> no fire + impl.enable(); + assert (0); // fires + + // --- multiple instances: OFF via one instance, ON via another --- + // Assertion control is global per-context (IEEE 1800-2023 20.11, no scope + // list), so OFF issued via one instance gates every assertion and ON issued + // via a different instance re-enables them. + c.inst_off(); // class: OFF via c + assert (0); // gated -> no fire + c2.inst_on(); // class: ON via c2 + assert (0); // fires + iface.suppress(); // interface: OFF via iface + assert (0); // gated -> no fire + iface2.enable(); // interface: ON via iface2 + assert (0); // fires + impl.suppress(); // interface class: OFF via impl + assert (0); // gated -> no fire + impl2.enable(); // interface class: ON via impl2 + assert (0); // fires + + // --- $assertkill (last: terminal per IEEE 1800-2023 Table 20-5) --- + c.kill_all(); + assert (0); // killed -> no fire + end +endmodule diff --git a/test_regress/t/t_assert_ctl_unsup.out b/test_regress/t/t_assert_ctl_unsup.out index 26e32ff0d..b00a2a58f 100644 --- a/test_regress/t/t_assert_ctl_unsup.out +++ b/test_regress/t/t_assert_ctl_unsup.out @@ -1,123 +1,103 @@ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:25:5: Unsupported: non-constant assert assertion-type expression +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:26:5: Unsupported: non-constant assert assertion-type expression : ... note: In instance 't.unsupported_ctl_type' - 25 | $assertcontrol(Lock, a); + 26 | $assertcontrol(Lock, a); | ^~~~~~~~~~~~~~ ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:27:5: Unsupported: $assertcontrol control_type '2' - 27 | $assertcontrol(Unlock); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:28:5: Unsupported: $assertcontrol control_type '2' + 28 | $assertcontrol(Unlock); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:29:5: Unsupported: $assertcontrol control_type '6' - 29 | $assertcontrol(PassOn); +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: $assertcontrol control_type '6' + 30 | $assertcontrol(PassOn); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:30:5: Unsupported: assert control assertion_type - : ... note: In instance 't.unsupported_ctl_type' - 30 | $assertpasson; - | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:31:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 31 | $assertpasson(a); + 31 | $assertpasson; | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:32:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 32 | $assertpasson(a, t); + 32 | $assertpasson(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:34:5: Unsupported: $assertcontrol control_type '7' - 34 | $assertcontrol(PassOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:33:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 35 | $assertpassoff; + 33 | $assertpasson(a, t); + | ^~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:35:5: Unsupported: $assertcontrol control_type '7' + 35 | $assertcontrol(PassOff); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:36:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 36 | $assertpassoff(a); + 36 | $assertpassoff; | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:37:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 37 | $assertpassoff(a, t); + 37 | $assertpassoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:39:5: Unsupported: $assertcontrol control_type '8' - 39 | $assertcontrol(FailOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:38:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 40 | $assertfailon; - | ^~~~~~~~~~~~~ + 38 | $assertpassoff(a, t); + | ^~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:40:5: Unsupported: $assertcontrol control_type '8' + 40 | $assertcontrol(FailOn); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:41:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 41 | $assertfailon(a); + 41 | $assertfailon; | ^~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:42:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 42 | $assertfailon(a, t); + 42 | $assertfailon(a); | ^~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:44:5: Unsupported: $assertcontrol control_type '9' - 44 | $assertcontrol(FailOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:43:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 45 | $assertfailoff; + 43 | $assertfailon(a, t); + | ^~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:45:5: Unsupported: $assertcontrol control_type '9' + 45 | $assertcontrol(FailOff); | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:46:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 46 | $assertfailoff(a); + 46 | $assertfailoff; | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:47:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 47 | $assertfailoff(a, t); + 47 | $assertfailoff(a); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:49:5: Unsupported: $assertcontrol control_type '10' - 49 | $assertcontrol(NonvacuousOn); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:48:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 50 | $assertnonvacuouson; - | ^~~~~~~~~~~~~~~~~~~ + 48 | $assertfailoff(a, t); + | ^~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:50:5: Unsupported: $assertcontrol control_type '10' + 50 | $assertcontrol(NonvacuousOn); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:51:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 51 | $assertnonvacuouson(a); + 51 | $assertnonvacuouson; | ^~~~~~~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:52:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 52 | $assertnonvacuouson(a, t); + 52 | $assertnonvacuouson(a); | ^~~~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:54:5: Unsupported: $assertcontrol control_type '11' - 54 | $assertcontrol(VacuousOff); - | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: assert control assertion_type +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:53:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 55 | $assertvacuousoff; - | ^~~~~~~~~~~~~~~~~ + 53 | $assertnonvacuouson(a, t); + | ^~~~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:55:5: Unsupported: $assertcontrol control_type '11' + 55 | $assertcontrol(VacuousOff); + | ^~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:56:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 56 | $assertvacuousoff(a); + 56 | $assertvacuousoff; | ^~~~~~~~~~~~~~~~~ %Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:57:5: Unsupported: assert control assertion_type : ... note: In instance 't.unsupported_ctl_type' - 57 | $assertvacuousoff(a, t); + 57 | $assertvacuousoff(a); | ^~~~~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:64:5: Unsupported: non-const assert control type expression +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:58:5: Unsupported: assert control assertion_type + : ... note: In instance 't.unsupported_ctl_type' + 58 | $assertvacuousoff(a, t); + | ^~~~~~~~~~~~~~~~~ +%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:65:5: Unsupported: non-const assert control type expression : ... note: In instance 't.unsupported_ctl_type_expr' - 64 | $assertcontrol(ctl_type); + 65 | $assertcontrol(ctl_type); | ^~~~~~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:93:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 93 | $asserton; - | ^~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:99:7: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_class' - 99 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:172:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface' - 172 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:138:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 138 | $assertoff; - | ^~~~~~~~~~ -%Error-UNSUPPORTED: t/t_assert_ctl_unsup.v:145:5: Unsupported: assertcontrols in classes or interfaces - : ... note: In instance 't.assert_iface_class' - 145 | $asserton; - | ^~~~~~~~~ %Error: Exiting due to diff --git a/test_regress/t/t_assert_ctl_unsup.v b/test_regress/t/t_assert_ctl_unsup.v index 98afcad10..90d63e86c 100644 --- a/test_regress/t/t_assert_ctl_unsup.v +++ b/test_regress/t/t_assert_ctl_unsup.v @@ -4,15 +4,16 @@ // SPDX-FileCopyrightText: 2024 Antmicro // SPDX-License-Identifier: CC0-1.0 -module t(input logic clk); - unsupported_ctl_type unsupported_ctl_type(clk ? 1 : 2); - unsupported_ctl_type_expr unsupported_ctl_type_expr(); - assert_class assert_class(); - assert_iface assert_iface(); - assert_iface_class assert_iface_class(); +module t ( + input logic clk +); + unsupported_ctl_type unsupported_ctl_type (clk ? 1 : 2); + unsupported_ctl_type_expr unsupported_ctl_type_expr (); endmodule -module unsupported_ctl_type(input int a); +module unsupported_ctl_type ( + input int a +); initial begin let Lock = 1; let Unlock = 2; @@ -64,122 +65,3 @@ module unsupported_ctl_type_expr; $assertcontrol(ctl_type); end endmodule - -module assert_class; - virtual class AssertCtl; - pure virtual function void virtual_assert_ctl(); - endclass - - class AssertCls; - static function void static_function(); - assert(0); - endfunction - static task static_task(); - assert(0); - endtask - function void assert_function(); - assert(0); - endfunction - task assert_task(); - assert(0); - endtask - virtual function void virtual_assert(); - assert(0); - endfunction - endclass - - class AssertOn extends AssertCtl; - virtual function void virtual_assert_ctl(); - $asserton; - endfunction - endclass - - class AssertOff extends AssertCtl; - virtual function void virtual_assert_ctl(); - $assertoff; - endfunction - endclass - - AssertCls assertCls; - AssertOn assertOn; - AssertOff assertOff; - initial begin - $assertoff; - AssertCls::static_function(); - AssertCls::static_task(); - $asserton; - AssertCls::static_function(); - AssertCls::static_task(); - - assertCls = new; - assertOn = new; - assertOff = new; - - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - - assertOn.virtual_assert_ctl(); - assertCls.assert_function(); - assertCls.assert_task(); - assertCls.virtual_assert(); - assertOff.virtual_assert_ctl(); - assertCls.assert_function(); - end -endmodule - -interface Iface; - function void assert_func(); - assert(0); - endfunction - - function void assertoff_func(); - $assertoff; - endfunction - - initial begin - assertoff_func(); - assert(0); - assert_func(); - $asserton; - assert(0); - assert_func(); - end -endinterface - -module assert_iface; - Iface iface(); - virtual Iface vIface = iface; - initial begin - vIface.assert_func(); - vIface.assertoff_func(); - vIface.assert_func(); - - iface.assert_func(); - iface.assertoff_func(); - iface.assert_func(); - end -endmodule - -interface class IfaceClass; - pure virtual function void assertoff_func(); - pure virtual function void assert_func(); -endclass - -class IfaceClassImpl implements IfaceClass; - virtual function void assertoff_func(); - $assertoff; - endfunction - virtual function void assert_func(); - assert(0); - endfunction -endclass - -module assert_iface_class; - IfaceClassImpl ifaceClassImpl = new; - initial begin - ifaceClassImpl.assertoff_func(); - ifaceClassImpl.assert_func(); - end -endmodule From 077558a9b0d6968bd9d3738b054108c28bbd73f7 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 14:36:21 +0200 Subject: [PATCH 41/46] Support cover sequence statement (#7764) --- src/V3AssertNfa.cpp | 167 +++++++++++++++++++--- src/V3AstNodeStmt.h | 6 + src/V3AstNodes.cpp | 8 ++ src/verilog.y | 30 +++- test_regress/t/t_cover_sequence.py | 18 +++ test_regress/t/t_cover_sequence.v | 90 ++++++++++++ test_regress/t/t_cover_sequence_unsup.out | 18 +++ test_regress/t/t_cover_sequence_unsup.py | 18 +++ test_regress/t/t_cover_sequence_unsup.v | 35 +++++ test_regress/t/t_debug_emitv.out | 5 + test_regress/t/t_debug_emitv.v | 2 + test_regress/t/t_sequence_sexpr_unsup.out | 11 -- 12 files changed, 369 insertions(+), 39 deletions(-) create mode 100755 test_regress/t/t_cover_sequence.py create mode 100644 test_regress/t/t_cover_sequence.v create mode 100644 test_regress/t/t_cover_sequence_unsup.out create mode 100755 test_regress/t/t_cover_sequence_unsup.py create mode 100644 test_regress/t/t_cover_sequence_unsup.v diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index 8db3e7761..c5c36178d 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -177,6 +177,10 @@ struct BuildResult final { // Mid-window sources for range delays (pure boolean RHS): match-only (isUnbounded) std::vector midSources; bool errorEmitted = false; // Builder already emitted specific error; skip generic + // For cover_sequence: when true, midSources already enumerate every + // end-of-match, so wireMatchAndMidSources must NOT add the main + // termVtxp -> matchVertex Link (would double-count via the merge vertex). + bool termIsMidMerge = false; bool valid() const { return termVertexp != nullptr; } static BuildResult fail(bool errored = false) { return {nullptr, nullptr, {}, errored}; } static BuildResult failWithError() { return {nullptr, nullptr, {}, true}; } @@ -199,6 +203,10 @@ class SvaNfaBuilder final { // (IEEE 1800-2023 16.12.14 outer-wraps-inner). std::vector m_outerAbortStack; bool m_inUnboundedScope = false; // Sticky: nodes created after inherit liveness + // IEEE 1800-2023 16.14.3 cover sequence: each end-of-match fires the action, + // not just the first. Builder builds parallel-branch (no first-match-wins) + // topology when true. Default false preserves cover_property semantics. + bool m_isCoverSeq = false; AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; @@ -406,6 +414,16 @@ class SvaNfaBuilder final { // count is O(1) in range regardless of user input; no adversarial N // blowup is possible. constexpr int kChainLimit = 256; + // IEEE 1800-2023 16.14.3: only a small bounded range before a plain + // boolean enumerates every end-of-match below. The counter FSM drops + // overlapping ends and the nested-sequence merge collapses them, so + // reject those for a cover sequence rather than under-count. + if (m_isCoverSeq && (range > kChainLimit || VN_IS(rhsExprp, SExpr))) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with this ranged cycle delay"); + outErrorEmitted = true; + return false; + } if (range > kChainLimit) { // Large range: counter FSM. Overlapping triggers during an active // count are dropped (non-overlapping semantics only). @@ -428,13 +446,21 @@ class SvaNfaBuilder final { } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. - AstVar* const hoistVarp = tryHoistSampled(rhsExprp, flp, range); + // For cover_sequence (IEEE 1800-2023 16.14.3) the advance edge is + // unconditional so every (start, end) pair fires independently -- + // dropping NOT(b) turns "first-match-wins" into "every end fires". + AstVar* const hoistVarp + = m_isCoverSeq ? nullptr : tryHoistSampled(rhsExprp, flp, range); midSources.push_back(currentp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); - AstNodeExpr* const notExprp - = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; - guardedEdge(currentp, nextVtxp, notExprp, flp); + if (m_isCoverSeq) { + guardedEdge(currentp, nextVtxp, flp); + } else { + AstNodeExpr* const notExprp + = new AstLogNot{flp, sampledRefOrClone(hoistVarp, rhsExprp, flp)}; + guardedEdge(currentp, nextVtxp, notExprp, flp); + } if (i < range - 1) midSources.push_back(nextVtxp); currentp = nextVtxp; } @@ -517,6 +543,10 @@ class SvaNfaBuilder final { if (exceedsAssertUnrollLimit(repp, totalSites)) return BuildResult::failWithError(); AstVar* const hoistVarp = tryHoistSampled(exprp, flp, totalSites); + // Cover-sequence (IEEE 1800-2023 16.14.3): collect each end-of-match + // position so they all fire the action, not just the merged terminal. + std::vector consMidSources; + SvaStateVertex* currentp = entryVtxp; for (int i = 0; i < minN; ++i) { if (i > 0) { @@ -532,6 +562,10 @@ class SvaNfaBuilder final { if (isTopLevelStep && (i == 0 || i == minN - 1)) { linkp->m_rejectOnFail = true; } currentp = condVtxp; } + // After minN: currentp is the first valid end-of-match position for [*m:n]. + if (m_isCoverSeq && (repp->unbounded() || repp->maxCountp())) { + consMidSources.push_back(currentp); + } if (repp->unbounded()) { if (minN == 0) { @@ -565,11 +599,19 @@ class SvaNfaBuilder final { guardedLink(nextVtxp, checkVtxp, sampledRefOrClone(hoistVarp, exprp, flp), flp); guardedLink(checkVtxp, mergeVtxp, flp); currentp = checkVtxp; + if (m_isCoverSeq) consMidSources.push_back(checkVtxp); } currentp = mergeVtxp; } // finalCond = nullptr (already checked via Links) - return {currentp, nullptr, {}}; + BuildResult res; + res.termVertexp = currentp; + res.finalCondp = nullptr; + res.midSources = std::move(consMidSources); + // mergeVtxp is the OR of all the end-positions we already pushed to + // midSources, so the main termVtxp -> matchVertex Link would duplicate. + res.termIsMidMerge = m_isCoverSeq && !res.midSources.empty(); + return res; } // always[lo:hi] / s_always[lo:hi] (IEEE 1800-2023 16.12.11). @@ -608,6 +650,16 @@ class SvaNfaBuilder final { UASSERT_OBJ(maxN >= minN, repp, "GotoRep range max < min (V3Width invariant)"); if (exceedsAssertUnrollLimit(repp, maxN)) return BuildResult::failWithError(); + // IEEE 1800-2023 16.14.3: a ranged goto repetition b[->M:N] ends at every + // M..N-th match, but only the shared merge vertex below reaches the + // terminal, so a cover sequence would under-count. Reject the ranged form + // (the single-count b[->N] has one end and is enumerated correctly). + if (m_isCoverSeq && hasMax && maxN > minN) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a ranged goto repetition"); + return BuildResult::failWithError(); + } + // Wait + match per iter -> 2 sites per iteration; range form needs // sites for every iteration in [0..maxN). NOT($sampled(x)) matches // $sampled(NOT(x)) at the value level (IEEE 1800-2023 16.9.9); @@ -662,6 +714,16 @@ class SvaNfaBuilder final { if (!lhs.valid() || !rhs.valid()) { // LCOV_EXCL_START -- sub-build fail bail return BuildResult::fail(lhs.errorEmitted || rhs.errorEmitted); } // LCOV_EXCL_STOP + // IEEE 1800-2023 16.14.3: a cover sequence counts every end-of-match. A + // sequence operand of 'or' can end more than once, but only its final + // end reaches the merge vertex below, so reject sequence operands rather + // than under-count. Plain boolean disjunction has one end per cycle and + // is handled by the OR-fold. + if (m_isCoverSeq && (lhs.termVertexp != entryVtxp || rhs.termVertexp != entryVtxp)) { + flp->v3warn(COVERIGN, + "Ignoring unsupported: cover sequence with a sequence operand of 'or'"); + return BuildResult::failWithError(); + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); if (lhs.finalCondp) { guardedLink(lhs.termVertexp, mergeVtxp, sampled(lhs.finalCondp->cloneTreePure(false)), @@ -971,10 +1033,12 @@ class SvaNfaBuilder final { } public: - SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames) + SvaNfaBuilder(SvaGraph& graph, AstNodeModule* modp, V3UniqueNames& propTempNames, + bool isCoverSeq = false) : m_graph{graph} , m_modp{modp} - , m_propTempNames{propTempNames} {} + , m_propTempNames{propTempNames} + , m_isCoverSeq{isCoverSeq} {} // Reset scope between antecedent and consequent: liveness must not leak. void resetScope() { @@ -1347,8 +1411,11 @@ class SvaNfaLowering final { // throughout-drop reject; clean up intermediate state signals. // Phase 3: terminalActive and rejectBase from Links to matchVertex. // Builder only adds Links (non-clocked) to matchVertex via addLink in - // wireMatchAndMidSources. - void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs) { + // wireMatchAndMidSources. When outPerMidSrcsp is non-null, also collect + // the per-edge match signal (IEEE 1800-2023 16.14.3 cover sequence: each + // end-of-match fires the action independently, no OR-fold). + void computeTerminalMatchAndReject(LowerCtx& c, AstNodeExpr* snapshotOkp, SignalSet& sigs, + std::vector* outPerMidSrcsp = nullptr) { for (const SvaTransEdge* const tep : c.edges) { if (tep->toVtxp() != c.graph.m_matchVertexp) continue; const int fi = tep->fromVtxp()->color(); @@ -1360,6 +1427,18 @@ class SvaNfaLowering final { if (snapshotOkp) { srcSigp = new AstLogAnd{c.flp, srcSigp, snapshotOkp->cloneTreePure(false)}; } + if (outPerMidSrcsp) { + // Per-mid signal must also AND in matchCondp (the final boolean + // check, e.g. sampled(b) for `a ##[1:3] b`). assembleResult does + // this for the OR-collapsed terminalActivep; we replicate it + // per-edge here so each end-of-match is gated identically. + AstNodeExpr* perMidp = srcSigp->cloneTreePure(false); + if (c.matchCondp) { + perMidp = new AstLogAnd{c.flp, perMidp, + sampled(c.matchCondp->cloneTreePure(false))}; + } + outPerMidSrcsp->push_back(perMidp); + } if (tep->fromVtxp()->m_isCounter) { sigs.terminalActivep @@ -1410,7 +1489,8 @@ class SvaNfaLowering final { } } - SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp) { + SignalSet computeSignals(LowerCtx& c, std::vector* outRequiredStepSrcsp, + std::vector* outPerMidSrcsp = nullptr) { SignalSet sigs; // Snapshot comparison expression for disable-iff counter. @@ -1422,7 +1502,7 @@ class SvaNfaLowering final { new AstVarRef{c.flp, c.disableCntVarp, VAccess::READ}}; } - computeTerminalMatchAndReject(c, snapshotOkp, sigs); + computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, @@ -1649,7 +1729,8 @@ public: AstNodeExpr* disableExprp = nullptr, bool negated = false, AstNodeExpr** outMatchpp = nullptr, AstVar* disableCntVarp = nullptr, AstVar* snapshotVarp = nullptr, - std::vector* outRequiredStepSrcsp = nullptr) { + std::vector* outRequiredStepSrcsp = nullptr, + std::vector* outPerMidSrcsp = nullptr) { const std::string baseName = m_names.get(""); // Number vertices with sequential colors for array indexing. @@ -1735,7 +1816,7 @@ public: emitAndCombinerDoneLatchNba(c); // Phase 3/3a/3b: Compute terminal match/reject signals (cleans up stateSig). - const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp); + const SignalSet sigs = computeSignals(c, outRequiredStepSrcsp, outPerMidSrcsp); AstNodeExpr* const resultp = assembleResult( flp, isCover, negated, matchCondp, sigs.terminalActivep, sigs.rejectBasep, @@ -1766,7 +1847,10 @@ class AssertNfaVisitor final : public VNVisitor { // Wire match vertex and mid-window sources for a successful NFA build. static void wireMatchAndMidSources(SvaGraph& graph, const BuildResult& result, FileLine* flp) { graph.createMatchVertex(); - graph.addLink(result.termVertexp, graph.m_matchVertexp); + // Skip the main term Link when midSources already cover every + // end-of-match (cover_sequence path); otherwise the per-mid extraction + // double-counts via the merge vertex. + if (!result.termIsMidMerge) { graph.addLink(result.termVertexp, graph.m_matchVertexp); } for (SvaStateVertex* srcVtxp : result.midSources) { AstNodeExpr* condp = nullptr; for (AstNodeExpr* const tc : srcVtxp->m_throughoutConds) { @@ -2235,9 +2319,11 @@ class AssertNfaVisitor final : public VNVisitor { FileLine* const flp = assertp->fileline(); const bool isCover = VN_IS(assertp, Cover); + AstCover* const coverp = VN_CAST(assertp, Cover); + const bool isCoverSeq = coverp && coverp->isCoverSeq(); SvaGraph graph; - SvaNfaBuilder builder{graph, m_modp, m_propTempNames}; + SvaNfaBuilder builder{graph, m_modp, m_propTempNames, isCoverSeq}; const BuildResult result = buildAssertionGraph(builder, graph, seqBodyp, parts, flp); if (result.valid()) wireMatchAndMidSources(graph, result, flp); @@ -2269,12 +2355,18 @@ class AssertNfaVisitor final : public VNVisitor { = !isCover && !parts.hasImplication && assertWithFailp && assertWithFailp->failsp(); std::vector requiredStepSrcs; + // For `cover sequence` (IEEE 1800-2023 16.14.3) collect per-edge match + // signals so each end-of-match fires the action independently, rather + // than getting OR-folded into a single per-cycle terminalActive. + // coverp / isCoverSeq are computed earlier (passed to SvaNfaBuilder). + std::vector perMidSrcs; + AstNodeExpr* const alwaysTriggerp = new AstConst{flp, AstConst::BitTrue{}}; - AstNodeExpr* const outputExprp - = m_loweringp->lower(flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, - disableExprp ? disableExprp->cloneTreePure(false) : nullptr, - negated, needMatch ? &matchExprp : nullptr, disableCntVarp, - snapshotVarp, needPerSrcFail ? &requiredStepSrcs : nullptr); + AstNodeExpr* const outputExprp = m_loweringp->lower( + flp, graph, alwaysTriggerp, senTreep, result.finalCondp, isCover, + disableExprp ? disableExprp->cloneTreePure(false) : nullptr, negated, + needMatch ? &matchExprp : nullptr, disableCntVarp, snapshotVarp, + needPerSrcFail ? &requiredStepSrcs : nullptr, isCoverSeq ? &perMidSrcs : nullptr); AstSenTree* const perSrcSenTreep = (requiredStepSrcs.size() >= 2) ? senTreep->cloneTree(false) : nullptr; @@ -2287,9 +2379,38 @@ class AssertNfaVisitor final : public VNVisitor { attachMatchHandlers(flp, assertAssertp, assertWithFailp, needMatch ? matchExprp : nullptr, perSrcSenTreep, requiredStepSrcs); - AstNode* const innerPropp = propSpecp->propp(); - innerPropp->replaceWith(outputExprp); - VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + if (isCoverSeq && perMidSrcs.size() > 1) { + // Clone AstCover (N-1) times, each gated by its own per-mid signal. + // V3Assert sees N independent covers and emits N `if (cond_i) {coverinc; + // userAction}` bodies; the shared AstCoverDecl bucket is incremented + // per fire, matching IEEE "executed each time the sequence matches." + // Clones reuse AstCover->propp's original SVA tree, but we overwrite + // each clone's inner propp with the corresponding per-mid signal + // BEFORE the next iterator step, so hasMultiCycleExpr() returns false + // and processAssertion skips them on revisit. + std::vector coverList; + coverList.push_back(coverp); + for (size_t i = 1; i < perMidSrcs.size(); ++i) { + AstCover* const clonep = coverp->cloneTree(false); + coverp->addNextHere(clonep); + coverList.push_back(clonep); + } + for (size_t i = 0; i < perMidSrcs.size(); ++i) { + AstPropSpec* const clonePropSpecp = VN_CAST(coverList[i]->propp(), PropSpec); + AstNode* const innerp = clonePropSpecp->propp(); + innerp->replaceWith(perMidSrcs[i]); + VL_DO_DANGLING(pushDeletep(innerp), innerp); + } + // Discard the OR-collapsed fallback signal -- cover_sequence path + // does not use it. + VL_DO_DANGLING(outputExprp->deleteTree(), outputExprp); + } else { + AstNode* const innerPropp = propSpecp->propp(); + innerPropp->replaceWith(outputExprp); + VL_DO_DANGLING(pushDeletep(innerPropp), innerPropp); + // If we collected per-mid (N==1) but didn't clone, drop the spare. + for (AstNodeExpr* const sp : perMidSrcs) pushDeletep(sp); + } UINFO(4, "NFA converted assertion at " << flp << endl); } diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index aed79ddc0..46c49b515 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -1607,12 +1607,18 @@ public: }; class AstCover final : public AstNodeCoverOrAssert { // @astgen op3 := coverincsp: List[AstNode] // Coverage node + bool m_isCoverSeq = false; // 'cover sequence' (IEEE 1800-2023 16.14.3): fires per + // end-of-match, not per property success public: ASTGEN_MEMBERS_AstCover; AstCover(FileLine* fl, AstNode* propp, AstNode* stmtsp, VAssertType type, const string& name = "") : ASTGEN_SUPER_Cover(fl, propp, stmtsp, type, VAssertDirectiveType::COVER, name) {} string verilogKwd() const override { return "cover"; } + void dump(std::ostream& str) const override; + void dumpJson(std::ostream& str) const override; + bool isCoverSeq() const { return m_isCoverSeq; } + void isCoverSeq(bool flag) { m_isCoverSeq = flag; } }; class AstRestrict final : public AstNodeCoverOrAssert { public: diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 0707c8d8f..453c45d21 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -2181,6 +2181,14 @@ void AstNodeCoverOrAssert::dumpJson(std::ostream& str) const { dumpJsonBoolFuncIf(str, immediate); dumpJsonBoolFuncIf(str, senFromAlways); } +void AstCover::dump(std::ostream& str) const { + this->AstNodeCoverOrAssert::dump(str); + if (isCoverSeq()) str << " [COVERSEQ]"; +} +void AstCover::dumpJson(std::ostream& str) const { + dumpJsonBoolFuncIf(str, isCoverSeq); + this->AstNodeCoverOrAssert::dumpJson(str); +} void AstClocking::dump(std::ostream& str) const { this->AstNode::dump(str); if (isDefault()) str << " [DEFAULT]"; diff --git a/src/verilog.y b/src/verilog.y index d4392a5bc..847a5f828 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -6497,14 +6497,34 @@ concurrent_assertion_statement: // ==IEEE: concurrent_assertion_stat | yCOVER yPROPERTY '(' property_spec ')' stmt { $$ = new AstCover{$1, $4, $6, VAssertType::CONCURRENT}; } // // IEEE: cover_sequence_statement + // // Reuses AstCover + AstPropSpec (same wrapper as + // // cover_property_statement above) and the isCoverSeq + // // flag drives V3AssertNfa to fire stmt per end-of-match + // // (IEEE 1800-2023 16.14.3), not per property success. | yCOVER ySEQUENCE '(' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $6); } - // // IEEE: yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt - // // sexpr already includes "clocking_event sexpr" + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), nullptr, nullptr, $4}, + $6, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } + | yCOVER ySEQUENCE '(' clocking_event sexpr ')' stmt + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, nullptr, $5}, + $7, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' clocking_event yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($4, $8, $10, $12);} + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$4->fileline(), $4, $8, $10}, + $12, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } | yCOVER ySEQUENCE '(' yDISABLE yIFF '(' expr/*expression_or_dist*/ ')' sexpr ')' stmt - { $$ = nullptr; BBCOVERIGN($2, "Ignoring unsupported: cover sequence"); DEL($7, $9, $11); } + { AstCover* const coverp = new AstCover{$1, + new AstPropSpec{$7->fileline(), nullptr, $7, $9}, + $11, VAssertType::CONCURRENT}; + coverp->isCoverSeq(true); + $$ = coverp; } // // IEEE: restrict_property_statement | yRESTRICT yPROPERTY '(' property_spec ')' ';' { $$ = new AstRestrict{$1, $4}; } diff --git a/test_regress/t/t_cover_sequence.py b/test_regress/t/t_cover_sequence.py new file mode 100755 index 000000000..23e13d99b --- /dev/null +++ b/test_regress/t/t_cover_sequence.py @@ -0,0 +1,18 @@ +#!/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') + +test.compile(timing_loop=True, verilator_flags2=['--assert', '--timing', '--coverage-user']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_cover_sequence.v b/test_regress/t/t_cover_sequence.v new file mode 100644 index 000000000..719a3c1ba --- /dev/null +++ b/test_regress/t/t_cover_sequence.v @@ -0,0 +1,90 @@ +// 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 checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0); +`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 ( + input clk +); + + logic [63:0] crc = 64'h5aef0c8dd70a4497; + logic rst_n = 1'b0; + logic a, b, c, d, e; + int cyc = 0; + + int hit_simple = 0; + int hit_clocked = 0; + int hit_clocked_disable = 0; + int hit_default_disable = 0; + int hit_consrep_range = 0; + int hit_consrep_2 = 0; + int hit_consrep_3 = 0; + + default clocking cb @(posedge clk); + endclocking + + // Non-adjacent CRC bits to avoid LFSR shift correlation + assign a = crc[0]; + assign b = crc[5]; + assign c = crc[10]; + assign d = crc[15]; + assign e = crc[20]; + + // Form 1: cover sequence ( sexpr ) stmt + cover sequence (a | b | c | d | e) hit_simple++; + + // Form 2: cover sequence ( clocking_event sexpr ) stmt + cover sequence (@(posedge clk) (a | b | c | d | e) ##[1:3] b) hit_clocked++; + + // Form 3: cover sequence ( clocking_event disable iff (expr) sexpr ) stmt + cover sequence (@(posedge clk) disable iff (!rst_n) a ##1 b) hit_clocked_disable++; + + // Form 4: cover sequence ( disable iff (expr) sexpr ) stmt + cover sequence (disable iff (!rst_n) a ##1 c) hit_default_disable++; + + // Form 5: consecutive repetition, counted per end-of-match + cover sequence (a [* 2: 3]) hit_consrep_range++; + cover sequence (a [* 2]) hit_consrep_2++; + cover sequence (a [* 3]) hit_consrep_3++; + + always @(posedge clk) begin +`ifdef TEST_VERBOSE + $write("[%0t] cyc=%0d crc=%x a=%b b=%b c=%b d=%b e=%b\n", $time, cyc, crc, a, b, c, d, e); +`endif + cyc <= cyc + 1; + crc <= {crc[62:0], crc[63] ^ crc[2] ^ crc[1] ^ crc[0]}; + if (cyc == 2) rst_n <= 1'b1; + if (cyc == 99) begin + `checkh(crc, 64'h261a9f1371d7aadf); + $finish; + end + end + + // Read the counters in 'final', not the clocked block: a same-cycle read of a + // cover counter races the cover's increment under --threads (vltmt). Verilator + // counts one more end-of-match than Questa 2022.3 on some forms at the + // simulation boundary; the Questa value is noted per check. + final begin +`ifdef TEST_VERBOSE + $write("simple=%0d clocked=%0d clk_dis=%0d def_dis=%0d range=%0d 2=%0d 3=%0d\n", hit_simple, + hit_clocked, hit_clocked_disable, hit_default_disable, hit_consrep_range, hit_consrep_2, + hit_consrep_3); +`endif + `checkd(hit_simple, 96); // Questa: 95 + `checkd(hit_clocked, 149); // Questa: 149 + `checkd(hit_clocked_disable, 28); // Questa: 27 + `checkd(hit_default_disable, 30); // Questa: 30 + `checkd(hit_consrep_2, 30); // Questa: 29 + `checkd(hit_consrep_3, 14); // Questa: 13 + // a[*2:3] == a[*2] or a[*3] (IEEE 1800-2023 16.9.2) + `checkd(hit_consrep_range, hit_consrep_2 + hit_consrep_3); // 44; Questa: 42 + $write("*-* All Finished *-*\n"); + end +endmodule diff --git a/test_regress/t/t_cover_sequence_unsup.out b/test_regress/t/t_cover_sequence_unsup.out new file mode 100644 index 000000000..47af703cb --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.out @@ -0,0 +1,18 @@ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:21:33: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 21 | cover sequence ((a ##[1:3] b) or 1'b0); + | ^~ + ... For warning description see https://verilator.org/warn/COVERIGN?v=latest + ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:24:32: Ignoring unsupported: cover sequence with a sequence operand of 'or' + 24 | cover sequence ((a [* 1: 3]) or 1'b0); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:27:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 27 | cover sequence (a ##[1:2] (b ##1 c)); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:30:21: Ignoring unsupported: cover sequence with this ranged cycle delay + 30 | cover sequence (a ##[1:300] b); + | ^~ +%Warning-COVERIGN: t/t_cover_sequence_unsup.v:33:21: Ignoring unsupported: cover sequence with a ranged goto repetition + 33 | cover sequence (a [-> 2: 3]); + | ^~~ +%Error: Exiting due to diff --git a/test_regress/t/t_cover_sequence_unsup.py b/test_regress/t/t_cover_sequence_unsup.py new file mode 100755 index 000000000..56c514bc5 --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.py @@ -0,0 +1,18 @@ +#!/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('vlt') + +test.lint(expect_filename=test.golden_filename, + verilator_flags2=['--assert --error-limit 1000'], + fails=True) + +test.passes() diff --git a/test_regress/t/t_cover_sequence_unsup.v b/test_regress/t/t_cover_sequence_unsup.v new file mode 100644 index 000000000..b2770a2fa --- /dev/null +++ b/test_regress/t/t_cover_sequence_unsup.v @@ -0,0 +1,35 @@ +// 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 + +module t ( + input clk +); + + logic a, b, c; + + default clocking cb @(posedge clk); + endclocking + + // cover sequence (IEEE 1800-2023 16.14.3) counts every end-of-match. The + // following forms put a sub-sequence where only its final end is forwarded, + // so they are ignored (COVERIGN) rather than under-counted. + + // Sequence operand of 'or' (ranged cycle delay). + cover sequence ((a ##[1:3] b) or 1'b0); + + // Sequence operand of 'or' (consecutive repetition). + cover sequence ((a [* 1: 3]) or 1'b0); + + // Ranged cycle delay before a multi-cycle sequence. + cover sequence (a ##[1:2] (b ##1 c)); + + // Ranged cycle delay wide enough to use the counter FSM. + cover sequence (a ##[1:300] b); + + // Ranged goto repetition (every M..N-th match is a separate end). + cover sequence (a [-> 2: 3]); + +endmodule diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index b65ab991e..53f8ac53b 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -651,6 +651,11 @@ module Vt_debug_emitv_t; $display("pass"); end end + begin : cover_sequence_concurrent + cover property (@(posedge clk) in##1 in + ) begin + end + end begin : assert_prop_always assert property (@(posedge clk) always ['sh0: 'sh3] in diff --git a/test_regress/t/t_debug_emitv.v b/test_regress/t/t_debug_emitv.v index edc1ec87b..7d3805052 100644 --- a/test_regress/t/t_debug_emitv.v +++ b/test_regress/t/t_debug_emitv.v @@ -343,6 +343,8 @@ module t (/*AUTOARG*/ cover_concurrent: cover property(prop); cover_concurrent_stmt: cover property(prop) $display("pass"); + cover_sequence_concurrent: cover sequence (@(posedge clk) in ##1 in); + assert_prop_always: assert property (@(posedge clk) always [0:3] in); assert_prop_s_always: assert property (@(posedge clk) s_always [1:2] in); assert_prop_overlap_impl: assert property (@(posedge clk) in |-> in); diff --git a/test_regress/t/t_sequence_sexpr_unsup.out b/test_regress/t/t_sequence_sexpr_unsup.out index 975a06491..9bf5bf9ea 100644 --- a/test_regress/t/t_sequence_sexpr_unsup.out +++ b/test_regress/t/t_sequence_sexpr_unsup.out @@ -8,15 +8,4 @@ %Error-UNSUPPORTED: t/t_sequence_sexpr_unsup.v:99:5: Unsupported: first_match with sequence_match_items 99 | first_match (a, res0 = 1, res1 = 2); | ^~~~~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:102:9: Ignoring unsupported: cover sequence - 102 | cover sequence (s_a) $display(""); - | ^~~~~~~~ - ... For warning description see https://verilator.org/warn/COVERIGN?v=latest - ... Use "/* verilator lint_off COVERIGN */" and lint_on around source to disable this message. -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:103:9: Ignoring unsupported: cover sequence - 103 | cover sequence (@(posedge a) disable iff (b) s_a) $display(""); - | ^~~~~~~~ -%Warning-COVERIGN: t/t_sequence_sexpr_unsup.v:104:9: Ignoring unsupported: cover sequence - 104 | cover sequence (disable iff (b) s_a) $display(""); - | ^~~~~~~~ %Error: Exiting due to From 709c444df3567b69d8f46af479d4b14d24444a17 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 14:42:34 +0200 Subject: [PATCH 42/46] Internals: Add AGENTS files to guide AI contributions (#7562) (#7765) Fixes #7562. --- AGENTS.md | 142 ++++++++++++++++++++++++++ docs/AGENTS.md | 38 +++++++ include/AGENTS.md | 60 +++++++++++ src/AGENTS.md | 227 +++++++++++++++++++++++++++++++++++++++++ test_regress/AGENTS.md | 128 +++++++++++++++++++++++ 5 files changed, 595 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/AGENTS.md create mode 100644 include/AGENTS.md create mode 100644 src/AGENTS.md create mode 100644 test_regress/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..1f0c6a6be --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ + + +# Verilator Guidelines for AI Coding Agents + +These files are the general layer an agent loads first -- nearest file wins, so +you read this repository-root file plus the one for the directory you are +editing. They stay deliberately high-level: where to start, how the tree is laid +out, and the conventions reviewers otherwise enforce by hand. They are an index, +not the architecture reference -- for depth (how a pass works internally, the +algorithms, node lifetime) they point you to `docs/internals.rst`. When the +guidance here is not enough, that is where to look next. + +This file has two parts. **Orientation** gets you productive in the codebase from +a cold start. **Before you open a PR** is the checklist of conventions reviewers +otherwise have to enforce by hand -- read it before submitting any change. + +Then read the directory guide for the area you are editing: + +- [src/AGENTS.md](src/AGENTS.md) -- compiler C++ sources: AST, visitors, passes, parser, style +- [include/AGENTS.md](include/AGENTS.md) -- runtime library (`verilated*`): C++14, MT-safety, fixed-width types +- [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files +- [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`) + +--- + +# Orientation + +## What Verilator is + +Verilator is a *compiler*, not an interpreter. It translates synthesizable (and +much behavioral) SystemVerilog into a cycle-accurate C++ model that you then +compile and run. Almost every decision is made at compile ("verilation") time; +the generated C++ just advances state each evaluation. Optimize for verilation- +time work over runtime work. + +## The pipeline is the spine + +A run is an ordered sequence of passes over one shared AST (abstract syntax +tree). In source order: + +| Stage | What it does | Key files | +|---|---|---| +| Preprocess + parse | Lex and parse text into a raw AST -- builds nodes only, no semantic checks | `verilog.l`, `verilog.y` | +| Link / elaborate | Resolve names, scopes, parameters; instantiate the hierarchy | `V3LinkParse`, `V3LinkDot`, `V3Param` | +| Width / type | Assign and check data types and bit widths | `V3Width` | +| Transform / optimize / schedule | Constant fold, lower language features, schedule events | `V3Const`, `V3Randomize`, `V3Assert*`, `V3Sched`, `V3Timing`, `V3Dfg` | +| Emit | Lower the final AST to generated C++ | `V3EmitC*` | +| Runtime | Library the generated model links against | `include/verilated*` | + +This table is the map; `docs/internals.rst` has the detail behind each stage. + +## Where to make a change + +Map the symptom to the pass that owns it, then start by reading that pass's +top-of-file comment. + +| Symptom or feature area | Start in | +|---|---| +| Type/width error, "what type is this", implicit conversion | `V3Width` | +| Name/scope/parameter resolution ("Can't find...", hierarchy) | `V3LinkDot`, `V3Param` | +| `randomize` / `constraint` / `rand` / `randc` | `V3Randomize` | +| `assert` / `property` / `sequence` / `cover` | `V3Assert`, `V3AssertPre`, `V3AssertNfa` | +| `fork` / timing / `#delay` / NBA / event scheduling | `V3Sched`, `V3Timing`, `V3Fork` | +| Syntax wrongly accepted or rejected | `verilog.y`, `verilog.l` | +| Wrong generated C++ | `V3EmitC*` | +| Runtime model behavior | `include/verilated*` | + +## Build and run a test + +- Build in the source tree: `autoconf && ./configure && make -j8`. Configure with + `--enable-ccwarn` so a new compiler warning stops the build. +- Run one test from the repository root: `test_regress/t/t_.py`. +- Run the full regression with `make test`. The complete suite requires + configuring with `--enable-longtests` (works on every OS, including macOS). + +--- + +# Before you open a PR + +## Scope and process + +- [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time. +- [ ] Fixed the general root cause, not just the reported case -- if it also + affects other modules/classes/interfaces, cover them or expect rejection. +- [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new + features each go in separate PRs; land standalone cleanups first. +- [ ] Every bug fix has a test that fails *without* the fix; include the issue's + own reproducer when possible. +- [ ] New code aims for 100% line coverage; branch coverage far below line coverage + signals guards callers never violate -- justify or remove them. +- [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`; + self-reviewed the diff for leftover debug code, stale comments, and + copy-paste errors. +- [ ] Ran the full regression on at least one OS before submitting. Partial runs + are fine during development, but the submitted PR is expected to pass every + test. +- [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer + updates it near release). + +## Pick the right diagnostic (and its required test) + +The API you choose determines which test must accompany the change. + +| API | Output | Meaning | Required test | +|---|---|---|---| +| `v3error("...")` | `%Error:` | User wrote invalid SystemVerilog | `t_*_bad*.v` + `.out` golden | +| `v3error("Unsupported: ...")` | `%Error-UNSUPPORTED:` | Legal SV that Verilator does not yet support | `t_*_unsup*.v` + `.out` golden | +| `v3warn(CODE, "...")` | `%Warning-CODE:` | Legal but suspicious code | warning test + `.out` golden | +| `v3fatalSrc("...")` | `%Error: Internal Error` | Should-never-happen internal assertion | none -- not user-triggerable | + +- Every `v3error`/`v3warn` needs a test in `test_regress/t/` -- enforced by the + warn-coverage distribution test. `v3fatalSrc` is exempt. +- Reserve "Unsupported:" for not-yet-implemented features, never for user mistakes. +- When an error enforces a spec-defined restriction, cite the clause + (`IEEE 1800-2023 11.4.7`) so it is verifiable. Update `docs/guide/warnings.rst` + when adding or changing a warning. +- On error paths, clean up or replace invalid AST (e.g. `AstConst::BitFalse`) so + later passes do not crash after the error. + +## Cross-cutting code rules + +- [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII + hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart + quote. At write time, not when CI complains. +- [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values, + configure feature lists, documented option lists. +- [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use + bash and platform-specific code (developer-only, not packaged). +- [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work); + C++20 only in timing code paths. +- [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl` + and document the API with `///` comments. +- [ ] A new code pattern is applied globally or not at all -- no one-off + convention in a single file. + +## Commits + +- Subject line is short and imperative and conventionally ends with the PR number: + `Support property case (#7721)`. A body is optional and common for non-trivial + changes. diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 000000000..ee4ae5249 --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,38 @@ + + +# docs/ Guidelines -- Verilator documentation (*.rst) + +When to check: before editing anything under `docs/`. +Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules. + +## Writing rules + +- Rewrap paragraphs after editing -- keep consistent line length in `*.rst` files. + +- Document only stable, implemented features -- never planned or in-development capabilities; prevents user confusion and support burden. + +- Explain WHAT and WHEN, not HOW -- conceptual purpose and practical use cases over implementation mechanics; describe behavior ("optimized as pure", not "treated as pure") and clarify ambiguous referents ("in the internals of Verilator"). + +- Keep terminology consistent -- one term per concept; update docs when renaming code constructs; spell out full terms, avoiding abbreviations like "sim"/"sims". + +- Use "how many" for countable nouns (threads, tasks, workers) and "how much" for uncountable quantities. + +- Mark internal or experimental features "for internal use only" -- prevents user dependence and forced deprecation cycles later. + +- Use specific IEEE references: `IEEE {number}-{year}` plus the section (e.g. `Annex I`) -- a vague "IEEE spec requires" is unverifiable. + +- Document all flags with descriptions, not just syntax. + +## reStructuredText mechanics + +- Use the `:vlopt:` role for Verilator option references -- makes cross-references clickable and consistent. + +- Escape angle brackets (`\<`, `\>`) in link targets -- prevents broken links to command-line options. + +- Generate documentation examples with `test.extract` from `test_regress` test files -- examples stay synced with actually tested behavior. + +## Hard constraint + +- Never edit `docs/CONTRIBUTORS` -- only humans may, after reading and agreeing to its statement; remind the user instead. diff --git a/include/AGENTS.md b/include/AGENTS.md new file mode 100644 index 000000000..5b409dfaf --- /dev/null +++ b/include/AGENTS.md @@ -0,0 +1,60 @@ + + +# include/ -- Verilator runtime library + +Covers the C++ runtime under `include/` (`verilated.h/.cpp`, `verilated_*.h/.cpp`) +that every generated model links against. Read the repository-root +[AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code +ships to users, runs every simulation cycle, and must stay portable and fast. + +--- + +# Orientation + +- **This is the runtime, not the compiler.** The passes in `src/` *emit* C++ that + calls into this library; this code then *runs* during simulation. Optimize it + for execution speed and portability, not for compile-time clarity. +- **Key files:** `verilated.h` (core model API), `verilated_types.h` (data + types), `verilated_random.cpp` (constrained-random runtime), `verilated_cov.*` + (coverage), `verilated_threads.*` (MT runtime), `verilated_timing.*` + (`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing). +- A runtime-only fix lives entirely here and does not rebuild `verilator_bin`. + +--- + +# Before you open a PR + +- **C++14 baseline.** The runtime must build under `--no-timing` with C++14; C++20 + features are allowed only in `--timing` code paths. +- **Public API naming and docs.** Prefix public classes and types with + `Verilated`/`Vl` to avoid collisions with user code, and document the API with + `///` comments -- they feed doc generation. +- **No exceptions in runtime code** -- use error returns or assertions; exceptions + add overhead on every path. +- **Use fixed-width model types** (`CData`/`SData`/`IData`/`QData`/`VlWide`), never + `size_t`, for model data. Process wide data word-by-word (`VL_ZERO_W`, + `VL_MEMCPY_W`), never bit-by-bit or byte-by-byte. +- **Do all string parsing at verilation time** -- never parse strings during + simulation; emit structured data or compile-time hints instead. +- **Keep per-cycle code lean.** Do not add vtables to high-frequency objects + (8 bytes per instance); guard optional features behind + `hasClasses()`/`hasEvents()`-style checks; functions called per cycle must avoid + mutexes -- use atomics or lockless designs. +- **Emit no runtime loops** the compiler could have expanded at verilation time; + prefer a single runtime call. +- **Thread safety.** Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > + `VL_MT_STABLE`; annotations must match the implementation. Annotate + mutex-protected members with `VL_GUARDED_BY` and document acquisition ordering. + Prefer has-a over is-a: a guarded class wrapping the unguarded internal one, with + the guarded version as the default public API. +- **Keep runtime and compiler headers separate** -- never include `verilated.h` + into the compiler in `src/`. + +## File-specific rules + +| File | Rule | +|---|---| +| `verilated_random.cpp` | Emit only portable SMT-LIB 2.6 -- no solver-specific or MaxSMT extensions; the generated solver text is the model's runtime constraint interface | +| `verilated_cov.cpp` | Coverage runtime is shared by all models -- keep per-point overhead minimal and the on-disk format stable for `verilator_coverage` | diff --git a/src/AGENTS.md b/src/AGENTS.md new file mode 100644 index 000000000..1904eb5c9 --- /dev/null +++ b/src/AGENTS.md @@ -0,0 +1,227 @@ + + +# src/ -- Verilator compiler sources + +Covers all C++ under `src/`, including astgen inputs and the parser/lexer +(`verilog.y`, `verilog.l`). Read the repository-root [AGENTS.md](../AGENTS.md) +first. This file has two parts: **Orientation** explains the AST and pass model; +**Before you open a PR** is the style and correctness checklist. + +--- + +# Orientation: the AST and the visitor model + +- **Everything is an `AstNode`.** Each construct is an `Ast*` subclass (`AstAdd`, + `AstVar`, `AstIf`). The design under analysis is one tree, with statement lists + threaded by sibling `nextp()` links. +- **Children sit in numbered slots `op1p()`..`op4p()`, accessed by name.** Use the + named accessors (`lhsp()`, `condp()`, `thensp()`), not the raw slots -- the + numbering is an implementation detail. +- **`astgen` generates node boilerplate.** Declare children and cross-node + pointers with `@astgen op` / `@astgen ptr` annotations in the `V3AstNode*.h` + headers; it emits accessors, clone, and broken-check code. Do not hand-write + what astgen can generate. +- **A pass is a visitor.** Convention: a class with a private constructor and a + static `apply()` entry point, named after its file (`TimingSuspendableVisitor` + in `V3Timing.cpp`). It walks the tree through `visit(AstFoo*)` handlers and + `iterateChildren()`. To understand a pass, read its top-of-file comment first -- + every `.cpp` opens with one describing the algorithm. +- **Scratch state lives on nodes.** Passes stash data in `user1p()`..`user5p()` + (and `user1()`..`user5()`), claimed for the pass lifetime with a `VNUser*InUse` + guard. Save and restore visitor members across recursion with `VL_RESTORER`. +- **Three downcasts, three null behaviors:** `VN_IS` returns bool, `VN_CAST` + returns nullptr on mismatch, `VN_AS` asserts the type. `V3Broken` re-validates + tree invariants between passes, so trust resolved pointers (`dtypep()`, + `varp()`) instead of adding defensive null checks for impossible cases. +- `docs/internals.rst` is the authoritative reference for the AST, the pass list, + and node lifetime. + +--- + +# Before you open a PR + +## Code style + +- Mark every variable, parameter, pointer, and member function `const` where possible. +- Pointer variables take a `p` suffix and pointer locals are doubly const where + possible: `AstVar* const varp`; non-pointers never use the `p` suffix. +- Do not use `auto` except for iterators or genuinely unwieldy types. +- Use pre-increment (`++i`) unless you specifically need post-increment's old value. +- Brace-initialize node and struct construction: `new AstIf{fl, condp, thenp, elsep}`. +- Never use C-style casts; instead use `static_cast` for non-AST types and + `VN_AS`/`VN_CAST` for AST downcasts. +- `static constexpr` for compile-time constants, not `#define` or file-scope const. +- Mark every `class`/`struct` `final` or `VL_NOT_FINAL` -- a distribution test + scans all definitions. +- Keep functions under roughly 100-150 lines; thread shared state through a + context struct rather than long parameter lists. +- Keep headers lean: move implementation to `.cpp`; convert large lambdas into + named member functions -- lambdas are opaque in stack traces and block reuse. +- Start every new `.cpp` with a top-of-file comment explaining the algorithm. +- Comments are capitalized sentences written for an unknown future reader, without + "I"/"we"/"our"; remove commented-out code -- version control preserves history. +- No `using namespace`; prefix non-namespaced symbols with `VL`/`Vl`. +- Prefer semantic predicates over enum comparisons: `varp->isClassMember()`, not + `varp->varType() == VVarType::MEMBER`. +- `new*` functions return a `new` object; `make*` functions do something more + complex -- pick the prefix accordingly. +- Name compiler-created temporaries with a `__V` prefix plus a context suffix + (`__VInside`, `__VCase`); runtime utility functions use a `vl_` prefix. +- Use `VL_*` bit/word macros from `verilatedos.h` (`VL_WORDS_I`, `VL_MASK_I`); do + not include `` directly. +- Replace magic numbers with named `static constexpr` constants. + +## AST construction and manipulation + +- Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes + (V3Name, `--protect`) rename AST identifiers but cannot see into raw strings. +- Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same + node -- use a single `VN_CAST`: + + ```cpp + // BAD: redundant double check + if (VN_IS(nodep, VarRef)) { AstVarRef* const refp = VN_AS(nodep, VarRef); } + // GOOD: single conditional cast + if (const AstVarRef* const refp = VN_CAST(nodep, VarRef)) { ... } + ``` + +- Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope; + use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`. +- Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in + visitors -- deferred deletion is safe against re-entry and unlinking order. + `deleteTree()` is only for fresh nodes that never entered the tree. +- Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap + these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`. + Override `isSame()` to include any new semantically meaningful field. +- Pointers to nodes outside op1p-op4p require a `broken()` override and + `cloneRelink()` support -- avoid storing out-of-tree node pointers when possible. +- Never allocate AstNode objects on the stack or by value -- always pointers. +- Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` -- + better for runtime, and handles diverse node types better. Prefer named + accessors over `op1p()`..`op4p()`, and verify traversal order is preserved + when converting manual iteration. +- Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code + instead of O(N); wrap the body in `AstBegin` for scope isolation. +- Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks + typedef support; prove the paths with typedef tests. +- Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding + V3Number comparisons against non-integer types. +- Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll + filename/lineno comparisons. +- Identify compiler-generated constructs by an attribute flag on the node (with + dump support), never by name-pattern matching -- magic names break with escaped + identifiers. +- Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i` + silently overflows at `i >= 32`. +- Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules, + and packages -- O(1) versus quadratic linear scans. +- Never emit raw source filenames or identifiers in generated code -- pass them + through `protect()`/`putsQuoted` so `--protect` does not leak source. + +## Visitors and passes + +- `VL_RESTORER` on every visitor member a `visit()` modifies before iterating + children -- even when nesting "cannot happen" today. +- Every pass using `userNp()` needs a `VNUserNInUse` guard, and the header + documents which user fields it uses. +- Use `iterateAndNextNull()` rather than `iterate()` -- the null-safe form + prevents copy-paste errors during refactors. +- Derive read-only visitors from `VNVisitorConst` with `iterateChildrenConst`. +- Reset per-module visitor state in `visit(AstNodeModule*)`, including numeric ID + counters, to keep generated names stable. +- Capture first-occurrence module state inside the node's own `visit()` handler, + not via a `foreach` pre-scan from `visit(AstNodeModule)` -- source order already + matches IEEE declaration-before-use. +- Avoid `backp()` -- it returns parent or prior sibling depending on position and + causes O(n^2) hunts; build maps or capture context during forward traversal. +- When raw node pointers key a map or set, erase entries when the node is deleted + -- allocators reuse addresses, so stale entries alias new nodes. +- Derive graph-shaped passes from V3Graph (`V3GraphVertex`/`V3GraphEdge`) -- it + gives dump, color, rank, topological sort, and reachability for free. +- When a change outgrows local rewrites, create a dedicated pass instead of + growing an existing one. +- State explicitly how side effects are preserved in optimizations involving + purity, expression lifting, or simplification. + +## Errors and warnings + +(See the root checklist for choosing the diagnostic API and its required test.) + +- Append `nodep->prettyNameQ()` for user-facing names; use `name()` only in + debug/UINFO output. Enclose specific values in single quotes: `'value'`. +- End messages with periods, never exclamation marks; do not write "Error:" in the + text -- the macro prints the prefix. +- State what was attempted and what was found: "Instance attempts to connect to + 'PARAM' as a parameter, but it is a variable". Add a `warnMore()` suggestion + where possible. +- Name warning codes object-first and short (`ASCRANGE`, not `RANGEASC`); rename + via `renamedTo()` so old suppressions keep working. +- Set warning suppression on `AstVar`, not `AstVarRef` -- VarRefs get recreated + and lose `warnIsOff`. +- "Unsupported:" messages describe the specific unsupported context, not just the + construct name -- each message must be distinct. +- When replacing or refactoring a pass, keep existing user-facing error strings -- + `.out` goldens and documentation depend on the wording. + +## Performance and memory + +- O(n^2) is never acceptable -- build maps for batch lookups; any quadratic loop + needs explicit justification in a comment. +- Prefer `std::map` for per-module structures (many small instances); use + `unordered_map` only for one-per-netlist data, and never let `unordered_*` + iteration order reach generated output. +- Prefer `emplace` over `insert` and check the returned `.second` instead of a + separate `find()`. `reserve()` strings and vectors when the size is estimable. +- Add no new static or global mutable data -- statics are being eliminated for + future parallelism. +- Use Verilator's fixed-width data types for model data (`CData`/`SData`/`IData`/ + `QData`/`VlWide`), not `size_t`. Process wide data word-by-word + (`VL_ZERO_W`, `VL_MEMCPY_W`), never bit-by-bit. +- No exceptions in verilated runtime code; do string parsing at verilation time, + never during simulation. +- Wrap unlikely hot-path branches in `VL_UNLIKELY`/`VL_LIKELY`. +- Count what every new pass does via V3Stats -- stats become deterministic + regression anchors. + +## Thread safety + +- Annotate with the hierarchy `VL_PURE` > `VL_MT_SAFE` > `VL_MT_STABLE`: PURE has + no side effects and calls only PURE; MT_SAFE is safe under locks; MT_STABLE is + safe only while tree topology is stable. Annotations must match the + implementation. +- Never include `verilated.h` in the compiler itself -- use `verilatedos.h`. +- Annotate mutex-protected members with `VL_GUARDED_BY` and document acquisition + ordering. `++` on shared state and container `empty()` are not thread-safe. + +## Parser and lexer (verilog.y, verilog.l) + +- Preserve IEEE Appendix A BNF comments (`// IEEE: {rule}`); comment explicitly + when accepting syntax beyond IEEE as an extension. +- The parser only builds AST nodes -- defer semantic validation, `VN_IS` checks, + and context-dependent logic to V3LinkParse/V3Width and later passes. +- Represent hierarchical paths as structured nodes (`AstDot`/parse-ref chains via + `idDotted`), never concatenated strings -- preserves per-segment FileLine. +- Prefer tightening a grammar rule's operand type over a runtime cast-chain guard + in a later visitor -- illegal operands then fail with a clean syntax error. +- Solve ambiguities with token-pipeline look-ahead (`tokenPipeScan*`) rather than + limiting grammar rules; mark unsupported rules with `//UNSUP`. +- Sort token declarations alphabetically by string literal; sort `yD_*` + productions by token name. +- Add a test for every `|` alternative and optional clause of a new or changed + grammar rule -- untested alternatives are where parse regressions hide. + +## File-specific rules + +| File | Rule | +|---|---| +| `src/V3Options.cpp` | Chain `.notForRerun()` onto `DECL_OPTION()` for options that do not affect semantic output | +| `src/V3Ast.cpp` | For composite types (queues, dynamic arrays) use `computeCastableImp()` on subtypes -- shallow `width()`/`similarDType()` checks miss nested incompatibility | +| `src/V3AstNode*.h` | Every node class gets a what-construct comment and every member a semantic-purpose comment; put enum type definitions in `V3AstAttr.h` | +| `src/V3AstNodeExpr.h` | `CCast` is only for basic C types (char/short/int/QData) -- never 4-state logic or structs | +| `src/V3AstNodeOther.h` | `cloneRelink` must propagate all stateful flags (e.g. `maybePointedTo`) and update internal references | +| `src/V3Const.cpp` | Check `keepIfEmpty` before removing empty functions -- flagged functions must survive for codegen/side effects | +| `src/V3Coverage.cpp` | Instrumentation contexts are opt-in (allowlist), never blocklist -- blocklists silently break when new contexts appear | +| `src/V3Inline.cpp` | Preserve `VarXRef::varp()` during passes -- pin-reconnection needs it before V3LinkDot re-resolves | +| `src/V3Sched*.cpp` | Every change needs a test proving necessity; isolate unrelated scheduler changes into separate PRs -- high-risk area | diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md new file mode 100644 index 000000000..05833d460 --- /dev/null +++ b/test_regress/AGENTS.md @@ -0,0 +1,128 @@ + + +# test_regress/ -- regression tests + +Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under +`test_regress/`. Read the repository-root [AGENTS.md](../AGENTS.md) first. This +file has two parts: **Orientation** explains how the harness runs a test; +**Before you open a PR** is the test-authoring checklist. + +--- + +# Orientation: how the harness works + +- **A test is a `.v`/`.sv` source plus a matching `.py` driver in `t/`.** The + driver calls `test.compile()`, `test.execute()`, and/or `test.lint()`. Without a + `.py` the source never runs and is dead code giving false coverage confidence. +- **Golden output lives in `.out` files**, compared via `expect_filename`. + Generate them with `HARNESS_UPDATE_GOLDEN=1 python3 t/.py` -- never + hand-write them; the harness normalizes paths, version markers, and line numbers + that hand edits get wrong. +- **Run one test from the repository root:** `test_regress/t/t_.py`. When + running from a checkout, set `VERILATOR_ROOT` to the checkout root first so the + harness compiles the generated C++ against the right headers. +- **`test.compile()` defaults are richer than they look:** the vlt driver + auto-injects `--exe` and a generated main, and `assert property` fires its + action blocks without `--assert` -- try the simple form before adding flags. +- **The `t_dist_*` tests enforce repository conventions** (file headers, sorted + lists, warning documentation, ASCII-only) -- run the relevant ones before + submitting. + +--- + +# Before you open a PR + +## Naming + +- Name tests `t_{category}_{description}` in snake_case; the first word groups the + category (`t_lint_unused_func_bad`, not `t_unused_func_lint_bad`) so related + tests are findable and runnable together. +- Use `_bad` when the code is illegal SystemVerilog, `_unsup` when it is legal but + unsupported, `_off` for disabled-behavior tests -- never `_fail`. +- Keep filenames under roughly 30-35 characters. + +## Files and drivers + +- Every `.v` needs a matching `.py` driver that actually calls `test.compile()` + and `test.execute()` (or `test.lint()` for static-analysis-only tests) -- a + driver that sets up but never runs hides coverage gaps silently. +- Copy the license header from an existing file: `.v` tests carry the CC0 notice, + `.py` drivers carry the standard driver header -- distribution tests check + headers on every committed file. +- Use `--binary` instead of `--exe --main --timing` -- it implies the others. +- Error tests use `test.compile(fails=True, expect_filename=test.golden_filename)` + (or `test.lint(...)`) and must not call `test.execute()`. +- Use `.out` golden files with `expect_filename` instead of inline `expect` + regex strings -- goldens are diffable and maintainable. +- Use `test.glob_one()`/`test.glob_some()` and `test.timeout()` instead of raw + `glob.glob()` and manual `time.time()` measurements. +- Coverage tests run `verilator_coverage` and verify individual bins and points, + not just aggregate percentages. +- Assert optimization stats with exact expected counts: + `test.file_grep(test.stats, r'Optimizations, ...\s+(\d+)', N)`. +- Add a `_protect_ids` variant when a feature emits user identifiers or filenames; + use conservative thread counts (2 or fewer) in multithreaded tests. +- Extend existing test files with related cases instead of creating many + single-purpose files; keep drivers minimal -- test logic belongs in the `.v`. + +## Golden .out files + +- Never hand-write or hand-edit `.out` goldens -- regenerate with + `HARNESS_UPDATE_GOLDEN=1`. +- When a feature lands, remove its now-supported entries from `t_*_unsup.v` / + `t_*_bad.v` in the same change and regenerate the goldens -- stale entries no + longer error and reviewers will flag them. + +## Verilog style + +- 2-space indentation, no tabs. +- Declarations are flush-left with a single space between type and name; never + column-align: + + ```systemverilog + // WRONG: column-aligned + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + // RIGHT: flush-left + bit [63:0] crc = 64'h5aef0c8d_d70a4497; + int cyc = 0; + ``` + +- Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in + `// verilog_format: off`/`on` so the formatter does not split them. +- Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding. +- Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``. +- Use inline `// verilator lint_off WARNCODE` only when that warning is itself + under test -- fix root causes otherwise. +- Use only IEEE 1800-compliant constructs other simulators also accept -- tests + validate standard behavior, not Verilator's parser leniency. +- Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`. + +## Self-checking + +- Use the `checkh`/`checkd`/`checks` assertion macros, not manual + `if`/`$display`/`$stop` sequences. Note `checkh` prints with `%p`, which renders + integers as hex -- use `checkd` for integer comparisons. +- Use the `` `stop `` macro rather than direct `$stop`. +- Drive logic with runtime-varying inputs (counters, CRC/LFSR registers) so + constant folding cannot pre-evaluate the logic under test; check behavior across + multiple clock cycles, not just initial values. +- For a test whose pass/fail depends on varying or random values, loop enough + iterations that the values demonstrably differ and size the value space so a + failure is probable per run, then confirm the test fails on an un-fixed tree + before submitting. + +## Test design + +- Use non-power-of-2, non-word-aligned widths (7, 15, 31, 33, 63, 65, 95) -- + exposes masking and word-boundary bugs that 32/64/128 hide. +- Test both `[high:low]` and `[low:high]` orderings plus non-zero bounds like + `[3:1]`; use different ranges for each axis of multidimensional arrays. +- When adding type support, test all basic types (chandle, string, real) and + typedef-wrapped variants. +- Include the issue's own reproducer as a committed test, and verify it fails + without the fix. +- Assert non-blocking-assignment results in the cycle immediately after they take + effect, before later overwrites, using indices that change post-NBA. From 0233e5044cb42dc53d7d9c58d6230bdd344f6830 Mon Sep 17 00:00:00 2001 From: Yilou Wang Date: Mon, 15 Jun 2026 14:56:34 +0200 Subject: [PATCH 43/46] Tests: Restore Antmicro copyright on test files accidentally overwritten (#7786) --- test_regress/t/t_constraint_unsup.v | 2 +- test_regress/t/t_property_sexpr_multi.v | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test_regress/t/t_constraint_unsup.v b/test_regress/t/t_constraint_unsup.v index 4b1c95031..84f67e5ce 100644 --- a/test_regress/t/t_constraint_unsup.v +++ b/test_regress/t/t_constraint_unsup.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 class Packet; diff --git a/test_regress/t/t_property_sexpr_multi.v b/test_regress/t/t_property_sexpr_multi.v index 9a99739e5..ff126810f 100644 --- a/test_regress/t/t_property_sexpr_multi.v +++ b/test_regress/t/t_property_sexpr_multi.v @@ -1,7 +1,7 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2026 PlanV GmbH +// SPDX-FileCopyrightText: 2025 Antmicro // SPDX-License-Identifier: CC0-1.0 // verilog_format: off From 7061c1f04d3356a22568ddea80b6d20cdeba396e Mon Sep 17 00:00:00 2001 From: Artur Bieniek Date: Mon, 15 Jun 2026 21:32:11 +0200 Subject: [PATCH 44/46] Fix not failing assertion when RHS of a range window rejects once (#7773) --- src/V3AssertNfa.cpp | 83 ++++++++++++++++--- test_regress/t/t_property_sexpr_range_delay.v | 34 ++++++-- 2 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/V3AssertNfa.cpp b/src/V3AssertNfa.cpp index c5c36178d..a7f2a8c1b 100644 --- a/src/V3AssertNfa.cpp +++ b/src/V3AssertNfa.cpp @@ -110,6 +110,9 @@ public: // Reject when source is active and condp is false; set only on // outermost required-step Link bool m_rejectOnFail = false; + // Optional dynamic condition vertex for m_rejectOnFail. Used when the + // success condition is another NFA state rather than a static expression. + SvaStateVertex* m_condVtxp = nullptr; // CONSTRUCTORS SvaTransEdge(V3Graph* graphp, V3GraphVertex* fromp, V3GraphVertex* top, AstNodeExpr* condp, @@ -208,6 +211,12 @@ class SvaNfaBuilder final { // topology when true. Default false preserves cover_property semantics. bool m_isCoverSeq = false; + struct RangeDelayRejectInfo final { + SvaStateVertex* startp = nullptr; + int range = 0; + int rhsLen = 0; + }; + AstNodeExpr* throughoutCond(AstNodeExpr* baseCondp, FileLine* flp) { if (m_throughoutStack.empty()) return baseCondp; // AND all throughout conditions (supports nesting) @@ -374,7 +383,7 @@ class SvaNfaBuilder final { // failure, sets outErrorEmitted per semantic-error policy and returns false. bool applyRangeDelay(AstDelay* delayp, AstNodeExpr* rhsExprp, SvaStateVertex*& currentp, std::vector& midSources, FileLine* flp, - bool& outErrorEmitted) { + bool& outErrorEmitted, RangeDelayRejectInfo* rangeRejectInfop = nullptr) { const int minDelay = getConstInt(delayp->lhsp()); if (minDelay < 0) { delayp->v3error("Range delay minimum is not a non-negative elaboration-time constant" @@ -433,8 +442,14 @@ class SvaNfaBuilder final { guardedEdge(currentp, counterVtxp, flp); currentp = counterVtxp; } else if (VN_IS(rhsExprp, SExpr)) { - // Nested-SExpr RHS: merge all [M,N] positions; continuation is per-attempt. + // Nested-SExpr RHS: merge all [M,N] positions. Candidate-local misses + // are not assertion rejects while a later position can still match. + if (rangeRejectInfop) { + const int rhsLen = fixedLength(rhsExprp); + if (rhsLen >= 0) *rangeRejectInfop = {currentp, range, rhsLen}; + } SvaStateVertex* const mergeVtxp = scopedCreateVertex(); + mergeVtxp->m_isUnbounded = true; guardedLink(currentp, mergeVtxp, flp); for (int i = 0; i < range; ++i) { SvaStateVertex* const nextVtxp = scopedCreateVertex(); @@ -443,6 +458,7 @@ class SvaNfaBuilder final { currentp = nextVtxp; } currentp = mergeVtxp; + m_inUnboundedScope = true; } else { // Pure boolean RHS: register chain. Each mid-position links to // match (match-only); last position is the reject source. @@ -468,12 +484,44 @@ class SvaNfaBuilder final { return true; } + void addFiniteRangeReject(const RangeDelayRejectInfo& info, const BuildResult& result, + FileLine* flp) { + if (!info.startp) return; + + SvaStateVertex* const expiryVtxp + = addDelayChain(info.startp, info.range + info.rhsLen, flp); + SvaStateVertex* const expiryMatchp = scopedCreateVertex(); + std::vector sources = result.midSources; + sources.push_back(result.termVertexp); + for (SvaStateVertex* const srcp : sources) { + AstNodeExpr* const condp + = result.finalCondp ? sampled(result.finalCondp->cloneTreePure(false)) : nullptr; + SvaStateVertex* const successNowp = scopedCreateVertex(); + guardedLink(srcp, successNowp, condp, flp); + SvaStateVertex* stagep = successNowp; + guardedLink(stagep, expiryMatchp, flp); + for (int i = 0; i < info.range; ++i) { + SvaStateVertex* const nextp = scopedCreateVertex(); + guardedEdge(stagep, nextp, flp); + stagep = nextp; + guardedLink(stagep, expiryMatchp, flp); + } + } + + SvaStateVertex* const sinkVtxp = m_graph.createStateVertex(); + sinkVtxp->m_isRejectSink = true; + SvaTransEdge* const rejectp = m_graph.addLink(expiryVtxp, sinkVtxp); + rejectp->m_rejectOnFail = true; + rejectp->m_condVtxp = expiryMatchp; + } + BuildResult buildSExpr(AstSExpr* sexprp, SvaStateVertex* entryVtxp, bool isTopLevelStep = false) { AstDelay* const delayp = VN_CAST(sexprp->delayp(), Delay); if (!delayp || !delayp->isCycleDelay()) return BuildResult::fail(); FileLine* const flp = sexprp->fileline(); + AstNodeExpr* const exprp = sexprp->exprp(); // Handle LHS (preExpr) SvaStateVertex* currentp = entryVtxp; @@ -496,10 +544,12 @@ class SvaNfaBuilder final { // Handle delay std::vector rangeMidSources; + RangeDelayRejectInfo rangeRejectInfo; + const bool addRangeReject = isTopLevelStep && !m_inUnboundedScope; if (delayp->isRangeDelay()) { bool errorEmitted = false; if (!applyRangeDelay(delayp, sexprp->exprp(), currentp, rangeMidSources, flp, - errorEmitted)) { + errorEmitted, addRangeReject ? &rangeRejectInfo : nullptr)) { return BuildResult::fail(errorEmitted); } } else { @@ -514,8 +564,11 @@ class SvaNfaBuilder final { } // Multi-cycle RHS: recurse (only plain boolean is returned as finalCondp). - AstNodeExpr* const exprp = sexprp->exprp(); - if (exprp->isMultiCycleSva()) return buildExpr(exprp, currentp, isTopLevelStep); + if (exprp->isMultiCycleSva()) { + const BuildResult result = buildExpr(exprp, currentp, isTopLevelStep); + if (result.valid()) addFiniteRangeReject(rangeRejectInfo, result, flp); + return result; + } return {currentp, exprp, std::move(rangeMidSources)}; } @@ -1505,15 +1558,25 @@ class SvaNfaLowering final { computeTerminalMatchAndReject(c, snapshotOkp, sigs, outPerMidSrcsp); // Phase 3a: required-step rejection. - // Builder only sets m_rejectOnFail on non-clocked Links with m_condp, - // and the source always has a resolved stateSig. + // Builder only sets m_rejectOnFail on non-clocked Links with m_condp + // or m_condVtxp, and the source always has a resolved stateSig. for (const SvaTransEdge* const tep : c.edges) { if (!tep->m_rejectOnFail) continue; const int fi = tep->fromVtxp()->color(); - UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && tep->m_condp, tep->fromVtxp(), - "rejectOnFail Link must have condp and source stateSig"); + UASSERT_OBJ(c.vtx[fi]->datap()->stateSigp && (tep->m_condp || tep->m_condVtxp), + tep->fromVtxp(), + "rejectOnFail Link must have condp/condVtxp and source stateSig"); AstNodeExpr* const srcSigp = c.vtx[fi]->datap()->stateSigp->cloneTreePure(false); - AstNodeExpr* const notCondp = new AstLogNot{c.flp, tep->m_condp->cloneTreePure(false)}; + AstNodeExpr* condp = nullptr; + if (tep->m_condVtxp) { + const int ci = tep->m_condVtxp->color(); + UASSERT_OBJ(c.vtx[ci]->datap()->stateSigp, tep->m_condVtxp, + "rejectOnFail condVtxp missing stateSig"); + condp = c.vtx[ci]->datap()->stateSigp->cloneTreePure(false); + } else { + condp = tep->m_condp->cloneTreePure(false); + } + AstNodeExpr* const notCondp = new AstLogNot{c.flp, condp}; AstNodeExpr* const failp = new AstLogAnd{c.flp, srcSigp, notCondp}; if (outRequiredStepSrcsp) { outRequiredStepSrcsp->push_back(failp->cloneTreePure(false)); diff --git a/test_regress/t/t_property_sexpr_range_delay.v b/test_regress/t/t_property_sexpr_range_delay.v index 5594b6a54..cd3037d03 100644 --- a/test_regress/t/t_property_sexpr_range_delay.v +++ b/test_regress/t/t_property_sexpr_range_delay.v @@ -30,6 +30,7 @@ module t ( int count_fail2 = 0; int count_fail3 = 0; int count_fail4 = 0; + int count_fail5 = 0; always_ff @(posedge clk) begin `ifdef TEST_VERBOSE @@ -49,13 +50,11 @@ module t ( else if (cyc == 99) begin `checkh(crc, 64'hc77bb9b3784ea091); `checkh(sum, 64'h38c614665c6b71ad); - // count_fail1 overcounts Questa by 1: NFA per-cycle reject merging - // OR's requiredStep-fail and terminal-fail into one signal; Questa - // resolves the same overlap as a single per-attempt miss. - `checkd(count_fail1, 25); // Questa: 24 - `checkd(count_fail2, 50); // Questa: 50 - `checkd(count_fail3, 24); // Questa: 24 - `checkd(count_fail4, 1); // Questa: 1 + `checkd(count_fail1, 24); + `checkd(count_fail2, 50); + `checkd(count_fail3, 24); + `checkd(count_fail4, 1); + `checkd(count_fail5, 1); $write("*-* All Finished *-*\n"); $finish; end @@ -131,4 +130,25 @@ module t ( assert property (@(posedge clk) disable iff (cyc < 2) a |-> ##[+] b ##1 (a | b | c | d | e)); + // Finite range delay with a multi-cycle RHS must not reject on an earlier + // candidate when a later candidate in the same window matches. + assert property (@(posedge clk) + cyc == 10 |-> ##[2:4] ((cyc == 12 || cyc == 13) ##1 cyc == 14)); + + // Same shape, but every RHS candidate in the range window rejects, so the + // assertion attempt itself must reject once. + assert property (@(posedge clk) + cyc == 20 |-> ##[2:4] ((cyc == 22 || cyc == 23) ##1 cyc == 25)) + else count_fail5 <= count_fail5 + 1; + + // Variable-length nested RHS, then another finite range below + // the liveness scope. + assert property (@(posedge clk) + cyc == 30 |-> ##[1:2] ((cyc == 31) ##[1:2] ((cyc == 32) ##1 1'b1))); + + // Finite range whose RHS ends in an NFA state, not a final + // boolean condition. + assert property (@(posedge clk) + cyc == 40 |-> ##[1:2] (##1 (((cyc == 43) ##1 1'b1) or ((cyc == 43) ##1 1'b1)))); + endmodule From c86816476c132049cc7755da42ddee2414812378 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 15 Jun 2026 17:37:49 -0400 Subject: [PATCH 45/46] Commentary: Changes update --- Changes | 4 ++++ docs/guide/exe_verilator.rst | 2 +- docs/spelling.txt | 1 + .../t_sched_ico_change_detect_input_assigned.v | 17 ++++++++++------- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Changes b/Changes index 9877ca716..afe5731e2 100644 --- a/Changes +++ b/Changes @@ -66,6 +66,8 @@ Verilator 5.049 devel * Support `s_until` and `s_until_with`(#7722). [Artur Bieniek, Antmicro Ltd.] * Support covergroup runtime model Phase A1 (#7728). [Matthew Ballance] * Support reduction XOR/AND operations in constraints (#7753). [Kornel Uriasz, Antmicro Ltd.] +* Support assertion control system tasks in classes and interfaces (#7761). [Yilou Wang] +* Support cover sequence statement (#7764). [Yilou Wang] * Support unpacked struct stream (#7767). [Nick Brereton] * Optimize emitting to_string() for compiler speedup (#7468). [Jakub Michalski, Antmicro Ltd.] * Optimize additional DFG peephole cases (#7553). [Varun Koyyalagunta, Testorrent USA, Inc.] @@ -86,6 +88,7 @@ Verilator 5.049 devel * Optimize bit select removal earlier in DFG (#7762). [Geza Lore, Testorrent USA, Inc.] * Optimize away proven redundant case statement assertions (#7771). [Geza Lore, Testorrent USA, Inc.] * Optimize table lookups in DFG (#7772). [Geza Lore, Testorrent USA, Inc.] +* Optimize input combinational logic by change detection (#7784). [Geza Lore, Testorrent USA, Inc.] * Fix TSP variable ordering for mtasks (#5342) (#7610). [Muzaffer Kal] * Fix inlining static initializer in V3Gate (#5381) (#7503). [Andrew Nolte] [Geza Lore, Testorrent USA, Inc.] * Fix timed nested fork block with disable (#6720) (#7743). [Marco Bartoli] @@ -161,6 +164,7 @@ Verilator 5.049 devel * Fix FSM detect unchecked casts and variable redeclaration (#7758). [Adam Kostrzewski, Antmicro Ltd.] * Fix no-scope internal error on virtual interface method calls (#7759). [Yilou Wang] * Fix 'case (_) inside' with x wildcards (#7766). [Geza Lore, Testorrent USA, Inc.] +* Fix not failing assertion when RHS of a range window rejects once (#7773). [Artur Bieniek, Antmicro Ltd.] * Fix $fflush and autoflush with --threads (#7782). diff --git a/docs/guide/exe_verilator.rst b/docs/guide/exe_verilator.rst index 73ed42486..29e3876c4 100644 --- a/docs/guide/exe_verilator.rst +++ b/docs/guide/exe_verilator.rst @@ -750,7 +750,7 @@ Summary: for top level input signals that are written within the design. Accesses via the VPI cannot be analyzed at compile time, therefore :vlopt:`--vpi` disables this optimization for all inputs; it may be turned back on by - explicitly passing :vlopt:`-fico-change-detect`. + explicitly passing :vlopt:`-fico-change-detect <-fno-ico-change-detect>`. .. option:: -fno-inline diff --git a/docs/spelling.txt b/docs/spelling.txt index c41825793..a21f66ed5 100644 --- a/docs/spelling.txt +++ b/docs/spelling.txt @@ -861,6 +861,7 @@ hx hyperthreading hyperthreads icecream +ico idmap ifdef ifdefed diff --git a/test_regress/t/t_sched_ico_change_detect_input_assigned.v b/test_regress/t/t_sched_ico_change_detect_input_assigned.v index 0f6f9025d..9a4f79b0b 100644 --- a/test_regress/t/t_sched_ico_change_detect_input_assigned.v +++ b/test_regress/t/t_sched_ico_change_detect_input_assigned.v @@ -10,20 +10,23 @@ `define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); // verilog_format: on -module t( - clk, i, o, cyc +module t ( + clk, + i, + o, + cyc ); input clk, i; output o, cyc; - logic clk; - int i; // Primary input that the design also drives - int o; - int cyc = 0; + logic clk; + int i; // Primary input that the design also drives + int o; + int cyc = 0; // Logic dependent on primary input 'i' - always_comb o = i + 10; + always_comb o = i + 10; always @(posedge clk) begin cyc <= cyc + 1; From 0e4a3a92b0f922508e4bc155bc47df1d523bccb3 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 15 Jun 2026 17:44:50 -0400 Subject: [PATCH 46/46] CI: Autoformat markdown files --- AGENTS.md | 36 ++++++++++++++++++------------------ Makefile.in | 14 +++++++++++++- docs/AGENTS.md | 2 +- include/AGENTS.md | 4 ++-- python-dev-requirements.txt | 1 + src/AGENTS.md | 18 ++++++++++++++++-- test_regress/AGENTS.md | 10 ++++++++-- 7 files changed, 59 insertions(+), 26 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1f0c6a6be..2f5f06eda 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -23,7 +23,7 @@ Then read the directory guide for the area you are editing: - [test_regress/AGENTS.md](test_regress/AGENTS.md) -- regression tests: harness, drivers, golden files - [docs/AGENTS.md](docs/AGENTS.md) -- documentation (`*.rst`) ---- +______________________________________________________________________ # Orientation @@ -75,7 +75,7 @@ top-of-file comment. - Run the full regression with `make test`. The complete suite requires configuring with `--enable-longtests` (works on every OS, including macOS). ---- +______________________________________________________________________ # Before you open a PR @@ -83,21 +83,21 @@ top-of-file comment. - [ ] Searched open PRs and issues -- duplicating in-flight work wastes review time. - [ ] Fixed the general root cause, not just the reported case -- if it also - affects other modules/classes/interfaces, cover them or expect rejection. + affects other modules/classes/interfaces, cover them or expect rejection. - [ ] PR is single-purpose. Refactors, drive-by fixes found along the way, and new - features each go in separate PRs; land standalone cleanups first. + features each go in separate PRs; land standalone cleanups first. - [ ] Every bug fix has a test that fails *without* the fix; include the issue's - own reproducer when possible. + own reproducer when possible. - [ ] New code aims for 100% line coverage; branch coverage far below line coverage - signals guards callers never violate -- justify or remove them. + signals guards callers never violate -- justify or remove them. - [ ] Ran `make format` (clang-format), `make cppcheck`, and `make lint-py`; - self-reviewed the diff for leftover debug code, stale comments, and - copy-paste errors. + self-reviewed the diff for leftover debug code, stale comments, and + copy-paste errors. - [ ] Ran the full regression on at least one OS before submitting. Partial runs - are fine during development, but the submitted PR is expected to pass every - test. + are fine during development, but the submitted PR is expected to pass every + test. - [ ] Did not edit `docs/CONTRIBUTORS` (humans only) or `Changes` (maintainer - updates it near release). + updates it near release). ## Pick the right diagnostic (and its required test) @@ -122,18 +122,18 @@ The API you choose determines which test must accompany the change. ## Cross-cutting code rules - [ ] No non-ASCII characters in C++ sources or headers: write `--` (two ASCII - hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart - quote. At write time, not when CI complains. + hyphens) rather than a Unicode em-dash, and a plain `'` rather than a smart + quote. At write time, not when CI complains. - [ ] Lists stay sorted: lexer/parser tokens, option declarations, enum values, - configure feature lists, documented option lists. + configure feature lists, documented option lists. - [ ] `bin/` scripts are Python (distributed cross-platform); `nodist/` may use - bash and platform-specific code (developer-only, not packaged). + bash and platform-specific code (developer-only, not packaged). - [ ] Runtime code in `include/` targets C++14 (`--no-timing` builds must work); - C++20 only in timing code paths. + C++20 only in timing code paths. - [ ] In `include/` public headers, prefix public classes with `Verilated`/`Vl` - and document the API with `///` comments. + and document the API with `///` comments. - [ ] A new code pattern is applied globally or not at all -- no one-off - convention in a single file. + convention in a single file. ## Commits diff --git a/Makefile.in b/Makefile.in index c2b60d004..6bcbdbaa5 100644 --- a/Makefile.in +++ b/Makefile.in @@ -495,6 +495,11 @@ MAKE_FILES = \ src/Makefile*.in \ test_regress/Makefile* \ +# Markdown +MD_FILES = \ + *.md \ + */*.md \ + # Perl programs PERL_PROGRAMS = \ bin/redirect \ @@ -553,7 +558,7 @@ YAML_FILES = \ # Format format: - $(MAKE) -j 5 format-c format-cmake format-exec format-py format-yaml + $(MAKE) -j 5 format-c format-cmake format-exec format-md format-py format-yaml BEAUTYSH = beautysh BEAUTYSH_FLAGS = --indent-size 2 @@ -590,6 +595,13 @@ format-make mbake: $(MBAKE) --version $(MBAKE) $(MBAKE_FLAGS) $(MAKE_FILES) +MDFORMAT = mdformat +MDFORMAT_FLAGS = + +format-md: + $(MDFORMAT) --version + $(MDFORMAT) $(MDFORMAT_FLAGS) $(MD_FILES) + YAPF = yapf YAPF_FLAGS = -i --parallel diff --git a/docs/AGENTS.md b/docs/AGENTS.md index ee4ae5249..1c0524c78 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -2,7 +2,7 @@ SPDX-FileCopyrightText: 2026-2026 Wilson Snyder SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 --> -# docs/ Guidelines -- Verilator documentation (*.rst) +# docs/ Guidelines -- Verilator documentation (\*.rst) When to check: before editing anything under `docs/`. Read the repository-root [AGENTS.md](../AGENTS.md) first for process and cross-cutting rules. diff --git a/include/AGENTS.md b/include/AGENTS.md index 5b409dfaf..0ac1fcbfe 100644 --- a/include/AGENTS.md +++ b/include/AGENTS.md @@ -9,7 +9,7 @@ that every generated model links against. Read the repository-root [AGENTS.md](../AGENTS.md) first. The rules here differ from `src/`: this code ships to users, runs every simulation cycle, and must stay portable and fast. ---- +______________________________________________________________________ # Orientation @@ -22,7 +22,7 @@ ships to users, runs every simulation cycle, and must stay portable and fast. (`--timing` runtime), `verilated_vcd_c.*`/`verilated_fst_c.*` (tracing). - A runtime-only fix lives entirely here and does not rebuild `verilator_bin`. ---- +______________________________________________________________________ # Before you open a PR diff --git a/python-dev-requirements.txt b/python-dev-requirements.txt index 11f9fad84..d6bcba7b1 100644 --- a/python-dev-requirements.txt +++ b/python-dev-requirements.txt @@ -21,6 +21,7 @@ compiledb==0.10.7 distro==1.9.0 gersemi==0.23.1 mbake==1.4.3 +mdformat==1.0.0 mypy==1.19.0 pylint==3.0.2 ruff==0.14.8 diff --git a/src/AGENTS.md b/src/AGENTS.md index 1904eb5c9..cb53dc31c 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -9,7 +9,7 @@ Covers all C++ under `src/`, including astgen inputs and the parser/lexer first. This file has two parts: **Orientation** explains the AST and pass model; **Before you open a PR** is the style and correctness checklist. ---- +______________________________________________________________________ # Orientation: the AST and the visitor model @@ -38,7 +38,7 @@ first. This file has two parts: **Orientation** explains the AST and pass model; - `docs/internals.rst` is the authoritative reference for the AST, the pass list, and node lifetime. ---- +______________________________________________________________________ # Before you open a PR @@ -77,6 +77,7 @@ first. This file has two parts: **Orientation** explains the AST and pass model; - Build logic as AST nodes, never as raw C text in `AstCStmt` -- later passes (V3Name, `--protect`) rename AST identifiers but cannot see into raw strings. + - Know the cast forms (above) and never pair `VN_IS` with `VN_AS` on the same node -- use a single `VN_CAST`: @@ -89,34 +90,47 @@ first. This file has two parts: **Orientation** explains the AST and pass model; - Use `UASSERT_OBJ(cond, nodep, "...")` over `UASSERT` when a node is in scope; use `v3fatalSrc("...")` for unreachable paths, never a silent `if (!ptr) return;`. + - Use `VL_DO_DANGLING(pushDeletep(nodep), nodep)` instead of `deleteTree()` in visitors -- deferred deletion is safe against re-entry and unlinking order. `deleteTree()` is only for fresh nodes that never entered the tree. + - Every new AST member needs both `dump()` and `dumpJson()` support -- never wrap these in `LCOV_EXCL`; cover them by adding a construct to `t_debug_emitv.v`. Override `isSame()` to include any new semantically meaningful field. + - Pointers to nodes outside op1p-op4p require a `broken()` override and `cloneRelink()` support -- avoid storing out-of-tree node pointers when possible. + - Never allocate AstNode objects on the stack or by value -- always pointers. + - Prefer a new `visit()` in an existing visitor over `nodep->foreach(...)` -- better for runtime, and handles diverse node types better. Prefer named accessors over `op1p()`..`op4p()`, and verify traversal order is preserved when converting manual iteration. + - Prefer `AstForeach` over generating unrolled loop bodies -- constant-size code instead of O(N); wrap the body in `AstBegin` for scope isolation. + - Always `skipRefp()` when comparing or resolving dtypes -- missing it breaks typedef support; prove the paths with typedef tests. + - Use `num().isOpaque()` rather than `isDouble() || isString()` when guarding V3Number comparisons against non-integer types. + - Use `FileLine::operatorCompare` for source-position ordering -- never hand-roll filename/lineno comparisons. + - Identify compiler-generated constructs by an attribute flag on the node (with dump support), never by name-pattern matching -- magic names break with escaped identifiers. + - Use `V3Number` arithmetic for `AstConst` values wider than 32 bits -- `1 << i` silently overflows at `i >= 32`. + - Use `VMemberMap`/`findMember()` for name lookups in structs, classes, modules, and packages -- O(1) versus quadratic linear scans. + - Never emit raw source filenames or identifiers in generated code -- pass them through `protect()`/`putsQuoted` so `--protect` does not leak source. diff --git a/test_regress/AGENTS.md b/test_regress/AGENTS.md index 05833d460..7d6b1b708 100644 --- a/test_regress/AGENTS.md +++ b/test_regress/AGENTS.md @@ -9,7 +9,7 @@ Covers `.v`/`.sv` sources, `.py` drivers, and `.out` golden files under file has two parts: **Orientation** explains how the harness runs a test; **Before you open a PR** is the test-authoring checklist. ---- +______________________________________________________________________ # Orientation: how the harness works @@ -30,7 +30,7 @@ file has two parts: **Orientation** explains how the harness runs a test; lists, warning documentation, ASCII-only) -- run the relevant ones before submitting. ---- +______________________________________________________________________ # Before you open a PR @@ -78,6 +78,7 @@ file has two parts: **Orientation** explains how the harness runs a test; ## Verilog style - 2-space indentation, no tabs. + - Declarations are flush-left with a single space between type and name; never column-align: @@ -92,12 +93,17 @@ file has two parts: **Orientation** explains how the harness runs a test; - Run `nodist/verilog_format` on new `.v` files; wrap macro definitions in `// verilog_format: off`/`on` so the formatter does not split them. + - Use `$display("%0d", ...)` not `%d` -- avoids leading-space padding. + - Wrap Verilator-specific test code (e.g. `$c`) in `` `ifdef VERILATOR ``. + - Use inline `// verilator lint_off WARNCODE` only when that warning is itself under test -- fix root causes otherwise. + - Use only IEEE 1800-compliant constructs other simulators also accept -- tests validate standard behavior, not Verilator's parser leniency. + - Omit optional end labels on `endmodule`/`endclass`/`endtask`/`endfunction`. ## Self-checking