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