From dc5a17fea01dca07ad3315a53de195b56e8899b2 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Wed, 20 Aug 2025 21:01:34 -0400 Subject: [PATCH] Support unpacked array `with` methods (#6134). --- Changes | 1 + include/verilated_types.h | 60 +++++++++ src/V3Width.cpp | 64 ++++++---- test_regress/t/t_array_method.v | 163 ++++++++++++++---------- test_regress/t/t_array_method_unsup.out | 22 ---- test_regress/t/t_array_method_unsup.py | 19 --- test_regress/t/t_array_method_unsup.v | 34 ----- 7 files changed, 195 insertions(+), 168 deletions(-) delete mode 100644 test_regress/t/t_array_method_unsup.out delete mode 100755 test_regress/t/t_array_method_unsup.py delete mode 100644 test_regress/t/t_array_method_unsup.v diff --git a/Changes b/Changes index d1905b720..c9269bce3 100644 --- a/Changes +++ b/Changes @@ -29,6 +29,7 @@ Verilator 5.039 devel * Support randomization of scope variables with 'std::randomize()' (#5438) (#6185). [Yilou Wang] * Support disabling a fork in additional contexts (#5432 partial) (#6174) (#6183). [Ryszard Rozak, Antmicro Ltd.] * Support bit queue streaming (#5830) (#6103). [Paul Swirhun] +* Support unpacked array `with` methods (#6134). * Support Verilog real ports as SystemC double ports (#6136) (#6158). [George Polack] * Support `$countones` in constraints (#6144 partial) (#6235). [Ryszard Rozak, Antmicro Ltd.] * Support disable dotted references (#6154). [Ryszard Rozak, Antmicro Ltd.] diff --git a/include/verilated_types.h b/include/verilated_types.h index 969fdec7c..d1d1e8119 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -1313,6 +1313,9 @@ class VlUnpacked final { using Unpacked = T_Value[N_Depth]; public: + template + using WithFuncReturnType = decltype(std::declval()(0, std::declval())); + // MEMBERS // This should be the only data member, otherwise generated static initializers need updating Unpacked m_storage; // Contents of the unpacked array @@ -1561,6 +1564,63 @@ public: return VlQueue::consV(*it); } + T_Value r_sum() const { + T_Value out(0); // Type must have assignment operator + for (const auto& i : m_storage) out += i; + return out; + } + template + T_Value r_sum(T_Func with_func) const { + T_Value out(0); // Type must have assignment operator + for (const auto& i : m_storage) out += with_func(0, i); + return out; + } + T_Value r_product() const { + T_Value out = T_Value(1); + for (const auto& i : m_storage) out *= i; + return out; + } + template + T_Value r_product(T_Func with_func) const { + T_Value out = T_Value(1); + for (const auto& i : m_storage) out *= with_func(0, i); + return out; + } + T_Value r_and() const { + if (m_storage.empty()) return T_Value(0); // The big three do it this way + T_Value out = ~T_Value(0); + for (const auto& i : m_storage) out &= i; + return out; + } + template + T_Value r_and(T_Func with_func) const { + T_Value out = ~T_Value(0); + for (const auto& i : m_storage) out &= with_func(0, i); + return out; + } + T_Value r_or() const { + T_Value out = T_Value(0); + for (const auto& i : m_storage) out |= i; + return out; + } + template + T_Value r_or(T_Func with_func) const { + T_Value out = T_Value(0); + for (const auto& i : m_storage) out |= with_func(0, i); + return out; + } + T_Value r_xor() const { + T_Value out = T_Value(0); + for (const auto& i : m_storage) out ^= i; + return out; + } + template + T_Value r_xor(T_Func with_func) const { + T_Value out = T_Value(0); + for (const auto& i : m_storage) out ^= with_func(0, i); + return out; + } + // Dumping. Verilog: str = $sformatf("%p", assoc) std::string to_string() const { std::string out = "'{"; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 70f52ac73..0753c96d5 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3452,16 +3452,11 @@ class WidthVisitor final : public VNVisitor { } return nullptr; } - void methodOkArguments(AstNodeFTaskRef* nodep, int minArg, int maxArg, - bool withUnsup = false) { + void methodOkArguments(AstNodeFTaskRef* nodep, int minArg, int maxArg) { int narg = 0; for (AstNode* argp = nodep->pinsp(); argp; argp = argp->nextp()) { if (VN_IS(argp, With)) { - if (withUnsup) { - argp->v3warn(E_UNSUPPORTED, "Unsupported: 'with' on this method"); - } else { - argp->v3error("'with' not legal on this method"); - } + argp->v3error("'with' not legal on this method"); // Delete all arguments as nextp() otherwise dangling VL_DO_DANGLING(pushDeletep(argp->unlinkFrBackWithNext()), argp); break; @@ -4325,27 +4320,46 @@ class WidthVisitor final : public VNVisitor { } if (methodId) { - methodOkArguments(nodep, 0, 0, true /*withUnsup*/); - FileLine* const fl = nodep->fileline(); - AstNodeExpr* newp = nullptr; - for (int i = 0; i < adtypep->elementsConst(); ++i) { - AstNodeExpr* const arrayRef = nodep->fromp()->cloneTreePure(false); - AstNodeExpr* const selector = new AstArraySel{fl, arrayRef, i}; - if (!newp) { - newp = selector; - } else { - switch (methodId) { - case ARRAY_OR: newp = new AstOr{fl, newp, selector}; break; - case ARRAY_AND: newp = new AstAnd{fl, newp, selector}; break; - case ARRAY_XOR: newp = new AstXor{fl, newp, selector}; break; - case ARRAY_SUM: newp = new AstAdd{fl, newp, selector}; break; - case ARRAY_PRODUCT: newp = new AstMul{fl, newp, selector}; break; - default: nodep->v3fatalSrc("bad case"); + AstWith* const withp + = methodWithArgument(nodep, false, false, adtypep->subDTypep(), + nodep->findUInt32DType(), adtypep->subDTypep()); + methodOkArguments(nodep, 0, 0); + if (withp) { + methodCallLValueRecurse(nodep, nodep->fromp(), VAccess::READ); + AstCMethodHard* const newp + = new AstCMethodHard{nodep->fileline(), nodep->fromp()->unlinkFrBack(), + "r_" + nodep->name(), withp}; + newp->dtypeFrom(withp ? withp->dtypep() : adtypep->subDTypep()); + if (!nodep->firstAbovep()) newp->dtypeSetVoid(); + newp->protect(false); + newp->didWidth(true); + nodep->replaceWith(newp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); + return; + } else { + // TODO use this code for small vectors, otherwise use runtime reduction. + // Historically we expand all vectors. + FileLine* const fl = nodep->fileline(); + AstNodeExpr* newp = nullptr; + for (int i = 0; i < adtypep->elementsConst(); ++i) { + AstNodeExpr* const arrayRef = nodep->fromp()->cloneTreePure(false); + AstNodeExpr* const selector = new AstArraySel{fl, arrayRef, i}; + if (!newp) { + newp = selector; + } else { + switch (methodId) { + case ARRAY_OR: newp = new AstOr{fl, newp, selector}; break; + case ARRAY_AND: newp = new AstAnd{fl, newp, selector}; break; + case ARRAY_XOR: newp = new AstXor{fl, newp, selector}; break; + case ARRAY_SUM: newp = new AstAdd{fl, newp, selector}; break; + case ARRAY_PRODUCT: newp = new AstMul{fl, newp, selector}; break; + default: nodep->v3fatalSrc("bad case"); + } } } + nodep->replaceWith(newp); + VL_DO_DANGLING(nodep->deleteTree(), nodep); } - nodep->replaceWith(newp); - VL_DO_DANGLING(nodep->deleteTree(), nodep); } else if (AstCMethodHard* newp = methodCallArray(nodep, adtypep)) { newp->protect(false); newp->didWidth(true); diff --git a/test_regress/t/t_array_method.v b/test_regress/t/t_array_method.v index f0447ce0b..0ad0d177f 100644 --- a/test_regress/t/t_array_method.v +++ b/test_regress/t/t_array_method.v @@ -4,90 +4,117 @@ // any use, without warranty, 2019 by 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); `define checkp(gotv,expv_s) do begin string gotv_s; gotv_s = $sformatf("%p", gotv); if ((gotv_s) != (expv_s)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv_s), (expv_s)); `stop; end end while(0); +// verilog_format: on -module t (/*AUTOARG*/); - initial begin - int q[5]; - int qv[$]; // Value returns - int qi[$]; // Index returns - int i; - string v; +module t; + initial begin + int q[5]; + int qv[$]; // Value returns + int qi[$]; // Index returns + int i; + string v; - q = '{1, 2, 2, 4, 3}; - `checkp(q, "'{'h1, 'h2, 'h2, 'h4, 'h3} "); + q = '{1, 2, 2, 4, 3}; + `checkp(q, "'{'h1, 'h2, 'h2, 'h4, 'h3} "); - // NOT tested: with ... selectors + // NOT tested: with ... selectors - q.sort; - `checkp(q, "'{'h1, 'h2, 'h2, 'h3, 'h4} "); - q.sort with (item == 2); - `checkp(q, "'{'h1, 'h3, 'h4, 'h2, 'h2} "); - q.sort(x) with (x == 3); - `checkp(q, "'{'h1, 'h4, 'h2, 'h2, 'h3} "); + q.sort; + `checkp(q, "'{'h1, 'h2, 'h2, 'h3, 'h4} "); + q.sort with (item == 2); + `checkp(q, "'{'h1, 'h3, 'h4, 'h2, 'h2} "); + q.sort(x) with (x == 3); + `checkp(q, "'{'h1, 'h4, 'h2, 'h2, 'h3} "); - q.rsort; - `checkp(q, "'{'h4, 'h3, 'h2, 'h2, 'h1} "); - q.rsort with (item == 2); - `checkp(q, "'{'h2, 'h2, 'h4, 'h3, 'h1} "); + q.rsort; + `checkp(q, "'{'h4, 'h3, 'h2, 'h2, 'h1} "); + q.rsort with (item == 2); + `checkp(q, "'{'h2, 'h2, 'h4, 'h3, 'h1} "); - qv = q.unique; - `checkp(qv, "'{'h2, 'h4, 'h3, 'h1} "); - qi = q.unique_index; qi.sort; - `checkp(qi, "'{'h0, 'h2, 'h3, 'h4} "); - q.reverse; - `checkp(q, "'{'h1, 'h3, 'h4, 'h2, 'h2} "); - q.shuffle(); q.sort; - `checkp(q, "'{'h1, 'h2, 'h2, 'h3, 'h4} "); + qv = q.unique; + `checkp(qv, "'{'h2, 'h4, 'h3, 'h1} "); + qi = q.unique_index; + qi.sort; + `checkp(qi, "'{'h0, 'h2, 'h3, 'h4} "); + q.reverse; + `checkp(q, "'{'h1, 'h3, 'h4, 'h2, 'h2} "); + q.shuffle(); + q.sort; + `checkp(q, "'{'h1, 'h2, 'h2, 'h3, 'h4} "); - // These require an with clause or are illegal - // TODO add a lint check that with clause is provided - qv = q.find with (item == 2); - `checkp(qv, "'{'h2, 'h2} "); - qv = q.find_first with (item == 2); - `checkp(qv, "'{'h2} "); - qv = q.find_last with (item == 2); - `checkp(qv, "'{'h2} "); + // These require an with clause or are illegal + // TODO add a lint check that with clause is provided + qv = q.find with (item == 2); + `checkp(qv, "'{'h2, 'h2} "); + qv = q.find_first with (item == 2); + `checkp(qv, "'{'h2} "); + qv = q.find_last with (item == 2); + `checkp(qv, "'{'h2} "); - qv = q.find with (item == 20); - `checkp(qv, "'{}"); - qv = q.find_first with (item == 20); - `checkp(qv, "'{}"); - qv = q.find_last with (item == 20); - `checkp(qv, "'{}"); + qv = q.find with (item == 20); + `checkp(qv, "'{}"); + qv = q.find_first with (item == 20); + `checkp(qv, "'{}"); + qv = q.find_last with (item == 20); + `checkp(qv, "'{}"); - qi = q.find_index with (item == 2); qi.sort; - `checkp(qi, "'{'h1, 'h2} "); - qi = q.find_first_index with (item == 2); - `checkp(qi, "'{'h1} "); - qi = q.find_last_index with (item == 2); - `checkp(qi, "'{'h2} "); + qi = q.find_index with (item == 2); + qi.sort; + `checkp(qi, "'{'h1, 'h2} "); + qi = q.find_first_index with (item == 2); + `checkp(qi, "'{'h1} "); + qi = q.find_last_index with (item == 2); + `checkp(qi, "'{'h2} "); - qi = q.find_index with (item == 20); qi.sort; - `checkp(qi, "'{}"); - qi = q.find_first_index with (item == 20); - `checkp(qi, "'{}"); - qi = q.find_last_index with (item == 20); - `checkp(qi, "'{}"); + qi = q.find_index with (item == 20); + qi.sort; + `checkp(qi, "'{}"); + qi = q.find_first_index with (item == 20); + `checkp(qi, "'{}"); + qi = q.find_last_index with (item == 20); + `checkp(qi, "'{}"); - qv = q.min; - `checkp(qv, "'{'h1} "); - qv = q.max; - `checkp(qv, "'{'h4} "); + qv = q.min; + `checkp(qv, "'{'h1} "); + qv = q.max; + `checkp(qv, "'{'h4} "); - // Reduction methods + // Reduction methods - i = q.sum; `checkh(i, 32'hc); - i = q.product; `checkh(i, 32'h30); + i = q.sum; + `checkh(i, 32'hc); + i = q.product; + `checkh(i, 32'h30); - q = '{32'b1100, 32'b1010, 32'b1100, 32'b1010, 32'b1010}; - i = q.and; `checkh(i, 32'b1000); - i = q.or; `checkh(i, 32'b1110); - i = q.xor; `checkh(i, 32'ha); + q = '{32'b1100, 32'b1010, 32'b1100, 32'b1010, 32'b1010}; + i = q.and; + `checkh(i, 32'b1000); + i = q.or; + `checkh(i, 32'b1110); + i = q.xor; + `checkh(i, 32'ha); - $write("*-* All Finished *-*\n"); - $finish; - end + q = '{1, 2, 2, 4, 3}; + // `checkp(q, "'{1, 2, 2, 4, 3} "); + + i = q.sum with (item + 1); + `checkh(i, 32'h11); + i = q.product with (item + 1); + `checkh(i, 32'h168); + + q = '{32'b1100, 32'b1010, 32'b1100, 32'b1010, 32'b1010}; + i = q.and with (item + 1); + `checkh(i, 32'b1001); + i = q.or with (item + 1); + `checkh(i, 32'b1111); + i = q.xor with (item + 1); + `checkh(i, 32'hb); + + $write("*-* All Finished *-*\n"); + $finish; + end endmodule diff --git a/test_regress/t/t_array_method_unsup.out b/test_regress/t/t_array_method_unsup.out deleted file mode 100644 index 9a5d614e1..000000000 --- a/test_regress/t/t_array_method_unsup.out +++ /dev/null @@ -1,22 +0,0 @@ -%Error-UNSUPPORTED: t/t_array_method_unsup.v:23:17: Unsupported: 'with' on this method - : ... note: In instance 't' - 23 | i = q.sum with (item + 1); do if ((i) !== (32'h11)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_array_method_unsup.v",23, (i), (32'h11)); $stop; end while(0);; - | ^~~~ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error-UNSUPPORTED: t/t_array_method_unsup.v:24:21: Unsupported: 'with' on this method - : ... note: In instance 't' - 24 | i = q.product with (item + 1); do if ((i) !== (32'h168)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_array_method_unsup.v",24, (i), (32'h168)); $stop; end while(0);; - | ^~~~ -%Error-UNSUPPORTED: t/t_array_method_unsup.v:27:17: Unsupported: 'with' on this method - : ... note: In instance 't' - 27 | i = q.and with (item + 1); do if ((i) !== (32'b1001)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_array_method_unsup.v",27, (i), (32'b1001)); $stop; end while(0);; - | ^~~~ -%Error-UNSUPPORTED: t/t_array_method_unsup.v:28:16: Unsupported: 'with' on this method - : ... note: In instance 't' - 28 | i = q.or with (item + 1); do if ((i) !== (32'b1111)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_array_method_unsup.v",28, (i), (32'b1111)); $stop; end while(0);; - | ^~~~ -%Error-UNSUPPORTED: t/t_array_method_unsup.v:29:17: Unsupported: 'with' on this method - : ... note: In instance 't' - 29 | i = q.xor with (item + 1); do if ((i) !== (32'hb)) begin $write("%%Error: %s:%0d: got='h%x exp='h%x\n", "t/t_array_method_unsup.v",29, (i), (32'hb)); $stop; end while(0);; - | ^~~~ -%Error: Exiting due to diff --git a/test_regress/t/t_array_method_unsup.py b/test_regress/t/t_array_method_unsup.py deleted file mode 100755 index 966dc53da..000000000 --- a/test_regress/t/t_array_method_unsup.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python3 -# DESCRIPTION: Verilator: Verilog Test driver/expect definition -# -# Copyright 2024 by Wilson Snyder. This program is free software; you -# can redistribute it and/or modify it under the terms of either the GNU -# Lesser General Public License Version 3 or the Perl Artistic License -# Version 2.0. -# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 - -import vltest_bootstrap - -test.scenarios('simulator') - -test.compile(fails=test.vlt_all, expect_filename=test.golden_filename) - -if not test.vlt_all: - test.execute() - -test.passes() diff --git a/test_regress/t/t_array_method_unsup.v b/test_regress/t/t_array_method_unsup.v deleted file mode 100644 index 2308f56a0..000000000 --- a/test_regress/t/t_array_method_unsup.v +++ /dev/null @@ -1,34 +0,0 @@ -// DESCRIPTION: Verilator: Verilog Test module -// -// This file ONLY is placed under the Creative Commons Public Domain, for -// any use, without warranty, 2023 by Wilson Snyder. -// SPDX-License-Identifier: CC0-1.0 - -`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); -`define checkp(gotv,expv_s) do begin string gotv_s; gotv_s = $sformatf("%p", gotv); if ((gotv_s) != (expv_s)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv_s), (expv_s)); `stop; end end while(0); - -module t (/*AUTOARG*/); - initial begin - int q[5]; - int qv[$]; // Value returns - int qi[$]; // Index returns - int i; - - q = '{1, 2, 2, 4, 3}; - `checkp(q, "'{1, 2, 2, 4, 3} "); - - // Reduction methods - - i = q.sum with (item + 1); `checkh(i, 32'h11); - i = q.product with (item + 1); `checkh(i, 32'h168); - - q = '{32'b1100, 32'b1010, 32'b1100, 32'b1010, 32'b1010}; - i = q.and with (item + 1); `checkh(i, 32'b1001); - i = q.or with (item + 1); `checkh(i, 32'b1111); - i = q.xor with (item + 1); `checkh(i, 32'hb); - - $write("*-* All Finished *-*\n"); - $finish; - end -endmodule