Support more than one dot in defparam (#7262)

Signed-off-by: Artur Bieniek <abieniek@antmicro.com>
This commit is contained in:
Artur Bieniek 2026-03-24 14:20:46 +01:00 committed by GitHub
parent 0b2bf991a6
commit aff85cef19
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 253 additions and 63 deletions

View File

@ -1024,19 +1024,18 @@ class AstDefParam final : public AstNode {
// A defparam assignment
// Parents: MODULE
// @astgen op1 := rhsp : AstNodeExpr
// @astgen op2 := pathp : AstNodeExpr
string m_name; // Name of variable getting set
string m_path; // Dotted cellname to set parameter of
public:
AstDefParam(FileLine* fl, const string& path, const string& name, AstNodeExpr* rhsp)
AstDefParam(FileLine* fl, AstNodeExpr* pathp, const string& name, AstNodeExpr* rhsp)
: ASTGEN_SUPER_DefParam(fl)
, m_name{name}
, m_path{path} {
, m_name{name} {
this->rhsp(rhsp);
this->pathp(pathp);
}
string name() const override VL_MT_STABLE { return m_name; } // * = Scope name
ASTGEN_MEMBERS_AstDefParam;
bool sameNode(const AstNode*) const override { return true; }
string path() const { return m_path; }
};
class AstDefaultDisable final : public AstNode {
// @astgen op1 := condp : AstNodeExpr
@ -1390,6 +1389,7 @@ class AstPin final : public AstNode {
// @astgen ptr := m_modPTypep : Optional[AstParamTypeDType] // Param type connects to on sub
int m_pinNum; // Pin number
string m_name; // Pin name, or "" for number based interconnect
string m_paramPath; // Original defparam cell path, if this pin came from a defparam
bool m_param = false; // Pin connects to parameter
bool m_svDotName = false; // Pin is SystemVerilog .name'ed
bool m_svImplicit = false; // Pin is SystemVerilog .name'ed, allow implicit
@ -1416,6 +1416,8 @@ public:
void modPTypep(AstParamTypeDType* nodep) { m_modPTypep = nodep; }
bool param() const { return m_param; }
void param(bool flag) { m_param = flag; }
const string& paramPath() const { return m_paramPath; }
void paramPath(const string& path) { m_paramPath = path; }
bool svDotName() const { return m_svDotName; }
void svDotName(bool flag) { m_svDotName = flag; }
bool svImplicit() const { return m_svImplicit; }

View File

@ -2312,12 +2312,14 @@ void AstPin::dump(std::ostream& str) const {
} else {
str << " ->UNLINKED";
}
if (!paramPath().empty()) str << " paramPath=" << paramPath();
if (svDotName()) str << " [.n]";
if (svImplicit()) str << " [.SV]";
}
void AstPin::dumpJson(std::ostream& str) const {
dumpJsonBoolFuncIf(str, svDotName);
dumpJsonBoolFuncIf(str, svImplicit);
if (!paramPath().empty()) dumpJsonStr(str, "paramPath", paramPath());
dumpJsonGen(str);
}
string AstPin::prettyOperatorName() const {

View File

@ -80,6 +80,23 @@
VL_DEFINE_DEBUG_FUNCTIONS;
static string extractDottedPath(AstNode* nodep, bool& hasPartSelect) {
if (AstParseRef* const refp = VN_CAST(nodep, ParseRef)) {
return refp->name();
} else if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
return refp->name();
} else if (AstDot* const dotp = VN_CAST(nodep, Dot)) {
const string lhs = extractDottedPath(dotp->lhsp(), hasPartSelect);
const string rhs = extractDottedPath(dotp->rhsp(), hasPartSelect);
return VString::dot(lhs, ".", rhs);
} else if (VN_IS(nodep, SelBit) || VN_IS(nodep, SelExtract) || VN_IS(nodep, SelPlus)
|| VN_IS(nodep, SelMinus)) {
hasPartSelect = true;
return "";
}
return "";
}
// ######################################################################
// Matcher classes (for suggestion matching)
@ -2401,19 +2418,26 @@ class LinkDotParamVisitor final : public VNVisitor {
<< nodep->warnMore()
<< "... Suggest use instantiation with #(."
<< nodep->prettyName() << "(...etc...))");
VSymEnt* const foundp = m_statep->getNodeSym(nodep)->findIdFallback(nodep->path());
bool hasPartSelect = false;
const string path = extractDottedPath(nodep->pathp(), hasPartSelect);
UASSERT_OBJ(!hasPartSelect && !path.empty(), nodep, "Unexpected defparam path shape");
string baddot;
VSymEnt* okSymp = nullptr;
VSymEnt* const foundp = m_statep->findDotted(
nodep->fileline(), m_statep->getNodeSym(nodep), path, baddot, okSymp, true);
AstCell* const cellp = foundp ? VN_AS(foundp->nodep(), Cell) : nullptr;
if (!cellp) {
nodep->v3error("In defparam, instance " << nodep->path() << " never declared");
nodep->v3error("In defparam, instance " << path << " never declared");
} else {
AstNodeExpr* const exprp = nodep->rhsp()->unlinkFrBack();
UINFO(9, "Defparam cell " << nodep->path() << "." << nodep->name() << " attach-to "
<< cellp << " <= " << exprp);
UINFO(9, "Defparam cell " << path << "." << nodep->name() << " attach-to " << cellp
<< " <= " << exprp);
// Don't need to check the name of the defparam exists. V3Param does.
AstPin* const pinp = new AstPin{nodep->fileline(),
-1, // Pin# not relevant
nodep->name(), exprp};
pinp->param(true);
pinp->paramPath(path);
cellp->addParamsp(pinp);
}
VL_DO_DANGLING(nodep->unlinkFrBack()->deleteTree(), nodep);
@ -2797,26 +2821,6 @@ class LinkDotIfaceVisitor final : public VNVisitor {
}
}
// Helper to extract a dotted path string from an AstDot tree
// Returns empty string and sets hasPartSelect=true if part-select detected
string extractDottedPath(AstNode* nodep, bool& hasPartSelect) {
if (AstParseRef* const refp = VN_CAST(nodep, ParseRef)) {
return refp->name();
} else if (AstVarRef* const refp = VN_CAST(nodep, VarRef)) {
return refp->name();
} else if (AstDot* const dotp = VN_CAST(nodep, Dot)) {
const string lhs = extractDottedPath(dotp->lhsp(), hasPartSelect);
const string rhs = extractDottedPath(dotp->rhsp(), hasPartSelect);
if (lhs.empty()) return rhs;
if (rhs.empty()) return lhs;
return lhs + "." + rhs;
} else if (VN_IS(nodep, SelBit) || VN_IS(nodep, SelExtract)) {
hasPartSelect = true;
return "";
}
return "";
}
// Helper to resolve remaining path through a nested interface
// When findDotted() partially matches (okSymp set, baddot non-empty),
// this follows the interface type to resolve the remaining path.
@ -3080,6 +3084,8 @@ class LinkDotResolveVisitor final : public VNVisitor {
bool m_insideClassExtParam = false; // Inside a class from m_extendsParam
AstNew* m_explicitSuperNewp = nullptr; // Hit a "super.new" call inside a "new" function
std::map<AstNode*, AstPin*> m_usedPins; // Pin used in this cell, map to duplicate
std::map<AstNode*, std::map<std::string, AstPin*>> m_usedDefParamPins;
// Defparam pins used for a given formal, by hierarchical path
std::map<std::string, AstNodeModule*> m_modulesToRevisit; // Modules to revisit a second time
AstNode* m_lastDeferredp = nullptr; // Last node which requested a revisit of its module
AstNodeDType* m_packedArrayDtp = nullptr; // Datatype reference for packed array
@ -3313,16 +3319,34 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
return foundNodep;
}
void duplicatePinError(AstPin* nodep, AstNode* origp, const char* whatp) {
nodep->v3error("Duplicate " << whatp << " connection: " << nodep->prettyNameQ() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< origp->warnOther() << "... Location of original " << whatp
<< " connection\n"
<< origp->warnContextSecondary());
}
void markAndCheckPinDup(AstPin* nodep, AstNode* refp, const char* whatp) {
const auto pair = m_usedPins.emplace(refp, nodep);
if (!pair.second) {
if (pair.second) {
if (!nodep->paramPath().empty())
m_usedDefParamPins[refp].emplace(nodep->paramPath(), nodep);
return;
} else {
AstNode* const origp = pair.first->second;
nodep->v3error("Duplicate " << whatp << " connection: " << nodep->prettyNameQ() << '\n'
<< nodep->warnContextPrimary() << '\n'
<< origp->warnOther() << "... Location of original "
<< whatp << " connection\n"
<< origp->warnContextSecondary());
if (nodep->paramPath().empty() || VN_AS(origp, Pin)->paramPath().empty()) {
duplicatePinError(nodep, origp, whatp);
return;
}
}
auto& defParamPins = m_usedDefParamPins[refp];
const auto defParamIt = defParamPins.find(nodep->paramPath());
if (defParamIt != defParamPins.end()) {
duplicatePinError(nodep, defParamIt->second, whatp);
return;
}
defParamPins.emplace(nodep->paramPath(), nodep);
}
VSymEnt* getCreateClockingEventSymEnt(AstClocking* clockingp) {
AstVar* const eventp = clockingp->ensureEventp(true);
@ -3698,7 +3722,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(5, indent() << "visit " << nodep);
checkNoDot(nodep);
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
UASSERT_OBJ(nodep->modp(), nodep,
"Instance has unlinked module"); // V3LinkCell should have errored out
VL_RESTORER(m_cellp);
@ -3736,7 +3762,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(5, indent() << "visit " << nodep);
// Can be under dot if called as package::class and that class resolves, so no checkNoDot
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
UASSERT_OBJ(nodep->classp(), nodep, "ClassRef has unlinked class");
UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp() || V3Error::errorCount(), nodep,
"class reference parameter not removed by V3Param");
@ -4620,7 +4648,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
UINFO(8, indent() << "visit " << nodep);
UINFO(9, indent() << m_ds.ascii());
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
UASSERT_OBJ(m_statep->forPrimary() || !nodep->paramsp(), nodep,
"class reference parameter not removed by V3Param");
{
@ -6036,7 +6066,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
if (ifacep->dead()) return;
checkNoDot(nodep);
VL_RESTORER(m_usedPins);
VL_RESTORER(m_usedDefParamPins);
m_usedPins.clear();
m_usedDefParamPins.clear();
VL_RESTORER(m_pinSymp);
m_pinSymp = m_statep->getNodeSym(ifacep);
iterateAndNextNull(nodep->paramsp());

View File

@ -530,6 +530,32 @@ class ParamProcessor final {
}
}
}
static bool hasDescendantDefparams(const AstNodeModule* modp,
std::set<const AstNodeModule*>& visited) {
if (!visited.insert(modp).second) return false;
for (AstNode* stmtp = modp->stmtsp(); stmtp; stmtp = stmtp->nextp()) {
AstCell* const cellp = VN_CAST(stmtp, Cell);
if (!cellp) continue;
for (AstPin* pinp = cellp->paramsp(); pinp; pinp = VN_AS(pinp->nextp(), Pin)) {
if (!pinp->paramPath().empty()) {
visited.erase(modp);
return true;
}
}
if (AstNodeModule* const childModp = cellp->modp()) {
if (hasDescendantDefparams(childModp, visited)) {
visited.erase(modp);
return true;
}
}
}
visited.erase(modp);
return false;
}
static bool hasDescendantDefparams(const AstNodeModule* modp) {
std::set<const AstNodeModule*> visited;
return hasDescendantDefparams(modp, visited);
}
// Check if parameter setting during instantiation is simple enough for hierarchical Verilation
void checkSupportedParam(AstNodeModule* modp, AstPin* pinp) const {
// InitArray is not supported because that can not be set via -G
@ -1609,6 +1635,10 @@ class ParamProcessor final {
}
}
}
if (!any_overrides && VN_IS(nodep, Cell) && hasDescendantDefparams(srcModp)) {
longname += "__Vdefparam" + V3Hash{srcModp->someInstanceName()}.toString();
any_overrides = true;
}
UINFO(9, "nodeDeparamCommon: " << srcModp->prettyNameQ() << " overrides=" << any_overrides
<< endl);
@ -1825,6 +1855,23 @@ public:
<< " parentSomeInstanceName='"
<< (modp ? modp->someInstanceName() : string("<null>")) << "'"
<< " inputSomeInstanceName='" << someInstanceName << "'" << endl);
string nodeName = nodep->name();
if (AstIfaceRefDType* const ifaceRefp = VN_CAST(nodep, IfaceRefDType)) {
if (nodeName.empty()) nodeName = ifaceRefp->cellName();
}
const string instanceName
= nodeName.empty() ? someInstanceName : (someInstanceName + "." + nodeName);
if (AstCell* const cellp = VN_CAST(nodep, Cell)) {
for (AstPin* pinp = cellp->paramsp(); pinp;) {
AstPin* const nextp = VN_AS(pinp->nextp(), Pin);
if (!pinp->paramPath().empty() && instanceName != pinp->paramPath()
&& !VString::endsWith(instanceName, "." + pinp->paramPath())) {
VL_DO_DANGLING(pinp->unlinkFrBack()->deleteTree(), pinp);
}
pinp = nextp;
}
}
// Create new module name with _'s between the constants
UINFOTREE(10, nodep, "", "cell");
// Evaluate all module constants
@ -1834,12 +1881,6 @@ public:
// so use cellName() which is the actual cell instance name.
// If both are empty (interface port, not a cell), skip appending
// to avoid double-dots in the path.
string nodeName = nodep->name();
if (AstIfaceRefDType* const ifaceRefp = VN_CAST(nodep, IfaceRefDType)) {
if (nodeName.empty()) nodeName = ifaceRefp->cellName();
}
const string instanceName
= nodeName.empty() ? someInstanceName : (someInstanceName + "." + nodeName);
srcModp->someInstanceName(instanceName);
UINFO(9, "nodeDeparam SET-SRC-INST srcMod="
<< srcModp->prettyNameQ() << " someInstanceName='"

View File

@ -3215,19 +3215,26 @@ list_of_defparam_assignments<nodep>: //== IEEE: list_of_defparam_assignments
;
defparam_assignment<nodep>: // ==IEEE: defparam_assignment
defparamIdRange '.' defparamIdRange '=' expr
{ $$ = new AstDefParam{$4, *$1, *$3, $5}; }
defparamIdRangeList '.' defparamIdRange '=' expr
{ $$ = new AstDefParam{$4, $1, *$3, $5}; }
| defparamIdRange '=' expr
{ $$ = nullptr; BBUNSUP($2, "Unsupported: defparam with no dot");
DEL($3); }
| defparamIdRange '.' defparamIdRange '.' defparamIdRangeList '=' expr
{ $$ = nullptr; BBUNSUP($4, "Unsupported: defparam with more than one dot");
DEL($7); }
;
defparamIdRangeList<strp>: // IEEE: part of defparam_assignment
defparamIdRange { $$ = $1; }
| defparamIdRangeList '.' defparamIdRange { $$ = $3; }
defparamIdRangeList<nodeExprp>: // IEEE: part of defparam_assignment
defparamIdRangeExpr { $$ = $1; }
| defparamIdRangeList '.' defparamIdRangeExpr
{ $$ = new AstDot{$2, false, $1, $3}; }
;
defparamIdRangeExpr<nodeExprp>: // IEEE: part of defparam_assignment
idAny
{ $$ = new AstParseRef{$<fl>1, *$1, nullptr, nullptr}; }
| idAny part_select_rangeList
{ $$ = new AstParseRef{$<fl>1, *$1, nullptr, nullptr};
BBUNSUP($2, "Unsupported: defparam with arrayed instance");
DEL($2); }
;
defparamIdRange<strp>: // IEEE: part of defparam_assignment

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(timing_loop=True, verilator_flags2=["--timing"])
test.execute()
test.passes()

View File

@ -0,0 +1,77 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain
// SPDX-FileCopyrightText: 2026 Antmicro
// SPDX-License-Identifier: CC0-1.0
`define stop $stop
`define checkh(gotv,expv) do if ((gotv) !== (expv)) begin $write("%%Error: %s:%0d: got=%0x exp=%0x (%s !== %s)\n", `__FILE__,`__LINE__, (gotv), (expv), `"gotv`", `"expv`"); `stop; end while(0);
module M0 #(parameter PRMTR = 1) (
output int value
);
assign value = PRMTR;
endmodule
module M1 #(parameter PRMTR = 1)(
output int value
);
int v0, v1;
M0 m0a(.value(v0));
M0 m0b(.value(v1));
assign value = v0 + v1;
endmodule
module M2 #(parameter PRMTR = 1)(
output int value
);
M1 m1(.value(value));
endmodule
module M3 #(parameter PRMTR = 1)(
output int value
);
int v0, v1;
M2 m2a(.value(v0));
M2 m2b(.value(v1));
assign value = v0 * v1;
endmodule
module top;
int value;
M3 m3(.value(value));
defparam m3.m2a.m1.m0a.PRMTR = 2;
defparam m3.m2a.m1.m0b.PRMTR = 3;
defparam m3.m2b.m1.m0a.PRMTR = 4;
defparam m3.m2b.m1.m0b.PRMTR = 5;
defparam m3.m2a.m1.PRMTR = 6;
defparam m3.m2b.m1.PRMTR = 7;
defparam m3.m2a.PRMTR = 8;
defparam m3.m2b.PRMTR = 9;
defparam m3.PRMTR = 10;
initial begin
`checkh(m3.m2a.m1.m0a.PRMTR, 2);
`checkh(m3.m2a.m1.m0b.PRMTR, 3);
`checkh(m3.m2b.m1.m0a.PRMTR, 4);
`checkh(m3.m2b.m1.m0b.PRMTR, 5);
`checkh(m3.m2a.m1.PRMTR, 6);
`checkh(m3.m2b.m1.PRMTR, 7);
`checkh(m3.m2a.PRMTR, 8);
`checkh(m3.m2b.PRMTR, 9);
`checkh(m3.PRMTR, 10);
#1;
`checkh(value, 45); // (2+3) * (4+5)
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -5,13 +5,4 @@
%Error-UNSUPPORTED: t/t_gen_defparam_multi.v:28:19: Unsupported: defparam with arrayed instance
28 | defparam blk[i].u_m3.PAR3 = i;
| ^
%Error-UNSUPPORTED: t/t_gen_defparam_multi.v:28:27: Unsupported: defparam with more than one dot
28 | defparam blk[i].u_m3.PAR3 = i;
| ^
%Error-UNSUPPORTED: t/t_gen_defparam_multi.v:51:43: Unsupported: defparam with more than one dot
51 | defparam m2.PAR2 = 8; defparam m2.m3.PAR3 = 80;
| ^
%Error-UNSUPPORTED: t/t_gen_defparam_multi.v:55:43: Unsupported: defparam with more than one dot
55 | defparam m2.PAR2 = 4; defparam m2.m3.PAR3 = 40;
| ^
%Error: Exiting due to

View File

@ -1,24 +1,27 @@
%Warning-PINMISSING: t/t_lint_pindup_bad.v:18:7: Instance has missing pin: 'exists'
18 | sub (
| ^~~
t/t_lint_pindup_bad.v:33:16: ... Location of port declaration
33 | input wire exists
t/t_lint_pindup_bad.v:37:16: ... Location of port declaration
37 | input wire exists
| ^~~~~~
... For warning description see https://verilator.org/warn/PINMISSING?v=latest
... Use "/* verilator lint_off PINMISSING */" and lint_on around source to disable this message.
%Error: t/t_lint_pindup_bad.v:26:30: In defparam, instance sub.subnotfound never declared
26 | defparam sub.subnotfound.P = 2;
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_lint_pindup_bad.v:21:8: Duplicate pin connection: 'i'
21 | .i(i2),
| ^
t/t_lint_pindup_bad.v:20:8: ... Location of original pin connection
20 | .i(i),
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error-PINNOTFOUND: t/t_lint_pindup_bad.v:22:8: Pin not found: 'nexist'
: ... Suggested alternative: 'exists'
22 | .nexist(i2)
| ^~~~~~
: ... Location of instance's module declaration
27 | module sub #(
31 | module sub #(
| ^~~
... For error description see https://verilator.org/warn/PINNOTFOUND?v=latest
%Error-PINNOTFOUND: t/t_lint_pindup_bad.v:14:8: Parameter not found: 'NEXIST'
@ -26,7 +29,7 @@
14 | .NEXIST(1),
| ^~~~~~
: ... Location of instance's module declaration
27 | module sub #(
31 | module sub #(
| ^~~
%Error: t/t_lint_pindup_bad.v:16:8: Duplicate parameter connection: 'P'
16 | .P(3)
@ -34,4 +37,17 @@
t/t_lint_pindup_bad.v:15:8: ... Location of original parameter connection
15 | .P(2),
| ^
%Error: t/t_lint_pindup_bad.v:25:18: Duplicate parameter connection: 'P'
25 | defparam sub.P = 2;
| ^
t/t_lint_pindup_bad.v:15:8: ... Location of original parameter connection
15 | .P(2),
| ^
%Error-PINNOTFOUND: t/t_lint_pindup_bad.v:27:23: Parameter not found: 'NEXIST'
: ... Suggested alternative: 'EXIST'
27 | defparam sub.NEXIST = 2;
| ^
: ... Location of instance's module declaration
31 | module sub #(
| ^~~
%Error: Exiting due to

View File

@ -22,6 +22,10 @@ module t (
.nexist(i2) // Not found
);
defparam sub.P = 2; // Dup
defparam sub.subnotfound.P = 2; // Not found
defparam sub.NEXIST = 2; // Not found
endmodule
module sub #(