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