From d4613ed1a4d75ab2717b22c718653b39752ff20c Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Fri, 10 Apr 2026 14:40:58 +0930 Subject: [PATCH 1/5] Tests: Add test for array pattern concatenation --- test_regress/t/t_array_pattern_concat.py | 18 +++ test_regress/t/t_array_pattern_concat.v | 159 +++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100755 test_regress/t/t_array_pattern_concat.py create mode 100644 test_regress/t/t_array_pattern_concat.v diff --git a/test_regress/t/t_array_pattern_concat.py b/test_regress/t/t_array_pattern_concat.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_array_pattern_concat.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_array_pattern_concat.v b/test_regress/t/t_array_pattern_concat.v new file mode 100644 index 000000000..40711dbcf --- /dev/null +++ b/test_regress/t/t_array_pattern_concat.v @@ -0,0 +1,159 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed under the Creative Commons Public Domain. +// SPDX-FileCopyrightText: 2026 Greg Davill +// 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); + +package arr_pkg; + localparam logic [31:0] PKG_ADDRS[3] = '{32'hAA001000, 32'hAA002000, 32'hAA003000}; +endpackage + +module t ( /*AUTOARG*/); + + // Test: array concatenation in pattern initialization + // An array localparam used as a value in another array's pattern + // should have its elements "flattened" into the target array. + + localparam logic [31:0] BASE_ADDRS[3] = '{32'h80001000, 32'h80002000, 32'h80003000}; + + // Sub-array slice at the start + localparam logic [31:0] ALL_ADDRS[4] = '{BASE_ADDRS[0:1], 32'h80003000, 32'h80004000}; + + // Sub-array slice in the middle + localparam logic [31:0] MID[5] = '{32'hFF, BASE_ADDRS[0:1], 32'hAA, 32'hBB}; + + // Multiple full sub-arrays + localparam logic [31:0] EXTRA[2] = '{32'hC0, 32'hD0}; + localparam logic [31:0] MULTI[5] = '{BASE_ADDRS, EXTRA}; + + // Sub-array with default (sparse InitArray) + localparam logic [31:0] DFLT[3] = '{default: 32'hDD}; + localparam logic [31:0] WITH_DFLT[4] = '{DFLT, 32'hEE}; + + // Slice at the end + localparam logic [31:0] TAIL[4] = '{32'hAA, 32'hBB, BASE_ADDRS[1:2]}; + + // Multiple slices combined + localparam logic [31:0] MULTI_SLICE[4] = '{BASE_ADDRS[0:1], BASE_ADDRS[1:2]}; + + // Single-element slice + localparam logic [31:0] SINGLE[3] = '{BASE_ADDRS[0:0], 32'hAA, 32'hBB}; + + // Descending-range source array + localparam logic [31:0] DESC[2:0] = '{32'hD0, 32'hD1, 32'hD2}; + localparam logic [31:0] WITH_DESC[4] = '{DESC[1:0], 32'hAA, 32'hBB}; + + // Slice bounds from parameter expressions + localparam int unsigned N = 2; + localparam logic [31:0] PARAM_SLICE[4] = '{BASE_ADDRS[0:N-1], 32'hAA, 32'hBB}; + + // Multiple param-bounded slices composing a larger array + localparam logic [31:0] SRC_A[4] = '{32'hA0, 32'hA1, 32'hA2, 32'hA3}; + localparam logic [31:0] SRC_B[4] = '{32'hB0, 32'hB1, 32'hB2, 32'hB3}; + localparam int unsigned NA = 2; + localparam int unsigned NB = 3; + localparam int unsigned TOTAL = NA + NB; + localparam logic [31:0] COMPOSED[TOTAL] = '{SRC_A[0:NA-1], SRC_B[0:NB-1]}; + + // Test key'd associative array initialisations + localparam logic [31:0] KEY_ARR_A[4] = '{0: BASE_ADDRS[0:1], 2: 32'hF2, 3: 32'hF3}; + localparam logic [31:0] KEY_ARR_B[4] = '{2: ALL_ADDRS[1:2], default: 32'h00}; + + // Keyed pattern where values are indexed from another array param — + // the key determines position, not the source array's element count. + localparam logic [31:0] KEYED_FROM_ARR[3] = '{ + 0: BASE_ADDRS[2], 1: BASE_ADDRS[0], 2: BASE_ADDRS[1] + }; + + // Package-scoped array as a positional pattern member + localparam logic [31:0] WITH_PKG[4] = '{arr_pkg::PKG_ADDRS, 32'hFF}; + + // Package-scoped array slice + localparam logic [31:0] PKG_SLICE[4] = '{arr_pkg::PKG_ADDRS[0:1], 32'hAA, 32'hBB}; + + initial begin + `checkh(ALL_ADDRS[0], 32'h80001000); + `checkh(ALL_ADDRS[1], 32'h80002000); + `checkh(ALL_ADDRS[2], 32'h80003000); + `checkh(ALL_ADDRS[3], 32'h80004000); + + `checkh(MID[0], 32'hFF); + `checkh(MID[1], 32'h80001000); + `checkh(MID[2], 32'h80002000); + `checkh(MID[3], 32'hAA); + `checkh(MID[4], 32'hBB); + + `checkh(MULTI[0], 32'h80001000); + `checkh(MULTI[1], 32'h80002000); + `checkh(MULTI[2], 32'h80003000); + `checkh(MULTI[3], 32'hC0); + `checkh(MULTI[4], 32'hD0); + + `checkh(WITH_DFLT[0], 32'hDD); + `checkh(WITH_DFLT[1], 32'hDD); + `checkh(WITH_DFLT[2], 32'hDD); + `checkh(WITH_DFLT[3], 32'hEE); + + `checkh(TAIL[0], 32'hAA); + `checkh(TAIL[1], 32'hBB); + `checkh(TAIL[2], 32'h80002000); + `checkh(TAIL[3], 32'h80003000); + + `checkh(MULTI_SLICE[0], 32'h80001000); + `checkh(MULTI_SLICE[1], 32'h80002000); + `checkh(MULTI_SLICE[2], 32'h80002000); + `checkh(MULTI_SLICE[3], 32'h80003000); + + `checkh(SINGLE[0], 32'h80001000); + `checkh(SINGLE[1], 32'hAA); + `checkh(SINGLE[2], 32'hBB); + + `checkh(WITH_DESC[0], 32'hD2); + `checkh(WITH_DESC[1], 32'hD1); + `checkh(WITH_DESC[2], 32'hAA); + `checkh(WITH_DESC[3], 32'hBB); + + `checkh(PARAM_SLICE[0], 32'h80001000); + `checkh(PARAM_SLICE[1], 32'h80002000); + `checkh(PARAM_SLICE[2], 32'hAA); + `checkh(PARAM_SLICE[3], 32'hBB); + + `checkh(COMPOSED[0], 32'hA0); + `checkh(COMPOSED[1], 32'hA1); + `checkh(COMPOSED[2], 32'hB0); + `checkh(COMPOSED[3], 32'hB1); + `checkh(COMPOSED[4], 32'hB2); + + `checkh(KEY_ARR_A[0], 32'h80001000); + `checkh(KEY_ARR_A[1], 32'h80002000); + `checkh(KEY_ARR_A[2], 32'hF2); + `checkh(KEY_ARR_A[3], 32'hF3); + + `checkh(KEY_ARR_B[0], 32'h00); + `checkh(KEY_ARR_B[1], 32'h00); + `checkh(KEY_ARR_B[2], 32'h80002000); + `checkh(KEY_ARR_B[3], 32'h80003000); + + `checkh(KEYED_FROM_ARR[0], 32'h80003000); + `checkh(KEYED_FROM_ARR[1], 32'h80001000); + `checkh(KEYED_FROM_ARR[2], 32'h80002000); + + `checkh(WITH_PKG[0], 32'hAA001000); + `checkh(WITH_PKG[1], 32'hAA002000); + `checkh(WITH_PKG[2], 32'hAA003000); + `checkh(WITH_PKG[3], 32'hFF); + + `checkh(PKG_SLICE[0], 32'hAA001000); + `checkh(PKG_SLICE[1], 32'hAA002000); + `checkh(PKG_SLICE[2], 32'hAA); + `checkh(PKG_SLICE[3], 32'hBB); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule From fee71d420c53bc29c8edb94fdb80f09363782885 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Fri, 10 Apr 2026 14:41:08 +0930 Subject: [PATCH 2/5] Fix array and slice flattening in assignment pattern initialisation --- src/V3Width.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/src/V3Width.cpp b/src/V3Width.cpp index b507ff84d..181a08284 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5492,8 +5492,6 @@ class WidthVisitor final : public VNVisitor { } if (patp) { - // Don't want the RHS an array - allConstant &= VN_IS(patp->lhssp(), Const); patp->dtypep(arrayDtp->subDTypep()); AstNodeExpr* const valuep = patternMemberValueIterate(patp); if (VN_IS(arrayDtp, UnpackArrayDType)) { @@ -5502,7 +5500,50 @@ class WidthVisitor final : public VNVisitor { = new AstInitArray{nodep->fileline(), arrayDtp, nullptr}; newp = newap; } - VN_AS(newp, InitArray)->addIndexValuep(ent - range.lo(), valuep); + // If valuep is a reference to an array constant (or a + // slice of one), flatten its elements into the target + // array. Note: at this point width resolution has run, + // so slices appear as AstSliceSel (the pre-resolution + // AstSelExtract is handled in patVectorMap). + const AstInitArray* subInitp = nullptr; + int flattenLo = 0; + int flattenElements = 0; + if (const auto* vrp = VN_CAST(valuep, VarRef)) { + subInitp = VN_CAST(vrp->varp()->valuep(), InitArray); + if (subInitp) { + if (const auto* adtp + = VN_CAST(vrp->varp()->dtypep()->skipRefp(), NodeArrayDType)) { + flattenElements = adtp->declRange().elements(); + } + } + } else if (const auto* slicep = VN_CAST(valuep, SliceSel)) { + if (const auto* vrp = VN_CAST(slicep->fromp(), VarRef)) { + subInitp = VN_CAST(vrp->varp()->valuep(), InitArray); + if (subInitp) { + flattenLo = slicep->declRange().lo(); + flattenElements = slicep->declRange().elements(); + } + } + } + if (subInitp && flattenElements > 0) { + // Sub-array values are always constant + VL_DO_DANGLING(pushDeletep(valuep), valuep); + for (int sn = 0; sn < flattenElements; ++sn) { + UASSERT_OBJ(entn < range.elements(), nodep, + "Flattened sub-array overflows target array"); + VN_AS(newp, InitArray) + ->addIndexValuep(ent - range.lo(), + subInitp->getIndexDefaultedValuep(flattenLo + sn) + ->cloneTree(false)); + if (sn < flattenElements - 1) { + ++entn; + ent += range.leftToRightInc(); + } + } + } else { + allConstant &= VN_IS(valuep, Const); + VN_AS(newp, InitArray)->addIndexValuep(ent - range.lo(), valuep); + } } else { // Packed. Convert to concat for now. if (!newp) { newp = valuep; @@ -9502,6 +9543,36 @@ class WidthVisitor final : public VNVisitor { return testp; } + // Return how many target-array positions a single pattern member + // occupies. For scalar values this is 1; for a full array reference + // or an array slice it is the element count of that array/slice. + // Called before width resolution, so slices are still AstSelExtract + // and VarRef dtypes may not be set yet — use varp()->dtypep() instead. + static int patMemberArrayElements(AstPatMember* patp) { + AstNodeExpr* const exprp = patp->lhssp(); + // For slice expressions (pre-width: SelExtract), compute from bounds + if (auto* selp = VN_CAST(exprp, SelExtract)) { + if (const auto* vrp = VN_CAST(selp->fromp(), VarRef)) { + if (!VN_CAST(vrp->varp()->valuep(), InitArray)) return 1; + // Bounds may be parameter expressions; constify before reading + V3Const::constifyParamsNoWarnEdit(selp->leftp()); + V3Const::constifyParamsNoWarnEdit(selp->rightp()); + const auto* msbp = VN_CAST(selp->leftp(), Const); + const auto* lsbp = VN_CAST(selp->rightp(), Const); + if (msbp && lsbp) return std::abs(msbp->toSInt() - lsbp->toSInt()) + 1; + } + return 1; + } + // Full array reference: check via the variable's dtype (not the + // expression's dtype, which may be unset before width resolution). + if (const auto* vrp = VN_CAST(exprp, NodeVarRef)) { + if (!VN_CAST(vrp->varp()->valuep(), InitArray)) return 1; + if (const auto* adtp = VN_CAST(vrp->varp()->dtypep()->skipRefp(), NodeArrayDType)) + return adtp->declRange().elements(); + } + return 1; + } + PatVecMap patVectorMap(AstPattern* nodep, const VNumRange& range) { PatVecMap patmap; int element = range.left(); @@ -9523,7 +9594,13 @@ class WidthVisitor final : public VNVisitor { if (!newEntry) { patp->v3error("Assignment pattern key used multiple times: " << element); } - element += range.leftToRightInc(); + // For positional members that reference an array (or a slice + // of one), advance by that array/slice's element count so + // subsequent members are mapped correctly. Note: this runs + // before width resolution, so slices are still AstSelExtract + // (the post-resolution AstSliceSel is handled in patternArray). + const int elementAdvance = patMemberArrayElements(patp); + element += range.leftToRightInc() * elementAdvance; } return patmap; } From e9f42fdfbd22a5a671ff2e1b23f83adac5be0c5e Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 11 Apr 2026 14:48:03 +0930 Subject: [PATCH 3/5] Resolve SelExtract and determine element width from dtype --- src/V3Width.cpp | 63 +++++++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 39 deletions(-) diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 181a08284..68ca70720 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -5502,13 +5502,13 @@ class WidthVisitor final : public VNVisitor { } // If valuep is a reference to an array constant (or a // slice of one), flatten its elements into the target - // array. Note: at this point width resolution has run, - // so slices appear as AstSliceSel (the pre-resolution - // AstSelExtract is handled in patVectorMap). + // array. Width resolution has already run (including + // early resolution in patVectorMap), so slices appear + // as AstSliceSel. const AstInitArray* subInitp = nullptr; int flattenLo = 0; int flattenElements = 0; - if (const auto* vrp = VN_CAST(valuep, VarRef)) { + if (const auto* vrp = VN_CAST(valuep, NodeVarRef)) { subInitp = VN_CAST(vrp->varp()->valuep(), InitArray); if (subInitp) { if (const auto* adtp @@ -5517,7 +5517,7 @@ class WidthVisitor final : public VNVisitor { } } } else if (const auto* slicep = VN_CAST(valuep, SliceSel)) { - if (const auto* vrp = VN_CAST(slicep->fromp(), VarRef)) { + if (const auto* vrp = VN_CAST(slicep->fromp(), NodeVarRef)) { subInitp = VN_CAST(vrp->varp()->valuep(), InitArray); if (subInitp) { flattenLo = slicep->declRange().lo(); @@ -9543,36 +9543,6 @@ class WidthVisitor final : public VNVisitor { return testp; } - // Return how many target-array positions a single pattern member - // occupies. For scalar values this is 1; for a full array reference - // or an array slice it is the element count of that array/slice. - // Called before width resolution, so slices are still AstSelExtract - // and VarRef dtypes may not be set yet — use varp()->dtypep() instead. - static int patMemberArrayElements(AstPatMember* patp) { - AstNodeExpr* const exprp = patp->lhssp(); - // For slice expressions (pre-width: SelExtract), compute from bounds - if (auto* selp = VN_CAST(exprp, SelExtract)) { - if (const auto* vrp = VN_CAST(selp->fromp(), VarRef)) { - if (!VN_CAST(vrp->varp()->valuep(), InitArray)) return 1; - // Bounds may be parameter expressions; constify before reading - V3Const::constifyParamsNoWarnEdit(selp->leftp()); - V3Const::constifyParamsNoWarnEdit(selp->rightp()); - const auto* msbp = VN_CAST(selp->leftp(), Const); - const auto* lsbp = VN_CAST(selp->rightp(), Const); - if (msbp && lsbp) return std::abs(msbp->toSInt() - lsbp->toSInt()) + 1; - } - return 1; - } - // Full array reference: check via the variable's dtype (not the - // expression's dtype, which may be unset before width resolution). - if (const auto* vrp = VN_CAST(exprp, NodeVarRef)) { - if (!VN_CAST(vrp->varp()->valuep(), InitArray)) return 1; - if (const auto* adtp = VN_CAST(vrp->varp()->dtypep()->skipRefp(), NodeArrayDType)) - return adtp->declRange().elements(); - } - return 1; - } - PatVecMap patVectorMap(AstPattern* nodep, const VNumRange& range) { PatVecMap patmap; int element = range.left(); @@ -9596,10 +9566,25 @@ class WidthVisitor final : public VNVisitor { } // For positional members that reference an array (or a slice // of one), advance by that array/slice's element count so - // subsequent members are mapped correctly. Note: this runs - // before width resolution, so slices are still AstSelExtract - // (the post-resolution AstSliceSel is handled in patternArray). - const int elementAdvance = patMemberArrayElements(patp); + // subsequent members are mapped correctly. Width-resolve the + // value expression so its dtype is set + int elementAdvance = 1; + if (!patp->keyp()) { + userIterateAndNext(patp->lhssp(), WidthVP{CONTEXT_DET, PRELIM}.p()); + AstNodeExpr* const exprp = patp->lhssp(); + if (const AstNodeDType* const dtypep = exprp->dtypep()) { + if (const auto* adtp = VN_CAST(dtypep->skipRefp(), UnpackArrayDType)) { + // Only flatten constant arrays backed by InitArray + const AstNodeVarRef* vrp = VN_CAST(exprp, NodeVarRef); + if (!vrp) { + if (const auto* slicep = VN_CAST(exprp, SliceSel)) + vrp = VN_CAST(slicep->fromp(), NodeVarRef); + } + if (vrp && VN_IS(vrp->varp()->valuep(), InitArray)) + elementAdvance = adtp->declRange().elements(); + } + } + } element += range.leftToRightInc() * elementAdvance; } return patmap; From 419f3192c57782245ea46e20ceb3cc5167b4e5e3 Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 11 Apr 2026 15:32:27 +0930 Subject: [PATCH 4/5] Avoid greedy calls to userIterateAndNext --- src/V3Width.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 68ca70720..0980a59c2 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -9569,7 +9569,8 @@ class WidthVisitor final : public VNVisitor { // subsequent members are mapped correctly. Width-resolve the // value expression so its dtype is set int elementAdvance = 1; - if (!patp->keyp()) { + if (!patp->keyp() + && (VN_IS(patp->lhssp(), NodeVarRef) || VN_IS(patp->lhssp(), SelExtract))) { userIterateAndNext(patp->lhssp(), WidthVP{CONTEXT_DET, PRELIM}.p()); AstNodeExpr* const exprp = patp->lhssp(); if (const AstNodeDType* const dtypep = exprp->dtypep()) { From c20a6f4396ca7445657f876ed8c034bd1e0373bc Mon Sep 17 00:00:00 2001 From: Greg Davill Date: Sat, 11 Apr 2026 16:03:13 +0930 Subject: [PATCH 5/5] Fix whitespace error --- test_regress/t/t_array_pattern_concat.v | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_regress/t/t_array_pattern_concat.v b/test_regress/t/t_array_pattern_concat.v index 40711dbcf..2c7f4900f 100644 --- a/test_regress/t/t_array_pattern_concat.v +++ b/test_regress/t/t_array_pattern_concat.v @@ -63,7 +63,7 @@ module t ( /*AUTOARG*/); localparam logic [31:0] KEY_ARR_A[4] = '{0: BASE_ADDRS[0:1], 2: 32'hF2, 3: 32'hF3}; localparam logic [31:0] KEY_ARR_B[4] = '{2: ALL_ADDRS[1:2], default: 32'h00}; - // Keyed pattern where values are indexed from another array param — + // Keyed pattern where values are indexed from another array param // the key determines position, not the source array's element count. localparam logic [31:0] KEYED_FROM_ARR[3] = '{ 0: BASE_ADDRS[2], 1: BASE_ADDRS[0], 2: BASE_ADDRS[1]