Fix interface modport resolution against non-scope symbol tables (#8420)

LinkDotFindVisitor queued an AstIface into m_ifaceModSyms once for every
symbol table entry it was reached through. When creating scopes,
LinkDotIfaceVisitor resolves the modport items against the AstVarScopes,
which LinkDotScopeVisitor only inserts under the entry of an AstScope. A
hierarchical block wrapper builds a second copy of the instance tree in
the symbol table, and an interface reached through that copy has no
AstVarScopes to resolve against.

Resolving is destructive (the AstModportVarRef is deleted once resolved),
so this only worked when a resolvable entry happened to be processed
first, and failed with "Modport item not found" otherwise. When creating
scopes, register from the AstScopes instead, as those are exactly the
entries that hold the AstVarScopes.

Reproduces on t_hier_block with -fno-inline, added as a test.
This commit is contained in:
Geza Lore
2026-09-20 20:52:17 +01:00
committed by GitHub
parent bef6c8afa0
commit 652495a932
2 changed files with 36 additions and 3 deletions
+12 -3
View File
@@ -1344,9 +1344,13 @@ class LinkDotFindVisitor final : public VNVisitor {
iterateChildren(nodep);
nodep->user2(false);
nodep->user4(true);
// Interfaces need another pass when signals are resolved
if (AstIface* const ifacep = VN_CAST(nodep, Iface)) {
m_statep->insertIfaceModSym(ifacep, m_curSymp);
// Interfaces need another pass when signals are resolved. When creating
// scopes this is done from the AstScopes in LinkDotScopeVisitor instead, as
// only those entries hold the AstVarScopes the modport items resolve against.
if (!m_statep->forScopeCreation()) {
if (AstIface* const ifacep = VN_CAST(nodep, Iface)) {
m_statep->insertIfaceModSym(ifacep, m_curSymp);
}
}
} else if (isHierBlockWrapper(nodep->name())) {
UINFO(5, "Module is hierarchical block, must not be dead: " << nodep);
@@ -2684,6 +2688,11 @@ private:
VL_RESTORER(m_modSymp);
VL_RESTORER(m_scopep);
m_modSymp = m_statep->getScopeSym(nodep);
// Interfaces need another pass when signals are resolved. Register the scope's
// entry, as that is the one holding the AstVarScopes.
if (AstIface* const ifacep = VN_CAST(nodep->modp(), Iface)) {
m_statep->insertIfaceModSym(ifacep, m_modSymp);
}
m_scopep = nodep;
iterateChildren(nodep);
}
+24
View File
@@ -0,0 +1,24 @@
#!/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.priority(30)
test.scenarios('vlt')
test.top_filename = "t/t_hier_block.v"
# Compilation of hierarchical blocks is skipped if their libs exist
test.clean_objs()
test.compile(v_flags2=['t/t_hier_block.cpp'],
verilator_flags2=['-fno-inline', '--hierarchical', '--Wno-TIMESCALEMOD'])
test.execute()
test.passes()