CIRCT  19.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 
13 #include "PassDetails.h"
21 #include "circt/Dialect/OM/OMOps.h"
22 #include "mlir/IR/BuiltinOps.h"
23 #include "mlir/IR/IRMapping.h"
24 #include "mlir/IR/PatternMatch.h"
25 #include "mlir/IR/Threading.h"
26 #include "mlir/Support/LogicalResult.h"
27 #include "mlir/Transforms/DialectConversion.h"
28 #include "llvm/ADT/DepthFirstIterator.h"
29 #include "llvm/ADT/STLExtras.h"
30 #include "llvm/Support/raw_ostream.h"
31 
32 using namespace mlir;
33 using namespace circt;
34 using namespace circt::firrtl;
35 using namespace circt::om;
36 
37 namespace {
38 
39 /// Helper class which holds a hierarchical path op reference and a pointer to
40 /// to the targeted operation.
41 struct PathInfo {
42  PathInfo() = default;
43  PathInfo(Operation *op, FlatSymbolRefAttr symRef,
44  StringAttr altBasePathModule)
45  : op(op), symRef(symRef), altBasePathModule(altBasePathModule) {
46  assert(op && "op must not be null");
47  assert(symRef && "symRef must not be null");
48  }
49 
50  operator bool() const { return op != nullptr; }
51 
52  /// The hardware component targeted by this path.
53  Operation *op = nullptr;
54 
55  /// A reference to the hierarchical path targeting the op.
56  FlatSymbolRefAttr symRef = nullptr;
57 
58  /// The module name of the root module from which we take an alternative base
59  /// path.
60  StringAttr altBasePathModule = nullptr;
61 };
62 
63 /// Maps a FIRRTL path id to the lowered PathInfo.
64 struct PathInfoTable {
65  // Add an alternative base path root module. The default base path from this
66  // module will be passed through to where it is needed.
67  void addAltBasePathRoot(StringAttr rootModuleName) {
68  altBasePathRoots.insert(rootModuleName);
69  }
70 
71  // Add a passthrough module for a given root module. The default base path
72  // from the root module will be passed through the passthrough module.
73  void addAltBasePathPassthrough(StringAttr passthroughModuleName,
74  StringAttr rootModuleName) {
75  auto &rootSequence = altBasePathsPassthroughs[passthroughModuleName];
76  rootSequence.push_back(rootModuleName);
77  }
78 
79  // Get an iterator range over the alternative base path root module names.
80  llvm::iterator_range<SmallPtrSetImpl<StringAttr>::iterator>
81  getAltBasePathRoots() const {
82  return llvm::make_range(altBasePathRoots.begin(), altBasePathRoots.end());
83  }
84 
85  // Get the number of alternative base paths passing through the given
86  // passthrough module.
87  size_t getNumAltBasePaths(StringAttr passthroughModuleName) const {
88  return altBasePathsPassthroughs.lookup(passthroughModuleName).size();
89  }
90 
91  // Get the root modules that are passing an alternative base path through the
92  // given passthrough module.
93  llvm::iterator_range<const StringAttr *>
94  getRootsForPassthrough(StringAttr passthroughModuleName) const {
95  auto it = altBasePathsPassthroughs.find(passthroughModuleName);
96  assert(it != altBasePathsPassthroughs.end() &&
97  "expected passthrough module to already exist");
98  return llvm::make_range(it->second.begin(), it->second.end());
99  }
100 
101  // Collect alternative base paths passing through `instance`, by looking up
102  // its associated `moduleNameAttr`. The results are collected in `result`.
103  void collectAltBasePaths(Operation *instance, StringAttr moduleNameAttr,
104  SmallVectorImpl<Value> &result) const {
105  auto altBasePaths = altBasePathsPassthroughs.lookup(moduleNameAttr);
106  auto parent = instance->getParentOfType<om::ClassOp>();
107 
108  // Handle each alternative base path for instances of this module-like.
109  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
110  if (parent.getName().starts_with(altBasePath)) {
111  // If we are passing down from the root, take the root base path.
112  result.push_back(instance->getBlock()->getArgument(0));
113  } else {
114  // Otherwise, pass through the appropriate base path from above.
115  // + 1 to skip default base path
116  auto basePath = instance->getBlock()->getArgument(1 + i);
117  assert(isa<om::BasePathType>(basePath.getType()) &&
118  "expected a passthrough base path");
119  result.push_back(basePath);
120  }
121  }
122  }
123 
124  // The table mapping DistinctAttrs to PathInfo structs.
125  DenseMap<DistinctAttr, PathInfo> table;
126 
127 private:
128  // Module name attributes indicating modules whose base path input should
129  // be used as alternate base paths.
130  SmallPtrSet<StringAttr, 16> altBasePathRoots;
131 
132  // Module name attributes mapping from modules who pass through alternative
133  // base paths from their parents to a sequence of the parents' module names.
134  DenseMap<StringAttr, SmallVector<StringAttr>> altBasePathsPassthroughs;
135 };
136 
137 /// The suffix to append to lowered module names.
138 static constexpr StringRef kClassNameSuffix = "_Class";
139 
140 /// Helper class to capture details about a property.
141 struct Property {
142  size_t index;
143  StringRef name;
144  Type type;
145  Location loc;
146 };
147 
148 /// Helper class to capture state about a Class being lowered.
149 struct ClassLoweringState {
150  FModuleLike moduleLike;
151  std::vector<hw::HierPathOp> paths;
152 };
153 
154 struct LoweringState {
155  PathInfoTable pathInfoTable;
156  DenseMap<om::ClassLike, ClassLoweringState> classLoweringStateTable;
157 };
158 
159 struct LowerClassesPass : public LowerClassesBase<LowerClassesPass> {
160  void runOnOperation() override;
161 
162 private:
163  LogicalResult processPaths(InstanceGraph &instanceGraph,
164  hw::InnerSymbolNamespaceCollection &namespaces,
165  HierPathCache &cache, PathInfoTable &pathInfoTable,
166  SymbolTable &symbolTable);
167 
168  // Predicate to check if a module-like needs a Class to be created.
169  bool shouldCreateClass(FModuleLike moduleLike);
170 
171  // Create an OM Class op from a FIRRTL Class op.
172  om::ClassLike createClass(FModuleLike moduleLike,
173  const PathInfoTable &pathInfoTable);
174 
175  // Lower the FIRRTL Class to OM Class.
176  void lowerClassLike(FModuleLike moduleLike, om::ClassLike classLike,
177  const PathInfoTable &pathInfoTable);
178  void lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
179  const PathInfoTable &pathInfoTable);
180  void lowerClassExtern(ClassExternOp classExternOp, FModuleLike moduleLike);
181 
182  // Update Object instantiations in a FIRRTL Module or OM Class.
183  LogicalResult updateInstances(Operation *op, InstanceGraph &instanceGraph,
184  const LoweringState &state,
185  const PathInfoTable &pathInfoTable);
186 
187  // Convert to OM ops and types in Classes or Modules.
188  LogicalResult dialectConversion(
189  Operation *op, const PathInfoTable &pathInfoTable,
190  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable);
191 
192  // State to memoize repeated calls to shouldCreateClass.
193  DenseMap<FModuleLike, bool> shouldCreateClassMemo;
194 };
195 
196 } // namespace
197 
198 /// This pass removes the OMIR tracker annotations from operations, and ensures
199 /// that each thing that was targeted has a hierarchical path targeting it. It
200 /// builds a table which maps the original OMIR tracker annotation IDs to the
201 /// corresponding hierarchical paths. We use this table to convert FIRRTL path
202 /// ops to OM. FIRRTL paths refer to their target using a target ID, while OM
203 /// paths refer to their target using hierarchical paths.
204 LogicalResult LowerClassesPass::processPaths(
205  InstanceGraph &instanceGraph,
206  hw::InnerSymbolNamespaceCollection &namespaces, HierPathCache &cache,
207  PathInfoTable &pathInfoTable, SymbolTable &symbolTable) {
208  auto *context = &getContext();
209  auto circuit = getOperation();
210 
211  // Collect the path declarations and owning modules.
212  OwningModuleCache owningModuleCache(instanceGraph);
213  DenseMap<DistinctAttr, FModuleOp> owningModules;
214  std::vector<Operation *> declarations;
215  auto result = circuit.walk([&](Operation *op) {
216  if (auto pathOp = dyn_cast<PathOp>(op)) {
217  // Find the owning module of this path reference.
218  auto owningModule = owningModuleCache.lookup(pathOp);
219  // If this reference does not have a single owning module, it is an error.
220  if (!owningModule) {
221  pathOp->emitError("path does not have a single owning module");
222  return WalkResult::interrupt();
223  }
224  auto target = pathOp.getTargetAttr();
225  auto [it, inserted] = owningModules.try_emplace(target, owningModule);
226  // If this declaration already has a reference, both references must have
227  // the same owning module.
228  if (!inserted && it->second != owningModule) {
229  pathOp->emitError()
230  << "path reference " << target << " has conflicting owning modules "
231  << it->second.getModuleNameAttr() << " and "
232  << owningModule.getModuleNameAttr();
233  return WalkResult::interrupt();
234  }
235  }
236  return WalkResult::advance();
237  });
238  if (result.wasInterrupted())
239  return failure();
240 
241  auto processPathTrackers = [&](AnnoTarget target) -> LogicalResult {
242  auto error = false;
243  auto annotations = target.getAnnotations();
244  auto *op = target.getOp();
245  annotations.removeAnnotations([&](Annotation anno) {
246  // If there has been an error, just skip this annotation.
247  if (error)
248  return false;
249 
250  // We are looking for OMIR tracker annotations.
251  if (!anno.isClass("circt.tracker"))
252  return false;
253 
254  // The token must have a valid ID.
255  auto id = anno.getMember<DistinctAttr>("id");
256  if (!id) {
257  op->emitError("circt.tracker annotation missing id field");
258  error = true;
259  return false;
260  }
261 
262  // Get the fieldID. If there is none, it is assumed to be 0.
263  uint64_t fieldID = anno.getFieldID();
264 
265  // Attach an inner sym to the operation.
266  Attribute targetSym;
267  if (auto portTarget = target.dyn_cast<PortAnnoTarget>()) {
268  targetSym = getInnerRefTo(
269  {portTarget.getPortNo(), portTarget.getOp(), fieldID},
270  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
271  return namespaces[module];
272  });
273  } else if (auto module = dyn_cast<FModuleLike>(op)) {
274  assert(!fieldID && "field not valid for modules");
275  targetSym = FlatSymbolRefAttr::get(module.getModuleNameAttr());
276  } else {
277  targetSym = getInnerRefTo(
278  {target.getOp(), fieldID},
279  [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
280  return namespaces[module];
281  });
282  }
283 
284  // Create the hierarchical path.
285  SmallVector<Attribute> path;
286 
287  // Copy the trailing final target part of the path.
288  path.push_back(targetSym);
289 
290  auto moduleName = target.getModule().getModuleNameAttr();
291 
292  // Verify a nonlocal annotation refers to a HierPathOp.
293  hw::HierPathOp hierPathOp;
294  if (auto hierName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal")) {
295  hierPathOp =
296  dyn_cast<hw::HierPathOp>(symbolTable.lookup(hierName.getAttr()));
297  if (!hierPathOp) {
298  op->emitError("annotation does not point at a HierPathOp");
299  error = true;
300  return false;
301  }
302  }
303 
304  auto [it, inserted] = pathInfoTable.table.try_emplace(id);
305  auto &pathInfo = it->second;
306  if (!inserted) {
307  auto diag =
308  emitError(pathInfo.op->getLoc(), "duplicate identifier found");
309  diag.attachNote(op->getLoc()) << "other identifier here";
310  error = true;
311  return false;
312  }
313  pathInfo.op = op;
314 
315  // Get the owning module. If there is no owning module, then this
316  // declaration does not have a use, and we can return early.
317  auto owningModule = owningModules.lookup(id);
318  if (!owningModule)
319  return true;
320 
321  // Copy the middle part from the annotation's NLA.
322  if (hierPathOp) {
323  // Copy the old path, dropping the module name.
324  auto oldPath = hierPathOp.getNamepath().getValue();
325  llvm::append_range(path, llvm::reverse(oldPath.drop_back()));
326 
327  // Set the moduleName based on the hierarchical path. If the
328  // owningModule is in the hierarichal path, set the moduleName to the
329  // owning module. Otherwise use the top of the hierarchical path.
330  bool pathContainsOwningModule =
331  llvm::any_of(oldPath, [&](auto pathFragment) {
332  return llvm::TypeSwitch<Attribute, bool>(pathFragment)
333  .Case([&](hw::InnerRefAttr innerRef) {
334  return innerRef.getModule() ==
335  owningModule.getModuleNameAttr();
336  })
337  .Case([&](FlatSymbolRefAttr symRef) {
338  return symRef.getAttr() == owningModule.getModuleNameAttr();
339  })
340  .Default([](auto attr) { return false; });
341  });
342  if (pathContainsOwningModule) {
343  moduleName = owningModule.getModuleNameAttr();
344  } else {
345  moduleName = cast<hw::InnerRefAttr>(oldPath.front()).getModule();
346  }
347  }
348 
349  // Copy the leading part of the hierarchical path from the owning module
350  // to the start of the annotation's NLA.
351  bool needsAltBasePath = false;
352  auto *node = instanceGraph.lookup(moduleName);
353  while (true) {
354  // If the path is rooted at the owning module, we're done.
355  if (node->getModule() == owningModule)
356  break;
357  // If there are no more parents, then the path op lives in a different
358  // hierarchy than the HW object it references, which needs to handled
359  // specially. Flag this, so we know to create an alternative base path
360  // below.
361  if (node->noUses()) {
362  needsAltBasePath = true;
363  break;
364  }
365  // If there is more than one instance of this module, then the path
366  // operation is ambiguous, which is an error.
367  if (!node->hasOneUse()) {
368  auto diag = op->emitError()
369  << "unable to uniquely resolve target due "
370  "to multiple instantiation";
371  for (auto *use : node->uses())
372  diag.attachNote(use->getInstance().getLoc()) << "instance here";
373  error = true;
374  return false;
375  }
376  node = (*node->usesBegin())->getParent();
377  }
378 
379  // Create the HierPathOp.
380  std::reverse(path.begin(), path.end());
381  auto pathAttr = ArrayAttr::get(context, path);
382 
383  // If we need an alternative base path, save the top module from the path.
384  // We will plumb in the basepath from this module.
385  StringAttr altBasePathModule;
386  if (needsAltBasePath) {
387  altBasePathModule =
388  TypeSwitch<Attribute, StringAttr>(path.front())
389  .Case<FlatSymbolRefAttr>([](auto a) { return a.getAttr(); })
390  .Case<hw::InnerRefAttr>([](auto a) { return a.getModule(); });
391 
392  pathInfoTable.addAltBasePathRoot(altBasePathModule);
393  }
394 
395  // Record the path operation associated with the path op.
396  pathInfo = {op, cache.getRefFor(pathAttr), altBasePathModule};
397 
398  // Remove this annotation from the operation.
399  return true;
400  });
401 
402  if (error)
403  return failure();
404  target.setAnnotations(annotations);
405  return success();
406  };
407 
408  for (auto module : circuit.getOps<FModuleLike>()) {
409  // Process the module annotations.
410  if (failed(processPathTrackers(OpAnnoTarget(module))))
411  return failure();
412  // Process module port annotations.
413  for (unsigned i = 0, e = module.getNumPorts(); i < e; ++i)
414  if (failed(processPathTrackers(PortAnnoTarget(module, i))))
415  return failure();
416  // Process ops in the module body.
417  auto result = module.walk([&](hw::InnerSymbolOpInterface op) {
418  if (failed(processPathTrackers(OpAnnoTarget(op))))
419  return WalkResult::interrupt();
420  return WalkResult::advance();
421  });
422  if (result.wasInterrupted())
423  return failure();
424  }
425 
426  // For each module that will be passing through a base path, compute its
427  // descendants that need this base path passed through.
428  for (auto rootModule : pathInfoTable.getAltBasePathRoots()) {
429  InstanceGraphNode *node = instanceGraph.lookup(rootModule);
430 
431  // Do a depth first traversal of the instance graph from rootModule, looking
432  // for descendants that need to be passed through.
433  auto start = llvm::df_begin(node);
434  auto end = llvm::df_end(node);
435  auto it = start;
436  while (it != end) {
437  // Nothing to do for the root module.
438  if (it == start) {
439  ++it;
440  continue;
441  }
442 
443  // If we aren't creating a class for this child, skip this hierarchy.
444  if (!shouldCreateClass(it->getModule<FModuleLike>())) {
445  it = it.skipChildren();
446  continue;
447  }
448 
449  // If we are at a leaf, nothing to do.
450  if (std::distance(it->begin(), it->end()) == 0) {
451  ++it;
452  continue;
453  }
454 
455  // Track state for this passthrough.
456  StringAttr passthroughModule = it->getModule().getModuleNameAttr();
457  pathInfoTable.addAltBasePathPassthrough(passthroughModule, rootModule);
458 
459  ++it;
460  }
461  }
462 
463  return success();
464 }
465 
466 /// Lower FIRRTL Class and Object ops to OM Class and Object ops
467 void LowerClassesPass::runOnOperation() {
468  MLIRContext *ctx = &getContext();
469 
470  // Get the CircuitOp.
471  CircuitOp circuit = getOperation();
472 
473  // Get the InstanceGraph and SymbolTable.
474  InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
475  SymbolTable &symbolTable = getAnalysis<SymbolTable>();
476 
477  hw::InnerSymbolNamespaceCollection namespaces;
478  HierPathCache cache(circuit, symbolTable);
479 
480  // Rewrite all path annotations into inner symbol targets.
481  PathInfoTable pathInfoTable;
482  if (failed(processPaths(instanceGraph, namespaces, cache, pathInfoTable,
483  symbolTable))) {
484  signalPassFailure();
485  return;
486  }
487 
488  LoweringState loweringState;
489 
490  // Create new OM Class ops serially.
491  DenseMap<StringAttr, firrtl::ClassType> classTypeTable;
492  for (auto moduleLike : circuit.getOps<FModuleLike>()) {
493  if (shouldCreateClass(moduleLike)) {
494  auto omClass = createClass(moduleLike, pathInfoTable);
495  auto &classLoweringState = loweringState.classLoweringStateTable[omClass];
496  classLoweringState.moduleLike = moduleLike;
497 
498  // Find the module instances under the current module with metadata. These
499  // ops will be converted to om objects by this pass. Create a hierarchical
500  // path for each of these instances, which will be used to rebase path
501  // operations. Hierarchical paths must be created serially to ensure their
502  // order in the circuit is deterministc.
503  moduleLike.walk([&](InstanceOp inst) {
504  // Get the referenced module.
505  auto module =
506  symbolTable.lookup<FModuleLike>(inst.getReferencedModuleNameAttr());
507  if (shouldCreateClass(module)) {
508  auto targetSym = getInnerRefTo(
509  {inst, 0}, [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
510  return namespaces[module];
511  });
512  SmallVector<Attribute> path = {targetSym};
513  auto pathAttr = ArrayAttr::get(ctx, path);
514  auto hierPath = cache.getOpFor(pathAttr);
515  classLoweringState.paths.push_back(hierPath);
516  }
517  });
518 
519  if (auto classLike =
520  dyn_cast<firrtl::ClassLike>(moduleLike.getOperation()))
521  classTypeTable[classLike.getNameAttr()] = classLike.getInstanceType();
522  }
523  }
524 
525  // Move ops from FIRRTL Class to OM Class in parallel.
526  mlir::parallelForEach(ctx, loweringState.classLoweringStateTable,
527  [this, &pathInfoTable](auto &entry) {
528  const auto &[classLike, state] = entry;
529  lowerClassLike(state.moduleLike, classLike,
530  pathInfoTable);
531  });
532 
533  // Completely erase Class module-likes, and remove from the InstanceGraph.
534  for (auto &[omClass, state] : loweringState.classLoweringStateTable) {
535  if (isa<firrtl::ClassLike>(state.moduleLike.getOperation())) {
536  InstanceGraphNode *node = instanceGraph.lookup(state.moduleLike);
537  for (auto *use : llvm::make_early_inc_range(node->uses()))
538  use->erase();
539  instanceGraph.erase(node);
540  state.moduleLike.erase();
541  }
542  }
543 
544  // Collect ops where Objects can be instantiated.
545  SmallVector<Operation *> objectContainers;
546  for (auto &op : circuit.getOps())
547  if (isa<FModuleOp, om::ClassLike>(op))
548  objectContainers.push_back(&op);
549 
550  // Update Object creation ops in Classes or Modules in parallel.
551  if (failed(
552  mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
553  return updateInstances(op, instanceGraph, loweringState,
554  pathInfoTable);
555  })))
556  return signalPassFailure();
557 
558  // Convert to OM ops and types in Classes or Modules in parallel.
559  if (failed(
560  mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
561  return dialectConversion(op, pathInfoTable, classTypeTable);
562  })))
563  return signalPassFailure();
564 
565  // We keep the instance graph up to date, so mark that analysis preserved.
566  markAnalysesPreserved<InstanceGraph>();
567 }
568 
569 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerClassesPass() {
570  return std::make_unique<LowerClassesPass>();
571 }
572 
573 // Predicate to check if a module-like needs a Class to be created.
574 bool LowerClassesPass::shouldCreateClass(FModuleLike moduleLike) {
575  // Memoize calls using the extra state.
576  auto it = shouldCreateClassMemo.find(moduleLike);
577  if (it != shouldCreateClassMemo.end())
578  return it->second;
579 
580  if (isa<firrtl::ClassLike>(moduleLike.getOperation())) {
581  shouldCreateClassMemo.insert({moduleLike, true});
582  return true;
583  }
584 
585  // Always create a class for public modules.
586  if (moduleLike.isPublic()) {
587  shouldCreateClassMemo.insert({moduleLike, true});
588  return true;
589  }
590 
591  // Create a class for modules with property ports.
592  bool hasClassPorts = llvm::any_of(moduleLike.getPorts(), [](PortInfo port) {
593  return isa<PropertyType>(port.type);
594  });
595 
596  if (hasClassPorts) {
597  shouldCreateClassMemo.insert({moduleLike, true});
598  return true;
599  }
600 
601  // Create a class for modules that instantiate classes or modules with
602  // property ports.
603  for (auto op :
604  moduleLike.getOperation()->getRegion(0).getOps<FInstanceLike>()) {
605  for (auto result : op->getResults()) {
606  if (type_isa<PropertyType>(result.getType())) {
607  shouldCreateClassMemo.insert({moduleLike, true});
608  return true;
609  }
610  }
611  }
612 
613  shouldCreateClassMemo.insert({moduleLike, false});
614  return false;
615 }
616 
617 // Create an OM Class op from a FIRRTL Class op or Module op with properties.
618 om::ClassLike
619 LowerClassesPass::createClass(FModuleLike moduleLike,
620  const PathInfoTable &pathInfoTable) {
621  // Collect the parameter names from input properties.
622  SmallVector<StringRef> formalParamNames;
623  // Every class gets a base path as its first parameter.
624  formalParamNames.emplace_back("basepath");
625 
626  // If this class is passing through base paths from above, add those.
627  size_t nAltBasePaths =
628  pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
629  for (size_t i = 0; i < nAltBasePaths; ++i)
630  formalParamNames.push_back(StringAttr::get(
631  moduleLike->getContext(), "alt_basepath_" + llvm::Twine(i)));
632 
633  for (auto [index, port] : llvm::enumerate(moduleLike.getPorts()))
634  if (port.isInput() && isa<PropertyType>(port.type))
635  formalParamNames.push_back(port.name);
636 
637  OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
638 
639  // Take the name from the FIRRTL Class or Module to create the OM Class name.
640  StringRef className = moduleLike.getName();
641 
642  // Use the defname for external modules.
643  if (auto externMod = dyn_cast<FExtModuleOp>(moduleLike.getOperation()))
644  if (auto defname = externMod.getDefname())
645  className = defname.value();
646 
647  // If the op is a Module or ExtModule, the OM Class would conflict with the HW
648  // Module, so give it a suffix. There is no formal ABI for this yet.
649  StringRef suffix =
650  isa<FModuleOp, FExtModuleOp>(moduleLike) ? kClassNameSuffix : "";
651 
652  // Construct the OM Class with the FIRRTL Class name and parameter names.
653  om::ClassLike loweredClassOp;
654  if (isa<firrtl::ExtClassOp, firrtl::FExtModuleOp>(moduleLike.getOperation()))
655  loweredClassOp = builder.create<om::ClassExternOp>(
656  moduleLike.getLoc(), className + suffix, formalParamNames);
657  else
658  loweredClassOp = builder.create<om::ClassOp>(
659  moduleLike.getLoc(), className + suffix, formalParamNames);
660 
661  return loweredClassOp;
662 }
663 
664 void LowerClassesPass::lowerClassLike(FModuleLike moduleLike,
665  om::ClassLike classLike,
666  const PathInfoTable &pathInfoTable) {
667 
668  if (auto classOp = dyn_cast<om::ClassOp>(classLike.getOperation())) {
669  return lowerClass(classOp, moduleLike, pathInfoTable);
670  }
671  if (auto classExternOp =
672  dyn_cast<om::ClassExternOp>(classLike.getOperation())) {
673  return lowerClassExtern(classExternOp, moduleLike);
674  }
675  llvm_unreachable("unhandled class-like op");
676 }
677 
678 void LowerClassesPass::lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
679  const PathInfoTable &pathInfoTable) {
680  // Map from Values in the FIRRTL Class to Values in the OM Class.
681  IRMapping mapping;
682 
683  // Collect information about property ports.
684  SmallVector<Property> inputProperties;
685  BitVector portsToErase(moduleLike.getNumPorts());
686  for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
687  // For Module ports that aren't property types, move along.
688  if (!isa<PropertyType>(port.type))
689  continue;
690 
691  // Remember input properties to create the OM Class formal parameters.
692  if (port.isInput())
693  inputProperties.push_back({index, port.name, port.type, port.loc});
694 
695  // In case this is a Module, remember to erase this port.
696  portsToErase.set(index);
697  }
698 
699  // Construct the OM Class body with block arguments for each input property,
700  // updating the mapping to map from the input property to the block argument.
701  Block *classBody = &classOp->getRegion(0).emplaceBlock();
702  // Every class created from a module gets a base path as its first parameter.
703  auto basePathType = BasePathType::get(&getContext());
704  auto unknownLoc = UnknownLoc::get(&getContext());
705  classBody->addArgument(basePathType, unknownLoc);
706 
707  // If this class is passing through base paths from above, add those.
708  size_t nAltBasePaths =
709  pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
710  for (size_t i = 0; i < nAltBasePaths; ++i)
711  classBody->addArgument(basePathType, unknownLoc);
712 
713  for (auto inputProperty : inputProperties) {
714  BlockArgument parameterValue =
715  classBody->addArgument(inputProperty.type, inputProperty.loc);
716  BlockArgument inputValue =
717  moduleLike->getRegion(0).getArgument(inputProperty.index);
718  mapping.map(inputValue, parameterValue);
719  }
720 
721  // Clone the property ops from the FIRRTL Class or Module to the OM Class.
722  SmallVector<Operation *> opsToErase;
723  OpBuilder builder = OpBuilder::atBlockBegin(classOp.getBodyBlock());
724  for (auto &op : moduleLike->getRegion(0).getOps()) {
725  // Check if any operand is a property.
726  auto propertyOperands = llvm::any_of(op.getOperandTypes(), [](Type type) {
727  return isa<PropertyType>(type);
728  });
729 
730  // Check if any result is a property.
731  auto propertyResults = llvm::any_of(
732  op.getResultTypes(), [](Type type) { return isa<PropertyType>(type); });
733 
734  // If there are no properties here, move along.
735  if (!propertyOperands && !propertyResults)
736  continue;
737 
738  // Actually clone the op over to the OM Class.
739  builder.clone(op, mapping);
740 
741  // In case this is a Module, remember to erase this op, unless it is an
742  // instance. Instances are handled later in updateInstances.
743  if (!isa<InstanceOp>(op))
744  opsToErase.push_back(&op);
745  }
746 
747  // Convert any output property assignments to Field ops.
748  for (auto op : llvm::make_early_inc_range(classOp.getOps<PropAssignOp>())) {
749  // Property assignments will currently be pointing back to the original
750  // FIRRTL Class for output ports.
751  auto outputPort = dyn_cast<BlockArgument>(op.getDest());
752  if (!outputPort)
753  continue;
754 
755  // Get the original port name, create a Field, and erase the propassign.
756  auto name = moduleLike.getPortName(outputPort.getArgNumber());
757  builder.create<ClassFieldOp>(op.getLoc(), name, op.getSrc());
758  op.erase();
759  }
760 
761  // If the module-like is a Class, it will be completely erased later.
762  // Otherwise, erase just the property ports and ops.
763  if (!isa<firrtl::ClassLike>(moduleLike.getOperation())) {
764  // Erase ops in use before def order, thanks to FIRRTL's SSA regions.
765  for (auto *op : llvm::reverse(opsToErase))
766  op->erase();
767 
768  // Erase property typed ports.
769  moduleLike.erasePorts(portsToErase);
770  }
771 }
772 
773 void LowerClassesPass::lowerClassExtern(ClassExternOp classExternOp,
774  FModuleLike moduleLike) {
775  // Construct the OM Class body.
776  // Add a block arguments for each input property.
777  // Add a class.extern.field op for each output.
778  BitVector portsToErase(moduleLike.getNumPorts());
779  Block *classBody = &classExternOp.getRegion().emplaceBlock();
780  OpBuilder builder = OpBuilder::atBlockBegin(classBody);
781 
782  // Every class gets a base path as its first parameter.
783  classBody->addArgument(BasePathType::get(&getContext()),
784  UnknownLoc::get(&getContext()));
785 
786  for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
787  auto type = moduleLike.getPortType(i);
788  if (!isa<PropertyType>(type))
789  continue;
790 
791  auto loc = moduleLike.getPortLocation(i);
792  auto direction = moduleLike.getPortDirection(i);
793  if (direction == Direction::In)
794  classBody->addArgument(type, loc);
795  else {
796  auto name = moduleLike.getPortNameAttr(i);
797  builder.create<om::ClassExternFieldOp>(loc, name, type);
798  }
799 
800  // In case this is a Module, remember to erase this port.
801  portsToErase.set(i);
802  }
803 
804  // If the module-like is a Class, it will be completely erased later.
805  // Otherwise, erase just the property ports and ops.
806  if (!isa<firrtl::ClassLike>(moduleLike.getOperation())) {
807  // Erase property typed ports.
808  moduleLike.erasePorts(portsToErase);
809  }
810 }
811 
812 // Helper to update an Object instantiation. FIRRTL Object instances are
813 // converted to OM Object instances.
814 static LogicalResult
815 updateObjectInClass(firrtl::ObjectOp firrtlObject,
816  const PathInfoTable &pathInfoTable,
817  SmallVectorImpl<Operation *> &opsToErase) {
818  // The 0'th argument is the base path.
819  auto basePath = firrtlObject->getBlock()->getArgument(0);
820  // build a table mapping the indices of input ports to their position in the
821  // om class's parameter list.
822  auto firrtlClassType = firrtlObject.getType();
823  auto numElements = firrtlClassType.getNumElements();
824  llvm::SmallVector<unsigned> argIndexTable;
825  argIndexTable.resize(numElements);
826 
827  // Get any alternative base paths passing through this module.
828  SmallVector<Value> altBasePaths;
829  pathInfoTable.collectAltBasePaths(
830  firrtlObject, firrtlClassType.getNameAttr().getAttr(), altBasePaths);
831 
832  // Account for the default base path and any alternatives.
833  unsigned nextArgIndex = 1 + altBasePaths.size();
834 
835  for (unsigned i = 0; i < numElements; ++i) {
836  auto direction = firrtlClassType.getElement(i).direction;
837  if (direction == Direction::In)
838  argIndexTable[i] = nextArgIndex++;
839  }
840 
841  // Collect its input actual parameters by finding any subfield ops that are
842  // assigned to. Take the source of the assignment as the actual parameter.
843 
844  llvm::SmallVector<Value> args;
845  args.resize(nextArgIndex);
846  args[0] = basePath;
847 
848  // Collect any alternative base paths passing through.
849  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths))
850  args[1 + i] = altBasePath; // + 1 to skip default base path
851 
852  for (auto *user : llvm::make_early_inc_range(firrtlObject->getUsers())) {
853  if (auto subfield = dyn_cast<ObjectSubfieldOp>(user)) {
854  auto index = subfield.getIndex();
855  auto direction = firrtlClassType.getElement(index).direction;
856 
857  // We only lower "writes to input ports" here. Reads from output
858  // ports will be handled using the conversion framework.
859  if (direction == Direction::Out)
860  continue;
861 
862  for (auto *subfieldUser :
863  llvm::make_early_inc_range(subfield->getUsers())) {
864  if (auto propassign = dyn_cast<PropAssignOp>(subfieldUser)) {
865  // the operands of the propassign may have already been converted to
866  // om. Use the generic operand getters to get the operands as
867  // untyped values.
868  auto dst = propassign.getOperand(0);
869  auto src = propassign.getOperand(1);
870  if (dst == subfield.getResult()) {
871  args[argIndexTable[index]] = src;
872  opsToErase.push_back(propassign);
873  }
874  }
875  }
876 
877  opsToErase.push_back(subfield);
878  }
879  }
880 
881  // Check that all input ports have been initialized.
882  for (unsigned i = 0; i < numElements; ++i) {
883  auto element = firrtlClassType.getElement(i);
884  if (element.direction == Direction::Out)
885  continue;
886 
887  auto argIndex = argIndexTable[i];
888  if (!args[argIndex])
889  return emitError(firrtlObject.getLoc())
890  << "uninitialized input port " << element.name;
891  }
892 
893  // Convert the FIRRTL Class type to an OM Class type.
894  auto className = firrtlObject.getType().getNameAttr();
895  auto classType = om::ClassType::get(firrtlObject->getContext(), className);
896 
897  // Create the new Object op.
898  OpBuilder builder(firrtlObject);
899  auto object = builder.create<om::ObjectOp>(
900  firrtlObject.getLoc(), classType, firrtlObject.getClassNameAttr(), args);
901 
902  // Replace uses of the FIRRTL Object with the OM Object. The later dialect
903  // conversion will take care of converting the types.
904  firrtlObject.replaceAllUsesWith(object.getResult());
905 
906  // Erase the original Object, now that we're done with it.
907  opsToErase.push_back(firrtlObject);
908  return success();
909 }
910 
911 // Helper to update a Module instantiation in a Class. Module instances within a
912 // Class are converted to OM Object instances of the Class derived from the
913 // Module.
914 static LogicalResult
915 updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath,
916  InstanceGraph &instanceGraph,
917  const PathInfoTable &pathInfoTable,
918  SmallVectorImpl<Operation *> &opsToErase) {
919 
920  // Set the insertion point right before the instance op.
921  OpBuilder builder(firrtlInstance);
922 
923  // Collect the FIRRTL instance inputs to form the Object instance actual
924  // parameters. The order of the SmallVector needs to match the order the
925  // formal parameters are declared on the corresponding Class.
926  SmallVector<Value> actualParameters;
927  // The 0'th argument is the base path.
928  auto basePath = firrtlInstance->getBlock()->getArgument(0);
929  auto symRef = FlatSymbolRefAttr::get(hierPath.getSymNameAttr());
930  auto rebasedPath = builder.create<om::BasePathCreateOp>(
931  firrtlInstance->getLoc(), basePath, symRef);
932 
933  actualParameters.push_back(rebasedPath);
934 
935  // Add any alternative base paths passing through this instance.
936  pathInfoTable.collectAltBasePaths(
937  firrtlInstance, firrtlInstance.getModuleNameAttr().getAttr(),
938  actualParameters);
939 
940  for (auto result : firrtlInstance.getResults()) {
941  // If the port is an output, continue.
942  if (firrtlInstance.getPortDirection(result.getResultNumber()) ==
943  Direction::Out)
944  continue;
945 
946  // If the port is not a property type, continue.
947  auto propertyResult = dyn_cast<FIRRTLPropertyValue>(result);
948  if (!propertyResult)
949  continue;
950 
951  // Get the property assignment to the input, and track the assigned
952  // Value as an actual parameter to the Object instance.
953  auto propertyAssignment = getPropertyAssignment(propertyResult);
954  assert(propertyAssignment && "properties require single assignment");
955  actualParameters.push_back(propertyAssignment.getSrcMutable().get());
956 
957  // Erase the property assignment.
958  opsToErase.push_back(propertyAssignment);
959  }
960 
961  // Get the referenced module to get its name.
962  auto referencedModule =
963  firrtlInstance.getReferencedModule<FModuleLike>(instanceGraph);
964 
965  StringRef moduleName = referencedModule.getName();
966 
967  // Use the defname for external modules.
968  if (auto externMod = dyn_cast<FExtModuleOp>(referencedModule.getOperation()))
969  if (auto defname = externMod.getDefname())
970  moduleName = defname.value();
971 
972  // Convert the FIRRTL Module name to an OM Class type.
973  auto className = FlatSymbolRefAttr::get(
974  builder.getStringAttr(moduleName + kClassNameSuffix));
975 
976  auto classType = om::ClassType::get(firrtlInstance->getContext(), className);
977 
978  // Create the new Object op.
979  auto object =
980  builder.create<om::ObjectOp>(firrtlInstance.getLoc(), classType,
981  className.getAttr(), actualParameters);
982 
983  // Replace uses of the FIRRTL instance outputs with field access into
984  // the OM Object. The later dialect conversion will take care of
985  // converting the types.
986  for (auto result : firrtlInstance.getResults()) {
987  // If the port isn't an output, continue.
988  if (firrtlInstance.getPortDirection(result.getResultNumber()) !=
989  Direction::Out)
990  continue;
991 
992  // If the port is not a property type, continue.
993  if (!isa<PropertyType>(result.getType()))
994  continue;
995 
996  // The path to the field is just this output's name.
997  auto objectFieldPath = builder.getArrayAttr({FlatSymbolRefAttr::get(
998  firrtlInstance.getPortName(result.getResultNumber()))});
999 
1000  // Create the field access.
1001  auto objectField = builder.create<ObjectFieldOp>(
1002  object.getLoc(), result.getType(), object, objectFieldPath);
1003 
1004  result.replaceAllUsesWith(objectField);
1005  }
1006 
1007  // Erase the original instance, now that we're done with it.
1008  opsToErase.push_back(firrtlInstance);
1009  return success();
1010 }
1011 
1012 // Helper to update a Module instantiation in a Module. Module instances within
1013 // a Module are updated to remove the property typed ports.
1014 static LogicalResult
1015 updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph,
1016  SmallVectorImpl<Operation *> &opsToErase) {
1017  // Collect property typed ports to erase.
1018  BitVector portsToErase(firrtlInstance.getNumResults());
1019  for (auto result : firrtlInstance.getResults())
1020  if (isa<PropertyType>(result.getType()))
1021  portsToErase.set(result.getResultNumber());
1022 
1023  // If there are none, nothing to do.
1024  if (portsToErase.none())
1025  return success();
1026 
1027  // Create a new instance with the property ports removed.
1028  OpBuilder builder(firrtlInstance);
1029  InstanceOp newInstance = firrtlInstance.erasePorts(builder, portsToErase);
1030 
1031  // Replace the instance in the instance graph. This is called from multiple
1032  // threads, but because the instance graph data structure is not mutated, and
1033  // only one thread ever sets the instance pointer for a given instance, this
1034  // should be safe.
1035  instanceGraph.replaceInstance(firrtlInstance, newInstance);
1036 
1037  // Erase the original instance, which is now replaced.
1038  opsToErase.push_back(firrtlInstance);
1039  return success();
1040 }
1041 
1042 static LogicalResult
1043 updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph,
1044  SmallVectorImpl<Operation *> &opsToErase) {
1045  OpBuilder builder(moduleOp);
1046  for (auto &op : moduleOp->getRegion(0).getOps()) {
1047  if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1048  assert(0 && "should be no objects in modules");
1049  } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1050  if (failed(updateInstanceInModule(instanceOp, instanceGraph, opsToErase)))
1051  return failure();
1052  }
1053  }
1054  return success();
1055 }
1056 
1058  om::ClassOp classOp, InstanceGraph &instanceGraph,
1059  const LoweringState &state, const PathInfoTable &pathInfoTable,
1060  SmallVectorImpl<Operation *> &opsToErase) {
1061  OpBuilder builder(classOp);
1062  auto &classState = state.classLoweringStateTable.at(classOp);
1063  auto it = classState.paths.begin();
1064  for (auto &op : classOp->getRegion(0).getOps()) {
1065  if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1066  if (failed(updateObjectInClass(objectOp, pathInfoTable, opsToErase)))
1067  return failure();
1068  } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1069  if (failed(updateInstanceInClass(instanceOp, *it++, instanceGraph,
1070  pathInfoTable, opsToErase)))
1071  return failure();
1072  }
1073  }
1074  return success();
1075 }
1076 
1077 // Update Object or Module instantiations in a FIRRTL Module or OM Class.
1078 LogicalResult
1079 LowerClassesPass::updateInstances(Operation *op, InstanceGraph &instanceGraph,
1080  const LoweringState &state,
1081  const PathInfoTable &pathInfoTable) {
1082 
1083  // Track ops to erase at the end. We can't do this eagerly, since we want to
1084  // loop over each op in the container's body, and we may end up removing some
1085  // ops later in the body when we visit instances earlier in the body.
1086  SmallVector<Operation *> opsToErase;
1087  auto result =
1088  TypeSwitch<Operation *, LogicalResult>(op)
1089 
1090  .Case([&](FModuleOp moduleOp) {
1091  // Convert FIRRTL Module instance within a Module to
1092  // remove property ports if necessary.
1093  return updateInstancesInModule(moduleOp, instanceGraph, opsToErase);
1094  })
1095  .Case([&](om::ClassOp classOp) {
1096  // Convert FIRRTL Module instance within a Class to OM
1097  // Object instance.
1099  classOp, instanceGraph, state, pathInfoTable, opsToErase);
1100  })
1101  .Default([](auto *op) { return success(); });
1102  if (failed(result))
1103  return result;
1104  // Erase the ops marked to be erased.
1105  for (auto *op : opsToErase)
1106  op->erase();
1107 
1108  return success();
1109 }
1110 
1111 // Pattern rewriters for dialect conversion.
1112 
1114  : public OpConversionPattern<FIntegerConstantOp> {
1115  using OpConversionPattern::OpConversionPattern;
1116 
1117  LogicalResult
1118  matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor,
1119  ConversionPatternRewriter &rewriter) const override {
1120  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1121  op, om::OMIntegerType::get(op.getContext()),
1122  om::IntegerAttr::get(op.getContext(), adaptor.getValueAttr()));
1123  return success();
1124  }
1125 };
1126 
1127 struct BoolConstantOpConversion : public OpConversionPattern<BoolConstantOp> {
1128  using OpConversionPattern::OpConversionPattern;
1129 
1130  LogicalResult
1131  matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor,
1132  ConversionPatternRewriter &rewriter) const override {
1133  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1134  op, rewriter.getBoolAttr(adaptor.getValue()));
1135  return success();
1136  }
1137 };
1138 
1140  : public OpConversionPattern<DoubleConstantOp> {
1141  using OpConversionPattern::OpConversionPattern;
1142 
1143  LogicalResult
1144  matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor,
1145  ConversionPatternRewriter &rewriter) const override {
1146  rewriter.replaceOpWithNewOp<om::ConstantOp>(op, adaptor.getValue());
1147  return success();
1148  }
1149 };
1150 
1152  : public OpConversionPattern<StringConstantOp> {
1153  using OpConversionPattern::OpConversionPattern;
1154 
1155  LogicalResult
1156  matchAndRewrite(StringConstantOp op, OpAdaptor adaptor,
1157  ConversionPatternRewriter &rewriter) const override {
1158  auto stringType = om::StringType::get(op.getContext());
1159  rewriter.replaceOpWithNewOp<om::ConstantOp>(
1160  op, stringType, StringAttr::get(op.getValue(), stringType));
1161  return success();
1162  }
1163 };
1164 
1166  : public OpConversionPattern<firrtl::ListCreateOp> {
1167  using OpConversionPattern::OpConversionPattern;
1168 
1169  LogicalResult
1170  matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor,
1171  ConversionPatternRewriter &rewriter) const override {
1172  auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1173  if (!listType)
1174  return failure();
1175  rewriter.replaceOpWithNewOp<om::ListCreateOp>(op, listType,
1176  adaptor.getElements());
1177  return success();
1178  }
1179 };
1180 
1182  : public OpConversionPattern<firrtl::IntegerAddOp> {
1183  using OpConversionPattern::OpConversionPattern;
1184 
1185  LogicalResult
1186  matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor,
1187  ConversionPatternRewriter &rewriter) const override {
1188  rewriter.replaceOpWithNewOp<om::IntegerAddOp>(op, adaptor.getLhs(),
1189  adaptor.getRhs());
1190  return success();
1191  }
1192 };
1193 
1195  : public OpConversionPattern<firrtl::IntegerMulOp> {
1196  using OpConversionPattern::OpConversionPattern;
1197 
1198  LogicalResult
1199  matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor,
1200  ConversionPatternRewriter &rewriter) const override {
1201  rewriter.replaceOpWithNewOp<om::IntegerMulOp>(op, adaptor.getLhs(),
1202  adaptor.getRhs());
1203  return success();
1204  }
1205 };
1206 
1208  : public OpConversionPattern<firrtl::IntegerShrOp> {
1209  using OpConversionPattern::OpConversionPattern;
1210 
1211  LogicalResult
1212  matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor,
1213  ConversionPatternRewriter &rewriter) const override {
1214  rewriter.replaceOpWithNewOp<om::IntegerShrOp>(op, adaptor.getLhs(),
1215  adaptor.getRhs());
1216  return success();
1217  }
1218 };
1219 
1220 struct PathOpConversion : public OpConversionPattern<firrtl::PathOp> {
1221 
1222  PathOpConversion(TypeConverter &typeConverter, MLIRContext *context,
1223  const PathInfoTable &pathInfoTable,
1224  PatternBenefit benefit = 1)
1225  : OpConversionPattern(typeConverter, context, benefit),
1226  pathInfoTable(pathInfoTable) {}
1227 
1228  LogicalResult
1229  matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor,
1230  ConversionPatternRewriter &rewriter) const override {
1231  auto *context = op->getContext();
1232  auto pathType = om::PathType::get(context);
1233  auto pathInfo = pathInfoTable.table.lookup(op.getTarget());
1234 
1235  // The 0'th argument is the base path by default.
1236  auto basePath = op->getBlock()->getArgument(0);
1237 
1238  // If the target was optimized away, then replace the path operation with
1239  // a deleted path.
1240  if (!pathInfo) {
1241  if (op.getTargetKind() == firrtl::TargetKind::DontTouch)
1242  return emitError(op.getLoc(), "DontTouch target was deleted");
1243  if (op.getTargetKind() == firrtl::TargetKind::Instance)
1244  return emitError(op.getLoc(), "Instance target was deleted");
1245  rewriter.replaceOpWithNewOp<om::EmptyPathOp>(op);
1246  return success();
1247  }
1248 
1249  auto symbol = pathInfo.symRef;
1250 
1251  // Convert the target kind to an OMIR target. Member references are updated
1252  // to reflect the current kind of reference.
1253  om::TargetKind targetKind;
1254  switch (op.getTargetKind()) {
1255  case firrtl::TargetKind::DontTouch:
1256  targetKind = om::TargetKind::DontTouch;
1257  break;
1258  case firrtl::TargetKind::Reference:
1259  targetKind = om::TargetKind::Reference;
1260  break;
1261  case firrtl::TargetKind::Instance:
1262  if (!isa<InstanceOp, FModuleLike>(pathInfo.op))
1263  return emitError(op.getLoc(), "invalid target for instance path")
1264  .attachNote(pathInfo.op->getLoc())
1265  << "target not instance or module";
1266  targetKind = om::TargetKind::Instance;
1267  break;
1268  case firrtl::TargetKind::MemberInstance:
1269  case firrtl::TargetKind::MemberReference:
1270  if (isa<InstanceOp, FModuleLike>(pathInfo.op))
1271  targetKind = om::TargetKind::MemberInstance;
1272  else
1273  targetKind = om::TargetKind::MemberReference;
1274  break;
1275  }
1276 
1277  // If we are using an alternative base path for this path, get it from the
1278  // passthrough port on the enclosing class.
1279  if (auto altBasePathModule = pathInfo.altBasePathModule) {
1280  // Get the original name of the parent. At this point both FIRRTL classes
1281  // and modules have been converted to OM classes, but we need to look up
1282  // based on the parent's original name.
1283  auto parent = op->getParentOfType<om::ClassOp>();
1284  auto parentName = parent.getName();
1285  if (parentName.ends_with(kClassNameSuffix))
1286  parentName = parentName.drop_back(kClassNameSuffix.size());
1287  auto originalParentName = StringAttr::get(op->getContext(), parentName);
1288 
1289  // Get the base paths passing through the parent.
1290  auto altBasePaths =
1291  pathInfoTable.getRootsForPassthrough(originalParentName);
1292  assert(!altBasePaths.empty() && "expected passthrough base paths");
1293 
1294  // Find the base path passthrough that was associated with this path.
1295  for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
1296  if (altBasePathModule == altBasePath) {
1297  // + 1 to skip default base path
1298  auto basePathArg = op->getBlock()->getArgument(1 + i);
1299  assert(isa<om::BasePathType>(basePathArg.getType()) &&
1300  "expected a passthrough base path");
1301  basePath = basePathArg;
1302  }
1303  }
1304  }
1305 
1306  rewriter.replaceOpWithNewOp<om::PathCreateOp>(
1307  op, pathType, om::TargetKindAttr::get(op.getContext(), targetKind),
1308  basePath, symbol);
1309  return success();
1310  }
1311 
1312  const PathInfoTable &pathInfoTable;
1313 };
1314 
1315 struct WireOpConversion : public OpConversionPattern<WireOp> {
1316  using OpConversionPattern::OpConversionPattern;
1317 
1318  LogicalResult
1319  matchAndRewrite(WireOp wireOp, OpAdaptor adaptor,
1320  ConversionPatternRewriter &rewriter) const override {
1321  auto wireValue = dyn_cast<FIRRTLPropertyValue>(wireOp.getResult());
1322 
1323  // If the wire isn't a Property, not much we can do here.
1324  if (!wireValue)
1325  return failure();
1326 
1327  // If the wire isn't inside a graph region, we can't trivially remove it. In
1328  // practice, this pattern does run for wires in graph regions, so this check
1329  // should pass and we can proceed with the trivial rewrite.
1330  auto regionKindInterface = wireOp->getParentOfType<RegionKindInterface>();
1331  if (!regionKindInterface)
1332  return failure();
1333  if (regionKindInterface.getRegionKind(0) != RegionKind::Graph)
1334  return failure();
1335 
1336  // Find the assignment to the wire.
1337  PropAssignOp propAssign = getPropertyAssignment(wireValue);
1338 
1339  // Use the source of the assignment instead of the wire.
1340  rewriter.replaceOp(wireOp, propAssign.getSrc());
1341 
1342  // Erase the source of the assignment.
1343  rewriter.eraseOp(propAssign);
1344 
1345  return success();
1346  }
1347 };
1348 
1349 struct AnyCastOpConversion : public OpConversionPattern<ObjectAnyRefCastOp> {
1350  using OpConversionPattern::OpConversionPattern;
1351 
1352  LogicalResult
1353  matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor,
1354  ConversionPatternRewriter &rewriter) const override {
1355  rewriter.replaceOpWithNewOp<AnyCastOp>(op, adaptor.getInput());
1356  return success();
1357  }
1358 };
1359 
1361  : public OpConversionPattern<firrtl::ObjectSubfieldOp> {
1362  using OpConversionPattern::OpConversionPattern;
1363 
1365  const TypeConverter &typeConverter, MLIRContext *context,
1366  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable)
1367  : OpConversionPattern(typeConverter, context),
1368  classTypeTable(classTypeTable) {}
1369 
1370  LogicalResult
1371  matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor,
1372  ConversionPatternRewriter &rewriter) const override {
1373  auto omClassType = dyn_cast<om::ClassType>(adaptor.getInput().getType());
1374  if (!omClassType)
1375  return failure();
1376 
1377  // Convert the field-index used by the firrtl implementation, to a symbol,
1378  // as used by the om implementation.
1379  auto firrtlClassType =
1380  classTypeTable.lookup(omClassType.getClassName().getAttr());
1381  if (!firrtlClassType)
1382  return failure();
1383 
1384  const auto &element = firrtlClassType.getElement(op.getIndex());
1385  // We cannot convert input ports to fields.
1386  if (element.direction == Direction::In)
1387  return failure();
1388 
1389  auto field = FlatSymbolRefAttr::get(element.name);
1390  auto path = rewriter.getArrayAttr({field});
1391  auto type = typeConverter->convertType(element.type);
1392  rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(op, type, adaptor.getInput(),
1393  path);
1394  return success();
1395  }
1396 
1397  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable;
1398 };
1399 
1400 struct ClassFieldOpConversion : public OpConversionPattern<ClassFieldOp> {
1401  using OpConversionPattern::OpConversionPattern;
1402 
1403  LogicalResult
1404  matchAndRewrite(ClassFieldOp op, OpAdaptor adaptor,
1405  ConversionPatternRewriter &rewriter) const override {
1406  rewriter.replaceOpWithNewOp<ClassFieldOp>(op, adaptor.getNameAttr(),
1407  adaptor.getValue());
1408  return success();
1409  }
1410 };
1411 
1413  : public OpConversionPattern<ClassExternFieldOp> {
1414  using OpConversionPattern::OpConversionPattern;
1415 
1416  LogicalResult
1417  matchAndRewrite(ClassExternFieldOp op, OpAdaptor adaptor,
1418  ConversionPatternRewriter &rewriter) const override {
1419  auto type = typeConverter->convertType(adaptor.getType());
1420  if (!type)
1421  return failure();
1422  rewriter.replaceOpWithNewOp<ClassExternFieldOp>(op, adaptor.getNameAttr(),
1423  type);
1424  return success();
1425  }
1426 };
1427 
1428 struct ObjectOpConversion : public OpConversionPattern<om::ObjectOp> {
1429  using OpConversionPattern::OpConversionPattern;
1430 
1431  LogicalResult
1432  matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor,
1433  ConversionPatternRewriter &rewriter) const override {
1434  // Replace the object with a new object using the converted actual parameter
1435  // types from the adaptor.
1436  rewriter.replaceOpWithNewOp<om::ObjectOp>(objectOp, objectOp.getType(),
1437  adaptor.getClassNameAttr(),
1438  adaptor.getActualParams());
1439  return success();
1440  }
1441 };
1442 
1443 struct ClassOpSignatureConversion : public OpConversionPattern<om::ClassOp> {
1444  using OpConversionPattern::OpConversionPattern;
1445 
1446  LogicalResult
1447  matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor,
1448  ConversionPatternRewriter &rewriter) const override {
1449  Block *body = classOp.getBodyBlock();
1450  TypeConverter::SignatureConversion result(body->getNumArguments());
1451 
1452  // Convert block argument types.
1453  if (failed(typeConverter->convertSignatureArgs(body->getArgumentTypes(),
1454  result)))
1455  return failure();
1456 
1457  // Convert the body.
1458  if (failed(rewriter.convertRegionTypes(body->getParent(), *typeConverter,
1459  &result)))
1460  return failure();
1461 
1462  rewriter.modifyOpInPlace(classOp, []() {});
1463 
1464  return success();
1465  }
1466 };
1467 
1469  : public OpConversionPattern<om::ClassExternOp> {
1470  using OpConversionPattern::OpConversionPattern;
1471 
1472  LogicalResult
1473  matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor,
1474  ConversionPatternRewriter &rewriter) const override {
1475  Block *body = classOp.getBodyBlock();
1476  TypeConverter::SignatureConversion result(body->getNumArguments());
1477 
1478  // Convert block argument types.
1479  if (failed(typeConverter->convertSignatureArgs(body->getArgumentTypes(),
1480  result)))
1481  return failure();
1482 
1483  // Convert the body.
1484  if (failed(rewriter.convertRegionTypes(body->getParent(), *typeConverter,
1485  &result)))
1486  return failure();
1487 
1488  rewriter.modifyOpInPlace(classOp, []() {});
1489 
1490  return success();
1491  }
1492 };
1493 
1494 struct ObjectFieldOpConversion : public OpConversionPattern<ObjectFieldOp> {
1495  using OpConversionPattern::OpConversionPattern;
1496 
1497  LogicalResult
1498  matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor,
1499  ConversionPatternRewriter &rewriter) const override {
1500  // Replace the object field with a new object field of the appropriate
1501  // result type based on the type converter.
1502  auto type = typeConverter->convertType(op.getType());
1503  if (!type)
1504  return failure();
1505 
1506  rewriter.replaceOpWithNewOp<ObjectFieldOp>(op, type, adaptor.getObject(),
1507  adaptor.getFieldPathAttr());
1508 
1509  return success();
1510  }
1511 };
1512 
1513 // Helpers for dialect conversion setup.
1514 
1515 static void populateConversionTarget(ConversionTarget &target) {
1516  // FIRRTL dialect operations inside ClassOps or not using only OM types must
1517  // be legalized.
1518  target.addDynamicallyLegalDialect<FIRRTLDialect>(
1519  [](Operation *op) { return !op->getParentOfType<om::ClassLike>(); });
1520 
1521  // OM dialect operations are legal if they don't use FIRRTL types.
1522  target.addDynamicallyLegalDialect<OMDialect>([](Operation *op) {
1523  auto containsFIRRTLType = [](Type type) {
1524  return type
1525  .walk([](Type type) {
1526  return failure(isa<FIRRTLDialect>(type.getDialect()));
1527  })
1528  .wasInterrupted();
1529  };
1530  auto noFIRRTLOperands =
1531  llvm::none_of(op->getOperandTypes(), [&containsFIRRTLType](Type type) {
1532  return containsFIRRTLType(type);
1533  });
1534  auto noFIRRTLResults =
1535  llvm::none_of(op->getResultTypes(), [&containsFIRRTLType](Type type) {
1536  return containsFIRRTLType(type);
1537  });
1538  return noFIRRTLOperands && noFIRRTLResults;
1539  });
1540 
1541  // the OM op class.extern.field doesn't have operands or results, so we must
1542  // check it's type for a firrtl dialect.
1543  target.addDynamicallyLegalOp<ClassExternFieldOp>(
1544  [](ClassExternFieldOp op) { return !isa<FIRRTLType>(op.getType()); });
1545 
1546  // OM Class ops are legal if they don't use FIRRTL types for block arguments.
1547  target.addDynamicallyLegalOp<om::ClassOp, om::ClassExternOp>(
1548  [](Operation *op) -> std::optional<bool> {
1549  auto classLike = dyn_cast<om::ClassLike>(op);
1550  if (!classLike)
1551  return std::nullopt;
1552 
1553  return llvm::none_of(
1554  classLike.getBodyBlock()->getArgumentTypes(),
1555  [](Type type) { return isa<FIRRTLDialect>(type.getDialect()); });
1556  });
1557 }
1558 
1559 static void populateTypeConverter(TypeConverter &converter) {
1560  // Convert FIntegerType to IntegerType.
1561  converter.addConversion(
1562  [](IntegerType type) { return OMIntegerType::get(type.getContext()); });
1563  converter.addConversion([](FIntegerType type) {
1564  // The actual width of the IntegerType doesn't actually get used; it will be
1565  // folded away by the dialect conversion infrastructure to the type of the
1566  // APSIntAttr used in the FIntegerConstantOp.
1567  return OMIntegerType::get(type.getContext());
1568  });
1569 
1570  // Convert FIRRTL StringType to OM StringType.
1571  converter.addConversion([](om::StringType type) { return type; });
1572  converter.addConversion([](firrtl::StringType type) {
1573  return om::StringType::get(type.getContext());
1574  });
1575 
1576  // Convert FIRRTL PathType to OM PathType.
1577  converter.addConversion([](om::PathType type) { return type; });
1578  converter.addConversion([](om::BasePathType type) { return type; });
1579  converter.addConversion([](om::FrozenPathType type) { return type; });
1580  converter.addConversion([](om::FrozenBasePathType type) { return type; });
1581  converter.addConversion([](firrtl::PathType type) {
1582  return om::PathType::get(type.getContext());
1583  });
1584 
1585  // Convert FIRRTL Class type to OM Class type.
1586  converter.addConversion([](om::ClassType type) { return type; });
1587  converter.addConversion([](firrtl::ClassType type) {
1588  return om::ClassType::get(type.getContext(), type.getNameAttr());
1589  });
1590 
1591  // Convert FIRRTL AnyRef type to OM Any type.
1592  converter.addConversion([](om::AnyType type) { return type; });
1593  converter.addConversion([](firrtl::AnyRefType type) {
1594  return om::AnyType::get(type.getContext());
1595  });
1596 
1597  // Convert FIRRTL List type to OM List type.
1598  auto convertListType = [&converter](auto type) -> std::optional<mlir::Type> {
1599  auto elementType = converter.convertType(type.getElementType());
1600  if (!elementType)
1601  return {};
1603  };
1604 
1605  converter.addConversion(
1606  [convertListType](om::ListType type) -> std::optional<mlir::Type> {
1607  // Convert any om.list<firrtl> -> om.list<om>
1608  return convertListType(type);
1609  });
1610 
1611  converter.addConversion(
1612  [convertListType](firrtl::ListType type) -> std::optional<mlir::Type> {
1613  // Convert any firrtl.list<firrtl> -> om.list<om>
1614  return convertListType(type);
1615  });
1616 
1617  // Convert FIRRTL Bool type to OM
1618  converter.addConversion(
1619  [](BoolType type) { return IntegerType::get(type.getContext(), 1); });
1620 
1621  // Convert FIRRTL double type to OM.
1622  converter.addConversion(
1623  [](DoubleType type) { return FloatType::getF64(type.getContext()); });
1624 
1625  // Add a target materialization to fold away unrealized conversion casts.
1626  converter.addTargetMaterialization(
1627  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
1628  assert(values.size() == 1);
1629  return values[0];
1630  });
1631 
1632  // Add a source materialization to fold away unrealized conversion casts.
1633  converter.addSourceMaterialization(
1634  [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
1635  assert(values.size() == 1);
1636  return values[0];
1637  });
1638 }
1639 
1641  RewritePatternSet &patterns, TypeConverter &converter,
1642  const PathInfoTable &pathInfoTable,
1643  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
1644  patterns.add<FIntegerConstantOpConversion>(converter, patterns.getContext());
1645  patterns.add<StringConstantOpConversion>(converter, patterns.getContext());
1646  patterns.add<PathOpConversion>(converter, patterns.getContext(),
1647  pathInfoTable);
1648  patterns.add<WireOpConversion>(converter, patterns.getContext());
1649  patterns.add<AnyCastOpConversion>(converter, patterns.getContext());
1650  patterns.add<ObjectSubfieldOpConversion>(converter, patterns.getContext(),
1651  classTypeTable);
1652  patterns.add<ClassFieldOpConversion>(converter, patterns.getContext());
1653  patterns.add<ClassExternFieldOpConversion>(converter, patterns.getContext());
1654  patterns.add<ClassOpSignatureConversion>(converter, patterns.getContext());
1656  patterns.getContext());
1657  patterns.add<ObjectOpConversion>(converter, patterns.getContext());
1658  patterns.add<ObjectFieldOpConversion>(converter, patterns.getContext());
1659  patterns.add<ListCreateOpConversion>(converter, patterns.getContext());
1660  patterns.add<BoolConstantOpConversion>(converter, patterns.getContext());
1661  patterns.add<DoubleConstantOpConversion>(converter, patterns.getContext());
1662  patterns.add<IntegerAddOpConversion>(converter, patterns.getContext());
1663  patterns.add<IntegerMulOpConversion>(converter, patterns.getContext());
1664  patterns.add<IntegerShrOpConversion>(converter, patterns.getContext());
1665 }
1666 
1667 // Convert to OM ops and types in Classes or Modules.
1668 LogicalResult LowerClassesPass::dialectConversion(
1669  Operation *op, const PathInfoTable &pathInfoTable,
1670  const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
1671  ConversionTarget target(getContext());
1672  populateConversionTarget(target);
1673 
1674  TypeConverter typeConverter;
1675  populateTypeConverter(typeConverter);
1676 
1677  RewritePatternSet patterns(&getContext());
1678  populateRewritePatterns(patterns, typeConverter, pathInfoTable,
1679  classTypeTable);
1680 
1681  return applyPartialConversion(op, target, std::move(patterns));
1682 }
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 LogicalResult updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath, InstanceGraph &instanceGraph, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static void populateTypeConverter(TypeConverter &converter)
static LogicalResult updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph, SmallVectorImpl< Operation * > &opsToErase)
static LogicalResult updateObjectsAndInstancesInClass(om::ClassOp classOp, InstanceGraph &instanceGraph, const LoweringState &state, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static LogicalResult updateObjectInClass(firrtl::ObjectOp firrtlObject, const PathInfoTable &pathInfoTable, SmallVectorImpl< Operation * > &opsToErase)
static void populateRewritePatterns(RewritePatternSet &patterns, TypeConverter &converter, const PathInfoTable &pathInfoTable, const DenseMap< StringAttr, firrtl::ClassType > &classTypeTable)
Builder builder
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.
llvm::iterator_range< UseIterator > uses()
virtual void replaceInstance(InstanceOpInterface inst, InstanceOpInterface newInst)
Replaces an instance of a module with another instance.
virtual void erase(InstanceGraphNode *node)
Remove this module from the instance graph.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
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()
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
LogicalResult matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ClassExternFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ClassFieldOp 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::IntegerShrOp 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.
A cache of existing HierPathOps, mostly used to facilitate HierPathOp reuse.
hw::HierPathOp getOpFor(ArrayAttr attr)
FlatSymbolRefAttr getRefFor(ArrayAttr attr)
This represents an annotation targeting a specific operation.
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.