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