21 #include "mlir/IR/BuiltinOps.h"
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"
39 using namespace circt;
40 using namespace firrtl;
41 using 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 <<
")";
88 circuitNamespace = &ns;
90 llvm::EquivalenceClasses<Value, ValueComparator> eq;
91 dataFlowClasses = &eq;
94 SmallVector<RefResolveOp> resolveOps;
95 SmallVector<RefSubOp> indexingOps;
96 SmallVector<Operation *> forceAndReleaseOps;
99 auto transferFunc = [&](Operation &op) -> LogicalResult {
100 return TypeSwitch<Operation *, LogicalResult>(&op)
101 .Case<RefSendOp>([&](RefSendOp send) {
104 Value xmrDef = send.getBase();
105 if (isZeroWidth(send.getType().getType())) {
106 markForRemoval(send);
110 if (
auto verbExpr = xmrDef.getDefiningOp<VerbatimExprOp>())
111 if (verbExpr.getSymbolsAttr().empty() && verbExpr->hasOneUse()) {
116 auto inRef = InnerRefAttr();
117 auto ind = addReachingSendsEntry(send.getResult(), inRef);
118 xmrPathSuffix[ind] = verbExpr.getText();
119 markForRemoval(verbExpr);
120 markForRemoval(send);
127 ImplicitLocOpBuilder b(xmrDef.getLoc(), &getContext());
128 b.setInsertionPointAfterValue(xmrDef);
129 SmallString<32> opName;
130 auto nameKind = NameKindEnum::DroppableName;
136 opName = name +
"_probe";
137 nameKind = NameKindEnum::InterestingName;
138 }
else if (
auto *xmrDefOp = xmrDef.getDefiningOp()) {
141 if (
auto name = xmrDefOp->getAttrOfType<StringAttr>(
"name")) {
142 (Twine(name.strref()) +
"_probe").
toVector(opName);
143 nameKind = NameKindEnum::InterestingName;
146 xmrDef = b.create<NodeOp>(xmrDef, opName, nameKind).getResult();
150 addReachingSendsEntry(send.getResult(),
getInnerRefTo(xmrDef));
151 markForRemoval(send);
154 .Case<RWProbeOp>([&](RWProbeOp rwprobe) {
155 if (!isZeroWidth(rwprobe.getType().getType()))
156 addReachingSendsEntry(rwprobe.getResult(), rwprobe.getTarget());
157 markForRemoval(rwprobe);
160 .Case<MemOp>([&](MemOp mem) {
166 for (
const auto &res : llvm::enumerate(mem.getResults()))
167 if (isa<RefType>(mem.getResult(res.index()).getType())) {
169 auto ind = addReachingSendsEntry(res.value(), inRef);
170 xmrPathSuffix[ind] =
"Memory";
173 refPortsToRemoveMap[mem].resize(1);
178 [&](
auto inst) {
return handleInstanceOp(inst, instanceGraph); })
179 .Case<FConnectLike>([&](FConnectLike
connect) {
181 if (!isa<RefType>(
connect.getSrc().getType()))
185 type_cast<RefType>(
connect.getSrc().getType()).getType()))
202 .Case<RefSubOp>([&](RefSubOp op) -> LogicalResult {
204 if (isZeroWidth(op.getType().getType()))
208 indexingOps.push_back(op);
211 .Case<RefResolveOp>([&](RefResolveOp resolve) {
222 markForRemoval(resolve);
223 if (!isZeroWidth(resolve.getType()))
224 dataFlowClasses->unionSets(resolve.getRef(), resolve.getResult());
225 resolveOps.push_back(resolve);
228 .Case<RefCastOp>([&](RefCastOp op) {
230 if (!isZeroWidth(op.getType().getType()))
231 dataFlowClasses->unionSets(op.getInput(), op.getResult());
234 .Case<Forceable>([&](Forceable op) {
236 if (type_isa<RefType>(op.getDataRaw().getType())) {
242 if (!op.isForceable() || op.getDataRef().use_empty() ||
243 isZeroWidth(op.getDataType()))
249 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp,
250 RefReleaseInitialOp>([&](
auto op) {
251 forceAndReleaseOps.push_back(op);
254 .Default([&](
auto) {
return success(); });
257 SmallVector<FModuleOp> publicModules;
260 for (
auto node : llvm::post_order(&instanceGraph)) {
261 auto module = dyn_cast<FModuleOp>(*node->getModule());
264 LLVM_DEBUG(llvm::dbgs()
265 <<
"Traversing module:" << module.getModuleNameAttr() <<
"\n");
267 if (module.isPublic())
268 publicModules.push_back(module);
270 for (Operation &op : module.getBodyBlock()->getOperations())
271 if (transferFunc(op).failed())
272 return signalPassFailure();
283 while (!indexingOps.empty()) {
285 decltype(indexingOps) worklist;
286 worklist.swap(indexingOps);
288 for (
auto op : worklist) {
290 getRemoteRefSend(op.getInput(),
false);
293 indexingOps.push_back(op);
295 addReachingSendsEntry(op.getResult(), op.getOperation(),
299 if (worklist.size() == indexingOps.size()) {
300 auto op = worklist.front();
301 getRemoteRefSend(op.getInput());
303 "indexing through probe of unknown origin (input probe?)")
304 .attachNote(op.getInput().getLoc())
305 .append(
"indexing through this reference");
306 return signalPassFailure();
311 size_t numPorts = module.getNumPorts();
312 for (
size_t portNum = 0; portNum < numPorts; ++portNum)
313 if (isa<RefType>(module.getPortType(portNum))) {
314 setPortToRemove(module, portNum, numPorts);
319 for (
auto I = dataFlowClasses->begin(), E = dataFlowClasses->end();
324 llvm::interleave(llvm::make_range(dataFlowClasses->member_begin(I),
325 dataFlowClasses->member_end()),
327 llvm::dbgs() <<
"\n dataflow at leader::" << I->getData() <<
"\n =>";
328 auto iter = dataflowAt.find(I->getData());
329 if (iter != dataflowAt.end()) {
330 for (auto init = refSendPathList[iter->getSecond()]; init.next;
331 init = refSendPathList[*init.next])
332 llvm::dbgs() <<
"\n " << init;
334 llvm::dbgs() <<
"\n Done\n";
337 for (
auto refResolve : resolveOps)
338 if (handleRefResolve(refResolve).failed())
339 return signalPassFailure();
340 for (
auto *op : forceAndReleaseOps)
341 if (failed(handleForceReleaseOp(op)))
342 return signalPassFailure();
343 for (
auto module : publicModules) {
344 if (failed(handlePublicModuleRefPorts(module)))
345 return signalPassFailure();
350 moduleNamespaces.clear();
351 visitedModules.clear();
353 refSendPathList.clear();
354 dataFlowClasses =
nullptr;
355 refPortsToRemoveMap.clear();
357 xmrPathSuffix.clear();
358 circuitNamespace =
nullptr;
360 pathInsertPoint = {};
365 auto modName = mod.getModuleName();
366 if (
auto ext = dyn_cast<FExtModuleOp>(*mod)) {
368 if (
auto defname = ext.getDefname(); defname && !defname->empty())
371 (Twine(
"ref_") + modName).
toVector(prefix);
377 const Twine &prefix,
bool backTick =
false) {
378 return StringAttr::get(&getContext(), Twine(backTick ?
"`" :
"") + prefix +
379 "_" + mod.getPortName(portIndex));
383 ImplicitLocOpBuilder builder,
384 mlir::FlatSymbolRefAttr &ref,
385 SmallString<128> &stringLeaf) {
386 assert(stringLeaf.empty());
388 auto remoteOpPath = getRemoteRefSend(refVal);
391 SmallVector<Attribute> refSendPath;
392 SmallVector<RefSubOp> indexing;
394 while (remoteOpPath) {
395 lastIndex = *remoteOpPath;
396 auto entr = refSendPathList[*remoteOpPath];
398 TypeSwitch<XMRNode::SymOrIndexOp>(entr.info)
399 .Case<Attribute>([&](
auto attr) {
403 refSendPath.push_back(attr);
406 [&](
auto *op) { indexing.push_back(cast<RefSubOp>(op)); });
407 remoteOpPath = entr.next;
409 auto iter = xmrPathSuffix.find(lastIndex);
413 if (iter != xmrPathSuffix.end()) {
414 if (!refSendPath.empty())
415 stringLeaf.append(
".");
416 stringLeaf.append(iter->getSecond());
419 assert(!(refSendPath.empty() && stringLeaf.empty()) &&
420 "nothing to index through");
433 for (
auto subOp : llvm::reverse(indexing)) {
434 TypeSwitch<FIRRTLBaseType>(subOp.getInput().getType().getType())
435 .Case<FVectorType, OpenVectorType>([&](
auto vecType) {
436 (Twine(
"[") + Twine(subOp.getIndex()) +
"]").
toVector(stringLeaf);
438 .Case<BundleType, OpenBundleType>([&](
auto bundleType) {
439 auto fieldName = bundleType.getElementName(subOp.getIndex());
440 stringLeaf.append({
".", fieldName});
444 if (!refSendPath.empty())
447 getOrCreatePath(builder.getArrayAttr(refSendPath), builder)
454 ImplicitLocOpBuilder &builder,
455 FlatSymbolRefAttr &ref, StringAttr &xmrAttr) {
456 auto remoteOpPath = getRemoteRefSend(refVal);
460 SmallString<128> xmrString;
461 if (failed(resolveReferencePath(refVal, builder, ref, xmrString)))
464 xmrString.empty() ? StringAttr{} : builder.getStringAttr(xmrString);
471 return TypeSwitch<Operation *, LogicalResult>(op)
472 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp, RefReleaseInitialOp>(
475 auto destType = op.getDest().getType();
476 if (isZeroWidth(destType.getType())) {
481 ImplicitLocOpBuilder builder(op.getLoc(), op);
482 FlatSymbolRefAttr ref;
484 if (failed(resolveReference(op.getDest(), builder, ref, str)))
487 Value xmr = builder.create<XMRRefOp>(destType, ref, str);
488 op.getDestMutable().assign(xmr);
491 .Default([](
auto *op) {
492 return op->emitError(
"unexpected operation kind");
499 if (resWidth.has_value() && *resWidth == 0) {
501 ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
503 auto zeroC = builder.createOrFold<BitCastOp>(
504 resolve.getType(), builder.create<ConstantOp>(
506 resolve.getResult().replaceAllUsesWith(zeroC);
510 FlatSymbolRefAttr ref;
512 ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
513 if (failed(resolveReference(resolve.getRef(), builder, ref, str)))
516 Value result = builder.create<XMRDerefOp>(resolve.getType(), ref, str);
517 resolve.getResult().replaceAllUsesWith(result);
522 if (refPortsToRemoveMap[op].size() < numPorts)
523 refPortsToRemoveMap[op].resize(numPorts);
524 refPortsToRemoveMap[op].set(index);
530 Operation *mod = inst.getReferencedModule(instanceGraph);
531 if (
auto extRefMod = dyn_cast<FExtModuleOp>(mod)) {
535 auto internalPaths = extRefMod.getInternalPaths();
536 auto numPorts = inst.getNumResults();
537 SmallString<128> circuitRefPrefix;
540 auto getPath = [&](
size_t portNo) {
544 cast<InternalPathAttr>(internalPaths->getValue()[portNo])
550 if (circuitRefPrefix.empty())
551 getRefABIPrefix(extRefMod, circuitRefPrefix);
553 return getRefABIMacroForPort(extRefMod, portNo, circuitRefPrefix,
true);
556 for (
const auto &res : llvm::enumerate(inst.getResults())) {
557 if (!isa<RefType>(inst.getResult(res.index()).getType()))
561 auto ind = addReachingSendsEntry(res.value(), inRef);
563 xmrPathSuffix[ind] = getPath(res.index());
565 setPortToRemove(inst, res.index(), numPorts);
566 setPortToRemove(extRefMod, res.index(), numPorts);
570 auto refMod = dyn_cast<FModuleOp>(mod);
571 bool multiplyInstantiated = !visitedModules.insert(refMod).second;
572 for (
size_t portNum = 0, numPorts = inst.getNumResults();
573 portNum < numPorts; ++portNum) {
574 auto instanceResult = inst.getResult(portNum);
575 if (!isa<RefType>(instanceResult.getType()))
578 return inst.emitOpError(
"cannot lower ext modules with RefType ports");
580 setPortToRemove(inst, portNum, numPorts);
582 if (instanceResult.use_empty() ||
583 isZeroWidth(type_cast<RefType>(instanceResult.getType()).getType()))
585 auto refModuleArg = refMod.getArgument(portNum);
590 auto remoteOpPath = getRemoteRefSend(refModuleArg);
602 if (multiplyInstantiated)
603 return refMod.emitOpError(
604 "multiply instantiated module with input RefType port '")
605 << refMod.getPortName(portNum) <<
"'";
606 dataFlowClasses->unionSets(
607 dataFlowClasses->getOrInsertLeaderValue(refModuleArg),
608 dataFlowClasses->getOrInsertLeaderValue(instanceResult));
615 auto *body = getOperation().getBodyBlock();
618 SmallString<128> circuitRefPrefix;
619 SmallVector<std::tuple<StringAttr, StringAttr, ArrayAttr>> ports;
621 ImplicitLocOpBuilder::atBlockBegin(module.getLoc(), body);
622 for (
size_t portIndex = 0, numPorts = module.getNumPorts();
623 portIndex != numPorts; ++portIndex) {
624 auto refType = type_dyn_cast<RefType>(module.getPortType(portIndex));
625 if (!refType || isZeroWidth(refType.getType()) ||
629 cast<mlir::TypedValue<RefType>>(module.getArgument(portIndex));
630 mlir::FlatSymbolRefAttr ref;
631 SmallString<128> stringLeaf;
632 if (failed(resolveReferencePath(portValue, declBuilder, ref, stringLeaf)))
635 SmallString<128> formatString;
637 formatString +=
"{{0}}";
638 formatString += stringLeaf;
642 if (circuitRefPrefix.empty())
643 getRefABIPrefix(module, circuitRefPrefix);
645 getRefABIMacroForPort(module, portIndex, circuitRefPrefix);
646 declBuilder.create<sv::MacroDeclOp>(macroName, ArrayAttr(), StringAttr());
647 ports.emplace_back(macroName, declBuilder.getStringAttr(formatString),
648 ref ? declBuilder.getArrayAttr({ref}) : ArrayAttr{});
657 auto fileBuilder = ImplicitLocOpBuilder(module.getLoc(), module);
658 fileBuilder.create<emit::FileOp>(circuitRefPrefix +
".sv", [&] {
659 for (
auto [macroName, formatString, symbols] : ports) {
661 formatString, symbols);
670 return moduleNamespaces.try_emplace(module, module).first->second;
674 if (
auto arg = dyn_cast<BlockArgument>(val))
676 cast<FModuleLike>(arg.getParentBlock()->getParentOp()),
678 [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
679 return getModuleNamespace(mod);
686 [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
687 return getModuleNamespace(mod);
694 bool errorIfNotFound =
true) {
695 auto iter = dataflowAt.find(dataFlowClasses->getOrInsertLeaderValue(val));
696 if (iter != dataflowAt.end())
697 return iter->getSecond();
698 if (!errorIfNotFound)
702 if (BlockArgument arg = dyn_cast<BlockArgument>(val))
703 arg.getOwner()->getParentOp()->emitError(
704 "reference dataflow cannot be traced back to the remote read op "
706 << dyn_cast<FModuleOp>(arg.getOwner()->getParentOp())
707 .getPortName(arg.getArgNumber())
710 val.getDefiningOp()->emitOpError(
711 "reference dataflow cannot be traced back to the remote read op");
718 std::optional<size_t> continueFrom = std::nullopt) {
719 auto leader = dataFlowClasses->getOrInsertLeaderValue(atRefVal);
720 auto indx = refSendPathList.size();
721 dataflowAt[leader] = indx;
722 refSendPathList.push_back({info, continueFrom});
730 for (Operation *op : llvm::reverse(opsToRemove))
732 for (
auto iter : refPortsToRemoveMap)
733 if (
auto mod = dyn_cast<FModuleOp>(iter.getFirst()))
734 mod.erasePorts(iter.getSecond());
735 else if (
auto mod = dyn_cast<FExtModuleOp>(iter.getFirst()))
736 mod.erasePorts(iter.getSecond());
737 else if (
auto inst = dyn_cast<InstanceOp>(iter.getFirst())) {
738 ImplicitLocOpBuilder b(inst.getLoc(), inst);
739 inst.erasePorts(b, iter.getSecond());
741 }
else if (
auto mem = dyn_cast<MemOp>(iter.getFirst())) {
743 ImplicitLocOpBuilder builder(mem.getLoc(), mem);
744 SmallVector<Attribute, 4> resultNames;
745 SmallVector<Type, 4> resultTypes;
746 SmallVector<Attribute, 4> portAnnotations;
747 SmallVector<Value, 4> oldResults;
748 for (
const auto &res : llvm::enumerate(mem.getResults())) {
749 if (isa<RefType>(mem.getResult(res.index()).getType()))
751 resultNames.push_back(mem.getPortName(res.index()));
752 resultTypes.push_back(res.value().getType());
753 portAnnotations.push_back(mem.getPortAnnotation(res.index()));
754 oldResults.push_back(res.value());
756 auto newMem = builder.create<MemOp>(
757 resultTypes, mem.getReadLatency(), mem.getWriteLatency(),
758 mem.getDepth(), RUWAttr::Undefined,
759 builder.getArrayAttr(resultNames), mem.getNameAttr(),
760 mem.getNameKind(), mem.getAnnotations(),
761 builder.getArrayAttr(portAnnotations), mem.getInnerSymAttr(),
762 mem.getInitAttr(), mem.getPrefixAttr());
763 for (
const auto &res : llvm::enumerate(oldResults))
764 res.value().replaceAllUsesWith(newMem.getResult(res.index()));
768 refPortsToRemoveMap.clear();
770 refSendPathList.clear();
778 ImplicitLocOpBuilder &builder) {
779 assert(pathArray && !pathArray.empty());
781 auto pathIter = pathCache.find(pathArray);
782 if (pathIter != pathCache.end())
783 return pathIter->second;
786 OpBuilder::InsertionGuard guard(builder);
790 if (pathInsertPoint.isSet())
791 builder.restoreInsertionPoint(pathInsertPoint);
793 builder.setInsertionPointToStart(getOperation().
getBodyBlock());
796 hw::HierPathOp path =
799 builder.create<hw::HierPathOp>(
800 circuitNamespace->newName(
"xmrPath"), pathArray)})
802 path.setVisibility(SymbolTable::Visibility::Private);
806 pathInsertPoint = builder.saveInsertionPoint();
836 return lhs.getImpl() < rhs.getImpl();
857 OpBuilder::InsertPoint pathInsertPoint = {};
861 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)
hw::InnerSymbolNamespace & getModuleNamespace(FModuleLike module)
Get the cached namespace for a module.
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)
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)
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)
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)
std::optional< size_t > getRemoteRefSend(Value val, bool errorIfNotFound=true)
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.
def connect(destination, source)
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
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.
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
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.