From a05cbd83827c9beeda60992c9042dc0854a6fa6f Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Sun, 1 Feb 2026 13:44:20 -0500 Subject: [PATCH] Support structure initial values (#6130). Fixes #6130. --- Changes | 1 + src/V3EmitCFunc.cpp | 37 +++++++++++------ src/V3EmitCFunc.h | 2 +- src/V3Width.cpp | 9 ++-- test_regress/t/t_struct_unpacked_init.out | 6 --- test_regress/t/t_struct_unpacked_init.py | 8 ++-- test_regress/t/t_struct_unpacked_init.v | 41 +++++++++++++------ .../t/t_struct_unpacked_init_param.py | 18 ++++++++ test_regress/t/t_struct_unpacked_init_param.v | 38 +++++++++++++++++ 9 files changed, 120 insertions(+), 40 deletions(-) delete mode 100644 test_regress/t/t_struct_unpacked_init.out create mode 100755 test_regress/t/t_struct_unpacked_init_param.py create mode 100644 test_regress/t/t_struct_unpacked_init_param.v diff --git a/Changes b/Changes index 773afdf8c..6fc89c2f0 100644 --- a/Changes +++ b/Changes @@ -18,6 +18,7 @@ Verilator 5.045 devel * Add parsing of solve-before inside foreach (#6934). [Pawel Kojma, Antmicro Ltd.] * Add error when accessing a non-static class field from a static function (#6948). [Artur Bieniek, Antmicro Ltd.] * Add VerilatedContext::useNumaAssign and set on threads() call (#6954). [Yangyu Chen] +* Support structure initial values (#6130). * Support vpi_put/vpi_get forcing of signals (#5933) (#6704). [Christian Hecken] * Support detailed failure info for constraint violations (#6617) (#6883). [Yilou Wang] * Support `unique` constraints (on 1D static arrays) (#6810) (#6878). [Srinivasan Venkataramanan] diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index ce569230d..6ff146d0a 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -542,6 +542,8 @@ void EmitCFunc::emitVarReset(AstVar* varp, bool constructing) { // If an ARRAYINIT we initialize it using an initial block similar to a signal // puts("// parameter "+varp->nameProtect()+" = "+varp->valuep()->name()+"\n"); } else if (const AstInitArray* const initarp = VN_CAST(varp->valuep(), InitArray)) { + // TODO this code probably better handled as initp argument to emitVarResetRecurse + // TODO merge this functionality with V3EmitCConstInit.h visitors if (VN_IS(dtypep, AssocArrayDType)) { if (initarp->defaultp()) { emitSetVarConstant(varNameProtected + ".atDefault()", @@ -583,13 +585,14 @@ void EmitCFunc::emitVarReset(AstVar* varp, bool constructing) { varp->v3fatalSrc("InitArray under non-arrayed var"); } } else { - putns(varp, emitVarResetRecurse(varp, constructing, varNameProtected, dtypep, 0, "")); + putns(varp, emitVarResetRecurse(varp, constructing, varNameProtected, dtypep, 0, "", + varp->valuep())); } } string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, const string& varNameProtected, AstNodeDType* dtypep, - int depth, const string& suffix) { + int depth, const string& suffix, const AstNode* valuep) { dtypep = dtypep->skipRefp(); AstBasicDType* const basicp = dtypep->basicp(); // Returns string to do resetting, empty to do nothing (which caller should handle) @@ -599,14 +602,14 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray); + depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) { // Access std::array as C array const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray); + depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); } else if (VN_IS(dtypep, CDType)) { return ""; // Constructor does it } else if (VN_IS(dtypep, ClassRefDType)) { @@ -619,14 +622,14 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray); + depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); } else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) { // Access std::array as C array const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; return pre + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + ".atDefault()" + cvtarray); + depth + 1, suffix + ".atDefault()" + cvtarray, nullptr); } else if (VN_IS(dtypep, SampleQueueDType)) { return ""; } else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { @@ -637,17 +640,17 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, + cvtToStr(adtypep->elementsConst()) + "; ++" + ivar + ") {\n"); const string below = emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), - depth + 1, suffix + "[" + ivar + "]"); + depth + 1, suffix + "[" + ivar + "]", nullptr); const string post = "}\n"; return below.empty() ? "" : pre + below + post; } else if (VN_IS(dtypep, NodeUOrStructDType) && !VN_AS(dtypep, NodeUOrStructDType)->packed()) { - const auto* const sdtypep = VN_AS(dtypep, NodeUOrStructDType); + const AstNodeUOrStructDType* const sdtypep = VN_AS(dtypep, NodeUOrStructDType); string literal; for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; itemp = VN_AS(itemp->nextp(), MemberDType)) { const std::string line = emitVarResetRecurse( varp, constructing, varNameProtected + suffix + "." + itemp->nameProtect(), - itemp->dtypep(), depth + 1, ""); + itemp->dtypep(), depth + 1, "", itemp->valuep()); if (!line.empty()) literal += line; } return literal; @@ -682,10 +685,10 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, splitSizeInc(1); if (dtypep->isWide()) { // Handle unpacked; not basicp->isWide string out; - if (varp->valuep()) { - const AstConst* const constp = VN_AS(varp->valuep(), Const); + if (valuep) { + const AstConst* const constp = VN_AS(valuep, Const); UASSERT_OBJ(constp, varp, "non-const initializer for variable"); - for (int w = 0; w < varp->widthWords(); ++w) { + for (int w = 0; w < dtypep->widthWords(); ++w) { out += varNameProtected + suffix + "[" + cvtToStr(w) + "] = "; out += cvtToStr(constp->num().edataWord(w)) + "U;\n"; } @@ -709,7 +712,15 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing, return out; } else { string out = varNameProtected + suffix; - if (zeroit) { + if (valuep) { + out += " = "; + // TODO cleanup code shared between here, V3EmitCConstInit.h, + // EmitCFunc::emitVarReset, EmitCFunc::emitConstant + const AstConst* const constp = VN_AS(valuep, Const); + UASSERT_OBJ(constp, varp, "non-const initializer for variable"); + out += cvtToStr(constp->num().edataWord(0)) + "U;\n"; + out += ";\n"; + } else if (zeroit) { out += " = 0;\n"; } else { emitVarResetScopeHash(); diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 12844a0d0..460c6ad69 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -215,7 +215,7 @@ public: void emitVarReset(AstVar* varp, bool constructing); string emitVarResetRecurse(const AstVar* varp, bool constructing, const string& varNameProtected, AstNodeDType* dtypep, int depth, - const string& suffix); + const string& suffix, const AstNode* valuep); void emitVarResetScopeHash(); void emitChangeDet(); void emitConstInit(AstNode* initp) { iterateConst(initp); } diff --git a/src/V3Width.cpp b/src/V3Width.cpp index 499f17ec7..02e7836e9 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -3270,10 +3270,6 @@ class WidthVisitor final : public VNVisitor { itemp->v3error("Initial values not allowed in packed struct/union" " (IEEE 1800-2023 7.2.2)"); pushDeletep(itemp->valuep()->unlinkFrBack()); - } else if (itemp->valuep()) { - itemp->valuep()->v3warn(E_UNSUPPORTED, - "Unsupported: Initial values in struct/union members"); - pushDeletep(itemp->valuep()->unlinkFrBack()); } } const bool isHardPackedUnion @@ -5146,6 +5142,11 @@ class WidthVisitor final : public VNVisitor { // default_value for any unmatched member yet return defaultp->cloneTree(false); } + if (memp->valuep()) { + return new AstPatMember{nodep->fileline(), + VN_AS(memp->valuep()->cloneTree(false), NodeExpr), + new AstText{nodep->fileline(), memp->name()}, nullptr}; + } if (!VN_IS(memp_vdtypep, UnionDType)) { nodep->v3error("Assignment pattern missed initializing elements: " << memp->virtRefDTypep()->prettyDTypeNameQ() << " " diff --git a/test_regress/t/t_struct_unpacked_init.out b/test_regress/t/t_struct_unpacked_init.out deleted file mode 100644 index 5e6a8ecfc..000000000 --- a/test_regress/t/t_struct_unpacked_init.out +++ /dev/null @@ -1,6 +0,0 @@ -%Error-UNSUPPORTED: t/t_struct_unpacked_init.v:12:24: Unsupported: Initial values in struct/union members - : ... note: In instance 't' - 12 | bit [3:0] m_lo = P; - | ^ - ... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest -%Error: Exiting due to diff --git a/test_regress/t/t_struct_unpacked_init.py b/test_regress/t/t_struct_unpacked_init.py index c7d9b21a5..8a938befd 100755 --- a/test_regress/t/t_struct_unpacked_init.py +++ b/test_regress/t/t_struct_unpacked_init.py @@ -4,13 +4,15 @@ # 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('linter') +test.scenarios('simulator') -test.lint(fails=True, expect_filename=test.golden_filename) +test.compile() + +test.execute() test.passes() diff --git a/test_regress/t/t_struct_unpacked_init.v b/test_regress/t/t_struct_unpacked_init.v index bb796af3e..a9a18e61b 100644 --- a/test_regress/t/t_struct_unpacked_init.v +++ b/test_regress/t/t_struct_unpacked_init.v @@ -1,25 +1,40 @@ // DESCRIPTION: Verilator: Verilog Test module // // This file ONLY is placed under the Creative Commons Public Domain. -// SPDX-FileCopyrightText: 2023 Wilson Snyder +// 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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + module t; - parameter P = 4'h5; + parameter P = 4'h5; - struct { // Can't legally be packed - bit [3:0] m_lo = P; - bit [3:0] m_hi; - } s; + typedef struct { // Must be unpacked -- existing error check + // Update ctor_var_reset to check instead of making a constructor + bit [3:0] m_lo = P; + bit [93:0] m_mid = '1; // Wide + bit [3:0] m_hi; + } s_t; + s_t s; - initial begin - s.m_hi = 4'ha; - if (s.m_lo != 4'h5) $stop; - if (s.m_hi != 4'ha) $stop; + initial begin + $display("%p", s); + `checkh(s.m_lo, 4'h5); + `checkh(s.m_mid, ~94'h0); + `checkh(s.m_hi, 4'h0); + s.m_mid = 94'ha; + s.m_hi = 4'hb; + $display("%p", s); + `checkh(s.m_lo, 4'h5); + `checkh(s.m_mid, 94'ha); + `checkh(s.m_hi, 4'hb); - $write("*-* All Finished *-*\n"); - $finish; - end + $write("*-* All Finished *-*\n"); + $finish; + end endmodule diff --git a/test_regress/t/t_struct_unpacked_init_param.py b/test_regress/t/t_struct_unpacked_init_param.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_struct_unpacked_init_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() + +test.execute() + +test.passes() diff --git a/test_regress/t/t_struct_unpacked_init_param.v b/test_regress/t/t_struct_unpacked_init_param.v new file mode 100644 index 000000000..c194836e7 --- /dev/null +++ b/test_regress/t/t_struct_unpacked_init_param.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='h%x exp='h%x\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0); +// verilog_format: on + +module t; + + parameter P = 4'h5; + + typedef struct { // Must be unpacked -- existing error check + bit [3:0] m_lo = P; + bit [93:0] m_mid = '1; // Wide + bit [3:0] m_hi; + } s_t; + + localparam s_t S = '{m_hi: 6}; // Not setting m_mid/m_hi + + localparam S_LO = S.m_lo; + localparam S_HI = S.m_hi; + + initial begin + `checkh(S.m_hi, 4'h6); + `checkh(S_HI, 4'h6); + + `checkh(S.m_lo, 4'h5); + `checkh(S_LO, 4'h5); + + $write("*-* All Finished *-*\n"); + $finish; + end + +endmodule