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