Fix wrong $bits() for parameterized interface struct typedefs (#7218) (#7219)

This commit is contained in:
em2machine
2026-03-09 22:32:13 -04:00
committed by GitHub
parent 1f67080a1f
commit 1b2b8afdc1
4 changed files with 321 additions and 10 deletions
+59 -8
View File
@@ -800,7 +800,8 @@ class ParamProcessor final {
const string lastCompBase
= (braPos == string::npos) ? lastComp : lastComp.substr(0, braPos);
if (lastComp != cloneCellp->name() && lastCompBase != cloneCellp->name()) return false;
if (lastDot == string::npos) return true; // No parent portion to verify
// No parent portion to verify - startModp itself must be the expected parent
if (lastDot == string::npos) return startModp == expectModp;
const string parentPath = cellPath.substr(0, lastDot);
const AstNodeModule* const resolvedp
= V3LinkDotIfaceCapture::followCellPath(startModp, parentPath);
@@ -869,6 +870,7 @@ class ParamProcessor final {
<< (correctModp ? correctModp->name() : "<null>")
<< endl);
if (!correctModp || correctModp->dead()) return;
if (correctModp->parameterizedTemplate()) return;
bool fixed = false;
if (refp->typedefp()) {
@@ -1292,8 +1294,10 @@ class ParamProcessor final {
if (resolvedp && (VN_IS(resolvedp, StructDType) || VN_IS(resolvedp, UnionDType))) {
AstNodeModule* const ownerModp
= V3LinkDotIfaceCapture::findOwnerModule(resolvedp);
// Skip if owned by a parameterized template (not yet specialized)
if (ownerModp && ownerModp->parameterizedTemplate()) {
// Skip if owned by a parameterized interface (template or not
// yet cloned). hasGParam() covers interfaces that haven't
// been cloned yet (parameterizedTemplate() not set).
if (ownerModp && VN_IS(ownerModp, Iface) && ownerModp->hasGParam()) {
skipWidthForTemplateStruct = true;
V3Stats::addStatSum("Param, Template struct width skips", 1);
UINFO(9, "SKIP-WIDTH-TEMPLATE: struct="
@@ -1776,6 +1780,28 @@ class ParamProcessor final {
}
public:
// After an interface cell inside parentModp has been deparameterized
// (rewired from template to clone), retarget REFDTYPEs that still
// reference the old template's types so that $bits(iface_typedef)
// evaluates with the clone's widths.
void retargetIfaceRefs(AstNodeModule* parentModp, const string& cellName) {
AstNodeModule* const correctModp
= V3LinkDotIfaceCapture::followCellPath(parentModp, cellName);
if (!correctModp || correctModp->dead() || correctModp->parameterizedTemplate()) return;
V3LinkDotIfaceCapture::forEach([&](const V3LinkDotIfaceCapture::CapturedEntry& entry) {
if (!entry.refp || entry.cloneCellPath.empty()) return;
if (entry.cellPath != cellName) return;
// Identity check: only retarget REFDTYPEs that actually live
// inside parentModp. Multiple clones share origName so name
// matching alone would let one clone overwrite another's refs.
if (V3LinkDotIfaceCapture::findOwnerModule(entry.refp) != parentModp) return;
if (retargetRefToModule(entry, correctModp)) {
UINFO(9, "retargetIfaceRefs: " << entry.refp << " -> "
<< correctModp->prettyNameQ() << endl);
}
});
}
AstNodeModule* nodeDeparam(AstNode* nodep, AstNodeModule* srcModp, AstNodeModule* modp,
const string& someInstanceName) {
// Return new or reused de-parameterized module
@@ -1895,7 +1921,11 @@ public:
// STATE - across all visitors
std::vector<AstClass*> m_paramClasses; // Parameterized classes
std::vector<AstDot*> m_dots; // Dot references to process
std::multimap<int, AstNodeModule*> m_workQueueNext; // Modules left to process
// Work queue keyed by (!isIface, level) so interfaces are always processed
// before non-interfaces. This ensures interface clones have their types
// properly widthed before any module that references those types.
using WQKey = std::pair<bool, int>;
std::multimap<WQKey, AstNodeModule*> m_workQueueNext; // Modules left to process
// Map from AstNodeModule to set of all AstNodeModules that instantiates it.
std::unordered_map<AstNodeModule*, std::unordered_set<AstNodeModule*>> m_parentps;
};
@@ -1930,7 +1960,7 @@ class ParamVisitor final : public VNVisitor {
void processWorkQ() {
UASSERT(!m_iterateModule, "Should not nest");
std::multimap<int, AstNodeModule*> workQueue;
std::multimap<ParamState::WQKey, AstNodeModule*> workQueue;
m_generateHierName = "";
m_iterateModule = true;
@@ -2005,10 +2035,21 @@ class ParamVisitor final : public VNVisitor {
if (VN_IS(srcModp, Iface)) {
logTemplateLeakRefs(modp, srcModp, "after queued nodeDeparam", cellp);
// After the interface cell is rewired to its clone,
// retarget REFDTYPEs in the parent module that still
// reference the template interface's types.
if (V3LinkDotIfaceCapture::enabled()) {
if (const AstCell* const modCellp = VN_CAST(cellp, Cell)) {
if (newModp != srcModp) {
m_processor.retargetIfaceRefs(modp, modCellp->name());
}
}
}
}
// Add the (now potentially specialized) child module to the work queue
workQueue.emplace(newModp->level(), newModp);
workQueue.emplace(ParamState::WQKey{!VN_IS(newModp, Iface), newModp->level()},
newModp);
// Add to the hierarchy registry
m_state.m_parentps[newModp].insert(modp);
@@ -2194,6 +2235,14 @@ class ParamVisitor final : public VNVisitor {
// destructively widthing the template with default (zero)
// values. See t_interface_nested_struct_param.v.
m_processor.nodeDeparam(cellp, srcModp, m_modp, m_modp->someInstanceName());
// After the interface cell is rewired to its clone,
// retarget REFDTYPEs in the parent module that still
// reference the template interface's types. This ensures
// $bits(iface_typedef) evaluates correctly when
// widthParamsEdit runs on subsequent lparams.
if (V3LinkDotIfaceCapture::enabled() && cellp->modp() != srcModp) {
m_processor.retargetIfaceRefs(m_modp, cellp->name());
}
}
}
@@ -2225,7 +2274,8 @@ class ParamVisitor final : public VNVisitor {
UINFO(4, " MOD-under-MOD. " << nodep);
// Delay until current module is done.
// processWorkQ() (which we are returning to) will process nodep later
m_state.m_workQueueNext.emplace(nodep->level(), nodep);
m_state.m_workQueueNext.emplace(
ParamState::WQKey{!VN_IS(nodep, Iface), nodep->level()}, nodep);
return;
}
@@ -2233,7 +2283,8 @@ class ParamVisitor final : public VNVisitor {
if (nodep->isTop() // Tops
|| VN_IS(nodep, Class) // Moved classes
|| VN_IS(nodep, Package)) { // Likewise haven't done wrapTopPackages yet
m_state.m_workQueueNext.emplace(nodep->level(), nodep);
m_state.m_workQueueNext.emplace(
ParamState::WQKey{!VN_IS(nodep, Iface), nodep->level()}, nodep);
processWorkQ();
}
}