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