CIRCT  20.0.0git
LowerClasses.cpp
Go to the documentation of this file.
1 //===- LowerClasses.cpp - Lower to OM classes and objects -----------------===//
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 defines the LowerClasses pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
22 #include "circt/Dialect/OM/OMOps.h"
23 #include "mlir/IR/BuiltinOps.h"
24 #include "mlir/IR/IRMapping.h"
25 #include "mlir/IR/PatternMatch.h"
26 #include "mlir/IR/Threading.h"
27 #include "mlir/Pass/Pass.h"
28 #include "mlir/Support/LogicalResult.h"
29 #include "mlir/Transforms/DialectConversion.h"
30 #include "llvm/ADT/DepthFirstIterator.h"
31 #include "llvm/ADT/STLExtras.h"
32 #include "llvm/Support/raw_ostream.h"
33 
34 namespace circt {
35 namespace firrtl {
36 #define GEN_PASS_DEF_LOWERCLASSES
37 #include "circt/Dialect/FIRRTL/Passes.h.inc"
38 } // namespace firrtl
39 } // namespace circt
40 
41 using namespace mlir;
42 using namespace circt;
43 using namespace circt::firrtl;
44 using namespace circt::om;
45 
46 namespace {
47 
48 static bool shouldCreateClassImpl(igraph::InstanceGraphNode *node) {
49  auto moduleLike = node->getModule<FModuleLike>();
50  if (!moduleLike)
51  return false;
52 
53  if (isa<firrtl::ClassLike>(moduleLike.getOperation()))
54  return true;
55 
56  // Always create a class for public modules.
57  if (moduleLike.isPublic())
58  return true;
59 
60  // Create a class for modules with property ports.
61  bool hasClassPorts = llvm::any_of(moduleLike.getPorts(), [](PortInfo port) {
62  return isa<PropertyType>(port.type);
63  });
64 
65  if (hasClassPorts)
66  return true;
67 
68  // Create a class for modules that instantiate classes or modules with
69  // property ports.
70  for (auto *instance : *node) {
71  if (auto op = instance->getInstance<FInstanceLike>())
72  for (auto result : op->getResults())
73  if (type_isa<PropertyType>(result.getType()))
74  return true;
75  }
76 
77  return false;
78 }
79 
80 /// Helper class which holds a hierarchical path op reference and a pointer to
81 /// to the targeted operation.
82 struct PathInfo {
83  PathInfo() = default;
84  PathInfo(Location loc, bool canBeInstanceTarget, FlatSymbolRefAttr symRef,
85  StringAttr altBasePathModule)
86  : loc(loc), canBeInstanceTarget(canBeInstanceTarget), symRef(symRef),
87  altBasePathModule(altBasePathModule) {
88  assert(symRef && "symRef must not be null");
89  }
90 
91  /// The Location of the hardware component targeted by this path.
92  std::optional<Location> loc = std::nullopt;
93 
94  /// Flag to indicate if the hardware component can be targeted as an instance.
95  bool canBeInstanceTarget = false;
96 
97  /// A reference to the hierarchical path targeting the op.
98  FlatSymbolRefAttr symRef = nullptr;
99 
100  /// The module name of the root module from which we take an alternative base
101  /// path.
102  StringAttr altBasePathModule = nullptr;
103 };
104 
105 /// Maps a FIRRTL path id to the lowered PathInfo.
106 struct PathInfoTable {
107  // Add an alternative base path root module. The default base path from this
108  // module will be passed through to where it is needed.
109  void addAltBasePathRoot(StringAttr rootModuleName) {
110  altBasePathRoots.insert(rootModuleName);
111  }
112 
113  // Add a passthrough module for a given root module. The default base path
114  // from the root module will be passed through the passthrough module.
115  void addAltBasePathPassthrough(StringAttr passthroughModuleName,
116  StringAttr rootModuleName) {
117  auto &rootSequence = altBasePathsPassthroughs[passthroughModuleName];
118  rootSequence.push_back(rootModuleName);
119  }
120 
121  // Get an iterator range over the alternative base path root module names.
122  llvm::iterator_range<SmallPtrSetImpl<StringAttr>::iterator>
123  getAltBasePathRoots() const {
124  return llvm::make_range(altBasePathRoots.begin(), altBasePathRoots.end());
125  }
126 
127  // Get the number of alternative base paths passing through the given
128  // passthrough module.
129  size_t getNumAltBasePaths(StringAttr passthroughModuleName) const {
130  return altBasePathsPassthroughs.lookup(passthroughModuleName).size();
131  }
132 
133  // Get the root modules that are passing an alternative base path through the
134  // given passthrough module.
135  llvm::iterator_range<const StringAttr *>
136  getRootsForPassthrough(StringAttr passthroughModuleName) const {
137  auto it = altBasePathsPassthroughs.find(passthroughModuleName);
138  assert(it != altBasePathsPassthroughs.end() &&
139  "expected passthrough module to already exist");
140  return llvm::make_range(it->second.begin(), it->second.end());
141  }
142 
143  // Collect alternative base paths passing through `instance`, by looking up
144  // its associated `moduleNameAttr`. The results are collected in `result`.
145  void collectAltBasePaths(Operation *instance, StringAttr moduleNameAttr,
146  SmallVectorImpl<Value> &result) const {
147  auto altBasePaths = altBasePathsPassthroughs.lookup(moduleNameAttr);
148  auto parent = instance->getParentOfType<om::ClassOp>();
149 
150  // Handle each alternative base path for instances of this module-like.
151  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
152  if (parent.getName().starts_with(altBasePath)) {
153  // If we are passing down from the root, take the root base path.
154  result.push_back(instance->getBlock()->getArgument(0));
155  } else {
156  // Otherwise, pass through the appropriate base path from above.
157  // + 1 to skip default base path
158  auto basePath = instance->getBlock()->getArgument(1 + i);
159  assert(isa<om::BasePathType>(basePath.getType()) &&
160  "expected a passthrough base path");
161  result.push_back(basePath);
162  }
163  }
164  }
165 
166  // The table mapping DistinctAttrs to PathInfo structs.
167  DenseMap<DistinctAttr, PathInfo> table;
168 
169 private:
170  // Module name attributes indicating modules whose base path input should
171  // be used as alternate base paths.
172  SmallPtrSet<StringAttr, 16> altBasePathRoots;
173 
174  // Module name attributes mapping from modules who pass through alternative
175  // base paths from their parents to a sequence of the parents' module names.
176  DenseMap<StringAttr, SmallVector<StringAttr>> altBasePathsPassthroughs;
177 };
178 
179 /// The suffix to append to lowered module names.
180 static constexpr StringRef kClassNameSuffix = "_Class";
181 
182 /// Helper class to capture details about a property.
183 struct Property {
184  size_t index;
185  StringRef name;
186  Type type;
187  Location loc;
188 };
189 
190 /// Helper class to capture state about a Class being lowered.
191 struct ClassLoweringState {
192  FModuleLike moduleLike;
193  std::vector<hw::HierPathOp> paths;
194 };
195 
196 struct LoweringState {
197  PathInfoTable pathInfoTable;
198  DenseMap<om::ClassLike, ClassLoweringState> classLoweringStateTable;
199 };
200 
201 /// Helper struct to capture state about an object that needs RtlPorts added.
202 struct RtlPortsInfo {
203  firrtl::PathOp containingModuleRef;
204  Value basePath;
205  om::ObjectOp object;
206 };
207 
208 struct LowerClassesPass
209  : public circt::firrtl::impl::LowerClassesBase<LowerClassesPass> {
210  void runOnOperation() override;
211 
212 private:
213  LogicalResult processPaths(InstanceGraph &instanceGraph,
214  hw::InnerSymbolNamespaceCollection &namespaces,
215  HierPathCache &cache, PathInfoTable &pathInfoTable,
216  SymbolTable &symbolTable);
217 
218  // Predicate to check if a module-like needs a Class to be created.
219  bool shouldCreateClass(StringAttr modName);
220 
221  // Create an OM Class op from a FIRRTL Class op.
222  om::ClassLike createClass(FModuleLike moduleLike,
223  const PathInfoTable &pathInfoTable,
224  std::mutex &intraPassMutex);
225 
226  // Lower the FIRRTL Class to OM Class.
227  void lowerClassLike(FModuleLike moduleLike, om::ClassLike classLike,
228  const PathInfoTable &pathInfoTable);
229  void lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
230  const PathInfoTable &pathInfoTable);
231  void lowerClassExtern(ClassExternOp classExternOp, FModuleLike moduleLike);
232 
233  // Update Object instantiations in a FIRRTL Module or OM Class.
234  LogicalResult updateInstances(Operation *op, InstanceGraph &instanceGraph,
235  const LoweringState &state,
236  const PathInfoTable &pathInfoTable,
237  std::mutex &intraPassMutex);
238 
239  /// Create and add all 'ports' lists of RtlPort objects for each object.
240  void createAllRtlPorts(const PathInfoTable &pathInfoTable,
241  hw::InnerSymbolNamespaceCollection &namespaces,
242  HierPathCache &hierPathCache);
243 
244  // Convert to OM ops and types in Classes or Modules.
245  LogicalResult dialectConversion(
246  Operation *op, const PathInfoTable &pathInfoTable,
247  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable);
248 
249  // State to memoize repeated calls to shouldCreateClass.
250  DenseMap<StringAttr, bool> shouldCreateClassMemo;
251 
252  // State used while creating the optional 'ports' list of RtlPort objects.
253  SmallVector<RtlPortsInfo> rtlPortsToCreate;
254 };
255 
256 struct PathTracker {
257  // An entry point for parallely tracking paths in the circuit and apply
258  // changes to `pathInfoTable`.
259  static LogicalResult
260  run(CircuitOp circuit, InstanceGraph &instanceGraph,
261  hw::InnerSymbolNamespaceCollection &namespaces, HierPathCache &cache,
262  PathInfoTable &pathInfoTable, const SymbolTable &symbolTable,
263  const DenseMap<DistinctAttr, FModuleOp> &owningModules);
264 
265  PathTracker(FModuleLike module,
266  hw::InnerSymbolNamespaceCollection &namespaces,
267  InstanceGraph &instanceGraph, const SymbolTable &symbolTable,
268  const DenseMap<DistinctAttr, FModuleOp> &owningModules)
269  : module(module), moduleNamespace(namespaces[module]),
270  namespaces(namespaces), instanceGraph(instanceGraph),
271  symbolTable(symbolTable), owningModules(owningModules) {}
272 
273 private:
274  struct PathInfoTableEntry {
275  Operation *op;
276  DistinctAttr id;
277  StringAttr altBasePathModule;
278  // This is null if the path has no owning module.
279  ArrayAttr pathAttr;
280  };
281 
282  // Run the main logic.
283  LogicalResult runOnModule();
284 
285  // Return updated annotations for a given AnnoTarget if success.
286  FailureOr<AnnotationSet> processPathTrackers(const AnnoTarget &target);
287 
288  LogicalResult updatePathInfoTable(PathInfoTable &pathInfoTable,
289  HierPathCache &cache) const;
290 
291  // Determine it is necessary to use an alternative base path for `moduleName`
292  // and `owningModule`.
293  FailureOr<bool> getOrComputeNeedsAltBasePath(Location loc,
294  StringAttr moduleName,
295  FModuleOp owningModule,
296  bool isNonLocal);
297  FModuleLike module;
298 
299  // Local data structures.
300  hw::InnerSymbolNamespace &moduleNamespace;
301  hw::InnerSymbolNamespaceCollection &namespaces;
302  DenseMap<std::pair<StringAttr, FModuleOp>, bool> needsAltBasePathCache;
303 
304  // Thread-unsafe global data structure. Don't mutate.
305  InstanceGraph &instanceGraph;
306  const SymbolTable &symbolTable;
307  const DenseMap<DistinctAttr, FModuleOp> &owningModules;
308 
309  // Result.
310  SmallVector<PathInfoTableEntry> entries;
311  SetVector<StringAttr> altBasePathRoots;
312 };
313 
314 /// Constants and helpers for creating the RtlPorts on the fly.
315 
316 static constexpr StringRef kContainingModuleName = "containingModule";
317 static constexpr StringRef kPortsName = "ports";
318 static constexpr StringRef kRtlPortClassName = "RtlPort";
319 
320 static Type getRtlPortsType(MLIRContext *context) {
322  context, FlatSymbolRefAttr::get(context, kRtlPortClassName)));
323 }
324 
325 /// Create and add the 'ports' list of RtlPort objects for an object.
326 static void createRtlPorts(const RtlPortsInfo &rtlPortToCreate,
327  const PathInfoTable &pathInfoTable,
328  hw::InnerSymbolNamespaceCollection &namespaces,
329  HierPathCache &hierPathCache, OpBuilder &builder) {
330  firrtl::PathOp containingModuleRef = rtlPortToCreate.containingModuleRef;
331  Value basePath = rtlPortToCreate.basePath;
332  om::ObjectOp object = rtlPortToCreate.object;
333 
334  // Set the builder to just before the object.
335  OpBuilder::InsertionGuard guard(builder);
336  builder.setInsertionPoint(object);
337 
338  // Look up the module from the containingModuleRef.
339 
340  FlatSymbolRefAttr containingModulePathRef =
341  pathInfoTable.table.at(containingModuleRef.getTarget()).symRef;
342 
343  const SymbolTable &symbolTable = hierPathCache.getSymbolTable();
344 
345  hw::HierPathOp containingModulePath =
346  symbolTable.lookup<hw::HierPathOp>(containingModulePathRef.getAttr());
347 
348  assert(containingModulePath.isModule() &&
349  "expected containing module path to target a module");
350 
351  StringAttr moduleName = containingModulePath.leafMod();
352 
353  FModuleLike mod = symbolTable.lookup<FModuleLike>(moduleName);
354  MLIRContext *ctx = mod.getContext();
355  Location loc = mod.getLoc();
356 
357  // Create the per-port information.
358 
359  auto portClassName = StringAttr::get(ctx, kRtlPortClassName);
360  auto portClassType =
361  om::ClassType::get(ctx, FlatSymbolRefAttr::get(portClassName));
362 
363  SmallVector<Value> ports;
364  for (unsigned i = 0, e = mod.getNumPorts(); i < e; ++i) {
365  // Only process ports that are not zero-width.
366  auto portType = type_dyn_cast<FIRRTLBaseType>(mod.getPortType(i));
367  if (!portType || portType.getBitWidthOrSentinel() == 0)
368  continue;
369 
370  // Get a path to the port. This may modify port attributes or the global
371  // namespace of hierpaths, so use the mutex around those operations.
372 
373  auto portTarget = PortAnnoTarget(mod, i);
374 
375  auto portSym =
376  getInnerRefTo({portTarget.getPortNo(), portTarget.getOp(), 0},
377  [&](FModuleLike m) -> hw::InnerSymbolNamespace & {
378  return namespaces[m];
379  });
380 
381  FlatSymbolRefAttr portPathRef =
382  hierPathCache.getRefFor(ArrayAttr::get(ctx, {portSym}));
383 
384  auto portPath = builder.create<om::PathCreateOp>(
385  loc, om::PathType::get(ctx),
386  om::TargetKindAttr::get(ctx, om::TargetKind::DontTouch), basePath,
387  portPathRef);
388 
389  // Get a direction attribute.
390 
391  StringRef portDirectionName =
392  mod.getPortDirection(i) == Direction::Out ? "Output" : "Input";
393 
394  auto portDirection = builder.create<om::ConstantOp>(
395  loc, om::StringType::get(ctx),
396  StringAttr::get(portDirectionName, om::StringType::get(ctx)));
397 
398  // Get a width attribute.
399 
400  auto portWidth = builder.create<om::ConstantOp>(
401  loc, om::OMIntegerType::get(ctx),
404  portType.getBitWidthOrSentinel())));
405 
406  // Create an RtlPort object for this port, and add it to the list.
407 
408  auto portObj = builder.create<om::ObjectOp>(
409  loc, portClassType, portClassName,
410  ArrayRef<Value>{portPath, portDirection, portWidth});
411 
412  ports.push_back(portObj);
413  }
414 
415  // Create a list of RtlPort objects to be included with the containingModule.
416 
417  auto portsList = builder.create<om::ListCreateOp>(
418  UnknownLoc::get(builder.getContext()),
419  getRtlPortsType(builder.getContext()), ports);
420 
421  object.getActualParamsMutable().append({portsList});
422 }
423 
424 } // namespace
425 
426 LogicalResult
427 PathTracker::run(CircuitOp circuit, InstanceGraph &instanceGraph,
428  hw::InnerSymbolNamespaceCollection &namespaces,
429  HierPathCache &cache, PathInfoTable &pathInfoTable,
430  const SymbolTable &symbolTable,
431  const DenseMap<DistinctAttr, FModuleOp> &owningModules) {
432  // First allocate module namespaces. Don't capture a namespace reference at
433  // this point since they could be invalidated when DenseMap grows.
434  for (auto *node : instanceGraph)
435  if (auto module = node->getModule<FModuleLike>())
436  (void)namespaces.get(module);
437 
438  for (auto *node : instanceGraph)
439  if (auto module = node->getModule<FModuleLike>()) {
440  // Classes do not have path trackers on them.
441  if (isa<firrtl::ClassOp, firrtl::ExtClassOp>(module))
442  continue;
443  PathTracker tracker(module, namespaces, instanceGraph, symbolTable,
444  owningModules);
445  if (failed(tracker.runOnModule()))
446  return failure();
447  if (failed(tracker.updatePathInfoTable(pathInfoTable, cache)))
448  return failure();
449  }
450 
451  return success();
452 }
453 
454 LogicalResult PathTracker::runOnModule() {
455  auto processAndUpdateAnnoTarget = [&](AnnoTarget target) -> LogicalResult {
456  auto anno = processPathTrackers(target);
457  if (failed(anno))
458  return failure();
459  target.setAnnotations(*anno);
460  return success();
461  };
462 
463  // Process the module annotations.
464  if (failed(processAndUpdateAnnoTarget(OpAnnoTarget(module))))
465  return failure();
466 
467  // Process module port annotations.
468  SmallVector<Attribute> portAnnotations;
469  portAnnotations.reserve(module.getNumPorts());
470  for (unsigned i = 0, e = module.getNumPorts(); i < e; ++i) {
471  auto annos = processPathTrackers(PortAnnoTarget(module, i));
472  if (failed(annos))
473  return failure();
474  portAnnotations.push_back(annos->getArrayAttr());
475  }
476  // Batch update port annotations.
477  module.setPortAnnotationsAttr(
478  ArrayAttr::get(module.getContext(), portAnnotations));
479 
480  // Process ops in the module body.
481  auto result = module.walk([&](hw::InnerSymbolOpInterface op) {
482  if (failed(processAndUpdateAnnoTarget(OpAnnoTarget(op))))
483  return WalkResult::interrupt();
484  return WalkResult::advance();
485  });
486 
487  if (result.wasInterrupted())
488  return failure();
489 
490  // Process paththrough.
491  return success();
492 }
493 
494 FailureOr<bool>
495 PathTracker::getOrComputeNeedsAltBasePath(Location loc, StringAttr moduleName,
496  FModuleOp owningModule,
497  bool isNonLocal) {
498 
499  auto it = needsAltBasePathCache.find({moduleName, owningModule});
500  if (it != needsAltBasePathCache.end())
501  return it->second;
502  bool needsAltBasePath = false;
503  auto *node = instanceGraph.lookup(moduleName);
504  while (true) {
505  // If the path is rooted at the owning module, we're done.
506  if (node->getModule() == owningModule)
507  break;
508  // If there are no more parents, then the path op lives in a different
509  // hierarchy than the HW object it references, which needs to handled
510  // specially. Flag this, so we know to create an alternative base path
511  // below.
512  if (node->noUses()) {
513  needsAltBasePath = true;
514  break;
515  }
516  // If there is more than one instance of this module, and the target is
517  // non-local, then the path operation is ambiguous, which is an error.
518  if (isNonLocal && !node->hasOneUse()) {
519  auto diag = mlir::emitError(loc)
520  << "unable to uniquely resolve target due "
521  "to multiple instantiation";
522  for (auto *use : node->uses())
523  diag.attachNote(use->getInstance().getLoc()) << "instance here";
524  return diag;
525  }
526  node = (*node->usesBegin())->getParent();
527  }
528  needsAltBasePathCache[{moduleName, owningModule}] = needsAltBasePath;
529  return needsAltBasePath;
530 }
531 
532 FailureOr<AnnotationSet>
533 PathTracker::processPathTrackers(const AnnoTarget &target) {
534  auto error = false;
535  auto annotations = target.getAnnotations();
536  auto *op = target.getOp();
537  annotations.removeAnnotations([&](Annotation anno) {
538  // If there has been an error, just skip this annotation.
539  if (error)
540  return false;
541 
542  // We are looking for OMIR tracker annotations.
543  if (!anno.isClass("circt.tracker"))
544  return false;
545 
546  // The token must have a valid ID.
547  auto id = anno.getMember<DistinctAttr>("id");
548  if (!id) {
549  op->emitError("circt.tracker annotation missing id field");
550  error = true;
551  return false;
552  }
553 
554  // Get the fieldID. If there is none, it is assumed to be 0.
555  uint64_t fieldID = anno.getFieldID();
556 
557  // Attach an inner sym to the operation.
558  Attribute targetSym;
559  if (auto portTarget = dyn_cast<PortAnnoTarget>(target)) {
560  targetSym =
561  getInnerRefTo({portTarget.getPortNo(), portTarget.getOp(), fieldID},
562  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
563  return moduleNamespace;
564  });
565  } else if (auto module = dyn_cast<FModuleLike>(op)) {
566  assert(!fieldID && "field not valid for modules");
567  targetSym = FlatSymbolRefAttr::get(module.getModuleNameAttr());
568  } else {
569  targetSym =
570  getInnerRefTo({target.getOp(), fieldID},
571  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
572  return moduleNamespace;
573  });
574  }
575 
576  // Create the hierarchical path.
577  SmallVector<Attribute> path;
578 
579  // Copy the trailing final target part of the path.
580  path.push_back(targetSym);
581 
582  auto moduleName = target.getModule().getModuleNameAttr();
583 
584  // Verify a nonlocal annotation refers to a HierPathOp.
585  hw::HierPathOp hierPathOp;
586  if (auto hierName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
587  hierPathOp =
588  dyn_cast<hw::HierPathOp>(symbolTable.lookup(hierName.getAttr()));
589  if (!hierPathOp) {
590  op->emitError("annotation does not point at a HierPathOp");
591  error = true;
592  return false;
593  }
594  }
595 
596  // Get the owning module. If there is no owning module, then this
597  // declaration does not have a use, and we can return early.
598  auto owningModule = owningModules.lookup(id);
599  if (!owningModule) {
600  PathInfoTableEntry entry;
601  // This entry is used for checking id uniquness.
602  entry.op = op;
603  entry.id = id;
604  entries.push_back(entry);
605  return true;
606  }
607 
608  // Copy the middle part from the annotation's NLA.
609  if (hierPathOp) {
610  // Get the original path.
611  auto oldPath = hierPathOp.getNamepath().getValue();
612 
613  // Set the moduleName and path based on the hierarchical path. If the
614  // owningModule is in the hierarichal path, start the hierarchical path
615  // there. Otherwise use the top of the hierarchical path.
616  bool pathContainsOwningModule = false;
617  size_t owningModuleIndex = 0;
618  for (auto [idx, pathFramgent] : llvm::enumerate(oldPath)) {
619  if (auto innerRef = dyn_cast<hw::InnerRefAttr>(pathFramgent)) {
620  if (innerRef.getModule() == owningModule.getModuleNameAttr()) {
621  pathContainsOwningModule = true;
622  owningModuleIndex = idx;
623  }
624  } else if (auto symRef = dyn_cast<FlatSymbolRefAttr>(pathFramgent)) {
625  if (symRef.getAttr() == owningModule.getModuleNameAttr()) {
626  pathContainsOwningModule = true;
627  owningModuleIndex = idx;
628  }
629  }
630  }
631 
632  if (pathContainsOwningModule) {
633  // Set the path root module name to the owning module.
634  moduleName = owningModule.getModuleNameAttr();
635 
636  // Copy the old path, dropping the module name and the prefix to the
637  // owning module.
638  llvm::append_range(path, llvm::reverse(oldPath.drop_back().drop_front(
639  owningModuleIndex)));
640  } else {
641  // Set the path root module name to the start of the path.
642  moduleName = cast<hw::InnerRefAttr>(oldPath.front()).getModule();
643 
644  // Copy the old path, dropping the module name.
645  llvm::append_range(path, llvm::reverse(oldPath.drop_back()));
646  }
647  }
648 
649  // Check if we need an alternative base path.
650  auto needsAltBasePath = getOrComputeNeedsAltBasePath(
651  op->getLoc(), moduleName, owningModule, hierPathOp);
652  if (failed(needsAltBasePath)) {
653  error = true;
654  return false;
655  }
656 
657  // Copy the leading part of the hierarchical path from the owning module
658  // to the start of the annotation's NLA.
659  InstanceGraphNode *node = instanceGraph.lookup(moduleName);
660  while (true) {
661  // If it's not a non-local target, we don't have to append anything,
662  // unless it needs an alternative base path, in which case we do need to
663  // make a hierarchical path.
664  if (!hierPathOp && !needsAltBasePath.value())
665  break;
666 
667  // If we get to the owning module or the top, we're done.
668  if (node->getModule() == owningModule || node->noUses())
669  break;
670 
671  // Append the next level of hierarchy to the path.
672  assert(node->hasOneUse() && "expected single instantiation");
673  InstanceRecord *inst = *node->usesBegin();
674  path.push_back(
675  OpAnnoTarget(inst->getInstance<InstanceOp>())
676  .getNLAReference(namespaces[inst->getParent()->getModule()]));
677 
678  node = inst->getParent();
679  }
680 
681  // Create the HierPathOp.
682  std::reverse(path.begin(), path.end());
683  auto pathAttr = ArrayAttr::get(op->getContext(), path);
684 
685  // If we need an alternative base path, save the top module from the
686  // path. We will plumb in the basepath from this module.
687  StringAttr altBasePathModule;
688  if (*needsAltBasePath) {
689  altBasePathModule =
690  TypeSwitch<Attribute, StringAttr>(path.front())
691  .Case<FlatSymbolRefAttr>([](auto a) { return a.getAttr(); })
692  .Case<hw::InnerRefAttr>([](auto a) { return a.getModule(); });
693 
694  altBasePathRoots.insert(altBasePathModule);
695  }
696 
697  // Record the path operation associated with the path op.
698  entries.push_back({op, id, altBasePathModule, pathAttr});
699 
700  // Remove this annotation from the operation.
701  return true;
702  });
703 
704  if (error)
705  return {};
706 
707  return annotations;
708 }
709 
710 LogicalResult PathTracker::updatePathInfoTable(PathInfoTable &pathInfoTable,
711  HierPathCache &cache) const {
712  for (auto root : altBasePathRoots)
713  pathInfoTable.addAltBasePathRoot(root);
714 
715  for (const auto &entry : entries) {
716  // Record the path operation associated with the path op.
717  auto [it, inserted] = pathInfoTable.table.try_emplace(entry.id);
718  auto &pathInfo = it->second;
719  if (!inserted) {
720  assert(pathInfo.loc.has_value() && "all PathInfo should have a Location");
721  auto diag = emitError(pathInfo.loc.value(),
722  "path identifier already found, paths must resolve "
723  "to a unique target");
724  diag.attachNote(entry.op->getLoc()) << "other path identifier here";
725  return failure();
726  }
727 
728  // Check if the op is targetable by an instance target. The op pointer may
729  // be invalidated later, so this is the last time we want to access it here.
730  bool canBeInstanceTarget = isa<InstanceOp, FModuleLike>(entry.op);
731 
732  if (entry.pathAttr) {
733  pathInfo = {entry.op->getLoc(), canBeInstanceTarget,
734  cache.getRefFor(entry.pathAttr), entry.altBasePathModule};
735  } else {
736  pathInfo.loc = entry.op->getLoc();
737  pathInfo.canBeInstanceTarget = canBeInstanceTarget;
738  }
739  }
740  return success();
741 }
742 
743 /// This pass removes the OMIR tracker annotations from operations, and ensures
744 /// that each thing that was targeted has a hierarchical path targeting it. It
745 /// builds a table which maps the original OMIR tracker annotation IDs to the
746 /// corresponding hierarchical paths. We use this table to convert FIRRTL path
747 /// ops to OM. FIRRTL paths refer to their target using a target ID, while OM
748 /// paths refer to their target using hierarchical paths.
749 LogicalResult LowerClassesPass::processPaths(
750  InstanceGraph &instanceGraph,
751  hw::InnerSymbolNamespaceCollection &namespaces, HierPathCache &cache,
752  PathInfoTable &pathInfoTable, SymbolTable &symbolTable) {
753  auto circuit = getOperation();
754 
755  // Collect the path declarations and owning modules.
756  OwningModuleCache owningModuleCache(instanceGraph);
757  DenseMap<DistinctAttr, FModuleOp> owningModules;
758  std::vector<Operation *> declarations;
759  auto result = circuit.walk([&](Operation *op) {
760  if (auto pathOp = dyn_cast<PathOp>(op)) {
761  // Find the owning module of this path reference.
762  auto owningModule = owningModuleCache.lookup(pathOp);
763  // If this reference does not have a single owning module, it is an error.
764  if (!owningModule) {
765  pathOp->emitError("path does not have a single owning module");
766  return WalkResult::interrupt();
767  }
768  auto target = pathOp.getTargetAttr();
769  auto [it, inserted] = owningModules.try_emplace(target, owningModule);
770  // If this declaration already has a reference, both references must have
771  // the same owning module.
772  if (!inserted && it->second != owningModule) {
773  pathOp->emitError()
774  << "path reference " << target << " has conflicting owning modules "
775  << it->second.getModuleNameAttr() << " and "
776  << owningModule.getModuleNameAttr();
777  return WalkResult::interrupt();
778  }
779  }
780  return WalkResult::advance();
781  });
782 
783  if (result.wasInterrupted())
784  return failure();
785 
786  if (failed(PathTracker::run(circuit, instanceGraph, namespaces, cache,
787  pathInfoTable, symbolTable, owningModules)))
788  return failure();
789 
790  // For each module that will be passing through a base path, compute its
791  // descendants that need this base path passed through.
792  for (auto rootModule : pathInfoTable.getAltBasePathRoots()) {
793  InstanceGraphNode *node = instanceGraph.lookup(rootModule);
794 
795  // Do a depth first traversal of the instance graph from rootModule,
796  // looking for descendants that need to be passed through.
797  auto start = llvm::df_begin(node);
798  auto end = llvm::df_end(node);
799  auto it = start;
800  while (it != end) {
801  // Nothing to do for the root module.
802  if (it == start) {
803  ++it;
804  continue;
805  }
806 
807  // If we aren't creating a class for this child, skip this hierarchy.
808  if (!shouldCreateClass(
809  it->getModule<FModuleLike>().getModuleNameAttr())) {
810  it = it.skipChildren();
811  continue;
812  }
813 
814  // If we are at a leaf, nothing to do.
815  if (it->begin() == it->end()) {
816  ++it;
817  continue;
818  }
819 
820  // Track state for this passthrough.
821  StringAttr passthroughModule = it->getModule().getModuleNameAttr();
822  pathInfoTable.addAltBasePathPassthrough(passthroughModule, rootModule);
823 
824  ++it;
825  }
826  }
827 
828  return success();
829 }
830 
831 /// Lower FIRRTL Class and Object ops to OM Class and Object ops
832 void LowerClassesPass::runOnOperation() {
833  MLIRContext *ctx = &getContext();
834  auto intraPassMutex = std::mutex();
835 
836  // Get the CircuitOp.
837  CircuitOp circuit = getOperation();
838 
839  // Get the InstanceGraph and SymbolTable.
840  InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
841  SymbolTable &symbolTable = getAnalysis<SymbolTable>();
842 
843  hw::InnerSymbolNamespaceCollection namespaces;
844  HierPathCache cache(circuit, symbolTable);
845 
846  // Fill `shouldCreateClassMemo`.
847  for (auto *node : instanceGraph)
848  if (auto moduleLike = node->getModule<firrtl::FModuleLike>())
849  shouldCreateClassMemo.insert({moduleLike.getModuleNameAttr(), false});
850 
851  parallelForEach(circuit.getContext(), instanceGraph,
852  [&](igraph::InstanceGraphNode *node) {
853  if (auto moduleLike = node->getModule<FModuleLike>())
854  shouldCreateClassMemo[moduleLike.getModuleNameAttr()] =
855  shouldCreateClassImpl(node);
856  });
857 
858  // Rewrite all path annotations into inner symbol targets.
859  PathInfoTable pathInfoTable;
860  if (failed(processPaths(instanceGraph, namespaces, cache, pathInfoTable,
861  symbolTable))) {
862  signalPassFailure();
863  return;
864  }
865 
866  LoweringState loweringState;
867 
868  // Create new OM Class ops serially.
869  DenseMap<StringAttr, firrtl::ClassType> classTypeTable;
870  for (auto *node : instanceGraph) {
871  auto moduleLike = node->getModule<firrtl::FModuleLike>();
872  if (!moduleLike)
873  continue;
874 
875  if (shouldCreateClass(moduleLike.getModuleNameAttr())) {
876  auto omClass = createClass(moduleLike, pathInfoTable, intraPassMutex);
877  auto &classLoweringState = loweringState.classLoweringStateTable[omClass];
878  classLoweringState.moduleLike = moduleLike;
879 
880  // Find the module instances under the current module with metadata. These
881  // ops will be converted to om objects by this pass. Create a hierarchical
882  // path for each of these instances, which will be used to rebase path
883  // operations. Hierarchical paths must be created serially to ensure their
884  // order in the circuit is deterministc.
885  for (auto *instance : *node) {
886  auto inst = instance->getInstance<firrtl::InstanceOp>();
887  if (!inst)
888  continue;
889  // Get the referenced module.
890  auto module = instance->getTarget()->getModule<FModuleLike>();
891  if (module && shouldCreateClass(module.getModuleNameAttr())) {
892  auto targetSym = getInnerRefTo(
893  {inst, 0}, [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
894  return namespaces[module];
895  });
896  SmallVector<Attribute> path = {targetSym};
897  auto pathAttr = ArrayAttr::get(ctx, path);
898  auto hierPath = cache.getOpFor(pathAttr);
899  classLoweringState.paths.push_back(hierPath);
900  }
901  }
902 
903  if (auto classLike =
904  dyn_cast<firrtl::ClassLike>(moduleLike.getOperation()))
905  classTypeTable[classLike.getNameAttr()] = classLike.getInstanceType();
906  }
907  }
908 
909  // Move ops from FIRRTL Class to OM Class in parallel.
910  mlir::parallelForEach(ctx, loweringState.classLoweringStateTable,
911  [this, &pathInfoTable](auto &entry) {
912  const auto &[classLike, state] = entry;
913  lowerClassLike(state.moduleLike, classLike,
914  pathInfoTable);
915  });
916 
917  // Completely erase Class module-likes, and remove from the InstanceGraph.
918  for (auto &[omClass, state] : loweringState.classLoweringStateTable) {
919  if (isa<firrtl::ClassLike>(state.moduleLike.getOperation())) {
920  InstanceGraphNode *node = instanceGraph.lookup(state.moduleLike);
921  for (auto *use : llvm::make_early_inc_range(node->uses()))
922  use->erase();
923  instanceGraph.erase(node);
924  state.moduleLike.erase();
925  }
926  }
927 
928  // Collect ops where Objects can be instantiated.
929  SmallVector<Operation *> objectContainers;
930  for (auto &op : circuit.getOps())
931  if (isa<FModuleOp, om::ClassLike>(op))
932  objectContainers.push_back(&op);
933 
934  // Update Object creation ops in Classes or Modules in parallel.
935  if (failed(
936  mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
937  return updateInstances(op, instanceGraph, loweringState,
938  pathInfoTable, intraPassMutex);
939  })))
940  return signalPassFailure();
941 
942  // If needed, create and add 'ports' lists of RtlPort objects.
943  if (!rtlPortsToCreate.empty())
944  createAllRtlPorts(pathInfoTable, namespaces, cache);
945 
946  // Convert to OM ops and types in Classes or Modules in parallel.
947  if (failed(
948  mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
949  return dialectConversion(op, pathInfoTable, classTypeTable);
950  })))
951  return signalPassFailure();
952 
953  // We keep the instance graph up to date, so mark that analysis preserved.
954  markAnalysesPreserved<InstanceGraph>();
955 
956  // Reset pass state.
957  rtlPortsToCreate.clear();
958 }
959 
960 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerClassesPass() {
961  return std::make_unique<LowerClassesPass>();
962 }
963 
964 // Predicate to check if a module-like needs a Class to be created.
965 bool LowerClassesPass::shouldCreateClass(StringAttr modName) {
966  // Return a memoized result.
967  return shouldCreateClassMemo.at(modName);
968 }
969 
970 void checkAddContainingModulePorts(bool hasContainingModule, OpBuilder builder,
971  SmallVector<Attribute> &fieldNames,
972  SmallVector<NamedAttribute> &fieldTypes) {
973  if (hasContainingModule) {
974  auto name = builder.getStringAttr(kPortsName);
975  fieldNames.push_back(name);
976  fieldTypes.push_back(NamedAttribute(
977  name, TypeAttr::get(getRtlPortsType(builder.getContext()))));
978  }
979 }
980 
981 static om::ClassLike convertExtClass(FModuleLike moduleLike, OpBuilder builder,
982  Twine name,
983  ArrayRef<StringRef> formalParamNames,
984  bool hasContainingModule) {
985  SmallVector<Attribute> fieldNames;
986  SmallVector<NamedAttribute> fieldTypes;
987  for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
988  auto type = moduleLike.getPortType(i);
989  if (!isa<PropertyType>(type))
990  continue;
991 
992  auto direction = moduleLike.getPortDirection(i);
993  if (direction != Direction::In) {
994  auto name = moduleLike.getPortNameAttr(i);
995  fieldNames.push_back(name);
996  fieldTypes.push_back(NamedAttribute(name, TypeAttr::get(type)));
997  }
998  }
999  checkAddContainingModulePorts(hasContainingModule, builder, fieldNames,
1000  fieldTypes);
1001  return builder.create<om::ClassExternOp>(
1002  moduleLike.getLoc(), name, formalParamNames, fieldNames, fieldTypes);
1003 }
1004 
1005 static om::ClassLike convertClass(FModuleLike moduleLike, OpBuilder builder,
1006  Twine name,
1007  ArrayRef<StringRef> formalParamNames,
1008  bool hasContainingModule) {
1009  // Collect output property assignments to get field names and types.
1010  SmallVector<Attribute> fieldNames;
1011  SmallVector<NamedAttribute> fieldTypes;
1012  for (auto op : llvm::make_early_inc_range(
1013  moduleLike->getRegion(0).getOps<PropAssignOp>())) {
1014  auto outputPort = dyn_cast<BlockArgument>(op.getDest());
1015  if (!outputPort)
1016  continue;
1017 
1018  StringAttr name = moduleLike.getPortNameAttr(outputPort.getArgNumber());
1019 
1020  fieldNames.push_back(name);
1021  fieldTypes.push_back(
1022  NamedAttribute(name, TypeAttr::get(op.getSrc().getType())));
1023  }
1024  checkAddContainingModulePorts(hasContainingModule, builder, fieldNames,
1025  fieldTypes);
1026  return builder.create<om::ClassOp>(moduleLike.getLoc(), name,
1027  formalParamNames, fieldNames, fieldTypes);
1028 }
1029 
1030 // Create an OM Class op from a FIRRTL Class op or Module op with properties.
1031 om::ClassLike LowerClassesPass::createClass(FModuleLike moduleLike,
1032  const PathInfoTable &pathInfoTable,
1033  std::mutex &intraPassMutex) {
1034  // Collect the parameter names from input properties.
1035  SmallVector<StringRef> formalParamNames;
1036  // Every class gets a base path as its first parameter.
1037  formalParamNames.emplace_back("basepath");
1038 
1039  // If this class is passing through base paths from above, add those.
1040  size_t nAltBasePaths =
1041  pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
1042  for (size_t i = 0; i < nAltBasePaths; ++i)
1043  formalParamNames.push_back(StringAttr::get(
1044  moduleLike->getContext(), "alt_basepath_" + llvm::Twine(i)));
1045 
1046  // Collect the input parameters.
1047  bool hasContainingModule = false;
1048  for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
1049  if (port.isInput() && isa<PropertyType>(port.type)) {
1050  formalParamNames.push_back(port.name);
1051 
1052  // Check if we have a 'containingModule' field.
1053  if (port.name.strref().starts_with(kContainingModuleName))
1054  hasContainingModule = true;
1055  }
1056  }
1057 
1058  OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
1059 
1060  // If there is a 'containingModule', add a parameter for 'ports'.
1061  if (hasContainingModule)
1062  formalParamNames.push_back(kPortsName);
1063 
1064  // Take the name from the FIRRTL Class or Module to create the OM Class name.
1065  StringRef className = moduleLike.getName();
1066 
1067  // Use the defname for external modules.
1068  if (auto externMod = dyn_cast<FExtModuleOp>(moduleLike.getOperation()))
1069  if (auto defname = externMod.getDefname())
1070  className = defname.value();
1071 
1072  // If the op is a Module or ExtModule, the OM Class would conflict with the HW
1073  // Module, so give it a suffix. There is no formal ABI for this yet.
1074  StringRef suffix =
1075  isa<FModuleOp, FExtModuleOp>(moduleLike) ? kClassNameSuffix : "";
1076 
1077  // Construct the OM Class with the FIRRTL Class name and parameter names.
1078  om::ClassLike loweredClassOp;
1079  if (isa<firrtl::ExtClassOp, firrtl::FExtModuleOp>(
1080  moduleLike.getOperation())) {
1081  loweredClassOp = convertExtClass(moduleLike, builder, className + suffix,
1082  formalParamNames, hasContainingModule);
1083  } else {
1084  loweredClassOp = convertClass(moduleLike, builder, className + suffix,
1085  formalParamNames, hasContainingModule);
1086  }
1087 
1088  return loweredClassOp;
1089 }
1090 
1091 void LowerClassesPass::lowerClassLike(FModuleLike moduleLike,
1092  om::ClassLike classLike,
1093  const PathInfoTable &pathInfoTable) {
1094 
1095  if (auto classOp = dyn_cast<om::ClassOp>(classLike.getOperation())) {
1096  return lowerClass(classOp, moduleLike, pathInfoTable);
1097  }
1098  if (auto classExternOp =
1099  dyn_cast<om::ClassExternOp>(classLike.getOperation())) {
1100  return lowerClassExtern(classExternOp, moduleLike);
1101  }
1102  llvm_unreachable("unhandled class-like op");
1103 }
1104 
1105 void LowerClassesPass::lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
1106  const PathInfoTable &pathInfoTable) {
1107  // Map from Values in the FIRRTL Class to Values in the OM Class.
1108  IRMapping mapping;
1109 
1110  // Collect information about property ports.
1111  SmallVector<Property> inputProperties;
1112  BitVector portsToErase(moduleLike.getNumPorts());
1113  bool hasContainingModule = false;
1114  for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
1115  // For Module ports that aren't property types, move along.
1116  if (!isa<PropertyType>(port.type))
1117  continue;
1118 
1119  // Remember input properties to create the OM Class formal parameters.
1120  if (port.isInput()) {
1121  inputProperties.push_back({index, port.name, port.type, port.loc});
1122 
1123  // Check if we have a 'containingModule' field.
1124  if (port.name.strref().starts_with(kContainingModuleName))
1125  hasContainingModule = true;
1126  }
1127 
1128  // In case this is a Module, remember to erase this port.
1129  portsToErase.set(index);
1130  }
1131 
1132  // Construct the OM Class body with block arguments for each input property,
1133  // updating the mapping to map from the input property to the block argument.
1134  Block *classBody = &classOp->getRegion(0).emplaceBlock();
1135  // Every class created from a module gets a base path as its first parameter.
1136  auto basePathType = BasePathType::get(&getContext());
1137  auto unknownLoc = UnknownLoc::get(&getContext());
1138  classBody->addArgument(basePathType, unknownLoc);
1139 
1140  // If this class is passing through base paths from above, add those.
1141  size_t nAltBasePaths =
1142  pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
1143  for (size_t i = 0; i < nAltBasePaths; ++i)
1144  classBody->addArgument(basePathType, unknownLoc);
1145 
1146  for (auto inputProperty : inputProperties) {
1147  BlockArgument parameterValue =
1148  classBody->addArgument(inputProperty.type, inputProperty.loc);
1149  BlockArgument inputValue =
1150  moduleLike->getRegion(0).getArgument(inputProperty.index);
1151  mapping.map(inputValue, parameterValue);
1152  }
1153 
1154  // Clone the property ops from the FIRRTL Class or Module to the OM Class.
1155  SmallVector<Operation *> opsToErase;
1156  OpBuilder builder = OpBuilder::atBlockBegin(classOp.getBodyBlock());
1157  llvm::SmallVector<mlir::Location> fieldLocs;
1158  llvm::SmallVector<mlir::Value> fieldValues;
1159  for (auto &op : moduleLike->getRegion(0).getOps()) {
1160  // Check if any operand is a property.
1161  auto propertyOperands = llvm::any_of(op.getOperandTypes(), [](Type type) {
1162  return isa<PropertyType>(type);
1163  });
1164  bool needsClone = false;
1165  if (auto instance = dyn_cast<InstanceOp>(op))
1166  needsClone = shouldCreateClass(instance.getReferencedModuleNameAttr());
1167 
1168  // Check if any result is a property.
1169  auto propertyResults = llvm::any_of(
1170  op.getResultTypes(), [](Type type) { return isa<PropertyType>(type); });
1171 
1172  // If there are no properties here, move along.
1173  if (!needsClone && !propertyOperands && !propertyResults)
1174  continue;
1175 
1176  bool isField = false;
1177  if (auto propAssign = dyn_cast<PropAssignOp>(op)) {
1178  if (isa<BlockArgument>(propAssign.getDest())) {
1179  // Store any output property assignments into fields op inputs.
1180  fieldLocs.push_back(op.getLoc());
1181  fieldValues.push_back(mapping.lookup(propAssign.getSrc()));
1182  isField = true;
1183  }
1184  }
1185 
1186  if (!isField)
1187  // Clone the op over to the OM Class.
1188  builder.clone(op, mapping);
1189 
1190  // In case this is a Module, remember to erase this op, unless it is an
1191  // instance. Instances are handled later in updateInstances.
1192  if (!isa<InstanceOp>(op))
1193  opsToErase.push_back(&op);
1194  }
1195 
1196  // If there is a 'containingModule', add an argument for 'ports', and a field.
1197  if (hasContainingModule) {
1198  BlockArgument argumentValue = classBody->addArgument(
1199  getRtlPortsType(&getContext()), UnknownLoc::get(&getContext()));
1200  fieldLocs.push_back(argumentValue.getLoc());
1201  fieldValues.push_back(argumentValue);
1202  }
1203 
1204  classOp.addNewFieldsOp(builder, fieldLocs, fieldValues);
1205 
1206  // If the module-like is a Class, it will be completely erased later.
1207  // Otherwise, erase just the property ports and ops.
1208  if (!isa<firrtl::ClassLike>(moduleLike.getOperation())) {
1209  // Erase ops in use before def order, thanks to FIRRTL's SSA regions.
1210  for (auto *op : llvm::reverse(opsToErase))
1211  op->erase();
1212 
1213  // Erase property typed ports.
1214  moduleLike.erasePorts(portsToErase);
1215  }
1216 }
1217 
1218 void LowerClassesPass::lowerClassExtern(ClassExternOp classExternOp,
1219  FModuleLike moduleLike) {
1220  // Construct the OM Class body.
1221  // Add a block arguments for each input property.
1222  // Add a class.extern.field op for each output.
1223  BitVector portsToErase(moduleLike.getNumPorts());
1224  Block *classBody = &classExternOp.getRegion().emplaceBlock();
1225 
1226  // Every class gets a base path as its first parameter.
1227  classBody->addArgument(BasePathType::get(&getContext()),
1228  UnknownLoc::get(&getContext()));
1229 
1230  for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
1231  auto type = moduleLike.getPortType(i);
1232  if (!isa<PropertyType>(type))
1233  continue;
1234 
1235  auto loc = moduleLike.getPortLocation(i);
1236  auto direction = moduleLike.getPortDirection(i);
1237  if (direction == Direction::In)
1238  classBody->addArgument(type, loc);
1239 
1240  // In case this is a Module, remember to erase this port.
1241  portsToErase.set(i);
1242  }
1243 
1244  // If the module-like is a Class, it will be completely erased later.
1245  // Otherwise, erase just the property ports and ops.
1246  if (!isa<firrtl::ClassLike>(moduleLike.getOperation())) {
1247  // Erase property typed ports.
1248  moduleLike.erasePorts(portsToErase);
1249  }
1250 }
1251 
1252 // Helper to update an Object instantiation. FIRRTL Object instances are
1253 // converted to OM Object instances.
1254 static LogicalResult updateObjectInClass(
1255  firrtl::ObjectOp firrtlObject, const PathInfoTable &pathInfoTable,
1256  SmallVectorImpl<RtlPortsInfo> &rtlPortsToCreate, std::mutex &intraPassMutex,
1257  SmallVectorImpl<Operation *> &opsToErase) {
1258  // The 0'th argument is the base path.
1259  auto basePath = firrtlObject->getBlock()->getArgument(0);
1260  // build a table mapping the indices of input ports to their position in the
1261  // om class's parameter list.
1262  auto firrtlClassType = firrtlObject.getType();
1263  auto numElements = firrtlClassType.getNumElements();
1264  llvm::SmallVector<unsigned> argIndexTable;
1265  argIndexTable.resize(numElements);
1266 
1267  // Get any alternative base paths passing through this module.
1268  SmallVector<Value> altBasePaths;
1269  pathInfoTable.collectAltBasePaths(
1270  firrtlObject, firrtlClassType.getNameAttr().getAttr(), altBasePaths);
1271 
1272  // Account for the default base path and any alternatives.
1273  unsigned nextArgIndex = 1 + altBasePaths.size();
1274 
1275  for (unsigned i = 0; i < numElements; ++i) {
1276  auto direction = firrtlClassType.getElement(i).direction;
1277  if (direction == Direction::In)
1278  argIndexTable[i] = nextArgIndex++;
1279  }
1280 
1281  // Collect its input actual parameters by finding any subfield ops that are
1282  // assigned to. Take the source of the assignment as the actual parameter.
1283 
1284  llvm::SmallVector<Value> args;
1285  args.resize(nextArgIndex);
1286  args[0] = basePath;
1287 
1288  // Collect any alternative base paths passing through.
1289  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths))
1290  args[1 + i] = altBasePath; // + 1 to skip default base path
1291 
1292  firrtl::PathOp containingModuleRef;
1293  for (auto *user : llvm::make_early_inc_range(firrtlObject->getUsers())) {
1294  if (auto subfield = dyn_cast<ObjectSubfieldOp>(user)) {
1295  auto index = subfield.getIndex();
1296  auto direction = firrtlClassType.getElement(index).direction;
1297 
1298  // We only lower "writes to input ports" here. Reads from output
1299  // ports will be handled using the conversion framework.
1300  if (direction == Direction::Out)
1301  continue;
1302 
1303  for (auto *subfieldUser :
1304  llvm::make_early_inc_range(subfield->getUsers())) {
1305  if (auto propassign = dyn_cast<PropAssignOp>(subfieldUser)) {
1306  // the operands of the propassign may have already been converted to
1307  // om. Use the generic operand getters to get the operands as
1308  // untyped values.
1309  auto dst = propassign.getOperand(0);
1310  auto src = propassign.getOperand(1);
1311  if (dst == subfield.getResult()) {
1312  args[argIndexTable[index]] = src;
1313  opsToErase.push_back(propassign);
1314 
1315  // Check if we have a 'containingModule' field.
1316  if (firrtlClassType.getElement(index).name.strref().starts_with(
1317  kContainingModuleName)) {
1318  assert(!containingModuleRef &&
1319  "expected exactly one containingModule");
1320  assert(isa_and_nonnull<firrtl::PathOp>(src.getDefiningOp()) &&
1321  "expected containingModule to be a PathOp");
1322  containingModuleRef = src.getDefiningOp<firrtl::PathOp>();
1323  }
1324  }
1325  }
1326  }
1327 
1328  opsToErase.push_back(subfield);
1329  }
1330  }
1331 
1332  // Check that all input ports have been initialized.
1333  for (unsigned i = 0; i < numElements; ++i) {
1334  auto element = firrtlClassType.getElement(i);
1335  if (element.direction == Direction::Out)
1336  continue;
1337 
1338  auto argIndex = argIndexTable[i];
1339  if (!args[argIndex])
1340  return emitError(firrtlObject.getLoc())
1341  << "uninitialized input port " << element.name;
1342  }
1343 
1344  // Convert the FIRRTL Class type to an OM Class type.
1345  auto className = firrtlObject.getType().getNameAttr();
1346  auto classType = om::ClassType::get(firrtlObject->getContext(), className);
1347 
1348  // Create the new Object op.
1349  OpBuilder builder(firrtlObject);
1350 
1351  auto object = builder.create<om::ObjectOp>(
1352  firrtlObject.getLoc(), classType, firrtlObject.getClassNameAttr(), args);
1353 
1354  // If there is a 'containingModule', track that we need to add 'ports'.
1355  if (containingModuleRef) {
1356  std::lock_guard<std::mutex> guard(intraPassMutex);
1357  rtlPortsToCreate.push_back({containingModuleRef, basePath, object});
1358  }
1359 
1360  // Replace uses of the FIRRTL Object with the OM Object. The later dialect
1361  // conversion will take care of converting the types.
1362  firrtlObject.replaceAllUsesWith(object.getResult());
1363 
1364  // Erase the original Object, now that we're done with it.
1365  opsToErase.push_back(firrtlObject);
1366  return success();
1367 }
1368 
1369 // Helper to update a Module instantiation in a Class. Module instances within a
1370 // Class are converted to OM Object instances of the Class derived from the
1371 // Module.
1372 static LogicalResult
1373 updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath,
1374  InstanceGraph &instanceGraph,
1375  const PathInfoTable &pathInfoTable,
1376  SmallVectorImpl<Operation *> &opsToErase) {
1377 
1378  // Set the insertion point right before the instance op.
1379  OpBuilder builder(firrtlInstance);
1380 
1381  // Collect the FIRRTL instance inputs to form the Object instance actual
1382  // parameters. The order of the SmallVector needs to match the order the
1383  // formal parameters are declared on the corresponding Class.
1384  SmallVector<Value> actualParameters;
1385  // The 0'th argument is the base path.
1386  auto basePath = firrtlInstance->getBlock()->getArgument(0);
1387  auto symRef = FlatSymbolRefAttr::get(hierPath.getSymNameAttr());
1388  auto rebasedPath = builder.create<om::BasePathCreateOp>(
1389  firrtlInstance->getLoc(), basePath, symRef);
1390 
1391  actualParameters.push_back(rebasedPath);
1392 
1393  // Add any alternative base paths passing through this instance.
1394  pathInfoTable.collectAltBasePaths(
1395  firrtlInstance, firrtlInstance.getModuleNameAttr().getAttr(),
1396  actualParameters);
1397 
1398  for (auto result : firrtlInstance.getResults()) {
1399  // If the port is an output, continue.
1400  if (firrtlInstance.getPortDirection(result.getResultNumber()) ==
1401  Direction::Out)
1402  continue;
1403 
1404  // If the port is not a property type, continue.
1405  auto propertyResult = dyn_cast<FIRRTLPropertyValue>(result);
1406  if (!propertyResult)
1407  continue;
1408 
1409  // Get the property assignment to the input, and track the assigned
1410  // Value as an actual parameter to the Object instance.
1411  auto propertyAssignment = getPropertyAssignment(propertyResult);
1412  assert(propertyAssignment && "properties require single assignment");
1413  actualParameters.push_back(propertyAssignment.getSrcMutable().get());
1414 
1415  // Erase the property assignment.
1416  opsToErase.push_back(propertyAssignment);
1417  }
1418 
1419  // Get the referenced module to get its name.
1420  auto referencedModule =
1421  firrtlInstance.getReferencedModule<FModuleLike>(instanceGraph);
1422 
1423  StringRef moduleName = referencedModule.getName();
1424 
1425  // Use the defname for external modules.
1426  if (auto externMod = dyn_cast<FExtModuleOp>(referencedModule.getOperation()))
1427  if (auto defname = externMod.getDefname())
1428  moduleName = defname.value();
1429 
1430  // Convert the FIRRTL Module name to an OM Class type.
1431  auto className = FlatSymbolRefAttr::get(
1432  builder.getStringAttr(moduleName + kClassNameSuffix));
1433 
1434  auto classType = om::ClassType::get(firrtlInstance->getContext(), className);
1435 
1436  // Create the new Object op.
1437  auto object =
1438  builder.create<om::ObjectOp>(firrtlInstance.getLoc(), classType,
1439  className.getAttr(), actualParameters);
1440 
1441  // Replace uses of the FIRRTL instance outputs with field access into
1442  // the OM Object. The later dialect conversion will take care of
1443  // converting the types.
1444  for (auto result : firrtlInstance.getResults()) {
1445  // If the port isn't an output, continue.
1446  if (firrtlInstance.getPortDirection(result.getResultNumber()) !=
1447  Direction::Out)
1448  continue;
1449 
1450  // If the port is not a property type, continue.
1451  if (!isa<PropertyType>(result.getType()))
1452  continue;
1453 
1454  // The path to the field is just this output's name.
1455  auto objectFieldPath = builder.getArrayAttr({FlatSymbolRefAttr::get(
1456  firrtlInstance.getPortName(result.getResultNumber()))});
1457 
1458  // Create the field access.
1459  auto objectField = builder.create<ObjectFieldOp>(
1460  object.getLoc(), result.getType(), object, objectFieldPath);
1461 
1462  result.replaceAllUsesWith(objectField);
1463  }
1464 
1465  // Erase the original instance, now that we're done with it.
1466  opsToErase.push_back(firrtlInstance);
1467  return success();
1468 }
1469 
1470 // Helper to update a Module instantiation in a Module. Module instances within
1471 // a Module are updated to remove the property typed ports.
1472 static LogicalResult
1473 updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph,
1474  SmallVectorImpl<Operation *> &opsToErase) {
1475  // Collect property typed ports to erase.
1476  BitVector portsToErase(firrtlInstance.getNumResults());
1477  for (auto result : firrtlInstance.getResults())
1478  if (isa<PropertyType>(result.getType()))
1479  portsToErase.set(result.getResultNumber());
1480 
1481  // If there are none, nothing to do.
1482  if (portsToErase.none())
1483  return success();
1484 
1485  // Create a new instance with the property ports removed.
1486  OpBuilder builder(firrtlInstance);
1487  InstanceOp newInstance = firrtlInstance.erasePorts(builder, portsToErase);
1488 
1489  // Replace the instance in the instance graph. This is called from multiple
1490  // threads, but because the instance graph data structure is not mutated, and
1491  // only one thread ever sets the instance pointer for a given instance, this
1492  // should be safe.
1493  instanceGraph.replaceInstance(firrtlInstance, newInstance);
1494 
1495  // Erase the original instance, which is now replaced.
1496  opsToErase.push_back(firrtlInstance);
1497  return success();
1498 }
1499 
1500 static LogicalResult
1501 updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph,
1502  SmallVectorImpl<Operation *> &opsToErase) {
1503  OpBuilder builder(moduleOp);
1504  for (auto &op : moduleOp->getRegion(0).getOps()) {
1505  if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1506  assert(0 && "should be no objects in modules");
1507  } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1508  if (failed(updateInstanceInModule(instanceOp, instanceGraph, opsToErase)))
1509  return failure();
1510  }
1511  }
1512  return success();
1513 }
1514 
1516  om::ClassOp classOp, InstanceGraph &instanceGraph,
1517  const LoweringState &state, const PathInfoTable &pathInfoTable,
1518  SmallVectorImpl<RtlPortsInfo> &rtlPortsToCreate, std::mutex &intraPassMutex,
1519  SmallVectorImpl<Operation *> &opsToErase) {
1520  OpBuilder builder(classOp);
1521  auto &classState = state.classLoweringStateTable.at(classOp);
1522  auto it = classState.paths.begin();
1523  for (auto &op : classOp->getRegion(0).getOps()) {
1524  if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1525  if (failed(updateObjectInClass(objectOp, pathInfoTable, rtlPortsToCreate,
1526  intraPassMutex, opsToErase)))
1527  return failure();
1528  } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1529  if (failed(updateInstanceInClass(instanceOp, *it++, instanceGraph,
1530  pathInfoTable, opsToErase)))
1531  return failure();
1532  }
1533  }
1534  return success();
1535 }
1536 
1537 // Update Object or Module instantiations in a FIRRTL Module or OM Class.
1538 LogicalResult LowerClassesPass::updateInstances(
1539  Operation *op, InstanceGraph &instanceGraph, const LoweringState &state,
1540  const PathInfoTable &pathInfoTable, std::mutex &intraPassMutex) {
1541 
1542  // Track ops to erase at the end. We can't do this eagerly, since we want to
1543  // loop over each op in the container's body, and we may end up removing some
1544  // ops later in the body when we visit instances earlier in the body.
1545  SmallVector<Operation *> opsToErase;
1546  auto result =
1547  TypeSwitch<Operation *, LogicalResult>(op)
1548 
1549  .Case([&](FModuleOp moduleOp) {
1550  // Convert FIRRTL Module instance within a Module to
1551  // remove property ports if necessary.
1552  return updateInstancesInModule(moduleOp, instanceGraph, opsToErase);
1553  })
1554  .Case([&](om::ClassOp classOp) {
1555  // Convert FIRRTL Module instance within a Class to OM
1556  // Object instance.
1558  classOp, instanceGraph, state, pathInfoTable, rtlPortsToCreate,
1559  intraPassMutex, opsToErase);
1560  })
1561  .Default([](auto *op) { return success(); });
1562  if (failed(result))
1563  return result;
1564  // Erase the ops marked to be erased.
1565  for (auto *op : opsToErase)
1566  op->erase();
1567 
1568  return success();
1569 }
1570 
1571 // Create and add all 'ports' lists of RtlPort objects for each object.
1572 void LowerClassesPass::createAllRtlPorts(
1573  const PathInfoTable &pathInfoTable,
1574  hw::InnerSymbolNamespaceCollection &namespaces,
1575  HierPathCache &hierPathCache) {
1576  MLIRContext *ctx = &getContext();
1577 
1578  // Get a builder initialized to the end of the top-level module.
1579  OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
1580 
1581  // Declare an RtlPort class on the fly.
1582  om::ClassOp::buildSimpleClassOp(
1583  builder, UnknownLoc::get(ctx), kRtlPortClassName,
1584  {"ref", "direction", "width"}, {"ref", "direction", "width"},
1586  om::OMIntegerType::get(ctx)});
1587 
1588  // Sort the collected rtlPortsToCreate and process each.
1589  llvm::stable_sort(rtlPortsToCreate, [](auto lhs, auto rhs) {
1590  return lhs.object.getClassName() < rhs.object.getClassName();
1591  });
1592 
1593  // Create each 'ports' list.
1594  for (auto rtlPortToCreate : rtlPortsToCreate)
1595  createRtlPorts(rtlPortToCreate, pathInfoTable, namespaces, hierPathCache,
1596  builder);
1597 }
1598 
1599 // Pattern rewriters for dialect conversion.
1600 
1602  : public OpConversionPattern<FIntegerConstantOp> {
1603  using OpConversionPattern::OpConversionPattern;
1604 
1605  LogicalResult
1606  matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor,
1607  ConversionPatternRewriter &rewriter) const override {
1608  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1609  op, om::OMIntegerType::get(op.getContext()),
1610  om::IntegerAttr::get(op.getContext(), adaptor.getValueAttr()));
1611  return success();
1612  }
1613 };
1614 
1615 struct BoolConstantOpConversion : public OpConversionPattern<BoolConstantOp> {
1616  using OpConversionPattern::OpConversionPattern;
1617 
1618  LogicalResult
1619  matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor,
1620  ConversionPatternRewriter &rewriter) const override {
1621  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1622  op, rewriter.getBoolAttr(adaptor.getValue()));
1623  return success();
1624  }
1625 };
1626 
1628  : public OpConversionPattern<DoubleConstantOp> {
1629  using OpConversionPattern::OpConversionPattern;
1630 
1631  LogicalResult
1632  matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor,
1633  ConversionPatternRewriter &rewriter) const override {
1634  rewriter.replaceOpWithNewOp<om::ConstantOp>(op, adaptor.getValue());
1635  return success();
1636  }
1637 };
1638 
1640  : public OpConversionPattern<StringConstantOp> {
1641  using OpConversionPattern::OpConversionPattern;
1642 
1643  LogicalResult
1644  matchAndRewrite(StringConstantOp op, OpAdaptor adaptor,
1645  ConversionPatternRewriter &rewriter) const override {
1646  auto stringType = om::StringType::get(op.getContext());
1647  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1648  op, stringType, StringAttr::get(op.getValue(), stringType));
1649  return success();
1650  }
1651 };
1652 
1654  : public OpConversionPattern<firrtl::ListCreateOp> {
1655  using OpConversionPattern::OpConversionPattern;
1656 
1657  LogicalResult
1658  matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor,
1659  ConversionPatternRewriter &rewriter) const override {
1660  auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1661  if (!listType)
1662  return failure();
1663  rewriter.replaceOpWithNewOp<om::ListCreateOp>(op, listType,
1664  adaptor.getElements());
1665  return success();
1666  }
1667 };
1668 
1670  : public OpConversionPattern<firrtl::ListConcatOp> {
1671  using OpConversionPattern::OpConversionPattern;
1672 
1673  LogicalResult
1674  matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor,
1675  ConversionPatternRewriter &rewriter) const override {
1676  auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1677  if (!listType)
1678  return failure();
1679  rewriter.replaceOpWithNewOp<om::ListConcatOp>(op, listType,
1680  adaptor.getSubLists());
1681  return success();
1682  }
1683 };
1684 
1686  : public OpConversionPattern<firrtl::IntegerAddOp> {
1687  using OpConversionPattern::OpConversionPattern;
1688 
1689  LogicalResult
1690  matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor,
1691  ConversionPatternRewriter &rewriter) const override {
1692  rewriter.replaceOpWithNewOp<om::IntegerAddOp>(op, adaptor.getLhs(),
1693  adaptor.getRhs());
1694  return success();
1695  }
1696 };
1697 
1699  : public OpConversionPattern<firrtl::IntegerMulOp> {
1700  using OpConversionPattern::OpConversionPattern;
1701 
1702  LogicalResult
1703  matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor,
1704  ConversionPatternRewriter &rewriter) const override {
1705  rewriter.replaceOpWithNewOp<om::IntegerMulOp>(op, adaptor.getLhs(),
1706  adaptor.getRhs());
1707  return success();
1708  }
1709 };
1710 
1712  : public OpConversionPattern<firrtl::IntegerShrOp> {
1713  using OpConversionPattern::OpConversionPattern;
1714 
1715  LogicalResult
1716  matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor,
1717  ConversionPatternRewriter &rewriter) const override {
1718  rewriter.replaceOpWithNewOp<om::IntegerShrOp>(op, adaptor.getLhs(),
1719  adaptor.getRhs());
1720  return success();
1721  }
1722 };
1723 
1725  : public OpConversionPattern<firrtl::IntegerShlOp> {
1726  using OpConversionPattern::OpConversionPattern;
1727 
1728  LogicalResult
1729  matchAndRewrite(firrtl::IntegerShlOp op, OpAdaptor adaptor,
1730  ConversionPatternRewriter &rewriter) const override {
1731  rewriter.replaceOpWithNewOp<om::IntegerShlOp>(op, adaptor.getLhs(),
1732  adaptor.getRhs());
1733  return success();
1734  }
1735 };
1736 
1737 struct PathOpConversion : public OpConversionPattern<firrtl::PathOp> {
1738 
1739  PathOpConversion(TypeConverter &typeConverter, MLIRContext *context,
1740  const PathInfoTable &pathInfoTable,
1741  PatternBenefit benefit = 1)
1742  : OpConversionPattern(typeConverter, context, benefit),
1743  pathInfoTable(pathInfoTable) {}
1744 
1745  LogicalResult
1746  matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor,
1747  ConversionPatternRewriter &rewriter) const override {
1748  auto *context = op->getContext();
1749  auto pathType = om::PathType::get(context);
1750  auto pathInfoIt = pathInfoTable.table.find(op.getTarget());
1751 
1752  // The 0'th argument is the base path by default.
1753  auto basePath = op->getBlock()->getArgument(0);
1754 
1755  // If the target was optimized away, then replace the path operation with
1756  // a deleted path.
1757  if (pathInfoIt == pathInfoTable.table.end()) {
1758  if (op.getTargetKind() == firrtl::TargetKind::DontTouch)
1759  return emitError(op.getLoc(), "DontTouch target was deleted");
1760  if (op.getTargetKind() == firrtl::TargetKind::Instance)
1761  return emitError(op.getLoc(), "Instance target was deleted");
1762  rewriter.replaceOpWithNewOp<om::EmptyPathOp>(op);
1763  return success();
1764  }
1765 
1766  auto pathInfo = pathInfoIt->second;
1767  auto symbol = pathInfo.symRef;
1768 
1769  // Convert the target kind to an OMIR target. Member references are updated
1770  // to reflect the current kind of reference.
1771  om::TargetKind targetKind;
1772  switch (op.getTargetKind()) {
1773  case firrtl::TargetKind::DontTouch:
1774  targetKind = om::TargetKind::DontTouch;
1775  break;
1776  case firrtl::TargetKind::Reference:
1777  targetKind = om::TargetKind::Reference;
1778  break;
1779  case firrtl::TargetKind::Instance:
1780  if (!pathInfo.canBeInstanceTarget)
1781  return emitError(op.getLoc(), "invalid target for instance path")
1782  .attachNote(pathInfo.loc)
1783  << "target not instance or module";
1784  targetKind = om::TargetKind::Instance;
1785  break;
1786  case firrtl::TargetKind::MemberInstance:
1787  case firrtl::TargetKind::MemberReference:
1788  if (pathInfo.canBeInstanceTarget)
1789  targetKind = om::TargetKind::MemberInstance;
1790  else
1791  targetKind = om::TargetKind::MemberReference;
1792  break;
1793  }
1794 
1795  // If we are using an alternative base path for this path, get it from the
1796  // passthrough port on the enclosing class.
1797  if (auto altBasePathModule = pathInfo.altBasePathModule) {
1798  // Get the original name of the parent. At this point both FIRRTL classes
1799  // and modules have been converted to OM classes, but we need to look up
1800  // based on the parent's original name.
1801  auto parent = op->getParentOfType<om::ClassOp>();
1802  auto parentName = parent.getName();
1803  if (parentName.ends_with(kClassNameSuffix))
1804  parentName = parentName.drop_back(kClassNameSuffix.size());
1805  auto originalParentName = StringAttr::get(op->getContext(), parentName);
1806 
1807  // Get the base paths passing through the parent.
1808  auto altBasePaths =
1809  pathInfoTable.getRootsForPassthrough(originalParentName);
1810  assert(!altBasePaths.empty() && "expected passthrough base paths");
1811 
1812  // Find the base path passthrough that was associated with this path.
1813  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
1814  if (altBasePathModule == altBasePath) {
1815  // + 1 to skip default base path
1816  auto basePathArg = op->getBlock()->getArgument(1 + i);
1817  assert(isa<om::BasePathType>(basePathArg.getType()) &&
1818  "expected a passthrough base path");
1819  basePath = basePathArg;
1820  }
1821  }
1822  }
1823 
1824  rewriter.replaceOpWithNewOp<om::PathCreateOp>(
1825  op, pathType, om::TargetKindAttr::get(op.getContext(), targetKind),
1826  basePath, symbol);
1827  return success();
1828  }
1829 
1830  const PathInfoTable &pathInfoTable;
1831 };
1832 
1833 struct WireOpConversion : public OpConversionPattern<WireOp> {
1834  using OpConversionPattern::OpConversionPattern;
1835 
1836  LogicalResult
1837  matchAndRewrite(WireOp wireOp, OpAdaptor adaptor,
1838  ConversionPatternRewriter &rewriter) const override {
1839  auto wireValue = dyn_cast<FIRRTLPropertyValue>(wireOp.getResult());
1840 
1841  // If the wire isn't a Property, not much we can do here.
1842  if (!wireValue)
1843  return failure();
1844 
1845  // If the wire isn't inside a graph region, we can't trivially remove it. In
1846  // practice, this pattern does run for wires in graph regions, so this check
1847  // should pass and we can proceed with the trivial rewrite.
1848  auto regionKindInterface = wireOp->getParentOfType<RegionKindInterface>();
1849  if (!regionKindInterface)
1850  return failure();
1851  if (regionKindInterface.getRegionKind(0) != RegionKind::Graph)
1852  return failure();
1853 
1854  // Find the assignment to the wire.
1855  PropAssignOp propAssign = getPropertyAssignment(wireValue);
1856 
1857  // Use the source of the assignment instead of the wire.
1858  rewriter.replaceOp(wireOp, propAssign.getSrc());
1859 
1860  // Erase the source of the assignment.
1861  rewriter.eraseOp(propAssign);
1862 
1863  return success();
1864  }
1865 };
1866 
1867 struct AnyCastOpConversion : public OpConversionPattern<ObjectAnyRefCastOp> {
1868  using OpConversionPattern::OpConversionPattern;
1869 
1870  LogicalResult
1871  matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor,
1872  ConversionPatternRewriter &rewriter) const override {
1873  rewriter.replaceOpWithNewOp<AnyCastOp>(op, adaptor.getInput());
1874  return success();
1875  }
1876 };
1877 
1879  : public OpConversionPattern<firrtl::ObjectSubfieldOp> {
1880  using OpConversionPattern::OpConversionPattern;
1881 
1883  const TypeConverter &typeConverter, MLIRContext *context,
1884  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable)
1885  : OpConversionPattern(typeConverter, context),
1886  classTypeTable(classTypeTable) {}
1887 
1888  LogicalResult
1889  matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor,
1890  ConversionPatternRewriter &rewriter) const override {
1891  auto omClassType = dyn_cast<om::ClassType>(adaptor.getInput().getType());
1892  if (!omClassType)
1893  return failure();
1894 
1895  // Convert the field-index used by the firrtl implementation, to a symbol,
1896  // as used by the om implementation.
1897  auto firrtlClassType =
1898  classTypeTable.lookup(omClassType.getClassName().getAttr());
1899  if (!firrtlClassType)
1900  return failure();
1901 
1902  const auto &element = firrtlClassType.getElement(op.getIndex());
1903  // We cannot convert input ports to fields.
1904  if (element.direction == Direction::In)
1905  return failure();
1906 
1907  auto field = FlatSymbolRefAttr::get(element.name);
1908  auto path = rewriter.getArrayAttr({field});
1909  auto type = typeConverter->convertType(element.type);
1910  rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(op, type, adaptor.getInput(),
1911  path);
1912  return success();
1913  }
1914 
1915  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable;
1916 };
1917 
1918 struct ClassFieldsOpConversion : public OpConversionPattern<ClassFieldsOp> {
1919  using OpConversionPattern::OpConversionPattern;
1920 
1921  LogicalResult
1922  matchAndRewrite(ClassFieldsOp op, OpAdaptor adaptor,
1923  ConversionPatternRewriter &rewriter) const override {
1924  rewriter.replaceOpWithNewOp<ClassFieldsOp>(op, adaptor.getOperands());
1925  return success();
1926  }
1927 };
1928 
1929 struct ObjectOpConversion : public OpConversionPattern<om::ObjectOp> {
1930  using OpConversionPattern::OpConversionPattern;
1931 
1932  LogicalResult
1933  matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor,
1934  ConversionPatternRewriter &rewriter) const override {
1935  // Replace the object with a new object using the converted actual parameter
1936  // types from the adaptor.
1937  rewriter.replaceOpWithNewOp<om::ObjectOp>(objectOp, objectOp.getType(),
1938  adaptor.getClassNameAttr(),
1939  adaptor.getActualParams());
1940  return success();
1941  }
1942 };
1943 
1944 static LogicalResult convertClassLike(om::ClassLike classOp,
1945  TypeConverter typeConverter,
1946  ConversionPatternRewriter &rewriter) {
1947  Block *body = classOp.getBodyBlock();
1948  TypeConverter::SignatureConversion result(body->getNumArguments());
1949 
1950  // Convert block argument types.
1951  if (failed(
1952  typeConverter.convertSignatureArgs(body->getArgumentTypes(), result)))
1953  return failure();
1954 
1955  // Convert the body.
1956  if (failed(rewriter.convertRegionTypes(body->getParent(), typeConverter,
1957  &result)))
1958  return failure();
1959 
1960  rewriter.modifyOpInPlace(classOp, [&]() {
1961  mlir::AttrTypeReplacer replacer;
1962  replacer.addReplacement([&](TypeAttr typeAttr) {
1963  return mlir::TypeAttr::get(
1964  typeConverter.convertType(typeAttr.getValue()));
1965  });
1966  classOp.replaceFieldTypes(replacer);
1967  });
1968 
1969  return success();
1970 }
1971 
1972 struct ClassOpSignatureConversion : public OpConversionPattern<om::ClassOp> {
1973  using OpConversionPattern::OpConversionPattern;
1974 
1975  LogicalResult
1976  matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor,
1977  ConversionPatternRewriter &rewriter) const override {
1978  return convertClassLike(classOp, *typeConverter, rewriter);
1979  }
1980 };
1981 
1983  : public OpConversionPattern<om::ClassExternOp> {
1984  using OpConversionPattern::OpConversionPattern;
1985 
1986  LogicalResult
1987  matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor,
1988  ConversionPatternRewriter &rewriter) const override {
1989  return convertClassLike(classOp, *typeConverter, rewriter);
1990  }
1991 };
1992 
1993 struct ObjectFieldOpConversion : public OpConversionPattern<ObjectFieldOp> {
1994  using OpConversionPattern::OpConversionPattern;
1995 
1996  LogicalResult
1997  matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor,
1998  ConversionPatternRewriter &rewriter) const override {
1999  // Replace the object field with a new object field of the appropriate
2000  // result type based on the type converter.
2001  auto type = typeConverter->convertType(op.getType());
2002  if (!type)
2003  return failure();
2004 
2005  rewriter.replaceOpWithNewOp<ObjectFieldOp>(op, type, adaptor.getObject(),
2006  adaptor.getFieldPathAttr());
2007 
2008  return success();
2009  }
2010 };
2011 
2012 // Helpers for dialect conversion setup.
2013 
2014 static void populateConversionTarget(ConversionTarget &target) {
2015  // FIRRTL dialect operations inside ClassOps or not using only OM types must
2016  // be legalized.
2017  target.addDynamicallyLegalDialect<FIRRTLDialect>(
2018  [](Operation *op) { return !op->getParentOfType<om::ClassLike>(); });
2019 
2020  // OM dialect operations are legal if they don't use FIRRTL types.
2021  target.addDynamicallyLegalDialect<OMDialect>([](Operation *op) {
2022  auto containsFIRRTLType = [](Type type) {
2023  return type
2024  .walk([](Type type) {
2025  return failure(isa<FIRRTLDialect>(type.getDialect()));
2026  })
2027  .wasInterrupted();
2028  };
2029  auto noFIRRTLOperands =
2030  llvm::none_of(op->getOperandTypes(), [&containsFIRRTLType](Type type) {
2031  return containsFIRRTLType(type);
2032  });
2033  auto noFIRRTLResults =
2034  llvm::none_of(op->getResultTypes(), [&containsFIRRTLType](Type type) {
2035  return containsFIRRTLType(type);
2036  });
2037  return noFIRRTLOperands && noFIRRTLResults;
2038  });
2039 
2040  // OM Class ops are legal if they don't use FIRRTL types for block arguments.
2041  target.addDynamicallyLegalOp<om::ClassOp, om::ClassExternOp>(
2042  [](Operation *op) -> std::optional<bool> {
2043  auto classLike = dyn_cast<om::ClassLike>(op);
2044  if (!classLike)
2045  return std::nullopt;
2046  auto fieldNames = classLike.getFieldNames();
2047  if (!llvm::all_of(fieldNames, [&](auto field) {
2048  std::optional<Type> type =
2049  classLike.getFieldType(cast<StringAttr>(field));
2050  return type.has_value() && !isa<FIRRTLType>(type.value());
2051  }))
2052  return false;
2053 
2054  return llvm::none_of(
2055  classLike.getBodyBlock()->getArgumentTypes(),
2056  [](Type type) { return isa<FIRRTLDialect>(type.getDialect()); });
2057  });
2058 }
2059 
2060 static void populateTypeConverter(TypeConverter &converter) {
2061  // Convert FIntegerType to IntegerType.
2062  converter.addConversion(
2063  [](IntegerType type) { return OMIntegerType::get(type.getContext()); });
2064  converter.addConversion([](FIntegerType type) {
2065  // The actual width of the IntegerType doesn't actually get used; it will be
2066  // folded away by the dialect conversion infrastructure to the type of the
2067  // APSIntAttr used in the FIntegerConstantOp.
2068  return OMIntegerType::get(type.getContext());
2069  });
2070 
2071  // Convert FIRRTL StringType to OM StringType.
2072  converter.addConversion([](om::StringType type) { return type; });
2073  converter.addConversion([](firrtl::StringType type) {
2074  return om::StringType::get(type.getContext());
2075  });
2076 
2077  // Convert FIRRTL PathType to OM PathType.
2078  converter.addConversion([](om::PathType type) { return type; });
2079  converter.addConversion([](om::BasePathType type) { return type; });
2080  converter.addConversion([](om::FrozenPathType type) { return type; });
2081  converter.addConversion([](om::FrozenBasePathType type) { return type; });
2082  converter.addConversion([](firrtl::PathType type) {
2083  return om::PathType::get(type.getContext());
2084  });
2085 
2086  // Convert FIRRTL Class type to OM Class type.
2087  converter.addConversion([](om::ClassType type) { return type; });
2088  converter.addConversion([](firrtl::ClassType type) {
2089  return om::ClassType::get(type.getContext(), type.getNameAttr());
2090  });
2091 
2092  // Convert FIRRTL AnyRef type to OM Any type.
2093  converter.addConversion([](om::AnyType type) { return type; });
2094  converter.addConversion([](firrtl::AnyRefType type) {
2095  return om::AnyType::get(type.getContext());
2096  });
2097 
2098  // Convert FIRRTL List type to OM List type.
2099  auto convertListType = [&converter](auto type) -> std::optional<mlir::Type> {
2100  // If the element type is already in the OM dialect, there's nothing to do.
2101  if (isa<om::OMDialect>(type.getElementType().getDialect()))
2102  return type;
2103  auto elementType = converter.convertType(type.getElementType());
2104  if (!elementType)
2105  return {};
2107  };
2108 
2109  converter.addConversion(
2110  [convertListType](om::ListType type) -> std::optional<mlir::Type> {
2111  // Convert any om.list<firrtl> -> om.list<om>
2112  return convertListType(type);
2113  });
2114 
2115  converter.addConversion(
2116  [convertListType](firrtl::ListType type) -> std::optional<mlir::Type> {
2117  // Convert any firrtl.list<firrtl> -> om.list<om>
2118  return convertListType(type);
2119  });
2120 
2121  // Convert FIRRTL Bool type to OM
2122  converter.addConversion(
2123  [](BoolType type) { return IntegerType::get(type.getContext(), 1); });
2124 
2125  // Convert FIRRTL double type to OM.
2126  converter.addConversion(
2127  [](DoubleType type) { return FloatType::getF64(type.getContext()); });
2128 
2129  // Add a target materialization such that the conversion does not fail when a
2130  // type conversion could not be reconciled automatically by the framework.
2131  converter.addTargetMaterialization(
2132  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2133  assert(values.size() == 1);
2134  return builder.create<UnrealizedConversionCastOp>(loc, type, values[0])
2135  ->getResult(0);
2136  });
2137 
2138  // Add a source materialization such that the conversion does not fail when a
2139  // type conversion could not be reconciled automatically by the framework.
2140  converter.addSourceMaterialization(
2141  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2142  assert(values.size() == 1);
2143  return builder.create<UnrealizedConversionCastOp>(loc, type, values[0])
2144  ->getResult(0);
2145  });
2146 }
2147 
2149  RewritePatternSet &patterns, TypeConverter &converter,
2150  const PathInfoTable &pathInfoTable,
2151  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2152  patterns.add<FIntegerConstantOpConversion>(converter, patterns.getContext());
2153  patterns.add<StringConstantOpConversion>(converter, patterns.getContext());
2154  patterns.add<PathOpConversion>(converter, patterns.getContext(),
2155  pathInfoTable);
2156  patterns.add<WireOpConversion>(converter, patterns.getContext());
2157  patterns.add<AnyCastOpConversion>(converter, patterns.getContext());
2158  patterns.add<ObjectSubfieldOpConversion>(converter, patterns.getContext(),
2159  classTypeTable);
2160  patterns.add<ClassFieldsOpConversion>(converter, patterns.getContext());
2161  patterns.add<ClassOpSignatureConversion>(converter, patterns.getContext());
2163  patterns.getContext());
2164  patterns.add<ObjectOpConversion>(converter, patterns.getContext());
2165  patterns.add<ObjectFieldOpConversion>(converter, patterns.getContext());
2166  patterns.add<ListCreateOpConversion>(converter, patterns.getContext());
2167  patterns.add<ListConcatOpConversion>(converter, patterns.getContext());
2168  patterns.add<BoolConstantOpConversion>(converter, patterns.getContext());
2169  patterns.add<DoubleConstantOpConversion>(converter, patterns.getContext());
2170  patterns.add<IntegerAddOpConversion>(converter, patterns.getContext());
2171  patterns.add<IntegerMulOpConversion>(converter, patterns.getContext());
2172  patterns.add<IntegerShrOpConversion>(converter, patterns.getContext());
2173  patterns.add<IntegerShlOpConversion>(converter, patterns.getContext());
2174 }
2175 
2176 // Convert to OM ops and types in Classes or Modules.
2177 LogicalResult LowerClassesPass::dialectConversion(
2178  Operation *op, const PathInfoTable &pathInfoTable,
2179  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2180  ConversionTarget target(getContext());
2181  populateConversionTarget(target);
2182 
2183  TypeConverter typeConverter;
2184  populateTypeConverter(typeConverter);
2185 
2186  RewritePatternSet patterns(&getContext());
2187  populateRewritePatterns(patterns, typeConverter, pathInfoTable,
2188  classTypeTable);
2189 
2190  return applyPartialConversion(op, target, std::move(patterns));
2191 }
assert(baseType &&"element must be base type")
MlirType uint64_t numElements
Definition: CHIRRTL.cpp:30
MlirType elementType
Definition: CHIRRTL.cpp:29
static LogicalResult updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph, SmallVectorImpl< Operation * > &opsToErase)
static void populateConversionTarget(ConversionTarget &target)
static om::ClassLike convertClass(FModuleLike moduleLike, OpBuilder builder, Twine name, ArrayRef< StringRef > formalParamNames, bool hasContainingModule)
static LogicalResult updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath, InstanceGraph &instanceGraph, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static LogicalResult convertClassLike(om::ClassLike classOp, TypeConverter typeConverter, ConversionPatternRewriter &rewriter)
static void populateTypeConverter(TypeConverter &converter)
static LogicalResult updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph, SmallVectorImpl< Operation * > &opsToErase)
static LogicalResult updateObjectInClass(firrtl::ObjectOp firrtlObject, const PathInfoTable &pathInfoTable, SmallVectorImpl< RtlPortsInfo > &rtlPortsToCreate, std::mutex &intraPassMutex, SmallVectorImpl< Operation * > &opsToErase)
void checkAddContainingModulePorts(bool hasContainingModule, OpBuilder builder, SmallVector< Attribute > &fieldNames, SmallVector< NamedAttribute > &fieldTypes)
static om::ClassLike convertExtClass(FModuleLike moduleLike, OpBuilder builder, Twine name, ArrayRef< StringRef > formalParamNames, bool hasContainingModule)
static LogicalResult updateObjectsAndInstancesInClass(om::ClassOp classOp, InstanceGraph &instanceGraph, const LoweringState &state, const PathInfoTable &pathInfoTable, SmallVectorImpl< RtlPortsInfo > &rtlPortsToCreate, std::mutex &intraPassMutex, SmallVectorImpl< Operation * > &opsToErase)
static void populateRewritePatterns(RewritePatternSet &patterns, TypeConverter &converter, const PathInfoTable &pathInfoTable, const DenseMap< StringAttr, firrtl::ClassType > &classTypeTable)
static Block * getBodyBlock(FModuleLike mod)
std::shared_ptr< calyx::CalyxLoweringState > loweringState
This class provides a read-only projection of an annotation.
unsigned getFieldID() const
Get the field id this attribute targets.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
This is a Node in the InstanceGraph.
bool noUses()
Return true if there are no more instances of this module.
auto getModule()
Get the module that this node is tracking.
UseIterator usesBegin()
Iterate the instance records which instantiate this module.
bool hasOneUse()
Return true if this module has exactly one use.
llvm::iterator_range< UseIterator > uses()
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
This is an edge in the InstanceGraph.
Definition: InstanceGraph.h:55
auto getInstance()
Get the instance-like op that this is tracking.
Definition: InstanceGraph.h:59
InstanceGraphNode * getParent() const
Get the module where the instantiation lives.
Definition: InstanceGraph.h:66
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
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.
PropAssignOp getPropertyAssignment(FIRRTLPropertyValue value)
Return the single assignment to a Property value.
std::unique_ptr< mlir::Pass > createLowerClassesPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
int run(Type[Generator] generator=CppGenerator, cmdline_args=sys.argv)
Definition: codegen.py:121
LogicalResult matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ClassFieldsOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerShlOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ObjectSubfieldOpConversion(const TypeConverter &typeConverter, MLIRContext *context, const DenseMap< StringAttr, firrtl::ClassType > &classTypeTable)
const DenseMap< StringAttr, firrtl::ClassType > & classTypeTable
LogicalResult matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
PathOpConversion(TypeConverter &typeConverter, MLIRContext *context, const PathInfoTable &pathInfoTable, PatternBenefit benefit=1)
const PathInfoTable & pathInfoTable
LogicalResult matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(StringConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(WireOp wireOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
An annotation target is used to keep track of something that is targeted by an Annotation.
Operation * getOp() const
FModuleLike getModule() const
Get the parent module of the target.
AnnotationSet getAnnotations() const
Get the annotations associated with the target.
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.
hw::HierPathOp getOpFor(ArrayAttr attr)
FlatSymbolRefAttr getRefFor(ArrayAttr attr)
const SymbolTable & getSymbolTable() const
This represents an annotation targeting a specific operation.
Attribute getNLAReference(hw::InnerSymbolNamespace &moduleNamespace) const
This implements an analysis to determine which module owns a given path operation.
This represents an annotation targeting a specific port of a module, memory, or instance.
This holds the name and type that describes the module's ports.