CIRCT 24.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.getModuleNameAttr()] =
1010 classLike.getInstanceType();
1011 }
1012 }
1013
1014 // Move ops from FIRRTL Class to OM Class in parallel.
1015 mlir::parallelForEach(ctx, loweringState.classLoweringStateTable,
1016 [this, &pathInfoTable](auto &entry) {
1017 const auto &[classLike, state] = entry;
1018 lowerClassLike(state.moduleLike, classLike,
1019 pathInfoTable);
1020 });
1021
1022 // Erase property ports from all modules that had classes created. This must
1023 // be done separately because multiple modules can share the same class (e.g.,
1024 // external modules with the same defname).
1025 for (auto moduleLike : modulesToErasePortsFrom) {
1026 BitVector portsToErase(moduleLike.getNumPorts());
1027 for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i)
1028 if (isa<PropertyType>(moduleLike.getPortType(i)))
1029 portsToErase.set(i);
1030 moduleLike.erasePorts(portsToErase);
1031 }
1032
1033 // Completely erase Class module-likes, and remove from the InstanceGraph.
1034 for (auto &[omClass, state] : loweringState.classLoweringStateTable) {
1035 if (isa<firrtl::ClassLike>(state.moduleLike.getOperation())) {
1036 InstanceGraphNode *node = instanceGraph->lookup(state.moduleLike);
1037 for (auto *use : llvm::make_early_inc_range(node->uses()))
1038 use->erase();
1039 instanceGraph->erase(node);
1040 state.moduleLike.erase();
1041 }
1042 }
1043
1044 // Collect ops where Objects can be instantiated.
1045 SmallVector<Operation *> objectContainers;
1046 for (auto &op : circuit.getOps())
1047 if (isa<FModuleOp, om::ClassLike>(op))
1048 objectContainers.push_back(&op);
1049
1050 // Update Object creation ops in Classes or Modules in parallel.
1051 if (failed(
1052 mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
1053 return updateInstances(op, *instanceGraph, loweringState,
1054 pathInfoTable, intraPassMutex);
1055 })))
1056 return signalPassFailure();
1057
1058 // If needed, create and add 'ports' lists of RtlPort objects.
1059 if (!rtlPortsToCreate.empty())
1060 createAllRtlPorts(pathInfoTable, namespaces, cache);
1061
1062 // Convert to OM ops and types in Classes or Modules in parallel.
1063 if (failed(
1064 mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
1065 return dialectConversion(op, pathInfoTable, classTypeTable);
1066 })))
1067 return signalPassFailure();
1068
1069 // We keep the instance graph up to date, so mark that analysis preserved.
1070 markAnalysesPreserved<InstanceGraph>();
1071
1072 // Reset pass state.
1073 rtlPortsToCreate.clear();
1074}
1075
1076// Predicate to check if a module-like needs a Class to be created.
1077bool LowerClassesPass::shouldCreateClass(igraph::ModuleOpInterface modOp) {
1078 return instanceInfo->moduleContainsProperties(modOp);
1079}
1080
1081bool LowerClassesPass::shouldCreateClass(StringAttr modName) {
1082 return shouldCreateClass(instanceGraph->lookup(modName)->getModule());
1083}
1084
1085void checkAddContainingModulePorts(bool hasContainingModule, OpBuilder builder,
1086 SmallVector<Attribute> &fieldNames,
1087 SmallVector<NamedAttribute> &fieldTypes) {
1088 if (hasContainingModule) {
1089 auto name = builder.getStringAttr(kPortsName);
1090 fieldNames.push_back(name);
1091 fieldTypes.push_back(NamedAttribute(
1092 name, TypeAttr::get(getRtlPortsType(builder.getContext()))));
1093 }
1094}
1095
1096static om::ClassLike convertExtClass(FModuleLike moduleLike, OpBuilder builder,
1097 Twine name,
1098 ArrayRef<StringRef> formalParamNames,
1099 bool hasContainingModule) {
1100 SmallVector<Attribute> fieldNames;
1101 SmallVector<NamedAttribute> fieldTypes;
1102 for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
1103 auto type = moduleLike.getPortType(i);
1104 if (!isa<PropertyType>(type))
1105 continue;
1106
1107 auto direction = moduleLike.getPortDirection(i);
1108 if (direction != Direction::In) {
1109 auto name = moduleLike.getPortNameAttr(i);
1110 fieldNames.push_back(name);
1111 fieldTypes.push_back(NamedAttribute(name, TypeAttr::get(type)));
1112 }
1113 }
1114 checkAddContainingModulePorts(hasContainingModule, builder, fieldNames,
1115 fieldTypes);
1116 return om::ClassExternOp::create(builder, moduleLike.getLoc(), name,
1117 formalParamNames, fieldNames, fieldTypes);
1118}
1119
1120static om::ClassLike convertClass(FModuleLike moduleLike, OpBuilder builder,
1121 Twine name,
1122 ArrayRef<StringRef> formalParamNames,
1123 bool hasContainingModule) {
1124 // Collect output property assignments to get field names and types.
1125 SmallVector<Attribute> fieldNames;
1126 SmallVector<NamedAttribute> fieldTypes;
1127 for (auto op : llvm::make_early_inc_range(
1128 moduleLike->getRegion(0).getOps<PropAssignOp>())) {
1129 auto outputPort = dyn_cast<BlockArgument>(op.getDest());
1130 if (!outputPort)
1131 continue;
1132
1133 StringAttr name = moduleLike.getPortNameAttr(outputPort.getArgNumber());
1134
1135 fieldNames.push_back(name);
1136 fieldTypes.push_back(
1137 NamedAttribute(name, TypeAttr::get(op.getSrc().getType())));
1138 }
1139
1140 checkAddContainingModulePorts(hasContainingModule, builder, fieldNames,
1141 fieldTypes);
1142 return om::ClassOp::create(builder, moduleLike.getLoc(), name,
1143 formalParamNames, fieldNames, fieldTypes);
1144}
1145
1146// Create an OM Class op from a FIRRTL Class op or Module op with properties.
1147om::ClassLike LowerClassesPass::createClass(FModuleLike moduleLike,
1148 const PathInfoTable &pathInfoTable,
1149 std::mutex &intraPassMutex) {
1150 // Collect the parameter names from input properties.
1151 SmallVector<StringRef> formalParamNames;
1152 // Every class gets a base path as its first parameter.
1153 formalParamNames.emplace_back("basepath");
1154
1155 // If this class is passing through base paths from above, add those.
1156 size_t nAltBasePaths =
1157 pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
1158 for (size_t i = 0; i < nAltBasePaths; ++i)
1159 formalParamNames.push_back(StringAttr::get(
1160 moduleLike->getContext(), "alt_basepath_" + llvm::Twine(i)));
1161
1162 // Collect the input parameters.
1163 bool hasContainingModule = false;
1164 for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
1165 if (port.isInput() && isa<PropertyType>(port.type)) {
1166 formalParamNames.push_back(port.name);
1167
1168 // Check if we have a 'containingModule' field.
1169 if (port.name.strref().contains(kContainingModuleName))
1170 hasContainingModule = true;
1171 }
1172 }
1173
1174 OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
1175
1176 // If there is a 'containingModule', add a parameter for 'ports'.
1177 if (hasContainingModule)
1178 formalParamNames.push_back(kPortsName);
1179
1180 // Take the name from the FIRRTL Class or Module to create the OM Class name.
1181 StringRef className = moduleLike.getModuleName();
1182 StringAttr baseClassNameAttr = moduleLike.getModuleNameAttr();
1183
1184 // Use the defname for external modules.
1185 if (auto externMod = dyn_cast<FExtModuleOp>(moduleLike.getOperation())) {
1186 className = externMod.getExtModuleName();
1187 baseClassNameAttr = externMod.getExtModuleNameAttr();
1188 }
1189
1190 // If the op is a Module or ExtModule, the OM Class would conflict with the HW
1191 // Module, so give it a suffix. There is no formal ABI for this yet.
1192 StringRef suffix =
1193 isa<FModuleOp, FExtModuleOp>(moduleLike) ? kClassNameSuffix : "";
1194
1195 // Construct the OM Class with the FIRRTL Class name and parameter names.
1196 om::ClassLike loweredClassOp;
1197 if (isa<firrtl::ExtClassOp, firrtl::FExtModuleOp>(
1198 moduleLike.getOperation())) {
1199 // External modules are "deduplicated" via their defname. Don't create a
1200 // new external class if we've already created one for this defname.
1201 auto [it, inserted] = externalClassMap.insert({baseClassNameAttr, {}});
1202 if (inserted)
1203 it->getSecond() = convertExtClass(moduleLike, builder, className + suffix,
1204 formalParamNames, hasContainingModule);
1205 loweredClassOp = it->getSecond();
1206 } else {
1207 loweredClassOp = convertClass(moduleLike, builder, className + suffix,
1208 formalParamNames, hasContainingModule);
1209 }
1210
1211 SymbolTable::setSymbolVisibility(
1212 loweredClassOp,
1213 cast<mlir::SymbolOpInterface>(moduleLike.getOperation()).getVisibility());
1214
1215 return loweredClassOp;
1216}
1217
1218void LowerClassesPass::lowerClassLike(FModuleLike moduleLike,
1219 om::ClassLike classLike,
1220 const PathInfoTable &pathInfoTable) {
1221
1222 if (auto classOp = dyn_cast<om::ClassOp>(classLike.getOperation())) {
1223 return lowerClass(classOp, moduleLike, pathInfoTable);
1224 }
1225 if (auto classExternOp =
1226 dyn_cast<om::ClassExternOp>(classLike.getOperation())) {
1227 return lowerClassExtern(classExternOp, moduleLike);
1228 }
1229 llvm_unreachable("unhandled class-like op");
1230}
1231
1232void LowerClassesPass::lowerClass(om::ClassOp classOp, FModuleLike moduleLike,
1233 const PathInfoTable &pathInfoTable) {
1234 // Collect information about property ports.
1235 SmallVector<Property> inputProperties;
1236 BitVector portsToErase(moduleLike.getNumPorts());
1237 bool hasContainingModule = false;
1238 for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
1239 // For Module ports that aren't property types, move along.
1240 if (!isa<PropertyType>(port.type))
1241 continue;
1242
1243 // Remember input properties to create the OM Class formal parameters.
1244 if (port.isInput()) {
1245 inputProperties.push_back({index, port.name, port.type, port.loc});
1246
1247 // Check if we have a 'containingModule' field.
1248 if (port.name.strref().contains(kContainingModuleName))
1249 hasContainingModule = true;
1250 }
1251
1252 // In case this is a Module, remember to erase this port.
1253 portsToErase.set(index);
1254 }
1255
1256 // Construct the OM Class body with block arguments for each input property,
1257 // updating the mapping to map from the input property to the block argument.
1258 Block *moduleBody = &moduleLike->getRegion(0).front();
1259 Block *classBody = &classOp->getRegion(0).emplaceBlock();
1260 // Every class created from a module gets a base path as its first parameter.
1261 auto basePathType = om::BasePathType::get(&getContext());
1262 auto unknownLoc = UnknownLoc::get(&getContext());
1263 classBody->addArgument(basePathType, unknownLoc);
1264
1265 // If this class is passing through base paths from above, add those.
1266 size_t nAltBasePaths =
1267 pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
1268 for (size_t i = 0; i < nAltBasePaths; ++i)
1269 classBody->addArgument(basePathType, unknownLoc);
1270
1271 // Move operations from the modulelike to the OM class.
1272 for (auto &op : llvm::make_early_inc_range(llvm::reverse(*moduleBody))) {
1273 if (auto instance = dyn_cast<InstanceOp>(op)) {
1274 if (!shouldCreateClass(instance.getReferencedModuleNameAttr()))
1275 continue;
1276 auto *clone = OpBuilder::atBlockBegin(classBody).clone(op);
1277 for (auto result : instance.getResults()) {
1278 if (isa<PropertyType>(result.getType()))
1279 result.replaceAllUsesWith(clone->getResult(result.getResultNumber()));
1280 }
1281 continue;
1282 }
1283
1284 auto isProperty = [](auto x) { return isa<PropertyType>(x.getType()); };
1285 if (llvm::any_of(op.getOperands(), isProperty) ||
1286 llvm::any_of(op.getResults(), isProperty))
1287 op.moveBefore(classBody, classBody->begin());
1288 }
1289
1290 // Move property ports from the module to the class.
1291 for (auto input : inputProperties) {
1292 auto arg = classBody->addArgument(input.type, input.loc);
1293 moduleBody->getArgument(input.index).replaceAllUsesWith(arg);
1294 }
1295
1296 llvm::SmallVector<mlir::Location> fieldLocs;
1297 llvm::SmallVector<mlir::Value> fieldValues;
1298 for (Operation &op :
1299 llvm::make_early_inc_range(classOp.getBodyBlock()->getOperations())) {
1300 if (auto propAssign = dyn_cast<PropAssignOp>(op)) {
1301 if (auto blockArg = dyn_cast<BlockArgument>(propAssign.getDest())) {
1302 // Store any output property assignments into fields op inputs.
1303 fieldLocs.push_back(op.getLoc());
1304 fieldValues.push_back(propAssign.getSrc());
1305 propAssign.erase();
1306 }
1307 }
1308 }
1309
1310 // If there is a 'containingModule', add an argument for 'ports', and a field.
1311 if (hasContainingModule) {
1312 BlockArgument argumentValue = classBody->addArgument(
1313 getRtlPortsType(&getContext()), UnknownLoc::get(&getContext()));
1314 fieldLocs.push_back(argumentValue.getLoc());
1315 fieldValues.push_back(argumentValue);
1316 }
1317
1318 OpBuilder builder = OpBuilder::atBlockEnd(classOp.getBodyBlock());
1319 classOp.addNewFieldsOp(builder, fieldLocs, fieldValues);
1320
1321 // Port erasure for the `moduleLike` is handled centrally in `runOnOperation`.
1322}
1323
1324void LowerClassesPass::lowerClassExtern(om::ClassExternOp classExternOp,
1325 FModuleLike moduleLike) {
1326 // Construct the OM Class body.
1327 // Add a block arguments for each input property.
1328 // Add a class.extern.field op for each output.
1329 Block *classBody = &classExternOp.getRegion().emplaceBlock();
1330
1331 // Every class gets a base path as its first parameter.
1332 classBody->addArgument(om::BasePathType::get(&getContext()),
1333 UnknownLoc::get(&getContext()));
1334
1335 for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
1336 auto type = moduleLike.getPortType(i);
1337 if (!isa<PropertyType>(type))
1338 continue;
1339
1340 auto loc = moduleLike.getPortLocation(i);
1341 auto direction = moduleLike.getPortDirection(i);
1342 if (direction == Direction::In)
1343 classBody->addArgument(type, loc);
1344 }
1345
1346 // Port erasure for the `moduleLike` is handled centrally in `runOnOperation`.
1347}
1348
1349// Helper to update an Object instantiation. FIRRTL Object instances are
1350// converted to OM Object instances.
1351static LogicalResult updateObjectInClass(
1352 firrtl::ObjectOp firrtlObject, const PathInfoTable &pathInfoTable,
1353 SmallVectorImpl<RtlPortsInfo> &rtlPortsToCreate, std::mutex &intraPassMutex,
1354 SmallVectorImpl<Operation *> &opsToErase) {
1355 // The 0'th argument is the base path.
1356 auto basePath = firrtlObject->getBlock()->getArgument(0);
1357 // build a table mapping the indices of input ports to their position in the
1358 // om class's parameter list.
1359 auto firrtlClassType = firrtlObject.getType();
1360 auto numElements = firrtlClassType.getNumElements();
1361 llvm::SmallVector<unsigned> argIndexTable;
1362 argIndexTable.resize(numElements);
1363
1364 // Get any alternative base paths passing through this module.
1365 SmallVector<Value> altBasePaths;
1366 pathInfoTable.collectAltBasePaths(
1367 firrtlObject, firrtlClassType.getNameAttr().getAttr(), altBasePaths);
1368
1369 // Account for the default base path and any alternatives.
1370 unsigned nextArgIndex = 1 + altBasePaths.size();
1371
1372 for (unsigned i = 0; i < numElements; ++i) {
1373 auto direction = firrtlClassType.getElement(i).direction;
1374 if (direction == Direction::In)
1375 argIndexTable[i] = nextArgIndex++;
1376 }
1377
1378 // Collect its input actual parameters by finding any subfield ops that are
1379 // assigned to. Take the source of the assignment as the actual parameter.
1380
1381 llvm::SmallVector<Value> args;
1382 args.resize(nextArgIndex);
1383 args[0] = basePath;
1384
1385 // Collect any alternative base paths passing through.
1386 for (auto [i, altBasePath] : llvm::enumerate(altBasePaths))
1387 args[1 + i] = altBasePath; // + 1 to skip default base path
1388
1389 firrtl::PathOp containingModuleRef;
1390 for (auto *user : llvm::make_early_inc_range(firrtlObject->getUsers())) {
1391 if (auto subfield = dyn_cast<ObjectSubfieldOp>(user)) {
1392 auto index = subfield.getIndex();
1393 auto direction = firrtlClassType.getElement(index).direction;
1394
1395 // We only lower "writes to input ports" here. Reads from output
1396 // ports will be handled using the conversion framework.
1397 if (direction == Direction::Out)
1398 continue;
1399
1400 for (auto *subfieldUser :
1401 llvm::make_early_inc_range(subfield->getUsers())) {
1402 if (auto propassign = dyn_cast<PropAssignOp>(subfieldUser)) {
1403 // the operands of the propassign may have already been converted to
1404 // om. Use the generic operand getters to get the operands as
1405 // untyped values.
1406 auto dst = propassign.getOperand(0);
1407 auto src = propassign.getOperand(1);
1408 if (dst == subfield.getResult()) {
1409 args[argIndexTable[index]] = src;
1410 opsToErase.push_back(propassign);
1411
1412 // Check if we have a 'containingModule' field.
1413 if (firrtlClassType.getElement(index).name.strref().contains(
1414 kContainingModuleName)) {
1415 assert(!containingModuleRef &&
1416 "expected exactly one containingModule");
1417 assert(isa_and_nonnull<firrtl::PathOp>(src.getDefiningOp()) &&
1418 "expected containingModule to be a PathOp");
1419 containingModuleRef = src.getDefiningOp<firrtl::PathOp>();
1420 }
1421 }
1422 }
1423 }
1424
1425 opsToErase.push_back(subfield);
1426 }
1427 }
1428
1429 // Check that all input ports have been initialized.
1430 for (unsigned i = 0; i < numElements; ++i) {
1431 auto element = firrtlClassType.getElement(i);
1432 if (element.direction == Direction::Out)
1433 continue;
1434
1435 auto argIndex = argIndexTable[i];
1436 if (!args[argIndex])
1437 return emitError(firrtlObject.getLoc())
1438 << "uninitialized input port " << element.name;
1439 }
1440
1441 // Convert the FIRRTL Class type to an OM Class type.
1442 auto className = firrtlObject.getType().getNameAttr();
1443 auto classType = om::ClassType::get(firrtlObject->getContext(), className);
1444
1445 // Create the new Object op.
1446 OpBuilder builder(firrtlObject);
1447
1448 auto object = om::ObjectOp::create(builder, firrtlObject.getLoc(), classType,
1449 firrtlObject.getClassNameAttr(), args);
1450
1451 // If there is a 'containingModule', track that we need to add 'ports'.
1452 if (containingModuleRef) {
1453 std::lock_guard<std::mutex> guard(intraPassMutex);
1454 rtlPortsToCreate.push_back({containingModuleRef, basePath, object});
1455 }
1456
1457 // Replace uses of the FIRRTL Object with the OM Object. The later dialect
1458 // conversion will take care of converting the types.
1459 auto cast = UnrealizedConversionCastOp::create(
1460 builder, object.getLoc(), firrtlObject.getType(), object.getResult());
1461 firrtlObject.replaceAllUsesWith(cast.getResult(0));
1462
1463 // Erase the original Object, now that we're done with it.
1464 opsToErase.push_back(firrtlObject);
1465 return success();
1466}
1467
1468// Helper to update a Module instantiation in a Class. Module instances within a
1469// Class are converted to OM Object instances of the Class derived from the
1470// Module.
1471static LogicalResult
1472updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath,
1473 InstanceGraph &instanceGraph,
1474 const PathInfoTable &pathInfoTable,
1475 SmallVectorImpl<Operation *> &opsToErase) {
1476
1477 // Set the insertion point right before the instance op.
1478 OpBuilder builder(firrtlInstance);
1479
1480 // Collect the FIRRTL instance inputs to form the Object instance actual
1481 // parameters. The order of the SmallVector needs to match the order the
1482 // formal parameters are declared on the corresponding Class.
1483 SmallVector<Value> actualParameters;
1484 // The 0'th argument is the base path.
1485 auto basePath = firrtlInstance->getBlock()->getArgument(0);
1486 auto symRef = FlatSymbolRefAttr::get(hierPath.getSymNameAttr());
1487 auto rebasedPath = om::BasePathCreateOp::create(
1488 builder, firrtlInstance->getLoc(), basePath, symRef);
1489
1490 actualParameters.push_back(rebasedPath);
1491
1492 // Add any alternative base paths passing through this instance.
1493 pathInfoTable.collectAltBasePaths(
1494 firrtlInstance, firrtlInstance.getModuleNameAttr().getAttr(),
1495 actualParameters);
1496
1497 for (auto result : firrtlInstance.getResults()) {
1498 // If the port is an output, continue.
1499 if (firrtlInstance.getPortDirection(result.getResultNumber()) ==
1500 Direction::Out)
1501 continue;
1502
1503 // If the port is not a property type, continue.
1504 auto propertyResult = dyn_cast<FIRRTLPropertyValue>(result);
1505 if (!propertyResult)
1506 continue;
1507
1508 // Get the property assignment to the input, and track the assigned
1509 // Value as an actual parameter to the Object instance.
1510 auto propertyAssignment = getPropertyAssignment(propertyResult);
1511 assert(propertyAssignment && "properties require single assignment");
1512 actualParameters.push_back(propertyAssignment.getSrcMutable().get());
1513
1514 // Erase the property assignment.
1515 opsToErase.push_back(propertyAssignment);
1516 }
1517
1518 // Get the referenced module to get its name.
1519 auto referencedModule =
1520 firrtlInstance.getReferencedModule<FModuleLike>(instanceGraph);
1521
1522 StringRef moduleName = referencedModule.getModuleName();
1523
1524 // Use the defname for external modules.
1525 if (auto externMod = dyn_cast<FExtModuleOp>(referencedModule.getOperation()))
1526 moduleName = externMod.getExtModuleName();
1527
1528 // Convert the FIRRTL Module name to an OM Class type.
1529 auto className = FlatSymbolRefAttr::get(
1530 builder.getStringAttr(moduleName + kClassNameSuffix));
1531
1532 auto classType = om::ClassType::get(firrtlInstance->getContext(), className);
1533
1534 // Create the new Object op.
1535 auto object =
1536 om::ObjectOp::create(builder, firrtlInstance.getLoc(), classType,
1537 className.getAttr(), actualParameters);
1538
1539 // Replace uses of the FIRRTL instance outputs with field access into
1540 // the OM Object. The later dialect conversion will take care of
1541 // converting the types.
1542 for (auto result : firrtlInstance.getResults()) {
1543 // If the port isn't an output, continue.
1544 if (firrtlInstance.getPortDirection(result.getResultNumber()) !=
1545 Direction::Out)
1546 continue;
1547
1548 // If the port is not a property type, continue.
1549 if (!isa<PropertyType>(result.getType()))
1550 continue;
1551
1552 // Create the field access.
1553 auto objectField = om::ObjectFieldOp::create(
1554 builder, object.getLoc(), result.getType(), object,
1555 firrtlInstance.getPortNameAttr(result.getResultNumber()));
1556
1557 result.replaceAllUsesWith(objectField);
1558 }
1559
1560 // Erase the original instance, now that we're done with it.
1561 opsToErase.push_back(firrtlInstance);
1562 return success();
1563}
1564
1565// Helper to update a Module instantiation in a Module. Module instances within
1566// a Module are updated to remove the property typed ports.
1567static LogicalResult
1568updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph,
1569 SmallVectorImpl<Operation *> &opsToErase) {
1570 // Collect property typed ports to erase.
1571 BitVector portsToErase(firrtlInstance.getNumResults());
1572 for (auto result : firrtlInstance.getResults())
1573 if (isa<PropertyType>(result.getType()))
1574 portsToErase.set(result.getResultNumber());
1575
1576 // If there are none, nothing to do.
1577 if (portsToErase.none())
1578 return success();
1579
1580 // Create a new instance with the property ports removed.
1581 auto newInstance =
1582 firrtlInstance.cloneWithErasedPortsAndReplaceUses(portsToErase);
1583
1584 // Replace the instance in the instance graph. This is called from multiple
1585 // threads, but because the instance graph data structure is not mutated, and
1586 // only one thread ever sets the instance pointer for a given instance, this
1587 // should be safe.
1588 instanceGraph.replaceInstance(firrtlInstance, newInstance);
1589
1590 // Erase the original instance, which is now replaced.
1591 opsToErase.push_back(firrtlInstance);
1592 return success();
1593}
1594
1595static LogicalResult
1596updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph,
1597 SmallVectorImpl<Operation *> &opsToErase) {
1598 OpBuilder builder(moduleOp);
1599 for (auto &op : moduleOp->getRegion(0).getOps()) {
1600 if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1601 assert(0 && "should be no objects in modules");
1602 } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1603 if (failed(updateInstanceInModule(instanceOp, instanceGraph, opsToErase)))
1604 return failure();
1605 }
1606 }
1607 return success();
1608}
1609
1611 om::ClassOp classOp, InstanceGraph &instanceGraph,
1612 const LoweringState &state, const PathInfoTable &pathInfoTable,
1613 SmallVectorImpl<RtlPortsInfo> &rtlPortsToCreate, std::mutex &intraPassMutex,
1614 SmallVectorImpl<Operation *> &opsToErase) {
1615 OpBuilder builder(classOp);
1616 auto &classState = state.classLoweringStateTable.at(classOp);
1617 auto it = classState.paths.begin();
1618 for (auto &op : classOp->getRegion(0).getOps()) {
1619 if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1620 if (failed(updateObjectInClass(objectOp, pathInfoTable, rtlPortsToCreate,
1621 intraPassMutex, opsToErase)))
1622 return failure();
1623 } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1624 if (failed(updateInstanceInClass(instanceOp, *it++, instanceGraph,
1625 pathInfoTable, opsToErase)))
1626 return failure();
1627 }
1628 }
1629 return success();
1630}
1631
1632// Update Object or Module instantiations in a FIRRTL Module or OM Class.
1633LogicalResult LowerClassesPass::updateInstances(
1634 Operation *op, InstanceGraph &instanceGraph, const LoweringState &state,
1635 const PathInfoTable &pathInfoTable, std::mutex &intraPassMutex) {
1636
1637 // Track ops to erase at the end. We can't do this eagerly, since we want to
1638 // loop over each op in the container's body, and we may end up removing some
1639 // ops later in the body when we visit instances earlier in the body.
1640 SmallVector<Operation *> opsToErase;
1641 auto result =
1642 TypeSwitch<Operation *, LogicalResult>(op)
1643
1644 .Case([&](FModuleOp moduleOp) {
1645 // Convert FIRRTL Module instance within a Module to
1646 // remove property ports if necessary.
1647 return updateInstancesInModule(moduleOp, instanceGraph, opsToErase);
1648 })
1649 .Case([&](om::ClassOp classOp) {
1650 // Convert FIRRTL Module instance within a Class to OM
1651 // Object instance.
1653 classOp, instanceGraph, state, pathInfoTable, rtlPortsToCreate,
1654 intraPassMutex, opsToErase);
1655 })
1656 .Default([](auto *op) { return success(); });
1657 if (failed(result))
1658 return result;
1659 // Erase the ops marked to be erased.
1660 for (auto *op : opsToErase)
1661 op->erase();
1662
1663 return success();
1664}
1665
1666// Create and add all 'ports' lists of RtlPort objects for each object.
1667void LowerClassesPass::createAllRtlPorts(
1668 const PathInfoTable &pathInfoTable,
1670 HierPathCache &hierPathCache) {
1671 MLIRContext *ctx = &getContext();
1672
1673 // Get a builder initialized to the end of the top-level module.
1674 OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
1675
1676 // Declare an RtlPort class on the fly.
1677 om::ClassOp::buildSimpleClassOp(
1678 builder, UnknownLoc::get(ctx), kRtlPortClassName,
1679 {"ref", "direction", "width"}, {"ref", "direction", "width"},
1680 {om::PathType::get(ctx), om::StringType::get(ctx),
1681 om::OMIntegerType::get(ctx)});
1682
1683 // Sort the collected rtlPortsToCreate and process each.
1684 llvm::stable_sort(rtlPortsToCreate, [](auto lhs, auto rhs) {
1685 return lhs.object.getClassName() < rhs.object.getClassName();
1686 });
1687
1688 // Create each 'ports' list.
1689 for (auto rtlPortToCreate : rtlPortsToCreate)
1690 createRtlPorts(rtlPortToCreate, pathInfoTable, namespaces, hierPathCache,
1691 builder);
1692}
1693
1694//===----------------------------------------------------------------------===//
1695// Conversion Patterns
1696//===----------------------------------------------------------------------===//
1697
1698namespace {
1699
1700struct FIntegerConstantOpConversion
1701 : public OpConversionPattern<FIntegerConstantOp> {
1702 using OpConversionPattern::OpConversionPattern;
1703
1704 LogicalResult
1705 matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor,
1706 ConversionPatternRewriter &rewriter) const override {
1707 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1708 op, om::OMIntegerType::get(op.getContext()),
1709 om::IntegerAttr::get(op.getContext(), adaptor.getValueAttr()));
1710 return success();
1711 }
1712};
1713
1714struct BoolConstantOpConversion : public OpConversionPattern<BoolConstantOp> {
1715 using OpConversionPattern::OpConversionPattern;
1716
1717 LogicalResult
1718 matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor,
1719 ConversionPatternRewriter &rewriter) const override {
1720 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1721 op, rewriter.getBoolAttr(adaptor.getValue()));
1722 return success();
1723 }
1724};
1725
1726struct PropertyAssertOpConversion
1727 : public OpConversionPattern<firrtl::PropertyAssertOp> {
1728 using OpConversionPattern::OpConversionPattern;
1729
1730 LogicalResult
1731 matchAndRewrite(firrtl::PropertyAssertOp op, OpAdaptor adaptor,
1732 ConversionPatternRewriter &rewriter) const override {
1733 rewriter.replaceOpWithNewOp<om::PropertyAssertOp>(
1734 op, adaptor.getCondition(), adaptor.getMessage());
1735 return success();
1736 }
1737};
1738
1739struct DoubleConstantOpConversion
1740 : public OpConversionPattern<DoubleConstantOp> {
1741 using OpConversionPattern::OpConversionPattern;
1742
1743 LogicalResult
1744 matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor,
1745 ConversionPatternRewriter &rewriter) const override {
1746 rewriter.replaceOpWithNewOp<om::ConstantOp>(op, adaptor.getValue());
1747 return success();
1748 }
1749};
1750
1751struct StringConstantOpConversion
1752 : public OpConversionPattern<StringConstantOp> {
1753 using OpConversionPattern::OpConversionPattern;
1754
1755 LogicalResult
1756 matchAndRewrite(StringConstantOp op, OpAdaptor adaptor,
1757 ConversionPatternRewriter &rewriter) const override {
1758 auto stringType = om::StringType::get(op.getContext());
1759 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1760 op, stringType, StringAttr::get(op.getValue(), stringType));
1761 return success();
1762 }
1763};
1764
1765struct ListCreateOpConversion
1766 : public OpConversionPattern<firrtl::ListCreateOp> {
1767 using OpConversionPattern::OpConversionPattern;
1768
1769 LogicalResult
1770 matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor,
1771 ConversionPatternRewriter &rewriter) const override {
1772 auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1773 if (!listType)
1774 return failure();
1775 rewriter.replaceOpWithNewOp<om::ListCreateOp>(op, listType,
1776 adaptor.getElements());
1777 return success();
1778 }
1779};
1780
1781struct ListConcatOpConversion
1782 : public OpConversionPattern<firrtl::ListConcatOp> {
1783 using OpConversionPattern::OpConversionPattern;
1784
1785 LogicalResult
1786 matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor,
1787 ConversionPatternRewriter &rewriter) const override {
1788 auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1789 if (!listType)
1790 return failure();
1791 rewriter.replaceOpWithNewOp<om::ListConcatOp>(op, listType,
1792 adaptor.getSubLists());
1793 return success();
1794 }
1795};
1796
1797struct IntegerAddOpConversion
1798 : public OpConversionPattern<firrtl::IntegerAddOp> {
1799 using OpConversionPattern::OpConversionPattern;
1800
1801 LogicalResult
1802 matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor,
1803 ConversionPatternRewriter &rewriter) const override {
1804 rewriter.replaceOpWithNewOp<om::IntegerAddOp>(op, adaptor.getLhs(),
1805 adaptor.getRhs());
1806 return success();
1807 }
1808};
1809
1810struct IntegerMulOpConversion
1811 : public OpConversionPattern<firrtl::IntegerMulOp> {
1812 using OpConversionPattern::OpConversionPattern;
1813
1814 LogicalResult
1815 matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor,
1816 ConversionPatternRewriter &rewriter) const override {
1817 rewriter.replaceOpWithNewOp<om::IntegerMulOp>(op, adaptor.getLhs(),
1818 adaptor.getRhs());
1819 return success();
1820 }
1821};
1822
1823struct IntegerShrOpConversion
1824 : public OpConversionPattern<firrtl::IntegerShrOp> {
1825 using OpConversionPattern::OpConversionPattern;
1826
1827 LogicalResult
1828 matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor,
1829 ConversionPatternRewriter &rewriter) const override {
1830 rewriter.replaceOpWithNewOp<om::IntegerShrOp>(op, adaptor.getLhs(),
1831 adaptor.getRhs());
1832 return success();
1833 }
1834};
1835
1836struct IntegerShlOpConversion
1837 : public OpConversionPattern<firrtl::IntegerShlOp> {
1838 using OpConversionPattern::OpConversionPattern;
1839
1840 LogicalResult
1841 matchAndRewrite(firrtl::IntegerShlOp op, OpAdaptor adaptor,
1842 ConversionPatternRewriter &rewriter) const override {
1843 rewriter.replaceOpWithNewOp<om::IntegerShlOp>(op, adaptor.getLhs(),
1844 adaptor.getRhs());
1845 return success();
1846 }
1847};
1848
1849struct StringConcatOpConversion
1850 : public OpConversionPattern<firrtl::StringConcatOp> {
1851 using OpConversionPattern::OpConversionPattern;
1852
1853 LogicalResult
1854 matchAndRewrite(firrtl::StringConcatOp op, OpAdaptor adaptor,
1855 ConversionPatternRewriter &rewriter) const override {
1856 rewriter.replaceOpWithNewOp<om::StringConcatOp>(op, adaptor.getOperands());
1857 return success();
1858 }
1859};
1860
1861struct PropEqOpConversion : public OpConversionPattern<firrtl::PropEqOp> {
1862 using OpConversionPattern::OpConversionPattern;
1863
1864 LogicalResult
1865 matchAndRewrite(firrtl::PropEqOp op, OpAdaptor adaptor,
1866 ConversionPatternRewriter &rewriter) const override {
1867 rewriter.replaceOpWithNewOp<om::PropEqOp>(op, adaptor.getLhs(),
1868 adaptor.getRhs());
1869 return success();
1870 }
1871};
1872
1873template <typename FIRRTLOp, typename OMOp>
1874static LogicalResult binaryOpConversion(FIRRTLOp op,
1875 typename FIRRTLOp::Adaptor adaptor,
1876 ConversionPatternRewriter &rewriter) {
1877 rewriter.replaceOpWithNewOp<OMOp>(op, adaptor.getLhs(), adaptor.getRhs());
1878 return success();
1879}
1880
1881struct PathOpConversion : public OpConversionPattern<firrtl::PathOp> {
1882
1883 PathOpConversion(TypeConverter &typeConverter, MLIRContext *context,
1884 const PathInfoTable &pathInfoTable,
1885 PatternBenefit benefit = 1)
1886 : OpConversionPattern(typeConverter, context, benefit),
1887 pathInfoTable(pathInfoTable) {}
1888
1889 LogicalResult
1890 matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor,
1891 ConversionPatternRewriter &rewriter) const override {
1892 auto *context = op->getContext();
1893 auto pathType = om::PathType::get(context);
1894 auto pathInfoIt = pathInfoTable.table.find(op.getTarget());
1895
1896 // The 0'th argument is the base path by default.
1897 auto basePath = op->getBlock()->getArgument(0);
1898
1899 // If the target was optimized away, then replace the path operation with
1900 // a deleted path.
1901 if (pathInfoIt == pathInfoTable.table.end()) {
1902 if (op.getTargetKind() == firrtl::TargetKind::DontTouch)
1903 return emitError(op.getLoc(), "DontTouch target was deleted");
1904 if (op.getTargetKind() == firrtl::TargetKind::Instance)
1905 return emitError(op.getLoc(), "Instance target was deleted");
1906 rewriter.replaceOpWithNewOp<om::EmptyPathOp>(op);
1907 return success();
1908 }
1909
1910 auto pathInfo = pathInfoIt->second;
1911 auto symbol = pathInfo.symRef;
1912
1913 // Convert the target kind to an OMIR target. Member references are updated
1914 // to reflect the current kind of reference.
1915 om::TargetKind targetKind;
1916 switch (op.getTargetKind()) {
1917 case firrtl::TargetKind::DontTouch:
1918 targetKind = om::TargetKind::DontTouch;
1919 break;
1920 case firrtl::TargetKind::Reference:
1921 targetKind = om::TargetKind::Reference;
1922 break;
1923 case firrtl::TargetKind::Instance:
1924 if (!pathInfo.canBeInstanceTarget)
1925 return emitError(op.getLoc(), "invalid target for instance path")
1926 .attachNote(pathInfo.loc)
1927 << "target not instance or module";
1928 targetKind = om::TargetKind::Instance;
1929 break;
1930 case firrtl::TargetKind::MemberInstance:
1931 case firrtl::TargetKind::MemberReference:
1932 if (pathInfo.canBeInstanceTarget)
1933 targetKind = om::TargetKind::MemberInstance;
1934 else
1935 targetKind = om::TargetKind::MemberReference;
1936 break;
1937 }
1938
1939 // If we are using an alternative base path for this path, get it from the
1940 // passthrough port on the enclosing class.
1941 if (auto altBasePathModule = pathInfo.altBasePathModule) {
1942 // Get the original name of the parent. At this point both FIRRTL classes
1943 // and modules have been converted to OM classes, but we need to look up
1944 // based on the parent's original name.
1945 auto parent = op->getParentOfType<om::ClassOp>();
1946 auto parentName = parent.getName();
1947 if (parentName.ends_with(kClassNameSuffix))
1948 parentName = parentName.drop_back(kClassNameSuffix.size());
1949 auto originalParentName = StringAttr::get(op->getContext(), parentName);
1950
1951 // Get the base paths passing through the parent.
1952 auto altBasePaths =
1953 pathInfoTable.getRootsForPassthrough(originalParentName);
1954 assert(!altBasePaths.empty() && "expected passthrough base paths");
1955
1956 // Find the base path passthrough that was associated with this path.
1957 for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
1958 if (altBasePathModule == altBasePath) {
1959 // + 1 to skip default base path
1960 auto basePathArg = op->getBlock()->getArgument(1 + i);
1961 assert(isa<om::BasePathType>(basePathArg.getType()) &&
1962 "expected a passthrough base path");
1963 basePath = basePathArg;
1964 }
1965 }
1966 }
1967
1968 rewriter.replaceOpWithNewOp<om::PathCreateOp>(
1969 op, pathType, om::TargetKindAttr::get(op.getContext(), targetKind),
1970 basePath, symbol);
1971 return success();
1972 }
1973
1974 const PathInfoTable &pathInfoTable;
1975};
1976
1977struct WireOpConversion : public OpConversionPattern<WireOp> {
1978 using OpConversionPattern::OpConversionPattern;
1979
1980 LogicalResult
1981 matchAndRewrite(WireOp wireOp, OpAdaptor adaptor,
1982 ConversionPatternRewriter &rewriter) const override {
1983 auto wireValue = dyn_cast<FIRRTLPropertyValue>(wireOp.getResult());
1984
1985 // If the wire isn't a Property, not much we can do here.
1986 if (!wireValue)
1987 return failure();
1988
1989 // If the wire isn't inside a graph region, we can't trivially remove it. In
1990 // practice, this pattern does run for wires in graph regions, so this check
1991 // should pass and we can proceed with the trivial rewrite.
1992 auto regionKindInterface = wireOp->getParentOfType<RegionKindInterface>();
1993 if (!regionKindInterface)
1994 return failure();
1995 if (regionKindInterface.getRegionKind(0) != RegionKind::Graph)
1996 return failure();
1997
1998 // Find the assignment to the wire.
1999 PropAssignOp propAssign = getPropertyAssignment(wireValue);
2000 if (!propAssign)
2001 return failure();
2002
2003 // Use the source of the assignment instead of the wire.
2004 rewriter.replaceOp(wireOp, propAssign.getSrc());
2005
2006 // Erase the source of the assignment.
2007 rewriter.eraseOp(propAssign);
2008
2009 return success();
2010 }
2011};
2012
2013struct AnyCastOpConversion : public OpConversionPattern<ObjectAnyRefCastOp> {
2014 using OpConversionPattern::OpConversionPattern;
2015
2016 LogicalResult
2017 matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor,
2018 ConversionPatternRewriter &rewriter) const override {
2019 rewriter.replaceOpWithNewOp<om::AnyCastOp>(op, adaptor.getInput());
2020 return success();
2021 }
2022};
2023
2024struct ObjectSubfieldOpConversion
2025 : public OpConversionPattern<firrtl::ObjectSubfieldOp> {
2026 using OpConversionPattern::OpConversionPattern;
2027
2028 ObjectSubfieldOpConversion(
2029 const TypeConverter &typeConverter, MLIRContext *context,
2030 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable)
2031 : OpConversionPattern(typeConverter, context),
2032 classTypeTable(classTypeTable) {}
2033
2034 LogicalResult
2035 matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor,
2036 ConversionPatternRewriter &rewriter) const override {
2037 auto omClassType = dyn_cast<om::ClassType>(adaptor.getInput().getType());
2038 if (!omClassType)
2039 return failure();
2040
2041 // Convert the field-index used by the firrtl implementation, to a symbol,
2042 // as used by the om implementation.
2043 auto firrtlClassType =
2044 classTypeTable.lookup(omClassType.getClassName().getAttr());
2045 if (!firrtlClassType)
2046 return failure();
2047
2048 const auto &element = firrtlClassType.getElement(op.getIndex());
2049 // We cannot convert input ports to fields.
2050 if (element.direction == Direction::In)
2051 return failure();
2052
2053 auto type = typeConverter->convertType(element.type);
2054 rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(op, type, adaptor.getInput(),
2055 element.name);
2056 return success();
2057 }
2058
2059 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable;
2060};
2061
2062struct ClassFieldsOpConversion : public OpConversionPattern<om::ClassFieldsOp> {
2063 using OpConversionPattern::OpConversionPattern;
2064
2065 LogicalResult
2066 matchAndRewrite(om::ClassFieldsOp op, OpAdaptor adaptor,
2067 ConversionPatternRewriter &rewriter) const override {
2068 rewriter.replaceOpWithNewOp<om::ClassFieldsOp>(op, adaptor.getOperands(),
2069 adaptor.getFieldLocsAttr());
2070 return success();
2071 }
2072};
2073
2074struct ObjectOpConversion : public OpConversionPattern<om::ObjectOp> {
2075 using OpConversionPattern::OpConversionPattern;
2076
2077 LogicalResult
2078 matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor,
2079 ConversionPatternRewriter &rewriter) const override {
2080 // Replace the object with a new object using the converted actual parameter
2081 // types from the adaptor.
2082 rewriter.replaceOpWithNewOp<om::ObjectOp>(objectOp, objectOp.getType(),
2083 adaptor.getClassNameAttr(),
2084 adaptor.getActualParams());
2085 return success();
2086 }
2087};
2088
2089static LogicalResult convertClassLike(om::ClassLike classOp,
2090 TypeConverter typeConverter,
2091 ConversionPatternRewriter &rewriter) {
2092 Block *body = classOp.getBodyBlock();
2093 TypeConverter::SignatureConversion result(body->getNumArguments());
2094
2095 // Convert block argument types.
2096 if (failed(
2097 typeConverter.convertSignatureArgs(body->getArgumentTypes(), result)))
2098 return failure();
2099
2100 // Convert the body.
2101 if (failed(rewriter.convertRegionTypes(body->getParent(), typeConverter,
2102 &result)))
2103 return failure();
2104
2105 rewriter.modifyOpInPlace(classOp, [&]() {
2106 mlir::AttrTypeReplacer replacer;
2107 replacer.addReplacement([&](TypeAttr typeAttr) {
2108 return mlir::TypeAttr::get(
2109 typeConverter.convertType(typeAttr.getValue()));
2110 });
2111 classOp.replaceFieldTypes(replacer);
2112 });
2113
2114 return success();
2115}
2116
2117struct ClassOpSignatureConversion : public OpConversionPattern<om::ClassOp> {
2118 using OpConversionPattern::OpConversionPattern;
2119
2120 LogicalResult
2121 matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor,
2122 ConversionPatternRewriter &rewriter) const override {
2123 return convertClassLike(classOp, *typeConverter, rewriter);
2124 }
2125};
2126
2127struct ClassExternOpSignatureConversion
2128 : public OpConversionPattern<om::ClassExternOp> {
2129 using OpConversionPattern::OpConversionPattern;
2130
2131 LogicalResult
2132 matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor,
2133 ConversionPatternRewriter &rewriter) const override {
2134 return convertClassLike(classOp, *typeConverter, rewriter);
2135 }
2136};
2137
2138struct ObjectFieldOpConversion : public OpConversionPattern<om::ObjectFieldOp> {
2139 using OpConversionPattern::OpConversionPattern;
2140
2141 LogicalResult
2142 matchAndRewrite(om::ObjectFieldOp op, OpAdaptor adaptor,
2143 ConversionPatternRewriter &rewriter) const override {
2144 // Replace the object field with a new object field of the appropriate
2145 // result type based on the type converter.
2146 auto type = typeConverter->convertType(op.getType());
2147 if (!type)
2148 return failure();
2149
2150 rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(
2151 op, type, adaptor.getObject(), adaptor.getFieldAttr());
2152
2153 return success();
2154 }
2155};
2156
2157/// Replace OM-to-FIRRTL casts with the OM value.
2158struct UnrealizedConversionCastOpConversion
2159 : public OpConversionPattern<UnrealizedConversionCastOp> {
2160 using OpConversionPattern::OpConversionPattern;
2161
2162 LogicalResult
2163 matchAndRewrite(UnrealizedConversionCastOp op, OpAdaptor adaptor,
2164 ConversionPatternRewriter &rewriter) const override {
2165 if (op.getNumOperands() != 1 || op.getNumResults() != 1)
2166 return failure();
2167 auto type = typeConverter->convertType(op.getResult(0));
2168 if (!type || type != adaptor.getOperands()[0].getType())
2169 return failure();
2170 rewriter.replaceOp(op, adaptor.getOperands()[0]);
2171 return success();
2172 }
2173};
2174
2175struct UnknownValueOpConversion : public OpConversionPattern<UnknownValueOp> {
2176 using OpConversionPattern::OpConversionPattern;
2177
2178 LogicalResult
2179 matchAndRewrite(UnknownValueOp op, OpAdaptor adaptor,
2180 ConversionPatternRewriter &rewriter) const override {
2181 auto convertedType = typeConverter->convertType(op.getType());
2182 if (!convertedType)
2183 return failure();
2184 rewriter.replaceOpWithNewOp<om::UnknownValueOp>(op, convertedType);
2185 return success();
2186 }
2187};
2188
2189} // namespace
2190
2191//===----------------------------------------------------------------------===//
2192// Conversion Setup
2193//===----------------------------------------------------------------------===//
2194
2195static void populateConversionTarget(ConversionTarget &target) {
2196 // FIRRTL dialect operations inside ClassOps or not using only OM types must
2197 // be legalized.
2198 target.addDynamicallyLegalDialect<FIRRTLDialect>(
2199 [](Operation *op) { return !op->getParentOfType<om::ClassLike>(); });
2200
2201 // OM dialect operations are legal if they don't use FIRRTL types.
2202 target.addDynamicallyLegalDialect<om::OMDialect>([](Operation *op) {
2203 auto containsFIRRTLType = [](Type type) {
2204 return type
2205 .walk([](Type type) {
2206 return failure(isa<FIRRTLDialect>(type.getDialect()));
2207 })
2208 .wasInterrupted();
2209 };
2210 auto noFIRRTLOperands =
2211 llvm::none_of(op->getOperandTypes(), [&containsFIRRTLType](Type type) {
2212 return containsFIRRTLType(type);
2213 });
2214 auto noFIRRTLResults =
2215 llvm::none_of(op->getResultTypes(), [&containsFIRRTLType](Type type) {
2216 return containsFIRRTLType(type);
2217 });
2218 return noFIRRTLOperands && noFIRRTLResults;
2219 });
2220
2221 // OM Class ops are legal if they don't use FIRRTL types for block arguments.
2222 target.addDynamicallyLegalOp<om::ClassOp, om::ClassExternOp>(
2223 [](Operation *op) -> std::optional<bool> {
2224 auto classLike = dyn_cast<om::ClassLike>(op);
2225 if (!classLike)
2226 return std::nullopt;
2227 auto fieldNames = classLike.getFieldNames();
2228 if (!llvm::all_of(fieldNames, [&](auto field) {
2229 std::optional<Type> type =
2230 classLike.getFieldType(cast<StringAttr>(field));
2231 return type.has_value() && !isa<FIRRTLType>(type.value());
2232 }))
2233 return false;
2234
2235 return llvm::none_of(
2236 classLike.getBodyBlock()->getArgumentTypes(),
2237 [](Type type) { return isa<FIRRTLDialect>(type.getDialect()); });
2238 });
2239}
2240
2241static void populateTypeConverter(TypeConverter &converter) {
2242 // Convert FIntegerType to IntegerType.
2243 converter.addConversion([](IntegerType type) {
2244 return om::OMIntegerType::get(type.getContext());
2245 });
2246 converter.addConversion([](FIntegerType type) {
2247 // The actual width of the IntegerType doesn't actually get used; it will be
2248 // folded away by the dialect conversion infrastructure to the type of the
2249 // APSIntAttr used in the FIntegerConstantOp.
2250 return om::OMIntegerType::get(type.getContext());
2251 });
2252
2253 // Convert FIRRTL StringType to OM StringType.
2254 converter.addConversion([](om::StringType type) { return type; });
2255 converter.addConversion([](firrtl::StringType type) {
2256 return om::StringType::get(type.getContext());
2257 });
2258
2259 // Convert FIRRTL PathType to OM PathType.
2260 converter.addConversion([](om::PathType type) { return type; });
2261 converter.addConversion([](om::BasePathType type) { return type; });
2262 converter.addConversion([](om::FrozenPathType type) { return type; });
2263 converter.addConversion([](om::FrozenBasePathType type) { return type; });
2264 converter.addConversion([](firrtl::PathType type) {
2265 return om::PathType::get(type.getContext());
2266 });
2267
2268 // Convert FIRRTL Class type to OM Class type.
2269 converter.addConversion([](om::ClassType type) { return type; });
2270 converter.addConversion([](firrtl::ClassType type) {
2271 return om::ClassType::get(type.getContext(), type.getNameAttr());
2272 });
2273
2274 // Convert FIRRTL AnyRef type to OM Any type.
2275 converter.addConversion([](om::AnyType type) { return type; });
2276 converter.addConversion([](firrtl::AnyRefType type) {
2277 return om::AnyType::get(type.getContext());
2278 });
2279
2280 // Convert FIRRTL List type to OM List type.
2281 auto convertListType = [&converter](auto type) -> std::optional<mlir::Type> {
2282 // If the element type is already in the OM dialect, there's nothing to do.
2283 if (isa<om::OMDialect>(type.getElementType().getDialect()))
2284 return type;
2285 auto elementType = converter.convertType(type.getElementType());
2286 if (!elementType)
2287 return {};
2288 return om::ListType::get(elementType);
2289 };
2290
2291 converter.addConversion(
2292 [convertListType](om::ListType type) -> std::optional<mlir::Type> {
2293 // Convert any om.list<firrtl> -> om.list<om>
2294 return convertListType(type);
2295 });
2296
2297 converter.addConversion(
2298 [convertListType](firrtl::ListType type) -> std::optional<mlir::Type> {
2299 // Convert any firrtl.list<firrtl> -> om.list<om>
2300 return convertListType(type);
2301 });
2302
2303 // Convert FIRRTL Bool type to OM
2304 converter.addConversion(
2305 [](BoolType type) { return IntegerType::get(type.getContext(), 1); });
2306
2307 // Convert FIRRTL double type to OM.
2308 converter.addConversion(
2309 [](DoubleType type) { return Float64Type::get(type.getContext()); });
2310
2311 // Add a target materialization such that the conversion does not fail when a
2312 // type conversion could not be reconciled automatically by the framework.
2313 converter.addTargetMaterialization(
2314 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2315 assert(values.size() == 1);
2316 return UnrealizedConversionCastOp::create(builder, loc, type, values[0])
2317 ->getResult(0);
2318 });
2319
2320 // Add a source materialization such that the conversion does not fail when a
2321 // type conversion could not be reconciled automatically by the framework.
2322 converter.addSourceMaterialization(
2323 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2324 assert(values.size() == 1);
2325 return UnrealizedConversionCastOp::create(builder, loc, type, values[0])
2326 ->getResult(0);
2327 });
2328}
2329
2331 ConversionPatternSet &patterns, TypeConverter &converter,
2332 const PathInfoTable &pathInfoTable,
2333 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2334 patterns.add<FIntegerConstantOpConversion>(converter, patterns.getContext());
2335 patterns.add<StringConstantOpConversion>(converter, patterns.getContext());
2336 patterns.add<PathOpConversion>(converter, patterns.getContext(),
2337 pathInfoTable);
2338 patterns.add<WireOpConversion>(converter, patterns.getContext());
2339 patterns.add<AnyCastOpConversion>(converter, patterns.getContext());
2340 patterns.add<ObjectSubfieldOpConversion>(converter, patterns.getContext(),
2341 classTypeTable);
2342 patterns.add<ClassFieldsOpConversion>(converter, patterns.getContext());
2343 patterns.add<ClassOpSignatureConversion>(converter, patterns.getContext());
2344 patterns.add<ClassExternOpSignatureConversion>(converter,
2345 patterns.getContext());
2346 patterns.add<ObjectOpConversion>(converter, patterns.getContext());
2347 patterns.add<ObjectFieldOpConversion>(converter, patterns.getContext());
2348 patterns.add<ListCreateOpConversion>(converter, patterns.getContext());
2349 patterns.add<ListConcatOpConversion>(converter, patterns.getContext());
2350 patterns.add<BoolConstantOpConversion>(converter, patterns.getContext());
2351 patterns.add<PropertyAssertOpConversion>(converter, patterns.getContext());
2352 patterns.add<DoubleConstantOpConversion>(converter, patterns.getContext());
2353 patterns.add<IntegerAddOpConversion>(converter, patterns.getContext());
2354 patterns.add<IntegerMulOpConversion>(converter, patterns.getContext());
2355 patterns.add<IntegerShrOpConversion>(converter, patterns.getContext());
2356 patterns.add<IntegerShlOpConversion>(converter, patterns.getContext());
2357 patterns.add<StringConcatOpConversion>(converter, patterns.getContext());
2358 patterns.add<PropEqOpConversion>(converter, patterns.getContext());
2359 patterns.add(binaryOpConversion<firrtl::BoolAndOp, om::IntegerAndOp>);
2360 patterns.add(binaryOpConversion<firrtl::BoolOrOp, om::IntegerOrOp>);
2361 patterns.add(binaryOpConversion<firrtl::BoolXorOp, om::IntegerXorOp>);
2362 patterns.add<UnrealizedConversionCastOpConversion>(converter,
2363 patterns.getContext());
2364 patterns.add<UnknownValueOpConversion>(converter, patterns.getContext());
2365}
2366
2367// Convert to OM ops and types in Classes or Modules.
2368LogicalResult LowerClassesPass::dialectConversion(
2369 Operation *op, const PathInfoTable &pathInfoTable,
2370 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2371 ConversionTarget target(getContext());
2373
2374 TypeConverter typeConverter;
2375 populateTypeConverter(typeConverter);
2376
2377 ConversionPatternSet patterns(&getContext(), typeConverter);
2378 populateRewritePatterns(patterns, typeConverter, pathInfoTable,
2379 classTypeTable);
2380
2381 return applyPartialConversion(op, target, std::move(patterns));
2382}
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:222
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, List[str] cmdline_args=sys.argv)
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.