CIRCT  20.0.0git
LowerXMR.cpp
Go to the documentation of this file.
1 //===- LowerXMR.cpp - FIRRTL Lower to XMR -----------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file implements FIRRTL XMR Lowering.
10 //
11 //===----------------------------------------------------------------------===//
12 
20 #include "circt/Dialect/SV/SVOps.h"
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"
28 
29 #define DEBUG_TYPE "firrtl-lower-xmr"
30 
31 namespace circt {
32 namespace firrtl {
33 #define GEN_PASS_DEF_LOWERXMR
34 #include "circt/Dialect/FIRRTL/Passes.h.inc"
35 } // namespace firrtl
36 } // namespace circt
37 
38 using namespace circt;
39 using namespace firrtl;
40 using hw::InnerRefAttr;
41 
42 /// The LowerXMRPass will replace every RefResolveOp with an XMR encoded within
43 /// a verbatim expr op. This also removes every RefType port from the modules
44 /// and corresponding instances. This is a dataflow analysis over a very
45 /// constrained RefType. Domain of the dataflow analysis is the set of all
46 /// RefSendOps. It computes an interprocedural reaching definitions (of
47 /// RefSendOp) analysis. Essentially every RefType value must be mapped to one
48 /// and only one RefSendOp. The analysis propagates the dataflow from every
49 /// RefSendOp to every value of RefType across modules. The RefResolveOp is the
50 /// final leaf into which the dataflow must reach.
51 ///
52 /// Since there can be multiple readers, multiple RefResolveOps can be reachable
53 /// from a single RefSendOp. To support multiply instantiated modules and
54 /// multiple readers, it is essential to track the path to the RefSendOp, other
55 /// than just the RefSendOp. For example, if there exists a wire `xmr_wire` in
56 /// module `Foo`, the algorithm needs to support generating Top.Bar.Foo.xmr_wire
57 /// and Top.Foo.xmr_wire and Top.Zoo.Foo.xmr_wire for different instance paths
58 /// that exist in the circuit.
59 
60 namespace {
61 struct XMRNode {
62  using NextNodeOnPath = std::optional<size_t>;
63  using SymOrIndexOp = PointerUnion<Attribute, Operation *>;
64  SymOrIndexOp info;
65  NextNodeOnPath next;
66 };
67 [[maybe_unused]] llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
68  const XMRNode &node) {
69  os << "node(";
70  if (auto attr = dyn_cast<Attribute>(node.info))
71  os << "path=" << attr;
72  else {
73  auto subOp = cast<RefSubOp>(cast<Operation *>(node.info));
74  os << "index=" << subOp.getIndex() << " (-> " << subOp.getType() << ")";
75  }
76  os << ", next=" << node.next << ")";
77  return os;
78 }
79 } // end anonymous namespace
80 
81 class LowerXMRPass : public circt::firrtl::impl::LowerXMRBase<LowerXMRPass> {
82 
83  void runOnOperation() override {
84  // Populate a CircuitNamespace that can be used to generate unique
85  // circuit-level symbols.
86  CircuitNamespace ns(getOperation());
87  circuitNamespace = &ns;
88 
89  llvm::EquivalenceClasses<Value, ValueComparator> eq;
90  dataFlowClasses = &eq;
91 
92  InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
93  SmallVector<RefResolveOp> resolveOps;
94  SmallVector<RefSubOp> indexingOps;
95  SmallVector<Operation *> forceAndReleaseOps;
96  // The dataflow function, that propagates the reachable RefSendOp across
97  // RefType Ops.
98  auto transferFunc = [&](Operation *op) -> LogicalResult {
99  return TypeSwitch<Operation *, LogicalResult>(op)
100  .Case<RefSendOp>([&](RefSendOp send) {
101  // Get a reference to the actual signal to which the XMR will be
102  // generated.
103  Value xmrDef = send.getBase();
104  if (isZeroWidth(send.getType().getType())) {
105  markForRemoval(send);
106  return success();
107  }
108 
109  if (auto verbExpr = xmrDef.getDefiningOp<VerbatimExprOp>())
110  if (verbExpr.getSymbolsAttr().empty() && verbExpr->hasOneUse()) {
111  // This represents the internal path into a module. For
112  // generating the correct XMR, no node can be created in this
113  // module. Create a null InnerRef and ensure the hierarchical
114  // path ends at the parent that instantiates this module.
115  auto inRef = InnerRefAttr();
116  auto ind = addReachingSendsEntry(send.getResult(), inRef);
117  xmrPathSuffix[ind] = verbExpr.getText();
118  markForRemoval(verbExpr);
119  markForRemoval(send);
120  return success();
121  }
122  // Get an InnerRefAttr to the value being sent.
123 
124  // Add a node, don't need to have symbol on defining operation,
125  // just a way to send out the value.
126  ImplicitLocOpBuilder b(xmrDef.getLoc(), &getContext());
127  b.setInsertionPointAfterValue(xmrDef);
128  SmallString<32> opName;
129  auto nameKind = NameKindEnum::DroppableName;
130 
131  if (auto [name, rootKnown] = getFieldName(
132  getFieldRefFromValue(xmrDef, /*lookThroughCasts=*/true),
133  /*nameSafe=*/true);
134  rootKnown) {
135  opName = name + "_probe";
136  nameKind = NameKindEnum::InterestingName;
137  } else if (auto *xmrDefOp = xmrDef.getDefiningOp()) {
138  // Inspect "name" directly for ops that aren't named by above.
139  // (e.g., firrtl.constant)
140  if (auto name = xmrDefOp->getAttrOfType<StringAttr>("name")) {
141  (Twine(name.strref()) + "_probe").toVector(opName);
142  nameKind = NameKindEnum::InterestingName;
143  }
144  }
145  xmrDef = b.create<NodeOp>(xmrDef, opName, nameKind).getResult();
146 
147  // Create a new entry for this RefSendOp. The path is currently
148  // local.
149  addReachingSendsEntry(send.getResult(), getInnerRefTo(xmrDef));
150  markForRemoval(send);
151  return success();
152  })
153  .Case<RWProbeOp>([&](RWProbeOp rwprobe) {
154  if (!isZeroWidth(rwprobe.getType().getType()))
155  addReachingSendsEntry(rwprobe.getResult(), rwprobe.getTarget());
156  markForRemoval(rwprobe);
157  return success();
158  })
159  .Case<MemOp>([&](MemOp mem) {
160  // MemOp can produce debug ports of RefType. Each debug port
161  // represents the RefType for the corresponding register of the
162  // memory. Since the memory is not yet generated the register name
163  // is assumed to be "Memory". Note that MemOp creates RefType
164  // without a RefSend.
165  for (const auto &res : llvm::enumerate(mem.getResults()))
166  if (isa<RefType>(mem.getResult(res.index()).getType())) {
167  auto inRef = getInnerRefTo(mem);
168  auto ind = addReachingSendsEntry(res.value(), inRef);
169  xmrPathSuffix[ind] = "Memory";
170  // Just node that all the debug ports of memory must be removed.
171  // So this does not record the port index.
172  refPortsToRemoveMap[mem].resize(1);
173  }
174  return success();
175  })
176  .Case<InstanceOp>(
177  [&](auto inst) { return handleInstanceOp(inst, instanceGraph); })
178  .Case<FConnectLike>([&](FConnectLike connect) {
179  // Ignore BaseType.
180  if (!isa<RefType>(connect.getSrc().getType()))
181  return success();
182  markForRemoval(connect);
183  if (isZeroWidth(
184  type_cast<RefType>(connect.getSrc().getType()).getType()))
185  return success();
186  // Merge the dataflow classes of destination into the source of the
187  // Connect. This handles two cases:
188  // 1. If the dataflow at the source is known, then the
189  // destination is also inferred. By merging the dataflow class of
190  // destination with source, every value reachable from the
191  // destination automatically infers a reaching RefSend.
192  // 2. If dataflow at source is unkown, then just record that both
193  // source and destination will have the same dataflow information.
194  // Later in the pass when the reaching RefSend is inferred at the
195  // leader of the dataflowClass, then we automatically infer the
196  // dataflow at this connect and every value reachable from the
197  // destination.
198  dataFlowClasses->unionSets(connect.getSrc(), connect.getDest());
199  return success();
200  })
201  .Case<RefSubOp>([&](RefSubOp op) -> LogicalResult {
202  markForRemoval(op);
203  if (isZeroWidth(op.getType().getType()))
204  return success();
205 
206  // Enqueue for processing after visiting other operations.
207  indexingOps.push_back(op);
208  return success();
209  })
210  .Case<RefResolveOp>([&](RefResolveOp resolve) {
211  // Merge dataflow, under the same conditions as above for Connect.
212  // 1. If dataflow at the resolve.getRef is known, propagate that to
213  // the result. This is true for downward scoped XMRs, that is,
214  // RefSendOp must be visited before the corresponding RefResolveOp
215  // is visited.
216  // 2. Else, just record that both result and ref should have the
217  // same reaching RefSend. This condition is true for upward scoped
218  // XMRs. That is, RefResolveOp can be visited before the
219  // corresponding RefSendOp is recorded.
220 
221  markForRemoval(resolve);
222  if (!isZeroWidth(resolve.getType()))
223  dataFlowClasses->unionSets(resolve.getRef(), resolve.getResult());
224  resolveOps.push_back(resolve);
225  return success();
226  })
227  .Case<RefCastOp>([&](RefCastOp op) {
228  markForRemoval(op);
229  if (!isZeroWidth(op.getType().getType()))
230  dataFlowClasses->unionSets(op.getInput(), op.getResult());
231  return success();
232  })
233  .Case<Forceable>([&](Forceable op) {
234  // Handle declarations containing refs as "data".
235  if (type_isa<RefType>(op.getDataRaw().getType())) {
236  markForRemoval(op);
237  return success();
238  }
239 
240  // Otherwise, if forceable track the rwprobe result.
241  if (!op.isForceable() || op.getDataRef().use_empty() ||
242  isZeroWidth(op.getDataType()))
243  return success();
244 
245  addReachingSendsEntry(op.getDataRef(), getInnerRefTo(op));
246  return success();
247  })
248  .Case<RefForceOp, RefForceInitialOp, RefReleaseOp,
249  RefReleaseInitialOp>([&](auto op) {
250  forceAndReleaseOps.push_back(op);
251  return success();
252  })
253  .Default([&](auto) { return success(); });
254  };
255 
256  SmallVector<FModuleOp> publicModules;
257 
258  // Traverse the modules in post order.
259  for (auto node : llvm::post_order(&instanceGraph)) {
260  auto module = dyn_cast<FModuleOp>(*node->getModule());
261  if (!module)
262  continue;
263  LLVM_DEBUG(llvm::dbgs()
264  << "Traversing module:" << module.getModuleNameAttr() << "\n");
265 
266  if (module.isPublic())
267  publicModules.push_back(module);
268 
269  auto result = module.walk([&](Operation *op) {
270  if (transferFunc(op).failed())
271  return WalkResult::interrupt();
272  return WalkResult::advance();
273  });
274 
275  if (result.wasInterrupted())
276  return signalPassFailure();
277 
278  // Clear any enabled layers.
279  module.setLayersAttr(ArrayAttr::get(module.getContext(), {}));
280 
281  // Since we walk operations pre-order and not along dataflow edges,
282  // ref.sub may not be resolvable when we encounter them (they're not just
283  // unification). This can happen when refs go through an output port or
284  // input instance result and back into the design. Handle these by walking
285  // them, resolving what we can, until all are handled or nothing can be
286  // resolved.
287  while (!indexingOps.empty()) {
288  // Grab the set of unresolved ref.sub's.
289  decltype(indexingOps) worklist;
290  worklist.swap(indexingOps);
291 
292  for (auto op : worklist) {
293  auto inputEntry =
294  getRemoteRefSend(op.getInput(), /*errorIfNotFound=*/false);
295  // If we can't resolve, add back and move on.
296  if (!inputEntry)
297  indexingOps.push_back(op);
298  else
299  addReachingSendsEntry(op.getResult(), op.getOperation(),
300  inputEntry);
301  }
302  // If nothing was resolved, give up.
303  if (worklist.size() == indexingOps.size()) {
304  auto op = worklist.front();
305  getRemoteRefSend(op.getInput());
306  op.emitError(
307  "indexing through probe of unknown origin (input probe?)")
308  .attachNote(op.getInput().getLoc())
309  .append("indexing through this reference");
310  return signalPassFailure();
311  }
312  }
313 
314  // Record all the RefType ports to be removed later.
315  size_t numPorts = module.getNumPorts();
316  for (size_t portNum = 0; portNum < numPorts; ++portNum)
317  if (isa<RefType>(module.getPortType(portNum))) {
318  setPortToRemove(module, portNum, numPorts);
319  }
320  }
321 
322  LLVM_DEBUG({
323  for (auto I = dataFlowClasses->begin(), E = dataFlowClasses->end();
324  I != E; ++I) { // Iterate over all of the equivalence sets.
325  if (!I->isLeader())
326  continue; // Ignore non-leader sets.
327  // Print members in this set.
328  llvm::interleave(llvm::make_range(dataFlowClasses->member_begin(I),
329  dataFlowClasses->member_end()),
330  llvm::dbgs(), "\n");
331  llvm::dbgs() << "\n dataflow at leader::" << I->getData() << "\n =>";
332  auto iter = dataflowAt.find(I->getData());
333  if (iter != dataflowAt.end()) {
334  for (auto init = refSendPathList[iter->getSecond()]; init.next;
335  init = refSendPathList[*init.next])
336  llvm::dbgs() << "\n " << init;
337  }
338  llvm::dbgs() << "\n Done\n"; // Finish set.
339  }
340  });
341  for (auto refResolve : resolveOps)
342  if (handleRefResolve(refResolve).failed())
343  return signalPassFailure();
344  for (auto *op : forceAndReleaseOps)
345  if (failed(handleForceReleaseOp(op)))
346  return signalPassFailure();
347  for (auto module : publicModules) {
348  if (failed(handlePublicModuleRefPorts(module)))
349  return signalPassFailure();
350  }
351  garbageCollect();
352 
353  // Clean up
354  moduleNamespaces.clear();
355  visitedModules.clear();
356  dataflowAt.clear();
357  refSendPathList.clear();
358  dataFlowClasses = nullptr;
359  refPortsToRemoveMap.clear();
360  opsToRemove.clear();
361  xmrPathSuffix.clear();
362  circuitNamespace = nullptr;
363  pathCache.clear();
364  pathInsertPoint = {};
365  }
366 
367  /// Generate the ABI ref_<module> prefix string into `prefix`.
368  void getRefABIPrefix(FModuleLike mod, SmallVectorImpl<char> &prefix) {
369  auto modName = mod.getModuleName();
370  if (auto ext = dyn_cast<FExtModuleOp>(*mod)) {
371  // Use defName for module portion, if set.
372  if (auto defname = ext.getDefname(); defname && !defname->empty())
373  modName = *defname;
374  }
375  (Twine("ref_") + modName).toVector(prefix);
376  }
377 
378  /// Get full macro name as StringAttr for the specified ref port.
379  /// Uses existing 'prefix', optionally preprends the backtick character.
380  StringAttr getRefABIMacroForPort(FModuleLike mod, size_t portIndex,
381  const Twine &prefix, bool backTick = false) {
382  return StringAttr::get(&getContext(), Twine(backTick ? "`" : "") + prefix +
383  "_" + mod.getPortName(portIndex));
384  }
385 
386  LogicalResult resolveReferencePath(mlir::TypedValue<RefType> refVal,
387  ImplicitLocOpBuilder builder,
388  mlir::FlatSymbolRefAttr &ref,
389  SmallString<128> &stringLeaf) {
390  assert(stringLeaf.empty());
391 
392  auto remoteOpPath = getRemoteRefSend(refVal);
393  if (!remoteOpPath)
394  return failure();
395  SmallVector<Attribute> refSendPath;
396  SmallVector<RefSubOp> indexing;
397  size_t lastIndex;
398  while (remoteOpPath) {
399  lastIndex = *remoteOpPath;
400  auto entr = refSendPathList[*remoteOpPath];
401  if (entr.info)
402  TypeSwitch<XMRNode::SymOrIndexOp>(entr.info)
403  .Case<Attribute>([&](auto attr) {
404  // If the path is a singular verbatim expression, the attribute of
405  // the send path list entry will be null.
406  if (attr)
407  refSendPath.push_back(attr);
408  })
409  .Case<Operation *>(
410  [&](auto *op) { indexing.push_back(cast<RefSubOp>(op)); });
411  remoteOpPath = entr.next;
412  }
413  auto iter = xmrPathSuffix.find(lastIndex);
414 
415  // If this xmr has a suffix string (internal path into a module, that is not
416  // yet generated).
417  if (iter != xmrPathSuffix.end()) {
418  if (!refSendPath.empty())
419  stringLeaf.append(".");
420  stringLeaf.append(iter->getSecond());
421  }
422 
423  assert(!(refSendPath.empty() && stringLeaf.empty()) &&
424  "nothing to index through");
425 
426  // All indexing done as the ref is plumbed around indexes through
427  // the target/referent, not the current point of the path which
428  // describes how to access the referent we're indexing through.
429  // Above we gathered all indexing operations, so now append them
430  // to the path (after any relevant `xmrPathSuffix`) to reach
431  // the target element.
432  // Generating these strings here (especially if ref is sent
433  // out from a different design) is fragile but should get this
434  // working well enough while sorting out how to do this better.
435  // Some discussion of this can be found here:
436  // https://github.com/llvm/circt/pull/5551#discussion_r1258908834
437  for (auto subOp : llvm::reverse(indexing)) {
438  TypeSwitch<FIRRTLBaseType>(subOp.getInput().getType().getType())
439  .Case<FVectorType, OpenVectorType>([&](auto vecType) {
440  (Twine("[") + Twine(subOp.getIndex()) + "]").toVector(stringLeaf);
441  })
442  .Case<BundleType, OpenBundleType>([&](auto bundleType) {
443  auto fieldName = bundleType.getElementName(subOp.getIndex());
444  stringLeaf.append({".", fieldName});
445  });
446  }
447 
448  if (!refSendPath.empty())
449  // Compute the HierPathOp that stores the path.
451  getOrCreatePath(builder.getArrayAttr(refSendPath), builder)
452  .getSymNameAttr());
453 
454  return success();
455  }
456 
457  LogicalResult resolveReference(mlir::TypedValue<RefType> refVal,
458  ImplicitLocOpBuilder &builder,
459  FlatSymbolRefAttr &ref, StringAttr &xmrAttr) {
460  auto remoteOpPath = getRemoteRefSend(refVal);
461  if (!remoteOpPath)
462  return failure();
463 
464  SmallString<128> xmrString;
465  if (failed(resolveReferencePath(refVal, builder, ref, xmrString)))
466  return failure();
467  xmrAttr =
468  xmrString.empty() ? StringAttr{} : builder.getStringAttr(xmrString);
469 
470  return success();
471  }
472 
473  // Replace the Force/Release's ref argument with a resolved XMRRef.
474  LogicalResult handleForceReleaseOp(Operation *op) {
475  return TypeSwitch<Operation *, LogicalResult>(op)
476  .Case<RefForceOp, RefForceInitialOp, RefReleaseOp, RefReleaseInitialOp>(
477  [&](auto op) {
478  // Drop if zero-width target.
479  auto destType = op.getDest().getType();
480  if (isZeroWidth(destType.getType())) {
481  op.erase();
482  return success();
483  }
484 
485  ImplicitLocOpBuilder builder(op.getLoc(), op);
486  FlatSymbolRefAttr ref;
487  StringAttr str;
488  if (failed(resolveReference(op.getDest(), builder, ref, str)))
489  return failure();
490 
491  Value xmr = builder.create<XMRRefOp>(destType, ref, str);
492  op.getDestMutable().assign(xmr);
493  return success();
494  })
495  .Default([](auto *op) {
496  return op->emitError("unexpected operation kind");
497  });
498  }
499 
500  // Replace the RefResolveOp with verbatim op representing the XMR.
501  LogicalResult handleRefResolve(RefResolveOp resolve) {
502  auto resWidth = getBitWidth(resolve.getType());
503  if (resWidth.has_value() && *resWidth == 0) {
504  // Donot emit 0 width XMRs, replace it with constant 0.
505  ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
506  auto zeroUintType = UIntType::get(builder.getContext(), 0);
507  auto zeroC = builder.createOrFold<BitCastOp>(
508  resolve.getType(), builder.create<ConstantOp>(
509  zeroUintType, getIntZerosAttr(zeroUintType)));
510  resolve.getResult().replaceAllUsesWith(zeroC);
511  return success();
512  }
513 
514  FlatSymbolRefAttr ref;
515  StringAttr str;
516  ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
517  if (failed(resolveReference(resolve.getRef(), builder, ref, str)))
518  return failure();
519 
520  Value result = builder.create<XMRDerefOp>(resolve.getType(), ref, str);
521  resolve.getResult().replaceAllUsesWith(result);
522  return success();
523  }
524 
525  void setPortToRemove(Operation *op, size_t index, size_t numPorts) {
526  if (refPortsToRemoveMap[op].size() < numPorts)
527  refPortsToRemoveMap[op].resize(numPorts);
528  refPortsToRemoveMap[op].set(index);
529  }
530 
531  // Propagate the reachable RefSendOp across modules.
532  LogicalResult handleInstanceOp(InstanceOp inst,
533  InstanceGraph &instanceGraph) {
534  Operation *mod = inst.getReferencedModule(instanceGraph);
535  if (auto extRefMod = dyn_cast<FExtModuleOp>(mod)) {
536  // Extern modules can generate RefType ports, they have an attached
537  // attribute which specifies the internal path into the extern module.
538  // This string attribute will be used to generate the final xmr.
539  auto internalPaths = extRefMod.getInternalPaths();
540  auto numPorts = inst.getNumResults();
541  SmallString<128> circuitRefPrefix;
542 
543  /// Get the resolution string for this ref-type port.
544  auto getPath = [&](size_t portNo) {
545  // If there's an internal path specified (with path), use that.
546  if (internalPaths)
547  if (auto path =
548  cast<InternalPathAttr>(internalPaths->getValue()[portNo])
549  .getPath())
550  return path;
551 
552  // Otherwise, we're using the ref ABI. Generate the prefix string
553  // and return the macro for the specified port.
554  if (circuitRefPrefix.empty())
555  getRefABIPrefix(extRefMod, circuitRefPrefix);
556 
557  return getRefABIMacroForPort(extRefMod, portNo, circuitRefPrefix, true);
558  };
559 
560  for (const auto &res : llvm::enumerate(inst.getResults())) {
561  if (!isa<RefType>(inst.getResult(res.index()).getType()))
562  continue;
563 
564  auto inRef = getInnerRefTo(inst);
565  auto ind = addReachingSendsEntry(res.value(), inRef);
566 
567  xmrPathSuffix[ind] = getPath(res.index());
568  // The instance result and module port must be marked for removal.
569  setPortToRemove(inst, res.index(), numPorts);
570  setPortToRemove(extRefMod, res.index(), numPorts);
571  }
572  return success();
573  }
574  auto refMod = dyn_cast<FModuleOp>(mod);
575  bool multiplyInstantiated = !visitedModules.insert(refMod).second;
576  for (size_t portNum = 0, numPorts = inst.getNumResults();
577  portNum < numPorts; ++portNum) {
578  auto instanceResult = inst.getResult(portNum);
579  if (!isa<RefType>(instanceResult.getType()))
580  continue;
581  if (!refMod)
582  return inst.emitOpError("cannot lower ext modules with RefType ports");
583  // Reference ports must be removed.
584  setPortToRemove(inst, portNum, numPorts);
585  // Drop the dead-instance-ports.
586  if (instanceResult.use_empty() ||
587  isZeroWidth(type_cast<RefType>(instanceResult.getType()).getType()))
588  continue;
589  auto refModuleArg = refMod.getArgument(portNum);
590  if (inst.getPortDirection(portNum) == Direction::Out) {
591  // For output instance ports, the dataflow is into this module.
592  // Get the remote RefSendOp, that flows through the module ports.
593  // If dataflow at remote module argument does not exist, error out.
594  auto remoteOpPath = getRemoteRefSend(refModuleArg);
595  if (!remoteOpPath)
596  return failure();
597  // Get the path to reaching refSend at the referenced module argument.
598  // Now append this instance to the path to the reaching refSend.
599  addReachingSendsEntry(instanceResult, getInnerRefTo(inst),
600  remoteOpPath);
601  } else {
602  // For input instance ports, the dataflow is into the referenced module.
603  // Input RefType port implies, generating an upward scoped XMR.
604  // No need to add the instance context, since downward reference must be
605  // through single instantiated modules.
606  if (multiplyInstantiated)
607  return refMod.emitOpError(
608  "multiply instantiated module with input RefType port '")
609  << refMod.getPortName(portNum) << "'";
610  dataFlowClasses->unionSets(
611  dataFlowClasses->getOrInsertLeaderValue(refModuleArg),
612  dataFlowClasses->getOrInsertLeaderValue(instanceResult));
613  }
614  }
615  return success();
616  }
617 
618  LogicalResult handlePublicModuleRefPorts(FModuleOp module) {
619  auto *body = getOperation().getBodyBlock();
620 
621  // Find all the output reference ports.
622  SmallString<128> circuitRefPrefix;
623  SmallVector<std::tuple<StringAttr, StringAttr, ArrayAttr>> ports;
624  auto declBuilder =
625  ImplicitLocOpBuilder::atBlockBegin(module.getLoc(), body);
626  for (size_t portIndex = 0, numPorts = module.getNumPorts();
627  portIndex != numPorts; ++portIndex) {
628  auto refType = type_dyn_cast<RefType>(module.getPortType(portIndex));
629  if (!refType || isZeroWidth(refType.getType()) ||
630  module.getPortDirection(portIndex) != Direction::Out)
631  continue;
632  auto portValue =
633  cast<mlir::TypedValue<RefType>>(module.getArgument(portIndex));
634  mlir::FlatSymbolRefAttr ref;
635  SmallString<128> stringLeaf;
636  if (failed(resolveReferencePath(portValue, declBuilder, ref, stringLeaf)))
637  return failure();
638 
639  SmallString<128> formatString;
640  if (ref)
641  formatString += "{{0}}";
642  formatString += stringLeaf;
643 
644  // Insert a macro with the format:
645  // ref_<module-name>_<ref-name> <path>
646  if (circuitRefPrefix.empty())
647  getRefABIPrefix(module, circuitRefPrefix);
648  auto macroName =
649  getRefABIMacroForPort(module, portIndex, circuitRefPrefix);
650  declBuilder.create<sv::MacroDeclOp>(macroName, ArrayAttr(), StringAttr());
651  ports.emplace_back(macroName, declBuilder.getStringAttr(formatString),
652  ref ? declBuilder.getArrayAttr({ref}) : ArrayAttr{});
653  }
654 
655  // Create a file only if the module has at least one ref port.
656  if (ports.empty())
657  return success();
658 
659  // The macros will be exported to a `ref_<module-name>.sv` file.
660  // In the IR, the file is inserted before the module.
661  auto fileBuilder = ImplicitLocOpBuilder(module.getLoc(), module);
662  fileBuilder.create<emit::FileOp>(circuitRefPrefix + ".sv", [&] {
663  for (auto [macroName, formatString, symbols] : ports) {
664  fileBuilder.create<sv::MacroDefOp>(FlatSymbolRefAttr::get(macroName),
665  formatString, symbols);
666  }
667  });
668 
669  return success();
670  }
671 
672  /// Get the cached namespace for a module.
673  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
674  return moduleNamespaces.try_emplace(module, module).first->second;
675  }
676 
677  InnerRefAttr getInnerRefTo(Value val) {
678  if (auto arg = dyn_cast<BlockArgument>(val))
680  cast<FModuleLike>(arg.getParentBlock()->getParentOp()),
681  arg.getArgNumber(),
682  [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
683  return getModuleNamespace(mod);
684  });
685  return getInnerRefTo(val.getDefiningOp());
686  }
687 
688  InnerRefAttr getInnerRefTo(Operation *op) {
690  [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
691  return getModuleNamespace(mod);
692  });
693  }
694 
695  void markForRemoval(Operation *op) { opsToRemove.push_back(op); }
696 
697  std::optional<size_t> getRemoteRefSend(Value val,
698  bool errorIfNotFound = true) {
699  auto iter = dataflowAt.find(dataFlowClasses->getOrInsertLeaderValue(val));
700  if (iter != dataflowAt.end())
701  return iter->getSecond();
702  if (!errorIfNotFound)
703  return std::nullopt;
704  // The referenced module must have already been analyzed, error out if the
705  // dataflow at the child module is not resolved.
706  if (BlockArgument arg = dyn_cast<BlockArgument>(val))
707  arg.getOwner()->getParentOp()->emitError(
708  "reference dataflow cannot be traced back to the remote read op "
709  "for module port '")
710  << dyn_cast<FModuleOp>(arg.getOwner()->getParentOp())
711  .getPortName(arg.getArgNumber())
712  << "'";
713  else
714  val.getDefiningOp()->emitOpError(
715  "reference dataflow cannot be traced back to the remote read op");
716  signalPassFailure();
717  return std::nullopt;
718  }
719 
720  size_t
721  addReachingSendsEntry(Value atRefVal, XMRNode::SymOrIndexOp info,
722  std::optional<size_t> continueFrom = std::nullopt) {
723  auto leader = dataFlowClasses->getOrInsertLeaderValue(atRefVal);
724  auto indx = refSendPathList.size();
725  dataflowAt[leader] = indx;
726  refSendPathList.push_back({info, continueFrom});
727  return indx;
728  }
729 
730  void garbageCollect() {
731  // Now erase all the Ops and ports of RefType.
732  // This needs to be done as the last step to ensure uses are erased before
733  // the def is erased.
734  for (Operation *op : llvm::reverse(opsToRemove))
735  op->erase();
736  for (auto iter : refPortsToRemoveMap)
737  if (auto mod = dyn_cast<FModuleOp>(iter.getFirst()))
738  mod.erasePorts(iter.getSecond());
739  else if (auto mod = dyn_cast<FExtModuleOp>(iter.getFirst()))
740  mod.erasePorts(iter.getSecond());
741  else if (auto inst = dyn_cast<InstanceOp>(iter.getFirst())) {
742  ImplicitLocOpBuilder b(inst.getLoc(), inst);
743  inst.erasePorts(b, iter.getSecond());
744  inst.erase();
745  } else if (auto mem = dyn_cast<MemOp>(iter.getFirst())) {
746  // Remove all debug ports of the memory.
747  ImplicitLocOpBuilder builder(mem.getLoc(), mem);
748  SmallVector<Attribute, 4> resultNames;
749  SmallVector<Type, 4> resultTypes;
750  SmallVector<Attribute, 4> portAnnotations;
751  SmallVector<Value, 4> oldResults;
752  for (const auto &res : llvm::enumerate(mem.getResults())) {
753  if (isa<RefType>(mem.getResult(res.index()).getType()))
754  continue;
755  resultNames.push_back(mem.getPortName(res.index()));
756  resultTypes.push_back(res.value().getType());
757  portAnnotations.push_back(mem.getPortAnnotation(res.index()));
758  oldResults.push_back(res.value());
759  }
760  auto newMem = builder.create<MemOp>(
761  resultTypes, mem.getReadLatency(), mem.getWriteLatency(),
762  mem.getDepth(), RUWAttr::Undefined,
763  builder.getArrayAttr(resultNames), mem.getNameAttr(),
764  mem.getNameKind(), mem.getAnnotations(),
765  builder.getArrayAttr(portAnnotations), mem.getInnerSymAttr(),
766  mem.getInitAttr(), mem.getPrefixAttr());
767  for (const auto &res : llvm::enumerate(oldResults))
768  res.value().replaceAllUsesWith(newMem.getResult(res.index()));
769  mem.erase();
770  }
771  opsToRemove.clear();
772  refPortsToRemoveMap.clear();
773  dataflowAt.clear();
774  refSendPathList.clear();
775  }
776 
777  bool isZeroWidth(FIRRTLBaseType t) { return t.getBitWidthOrSentinel() == 0; }
778 
779  /// Return a HierPathOp for the provided pathArray. This will either return
780  /// an existing HierPathOp or it will create and return a new one.
781  hw::HierPathOp getOrCreatePath(ArrayAttr pathArray,
782  ImplicitLocOpBuilder &builder) {
783  assert(pathArray && !pathArray.empty());
784  // Return an existing HierPathOp if one exists with the same path.
785  auto pathIter = pathCache.find(pathArray);
786  if (pathIter != pathCache.end())
787  return pathIter->second;
788 
789  // Reset the insertion point after this function returns.
790  OpBuilder::InsertionGuard guard(builder);
791 
792  // Set the insertion point to either the known location where the pass
793  // inserts HierPathOps or to the start of the circuit.
794  if (pathInsertPoint.isSet())
795  builder.restoreInsertionPoint(pathInsertPoint);
796  else
797  builder.setInsertionPointToStart(getOperation().getBodyBlock());
798 
799  // Create the new HierPathOp and insert it into the pathCache.
800  hw::HierPathOp path =
801  pathCache
802  .insert({pathArray,
803  builder.create<hw::HierPathOp>(
804  circuitNamespace->newName("xmrPath"), pathArray)})
805  .first->second;
806  path.setVisibility(SymbolTable::Visibility::Private);
807 
808  // Save the insertion point so other unique HierPathOps will be created
809  // after this one.
810  pathInsertPoint = builder.saveInsertionPoint();
811 
812  // Return the new path.
813  return path;
814  }
815 
816 private:
817  /// Cached module namespaces.
818  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
819 
820  DenseSet<Operation *> visitedModules;
821  /// Map of a reference value to an entry into refSendPathList. Each entry in
822  /// refSendPathList represents the path to RefSend.
823  /// The path is required since there can be multiple paths to the RefSend and
824  /// we need to identify a unique path.
825  DenseMap<Value, size_t> dataflowAt;
826 
827  /// refSendPathList is used to construct a path to the RefSendOp. Each entry
828  /// is an XMRNode, with an InnerRefAttr or indexing op, and a pointer to the
829  /// next node in the path. The InnerRefAttr can be to an InstanceOp or to the
830  /// XMR defining op, the index op records narrowing along path. All the nodes
831  /// representing an InstanceOp or indexing operation must have a valid
832  /// NextNodeOnPath. Only the node representing the final XMR defining op has
833  /// no NextNodeOnPath, which denotes a leaf node on the path.
834  SmallVector<XMRNode> refSendPathList;
835 
836  /// llvm::EquivalenceClasses wants comparable elements. This comparator uses
837  /// uses pointer comparison on the Impl.
839  bool operator()(const Value &lhs, const Value &rhs) const {
840  return lhs.getImpl() < rhs.getImpl();
841  }
842  };
843 
844  llvm::EquivalenceClasses<Value, ValueComparator> *dataFlowClasses;
845  // Instance and module ref ports that needs to be removed.
846  DenseMap<Operation *, llvm::BitVector> refPortsToRemoveMap;
847 
848  /// RefResolve, RefSend, and Connects involving them that will be removed.
849  SmallVector<Operation *> opsToRemove;
850 
851  /// Record the internal path to an external module or a memory.
852  DenseMap<size_t, SmallString<128>> xmrPathSuffix;
853 
855 
856  /// A cache of already created HierPathOps. This is used to avoid repeatedly
857  /// creating the same HierPathOp.
858  DenseMap<Attribute, hw::HierPathOp> pathCache;
859 
860  /// The insertion point where the pass inserts HierPathOps.
861  OpBuilder::InsertPoint pathInsertPoint = {};
862 };
863 
864 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerXMRPass() {
865  return std::make_unique<LowerXMRPass>();
866 }
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.
Definition: LowerXMR.cpp:673
LogicalResult resolveReference(mlir::TypedValue< RefType > refVal, ImplicitLocOpBuilder &builder, FlatSymbolRefAttr &ref, StringAttr &xmrAttr)
Definition: LowerXMR.cpp:457
DenseMap< Operation *, hw::InnerSymbolNamespace > moduleNamespaces
Cached module namespaces.
Definition: LowerXMR.cpp:818
DenseMap< size_t, SmallString< 128 > > xmrPathSuffix
Record the internal path to an external module or a memory.
Definition: LowerXMR.cpp:852
InnerRefAttr getInnerRefTo(Value val)
Definition: LowerXMR.cpp:677
size_t addReachingSendsEntry(Value atRefVal, XMRNode::SymOrIndexOp info, std::optional< size_t > continueFrom=std::nullopt)
Definition: LowerXMR.cpp:721
hw::HierPathOp getOrCreatePath(ArrayAttr pathArray, ImplicitLocOpBuilder &builder)
Return a HierPathOp for the provided pathArray.
Definition: LowerXMR.cpp:781
LogicalResult resolveReferencePath(mlir::TypedValue< RefType > refVal, ImplicitLocOpBuilder builder, mlir::FlatSymbolRefAttr &ref, SmallString< 128 > &stringLeaf)
Definition: LowerXMR.cpp:386
DenseMap< Value, size_t > dataflowAt
Map of a reference value to an entry into refSendPathList.
Definition: LowerXMR.cpp:825
void setPortToRemove(Operation *op, size_t index, size_t numPorts)
Definition: LowerXMR.cpp:525
llvm::EquivalenceClasses< Value, ValueComparator > * dataFlowClasses
Definition: LowerXMR.cpp:844
void markForRemoval(Operation *op)
Definition: LowerXMR.cpp:695
void garbageCollect()
Definition: LowerXMR.cpp:730
LogicalResult handlePublicModuleRefPorts(FModuleOp module)
Definition: LowerXMR.cpp:618
void getRefABIPrefix(FModuleLike mod, SmallVectorImpl< char > &prefix)
Generate the ABI ref_<module> prefix string into prefix.
Definition: LowerXMR.cpp:368
void runOnOperation() override
Definition: LowerXMR.cpp:83
DenseMap< Attribute, hw::HierPathOp > pathCache
A cache of already created HierPathOps.
Definition: LowerXMR.cpp:858
LogicalResult handleRefResolve(RefResolveOp resolve)
Definition: LowerXMR.cpp:501
DenseMap< Operation *, llvm::BitVector > refPortsToRemoveMap
Definition: LowerXMR.cpp:846
SmallVector< XMRNode > refSendPathList
refSendPathList is used to construct a path to the RefSendOp.
Definition: LowerXMR.cpp:834
LogicalResult handleInstanceOp(InstanceOp inst, InstanceGraph &instanceGraph)
Definition: LowerXMR.cpp:532
LogicalResult handleForceReleaseOp(Operation *op)
Definition: LowerXMR.cpp:474
DenseSet< Operation * > visitedModules
Definition: LowerXMR.cpp:820
InnerRefAttr getInnerRefTo(Operation *op)
Definition: LowerXMR.cpp:688
StringAttr getRefABIMacroForPort(FModuleLike mod, size_t portIndex, const Twine &prefix, bool backTick=false)
Get full macro name as StringAttr for the specified ref port.
Definition: LowerXMR.cpp:380
CircuitNamespace * circuitNamespace
Definition: LowerXMR.cpp:854
bool isZeroWidth(FIRRTLBaseType t)
Definition: LowerXMR.cpp:777
std::optional< size_t > getRemoteRefSend(Value val, bool errorIfNotFound=true)
Definition: LowerXMR.cpp:697
SmallVector< Operation * > opsToRemove
RefResolve, RefSend, and Connects involving them that will be removed.
Definition: LowerXMR.cpp:849
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)
Definition: support.py:39
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
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()
Definition: LowerXMR.cpp:864
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.
Definition: DebugAnalysis.h:21
llvm::EquivalenceClasses wants comparable elements.
Definition: LowerXMR.cpp:838
bool operator()(const Value &lhs, const Value &rhs) const
Definition: LowerXMR.cpp:839
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24