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