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