Support modport expression syntax + nested (#2601) (#5581) (#7005)

This commit is contained in:
Leela Pakanati 2026-02-06 05:38:16 -06:00 committed by GitHub
parent 2215d01d6b
commit b14d65a787
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 836 additions and 88 deletions

View File

@ -2649,13 +2649,167 @@ class LinkDotIfaceVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep), nodep);
}
}
// 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.
// Returns the resolved symbol, or nullptr if not found.
// On success, clears baddot; on partial match of multi-level path, updates baddot.
VSymEnt* resolveNestedInterfacePath(FileLine* fl, VSymEnt* okSymp, string& baddot) {
if (!okSymp || baddot.empty()) return nullptr;
static constexpr int MAX_NESTING_DEPTH = 64;
VSymEnt* curOkSymp = okSymp;
for (int depth = 0; depth < MAX_NESTING_DEPTH; ++depth) {
// Try to get interface from the partially-matched symbol
AstIface* ifacep = nullptr;
if (const AstCell* const cellp = VN_CAST(curOkSymp->nodep(), Cell)) {
ifacep = VN_CAST(cellp->modp(), Iface);
} else if (const AstVar* const varp = VN_CAST(curOkSymp->nodep(), Var)) {
if (varp->isIfaceRef()) {
if (const AstIfaceRefDType* const ifaceRefp
= LinkDotState::ifaceRefFromArray(varp->dtypep())) {
ifacep = ifaceRefp->ifaceViaCellp();
}
}
}
if (!ifacep || !m_statep->existsNodeSym(ifacep)) return nullptr;
VSymEnt* const ifaceSymp = m_statep->getNodeSym(ifacep);
if (baddot.find('.') == string::npos) {
// Simple identifier - direct lookup
VSymEnt* const symp = ifaceSymp->findIdFallback(baddot);
if (symp) baddot.clear();
return symp;
}
// Multi-level path - use findDotted for partial resolution
string remainingBaddot;
VSymEnt* remainingOkSymp = nullptr;
VSymEnt* const symp = m_statep->findDotted(fl, ifaceSymp, baddot, remainingBaddot,
remainingOkSymp, true);
if (symp) {
baddot = remainingBaddot;
return symp;
}
// findDotted partially matched - check progress
if (remainingBaddot == baddot || !remainingOkSymp) return nullptr;
// Continue resolving with updated state
baddot = remainingBaddot;
curOkSymp = remainingOkSymp;
}
UINFO(1, "Nested interface resolution depth limit exceeded at " << fl << endl);
return nullptr;
}
// Resolve a modport expression to find the referenced symbol
VSymEnt* resolveModportExpression(FileLine* fl, AstNodeExpr* exprp) {
UINFO(5, " resolveModportExpression: " << exprp << endl);
VSymEnt* symp = nullptr;
if (AstParseRef* const refp = VN_CAST(exprp, ParseRef)) {
// Simple variable reference: modport mp(input .a(sig_a))
symp = m_curSymp->findIdFallback(refp->name());
} else if (AstVarRef* const refp = VN_CAST(exprp, VarRef)) {
// Already resolved VarRef (can happen if iterateChildren resolved it)
if (refp->varScopep()) {
// During scope creation, VarRef may have VarScope already linked
symp = m_statep->getNodeSym(refp->varScopep());
} else if (refp->varp()) {
symp = m_curSymp->findIdFallback(refp->varp()->name());
}
} else if (AstVarXRef* const refp = VN_CAST(exprp, VarXRef)) {
// Resolved VarXRef (dotted reference resolved by earlier pass)
if (refp->varp()) {
// For nested interfaces, the var is in a different scope
// First try to find the symbol via the var itself
if (m_statep->existsNodeSym(refp->varp())) {
symp = m_statep->getNodeSym(refp->varp());
} else {
// Fallback: look up in current scope
symp = m_curSymp->findIdFallback(refp->varp()->name());
}
} else if (!refp->dotted().empty()) {
// varp not set yet - use dotted path to find the symbol
// The dotted part (e.g., "base") is the interface cell, and name is the member
const string fullPath = refp->dotted() + "." + refp->name();
string baddot;
VSymEnt* okSymp = nullptr;
symp = m_statep->findDotted(fl, m_curSymp, fullPath, baddot, okSymp, true);
// Handle nested interface path when findDotted had partial match
if (okSymp && !baddot.empty()) {
VSymEnt* const resolved = resolveNestedInterfacePath(fl, okSymp, baddot);
if (resolved) symp = resolved;
}
}
} else if (AstDot* const dotp = VN_CAST(exprp, Dot)) {
// Dotted path: modport mp(input .a(inner.sig))
bool hasPartSelect = false;
const string dottedPath = extractDottedPath(dotp, hasPartSelect);
if (hasPartSelect) {
fl->v3warn(E_UNSUPPORTED,
"Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)");
} else {
string baddot;
VSymEnt* okSymp = nullptr;
symp = m_statep->findDotted(fl, m_curSymp, dottedPath, baddot, okSymp, true);
// Handle nested interface path when findDotted had partial match
if (okSymp && !baddot.empty()) {
VSymEnt* const resolved = resolveNestedInterfacePath(fl, okSymp, baddot);
if (resolved) symp = resolved;
}
if (!symp || !baddot.empty()) {
fl->v3error("Can't find modport expression target: "
<< AstNode::prettyNameQ(dottedPath));
symp = nullptr;
}
}
} else if (VN_IS(exprp, SelBit) || VN_IS(exprp, SelExtract)) {
// Part select expressions not yet supported
fl->v3warn(E_UNSUPPORTED,
"Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)");
} else {
// Other expression types not supported
fl->v3warn(E_UNSUPPORTED,
"Unsupported: Complex modport expression (IEEE 1800-2023 25.5.4)");
}
return symp;
}
void visit(AstModportVarRef* nodep) override { // IfaceVisitor::
UINFO(5, " fiv: " << nodep);
iterateChildren(nodep);
VSymEnt* symp = nullptr;
if (nodep->exprp()) {
nodep->v3warn(E_UNSUPPORTED,
"Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)");
// Modport expression syntax: modport mp(input .port_name(expression))
symp = resolveModportExpression(nodep->fileline(), nodep->exprp());
} else {
symp = m_curSymp->findIdFallback(nodep->name());
}
@ -2665,11 +2819,30 @@ class LinkDotIfaceVisitor final : public VNVisitor {
// Make symbol under modport that points at the _interface_'s var via the modport.
// (Need modport still to test input/output markings)
nodep->varp(varp);
m_statep->insertSym(m_curSymp, nodep->name(), nodep, nullptr /*package*/);
if (nodep->exprp()) {
// For modport expressions, insert symbol pointing to the underlying var (not the
// ModportVarRef which will be deleted during scope creation). The virtual port
// name maps to the real signal's var.
VSymEnt* const subSymp
= m_statep->insertSym(m_curSymp, nodep->name(), varp, nullptr /*package*/);
m_statep->insertScopeAlias(LinkDotState::SAMN_MODPORT, subSymp, symp);
} else {
// For regular modport items, insert symbol pointing to ModportVarRef for
// input/output marking tests.
m_statep->insertSym(m_curSymp, nodep->name(), nodep, nullptr /*package*/);
}
} else if (AstVarScope* const vscp = VN_CAST(symp->nodep(), VarScope)) {
// Make symbol under modport that points at the _interface_'s var, not the modport.
nodep->varp(vscp->varp());
m_statep->insertSym(m_curSymp, nodep->name(), vscp, nullptr /*package*/);
// Only insert symbol for modport expression virtual ports (exprp set).
// For regular modport items, don't insert into the shared modport table because
// each instance would overwrite the previous one, causing wrong VarScope lookups.
// Regular items are found via the modport's fallback to the interface.
if (nodep->exprp()) {
VSymEnt* const subSymp
= m_statep->insertSym(m_curSymp, nodep->name(), vscp, nullptr /*package*/);
m_statep->insertScopeAlias(LinkDotState::SAMN_MODPORT, subSymp, symp);
}
} else {
nodep->v3error("Modport item is not a variable: " << nodep->prettyNameQ());
}
@ -2866,6 +3039,46 @@ class LinkDotResolveVisitor final : public VNVisitor {
return nullptr;
}
}
// Look up a virtual port through modport expression mapping.
// When a dotted reference uses a modport with expression syntax, the virtual port name
// maps to the real signal. This helper resolves that mapping during scope creation.
// Returns the VarScope for the real signal, or nullptr if not found.
AstVarScope* findVirtualPortVarScope(VSymEnt* dotSymp, const string& portName) {
const AstVarScope* const dotVscp = VN_CAST(dotSymp->nodep(), VarScope);
const AstVar* const dotVarp = dotVscp ? dotVscp->varp() : nullptr;
if (!dotVarp) return nullptr;
AstNodeDType* dtypep = dotVarp->childDTypep();
if (!dtypep) dtypep = dotVarp->subDTypep();
if (!dtypep) return nullptr;
const AstIfaceRefDType* const ifaceRefp = VN_CAST(dtypep, IfaceRefDType);
if (!ifaceRefp || !ifaceRefp->modportp() || !ifaceRefp->ifaceViaCellp()) return nullptr;
// Get the interface cell's symbol table
VSymEnt* const ifaceCellSymp = m_statep->getNodeSym(ifaceRefp->ifaceViaCellp());
// Look up modport by name in interface cell
VSymEnt* const modportSymp = ifaceCellSymp->findIdFallback(ifaceRefp->modportp()->name());
if (!modportSymp) return nullptr;
// Look up virtual port name in modport symbol table
string baddot;
VSymEnt* const virtPortp = m_statep->findSymPrefixed(modportSymp, portName, baddot, true);
if (!virtPortp) return nullptr;
// Symbol may point to VarScope or Var
if (AstVarScope* vscp = VN_CAST(virtPortp->nodep(), VarScope)) return vscp;
// Symbol points to Var - look up VarScope by name in interface cell
if (const AstVar* const realVarp = VN_CAST(virtPortp->nodep(), Var)) {
VSymEnt* const realFoundp
= m_statep->findSymPrefixed(ifaceCellSymp, realVarp->name(), baddot, true);
return realFoundp ? VN_CAST(realFoundp->nodep(), VarScope) : nullptr;
}
return nullptr;
}
AstNodeStmt* addImplicitSuperNewCall(AstFunc* const nodep,
const AstClassExtends* const classExtendsp) {
// Returns the added node
@ -3863,6 +4076,24 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
foundp = m_ds.m_dotSymp->findIdFlat(nodep->name());
}
// If not found in modport, check interface fallback for parameters.
// Parameters are always visible through a modport (IEEE 1800-2023 25.5).
// This mirrors the VarXRef modport parameter fallback in visit(AstVarXRef).
if (!foundp && VN_IS(m_ds.m_dotSymp->nodep(), Modport)
&& m_ds.m_dotSymp->fallbackp()) {
VSymEnt* const ifaceFoundp
= m_ds.m_dotSymp->fallbackp()->findIdFlat(nodep->name());
if (ifaceFoundp) {
if (const AstVar* const varp = VN_CAST(ifaceFoundp->nodep(), Var)) {
if (varp->isParam()) foundp = ifaceFoundp;
}
}
}
// When flat lookup in modport fails, provide dotSymp for error diagnostics
// so the "Known scopes under..." hint appears (restores pre-routing behavior)
if (!foundp && !okSymp && VN_IS(m_ds.m_dotSymp->nodep(), Modport)) {
okSymp = m_ds.m_dotSymp;
}
if (foundp) {
UINFO(9, indent() << "found=se" << cvtToHex(foundp) << " exp=" << expectWhat
<< " n=" << foundp->nodep());
@ -3959,7 +4190,12 @@ class LinkDotResolveVisitor final : public VNVisitor {
// Really this is a scope reference into an interface
UINFO(9, indent() << "varref-ifaceref " << m_ds.m_dotText << " " << nodep);
m_ds.m_dotText = VString::dot(m_ds.m_dotText, ".", nodep->name());
m_ds.m_dotSymp = m_statep->getNodeSym(ifacerefp->ifaceViaCellp());
// If modport specified, use modport symbol table for lookup of virtual ports
if (ifacerefp->modportp() && m_statep->existsNodeSym(ifacerefp->modportp())) {
m_ds.m_dotSymp = m_statep->getNodeSym(ifacerefp->modportp());
} else {
m_ds.m_dotSymp = m_statep->getNodeSym(ifacerefp->ifaceViaCellp());
}
m_ds.m_dotPos = DP_SCOPE;
ok = true;
AstNode* const newp = new AstVarRef{nodep->fileline(), varp, VAccess::READ};
@ -4109,6 +4345,12 @@ class LinkDotResolveVisitor final : public VNVisitor {
m_ds.m_dotSymp = foundp;
if (m_ds.m_dotText != "") m_ds.m_dotText += "." + nodep->name();
ok = m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST;
} else if (const AstModportClockingRef* const clockingRefp
= VN_CAST(foundp->nodep(), ModportClockingRef)) {
// Clocking block accessed through a modport - redirect to actual clocking
m_ds.m_dotSymp = m_statep->getNodeSym(clockingRefp->clockingp());
if (m_ds.m_dotText != "") m_ds.m_dotText += "." + nodep->name();
ok = m_ds.m_dotPos == DP_SCOPE || m_ds.m_dotPos == DP_FIRST;
} else if (const AstNodeFTask* const ftaskp = VN_CAST(foundp->nodep(), NodeFTask)) {
if (!ftaskp->isFunction() || ftaskp->classMethod()) {
ok = m_ds.m_dotPos == DP_NONE;
@ -4396,7 +4638,7 @@ class LinkDotResolveVisitor final : public VNVisitor {
}
bool modport = false;
if (const AstVar* varp = VN_CAST(dotSymp->nodep(), Var)) {
if (const AstVar* const varp = VN_CAST(dotSymp->nodep(), Var)) {
if (const AstIfaceRefDType* const ifaceRefp
= VN_CAST(varp->childDTypep(), IfaceRefDType)) {
if (ifaceRefp->modportp()) {
@ -4474,7 +4716,9 @@ class LinkDotResolveVisitor final : public VNVisitor {
} else {
VSymEnt* const foundp
= m_statep->findSymPrefixed(dotSymp, nodep->name(), baddot, true);
AstVarScope* vscp = foundp ? VN_AS(foundp->nodep(), VarScope) : nullptr;
AstVarScope* vscp = foundp ? VN_CAST(foundp->nodep(), VarScope) : nullptr;
// Handle modport expression virtual ports
if (!vscp) vscp = findVirtualPortVarScope(dotSymp, nodep->name());
// If found, check if it's ok to access in case it's in a hier_block
if (vscp && errorHierNonPort(nodep, vscp->varp(), dotSymp)) return;
if (!vscp) {

View File

@ -1,7 +1,7 @@
%Error: t/t_interface_generic_modport_bad3.v:18:11: Can't find definition of 'v' in dotted signal: 'a.v'
%Error: t/t_interface_generic_modport_bad3.v:18:11: Can't find definition of 'v' in dotted variable/method: 'a.v'
: ... note: In instance 't.genericModule'
18 | if (a.v != 7) $stop;
| ^
... Known scopes under 'v': <no instances found>
... Known scopes under 'a': <no instances found>
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -1,6 +1,6 @@
%Error: t/t_interface_mismodport_bad.v:32:12: Can't find definition of 'bad' in dotted signal: 'isub.bad'
%Error: t/t_interface_mismodport_bad.v:32:12: Can't find definition of 'bad' in dotted variable/method: 'isub.bad'
32 | isub.bad = i_value;
| ^~~
... Known scopes under 'bad': <no instances found>
... Known scopes under 'isub': <no instances found>
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: Exiting due to

View File

@ -1,39 +0,0 @@
%Error-UNSUPPORTED: t/t_interface_modport_expr.v:15:22: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
15 | modport mp1(input .a(sig_a), output .b(sig_b));
| ^
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: t/t_interface_modport_expr.v:15:22: Modport item not found: 'a'
15 | modport mp1(input .a(sig_a), output .b(sig_b));
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error-UNSUPPORTED: t/t_interface_modport_expr.v:15:40: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
15 | modport mp1(input .a(sig_a), output .b(sig_b));
| ^
%Error: t/t_interface_modport_expr.v:15:40: Modport item not found: 'b'
15 | modport mp1(input .a(sig_a), output .b(sig_b));
| ^
%Error-UNSUPPORTED: t/t_interface_modport_expr.v:16:22: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
16 | modport mp2(input .a(sig_c), output .b(sig_d));
| ^
%Error: t/t_interface_modport_expr.v:16:22: Modport item not found: 'a'
16 | modport mp2(input .a(sig_c), output .b(sig_d));
| ^
%Error-UNSUPPORTED: t/t_interface_modport_expr.v:16:40: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
16 | modport mp2(input .a(sig_c), output .b(sig_d));
| ^
%Error: t/t_interface_modport_expr.v:16:40: Modport item not found: 'b'
16 | modport mp2(input .a(sig_c), output .b(sig_d));
| ^
%Error: t/t_interface_modport_expr.v:28:18: Can't find definition of 'a' in dotted variable/method: 'i.a'
28 | assign i.b = i.a;
| ^
%Error: t/t_interface_modport_expr.v:28:12: Can't find definition of 'b' in dotted variable/method: 'i.b'
28 | assign i.b = i.a;
| ^
%Error: t/t_interface_modport_expr.v:22:18: Can't find definition of 'a' in dotted variable/method: 'i.a'
22 | assign i.b = i.a;
| ^
%Error: t/t_interface_modport_expr.v:22:12: Can't find definition of 'b' in dotted variable/method: 'i.b'
22 | assign i.b = i.a;
| ^
%Error: Exiting due to

View File

@ -4,18 +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: 2025 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile(fails=test.vlt_all,
expect_filename=test.golden_filename,
verilator_flags2=["--binary"])
test.compile(verilator_flags2=["--binary"])
if not test.vlt_all:
test.execute()
test.execute()
test.passes()

View File

@ -9,11 +9,18 @@
`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);
// verilog_format: on
interface my_if;
logic sig_a, sig_b, sig_c, sig_d;
interface my_if #(parameter WIDTH = 1);
logic [WIDTH-1:0] sig_a, sig_b, sig_c, sig_d;
logic [WIDTH-1:0] sig_e, sig_f;
// Multiple expressions same direction
logic [WIDTH-1:0] m1, m2, m3;
modport mp1(input .a(sig_a), output .b(sig_b));
modport mp2(input .a(sig_c), output .b(sig_d));
// Mixed regular and expression items
modport mp3(input sig_e, output .f(sig_f));
// Multiple expressions with same direction
modport mp4(input .in1(m1), input .in2(m2), output .out(m3));
endinterface
module mod1 (
@ -28,17 +35,35 @@ module mod2 (
assign i.b = i.a;
endmodule
module top ();
my_if myIf ();
assign myIf.sig_a = 1'b1, myIf.sig_c = 1'b1;
module mod3 (
my_if.mp3 i
);
assign i.f = i.sig_e; // sig_e is regular, f is expression
endmodule
mod1 mod1Instance (myIf);
mod2 mod2Instance (myIf);
module mod4 (
my_if.mp4 i
);
assign i.out = i.in1 ^ i.in2;
endmodule
module top ();
my_if #(.WIDTH(8)) myIf ();
assign myIf.sig_a = 8'h42, myIf.sig_c = 8'hAB;
assign myIf.sig_e = 8'hCD;
assign myIf.m1 = 8'hF0, myIf.m2 = 8'h0F;
mod1 mod1i (myIf.mp1);
mod2 mod2i (myIf.mp2);
mod3 mod3i (myIf.mp3);
mod4 mod4i (myIf.mp4);
initial begin
#1;
`checkh(myIf.sig_a, myIf.sig_b);
`checkh(myIf.sig_c, myIf.sig_d);
`checkh(myIf.sig_b, 8'h42); // mp1: b = a
`checkh(myIf.sig_d, 8'hAB); // mp2: b = a
`checkh(myIf.sig_f, 8'hCD); // mp3: f = sig_e
`checkh(myIf.m3, 8'hFF); // mp4: out = in1 ^ in2
#1;
$write("*-* All Finished *-*\n");
$finish;

View File

@ -0,0 +1,43 @@
%Error-UNSUPPORTED: t/t_interface_modport_expr_array.v:26:13: Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)
26 | output .ch0_wr(ch[0].wr), input .ch0_rd(ch[0].rd),
| ^~~~~~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: t/t_interface_modport_expr_array.v:26:13: Modport item not found: 'ch0_wr'
26 | output .ch0_wr(ch[0].wr), input .ch0_rd(ch[0].rd),
| ^~~~~~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error-UNSUPPORTED: t/t_interface_modport_expr_array.v:26:38: Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)
26 | output .ch0_wr(ch[0].wr), input .ch0_rd(ch[0].rd),
| ^~~~~~
%Error: t/t_interface_modport_expr_array.v:26:38: Modport item not found: 'ch0_rd'
26 | output .ch0_wr(ch[0].wr), input .ch0_rd(ch[0].rd),
| ^~~~~~
%Error-UNSUPPORTED: t/t_interface_modport_expr_array.v:27:13: Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)
27 | output .ch1_wr(ch[1].wr), input .ch1_rd(ch[1].rd)
| ^~~~~~
%Error: t/t_interface_modport_expr_array.v:27:13: Modport item not found: 'ch1_wr'
27 | output .ch1_wr(ch[1].wr), input .ch1_rd(ch[1].rd)
| ^~~~~~
%Error-UNSUPPORTED: t/t_interface_modport_expr_array.v:27:38: Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)
27 | output .ch1_wr(ch[1].wr), input .ch1_rd(ch[1].rd)
| ^~~~~~
%Error: t/t_interface_modport_expr_array.v:27:38: Modport item not found: 'ch1_rd'
27 | output .ch1_wr(ch[1].wr), input .ch1_rd(ch[1].rd)
| ^~~~~~
%Error: t/t_interface_modport_expr_array.v:33:29: Can't find definition of 'ch0_wr' in dotted variable/method: 'port.ch0_wr'
33 | assign port.ch0_rd = port.ch0_wr + 8'h10;
| ^~~~~~
... Known scopes under 'port': <no instances found>
%Error: t/t_interface_modport_expr_array.v:33:15: Can't find definition of 'ch0_rd' in dotted variable/method: 'port.ch0_rd'
33 | assign port.ch0_rd = port.ch0_wr + 8'h10;
| ^~~~~~
... Known scopes under 'port': <no instances found>
%Error: t/t_interface_modport_expr_array.v:34:29: Can't find definition of 'ch1_wr' in dotted variable/method: 'port.ch1_wr'
34 | assign port.ch1_rd = port.ch1_wr + 8'h20;
| ^~~~~~
... Known scopes under 'port': <no instances found>
%Error: t/t_interface_modport_expr_array.v:34:15: Can't find definition of 'ch1_rd' in dotted variable/method: 'port.ch1_rd'
34 | assign port.ch1_rd = port.ch1_wr + 8'h20;
| ^~~~~~
... Known scopes under 'port': <no instances found>
%Error: Exiting due to

View File

@ -0,0 +1,21 @@
#!/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(fails=test.vlt_all,
expect_filename=test.golden_filename,
verilator_flags2=["--binary"])
if not test.vlt_all:
test.execute()
test.passes()

View File

@ -0,0 +1,51 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
// Test modport expressions with arrayed interface instances (unsupported)
// verilog_format: off
`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);
// verilog_format: on
interface base_if;
logic [7:0] wr;
logic [7:0] rd;
modport host(output wr, input rd);
modport dev(input wr, output rd);
endinterface
interface container_if;
base_if ch[2]();
// Modport expressions accessing arrayed interface instances
modport mp(
output .ch0_wr(ch[0].wr), input .ch0_rd(ch[0].rd),
output .ch1_wr(ch[1].wr), input .ch1_rd(ch[1].rd)
);
endinterface
module consumer(container_if.mp port);
// Access through modport expression virtual ports
assign port.ch0_rd = port.ch0_wr + 8'h10;
assign port.ch1_rd = port.ch1_wr + 8'h20;
endmodule
module top;
container_if cont();
consumer m_cons(.port(cont));
initial begin
cont.ch[0].wr = 8'hA0;
cont.ch[1].wr = 8'hB0;
#1;
`checkh(cont.ch[0].rd, 8'hB0);
`checkh(cont.ch[1].rd, 8'hD0);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,18 @@
%Error: t/t_interface_modport_expr_bad.v:10:22: Can't find modport expression target: 'nonexist.sig'
10 | modport mp1(input .in(nonexist.sig));
| ^~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_interface_modport_expr_bad.v:10:22: Modport item not found: 'in'
10 | modport mp1(input .in(nonexist.sig));
| ^~
%Error-UNSUPPORTED: t/t_interface_modport_expr_bad.v:12:22: Unsupported: Complex modport expression (IEEE 1800-2023 25.5.4)
12 | modport mp2(input .in(~a));
| ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
%Error: t/t_interface_modport_expr_bad.v:12:22: Modport item not found: 'in'
12 | modport mp2(input .in(~a));
| ^~
%Error: t/t_interface_modport_expr_bad.v:10:25: Can't find definition of scope/variable: 'nonexist'
10 | modport mp1(input .in(nonexist.sig));
| ^~~~~~~~
%Error: Exiting due to

View File

@ -0,0 +1,21 @@
#!/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(fails=test.vlt_all,
expect_filename=test.golden_filename,
verilator_flags2=["--binary"])
if not test.vlt_all:
test.execute()
test.passes()

View File

@ -0,0 +1,17 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
interface iface;
logic a, b;
// Dotted path to non-existent signal
modport mp1(input .in(nonexist.sig));
// Complex expression not supported as modport expression
modport mp2(input .in(~a));
endinterface
module t;
iface intf ();
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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,142 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
// Test modport expressions with hierarchical module instantiation
// verilog_format: off
`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);
// verilog_format: on
// ============================================================
// Scenario A: Single-level hierarchy + expression modport
// ============================================================
interface if_a;
logic [7:0] raw_in;
logic [7:0] raw_out;
modport mp(input .data_in(raw_in), output .data_out(raw_out));
endinterface
// Leaf module: uses modport expression virtual ports
module a_leaf(if_a.mp port);
assign port.data_out = port.data_in + 8'h10;
endmodule
// Mid-level module: passes interface down
module a_mid(if_a.mp port);
a_leaf u_leaf(.port(port));
endmodule
// ============================================================
// Scenario B: 2-level deep hierarchy
// ============================================================
interface if_b;
logic [15:0] x;
logic [15:0] y;
modport mp(input .alpha(x), output .beta(y));
endinterface
module b_leaf(if_b.mp port);
assign port.beta = port.alpha ^ 16'hFFFF;
endmodule
module b_mid(if_b.mp port);
b_leaf u_leaf(.port(port));
endmodule
module b_top_wrap(if_b.mp port);
b_mid u_mid(.port(port));
endmodule
// ============================================================
// Scenario C: Nested interface + expression modport + hierarchy
// ============================================================
interface if_c_inner;
logic [7:0] val;
endinterface
interface if_c_outer;
if_c_inner inner();
modport mp(output .w(inner.val), input .r(inner.val));
endinterface
module c_leaf(if_c_outer.mp port);
assign port.w = 8'hBE;
wire [7:0] c_read = port.r;
endmodule
module c_mid(if_c_outer.mp port);
c_leaf u_leaf(.port(port));
endmodule
// ============================================================
// Scenario D: Multiple instances of same wrapper (no cross-talk)
// ============================================================
interface if_d;
logic [7:0] din;
logic [7:0] dout;
modport mp(input .vi(din), output .vo(dout));
endinterface
module d_leaf(if_d.mp port);
assign port.vo = port.vi + 8'h01;
endmodule
module d_mid(if_d.mp port);
d_leaf u_leaf(.port(port));
endmodule
// ============================================================
// Top module
// ============================================================
module top;
// --- Scenario A ---
if_a ifa();
a_mid u_a(.port(ifa));
// --- Scenario B ---
if_b ifb();
b_top_wrap u_b(.port(ifb));
// --- Scenario C ---
if_c_outer ifc();
c_mid u_c(.port(ifc));
// --- Scenario D ---
if_d ifd();
d_mid u_d(.port(ifd));
initial begin
// Scenario A: single-level hierarchy
ifa.raw_in = 8'h20;
#1;
`checkh(ifa.raw_out, 8'h30);
// Scenario B: 2-level deep hierarchy
ifb.x = 16'h1234;
#1;
`checkh(ifb.y, 16'hEDCB);
// Scenario C: nested interface + hierarchy
#1;
`checkh(ifc.inner.val, 8'hBE);
`checkh(u_c.u_leaf.c_read, 8'hBE);
// Scenario D: single instance through hierarchy
ifd.din = 8'h10;
#1;
`checkh(ifd.dout, 8'h11);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,19 @@
#!/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.top_filename = "t/t_interface_modport_expr_hier.v"
test.compile(verilator_flags2=["--binary", "-fno-inline"])
test.execute()
test.passes()

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(verilator_flags2=["--binary"])
test.execute()
test.passes()

View File

@ -0,0 +1,130 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Leela Pakanati
// SPDX-License-Identifier: CC0-1.0
// Test modport expressions with nested interfaces
// verilog_format: off
`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);
// verilog_format: on
interface base_reg_if;
logic [7:0] wr;
logic [7:0] rd;
modport host(output wr, input rd);
modport dev(input wr, output rd);
endinterface
interface example_reg_if;
logic [15:0] wr;
logic [15:0] rd;
modport host(output wr, input rd);
modport dev(input wr, output rd);
endinterface
interface app_reg_if;
base_reg_if base();
example_reg_if example();
// Use modport expressions to expose nested interface signals
modport host(
output .base_wr(base.wr), input .base_rd(base.rd),
output .example_wr(example.wr), input .example_rd(example.rd)
);
modport dev(
input .base_wr(base.wr), output .base_rd(base.rd),
input .example_wr(example.wr), output .example_rd(example.rd)
);
endinterface
// Deep nesting test (3 levels)
interface inner_if;
logic [7:0] data;
modport producer(output data);
modport consumer(input data);
endinterface
interface middle_if;
inner_if inner();
endinterface
interface outer_if;
middle_if middle();
// 3-level deep modport expression
modport mp(
output .deep_out(middle.inner.data),
input .deep_in(middle.inner.data)
);
endinterface
module deep_consumer(outer_if.mp port);
assign port.deep_out = 8'hDE;
// Verify reading through deep_in virtual port
wire [7:0] deep_in_val = port.deep_in;
endmodule
// 4-level deep nesting test
interface level1_if;
logic [7:0] val;
endinterface
interface level2_if;
level1_if l1();
endinterface
interface level3_if;
level2_if l2();
endinterface
interface level4_if;
level3_if l3();
// 4-level deep modport expression
modport mp(
output .deep4_w(l3.l2.l1.val),
input .deep4_r(l3.l2.l1.val)
);
endinterface
module deep4_consumer(level4_if.mp port);
assign port.deep4_w = 8'h4D;
wire [7:0] deep4_read = port.deep4_r;
endmodule
module app_consumer (
app_reg_if.dev i_app_regs
);
// Access through modport expression virtual ports
assign i_app_regs.base_rd = i_app_regs.base_wr + 8'h1;
assign i_app_regs.example_rd = i_app_regs.example_wr + 16'h1;
endmodule
module top;
app_reg_if app_regs();
outer_if outer();
level4_if lev4();
app_consumer m_app(.i_app_regs(app_regs));
deep_consumer m_deep(.port(outer));
deep4_consumer m_deep4(.port(lev4));
initial begin
app_regs.base.wr = 8'hAB;
app_regs.example.wr = 16'hCDEF;
#1;
`checkh(app_regs.base.rd, 8'hAC);
`checkh(app_regs.example.rd, 16'hCDF0);
// Verify 3-level deep nesting
`checkh(outer.middle.inner.data, 8'hDE);
`checkh(m_deep.deep_in_val, 8'hDE);
// Verify 4-level deep nesting
`checkh(lev4.l3.l2.l1.val, 8'h4D);
`checkh(m_deep4.deep4_read, 8'h4D);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -0,0 +1,19 @@
#!/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.top_filename = "t/t_interface_modport_expr_nested.v"
test.compile(verilator_flags2=["--binary", "-fno-inline"])
test.execute()
test.passes()

View File

@ -0,0 +1,19 @@
#!/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.top_filename = "t/t_interface_modport_expr.v"
test.compile(verilator_flags2=["--binary", "-fno-inline"])
test.execute()
test.passes()

View File

@ -1,4 +1,4 @@
%Error-UNSUPPORTED: t/t_interface_modport_expr_partsel.v:16:22: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
%Error-UNSUPPORTED: t/t_interface_modport_expr_partsel.v:16:22: Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)
16 | modport mp1(input .in(a[7:0]), output .out(b));
| ^~
... For error description see https://verilator.org/warn/UNSUPPORTED?v=latest
@ -6,34 +6,18 @@
16 | modport mp1(input .in(a[7:0]), output .out(b));
| ^~
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error-UNSUPPORTED: t/t_interface_modport_expr_partsel.v:16:42: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
16 | modport mp1(input .in(a[7:0]), output .out(b));
| ^~~
%Error: t/t_interface_modport_expr_partsel.v:16:42: Modport item not found: 'out'
16 | modport mp1(input .in(a[7:0]), output .out(b));
| ^~~
%Error-UNSUPPORTED: t/t_interface_modport_expr_partsel.v:17:22: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
%Error-UNSUPPORTED: t/t_interface_modport_expr_partsel.v:17:22: Unsupported: Modport expression with part select (IEEE 1800-2023 25.5.4)
17 | modport mp2(input .in(a[15:8]), output .out(c));
| ^~
%Error: t/t_interface_modport_expr_partsel.v:17:22: Modport item not found: 'in'
17 | modport mp2(input .in(a[15:8]), output .out(c));
| ^~
%Error-UNSUPPORTED: t/t_interface_modport_expr_partsel.v:17:43: Unsupported: Modport expressions (IEEE 1800-2023 25.5.4)
17 | modport mp2(input .in(a[15:8]), output .out(c));
| ^~~
%Error: t/t_interface_modport_expr_partsel.v:17:43: Modport item not found: 'out'
17 | modport mp2(input .in(a[15:8]), output .out(c));
| ^~~
%Error: t/t_interface_modport_expr_partsel.v:29:21: Can't find definition of 'in' in dotted variable/method: 'i.in'
29 | assign i.out = ~i.in;
| ^~
%Error: t/t_interface_modport_expr_partsel.v:29:12: Can't find definition of 'out' in dotted variable/method: 'i.out'
29 | assign i.out = ~i.in;
| ^~~
... Known scopes under 'i': <no instances found>
%Error: t/t_interface_modport_expr_partsel.v:23:20: Can't find definition of 'in' in dotted variable/method: 'i.in'
23 | assign i.out = i.in;
| ^~
%Error: t/t_interface_modport_expr_partsel.v:23:12: Can't find definition of 'out' in dotted variable/method: 'i.out'
23 | assign i.out = i.in;
| ^~~
... Known scopes under 'i': <no instances found>
%Error: Exiting due to

View File

@ -4,7 +4,7 @@
# 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: 2025 Wilson Snyder
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap

View File

@ -5,7 +5,8 @@
%Error: t/t_mod_interface_clocking_bad.v:16:41: Modport item not found: 'cx'
16 | modport mp(input clk, clocking reset, clocking cx);
| ^~~~~~~~
%Error: t/t_mod_interface_clocking_bad.v:25:10: Can't find definition of 'cb'
%Error: t/t_mod_interface_clocking_bad.v:25:7: Can't find definition of 'cb' in dotted scope/variable: 'x.cb'
25 | x.cb.reset <= 1;
| ^~~~~
| ^~
... Known scopes under 'x': <no instances found>
%Error: Exiting due to