22#include "mlir/IR/ImplicitLocOpBuilder.h"
23#include "mlir/Pass/Pass.h"
24#include "llvm/ADT/BitVector.h"
25#include "llvm/ADT/DenseMap.h"
26#include "llvm/ADT/EquivalenceClasses.h"
27#include "llvm/ADT/PostOrderIterator.h"
28#include "llvm/Support/Debug.h"
30#define DEBUG_TYPE "firrtl-lower-xmr"
34#define GEN_PASS_DEF_LOWERXMR
35#include "circt/Dialect/FIRRTL/Passes.h.inc"
40using namespace firrtl;
41using hw::InnerRefAttr;
63 using NextNodeOnPath = std::optional<size_t>;
64 using SymOrIndexOp = PointerUnion<Attribute, Operation *>;
68[[maybe_unused]] llvm::raw_ostream &
operator<<(llvm::raw_ostream &os,
69 const XMRNode &node) {
71 if (
auto attr = dyn_cast<Attribute>(node.info))
72 os <<
"path=" << attr;
74 auto subOp = cast<RefSubOp>(cast<Operation *>(node.info));
75 os <<
"index=" << subOp.getIndex() <<
" (-> " << subOp.getType() <<
")";
77 os <<
", next=" << node.next <<
")";
86 ModuleState(FModuleOp &moduleOp) : body(moduleOp.
getBodyBlock()) {}
92 Value getOrCreateXMRRefOp(Type type, FlatSymbolRefAttr symbol,
93 StringAttr suffix, ImplicitLocOpBuilder &builder) {
95 auto it = xmrRefCache.find({type, symbol, suffix});
96 if (it != xmrRefCache.end())
97 return it->getSecond();
100 OpBuilder::InsertionGuard guard(builder);
101 if (xmrRefPoint.isSet())
102 builder.restoreInsertionPoint(xmrRefPoint);
104 builder.setInsertionPointToStart(body);
106 Value xmr = XMRRefOp::create(builder, type, symbol, suffix);
107 xmrRefCache.insert({{type, symbol, suffix}, xmr});
109 xmrRefPoint = builder.saveInsertionPoint();
119 DenseMap<std::tuple<Type, SymbolRefAttr, StringAttr>, Value> xmrRefCache;
122 OpBuilder::InsertPoint xmrRefPoint;
135 &ns, OpBuilder::InsertPoint(getOperation().
getBodyBlock(),
139 llvm::EquivalenceClasses<Value> eq;
143 SmallVector<RefResolveOp> resolveOps;
144 SmallVector<RefSubOp> indexingOps;
145 SmallVector<Operation *> forceAndReleaseOps;
148 auto transferFunc = [&](Operation *op) -> LogicalResult {
149 return TypeSwitch<Operation *, LogicalResult>(op)
150 .Case<RefSendOp>([&](RefSendOp send) {
153 Value xmrDef = send.getBase();
159 if (
auto verbExpr = xmrDef.getDefiningOp<VerbatimExprOp>())
160 if (verbExpr.getSymbolsAttr().empty() && verbExpr->hasOneUse()) {
165 auto inRef = InnerRefAttr();
175 if (isa<BlockArgument>(xmrDef)) {
182 auto *xmrDefOp = xmrDef.getDefiningOp();
189 if (
auto innerSymOp =
190 dyn_cast_or_null<hw::InnerSymbolOpInterface>(xmrDefOp))
191 if (innerSymOp.getTargetResultIndex()) {
201 ImplicitLocOpBuilder b(xmrDef.getLoc(), &getContext());
202 b.setInsertionPointAfterValue(xmrDef);
203 SmallString<32> opName;
204 auto nameKind = NameKindEnum::DroppableName;
210 opName = name +
"_probe";
211 nameKind = NameKindEnum::InterestingName;
212 }
else if (xmrDefOp) {
215 if (
auto name = xmrDefOp->getAttrOfType<StringAttr>(
"name")) {
216 (Twine(name.strref()) +
"_probe").
toVector(opName);
217 nameKind = NameKindEnum::InterestingName;
220 auto node = NodeOp::create(b, xmrDef, opName, nameKind);
221 auto newValue = node.getResult();
225 xmrDef.replaceUsesWithIf(newValue, [&](OpOperand &operand) {
226 if (operand.getOwner() == node.getOperation())
228 if (isa<FConnectLike>(operand.getOwner()) &&
229 operand.getOperandNumber() == 0)
241 .Case<RWProbeOp>([&](RWProbeOp rwprobe) {
247 .Case<MemOp>([&](MemOp mem) {
253 for (
const auto &res : llvm::enumerate(mem.getResults()))
254 if (isa<RefType>(mem.getResult(res.index()).getType())) {
266 .Case<FConnectLike>([&](FConnectLike connect) {
268 if (!isa<RefType>(connect.getSrc().getType()))
272 type_cast<RefType>(connect.getSrc().getType()).getType()))
289 .Case<RefSubOp>([&](RefSubOp op) -> LogicalResult {
295 indexingOps.push_back(op);
298 .Case<RefResolveOp>([&](RefResolveOp
resolve) {
315 .Case<RefCastOp>([&](RefCastOp op) {
321 .Case<Forceable>([&](Forceable op) {
323 if (type_isa<RefType>(op.getDataRaw().getType())) {
329 if (!op.isForceable() || op.getDataRef().use_empty() ||
336 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp,
337 RefReleaseInitialOp>([&](
auto op) {
338 forceAndReleaseOps.push_back(op);
341 .Default([&](
auto) {
return success(); });
344 SmallVector<FModuleOp> publicModules;
347 auto result = instanceGraph.
walkPostOrder([&](
auto &node) -> LogicalResult {
348 auto module = dyn_cast<FModuleOp>(*node.getModule());
351 LLVM_DEBUG(llvm::dbgs()
352 <<
"Traversing module:" << module.getModuleNameAttr() <<
"\n");
356 if (module.isPublic())
357 publicModules.push_back(module);
359 auto result =
module.walk([&](Operation *op) {
360 if (transferFunc(op).failed())
361 return WalkResult::interrupt();
362 return WalkResult::advance();
365 if (result.wasInterrupted())
374 while (!indexingOps.empty()) {
376 decltype(indexingOps) worklist;
377 worklist.swap(indexingOps);
379 for (
auto op : worklist) {
384 indexingOps.push_back(op);
390 if (worklist.size() == indexingOps.size()) {
391 auto op = worklist.front();
394 "indexing through probe of unknown origin (input probe?)")
395 .attachNote(op.getInput().getLoc())
396 .append(
"indexing through this reference");
402 size_t numPorts =
module.getNumPorts();
403 for (
size_t portNum = 0; portNum < numPorts; ++portNum)
404 if (isa<RefType>(module.getPortType(portNum)))
410 return signalPassFailure();
419 llvm::dbgs() <<
"\n dataflow at leader::" << I->getData() <<
"\n =>";
424 llvm::dbgs() <<
"\n " << init;
426 llvm::dbgs() <<
"\n Done\n";
429 for (
auto refResolve : resolveOps)
431 return signalPassFailure();
432 for (
auto *op : forceAndReleaseOps)
434 return signalPassFailure();
435 for (
auto module : publicModules) {
437 return signalPassFailure();
456 auto modName = mod.getModuleName();
457 if (
auto ext = dyn_cast<FExtModuleOp>(*mod))
458 modName = ext.getExtModuleName();
459 (Twine(
"ref_") + modName).
toVector(prefix);
465 const Twine &prefix,
bool backTick =
false) {
466 return StringAttr::get(&getContext(), Twine(backTick ?
"`" :
"") + prefix +
467 "_" + mod.getPortName(portIndex));
471 ImplicitLocOpBuilder builder,
472 mlir::FlatSymbolRefAttr &ref,
473 SmallString<128> &stringLeaf) {
474 assert(stringLeaf.empty());
476 auto remoteOpPath = getRemoteRefSend(refVal);
479 SmallVector<Attribute> refSendPath;
480 SmallVector<RefSubOp> indexing;
482 while (remoteOpPath) {
483 lastIndex = *remoteOpPath;
484 auto entr = refSendPathList[*remoteOpPath];
486 TypeSwitch<XMRNode::SymOrIndexOp>(entr.info)
487 .Case<Attribute>([&](
auto attr) {
491 refSendPath.push_back(attr);
494 [&](
auto *op) { indexing.push_back(cast<RefSubOp>(op)); });
495 remoteOpPath = entr.next;
497 auto iter = xmrPathSuffix.find(lastIndex);
501 if (iter != xmrPathSuffix.end()) {
502 if (!refSendPath.empty())
503 stringLeaf.append(
".");
504 stringLeaf.append(iter->getSecond());
507 assert(!(refSendPath.empty() && stringLeaf.empty()) &&
508 "nothing to index through");
521 for (
auto subOp : llvm::reverse(indexing)) {
522 TypeSwitch<FIRRTLBaseType>(subOp.getInput().getType().getType())
523 .Case<FVectorType, OpenVectorType>([&](
auto vecType) {
524 (Twine(
"[") + Twine(subOp.getIndex()) +
"]").
toVector(stringLeaf);
526 .Case<BundleType, OpenBundleType>([&](
auto bundleType) {
527 auto fieldName = bundleType.getElementName(subOp.getIndex());
528 stringLeaf.append({
".", fieldName});
532 if (!refSendPath.empty())
534 ref = FlatSymbolRefAttr::get(
536 ->getOrCreatePath(builder.getArrayAttr(refSendPath),
544 ImplicitLocOpBuilder &builder,
545 FlatSymbolRefAttr &ref, StringAttr &xmrAttr) {
546 auto remoteOpPath = getRemoteRefSend(refVal);
550 SmallString<128> xmrString;
551 if (failed(resolveReferencePath(refVal, builder, ref, xmrString)))
554 xmrString.empty() ? StringAttr{} : builder.getStringAttr(xmrString);
561 return TypeSwitch<Operation *, LogicalResult>(op)
562 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp, RefReleaseInitialOp>(
565 auto destType = op.getDest().getType();
566 if (isZeroWidth(destType.getType())) {
571 ImplicitLocOpBuilder builder(op.getLoc(), op);
572 FlatSymbolRefAttr ref;
574 if (failed(resolveReference(op.getDest(), builder, ref, str)))
578 moduleStates.find(op->template getParentOfType<FModuleOp>())
580 .getOrCreateXMRRefOp(destType, ref, str, builder);
581 op.getDestMutable().assign(xmr);
584 .Default([](
auto *op) {
585 return op->emitError(
"unexpected operation kind");
592 if (resWidth.has_value() && *resWidth == 0) {
595 auto zeroUintType = UIntType::get(builder.getContext(), 0);
596 auto zeroC = builder.createOrFold<BitCastOp>(
597 resolve.getType(), ConstantOp::create(builder, zeroUintType,
599 resolve.getResult().replaceAllUsesWith(zeroC);
603 FlatSymbolRefAttr ref;
606 if (failed(resolveReference(
resolve.getRef(), builder, ref, str)))
609 Value result = XMRDerefOp::create(builder,
resolve.getType(), ref, str);
610 resolve.getResult().replaceAllUsesWith(result);
615 if (refPortsToRemoveMap[op].size() < numPorts)
616 refPortsToRemoveMap[op].resize(numPorts);
617 refPortsToRemoveMap[op].set(index);
623 Operation *mod = inst.getReferencedModule(instanceGraph);
624 if (
auto extRefMod = dyn_cast<FExtModuleOp>(mod)) {
625 auto numPorts = inst.getNumResults();
626 SmallString<128> circuitRefPrefix;
629 auto getPath = [&](
size_t portNo) {
632 if (circuitRefPrefix.empty())
633 getRefABIPrefix(extRefMod, circuitRefPrefix);
635 return getRefABIMacroForPort(extRefMod, portNo, circuitRefPrefix,
true);
638 for (
const auto &res : llvm::enumerate(inst.getResults())) {
639 if (!isa<RefType>(inst.getResult(res.index()).getType()))
643 auto ind = addReachingSendsEntry(res.value(), inRef);
645 xmrPathSuffix[ind] = getPath(res.index());
647 setPortToRemove(inst, res.index(), numPorts);
648 setPortToRemove(extRefMod, res.index(), numPorts);
652 auto refMod = dyn_cast<FModuleOp>(mod);
653 bool multiplyInstantiated = !visitedModules.insert(refMod).second;
654 for (
size_t portNum = 0, numPorts = inst.getNumResults();
655 portNum < numPorts; ++portNum) {
656 auto instanceResult = inst.getResult(portNum);
657 if (!isa<RefType>(instanceResult.getType()))
660 return inst.emitOpError(
"cannot lower ext modules with RefType ports");
662 setPortToRemove(inst, portNum, numPorts);
664 if (instanceResult.use_empty() ||
665 isZeroWidth(type_cast<RefType>(instanceResult.getType()).getType()))
667 auto refModuleArg = refMod.getArgument(portNum);
668 if (inst.getPortDirection(portNum) == Direction::Out) {
672 auto remoteOpPath = getRemoteRefSend(refModuleArg);
684 if (multiplyInstantiated)
685 return refMod.emitOpError(
686 "multiply instantiated module with input RefType port '")
687 << refMod.getPortName(portNum) <<
"'";
688 dataFlowClasses->unionSets(
689 dataFlowClasses->getOrInsertLeaderValue(refModuleArg),
690 dataFlowClasses->getOrInsertLeaderValue(instanceResult));
697 auto *body = getOperation().getBodyBlock();
700 SmallString<128> circuitRefPrefix;
701 SmallVector<std::tuple<StringAttr, StringAttr, ArrayAttr>> ports;
703 ImplicitLocOpBuilder::atBlockBegin(module.getLoc(), body);
704 for (
size_t portIndex = 0, numPorts = module.getNumPorts();
705 portIndex != numPorts; ++portIndex) {
706 auto refType = type_dyn_cast<RefType>(module.getPortType(portIndex));
707 if (!refType || isZeroWidth(refType.getType()) ||
708 module.getPortDirection(portIndex) != Direction::Out)
711 cast<mlir::TypedValue<RefType>>(
module.getArgument(portIndex));
712 mlir::FlatSymbolRefAttr ref;
713 SmallString<128> stringLeaf;
714 if (failed(resolveReferencePath(portValue, declBuilder, ref, stringLeaf)))
717 SmallString<128> formatString;
719 formatString +=
"{{0}}";
720 formatString += stringLeaf;
724 if (circuitRefPrefix.empty())
725 getRefABIPrefix(module, circuitRefPrefix);
727 getRefABIMacroForPort(module, portIndex, circuitRefPrefix);
728 sv::MacroDeclOp::create(declBuilder, macroName, ArrayAttr(),
730 ports.emplace_back(macroName, declBuilder.getStringAttr(formatString),
731 ref ? declBuilder.getArrayAttr({ref}) : ArrayAttr{});
740 auto fileBuilder = ImplicitLocOpBuilder(module.getLoc(), module);
741 emit::FileOp::create(fileBuilder, circuitRefPrefix +
".sv", [&] {
742 for (
auto [macroName, formatString, symbols] : ports) {
743 sv::MacroDefOp::create(fileBuilder, FlatSymbolRefAttr::get(macroName),
744 formatString, symbols);
753 return moduleNamespaces.try_emplace(module, module).first->second;
757 if (
auto arg = dyn_cast<BlockArgument>(val))
758 return ::getInnerRefTo(
759 cast<FModuleLike>(arg.getParentBlock()->getParentOp()),
762 return getModuleNamespace(mod);
768 return ::getInnerRefTo(op,
770 return getModuleNamespace(mod);
777 bool errorIfNotFound =
true) {
778 auto iter = dataflowAt.find(dataFlowClasses->getOrInsertLeaderValue(val));
779 if (iter != dataflowAt.end())
780 return iter->getSecond();
781 if (!errorIfNotFound)
785 if (BlockArgument arg = dyn_cast<BlockArgument>(val))
786 arg.getOwner()->getParentOp()->emitError(
787 "reference dataflow cannot be traced back to the remote read op "
789 << dyn_cast<FModuleOp>(arg.getOwner()->getParentOp())
790 .getPortName(arg.getArgNumber())
793 val.getDefiningOp()->emitOpError(
794 "reference dataflow cannot be traced back to the remote read op");
801 std::optional<size_t> continueFrom = std::nullopt) {
802 auto leader = dataFlowClasses->getOrInsertLeaderValue(atRefVal);
803 auto indx = refSendPathList.size();
804 dataflowAt[leader] = indx;
805 refSendPathList.push_back({info, continueFrom});
813 for (Operation *op : llvm::reverse(opsToRemove))
815 for (
auto iter : refPortsToRemoveMap)
816 if (
auto mod = dyn_cast<FModuleOp>(iter.getFirst()))
817 mod.erasePorts(iter.getSecond());
818 else if (
auto mod = dyn_cast<FExtModuleOp>(iter.getFirst()))
819 mod.erasePorts(iter.getSecond());
820 else if (
auto inst = dyn_cast<InstanceOp>(iter.getFirst())) {
821 inst.cloneWithErasedPortsAndReplaceUses(iter.getSecond());
823 }
else if (
auto mem = dyn_cast<MemOp>(iter.getFirst())) {
825 ImplicitLocOpBuilder builder(mem.getLoc(), mem);
826 SmallVector<Attribute, 4> resultNames;
827 SmallVector<Type, 4> resultTypes;
828 SmallVector<Attribute, 4> portAnnotations;
829 SmallVector<Value, 4> oldResults;
830 for (
const auto &res : llvm::enumerate(mem.getResults())) {
831 if (isa<RefType>(mem.getResult(res.index()).getType()))
833 resultNames.push_back(mem.getPortNameAttr(res.index()));
834 resultTypes.push_back(res.value().getType());
835 portAnnotations.push_back(mem.getPortAnnotation(res.index()));
836 oldResults.push_back(res.value());
838 auto newMem = MemOp::create(
839 builder, resultTypes, mem.getReadLatency(), mem.getWriteLatency(),
840 mem.getDepth(), RUWBehavior::Undefined,
841 builder.getArrayAttr(resultNames), mem.getNameAttr(),
842 mem.getNameKind(), mem.getAnnotations(),
843 builder.getArrayAttr(portAnnotations), mem.getInnerSymAttr(),
844 mem.getInitAttr(), mem.getPrefixAttr());
845 for (
const auto &res : llvm::enumerate(oldResults))
846 res.value().replaceAllUsesWith(newMem.getResult(res.index()));
850 refPortsToRemoveMap.clear();
852 refSendPathList.clear();
853 moduleStates.clear();
assert(baseType &&"element must be base type")
static mlir::Operation * resolve(Context &context, mlir::SymbolRefAttr sym)
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.
llvm::EquivalenceClasses< Value > * dataFlowClasses
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.
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.
void markForRemoval(Operation *op)
hw::HierPathCache * hierPathCache
Utility to create HerPathOps at a predefined location in the circuit.
LogicalResult handlePublicModuleRefPorts(FModuleOp module)
void getRefABIPrefix(FModuleLike mod, SmallVectorImpl< char > &prefix)
Generate the ABI ref_<module> prefix string into prefix.
void runOnOperation() override
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)
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.
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.
decltype(auto) walkPostOrder(Fn &&fn)
Perform a post-order walk across the modules.
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::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.
The namespace of a CircuitOp, generally inhabited by modules.