21#include "mlir/IR/ImplicitLocOpBuilder.h"
22#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/BitVector.h"
24#include "llvm/ADT/DenseMap.h"
25#include "llvm/ADT/EquivalenceClasses.h"
26#include "llvm/ADT/PostOrderIterator.h"
27#include "llvm/Support/Debug.h"
29#define DEBUG_TYPE "firrtl-lower-xmr"
33#define GEN_PASS_DEF_LOWERXMR
34#include "circt/Dialect/FIRRTL/Passes.h.inc"
39using namespace firrtl;
40using hw::InnerRefAttr;
62 using NextNodeOnPath = std::optional<size_t>;
63 using SymOrIndexOp = PointerUnion<Attribute, Operation *>;
67[[maybe_unused]] llvm::raw_ostream &
operator<<(llvm::raw_ostream &os,
68 const XMRNode &node) {
70 if (
auto attr = dyn_cast<Attribute>(node.info))
71 os <<
"path=" << attr;
73 auto subOp = cast<RefSubOp>(cast<Operation *>(node.info));
74 os <<
"index=" << subOp.getIndex() <<
" (-> " << subOp.getType() <<
")";
76 os <<
", next=" << node.next <<
")";
85 ModuleState(FModuleOp &moduleOp) : body(moduleOp.
getBodyBlock()) {}
91 Value getOrCreateXMRRefOp(Type type, FlatSymbolRefAttr symbol,
92 StringAttr suffix, ImplicitLocOpBuilder &builder) {
94 auto it = xmrRefCache.find({type, symbol, suffix});
95 if (it != xmrRefCache.end())
96 return it->getSecond();
99 OpBuilder::InsertionGuard guard(builder);
100 if (xmrRefPoint.isSet())
101 builder.restoreInsertionPoint(xmrRefPoint);
103 builder.setInsertionPointToStart(body);
105 Value xmr = builder.create<XMRRefOp>(type, symbol, suffix);
106 xmrRefCache.insert({{type, symbol, suffix}, xmr});
108 xmrRefPoint = builder.saveInsertionPoint();
118 DenseMap<std::tuple<Type, SymbolRefAttr, StringAttr>, Value> xmrRefCache;
121 OpBuilder::InsertPoint xmrRefPoint;
133 llvm::EquivalenceClasses<Value, ValueComparator> eq;
137 SmallVector<RefResolveOp> resolveOps;
138 SmallVector<RefSubOp> indexingOps;
139 SmallVector<Operation *> forceAndReleaseOps;
142 auto transferFunc = [&](Operation *op) -> LogicalResult {
143 return TypeSwitch<Operation *, LogicalResult>(op)
144 .Case<RefSendOp>([&](RefSendOp send) {
147 Value xmrDef = send.getBase();
153 if (
auto verbExpr = xmrDef.getDefiningOp<VerbatimExprOp>())
154 if (verbExpr.getSymbolsAttr().empty() && verbExpr->hasOneUse()) {
159 auto inRef = InnerRefAttr();
170 ImplicitLocOpBuilder b(xmrDef.getLoc(), &getContext());
171 b.setInsertionPointAfterValue(xmrDef);
172 SmallString<32> opName;
173 auto nameKind = NameKindEnum::DroppableName;
179 opName = name +
"_probe";
180 nameKind = NameKindEnum::InterestingName;
181 }
else if (
auto *xmrDefOp = xmrDef.getDefiningOp()) {
184 if (
auto name = xmrDefOp->getAttrOfType<StringAttr>(
"name")) {
185 (Twine(name.strref()) +
"_probe").
toVector(opName);
186 nameKind = NameKindEnum::InterestingName;
189 xmrDef = b.create<NodeOp>(xmrDef, opName, nameKind).getResult();
197 .Case<RWProbeOp>([&](RWProbeOp rwprobe) {
203 .Case<MemOp>([&](MemOp mem) {
209 for (
const auto &res : llvm::enumerate(mem.getResults()))
210 if (isa<RefType>(mem.getResult(res.index()).getType())) {
222 .Case<FConnectLike>([&](FConnectLike connect) {
224 if (!isa<RefType>(connect.getSrc().getType()))
228 type_cast<RefType>(connect.getSrc().getType()).getType()))
245 .Case<RefSubOp>([&](RefSubOp op) -> LogicalResult {
251 indexingOps.push_back(op);
254 .Case<RefResolveOp>([&](RefResolveOp resolve) {
268 resolveOps.push_back(resolve);
271 .Case<RefCastOp>([&](RefCastOp op) {
277 .Case<Forceable>([&](Forceable op) {
279 if (type_isa<RefType>(op.getDataRaw().getType())) {
285 if (!op.isForceable() || op.getDataRef().use_empty() ||
292 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp,
293 RefReleaseInitialOp>([&](
auto op) {
294 forceAndReleaseOps.push_back(op);
297 .Default([&](
auto) {
return success(); });
300 SmallVector<FModuleOp> publicModules;
304 DenseSet<InstanceGraphNode *> visited;
305 for (
auto *root : instanceGraph) {
306 for (
auto *node : llvm::post_order_ext(root, visited)) {
307 auto module = dyn_cast<FModuleOp>(*node->getModule());
310 LLVM_DEBUG(llvm::dbgs() <<
"Traversing module:"
311 << module.getModuleNameAttr() <<
"\n");
315 if (module.isPublic())
316 publicModules.push_back(module);
318 auto result =
module.walk([&](Operation *op) {
319 if (transferFunc(op).failed())
320 return WalkResult::interrupt();
321 return WalkResult::advance();
324 if (result.wasInterrupted())
325 return signalPassFailure();
328 module.setLayersAttr(ArrayAttr::get(module.getContext(), {}));
336 while (!indexingOps.empty()) {
338 decltype(indexingOps) worklist;
339 worklist.swap(indexingOps);
341 for (
auto op : worklist) {
346 indexingOps.push_back(op);
352 if (worklist.size() == indexingOps.size()) {
353 auto op = worklist.front();
356 "indexing through probe of unknown origin (input probe?)")
357 .attachNote(op.getInput().getLoc())
358 .append(
"indexing through this reference");
359 return signalPassFailure();
364 size_t numPorts =
module.getNumPorts();
365 for (
size_t portNum = 0; portNum < numPorts; ++portNum)
366 if (isa<RefType>(module.getPortType(portNum))) {
381 llvm::dbgs() <<
"\n dataflow at leader::" << I->getData() <<
"\n =>";
386 llvm::dbgs() <<
"\n " << init;
388 llvm::dbgs() <<
"\n Done\n";
391 for (
auto refResolve : resolveOps)
393 return signalPassFailure();
394 for (
auto *op : forceAndReleaseOps)
396 return signalPassFailure();
397 for (
auto module : publicModules) {
399 return signalPassFailure();
419 auto modName = mod.getModuleName();
420 if (
auto ext = dyn_cast<FExtModuleOp>(*mod)) {
422 if (
auto defname = ext.getDefname(); defname && !defname->empty())
425 (Twine(
"ref_") + modName).
toVector(prefix);
431 const Twine &prefix,
bool backTick =
false) {
432 return StringAttr::get(&getContext(), Twine(backTick ?
"`" :
"") + prefix +
433 "_" + mod.getPortName(portIndex));
437 ImplicitLocOpBuilder builder,
438 mlir::FlatSymbolRefAttr &ref,
439 SmallString<128> &stringLeaf) {
440 assert(stringLeaf.empty());
442 auto remoteOpPath = getRemoteRefSend(refVal);
445 SmallVector<Attribute> refSendPath;
446 SmallVector<RefSubOp> indexing;
448 while (remoteOpPath) {
449 lastIndex = *remoteOpPath;
450 auto entr = refSendPathList[*remoteOpPath];
452 TypeSwitch<XMRNode::SymOrIndexOp>(entr.info)
453 .Case<Attribute>([&](
auto attr) {
457 refSendPath.push_back(attr);
460 [&](
auto *op) { indexing.push_back(cast<RefSubOp>(op)); });
461 remoteOpPath = entr.next;
463 auto iter = xmrPathSuffix.find(lastIndex);
467 if (iter != xmrPathSuffix.end()) {
468 if (!refSendPath.empty())
469 stringLeaf.append(
".");
470 stringLeaf.append(iter->getSecond());
473 assert(!(refSendPath.empty() && stringLeaf.empty()) &&
474 "nothing to index through");
487 for (
auto subOp : llvm::reverse(indexing)) {
488 TypeSwitch<FIRRTLBaseType>(subOp.getInput().getType().getType())
489 .Case<FVectorType, OpenVectorType>([&](
auto vecType) {
490 (Twine(
"[") + Twine(subOp.getIndex()) +
"]").
toVector(stringLeaf);
492 .Case<BundleType, OpenBundleType>([&](
auto bundleType) {
493 auto fieldName = bundleType.getElementName(subOp.getIndex());
494 stringLeaf.append({
".", fieldName});
498 if (!refSendPath.empty())
500 ref = FlatSymbolRefAttr::get(
501 getOrCreatePath(builder.getArrayAttr(refSendPath), builder)
508 ImplicitLocOpBuilder &builder,
509 FlatSymbolRefAttr &ref, StringAttr &xmrAttr) {
510 auto remoteOpPath = getRemoteRefSend(refVal);
514 SmallString<128> xmrString;
515 if (failed(resolveReferencePath(refVal, builder, ref, xmrString)))
518 xmrString.empty() ? StringAttr{} : builder.getStringAttr(xmrString);
525 return TypeSwitch<Operation *, LogicalResult>(op)
526 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp, RefReleaseInitialOp>(
529 auto destType = op.getDest().getType();
530 if (isZeroWidth(destType.getType())) {
535 ImplicitLocOpBuilder builder(op.getLoc(), op);
536 FlatSymbolRefAttr ref;
538 if (failed(resolveReference(op.getDest(), builder, ref, str)))
542 moduleStates.find(op->template getParentOfType<FModuleOp>())
544 .getOrCreateXMRRefOp(destType, ref, str, builder);
545 op.getDestMutable().assign(xmr);
548 .Default([](
auto *op) {
549 return op->emitError(
"unexpected operation kind");
556 if (resWidth.has_value() && *resWidth == 0) {
558 ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
559 auto zeroUintType = UIntType::get(builder.getContext(), 0);
560 auto zeroC = builder.createOrFold<BitCastOp>(
561 resolve.getType(), builder.create<ConstantOp>(
563 resolve.getResult().replaceAllUsesWith(zeroC);
567 FlatSymbolRefAttr ref;
569 ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
570 if (failed(resolveReference(resolve.getRef(), builder, ref, str)))
573 Value result = builder.create<XMRDerefOp>(resolve.getType(), ref, str);
574 resolve.getResult().replaceAllUsesWith(result);
579 if (refPortsToRemoveMap[op].size() < numPorts)
580 refPortsToRemoveMap[op].resize(numPorts);
581 refPortsToRemoveMap[op].set(index);
587 Operation *mod = inst.getReferencedModule(instanceGraph);
588 if (
auto extRefMod = dyn_cast<FExtModuleOp>(mod)) {
592 auto internalPaths = extRefMod.getInternalPaths();
593 auto numPorts = inst.getNumResults();
594 SmallString<128> circuitRefPrefix;
597 auto getPath = [&](
size_t portNo) {
601 cast<InternalPathAttr>(internalPaths->getValue()[portNo])
607 if (circuitRefPrefix.empty())
608 getRefABIPrefix(extRefMod, circuitRefPrefix);
610 return getRefABIMacroForPort(extRefMod, portNo, circuitRefPrefix,
true);
613 for (
const auto &res : llvm::enumerate(inst.getResults())) {
614 if (!isa<RefType>(inst.getResult(res.index()).getType()))
618 auto ind = addReachingSendsEntry(res.value(), inRef);
620 xmrPathSuffix[ind] = getPath(res.index());
622 setPortToRemove(inst, res.index(), numPorts);
623 setPortToRemove(extRefMod, res.index(), numPorts);
627 auto refMod = dyn_cast<FModuleOp>(mod);
628 bool multiplyInstantiated = !visitedModules.insert(refMod).second;
629 for (
size_t portNum = 0, numPorts = inst.getNumResults();
630 portNum < numPorts; ++portNum) {
631 auto instanceResult = inst.getResult(portNum);
632 if (!isa<RefType>(instanceResult.getType()))
635 return inst.emitOpError(
"cannot lower ext modules with RefType ports");
637 setPortToRemove(inst, portNum, numPorts);
639 if (instanceResult.use_empty() ||
640 isZeroWidth(type_cast<RefType>(instanceResult.getType()).getType()))
642 auto refModuleArg = refMod.getArgument(portNum);
643 if (inst.getPortDirection(portNum) == Direction::Out) {
647 auto remoteOpPath = getRemoteRefSend(refModuleArg);
659 if (multiplyInstantiated)
660 return refMod.emitOpError(
661 "multiply instantiated module with input RefType port '")
662 << refMod.getPortName(portNum) <<
"'";
663 dataFlowClasses->unionSets(
664 dataFlowClasses->getOrInsertLeaderValue(refModuleArg),
665 dataFlowClasses->getOrInsertLeaderValue(instanceResult));
672 auto *body = getOperation().getBodyBlock();
675 SmallString<128> circuitRefPrefix;
676 SmallVector<std::tuple<StringAttr, StringAttr, ArrayAttr>> ports;
678 ImplicitLocOpBuilder::atBlockBegin(module.getLoc(), body);
679 for (
size_t portIndex = 0, numPorts = module.getNumPorts();
680 portIndex != numPorts; ++portIndex) {
681 auto refType = type_dyn_cast<RefType>(module.getPortType(portIndex));
682 if (!refType || isZeroWidth(refType.getType()) ||
683 module.getPortDirection(portIndex) != Direction::Out)
686 cast<mlir::TypedValue<RefType>>(
module.getArgument(portIndex));
687 mlir::FlatSymbolRefAttr ref;
688 SmallString<128> stringLeaf;
689 if (failed(resolveReferencePath(portValue, declBuilder, ref, stringLeaf)))
692 SmallString<128> formatString;
694 formatString +=
"{{0}}";
695 formatString += stringLeaf;
699 if (circuitRefPrefix.empty())
700 getRefABIPrefix(module, circuitRefPrefix);
702 getRefABIMacroForPort(module, portIndex, circuitRefPrefix);
703 declBuilder.create<sv::MacroDeclOp>(macroName, ArrayAttr(), StringAttr());
704 ports.emplace_back(macroName, declBuilder.getStringAttr(formatString),
705 ref ? declBuilder.getArrayAttr({ref}) : ArrayAttr{});
714 auto fileBuilder = ImplicitLocOpBuilder(module.getLoc(), module);
715 fileBuilder.create<emit::FileOp>(circuitRefPrefix +
".sv", [&] {
716 for (
auto [macroName, formatString, symbols] : ports) {
717 fileBuilder.create<sv::MacroDefOp>(FlatSymbolRefAttr::get(macroName),
718 formatString, symbols);
727 return moduleNamespaces.try_emplace(module, module).first->second;
731 if (
auto arg = dyn_cast<BlockArgument>(val))
732 return ::getInnerRefTo(
733 cast<FModuleLike>(arg.getParentBlock()->getParentOp()),
736 return getModuleNamespace(mod);
742 return ::getInnerRefTo(op,
744 return getModuleNamespace(mod);
751 bool errorIfNotFound =
true) {
752 auto iter = dataflowAt.find(dataFlowClasses->getOrInsertLeaderValue(val));
753 if (iter != dataflowAt.end())
754 return iter->getSecond();
755 if (!errorIfNotFound)
759 if (BlockArgument arg = dyn_cast<BlockArgument>(val))
760 arg.getOwner()->getParentOp()->emitError(
761 "reference dataflow cannot be traced back to the remote read op "
763 << dyn_cast<FModuleOp>(arg.getOwner()->getParentOp())
764 .getPortName(arg.getArgNumber())
767 val.getDefiningOp()->emitOpError(
768 "reference dataflow cannot be traced back to the remote read op");
775 std::optional<size_t> continueFrom = std::nullopt) {
776 auto leader = dataFlowClasses->getOrInsertLeaderValue(atRefVal);
777 auto indx = refSendPathList.size();
778 dataflowAt[leader] = indx;
779 refSendPathList.push_back({info, continueFrom});
787 for (Operation *op : llvm::reverse(opsToRemove))
789 for (
auto iter : refPortsToRemoveMap)
790 if (
auto mod = dyn_cast<FModuleOp>(iter.getFirst()))
791 mod.erasePorts(iter.getSecond());
792 else if (
auto mod = dyn_cast<FExtModuleOp>(iter.getFirst()))
793 mod.erasePorts(iter.getSecond());
794 else if (
auto inst = dyn_cast<InstanceOp>(iter.getFirst())) {
795 ImplicitLocOpBuilder b(inst.getLoc(), inst);
796 inst.erasePorts(b, iter.getSecond());
798 }
else if (
auto mem = dyn_cast<MemOp>(iter.getFirst())) {
800 ImplicitLocOpBuilder builder(mem.getLoc(), mem);
801 SmallVector<Attribute, 4> resultNames;
802 SmallVector<Type, 4> resultTypes;
803 SmallVector<Attribute, 4> portAnnotations;
804 SmallVector<Value, 4> oldResults;
805 for (
const auto &res : llvm::enumerate(mem.getResults())) {
806 if (isa<RefType>(mem.getResult(res.index()).getType()))
808 resultNames.push_back(mem.getPortName(res.index()));
809 resultTypes.push_back(res.value().getType());
810 portAnnotations.push_back(mem.getPortAnnotation(res.index()));
811 oldResults.push_back(res.value());
813 auto newMem = builder.create<MemOp>(
814 resultTypes, mem.getReadLatency(), mem.getWriteLatency(),
815 mem.getDepth(), RUWAttr::Undefined,
816 builder.getArrayAttr(resultNames), mem.getNameAttr(),
817 mem.getNameKind(), mem.getAnnotations(),
818 builder.getArrayAttr(portAnnotations), mem.getInnerSymAttr(),
819 mem.getInitAttr(), mem.getPrefixAttr());
820 for (
const auto &res : llvm::enumerate(oldResults))
821 res.value().replaceAllUsesWith(newMem.getResult(res.index()));
825 refPortsToRemoveMap.clear();
827 refSendPathList.clear();
828 moduleStates.clear();
836 ImplicitLocOpBuilder &builder) {
837 assert(pathArray && !pathArray.empty());
839 auto pathIter = pathCache.find(pathArray);
840 if (pathIter != pathCache.end())
841 return pathIter->second;
844 OpBuilder::InsertionGuard guard(builder);
848 if (pathInsertPoint.isSet())
849 builder.restoreInsertionPoint(pathInsertPoint);
851 builder.setInsertionPointToStart(getOperation().
getBodyBlock());
854 hw::HierPathOp path =
857 builder.create<hw::HierPathOp>(
858 circuitNamespace->newName(
"xmrPath"), pathArray)})
860 path.setVisibility(SymbolTable::Visibility::Private);
864 pathInsertPoint = builder.saveInsertionPoint();
894 return lhs.getImpl() < rhs.getImpl();
915 OpBuilder::InsertPoint pathInsertPoint = {};
922 return std::make_unique<LowerXMRPass>();
assert(baseType &&"element must be base type")
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
static Block * getBodyBlock(FModuleLike mod)
LogicalResult resolveReference(mlir::TypedValue< RefType > refVal, ImplicitLocOpBuilder &builder, FlatSymbolRefAttr &ref, StringAttr &xmrAttr)
DenseMap< Operation *, hw::InnerSymbolNamespace > moduleNamespaces
Cached module namespaces.
DenseMap< size_t, SmallString< 128 > > xmrPathSuffix
Record the internal path to an external module or a memory.
InnerRefAttr getInnerRefTo(Value val)
size_t addReachingSendsEntry(Value atRefVal, XMRNode::SymOrIndexOp info, std::optional< size_t > continueFrom=std::nullopt)
DenseMap< FModuleOp, ModuleState > moduleStates
Per-module helpers for creating operations within modules.
hw::HierPathOp getOrCreatePath(ArrayAttr pathArray, ImplicitLocOpBuilder &builder)
Return a HierPathOp for the provided pathArray.
LogicalResult resolveReferencePath(mlir::TypedValue< RefType > refVal, ImplicitLocOpBuilder builder, mlir::FlatSymbolRefAttr &ref, SmallString< 128 > &stringLeaf)
DenseMap< Value, size_t > dataflowAt
Map of a reference value to an entry into refSendPathList.
void setPortToRemove(Operation *op, size_t index, size_t numPorts)
hw::InnerSymbolNamespace & getModuleNamespace(FModuleLike module)
Get the cached namespace for a module.
llvm::EquivalenceClasses< Value, ValueComparator > * dataFlowClasses
void markForRemoval(Operation *op)
LogicalResult handlePublicModuleRefPorts(FModuleOp module)
void getRefABIPrefix(FModuleLike mod, SmallVectorImpl< char > &prefix)
Generate the ABI ref_<module> prefix string into prefix.
void runOnOperation() override
DenseMap< Attribute, hw::HierPathOp > pathCache
A cache of already created HierPathOps.
LogicalResult handleRefResolve(RefResolveOp resolve)
DenseMap< Operation *, llvm::BitVector > refPortsToRemoveMap
SmallVector< XMRNode > refSendPathList
refSendPathList is used to construct a path to the RefSendOp.
LogicalResult handleInstanceOp(InstanceOp inst, InstanceGraph &instanceGraph)
LogicalResult handleForceReleaseOp(Operation *op)
std::optional< size_t > getRemoteRefSend(Value val, bool errorIfNotFound=true)
DenseSet< Operation * > visitedModules
InnerRefAttr getInnerRefTo(Operation *op)
OpBuilder::InsertPoint pathInsertPoint
The insertion point where the pass inserts HierPathOps.
StringAttr getRefABIMacroForPort(FModuleLike mod, size_t portIndex, const Twine &prefix, bool backTick=false)
Get full macro name as StringAttr for the specified ref port.
CircuitNamespace * circuitNamespace
bool isZeroWidth(FIRRTLBaseType t)
SmallVector< Operation * > opsToRemove
RefResolve, RefSend, and Connects involving them that will be removed.
void clear()
Empty the namespace.
int32_t getBitWidthOrSentinel()
If this is an IntType, AnalogType, or sugar type for a single bit (Clock, Reset, etc) then return the...
This graph tracks modules and where they are instantiated.
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
std::unique_ptr< mlir::Pass > createLowerXMRPass()
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
IntegerAttr getIntZerosAttr(Type type)
Utility for generating a constant zero attribute.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::EquivalenceClasses wants comparable elements.
bool operator()(const Value &lhs, const Value &rhs) const
The namespace of a CircuitOp, generally inhabited by modules.