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