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