Support structure initial values (#6130).

Fixes #6130.
This commit is contained in:
Wilson Snyder 2026-02-01 13:44:20 -05:00
parent d3f608058f
commit a05cbd8382
9 changed files with 120 additions and 40 deletions

View File

@ -18,6 +18,7 @@ Verilator 5.045 devel
* Add parsing of solve-before inside foreach (#6934). [Pawel Kojma, Antmicro Ltd.] * 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 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] * 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 vpi_put/vpi_get forcing of signals (#5933) (#6704). [Christian Hecken]
* Support detailed failure info for constraint violations (#6617) (#6883). [Yilou Wang] * Support detailed failure info for constraint violations (#6617) (#6883). [Yilou Wang]
* Support `unique` constraints (on 1D static arrays) (#6810) (#6878). [Srinivasan Venkataramanan] * Support `unique` constraints (on 1D static arrays) (#6810) (#6878). [Srinivasan Venkataramanan]

View File

@ -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 // If an ARRAYINIT we initialize it using an initial block similar to a signal
// puts("// parameter "+varp->nameProtect()+" = "+varp->valuep()->name()+"\n"); // puts("// parameter "+varp->nameProtect()+" = "+varp->valuep()->name()+"\n");
} else if (const AstInitArray* const initarp = VN_CAST(varp->valuep(), InitArray)) { } 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 (VN_IS(dtypep, AssocArrayDType)) {
if (initarp->defaultp()) { if (initarp->defaultp()) {
emitSetVarConstant(varNameProtected + ".atDefault()", emitSetVarConstant(varNameProtected + ".atDefault()",
@ -583,13 +585,14 @@ void EmitCFunc::emitVarReset(AstVar* varp, bool constructing) {
varp->v3fatalSrc("InitArray under non-arrayed var"); varp->v3fatalSrc("InitArray under non-arrayed var");
} }
} else { } 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, string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
const string& varNameProtected, AstNodeDType* dtypep, const string& varNameProtected, AstNodeDType* dtypep,
int depth, const string& suffix) { int depth, const string& suffix, const AstNode* valuep) {
dtypep = dtypep->skipRefp(); dtypep = dtypep->skipRefp();
AstBasicDType* const basicp = dtypep->basicp(); AstBasicDType* const basicp = dtypep->basicp();
// Returns string to do resetting, empty to do nothing (which caller should handle) // 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"; const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), + 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)) { } else if (AstWildcardArrayDType* const adtypep = VN_CAST(dtypep, WildcardArrayDType)) {
// Access std::array as C array // Access std::array as C array
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + ".atDefault()" + cvtarray); depth + 1, suffix + ".atDefault()" + cvtarray, nullptr);
} else if (VN_IS(dtypep, CDType)) { } else if (VN_IS(dtypep, CDType)) {
return ""; // Constructor does it return ""; // Constructor does it
} else if (VN_IS(dtypep, ClassRefDType)) { } 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"; const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), + 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)) { } else if (const AstQueueDType* const adtypep = VN_CAST(dtypep, QueueDType)) {
// Access std::array as C array // Access std::array as C array
const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : ""); const string cvtarray = (adtypep->subDTypep()->isWide() ? ".data()" : "");
const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n"; const string pre = constructing ? "" : varNameProtected + suffix + ".clear();\n";
return pre return pre
+ emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), + emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + ".atDefault()" + cvtarray); depth + 1, suffix + ".atDefault()" + cvtarray, nullptr);
} else if (VN_IS(dtypep, SampleQueueDType)) { } else if (VN_IS(dtypep, SampleQueueDType)) {
return ""; return "";
} else if (const AstUnpackArrayDType* const adtypep = VN_CAST(dtypep, UnpackArrayDType)) { } 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"); + cvtToStr(adtypep->elementsConst()) + "; ++" + ivar + ") {\n");
const string below const string below
= emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(), = emitVarResetRecurse(varp, constructing, varNameProtected, adtypep->subDTypep(),
depth + 1, suffix + "[" + ivar + "]"); depth + 1, suffix + "[" + ivar + "]", nullptr);
const string post = "}\n"; const string post = "}\n";
return below.empty() ? "" : pre + below + post; return below.empty() ? "" : pre + below + post;
} else if (VN_IS(dtypep, NodeUOrStructDType) && !VN_AS(dtypep, NodeUOrStructDType)->packed()) { } 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; string literal;
for (const AstMemberDType* itemp = sdtypep->membersp(); itemp; for (const AstMemberDType* itemp = sdtypep->membersp(); itemp;
itemp = VN_AS(itemp->nextp(), MemberDType)) { itemp = VN_AS(itemp->nextp(), MemberDType)) {
const std::string line = emitVarResetRecurse( const std::string line = emitVarResetRecurse(
varp, constructing, varNameProtected + suffix + "." + itemp->nameProtect(), varp, constructing, varNameProtected + suffix + "." + itemp->nameProtect(),
itemp->dtypep(), depth + 1, ""); itemp->dtypep(), depth + 1, "", itemp->valuep());
if (!line.empty()) literal += line; if (!line.empty()) literal += line;
} }
return literal; return literal;
@ -682,10 +685,10 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
splitSizeInc(1); splitSizeInc(1);
if (dtypep->isWide()) { // Handle unpacked; not basicp->isWide if (dtypep->isWide()) { // Handle unpacked; not basicp->isWide
string out; string out;
if (varp->valuep()) { if (valuep) {
const AstConst* const constp = VN_AS(varp->valuep(), Const); const AstConst* const constp = VN_AS(valuep, Const);
UASSERT_OBJ(constp, varp, "non-const initializer for variable"); 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 += varNameProtected + suffix + "[" + cvtToStr(w) + "] = ";
out += cvtToStr(constp->num().edataWord(w)) + "U;\n"; out += cvtToStr(constp->num().edataWord(w)) + "U;\n";
} }
@ -709,7 +712,15 @@ string EmitCFunc::emitVarResetRecurse(const AstVar* varp, bool constructing,
return out; return out;
} else { } else {
string out = varNameProtected + suffix; 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"; out += " = 0;\n";
} else { } else {
emitVarResetScopeHash(); emitVarResetScopeHash();

View File

@ -215,7 +215,7 @@ public:
void emitVarReset(AstVar* varp, bool constructing); void emitVarReset(AstVar* varp, bool constructing);
string emitVarResetRecurse(const AstVar* varp, bool constructing, string emitVarResetRecurse(const AstVar* varp, bool constructing,
const string& varNameProtected, AstNodeDType* dtypep, int depth, const string& varNameProtected, AstNodeDType* dtypep, int depth,
const string& suffix); const string& suffix, const AstNode* valuep);
void emitVarResetScopeHash(); void emitVarResetScopeHash();
void emitChangeDet(); void emitChangeDet();
void emitConstInit(AstNode* initp) { iterateConst(initp); } void emitConstInit(AstNode* initp) { iterateConst(initp); }

View File

@ -3270,10 +3270,6 @@ class WidthVisitor final : public VNVisitor {
itemp->v3error("Initial values not allowed in packed struct/union" itemp->v3error("Initial values not allowed in packed struct/union"
" (IEEE 1800-2023 7.2.2)"); " (IEEE 1800-2023 7.2.2)");
pushDeletep(itemp->valuep()->unlinkFrBack()); 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 const bool isHardPackedUnion
@ -5146,6 +5142,11 @@ class WidthVisitor final : public VNVisitor {
// default_value for any unmatched member yet // default_value for any unmatched member yet
return defaultp->cloneTree(false); 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)) { if (!VN_IS(memp_vdtypep, UnionDType)) {
nodep->v3error("Assignment pattern missed initializing elements: " nodep->v3error("Assignment pattern missed initializing elements: "
<< memp->virtRefDTypep()->prettyDTypeNameQ() << " " << memp->virtRefDTypep()->prettyDTypeNameQ() << " "

View File

@ -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

View File

@ -4,13 +4,15 @@
# This program is free software; you can redistribute it and/or modify it # 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 # under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0. # 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 # SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap import vltest_bootstrap
test.scenarios('linter') test.scenarios('simulator')
test.lint(fails=True, expect_filename=test.golden_filename) test.compile()
test.execute()
test.passes() test.passes()

View File

@ -1,25 +1,40 @@
// DESCRIPTION: Verilator: Verilog Test module // DESCRIPTION: Verilator: Verilog Test module
// //
// This file ONLY is placed under the Creative Commons Public Domain. // 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 // 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; module t;
parameter P = 4'h5; parameter P = 4'h5;
struct { // Can't legally be packed typedef struct { // Must be unpacked -- existing error check
bit [3:0] m_lo = P; // Update ctor_var_reset to check instead of making a constructor
bit [3:0] m_hi; bit [3:0] m_lo = P;
} s; bit [93:0] m_mid = '1; // Wide
bit [3:0] m_hi;
} s_t;
s_t s;
initial begin initial begin
s.m_hi = 4'ha; $display("%p", s);
if (s.m_lo != 4'h5) $stop; `checkh(s.m_lo, 4'h5);
if (s.m_hi != 4'ha) $stop; `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"); $write("*-* All Finished *-*\n");
$finish; $finish;
end end
endmodule endmodule

View File

@ -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()

View File

@ -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