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