From 218659f4e81768b7bd303295a967efb311b5f7fd Mon Sep 17 00:00:00 2001 From: Michael Bedford Taylor Date: Wed, 6 Aug 2025 14:29:40 -0700 Subject: [PATCH] Support parameter resolution of 1D unpacked array slices (#6257) (#6268) --- docs/CONTRIBUTORS | 1 + src/V3Const.cpp | 12 ++++ src/V3Param.cpp | 8 +++ src/V3Simulate.h | 34 ++++++++++++ test_regress/t/t_param_slice.py | 17 ++++++ test_regress/t/t_param_slice.v | 98 +++++++++++++++++++++++++++++++++ 6 files changed, 170 insertions(+) create mode 100644 test_regress/t/t_param_slice.py create mode 100644 test_regress/t/t_param_slice.v diff --git a/docs/CONTRIBUTORS b/docs/CONTRIBUTORS index 9b75ecf02..e30c878d2 100644 --- a/docs/CONTRIBUTORS +++ b/docs/CONTRIBUTORS @@ -161,6 +161,7 @@ Martin Stadler Mateusz Gancarz Matthew Ballance Max Wipfli +Michael Bedford Taylor Michael Bikovitsky Michael Killough Michal Czyz diff --git a/src/V3Const.cpp b/src/V3Const.cpp index 3c5e568c1..179f856b4 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -2787,6 +2787,18 @@ class ConstVisitor final : public VNVisitor { } m_selp = nullptr; } + + // Evaluate a slice of an unpacked array. If constantification is + // required (m_required=true), call replaceWithSimulation() to compute + // the slice via simulation. Otherwise just iterate the children. + void visit(AstSliceSel* nodep) override { + // First constify or width any child nodes + iterateChildren(nodep); + if (!m_required) return; // Do nothing unless we are in parameter mode + // Fallback to simulation: this will invoke SimulateVisitor::visit(AstSliceSel*) + replaceWithSimulation(nodep); + } + void visit(AstCAwait* nodep) override { m_hasJumpDelay = true; iterateChildren(nodep); diff --git a/src/V3Param.cpp b/src/V3Param.cpp index 7bf2a98b3..a76fb208d 100644 --- a/src/V3Param.cpp +++ b/src/V3Param.cpp @@ -732,6 +732,14 @@ class ParamProcessor final { AstNode* const exprp = pinp->exprp(); longnamer += "_" + paramSmallName(srcModp, modvarp) + paramValueNumber(exprp); any_overridesr = true; + } else if (VN_IS(pinp->exprp(), InitArray)) { + // Array assigned to scalar parameter. Treat the InitArray as a constant + // integer array and include it in the module name. Constantify nested + // expressions before mangling the value number. + V3Const::constifyParamsEdit(pinp->exprp()); + longnamer += "_" + paramSmallName(srcModp, modvarp) + + paramValueNumber(pinp->exprp()); + any_overridesr = true; } else { V3Const::constifyParamsEdit(pinp->exprp()); // String constants are parsed as logic arrays and converted to strings in V3Const. diff --git a/src/V3Simulate.h b/src/V3Simulate.h index 190b3580f..236dd442a 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -894,6 +894,40 @@ private: clearOptimizable(nodep, "Array select of non-array"); } } + + // Evaluate a slice of an unpacked array. If the base value is a constant + // AstInitArray, build a new AstInitArray representing the slice and assign + // it as this node's value. New index 0 corresponds to the lowest index of + // the slice. Otherwise, mark this node as unoptimizable. + void visit(AstSliceSel* nodep) override { + checkNodeInfo(nodep); + iterateChildrenConst(nodep); + if (m_checkOnly || !optimizable()) return; + // Fetch the base constant array + if (AstInitArray* const initp = VN_CAST(fetchValueNull(nodep->fromp()), InitArray)) { + const VNumRange& sliceRange = nodep->declRange(); + const uint32_t sliceElements = sliceRange.elements(); + const int sliceLo = sliceRange.lo(); + // Use this node's dtype for the slice array + AstNodeDType* const dtypep = nodep->dtypep()->skipRefp(); + // Clone the default value from the base array, if present + AstNodeExpr* defaultp = nullptr; + if (initp->defaultp()) defaultp = initp->defaultp()->cloneTree(false); + AstInitArray* const newInitp = new AstInitArray{nodep->fileline(), dtypep, defaultp}; + // Copy slice elements in ascending order + for (uint32_t idx = 0; idx < sliceElements; ++idx) { + const uint32_t baseIdx = sliceLo + idx; + AstNodeExpr* const itemp = initp->getIndexDefaultedValuep(baseIdx); + if (itemp) newInitp->addIndexValuep(idx, itemp->cloneTree(false)); + } + // Assign the new constant array and track it for later deletion + setValue(nodep, newInitp); + m_reclaimValuesp.push_back(newInitp); + } else { + clearOptimizable(nodep, "Slice select of non-array"); + } + } + void visit(AstBegin* nodep) override { checkNodeInfo(nodep); iterateChildrenConst(nodep); diff --git a/test_regress/t/t_param_slice.py b/test_regress/t/t_param_slice.py new file mode 100644 index 000000000..125f07bb2 --- /dev/null +++ b/test_regress/t/t_param_slice.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Test constant parameter slicing of unpacked arrays (issue #6257) +# +# Copyright 2025 by Wilson Snyder. This program is free software; you +# can redistribute it and/or modify it under the terms of either the GNU +# Lesser General Public License Version 3 or the Perl Artistic License +# Version 2.0. +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 +import vltest_bootstrap + +test.scenarios('vlt') + +test.compile(verilator_flags2=['--exe','--main','--timing']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_param_slice.v b/test_regress/t/t_param_slice.v new file mode 100644 index 000000000..96adbdf0b --- /dev/null +++ b/test_regress/t/t_param_slice.v @@ -0,0 +1,98 @@ +// DESCRIPTION: Verilator: Verilog Test module +// +// This file ONLY is placed into the Public Domain, for any use, +// without warranty, 2025 by Wilson Snyder. +// SPDX-License-Identifier: CC0-1.0 + +`timescale 1ns/1ps +// Test constant parameter slicing of unpacked arrays with various slice ranges. + +module issue_desc #( + parameter int els_p = 1, + parameter int val_p [els_p+1:2], + parameter int orig_els = 1 +) (); + // Drop the lowest index (2) in each recursion: slice [high:3] + if (els_p > 1) begin : r + issue_desc #( + .els_p(els_p-1), + .val_p(val_p[els_p+1:3]), + .orig_els(orig_els) + ) x(); + end + initial begin + int expected = orig_els - els_p + 1; + if (val_p[2] !== expected) begin + $error("DESC wrong value %0d expected %0d in %m", val_p[2], expected); + $finish; + end + $display("%08x (desc %m)", val_p[2]); + end +endmodule + +module issue_rev #( + parameter int els_p = 1, + parameter int val_p [2:els_p+1], + parameter int orig_els = 1 +) (); + // Drop the lowest index (2) in each recursion: slice [3:high] + if (els_p > 1) begin : r + issue_rev #( + .els_p(els_p-1), + .val_p(val_p[3:els_p+1]), + .orig_els(orig_els) + ) x(); + end + initial begin + int expected = orig_els - els_p + 1; + if (val_p[2] !== expected) begin + $error("REV wrong value %0d expected %0d in %m", val_p[2], expected); + $finish; + end + $display("%08x (rev %m)", val_p[2]); + end +endmodule + +module issue_def #( + parameter int els_p = 1, + // Internal default fill is zero; the test overrides this with DEADBEEF. + parameter int val_p [els_p+1:2] = '{default:0}, + parameter int orig_els = 1 +) (); + // Recursively slice off the lowest index (2) + if (els_p > 1) begin : r + issue_def #( + .els_p(els_p-1), + .val_p(val_p[els_p+1:3]), + .orig_els(orig_els) + ) x(); + end + initial begin + // Expect 32'hDEADBEEF when overridden by the top-level test. + if (val_p[2] !== 32'hDEADBEEF) begin + $error("DEF wrong value %0x expected DEADBEEF in %m", val_p[2]); + $finish; + end + $display("%08x (def %m)", val_p[2]); + end +endmodule + +module t; + // For els_p=5, the range [els_p+1:2] is [6:2]. + // Descending initializer: index 6=5,5=4,4=3,3=2,2=1. + parameter int val_desc [6:2] = '{5,4,3,2,1}; + // Reverse slice initializer: ascending values on [2:6]. + parameter int val_rev [2:6] = '{1,2,3,4,5}; + // Override for default-array test: all elements set to 32'hDEADBEEF on [6:2]. + parameter int val_def [6:2] = '{default: 32'hDEADBEEF}; + + issue_desc #(.els_p(5), .val_p(val_desc), .orig_els(5)) iss_desc(); + issue_rev #(.els_p(5), .val_p(val_rev), .orig_els(5)) iss_rev(); + issue_def #(.els_p(5), .val_p(val_def), .orig_els(5)) iss_def(); + + initial begin + #1; + $write("*-* All Finished *-*\\n"); + $finish; + end +endmodule