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