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