CIRCT 20.0.0git
Loading...
Searching...
No Matches
LowerClasses.cpp
Go to the documentation of this file.
1//===- LowerClasses.cpp - Lower to OM classes and objects -----------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This file defines the LowerClasses pass.
10//
11//===----------------------------------------------------------------------===//
12
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 = builder.create<om::PathCreateOp>(
383 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 = builder.create<om::ConstantOp>(
393 loc, om::StringType::get(ctx),
394 StringAttr::get(portDirectionName, om::StringType::get(ctx)));
395
396 // Get a width attribute.
397
398 auto portWidth = builder.create<om::ConstantOp>(
399 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 = builder.create<om::ObjectOp>(
407 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 = builder.create<om::ListCreateOp>(
416 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 PathInfoTableEntry entry;
599 // This entry is used for checking id uniquness.
600 entry.op = op;
601 entry.id = id;
602 entries.push_back(entry);
603 return true;
604 }
605
606 // Copy the middle part from the annotation's NLA.
607 if (hierPathOp) {
608 // Get the original path.
609 auto oldPath = hierPathOp.getNamepath().getValue();
610
611 // Set the moduleName and path based on the hierarchical path. If the
612 // owningModule is in the hierarichal path, start the hierarchical path
613 // there. Otherwise use the top of the hierarchical path.
614 bool pathContainsOwningModule = false;
615 size_t owningModuleIndex = 0;
616 for (auto [idx, pathFramgent] : llvm::enumerate(oldPath)) {
617 if (auto innerRef = dyn_cast<hw::InnerRefAttr>(pathFramgent)) {
618 if (innerRef.getModule() == owningModule.getModuleNameAttr()) {
619 pathContainsOwningModule = true;
620 owningModuleIndex = idx;
621 }
622 } else if (auto symRef = dyn_cast<FlatSymbolRefAttr>(pathFramgent)) {
623 if (symRef.getAttr() == owningModule.getModuleNameAttr()) {
624 pathContainsOwningModule = true;
625 owningModuleIndex = idx;
626 }
627 }
628 }
629
630 if (pathContainsOwningModule) {
631 // Set the path root module name to the owning module.
632 moduleName = owningModule.getModuleNameAttr();
633
634 // Copy the old path, dropping the module name and the prefix to the
635 // owning module.
636 llvm::append_range(path, llvm::reverse(oldPath.drop_back().drop_front(
637 owningModuleIndex)));
638 } else {
639 // Set the path root module name to the start of the path.
640 moduleName = cast<hw::InnerRefAttr>(oldPath.front()).getModule();
641
642 // Copy the old path, dropping the module name.
643 llvm::append_range(path, llvm::reverse(oldPath.drop_back()));
644 }
645 }
646
647 // Check if we need an alternative base path.
648 auto needsAltBasePath = getOrComputeNeedsAltBasePath(
649 op->getLoc(), moduleName, owningModule, hierPathOp);
650 if (failed(needsAltBasePath)) {
651 error = true;
652 return false;
653 }
654
655 // Copy the leading part of the hierarchical path from the owning module
656 // to the start of the annotation's NLA.
657 InstanceGraphNode *node = instanceGraph.lookup(moduleName);
658 while (true) {
659 // If it's not a non-local target, we don't have to append anything,
660 // unless it needs an alternative base path, in which case we do need to
661 // make a hierarchical path.
662 if (!hierPathOp && !needsAltBasePath.value())
663 break;
664
665 // If we get to the owning module or the top, we're done.
666 if (node->getModule() == owningModule || node->noUses())
667 break;
668
669 // Append the next level of hierarchy to the path.
670 assert(node->hasOneUse() && "expected single instantiation");
671 InstanceRecord *inst = *node->usesBegin();
672 path.push_back(
673 OpAnnoTarget(inst->getInstance<InstanceOp>())
674 .getNLAReference(namespaces[inst->getParent()->getModule()]));
675
676 node = inst->getParent();
677 }
678
679 // Create the HierPathOp.
680 std::reverse(path.begin(), path.end());
681 auto pathAttr = ArrayAttr::get(op->getContext(), path);
682
683 // If we need an alternative base path, save the top module from the
684 // path. We will plumb in the basepath from this module.
685 StringAttr altBasePathModule;
686 if (*needsAltBasePath) {
687 altBasePathModule =
688 TypeSwitch<Attribute, StringAttr>(path.front())
689 .Case<FlatSymbolRefAttr>([](auto a) { return a.getAttr(); })
690 .Case<hw::InnerRefAttr>([](auto a) { return a.getModule(); });
691
692 altBasePathRoots.insert(altBasePathModule);
693 }
694
695 // Record the path operation associated with the path op.
696 entries.push_back({op, id, altBasePathModule, pathAttr});
697
698 // Remove this annotation from the operation.
699 return true;
700 });
701
702 if (error)
703 return {};
704
705 return annotations;
706}
707
708LogicalResult PathTracker::updatePathInfoTable(PathInfoTable &pathInfoTable,
709 HierPathCache &cache) const {
710 for (auto root : altBasePathRoots)
711 pathInfoTable.addAltBasePathRoot(root);
712
713 for (const auto &entry : entries) {
714 // Record the path operation associated with the path op.
715 auto [it, inserted] = pathInfoTable.table.try_emplace(entry.id);
716 auto &pathInfo = it->second;
717 if (!inserted) {
718 assert(pathInfo.loc.has_value() && "all PathInfo should have a Location");
719 auto diag = emitError(pathInfo.loc.value(),
720 "path identifier already found, paths must resolve "
721 "to a unique target");
722 diag.attachNote(entry.op->getLoc()) << "other path identifier here";
723 return failure();
724 }
725
726 // Check if the op is targetable by an instance target. The op pointer may
727 // be invalidated later, so this is the last time we want to access it here.
728 bool canBeInstanceTarget = isa<InstanceOp, FModuleLike>(entry.op);
729
730 if (entry.pathAttr) {
731 pathInfo = {entry.op->getLoc(), canBeInstanceTarget,
732 cache.getRefFor(entry.pathAttr), entry.altBasePathModule};
733 } else {
734 pathInfo.loc = entry.op->getLoc();
735 pathInfo.canBeInstanceTarget = canBeInstanceTarget;
736 }
737 }
738 return success();
739}
740
741/// This pass removes the OMIR tracker annotations from operations, and ensures
742/// that each thing that was targeted has a hierarchical path targeting it. It
743/// builds a table which maps the original OMIR tracker annotation IDs to the
744/// corresponding hierarchical paths. We use this table to convert FIRRTL path
745/// ops to OM. FIRRTL paths refer to their target using a target ID, while OM
746/// paths refer to their target using hierarchical paths.
747LogicalResult LowerClassesPass::processPaths(
748 InstanceGraph &instanceGraph,
750 PathInfoTable &pathInfoTable, SymbolTable &symbolTable) {
751 auto circuit = getOperation();
752
753 // Collect the path declarations and owning modules.
754 OwningModuleCache owningModuleCache(instanceGraph);
755 DenseMap<DistinctAttr, FModuleOp> owningModules;
756 std::vector<Operation *> declarations;
757 auto result = circuit.walk([&](Operation *op) {
758 if (auto pathOp = dyn_cast<PathOp>(op)) {
759 // Find the owning module of this path reference.
760 auto owningModule = owningModuleCache.lookup(pathOp);
761 // If this reference does not have a single owning module, it is an error.
762 if (!owningModule) {
763 pathOp->emitError("path does not have a single owning module");
764 return WalkResult::interrupt();
765 }
766 auto target = pathOp.getTargetAttr();
767 auto [it, inserted] = owningModules.try_emplace(target, owningModule);
768 // If this declaration already has a reference, both references must have
769 // the same owning module.
770 if (!inserted && it->second != owningModule) {
771 pathOp->emitError()
772 << "path reference " << target << " has conflicting owning modules "
773 << it->second.getModuleNameAttr() << " and "
774 << owningModule.getModuleNameAttr();
775 return WalkResult::interrupt();
776 }
777 }
778 return WalkResult::advance();
779 });
780
781 if (result.wasInterrupted())
782 return failure();
783
784 if (failed(PathTracker::run(circuit, instanceGraph, namespaces, cache,
785 pathInfoTable, symbolTable, owningModules)))
786 return failure();
787
788 // For each module that will be passing through a base path, compute its
789 // descendants that need this base path passed through.
790 for (auto rootModule : pathInfoTable.getAltBasePathRoots()) {
791 InstanceGraphNode *node = instanceGraph.lookup(rootModule);
792
793 // Do a depth first traversal of the instance graph from rootModule,
794 // looking for descendants that need to be passed through.
795 auto start = llvm::df_begin(node);
796 auto end = llvm::df_end(node);
797 auto it = start;
798 while (it != end) {
799 // Nothing to do for the root module.
800 if (it == start) {
801 ++it;
802 continue;
803 }
804
805 // If we aren't creating a class for this child, skip this hierarchy.
806 if (!shouldCreateClass(
807 it->getModule<FModuleLike>().getModuleNameAttr())) {
808 it = it.skipChildren();
809 continue;
810 }
811
812 // If we are at a leaf, nothing to do.
813 if (it->begin() == it->end()) {
814 ++it;
815 continue;
816 }
817
818 // Track state for this passthrough.
819 StringAttr passthroughModule = it->getModule().getModuleNameAttr();
820 pathInfoTable.addAltBasePathPassthrough(passthroughModule, rootModule);
821
822 ++it;
823 }
824 }
825
826 return success();
827}
828
829/// Lower FIRRTL Class and Object ops to OM Class and Object ops
830void LowerClassesPass::runOnOperation() {
831 MLIRContext *ctx = &getContext();
832 auto intraPassMutex = std::mutex();
833
834 // Get the CircuitOp.
835 CircuitOp circuit = getOperation();
836
837 // Get the InstanceGraph and SymbolTable.
838 InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
839 SymbolTable &symbolTable = getAnalysis<SymbolTable>();
840
842 HierPathCache cache(circuit, symbolTable);
843
844 // Fill `shouldCreateClassMemo`.
845 for (auto *node : instanceGraph)
846 if (auto moduleLike = node->getModule<firrtl::FModuleLike>())
847 shouldCreateClassMemo.insert({moduleLike.getModuleNameAttr(), false});
848
849 parallelForEach(circuit.getContext(), instanceGraph,
850 [&](igraph::InstanceGraphNode *node) {
851 if (auto moduleLike = node->getModule<FModuleLike>())
852 shouldCreateClassMemo[moduleLike.getModuleNameAttr()] =
853 shouldCreateClassImpl(node);
854 });
855
856 // Rewrite all path annotations into inner symbol targets.
857 PathInfoTable pathInfoTable;
858 if (failed(processPaths(instanceGraph, namespaces, cache, pathInfoTable,
859 symbolTable))) {
860 signalPassFailure();
861 return;
862 }
863
864 LoweringState loweringState;
865
866 // Create new OM Class ops serially.
867 DenseMap<StringAttr, firrtl::ClassType> classTypeTable;
868 for (auto *node : instanceGraph) {
869 auto moduleLike = node->getModule<firrtl::FModuleLike>();
870 if (!moduleLike)
871 continue;
872
873 if (shouldCreateClass(moduleLike.getModuleNameAttr())) {
874 auto omClass = createClass(moduleLike, pathInfoTable, intraPassMutex);
875 auto &classLoweringState = loweringState.classLoweringStateTable[omClass];
876 classLoweringState.moduleLike = moduleLike;
877
878 // Find the module instances under the current module with metadata. These
879 // ops will be converted to om objects by this pass. Create a hierarchical
880 // path for each of these instances, which will be used to rebase path
881 // operations. Hierarchical paths must be created serially to ensure their
882 // order in the circuit is deterministc.
883 for (auto *instance : *node) {
884 auto inst = instance->getInstance<firrtl::InstanceOp>();
885 if (!inst)
886 continue;
887 // Get the referenced module.
888 auto module = instance->getTarget()->getModule<FModuleLike>();
889 if (module && shouldCreateClass(module.getModuleNameAttr())) {
890 auto targetSym = getInnerRefTo(
891 {inst, 0}, [&](FModuleLike module) -> hw::InnerSymbolNamespace & {
892 return namespaces[module];
893 });
894 SmallVector<Attribute> path = {targetSym};
895 auto pathAttr = ArrayAttr::get(ctx, path);
896 auto hierPath = cache.getOpFor(pathAttr);
897 classLoweringState.paths.push_back(hierPath);
898 }
899 }
900
901 if (auto classLike =
902 dyn_cast<firrtl::ClassLike>(moduleLike.getOperation()))
903 classTypeTable[classLike.getNameAttr()] = classLike.getInstanceType();
904 }
905 }
906
907 // Move ops from FIRRTL Class to OM Class in parallel.
908 mlir::parallelForEach(ctx, loweringState.classLoweringStateTable,
909 [this, &pathInfoTable](auto &entry) {
910 const auto &[classLike, state] = entry;
911 lowerClassLike(state.moduleLike, classLike,
912 pathInfoTable);
913 });
914
915 // Completely erase Class module-likes, and remove from the InstanceGraph.
916 for (auto &[omClass, state] : loweringState.classLoweringStateTable) {
917 if (isa<firrtl::ClassLike>(state.moduleLike.getOperation())) {
918 InstanceGraphNode *node = instanceGraph.lookup(state.moduleLike);
919 for (auto *use : llvm::make_early_inc_range(node->uses()))
920 use->erase();
921 instanceGraph.erase(node);
922 state.moduleLike.erase();
923 }
924 }
925
926 // Collect ops where Objects can be instantiated.
927 SmallVector<Operation *> objectContainers;
928 for (auto &op : circuit.getOps())
929 if (isa<FModuleOp, om::ClassLike>(op))
930 objectContainers.push_back(&op);
931
932 // Update Object creation ops in Classes or Modules in parallel.
933 if (failed(
934 mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
935 return updateInstances(op, instanceGraph, loweringState,
936 pathInfoTable, intraPassMutex);
937 })))
938 return signalPassFailure();
939
940 // If needed, create and add 'ports' lists of RtlPort objects.
941 if (!rtlPortsToCreate.empty())
942 createAllRtlPorts(pathInfoTable, namespaces, cache);
943
944 // Convert to OM ops and types in Classes or Modules in parallel.
945 if (failed(
946 mlir::failableParallelForEach(ctx, objectContainers, [&](auto *op) {
947 return dialectConversion(op, pathInfoTable, classTypeTable);
948 })))
949 return signalPassFailure();
950
951 // We keep the instance graph up to date, so mark that analysis preserved.
952 markAnalysesPreserved<InstanceGraph>();
953
954 // Reset pass state.
955 rtlPortsToCreate.clear();
956}
957
958std::unique_ptr<mlir::Pass> circt::firrtl::createLowerClassesPass() {
959 return std::make_unique<LowerClassesPass>();
960}
961
962// Predicate to check if a module-like needs a Class to be created.
963bool LowerClassesPass::shouldCreateClass(StringAttr modName) {
964 // Return a memoized result.
965 return shouldCreateClassMemo.at(modName);
966}
967
968void checkAddContainingModulePorts(bool hasContainingModule, OpBuilder builder,
969 SmallVector<Attribute> &fieldNames,
970 SmallVector<NamedAttribute> &fieldTypes) {
971 if (hasContainingModule) {
972 auto name = builder.getStringAttr(kPortsName);
973 fieldNames.push_back(name);
974 fieldTypes.push_back(NamedAttribute(
975 name, TypeAttr::get(getRtlPortsType(builder.getContext()))));
976 }
977}
978
979static om::ClassLike convertExtClass(FModuleLike moduleLike, OpBuilder builder,
980 Twine name,
981 ArrayRef<StringRef> formalParamNames,
982 bool hasContainingModule) {
983 SmallVector<Attribute> fieldNames;
984 SmallVector<NamedAttribute> fieldTypes;
985 for (unsigned i = 0, e = moduleLike.getNumPorts(); i < e; ++i) {
986 auto type = moduleLike.getPortType(i);
987 if (!isa<PropertyType>(type))
988 continue;
989
990 auto direction = moduleLike.getPortDirection(i);
991 if (direction != Direction::In) {
992 auto name = moduleLike.getPortNameAttr(i);
993 fieldNames.push_back(name);
994 fieldTypes.push_back(NamedAttribute(name, TypeAttr::get(type)));
995 }
996 }
997 checkAddContainingModulePorts(hasContainingModule, builder, fieldNames,
998 fieldTypes);
999 return builder.create<om::ClassExternOp>(
1000 moduleLike.getLoc(), name, formalParamNames, fieldNames, fieldTypes);
1001}
1002
1003static om::ClassLike convertClass(FModuleLike moduleLike, OpBuilder builder,
1004 Twine name,
1005 ArrayRef<StringRef> formalParamNames,
1006 bool hasContainingModule) {
1007 // Collect output property assignments to get field names and types.
1008 SmallVector<Attribute> fieldNames;
1009 SmallVector<NamedAttribute> fieldTypes;
1010 for (auto op : llvm::make_early_inc_range(
1011 moduleLike->getRegion(0).getOps<PropAssignOp>())) {
1012 auto outputPort = dyn_cast<BlockArgument>(op.getDest());
1013 if (!outputPort)
1014 continue;
1015
1016 StringAttr name = moduleLike.getPortNameAttr(outputPort.getArgNumber());
1017
1018 fieldNames.push_back(name);
1019 fieldTypes.push_back(
1020 NamedAttribute(name, TypeAttr::get(op.getSrc().getType())));
1021 }
1022
1023 checkAddContainingModulePorts(hasContainingModule, builder, fieldNames,
1024 fieldTypes);
1025 return builder.create<om::ClassOp>(moduleLike.getLoc(), name,
1026 formalParamNames, fieldNames, fieldTypes);
1027}
1028
1029// Create an OM Class op from a FIRRTL Class op or Module op with properties.
1030om::ClassLike LowerClassesPass::createClass(FModuleLike moduleLike,
1031 const PathInfoTable &pathInfoTable,
1032 std::mutex &intraPassMutex) {
1033 // Collect the parameter names from input properties.
1034 SmallVector<StringRef> formalParamNames;
1035 // Every class gets a base path as its first parameter.
1036 formalParamNames.emplace_back("basepath");
1037
1038 // If this class is passing through base paths from above, add those.
1039 size_t nAltBasePaths =
1040 pathInfoTable.getNumAltBasePaths(moduleLike.getModuleNameAttr());
1041 for (size_t i = 0; i < nAltBasePaths; ++i)
1042 formalParamNames.push_back(StringAttr::get(
1043 moduleLike->getContext(), "alt_basepath_" + llvm::Twine(i)));
1044
1045 // Collect the input parameters.
1046 bool hasContainingModule = false;
1047 for (auto [index, port] : llvm::enumerate(moduleLike.getPorts())) {
1048 if (port.isInput() && isa<PropertyType>(port.type)) {
1049 formalParamNames.push_back(port.name);
1050
1051 // Check if we have a 'containingModule' field.
1052 if (port.name.strref().starts_with(kContainingModuleName))
1053 hasContainingModule = true;
1054 }
1055 }
1056
1057 OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
1058
1059 // If there is a 'containingModule', add a parameter for 'ports'.
1060 if (hasContainingModule)
1061 formalParamNames.push_back(kPortsName);
1062
1063 // Take the name from the FIRRTL Class or Module to create the OM Class name.
1064 StringRef className = moduleLike.getName();
1065
1066 // Use the defname for external modules.
1067 if (auto externMod = dyn_cast<FExtModuleOp>(moduleLike.getOperation()))
1068 if (auto defname = externMod.getDefname())
1069 className = defname.value();
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().starts_with(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().starts_with(
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 = builder.create<om::ObjectOp>(
1335 firrtlObject.getLoc(), classType, 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 firrtlObject.replaceAllUsesWith(object.getResult());
1346
1347 // Erase the original Object, now that we're done with it.
1348 opsToErase.push_back(firrtlObject);
1349 return success();
1350}
1351
1352// Helper to update a Module instantiation in a Class. Module instances within a
1353// Class are converted to OM Object instances of the Class derived from the
1354// Module.
1355static LogicalResult
1356updateInstanceInClass(InstanceOp firrtlInstance, hw::HierPathOp hierPath,
1357 InstanceGraph &instanceGraph,
1358 const PathInfoTable &pathInfoTable,
1359 SmallVectorImpl<Operation *> &opsToErase) {
1360
1361 // Set the insertion point right before the instance op.
1362 OpBuilder builder(firrtlInstance);
1363
1364 // Collect the FIRRTL instance inputs to form the Object instance actual
1365 // parameters. The order of the SmallVector needs to match the order the
1366 // formal parameters are declared on the corresponding Class.
1367 SmallVector<Value> actualParameters;
1368 // The 0'th argument is the base path.
1369 auto basePath = firrtlInstance->getBlock()->getArgument(0);
1370 auto symRef = FlatSymbolRefAttr::get(hierPath.getSymNameAttr());
1371 auto rebasedPath = builder.create<om::BasePathCreateOp>(
1372 firrtlInstance->getLoc(), basePath, symRef);
1373
1374 actualParameters.push_back(rebasedPath);
1375
1376 // Add any alternative base paths passing through this instance.
1377 pathInfoTable.collectAltBasePaths(
1378 firrtlInstance, firrtlInstance.getModuleNameAttr().getAttr(),
1379 actualParameters);
1380
1381 for (auto result : firrtlInstance.getResults()) {
1382 // If the port is an output, continue.
1383 if (firrtlInstance.getPortDirection(result.getResultNumber()) ==
1384 Direction::Out)
1385 continue;
1386
1387 // If the port is not a property type, continue.
1388 auto propertyResult = dyn_cast<FIRRTLPropertyValue>(result);
1389 if (!propertyResult)
1390 continue;
1391
1392 // Get the property assignment to the input, and track the assigned
1393 // Value as an actual parameter to the Object instance.
1394 auto propertyAssignment = getPropertyAssignment(propertyResult);
1395 assert(propertyAssignment && "properties require single assignment");
1396 actualParameters.push_back(propertyAssignment.getSrcMutable().get());
1397
1398 // Erase the property assignment.
1399 opsToErase.push_back(propertyAssignment);
1400 }
1401
1402 // Get the referenced module to get its name.
1403 auto referencedModule =
1404 firrtlInstance.getReferencedModule<FModuleLike>(instanceGraph);
1405
1406 StringRef moduleName = referencedModule.getName();
1407
1408 // Use the defname for external modules.
1409 if (auto externMod = dyn_cast<FExtModuleOp>(referencedModule.getOperation()))
1410 if (auto defname = externMod.getDefname())
1411 moduleName = defname.value();
1412
1413 // Convert the FIRRTL Module name to an OM Class type.
1414 auto className = FlatSymbolRefAttr::get(
1415 builder.getStringAttr(moduleName + kClassNameSuffix));
1416
1417 auto classType = om::ClassType::get(firrtlInstance->getContext(), className);
1418
1419 // Create the new Object op.
1420 auto object =
1421 builder.create<om::ObjectOp>(firrtlInstance.getLoc(), classType,
1422 className.getAttr(), actualParameters);
1423
1424 // Replace uses of the FIRRTL instance outputs with field access into
1425 // the OM Object. The later dialect conversion will take care of
1426 // converting the types.
1427 for (auto result : firrtlInstance.getResults()) {
1428 // If the port isn't an output, continue.
1429 if (firrtlInstance.getPortDirection(result.getResultNumber()) !=
1430 Direction::Out)
1431 continue;
1432
1433 // If the port is not a property type, continue.
1434 if (!isa<PropertyType>(result.getType()))
1435 continue;
1436
1437 // The path to the field is just this output's name.
1438 auto objectFieldPath = builder.getArrayAttr({FlatSymbolRefAttr::get(
1439 firrtlInstance.getPortName(result.getResultNumber()))});
1440
1441 // Create the field access.
1442 auto objectField = builder.create<ObjectFieldOp>(
1443 object.getLoc(), result.getType(), object, objectFieldPath);
1444
1445 result.replaceAllUsesWith(objectField);
1446 }
1447
1448 // Erase the original instance, now that we're done with it.
1449 opsToErase.push_back(firrtlInstance);
1450 return success();
1451}
1452
1453// Helper to update a Module instantiation in a Module. Module instances within
1454// a Module are updated to remove the property typed ports.
1455static LogicalResult
1456updateInstanceInModule(InstanceOp firrtlInstance, InstanceGraph &instanceGraph,
1457 SmallVectorImpl<Operation *> &opsToErase) {
1458 // Collect property typed ports to erase.
1459 BitVector portsToErase(firrtlInstance.getNumResults());
1460 for (auto result : firrtlInstance.getResults())
1461 if (isa<PropertyType>(result.getType()))
1462 portsToErase.set(result.getResultNumber());
1463
1464 // If there are none, nothing to do.
1465 if (portsToErase.none())
1466 return success();
1467
1468 // Create a new instance with the property ports removed.
1469 OpBuilder builder(firrtlInstance);
1470 InstanceOp newInstance = firrtlInstance.erasePorts(builder, portsToErase);
1471
1472 // Replace the instance in the instance graph. This is called from multiple
1473 // threads, but because the instance graph data structure is not mutated, and
1474 // only one thread ever sets the instance pointer for a given instance, this
1475 // should be safe.
1476 instanceGraph.replaceInstance(firrtlInstance, newInstance);
1477
1478 // Erase the original instance, which is now replaced.
1479 opsToErase.push_back(firrtlInstance);
1480 return success();
1481}
1482
1483static LogicalResult
1484updateInstancesInModule(FModuleOp moduleOp, InstanceGraph &instanceGraph,
1485 SmallVectorImpl<Operation *> &opsToErase) {
1486 OpBuilder builder(moduleOp);
1487 for (auto &op : moduleOp->getRegion(0).getOps()) {
1488 if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1489 assert(0 && "should be no objects in modules");
1490 } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1491 if (failed(updateInstanceInModule(instanceOp, instanceGraph, opsToErase)))
1492 return failure();
1493 }
1494 }
1495 return success();
1496}
1497
1499 om::ClassOp classOp, InstanceGraph &instanceGraph,
1500 const LoweringState &state, const PathInfoTable &pathInfoTable,
1501 SmallVectorImpl<RtlPortsInfo> &rtlPortsToCreate, std::mutex &intraPassMutex,
1502 SmallVectorImpl<Operation *> &opsToErase) {
1503 OpBuilder builder(classOp);
1504 auto &classState = state.classLoweringStateTable.at(classOp);
1505 auto it = classState.paths.begin();
1506 for (auto &op : classOp->getRegion(0).getOps()) {
1507 if (auto objectOp = dyn_cast<firrtl::ObjectOp>(op)) {
1508 if (failed(updateObjectInClass(objectOp, pathInfoTable, rtlPortsToCreate,
1509 intraPassMutex, opsToErase)))
1510 return failure();
1511 } else if (auto instanceOp = dyn_cast<InstanceOp>(op)) {
1512 if (failed(updateInstanceInClass(instanceOp, *it++, instanceGraph,
1513 pathInfoTable, opsToErase)))
1514 return failure();
1515 }
1516 }
1517 return success();
1518}
1519
1520// Update Object or Module instantiations in a FIRRTL Module or OM Class.
1521LogicalResult LowerClassesPass::updateInstances(
1522 Operation *op, InstanceGraph &instanceGraph, const LoweringState &state,
1523 const PathInfoTable &pathInfoTable, std::mutex &intraPassMutex) {
1524
1525 // Track ops to erase at the end. We can't do this eagerly, since we want to
1526 // loop over each op in the container's body, and we may end up removing some
1527 // ops later in the body when we visit instances earlier in the body.
1528 SmallVector<Operation *> opsToErase;
1529 auto result =
1530 TypeSwitch<Operation *, LogicalResult>(op)
1531
1532 .Case([&](FModuleOp moduleOp) {
1533 // Convert FIRRTL Module instance within a Module to
1534 // remove property ports if necessary.
1535 return updateInstancesInModule(moduleOp, instanceGraph, opsToErase);
1536 })
1537 .Case([&](om::ClassOp classOp) {
1538 // Convert FIRRTL Module instance within a Class to OM
1539 // Object instance.
1541 classOp, instanceGraph, state, pathInfoTable, rtlPortsToCreate,
1542 intraPassMutex, opsToErase);
1543 })
1544 .Default([](auto *op) { return success(); });
1545 if (failed(result))
1546 return result;
1547 // Erase the ops marked to be erased.
1548 for (auto *op : opsToErase)
1549 op->erase();
1550
1551 return success();
1552}
1553
1554// Create and add all 'ports' lists of RtlPort objects for each object.
1555void LowerClassesPass::createAllRtlPorts(
1556 const PathInfoTable &pathInfoTable,
1558 HierPathCache &hierPathCache) {
1559 MLIRContext *ctx = &getContext();
1560
1561 // Get a builder initialized to the end of the top-level module.
1562 OpBuilder builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
1563
1564 // Declare an RtlPort class on the fly.
1565 om::ClassOp::buildSimpleClassOp(
1566 builder, UnknownLoc::get(ctx), kRtlPortClassName,
1567 {"ref", "direction", "width"}, {"ref", "direction", "width"},
1568 {om::PathType::get(ctx), om::StringType::get(ctx),
1569 om::OMIntegerType::get(ctx)});
1570
1571 // Sort the collected rtlPortsToCreate and process each.
1572 llvm::stable_sort(rtlPortsToCreate, [](auto lhs, auto rhs) {
1573 return lhs.object.getClassName() < rhs.object.getClassName();
1574 });
1575
1576 // Create each 'ports' list.
1577 for (auto rtlPortToCreate : rtlPortsToCreate)
1578 createRtlPorts(rtlPortToCreate, pathInfoTable, namespaces, hierPathCache,
1579 builder);
1580}
1581
1582// Pattern rewriters for dialect conversion.
1583
1585 : public OpConversionPattern<FIntegerConstantOp> {
1586 using OpConversionPattern::OpConversionPattern;
1587
1588 LogicalResult
1589 matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor,
1590 ConversionPatternRewriter &rewriter) const override {
1591 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1592 op, om::OMIntegerType::get(op.getContext()),
1593 om::IntegerAttr::get(op.getContext(), adaptor.getValueAttr()));
1594 return success();
1595 }
1596};
1597
1598struct BoolConstantOpConversion : public OpConversionPattern<BoolConstantOp> {
1599 using OpConversionPattern::OpConversionPattern;
1600
1601 LogicalResult
1602 matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor,
1603 ConversionPatternRewriter &rewriter) const override {
1604 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1605 op, rewriter.getBoolAttr(adaptor.getValue()));
1606 return success();
1607 }
1608};
1609
1611 : public OpConversionPattern<DoubleConstantOp> {
1612 using OpConversionPattern::OpConversionPattern;
1613
1614 LogicalResult
1615 matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor,
1616 ConversionPatternRewriter &rewriter) const override {
1617 rewriter.replaceOpWithNewOp<om::ConstantOp>(op, adaptor.getValue());
1618 return success();
1619 }
1620};
1621
1623 : public OpConversionPattern<StringConstantOp> {
1624 using OpConversionPattern::OpConversionPattern;
1625
1626 LogicalResult
1627 matchAndRewrite(StringConstantOp op, OpAdaptor adaptor,
1628 ConversionPatternRewriter &rewriter) const override {
1629 auto stringType = om::StringType::get(op.getContext());
1630 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1631 op, stringType, StringAttr::get(op.getValue(), stringType));
1632 return success();
1633 }
1634};
1635
1637 : public OpConversionPattern<firrtl::ListCreateOp> {
1638 using OpConversionPattern::OpConversionPattern;
1639
1640 LogicalResult
1641 matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor,
1642 ConversionPatternRewriter &rewriter) const override {
1643 auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1644 if (!listType)
1645 return failure();
1646 rewriter.replaceOpWithNewOp<om::ListCreateOp>(op, listType,
1647 adaptor.getElements());
1648 return success();
1649 }
1650};
1651
1653 : public OpConversionPattern<firrtl::ListConcatOp> {
1654 using OpConversionPattern::OpConversionPattern;
1655
1656 LogicalResult
1657 matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor,
1658 ConversionPatternRewriter &rewriter) const override {
1659 auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1660 if (!listType)
1661 return failure();
1662 rewriter.replaceOpWithNewOp<om::ListConcatOp>(op, listType,
1663 adaptor.getSubLists());
1664 return success();
1665 }
1666};
1667
1669 : public OpConversionPattern<firrtl::IntegerAddOp> {
1670 using OpConversionPattern::OpConversionPattern;
1671
1672 LogicalResult
1673 matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor,
1674 ConversionPatternRewriter &rewriter) const override {
1675 rewriter.replaceOpWithNewOp<om::IntegerAddOp>(op, adaptor.getLhs(),
1676 adaptor.getRhs());
1677 return success();
1678 }
1679};
1680
1682 : public OpConversionPattern<firrtl::IntegerMulOp> {
1683 using OpConversionPattern::OpConversionPattern;
1684
1685 LogicalResult
1686 matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor,
1687 ConversionPatternRewriter &rewriter) const override {
1688 rewriter.replaceOpWithNewOp<om::IntegerMulOp>(op, adaptor.getLhs(),
1689 adaptor.getRhs());
1690 return success();
1691 }
1692};
1693
1695 : public OpConversionPattern<firrtl::IntegerShrOp> {
1696 using OpConversionPattern::OpConversionPattern;
1697
1698 LogicalResult
1699 matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor,
1700 ConversionPatternRewriter &rewriter) const override {
1701 rewriter.replaceOpWithNewOp<om::IntegerShrOp>(op, adaptor.getLhs(),
1702 adaptor.getRhs());
1703 return success();
1704 }
1705};
1706
1708 : public OpConversionPattern<firrtl::IntegerShlOp> {
1709 using OpConversionPattern::OpConversionPattern;
1710
1711 LogicalResult
1712 matchAndRewrite(firrtl::IntegerShlOp op, OpAdaptor adaptor,
1713 ConversionPatternRewriter &rewriter) const override {
1714 rewriter.replaceOpWithNewOp<om::IntegerShlOp>(op, adaptor.getLhs(),
1715 adaptor.getRhs());
1716 return success();
1717 }
1718};
1719
1720struct PathOpConversion : public OpConversionPattern<firrtl::PathOp> {
1721
1722 PathOpConversion(TypeConverter &typeConverter, MLIRContext *context,
1723 const PathInfoTable &pathInfoTable,
1724 PatternBenefit benefit = 1)
1725 : OpConversionPattern(typeConverter, context, benefit),
1727
1728 LogicalResult
1729 matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor,
1730 ConversionPatternRewriter &rewriter) const override {
1731 auto *context = op->getContext();
1732 auto pathType = om::PathType::get(context);
1733 auto pathInfoIt = pathInfoTable.table.find(op.getTarget());
1734
1735 // The 0'th argument is the base path by default.
1736 auto basePath = op->getBlock()->getArgument(0);
1737
1738 // If the target was optimized away, then replace the path operation with
1739 // a deleted path.
1740 if (pathInfoIt == pathInfoTable.table.end()) {
1741 if (op.getTargetKind() == firrtl::TargetKind::DontTouch)
1742 return emitError(op.getLoc(), "DontTouch target was deleted");
1743 if (op.getTargetKind() == firrtl::TargetKind::Instance)
1744 return emitError(op.getLoc(), "Instance target was deleted");
1745 rewriter.replaceOpWithNewOp<om::EmptyPathOp>(op);
1746 return success();
1747 }
1748
1749 auto pathInfo = pathInfoIt->second;
1750 auto symbol = pathInfo.symRef;
1751
1752 // Convert the target kind to an OMIR target. Member references are updated
1753 // to reflect the current kind of reference.
1754 om::TargetKind targetKind;
1755 switch (op.getTargetKind()) {
1756 case firrtl::TargetKind::DontTouch:
1757 targetKind = om::TargetKind::DontTouch;
1758 break;
1759 case firrtl::TargetKind::Reference:
1760 targetKind = om::TargetKind::Reference;
1761 break;
1762 case firrtl::TargetKind::Instance:
1763 if (!pathInfo.canBeInstanceTarget)
1764 return emitError(op.getLoc(), "invalid target for instance path")
1765 .attachNote(pathInfo.loc)
1766 << "target not instance or module";
1767 targetKind = om::TargetKind::Instance;
1768 break;
1769 case firrtl::TargetKind::MemberInstance:
1770 case firrtl::TargetKind::MemberReference:
1771 if (pathInfo.canBeInstanceTarget)
1772 targetKind = om::TargetKind::MemberInstance;
1773 else
1774 targetKind = om::TargetKind::MemberReference;
1775 break;
1776 }
1777
1778 // If we are using an alternative base path for this path, get it from the
1779 // passthrough port on the enclosing class.
1780 if (auto altBasePathModule = pathInfo.altBasePathModule) {
1781 // Get the original name of the parent. At this point both FIRRTL classes
1782 // and modules have been converted to OM classes, but we need to look up
1783 // based on the parent's original name.
1784 auto parent = op->getParentOfType<om::ClassOp>();
1785 auto parentName = parent.getName();
1786 if (parentName.ends_with(kClassNameSuffix))
1787 parentName = parentName.drop_back(kClassNameSuffix.size());
1788 auto originalParentName = StringAttr::get(op->getContext(), parentName);
1789
1790 // Get the base paths passing through the parent.
1791 auto altBasePaths =
1792 pathInfoTable.getRootsForPassthrough(originalParentName);
1793 assert(!altBasePaths.empty() && "expected passthrough base paths");
1794
1795 // Find the base path passthrough that was associated with this path.
1796 for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
1797 if (altBasePathModule == altBasePath) {
1798 // + 1 to skip default base path
1799 auto basePathArg = op->getBlock()->getArgument(1 + i);
1800 assert(isa<om::BasePathType>(basePathArg.getType()) &&
1801 "expected a passthrough base path");
1802 basePath = basePathArg;
1803 }
1804 }
1805 }
1806
1807 rewriter.replaceOpWithNewOp<om::PathCreateOp>(
1808 op, pathType, om::TargetKindAttr::get(op.getContext(), targetKind),
1809 basePath, symbol);
1810 return success();
1811 }
1812
1813 const PathInfoTable &pathInfoTable;
1814};
1815
1816struct WireOpConversion : public OpConversionPattern<WireOp> {
1817 using OpConversionPattern::OpConversionPattern;
1818
1819 LogicalResult
1820 matchAndRewrite(WireOp wireOp, OpAdaptor adaptor,
1821 ConversionPatternRewriter &rewriter) const override {
1822 auto wireValue = dyn_cast<FIRRTLPropertyValue>(wireOp.getResult());
1823
1824 // If the wire isn't a Property, not much we can do here.
1825 if (!wireValue)
1826 return failure();
1827
1828 // If the wire isn't inside a graph region, we can't trivially remove it. In
1829 // practice, this pattern does run for wires in graph regions, so this check
1830 // should pass and we can proceed with the trivial rewrite.
1831 auto regionKindInterface = wireOp->getParentOfType<RegionKindInterface>();
1832 if (!regionKindInterface)
1833 return failure();
1834 if (regionKindInterface.getRegionKind(0) != RegionKind::Graph)
1835 return failure();
1836
1837 // Find the assignment to the wire.
1838 PropAssignOp propAssign = getPropertyAssignment(wireValue);
1839
1840 // Use the source of the assignment instead of the wire.
1841 rewriter.replaceOp(wireOp, propAssign.getSrc());
1842
1843 // Erase the source of the assignment.
1844 rewriter.eraseOp(propAssign);
1845
1846 return success();
1847 }
1848};
1849
1850struct AnyCastOpConversion : public OpConversionPattern<ObjectAnyRefCastOp> {
1851 using OpConversionPattern::OpConversionPattern;
1852
1853 LogicalResult
1854 matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor,
1855 ConversionPatternRewriter &rewriter) const override {
1856 rewriter.replaceOpWithNewOp<AnyCastOp>(op, adaptor.getInput());
1857 return success();
1858 }
1859};
1860
1862 : public OpConversionPattern<firrtl::ObjectSubfieldOp> {
1863 using OpConversionPattern::OpConversionPattern;
1864
1866 const TypeConverter &typeConverter, MLIRContext *context,
1867 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable)
1868 : OpConversionPattern(typeConverter, context),
1870
1871 LogicalResult
1872 matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor,
1873 ConversionPatternRewriter &rewriter) const override {
1874 auto omClassType = dyn_cast<om::ClassType>(adaptor.getInput().getType());
1875 if (!omClassType)
1876 return failure();
1877
1878 // Convert the field-index used by the firrtl implementation, to a symbol,
1879 // as used by the om implementation.
1880 auto firrtlClassType =
1881 classTypeTable.lookup(omClassType.getClassName().getAttr());
1882 if (!firrtlClassType)
1883 return failure();
1884
1885 const auto &element = firrtlClassType.getElement(op.getIndex());
1886 // We cannot convert input ports to fields.
1887 if (element.direction == Direction::In)
1888 return failure();
1889
1890 auto field = FlatSymbolRefAttr::get(element.name);
1891 auto path = rewriter.getArrayAttr({field});
1892 auto type = typeConverter->convertType(element.type);
1893 rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(op, type, adaptor.getInput(),
1894 path);
1895 return success();
1896 }
1897
1898 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable;
1899};
1900
1901struct ClassFieldsOpConversion : public OpConversionPattern<ClassFieldsOp> {
1902 using OpConversionPattern::OpConversionPattern;
1903
1904 LogicalResult
1905 matchAndRewrite(ClassFieldsOp op, OpAdaptor adaptor,
1906 ConversionPatternRewriter &rewriter) const override {
1907 rewriter.replaceOpWithNewOp<ClassFieldsOp>(op, adaptor.getOperands());
1908 return success();
1909 }
1910};
1911
1912struct ObjectOpConversion : public OpConversionPattern<om::ObjectOp> {
1913 using OpConversionPattern::OpConversionPattern;
1914
1915 LogicalResult
1916 matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor,
1917 ConversionPatternRewriter &rewriter) const override {
1918 // Replace the object with a new object using the converted actual parameter
1919 // types from the adaptor.
1920 rewriter.replaceOpWithNewOp<om::ObjectOp>(objectOp, objectOp.getType(),
1921 adaptor.getClassNameAttr(),
1922 adaptor.getActualParams());
1923 return success();
1924 }
1925};
1926
1927static LogicalResult convertClassLike(om::ClassLike classOp,
1928 TypeConverter typeConverter,
1929 ConversionPatternRewriter &rewriter) {
1930 Block *body = classOp.getBodyBlock();
1931 TypeConverter::SignatureConversion result(body->getNumArguments());
1932
1933 // Convert block argument types.
1934 if (failed(
1935 typeConverter.convertSignatureArgs(body->getArgumentTypes(), result)))
1936 return failure();
1937
1938 // Convert the body.
1939 if (failed(rewriter.convertRegionTypes(body->getParent(), typeConverter,
1940 &result)))
1941 return failure();
1942
1943 rewriter.modifyOpInPlace(classOp, [&]() {
1944 mlir::AttrTypeReplacer replacer;
1945 replacer.addReplacement([&](TypeAttr typeAttr) {
1946 return mlir::TypeAttr::get(
1947 typeConverter.convertType(typeAttr.getValue()));
1948 });
1949 classOp.replaceFieldTypes(replacer);
1950 });
1951
1952 return success();
1953}
1954
1956 using OpConversionPattern::OpConversionPattern;
1957
1958 LogicalResult
1959 matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor,
1960 ConversionPatternRewriter &rewriter) const override {
1961 return convertClassLike(classOp, *typeConverter, rewriter);
1962 }
1963};
1964
1966 : public OpConversionPattern<om::ClassExternOp> {
1967 using OpConversionPattern::OpConversionPattern;
1968
1969 LogicalResult
1970 matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor,
1971 ConversionPatternRewriter &rewriter) const override {
1972 return convertClassLike(classOp, *typeConverter, rewriter);
1973 }
1974};
1975
1976struct ObjectFieldOpConversion : public OpConversionPattern<ObjectFieldOp> {
1977 using OpConversionPattern::OpConversionPattern;
1978
1979 LogicalResult
1980 matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor,
1981 ConversionPatternRewriter &rewriter) const override {
1982 // Replace the object field with a new object field of the appropriate
1983 // result type based on the type converter.
1984 auto type = typeConverter->convertType(op.getType());
1985 if (!type)
1986 return failure();
1987
1988 rewriter.replaceOpWithNewOp<ObjectFieldOp>(op, type, adaptor.getObject(),
1989 adaptor.getFieldPathAttr());
1990
1991 return success();
1992 }
1993};
1994
1995// Helpers for dialect conversion setup.
1996
1997static void populateConversionTarget(ConversionTarget &target) {
1998 // FIRRTL dialect operations inside ClassOps or not using only OM types must
1999 // be legalized.
2000 target.addDynamicallyLegalDialect<FIRRTLDialect>(
2001 [](Operation *op) { return !op->getParentOfType<om::ClassLike>(); });
2002
2003 // OM dialect operations are legal if they don't use FIRRTL types.
2004 target.addDynamicallyLegalDialect<OMDialect>([](Operation *op) {
2005 auto containsFIRRTLType = [](Type type) {
2006 return type
2007 .walk([](Type type) {
2008 return failure(isa<FIRRTLDialect>(type.getDialect()));
2009 })
2010 .wasInterrupted();
2011 };
2012 auto noFIRRTLOperands =
2013 llvm::none_of(op->getOperandTypes(), [&containsFIRRTLType](Type type) {
2014 return containsFIRRTLType(type);
2015 });
2016 auto noFIRRTLResults =
2017 llvm::none_of(op->getResultTypes(), [&containsFIRRTLType](Type type) {
2018 return containsFIRRTLType(type);
2019 });
2020 return noFIRRTLOperands && noFIRRTLResults;
2021 });
2022
2023 // OM Class ops are legal if they don't use FIRRTL types for block arguments.
2024 target.addDynamicallyLegalOp<om::ClassOp, om::ClassExternOp>(
2025 [](Operation *op) -> std::optional<bool> {
2026 auto classLike = dyn_cast<om::ClassLike>(op);
2027 if (!classLike)
2028 return std::nullopt;
2029 auto fieldNames = classLike.getFieldNames();
2030 if (!llvm::all_of(fieldNames, [&](auto field) {
2031 std::optional<Type> type =
2032 classLike.getFieldType(cast<StringAttr>(field));
2033 return type.has_value() && !isa<FIRRTLType>(type.value());
2034 }))
2035 return false;
2036
2037 return llvm::none_of(
2038 classLike.getBodyBlock()->getArgumentTypes(),
2039 [](Type type) { return isa<FIRRTLDialect>(type.getDialect()); });
2040 });
2041}
2042
2043static void populateTypeConverter(TypeConverter &converter) {
2044 // Convert FIntegerType to IntegerType.
2045 converter.addConversion(
2046 [](IntegerType type) { return OMIntegerType::get(type.getContext()); });
2047 converter.addConversion([](FIntegerType type) {
2048 // The actual width of the IntegerType doesn't actually get used; it will be
2049 // folded away by the dialect conversion infrastructure to the type of the
2050 // APSIntAttr used in the FIntegerConstantOp.
2051 return OMIntegerType::get(type.getContext());
2052 });
2053
2054 // Convert FIRRTL StringType to OM StringType.
2055 converter.addConversion([](om::StringType type) { return type; });
2056 converter.addConversion([](firrtl::StringType type) {
2057 return om::StringType::get(type.getContext());
2058 });
2059
2060 // Convert FIRRTL PathType to OM PathType.
2061 converter.addConversion([](om::PathType type) { return type; });
2062 converter.addConversion([](om::BasePathType type) { return type; });
2063 converter.addConversion([](om::FrozenPathType type) { return type; });
2064 converter.addConversion([](om::FrozenBasePathType type) { return type; });
2065 converter.addConversion([](firrtl::PathType type) {
2066 return om::PathType::get(type.getContext());
2067 });
2068
2069 // Convert FIRRTL Class type to OM Class type.
2070 converter.addConversion([](om::ClassType type) { return type; });
2071 converter.addConversion([](firrtl::ClassType type) {
2072 return om::ClassType::get(type.getContext(), type.getNameAttr());
2073 });
2074
2075 // Convert FIRRTL AnyRef type to OM Any type.
2076 converter.addConversion([](om::AnyType type) { return type; });
2077 converter.addConversion([](firrtl::AnyRefType type) {
2078 return om::AnyType::get(type.getContext());
2079 });
2080
2081 // Convert FIRRTL List type to OM List type.
2082 auto convertListType = [&converter](auto type) -> std::optional<mlir::Type> {
2083 // If the element type is already in the OM dialect, there's nothing to do.
2084 if (isa<om::OMDialect>(type.getElementType().getDialect()))
2085 return type;
2086 auto elementType = converter.convertType(type.getElementType());
2087 if (!elementType)
2088 return {};
2089 return om::ListType::get(elementType);
2090 };
2091
2092 converter.addConversion(
2093 [convertListType](om::ListType type) -> std::optional<mlir::Type> {
2094 // Convert any om.list<firrtl> -> om.list<om>
2095 return convertListType(type);
2096 });
2097
2098 converter.addConversion(
2099 [convertListType](firrtl::ListType type) -> std::optional<mlir::Type> {
2100 // Convert any firrtl.list<firrtl> -> om.list<om>
2101 return convertListType(type);
2102 });
2103
2104 // Convert FIRRTL Bool type to OM
2105 converter.addConversion(
2106 [](BoolType type) { return IntegerType::get(type.getContext(), 1); });
2107
2108 // Convert FIRRTL double type to OM.
2109 converter.addConversion(
2110 [](DoubleType type) { return Float64Type::get(type.getContext()); });
2111
2112 // Add a target materialization such that the conversion does not fail when a
2113 // type conversion could not be reconciled automatically by the framework.
2114 converter.addTargetMaterialization(
2115 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2116 assert(values.size() == 1);
2117 return builder.create<UnrealizedConversionCastOp>(loc, type, values[0])
2118 ->getResult(0);
2119 });
2120
2121 // Add a source materialization such that the conversion does not fail when a
2122 // type conversion could not be reconciled automatically by the framework.
2123 converter.addSourceMaterialization(
2124 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2125 assert(values.size() == 1);
2126 return builder.create<UnrealizedConversionCastOp>(loc, type, values[0])
2127 ->getResult(0);
2128 });
2129}
2130
2132 RewritePatternSet &patterns, TypeConverter &converter,
2133 const PathInfoTable &pathInfoTable,
2134 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2135 patterns.add<FIntegerConstantOpConversion>(converter, patterns.getContext());
2136 patterns.add<StringConstantOpConversion>(converter, patterns.getContext());
2137 patterns.add<PathOpConversion>(converter, patterns.getContext(),
2138 pathInfoTable);
2139 patterns.add<WireOpConversion>(converter, patterns.getContext());
2140 patterns.add<AnyCastOpConversion>(converter, patterns.getContext());
2141 patterns.add<ObjectSubfieldOpConversion>(converter, patterns.getContext(),
2142 classTypeTable);
2143 patterns.add<ClassFieldsOpConversion>(converter, patterns.getContext());
2144 patterns.add<ClassOpSignatureConversion>(converter, patterns.getContext());
2146 patterns.getContext());
2147 patterns.add<ObjectOpConversion>(converter, patterns.getContext());
2148 patterns.add<ObjectFieldOpConversion>(converter, patterns.getContext());
2149 patterns.add<ListCreateOpConversion>(converter, patterns.getContext());
2150 patterns.add<ListConcatOpConversion>(converter, patterns.getContext());
2151 patterns.add<BoolConstantOpConversion>(converter, patterns.getContext());
2152 patterns.add<DoubleConstantOpConversion>(converter, patterns.getContext());
2153 patterns.add<IntegerAddOpConversion>(converter, patterns.getContext());
2154 patterns.add<IntegerMulOpConversion>(converter, patterns.getContext());
2155 patterns.add<IntegerShrOpConversion>(converter, patterns.getContext());
2156 patterns.add<IntegerShlOpConversion>(converter, patterns.getContext());
2157}
2158
2159// Convert to OM ops and types in Classes or Modules.
2160LogicalResult LowerClassesPass::dialectConversion(
2161 Operation *op, const PathInfoTable &pathInfoTable,
2162 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2163 ConversionTarget target(getContext());
2165
2166 TypeConverter typeConverter;
2167 populateTypeConverter(typeConverter);
2168
2169 RewritePatternSet patterns(&getContext());
2170 populateRewritePatterns(patterns, typeConverter, pathInfoTable,
2171 classTypeTable);
2172
2173 return applyPartialConversion(op, target, std::move(patterns));
2174}
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 LogicalResult convertClassLike(om::ClassLike classOp, TypeConverter typeConverter, ConversionPatternRewriter &rewriter)
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 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.
std::unique_ptr< mlir::Pass > createLowerClassesPass()
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
LogicalResult matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(BoolConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ClassFieldsOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(FIntegerConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerShlOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(ObjectFieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
ObjectSubfieldOpConversion(const TypeConverter &typeConverter, MLIRContext *context, const DenseMap< StringAttr, firrtl::ClassType > &classTypeTable)
const DenseMap< StringAttr, firrtl::ClassType > & classTypeTable
LogicalResult matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
PathOpConversion(TypeConverter &typeConverter, MLIRContext *context, const PathInfoTable &pathInfoTable, PatternBenefit benefit=1)
const PathInfoTable & pathInfoTable
LogicalResult matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(StringConstantOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
LogicalResult matchAndRewrite(WireOp wireOp, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override
An annotation target is used to keep track of something that is targeted by an Annotation.
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.