mirror of https://github.com/llvm/circt.git
492 lines
19 KiB
C++
492 lines
19 KiB
C++
//===- PrefixModules.cpp - Prefix module names pass -------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// This file defines the PrefixModules pass.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "PassDetails.h"
|
|
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
|
|
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
|
|
#include "circt/Dialect/FIRRTL/NLATable.h"
|
|
#include "circt/Dialect/FIRRTL/Passes.h"
|
|
#include "circt/Dialect/HW/HWAttributes.h"
|
|
#include "circt/Support/LLVM.h"
|
|
#include "mlir/IR/AttrTypeSubElements.h"
|
|
#include "llvm/ADT/DenseMap.h"
|
|
#include "llvm/ADT/PostOrderIterator.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
|
|
using namespace circt;
|
|
using namespace firrtl;
|
|
|
|
/// This maps a FModuleOp to a list of all prefixes that need to be applied.
|
|
/// When a module has multiple prefixes, it will be cloned for each one. Usually
|
|
/// there is only a single prefix applied to each module, although there could
|
|
/// be many.
|
|
using PrefixMap = llvm::DenseMap<StringRef, std::vector<std::string>>;
|
|
|
|
/// Insert a string into the end of vector if the string is not already present.
|
|
static void recordPrefix(PrefixMap &prefixMap, StringRef moduleName,
|
|
std::string prefix) {
|
|
auto &modulePrefixes = prefixMap[moduleName];
|
|
if (llvm::find(modulePrefixes, prefix) == modulePrefixes.end())
|
|
modulePrefixes.push_back(prefix);
|
|
}
|
|
|
|
namespace {
|
|
/// This is the prefix which will be applied to a module.
|
|
struct PrefixInfo {
|
|
|
|
/// The string to prefix on to the module and all of its children.
|
|
StringRef prefix;
|
|
|
|
/// If true, this prefix applies to the module itself. If false, the prefix
|
|
/// only applies to the module's children.
|
|
bool inclusive;
|
|
};
|
|
} // end anonymous namespace
|
|
|
|
/// Get the PrefixInfo for a module from a NestedPrefixModulesAnnotation on a
|
|
/// module. If the module is not annotated, the prefix returned will be empty.
|
|
static PrefixInfo getPrefixInfo(Operation *module) {
|
|
AnnotationSet annotations(module);
|
|
|
|
// Get the annotation from the module.
|
|
auto anno = annotations.getAnnotation(prefixModulesAnnoClass);
|
|
if (!anno)
|
|
return {"", false};
|
|
|
|
// Get the prefix from the annotation.
|
|
StringRef prefix = "";
|
|
if (auto prefixAttr = anno.getMember<StringAttr>("prefix"))
|
|
prefix = prefixAttr.getValue();
|
|
|
|
// Get the inclusive flag from the annotation.
|
|
bool inclusive = false;
|
|
if (auto inclusiveAttr = anno.getMember<BoolAttr>("inclusive"))
|
|
inclusive = inclusiveAttr.getValue();
|
|
|
|
return {prefix, inclusive};
|
|
}
|
|
|
|
/// If there is an inclusive prefix attached to the module, return it.
|
|
static StringRef getPrefix(Operation *module) {
|
|
auto prefixInfo = getPrefixInfo(module);
|
|
if (prefixInfo.inclusive)
|
|
return prefixInfo.prefix;
|
|
return "";
|
|
}
|
|
|
|
/// This pass finds modules annotated with NestedPrefixAnnotation and prefixes
|
|
/// module names using the string stored in the annotation. This pass prefixes
|
|
/// every module instantiated under the annotated root module's hierarchy. If a
|
|
/// module is instantiated under two different prefix hierarchies, it will be
|
|
/// duplicated and each module will have one prefix applied.
|
|
namespace {
|
|
class PrefixModulesPass : public PrefixModulesBase<PrefixModulesPass> {
|
|
void removeDeadAnnotations(StringAttr moduleName, Operation *op);
|
|
void renameModuleBody(std::string prefix, StringRef oldName,
|
|
FModuleOp module);
|
|
void renameModule(FModuleOp module);
|
|
void renameExtModule(FExtModuleOp extModule);
|
|
void renameMemModule(FMemModuleOp memModule);
|
|
void runOnOperation() override;
|
|
|
|
/// Mutate Grand Central Interface definitions (an Annotation on the circuit)
|
|
/// with a field "prefix" containing the prefix for that annotation. This
|
|
/// relies on information built up during renameModule and stored in
|
|
/// interfacePrefixMap.
|
|
void prefixGrandCentralInterfaces();
|
|
|
|
/// This is a map from a module name to new prefixes to be applied.
|
|
PrefixMap prefixMap;
|
|
|
|
/// A map of Grand Central interface ID to prefix.
|
|
DenseMap<Attribute, std::string> interfacePrefixMap;
|
|
|
|
/// Cached instance graph analysis.
|
|
InstanceGraph *instanceGraph = nullptr;
|
|
|
|
/// Cached nla table analysis.
|
|
NLATable *nlaTable = nullptr;
|
|
|
|
/// Boolean keeping track of any name changes.
|
|
bool anythingChanged = false;
|
|
};
|
|
} // namespace
|
|
|
|
/// When a module is cloned, it carries with it all non-local annotations. This
|
|
/// function will remove all non-local annotations from the clone with a path
|
|
/// that doesn't match.
|
|
void PrefixModulesPass::removeDeadAnnotations(StringAttr moduleName,
|
|
Operation *op) {
|
|
// A predicate to check if an annotation can be removed. If there is a
|
|
// reference to a NLA, the NLA should either contain this module in its path,
|
|
// if its an InstanceOp. Else, it must exist at the leaf of the NLA. Otherwise
|
|
// the NLA reference can be removed, since its a spurious annotation, result
|
|
// of cloning the original module.
|
|
auto canRemoveAnno = [&](Annotation anno, Operation *op) -> bool {
|
|
auto nla = anno.getMember("circt.nonlocal");
|
|
if (!nla)
|
|
return false;
|
|
auto nlaName = cast<FlatSymbolRefAttr>(nla).getAttr();
|
|
auto nlaOp = nlaTable->getNLA(nlaName);
|
|
if (!nlaOp) {
|
|
op->emitError("cannot find HierPathOp :" + nlaName.getValue());
|
|
signalPassFailure();
|
|
return false;
|
|
}
|
|
|
|
bool isValid = false;
|
|
if (isa<InstanceOp>(op))
|
|
isValid = nlaOp.hasModule(moduleName);
|
|
else
|
|
isValid = nlaOp.leafMod() == moduleName;
|
|
return !isValid;
|
|
};
|
|
AnnotationSet::removePortAnnotations(
|
|
op, std::bind(canRemoveAnno, std::placeholders::_2, op));
|
|
AnnotationSet::removeAnnotations(
|
|
op, std::bind(canRemoveAnno, std::placeholders::_1, op));
|
|
}
|
|
|
|
/// Applies the prefix to the module. This will update the required prefixes of
|
|
/// any referenced module in the prefix map.
|
|
void PrefixModulesPass::renameModuleBody(std::string prefix, StringRef oldName,
|
|
FModuleOp module) {
|
|
auto *context = module.getContext();
|
|
|
|
// If we are renaming the body of this module, we need to mark that we have
|
|
// changed the IR. If we are prefixing with the empty string, then nothing has
|
|
// changed yet.
|
|
if (!prefix.empty())
|
|
anythingChanged = true;
|
|
StringAttr thisMod = module.getNameAttr();
|
|
|
|
// Remove spurious NLA references from the module ports and the module itself.
|
|
// Some of the NLA references become invalid after a module is cloned, based
|
|
// on the instance.
|
|
removeDeadAnnotations(thisMod, module);
|
|
|
|
mlir::AttrTypeReplacer replacer;
|
|
replacer.addReplacement(
|
|
[&](hw::InnerRefAttr innerRef) -> std::pair<Attribute, WalkResult> {
|
|
StringAttr moduleName = innerRef.getModule();
|
|
StringAttr symName = innerRef.getName();
|
|
|
|
StringAttr newTarget;
|
|
if (moduleName == oldName) {
|
|
newTarget = module.getNameAttr();
|
|
} else {
|
|
auto target = instanceGraph->lookup(moduleName)->getModule();
|
|
newTarget = StringAttr::get(context, prefix + getPrefix(target) +
|
|
target.getModuleName());
|
|
}
|
|
return {hw::InnerRefAttr::get(newTarget, symName), WalkResult::skip()};
|
|
});
|
|
|
|
module.getBody().walk([&](Operation *op) {
|
|
// Remove spurious NLA references either on a leaf op, or the InstanceOp.
|
|
removeDeadAnnotations(thisMod, op);
|
|
|
|
if (auto memOp = dyn_cast<MemOp>(op)) {
|
|
StringAttr newPrefix;
|
|
if (auto oldPrefix = memOp->getAttrOfType<StringAttr>("prefix"))
|
|
newPrefix = StringAttr::get(context, prefix + oldPrefix.getValue());
|
|
else
|
|
newPrefix = StringAttr::get(context, prefix);
|
|
memOp->setAttr("prefix", newPrefix);
|
|
} else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
|
|
auto target = instanceOp.getReferencedModule(*instanceGraph);
|
|
|
|
// Skip all external modules, unless one of the following conditions
|
|
// is true:
|
|
// - This is a Grand Central Data Tap
|
|
// - This is a Grand Central Mem Tap
|
|
if (auto *extModule = dyn_cast_or_null<FExtModuleOp>(&target)) {
|
|
auto isDataTap =
|
|
AnnotationSet(*extModule).hasAnnotation(dataTapsBlackboxClass);
|
|
auto isMemTap = AnnotationSet::forPort(*extModule, 0)
|
|
.hasAnnotation(memTapPortClass);
|
|
if (!isDataTap && !isMemTap)
|
|
return;
|
|
}
|
|
|
|
// Record that we must prefix the target module with the current prefix.
|
|
recordPrefix(prefixMap, target.getModuleName(), prefix);
|
|
|
|
// Fixup this instance op to use the prefixed module name. Note that the
|
|
// referenced FModuleOp will be renamed later.
|
|
auto newTarget = StringAttr::get(context, prefix + getPrefix(target) +
|
|
target.getModuleName());
|
|
AnnotationSet instAnnos(instanceOp);
|
|
// If the instance has HierPathOp, then update its module name also.
|
|
// There can be multiple HierPathOps attached to the instance op.
|
|
|
|
StringAttr oldModName = instanceOp.getModuleNameAttr().getAttr();
|
|
// Update the NLAs that apply on this InstanceOp.
|
|
for (Annotation anno : instAnnos) {
|
|
if (auto nla = anno.getMember("circt.nonlocal")) {
|
|
auto nlaName = cast<FlatSymbolRefAttr>(nla).getAttr();
|
|
nlaTable->updateModuleInNLA(nlaName, oldModName, newTarget);
|
|
}
|
|
}
|
|
// Now get the NLAs that pass through the InstanceOp and update them also.
|
|
DenseSet<hw::HierPathOp> instNLAs;
|
|
nlaTable->getInstanceNLAs(instanceOp, instNLAs);
|
|
for (auto nla : instNLAs)
|
|
nlaTable->updateModuleInNLA(nla, oldModName, newTarget);
|
|
|
|
instanceOp.setModuleNameAttr(FlatSymbolRefAttr::get(context, newTarget));
|
|
} else {
|
|
replacer.replaceElementsIn(op);
|
|
}
|
|
});
|
|
}
|
|
|
|
/// Apply all required renames to the current module. This will update the
|
|
/// prefix map for any referenced module.
|
|
void PrefixModulesPass::renameModule(FModuleOp module) {
|
|
// If the module is annotated to have a prefix, it will be applied after the
|
|
// parent's prefix.
|
|
auto prefixInfo = getPrefixInfo(module);
|
|
auto innerPrefix = prefixInfo.prefix;
|
|
|
|
// Remove the annotation from the module.
|
|
AnnotationSet::removeAnnotations(module, prefixModulesAnnoClass);
|
|
|
|
// We only add the annotated prefix to the module name if it is inclusive.
|
|
auto oldName = module.getName().str();
|
|
std::string moduleName =
|
|
(prefixInfo.inclusive ? innerPrefix + oldName : oldName).str();
|
|
|
|
auto &prefixes = prefixMap[module.getName()];
|
|
|
|
// If there are no required prefixes of this module, then this module is a
|
|
// top-level module, and there is an implicit requirement that it has an empty
|
|
// prefix. This empty prefix will be applied to all modules instantiated by
|
|
// this module.
|
|
if (prefixes.empty())
|
|
prefixes.push_back("");
|
|
|
|
auto &firstPrefix = prefixes.front();
|
|
|
|
auto fixNLAsRootedAt = [&](StringAttr oldModName, StringAttr newModuleName) {
|
|
DenseSet<hw::HierPathOp> nlas;
|
|
nlaTable->getNLAsInModule(oldModName, nlas);
|
|
for (auto n : nlas)
|
|
if (n.root() == oldModName)
|
|
nlaTable->updateModuleInNLA(n, oldModName, newModuleName);
|
|
};
|
|
// Rename the module for each required prefix. This will clone the module
|
|
// once for each prefix but the first.
|
|
OpBuilder builder(module);
|
|
builder.setInsertionPointAfter(module);
|
|
auto oldModName = module.getNameAttr();
|
|
for (auto &outerPrefix : llvm::drop_begin(prefixes)) {
|
|
auto moduleClone = cast<FModuleOp>(builder.clone(*module));
|
|
std::string newModName = outerPrefix + moduleName;
|
|
auto newModNameAttr = StringAttr::get(module.getContext(), newModName);
|
|
moduleClone.setName(newModNameAttr);
|
|
// It is critical to add the new module to the NLATable, otherwise the
|
|
// rename operation would fail.
|
|
nlaTable->addModule(moduleClone);
|
|
fixNLAsRootedAt(oldModName, newModNameAttr);
|
|
// Each call to this function could invalidate the `prefixes` reference.
|
|
renameModuleBody((outerPrefix + innerPrefix).str(), oldName, moduleClone);
|
|
}
|
|
|
|
auto prefixFull = (firstPrefix + innerPrefix).str();
|
|
auto newModuleName = firstPrefix + moduleName;
|
|
auto newModuleNameAttr = StringAttr::get(module.getContext(), newModuleName);
|
|
|
|
// The first prefix renames the module in place. There is always at least 1
|
|
// prefix.
|
|
module.setName(newModuleNameAttr);
|
|
nlaTable->addModule(module);
|
|
fixNLAsRootedAt(oldModName, newModuleNameAttr);
|
|
renameModuleBody(prefixFull, oldName, module);
|
|
|
|
AnnotationSet annotations(module);
|
|
SmallVector<Annotation, 1> newAnnotations;
|
|
annotations.removeAnnotations([&](Annotation anno) {
|
|
if (anno.getClass() == dutAnnoClass) {
|
|
anno.setMember("prefix", builder.getStringAttr(prefixFull));
|
|
newAnnotations.push_back(anno);
|
|
return true;
|
|
}
|
|
|
|
// If this module contains a Grand Central interface, then also apply
|
|
// renames to that, but only if there are prefixes to apply.
|
|
if (anno.getClass() == companionAnnoClass)
|
|
interfacePrefixMap[anno.getMember<IntegerAttr>("id")] = prefixFull;
|
|
return false;
|
|
});
|
|
|
|
// If any annotations were updated, then update the annotations on the module.
|
|
if (!newAnnotations.empty()) {
|
|
annotations.addAnnotations(newAnnotations);
|
|
annotations.applyToOperation(module);
|
|
}
|
|
}
|
|
|
|
/// Apply prefixes from the `prefixMap` to an external module. No modifications
|
|
/// are made if there are no prefixes for this external module. If one prefix
|
|
/// exists, then the external module will be updated in place. If multiple
|
|
/// prefixes exist, then the original external module will be updated in place
|
|
/// and prefixes _after_ the first will cause the module to be cloned
|
|
/// ("duplicated" in Scala FIRRTL Compiler terminology). The logic of this
|
|
/// member function is the same as `renameModule` except that there is no module
|
|
/// body to recursively update.
|
|
void PrefixModulesPass::renameExtModule(FExtModuleOp extModule) {
|
|
// Lookup prefixes for this module. If none exist, bail out.
|
|
auto &prefixes = prefixMap[extModule.getName()];
|
|
if (prefixes.empty())
|
|
return;
|
|
|
|
OpBuilder builder(extModule);
|
|
builder.setInsertionPointAfter(extModule);
|
|
|
|
// Function to apply an outer prefix to an external module. If the module has
|
|
// an optional "defname" (a name that will be used to generate Verilog), also
|
|
// update the defname.
|
|
auto applyPrefixToNameAndDefName = [&](FExtModuleOp &extModule,
|
|
StringRef prefix) {
|
|
extModule.setName((prefix + extModule.getName()).str());
|
|
if (auto defname = extModule.getDefname())
|
|
extModule->setAttr("defname", builder.getStringAttr(prefix + *defname));
|
|
};
|
|
|
|
// Duplicate the external module if there is more than one prefix.
|
|
for (auto &prefix : llvm::drop_begin(prefixes)) {
|
|
auto duplicate = cast<FExtModuleOp>(builder.clone(*extModule));
|
|
applyPrefixToNameAndDefName(duplicate, prefix);
|
|
}
|
|
|
|
// Update the original module with a new prefix.
|
|
applyPrefixToNameAndDefName(extModule, prefixes.front());
|
|
}
|
|
|
|
/// Apply prefixes from the `prefixMap` to a memory module.
|
|
void PrefixModulesPass::renameMemModule(FMemModuleOp memModule) {
|
|
// Lookup prefixes for this module. If none exist, bail out.
|
|
auto &prefixes = prefixMap[memModule.getName()];
|
|
if (prefixes.empty())
|
|
return;
|
|
|
|
OpBuilder builder(memModule);
|
|
builder.setInsertionPointAfter(memModule);
|
|
|
|
// Duplicate the external module if there is more than one prefix.
|
|
auto originalName = memModule.getName();
|
|
for (auto &prefix : llvm::drop_begin(prefixes)) {
|
|
auto duplicate = cast<FMemModuleOp>(builder.clone(*memModule));
|
|
duplicate.setName((prefix + originalName).str());
|
|
removeDeadAnnotations(duplicate.getNameAttr(), duplicate);
|
|
}
|
|
|
|
// Update the original module with a new prefix.
|
|
memModule.setName((prefixes.front() + originalName).str());
|
|
removeDeadAnnotations(memModule.getNameAttr(), memModule);
|
|
}
|
|
|
|
/// Mutate circuit-level annotations to add prefix information to Grand Central
|
|
/// (SystemVerilog) interfaces. Add a "prefix" field to each interface
|
|
/// definition (an annotation with class "AugmentedBundleType") that holds the
|
|
/// prefix that was determined during runOnModule. It is assumed that this
|
|
/// field did not exist before.
|
|
void PrefixModulesPass::prefixGrandCentralInterfaces() {
|
|
// Early exit if no interfaces need prefixes.
|
|
if (interfacePrefixMap.empty())
|
|
return;
|
|
|
|
auto circuit = getOperation();
|
|
OpBuilder builder(circuit);
|
|
|
|
SmallVector<Attribute> newCircuitAnnotations;
|
|
for (auto anno : AnnotationSet(circuit)) {
|
|
// Only mutate this annotation if it is an AugmentedBundleType and
|
|
// interfacePrefixMap has prefix information for it.
|
|
StringRef prefix;
|
|
if (anno.isClass(augmentedBundleTypeClass)) {
|
|
if (auto id = anno.getMember<IntegerAttr>("id"))
|
|
prefix = interfacePrefixMap[id];
|
|
}
|
|
|
|
// Nothing to do. Copy the annotation.
|
|
if (prefix.empty()) {
|
|
newCircuitAnnotations.push_back(anno.getDict());
|
|
continue;
|
|
}
|
|
|
|
// Add a "prefix" field with the prefix for this interface. This is safe to
|
|
// put at the back and do a `getWithSorted` because the last field is
|
|
// conveniently called "name".
|
|
NamedAttrList newAnno(anno.getDict().getValue());
|
|
newAnno.append("prefix", builder.getStringAttr(prefix));
|
|
newCircuitAnnotations.push_back(
|
|
DictionaryAttr::getWithSorted(builder.getContext(), newAnno));
|
|
}
|
|
|
|
// Overwrite the old circuit annotation with the new one created here.
|
|
AnnotationSet(newCircuitAnnotations, builder.getContext())
|
|
.applyToOperation(circuit);
|
|
}
|
|
|
|
void PrefixModulesPass::runOnOperation() {
|
|
auto *context = &getContext();
|
|
instanceGraph = &getAnalysis<InstanceGraph>();
|
|
nlaTable = &getAnalysis<NLATable>();
|
|
auto circuitOp = getOperation();
|
|
|
|
// If the main module is prefixed, we have to update the CircuitOp.
|
|
auto mainModule = instanceGraph->getTopLevelModule();
|
|
auto prefix = getPrefix(mainModule);
|
|
if (!prefix.empty()) {
|
|
auto oldModName = mainModule.getModuleNameAttr();
|
|
auto newMainModuleName =
|
|
StringAttr::get(context, (prefix + circuitOp.getName()).str());
|
|
circuitOp.setNameAttr(newMainModuleName);
|
|
|
|
// Now update all the NLAs that have the top level module symbol.
|
|
nlaTable->renameModule(oldModName, newMainModuleName);
|
|
for (auto n : nlaTable->lookup(oldModName))
|
|
if (n.root() == oldModName)
|
|
nlaTable->updateModuleInNLA(n, oldModName, newMainModuleName);
|
|
}
|
|
|
|
// Walk all Modules in a top-down order. For each module, look at the list of
|
|
// required prefixes to be applied.
|
|
DenseSet<InstanceGraphNode *> visited;
|
|
for (auto *current : *instanceGraph) {
|
|
for (auto &node : llvm::inverse_post_order_ext(current, visited)) {
|
|
if (auto module = dyn_cast<FModuleOp>(*node->getModule()))
|
|
renameModule(module);
|
|
if (auto extModule = dyn_cast<FExtModuleOp>(*node->getModule()))
|
|
renameExtModule(extModule);
|
|
if (auto memModule = dyn_cast<FMemModuleOp>(*node->getModule()))
|
|
renameMemModule(memModule);
|
|
}
|
|
}
|
|
|
|
// Update any interface definitions if needed.
|
|
prefixGrandCentralInterfaces();
|
|
|
|
prefixMap.clear();
|
|
interfacePrefixMap.clear();
|
|
if (!anythingChanged)
|
|
markAllAnalysesPreserved();
|
|
}
|
|
|
|
std::unique_ptr<mlir::Pass> circt::firrtl::createPrefixModulesPass() {
|
|
return std::make_unique<PrefixModulesPass>();
|
|
}
|