Internals: Refactoring V3Param. No functional change intended.

This commit is contained in:
Wilson Snyder 2025-09-13 22:07:13 -04:00
parent e348c9c344
commit d7b8ede53f
3 changed files with 146 additions and 106 deletions

View File

@ -1899,7 +1899,7 @@ public:
// nodes needs to be deferred to a later time, because pointers to the
// removed nodes might still exist.
class VNDeleter final {
class VNDeleter VL_NOT_FINAL {
// MEMBERS
std::vector<AstNode*> m_deleteps; // Nodes to delete

View File

@ -947,7 +947,7 @@ public:
explicit AstConstPool(FileLine* fl);
ASTGEN_MEMBERS_AstConstPool;
bool maybePointedTo() const override VL_MT_SAFE { return true; }
void cloneRelink() override { V3ERROR_NA; }
void cloneRelink() override { UASSERT(!clonep(), "Not cloneable"); }
AstModule* modp() const { return m_modp; }
// Find a table (unpacked array) within the constant pool which is initialized with the
@ -1112,7 +1112,7 @@ public:
explicit AstExecGraph(FileLine* fl, const string& name) VL_MT_DISABLED;
~AstExecGraph() override;
ASTGEN_MEMBERS_AstExecGraph;
void cloneRelink() override { V3ERROR_NA; }
void cloneRelink() override { UASSERT(!clonep(), "Not cloneable"); }
const char* broken() const override {
BROKEN_RTN(!m_depGraphp);
return nullptr;
@ -1166,7 +1166,7 @@ public:
explicit AstMTaskBody(FileLine* fl)
: ASTGEN_SUPER_MTaskBody(fl) {}
ASTGEN_MEMBERS_AstMTaskBody;
void cloneRelink() override { V3ERROR_NA; }
void cloneRelink() override { UASSERT(!clonep(), "Not cloneable"); }
const char* broken() const override {
BROKEN_RTN(!m_execMTaskp);
return nullptr;
@ -1269,7 +1269,7 @@ public:
AstNetlist();
ASTGEN_MEMBERS_AstNetlist;
void deleteContents();
void cloneRelink() override { V3ERROR_NA; }
void cloneRelink() override { UASSERT(!clonep(), "Not cloneable"); }
string name() const override VL_MT_STABLE { return "$root"; }
void dump(std::ostream& str) const override;
void dumpJson(std::ostream& str) const override;
@ -1669,7 +1669,7 @@ public:
explicit AstTypeTable(FileLine* fl);
ASTGEN_MEMBERS_AstTypeTable;
bool maybePointedTo() const override VL_MT_SAFE { return true; }
void cloneRelink() override { V3ERROR_NA; }
void cloneRelink() override { UASSERT(!clonep(), "Not cloneable"); }
AstBasicDType* findBasicDType(FileLine* fl, VBasicDTypeKwd kwd);
AstBasicDType* findLogicBitDType(FileLine* fl, VBasicDTypeKwd kwd, int width, int widthMin,
VSigning numeric);

View File

@ -85,8 +85,8 @@ class ParameterizedHierBlocks final {
HierBlockModMap m_hierBlockMod;
// Overridden parameters of the hierarchical block
std::map<const V3HierarchicalBlockOption*, ParamConstMap> m_hierParams;
std::map<const std::string, GParamsMap>
m_modParams; // Parameter variables of hierarchical blocks
// Parameter variables of hierarchical blocks
std::map<const std::string, GParamsMap> m_modParams;
// METHODS
@ -655,6 +655,7 @@ class ParamProcessor final {
collectPins(clonemapp, newModp, srcModp->user3p());
// Relink parameter vars to the new module
relinkPins(clonemapp, paramsp);
// Fix any interface references
for (auto it = ifaceRefRefs.cbegin(); it != ifaceRefRefs.cend(); ++it) {
const AstIfaceRefDType* const portIrefp = it->first;
@ -1165,13 +1166,13 @@ public:
const string instanceName = someInstanceName + "." + nodep->name();
srcModpr->someInstanceName(instanceName);
if (auto* cellp = VN_CAST(nodep, Cell)) {
if (AstCell* cellp = VN_CAST(nodep, Cell)) {
cellDeparam(cellp, srcModpr);
} else if (auto* ifaceRefDTypep = VN_CAST(nodep, IfaceRefDType)) {
} else if (AstIfaceRefDType* ifaceRefDTypep = VN_CAST(nodep, IfaceRefDType)) {
ifaceRefDeparam(ifaceRefDTypep, srcModpr);
} else if (auto* classRefp = VN_CAST(nodep, ClassRefDType)) {
} else if (AstClassRefDType* classRefp = VN_CAST(nodep, ClassRefDType)) {
classRefDeparam(classRefp, srcModpr);
} else if (auto* classRefp = VN_CAST(nodep, ClassOrPackageRef)) {
} else if (AstClassOrPackageRef* classRefp = VN_CAST(nodep, ClassOrPackageRef)) {
classRefDeparam(classRefp, srcModpr);
} else {
nodep->v3fatalSrc("Expected module parameterization");
@ -1198,7 +1199,8 @@ public:
};
//######################################################################
// This visitor records classes that are referenced with parameter pins
// Record classes that are referenced with parameter pins
class ClassRefUnlinkerVisitor final : public VNVisitor {
public:
@ -1215,6 +1217,19 @@ public:
void visit(AstNode* nodep) override { iterateChildren(nodep); }
};
//######################################################################
// Process parameter top state
class ParamState final {
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
// Map from AstNodeModule to set of all AstNodeModules that instantiates it.
std::unordered_map<AstNodeModule*, std::unordered_set<AstNodeModule*>> m_parentps;
};
//######################################################################
// Process parameter visitor
@ -1222,34 +1237,33 @@ class ParamVisitor final : public VNVisitor {
// NODE STATE
// AstNodeModule::user1 -> bool: already fixed level (temporary)
// STATE
// STATE - across all visitors
ParamState& m_state; // Common state
ParamProcessor m_processor; // De-parameterize a cell, build modules
UnrollStateful m_unroller; // Loop unroller
bool m_iterateModule = false; // Iterating module body
string m_generateHierName; // Generate portion of hierarchy name
string m_unlinkedTxt; // Text for AstUnlinkedRef
AstNodeModule* m_modp; // Module iterating
std::vector<AstDot*> m_dots; // Dot references to process
std::multimap<bool, AstNode*> m_cellps; // Cells left to process (in current module)
std::multimap<int, AstNodeModule*> m_workQueue; // Modules left to process
std::vector<AstClass*> m_paramClasses; // Parameterized classes
std::deque<std::string> m_strings; // Allocator for temporary strings
// Map from AstNodeModule to set of all AstNodeModules that instantiates it.
std::unordered_map<AstNodeModule*, std::unordered_set<AstNodeModule*>> m_parentps;
// STATE - for current visit position (use VL_RESTORER)
AstNodeModule* m_modp; // Module iterating
string m_generateHierName; // Generate portion of hierarchy name
// METHODS
void visitCells(AstNodeModule* nodep) {
UASSERT_OBJ(!m_iterateModule, nodep, "Should not nest");
void processWorkQ() {
UASSERT(!m_iterateModule, "Should not nest");
std::multimap<int, AstNodeModule*> workQueue;
workQueue.emplace(nodep->level(), nodep);
m_generateHierName = "";
m_iterateModule = true;
// Visit all cells under module, recursively
do {
while (true) {
if (workQueue.empty()) std::swap(workQueue, m_state.m_workQueueNext);
if (workQueue.empty()) break;
const auto itm = workQueue.cbegin();
AstNodeModule* const modp = itm->second;
workQueue.erase(itm);
@ -1278,14 +1292,15 @@ class ParamVisitor final : public VNVisitor {
m_cellps.erase(itim);
AstNodeModule* srcModp = nullptr;
if (const auto* modCellp = VN_CAST(cellp, Cell)) {
if (const AstCell* modCellp = VN_CAST(cellp, Cell)) {
srcModp = modCellp->modp();
} else if (const auto* classRefp = VN_CAST(cellp, ClassOrPackageRef)) {
} else if (const AstClassOrPackageRef* classRefp
= VN_CAST(cellp, ClassOrPackageRef)) {
srcModp = classRefp->classOrPackageSkipp();
if (VN_IS(classRefp->classOrPackageNodep(), ParamTypeDType)) continue;
} else if (const auto* classRefp = VN_CAST(cellp, ClassRefDType)) {
} else if (const AstClassRefDType* classRefp = VN_CAST(cellp, ClassRefDType)) {
srcModp = classRefp->classp();
} else if (const auto* ifaceRefp = VN_CAST(cellp, IfaceRefDType)) {
} else if (const AstIfaceRefDType* ifaceRefp = VN_CAST(cellp, IfaceRefDType)) {
srcModp = ifaceRefp->ifacep();
} else {
cellp->v3fatalSrc("Expected module parameterization");
@ -1306,26 +1321,13 @@ class ParamVisitor final : public VNVisitor {
workQueue.emplace(srcModp->level(), srcModp);
// Add to the hierarchy registry
m_parentps[srcModp].insert(modp);
m_state.m_parentps[srcModp].insert(modp);
}
if (workQueue.empty()) std::swap(workQueue, m_workQueue);
} while (!workQueue.empty());
}
m_iterateModule = false;
}
// Fix up level of module, based on who instantiates it
void fixLevel(AstNodeModule* modp) {
if (modp->user1SetOnce()) return; // Already fixed
if (m_parentps[modp].empty()) return; // Leave top levels alone
int maxParentLevel = 0;
for (AstNodeModule* parentp : m_parentps[modp]) {
fixLevel(parentp); // Ensure parent level is correct
maxParentLevel = std::max(maxParentLevel, parentp->level());
}
if (modp->level() <= maxParentLevel) modp->level(maxParentLevel + 1);
}
// A generic visitor for cells and class refs
void visitCellOrClassRef(AstNode* nodep, bool isIface) {
// Must do ifaces first, so push to list and do in proper order
@ -1336,21 +1338,6 @@ class ParamVisitor final : public VNVisitor {
m_cellps.emplace(!isIface, nodep);
}
// RHSs of AstDots need a relink when LHS is a parameterized class reference
void relinkDots() {
for (AstDot* const dotp : m_dots) {
const AstClassOrPackageRef* const classRefp = VN_AS(dotp->lhsp(), ClassOrPackageRef);
const AstClass* const lhsClassp = VN_AS(classRefp->classOrPackageSkipp(), Class);
AstClassOrPackageRef* const rhsp = VN_AS(dotp->rhsp(), ClassOrPackageRef);
for (auto* itemp = lhsClassp->membersp(); itemp; itemp = itemp->nextp()) {
if (itemp->name() == rhsp->name()) {
rhsp->classOrPackageNodep(itemp);
break;
}
}
}
}
// VISITORS
void visit(AstNodeModule* nodep) override {
if (nodep->recursiveClone()) nodep->dead(true); // Fake, made for recursive elimination
@ -1359,15 +1346,16 @@ class ParamVisitor final : public VNVisitor {
if (classp->hasGParam()) {
// Don't enter into a definition.
// If a class is used, it will be visited through a reference and cloned
m_paramClasses.push_back(classp);
m_state.m_paramClasses.push_back(classp);
return;
}
}
if (m_iterateModule) { // Iterating from visitCells
if (m_iterateModule) { // Iterating from processWorkQ
UINFO(4, " MOD-under-MOD. " << nodep);
m_workQueue.emplace(nodep->level(), nodep); // Delay until current module is done
// visitCells (which we are returning to) will process nodep from m_workQueue later
// Delay until current module is done.
// processWorkQ() (which we are returning to) will process nodep later
m_state.m_workQueueNext.emplace(nodep->level(), nodep);
return;
}
@ -1375,7 +1363,8 @@ class ParamVisitor final : public VNVisitor {
if (nodep->level() <= 2 // Haven't added top yet, so level 2 is the top
|| VN_IS(nodep, Class) // Nor moved classes
|| VN_IS(nodep, Package)) { // Likewise haven't done wrapTopPackages yet
visitCells(nodep);
m_state.m_workQueueNext.emplace(nodep->level(), nodep);
processWorkQ();
}
}
@ -1503,7 +1492,7 @@ class ParamVisitor final : public VNVisitor {
AstClassOrPackageRef* const rhsp = VN_CAST(nodep->rhsp(), ClassOrPackageRef);
if (rhsp) rhsDefp = rhsp->classOrPackageNodep();
if (lhsClassp && rhsDefp) {
m_dots.push_back(nodep);
m_state.m_dots.push_back(nodep);
// No need to iterate into rhsp, because there should be nothing to do
} else {
iterate(nodep->rhsp());
@ -1677,57 +1666,108 @@ class ParamVisitor final : public VNVisitor {
public:
// CONSTRUCTORS
explicit ParamVisitor(AstNetlist* netlistp)
: m_processor{netlistp} {
explicit ParamVisitor(ParamState& state, AstNetlist* netlistp)
: m_state{state}
, m_processor{netlistp} {
// Relies on modules already being in top-down-order
iterate(netlistp);
}
~ParamVisitor() override = default;
VL_UNCOPYABLE(ParamVisitor);
};
// Mark classes which cannot be removed because they are still referenced
ClassRefUnlinkerVisitor markVisitor{netlistp};
//######################################################################
// Process top handler
relinkDots();
class ParamTop final : VNDeleter {
// NODE STATE
// AstNodeModule::user1 -> bool: already fixed level (temporary)
// Re-sort module list to be in topological order and fix-up incorrect levels. We need to
// do this globally at the end due to the presence of recursive modules, which might be
// expanded in orders that reuse earlier specializations later at a lower level.
{
// Gather modules
std::vector<AstNodeModule*> modps;
for (AstNodeModule *modp = netlistp->modulesp(), *nextp; modp; modp = nextp) {
nextp = VN_AS(modp->nextp(), NodeModule);
modp->unlinkFrBack();
modps.push_back(modp);
}
// STATE
ParamState m_state; // Common state
// Fix-up levels
{
const VNUser1InUse user1InUse;
for (AstNodeModule* const modp : modps) fixLevel(modp);
}
// METHODS
// Sort by level
std::stable_sort(modps.begin(), modps.end(),
[](const AstNodeModule* ap, const AstNodeModule* bp) {
return ap->level() < bp->level();
});
// Re-insert modules
for (AstNodeModule* const modp : modps) netlistp->addModulesp(modp);
for (AstClass* const classp : m_paramClasses) {
if (!classp->user3p()) {
// The default value isn't referenced, so it can be removed
VL_DO_DANGLING(pushDeletep(classp->unlinkFrBack()), classp);
} else {
// Referenced. classp became a specialized class with the default
// values of parameters and is not a parameterized class anymore
classp->hasGParam(false);
void relinkDots() {
// RHSs of AstDots need a relink when LHS is a parameterized class reference
for (AstDot* const dotp : m_state.m_dots) {
const AstClassOrPackageRef* const classRefp = VN_AS(dotp->lhsp(), ClassOrPackageRef);
const AstClass* const lhsClassp = VN_AS(classRefp->classOrPackageSkipp(), Class);
AstClassOrPackageRef* const rhsp = VN_AS(dotp->rhsp(), ClassOrPackageRef);
for (auto* itemp = lhsClassp->membersp(); itemp; itemp = itemp->nextp()) {
if (itemp->name() == rhsp->name()) {
rhsp->classOrPackageNodep(itemp);
break;
}
}
}
}
~ParamVisitor() override = default;
VL_UNCOPYABLE(ParamVisitor);
void fixLevel(AstNodeModule* modp) {
// Fix up level of module, based on who instantiates it
if (modp->user1SetOnce()) return; // Already fixed
if (m_state.m_parentps[modp].empty()) return; // Leave top levels alone
int maxParentLevel = 0;
for (AstNodeModule* parentp : m_state.m_parentps[modp]) {
fixLevel(parentp); // Ensure parent level is correct
maxParentLevel = std::max(maxParentLevel, parentp->level());
}
if (modp->level() <= maxParentLevel) modp->level(maxParentLevel + 1);
}
void resortNetlistModules(AstNetlist* netlistp) {
// Re-sort module list to be in topological order and fix-up incorrect levels. We need to
// do this globally at the end due to the presence of recursive modules, which might be
// expanded in orders that reuse earlier specializations later at a lower level.
// Gather modules
std::vector<AstNodeModule*> modps;
for (AstNodeModule *modp = netlistp->modulesp(), *nextp; modp; modp = nextp) {
nextp = VN_AS(modp->nextp(), NodeModule);
modp->unlinkFrBack();
modps.push_back(modp);
}
// Fix-up levels
const VNUser1InUse user1InUse;
for (AstNodeModule* const modp : modps) fixLevel(modp);
// Sort by level
std::stable_sort(modps.begin(), modps.end(),
[](const AstNodeModule* ap, const AstNodeModule* bp) {
return ap->level() < bp->level();
});
// Re-insert modules
for (AstNodeModule* const modp : modps) netlistp->addModulesp(modp);
}
public:
// CONSTRUCTORS
explicit ParamTop(AstNetlist* netlistp) {
// Relies on modules already being in top-down-order
ParamVisitor paramVisitor{m_state /*ref*/, netlistp};
// Mark classes which cannot be removed because they are still referenced
ClassRefUnlinkerVisitor classUnlinkerVisitor{netlistp};
relinkDots();
resortNetlistModules(netlistp);
// Remove defaulted classes
for (AstClass* const classp : m_state.m_paramClasses) {
if (!classp->user3p()) {
// The default value isn't referenced, so it can be removed
VL_DO_DANGLING(pushDeletep(classp->unlinkFrBack()), classp);
} else {
// Referenced. classp became a specialized class with the default
// values of parameters and is not a parameterized class anymore
classp->hasGParam(false);
}
}
}
~ParamTop() = default;
VL_UNCOPYABLE(ParamTop);
};
//######################################################################
@ -1735,6 +1775,6 @@ public:
void V3Param::param(AstNetlist* rootp) {
UINFO(2, __FUNCTION__ << ":");
{ ParamVisitor{rootp}; } // Destruct before checking
{ ParamTop{rootp}; } // Destruct before checking
V3Global::dumpCheckGlobalTree("param", 0, dumpTreeEitherLevel() >= 3);
}