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 PropertyAssertOpConversion
1643 : public OpConversionPattern<firrtl::PropertyAssertOp> {
1644 using OpConversionPattern::OpConversionPattern;
1645
1646 LogicalResult
1647 matchAndRewrite(firrtl::PropertyAssertOp op, OpAdaptor adaptor,
1648 ConversionPatternRewriter &rewriter) const override {
1649 rewriter.replaceOpWithNewOp<om::PropertyAssertOp>(
1650 op, adaptor.getCondition(), op.getMessageAttr());
1651 return success();
1652 }
1653};
1654
1655struct DoubleConstantOpConversion
1656 : public OpConversionPattern<DoubleConstantOp> {
1657 using OpConversionPattern::OpConversionPattern;
1658
1659 LogicalResult
1660 matchAndRewrite(DoubleConstantOp op, OpAdaptor adaptor,
1661 ConversionPatternRewriter &rewriter) const override {
1662 rewriter.replaceOpWithNewOp<om::ConstantOp>(op, adaptor.getValue());
1663 return success();
1664 }
1665};
1666
1667struct StringConstantOpConversion
1668 : public OpConversionPattern<StringConstantOp> {
1669 using OpConversionPattern::OpConversionPattern;
1670
1671 LogicalResult
1672 matchAndRewrite(StringConstantOp op, OpAdaptor adaptor,
1673 ConversionPatternRewriter &rewriter) const override {
1674 auto stringType = om::StringType::get(op.getContext());
1675 rewriter.replaceOpWithNewOp<om::ConstantOp>(
1676 op, stringType, StringAttr::get(op.getValue(), stringType));
1677 return success();
1678 }
1679};
1680
1681struct ListCreateOpConversion
1682 : public OpConversionPattern<firrtl::ListCreateOp> {
1683 using OpConversionPattern::OpConversionPattern;
1684
1685 LogicalResult
1686 matchAndRewrite(firrtl::ListCreateOp op, OpAdaptor adaptor,
1687 ConversionPatternRewriter &rewriter) const override {
1688 auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1689 if (!listType)
1690 return failure();
1691 rewriter.replaceOpWithNewOp<om::ListCreateOp>(op, listType,
1692 adaptor.getElements());
1693 return success();
1694 }
1695};
1696
1697struct ListConcatOpConversion
1698 : public OpConversionPattern<firrtl::ListConcatOp> {
1699 using OpConversionPattern::OpConversionPattern;
1700
1701 LogicalResult
1702 matchAndRewrite(firrtl::ListConcatOp op, OpAdaptor adaptor,
1703 ConversionPatternRewriter &rewriter) const override {
1704 auto listType = getTypeConverter()->convertType<om::ListType>(op.getType());
1705 if (!listType)
1706 return failure();
1707 rewriter.replaceOpWithNewOp<om::ListConcatOp>(op, listType,
1708 adaptor.getSubLists());
1709 return success();
1710 }
1711};
1712
1713struct IntegerAddOpConversion
1714 : public OpConversionPattern<firrtl::IntegerAddOp> {
1715 using OpConversionPattern::OpConversionPattern;
1716
1717 LogicalResult
1718 matchAndRewrite(firrtl::IntegerAddOp op, OpAdaptor adaptor,
1719 ConversionPatternRewriter &rewriter) const override {
1720 rewriter.replaceOpWithNewOp<om::IntegerAddOp>(op, adaptor.getLhs(),
1721 adaptor.getRhs());
1722 return success();
1723 }
1724};
1725
1726struct IntegerMulOpConversion
1727 : public OpConversionPattern<firrtl::IntegerMulOp> {
1728 using OpConversionPattern::OpConversionPattern;
1729
1730 LogicalResult
1731 matchAndRewrite(firrtl::IntegerMulOp op, OpAdaptor adaptor,
1732 ConversionPatternRewriter &rewriter) const override {
1733 rewriter.replaceOpWithNewOp<om::IntegerMulOp>(op, adaptor.getLhs(),
1734 adaptor.getRhs());
1735 return success();
1736 }
1737};
1738
1739struct IntegerShrOpConversion
1740 : public OpConversionPattern<firrtl::IntegerShrOp> {
1741 using OpConversionPattern::OpConversionPattern;
1742
1743 LogicalResult
1744 matchAndRewrite(firrtl::IntegerShrOp op, OpAdaptor adaptor,
1745 ConversionPatternRewriter &rewriter) const override {
1746 rewriter.replaceOpWithNewOp<om::IntegerShrOp>(op, adaptor.getLhs(),
1747 adaptor.getRhs());
1748 return success();
1749 }
1750};
1751
1752struct IntegerShlOpConversion
1753 : public OpConversionPattern<firrtl::IntegerShlOp> {
1754 using OpConversionPattern::OpConversionPattern;
1755
1756 LogicalResult
1757 matchAndRewrite(firrtl::IntegerShlOp op, OpAdaptor adaptor,
1758 ConversionPatternRewriter &rewriter) const override {
1759 rewriter.replaceOpWithNewOp<om::IntegerShlOp>(op, adaptor.getLhs(),
1760 adaptor.getRhs());
1761 return success();
1762 }
1763};
1764
1765struct StringConcatOpConversion
1766 : public OpConversionPattern<firrtl::StringConcatOp> {
1767 using OpConversionPattern::OpConversionPattern;
1768
1769 LogicalResult
1770 matchAndRewrite(firrtl::StringConcatOp op, OpAdaptor adaptor,
1771 ConversionPatternRewriter &rewriter) const override {
1772 rewriter.replaceOpWithNewOp<om::StringConcatOp>(op, adaptor.getOperands());
1773 return success();
1774 }
1775};
1776
1777struct PropEqOpConversion : public OpConversionPattern<firrtl::PropEqOp> {
1778 using OpConversionPattern::OpConversionPattern;
1779
1780 LogicalResult
1781 matchAndRewrite(firrtl::PropEqOp op, OpAdaptor adaptor,
1782 ConversionPatternRewriter &rewriter) const override {
1783 rewriter.replaceOpWithNewOp<om::PropEqOp>(op, adaptor.getLhs(),
1784 adaptor.getRhs());
1785 return success();
1786 }
1787};
1788
1789struct PathOpConversion : public OpConversionPattern<firrtl::PathOp> {
1790
1791 PathOpConversion(TypeConverter &typeConverter, MLIRContext *context,
1792 const PathInfoTable &pathInfoTable,
1793 PatternBenefit benefit = 1)
1794 : OpConversionPattern(typeConverter, context, benefit),
1795 pathInfoTable(pathInfoTable) {}
1796
1797 LogicalResult
1798 matchAndRewrite(firrtl::PathOp op, OpAdaptor adaptor,
1799 ConversionPatternRewriter &rewriter) const override {
1800 auto *context = op->getContext();
1801 auto pathType = om::PathType::get(context);
1802 auto pathInfoIt = pathInfoTable.table.find(op.getTarget());
1803
1804 // The 0'th argument is the base path by default.
1805 auto basePath = op->getBlock()->getArgument(0);
1806
1807 // If the target was optimized away, then replace the path operation with
1808 // a deleted path.
1809 if (pathInfoIt == pathInfoTable.table.end()) {
1810 if (op.getTargetKind() == firrtl::TargetKind::DontTouch)
1811 return emitError(op.getLoc(), "DontTouch target was deleted");
1812 if (op.getTargetKind() == firrtl::TargetKind::Instance)
1813 return emitError(op.getLoc(), "Instance target was deleted");
1814 rewriter.replaceOpWithNewOp<om::EmptyPathOp>(op);
1815 return success();
1816 }
1817
1818 auto pathInfo = pathInfoIt->second;
1819 auto symbol = pathInfo.symRef;
1820
1821 // Convert the target kind to an OMIR target. Member references are updated
1822 // to reflect the current kind of reference.
1823 om::TargetKind targetKind;
1824 switch (op.getTargetKind()) {
1825 case firrtl::TargetKind::DontTouch:
1826 targetKind = om::TargetKind::DontTouch;
1827 break;
1828 case firrtl::TargetKind::Reference:
1829 targetKind = om::TargetKind::Reference;
1830 break;
1831 case firrtl::TargetKind::Instance:
1832 if (!pathInfo.canBeInstanceTarget)
1833 return emitError(op.getLoc(), "invalid target for instance path")
1834 .attachNote(pathInfo.loc)
1835 << "target not instance or module";
1836 targetKind = om::TargetKind::Instance;
1837 break;
1838 case firrtl::TargetKind::MemberInstance:
1839 case firrtl::TargetKind::MemberReference:
1840 if (pathInfo.canBeInstanceTarget)
1841 targetKind = om::TargetKind::MemberInstance;
1842 else
1843 targetKind = om::TargetKind::MemberReference;
1844 break;
1845 }
1846
1847 // If we are using an alternative base path for this path, get it from the
1848 // passthrough port on the enclosing class.
1849 if (auto altBasePathModule = pathInfo.altBasePathModule) {
1850 // Get the original name of the parent. At this point both FIRRTL classes
1851 // and modules have been converted to OM classes, but we need to look up
1852 // based on the parent's original name.
1853 auto parent = op->getParentOfType<om::ClassOp>();
1854 auto parentName = parent.getName();
1855 if (parentName.ends_with(kClassNameSuffix))
1856 parentName = parentName.drop_back(kClassNameSuffix.size());
1857 auto originalParentName = StringAttr::get(op->getContext(), parentName);
1858
1859 // Get the base paths passing through the parent.
1860 auto altBasePaths =
1861 pathInfoTable.getRootsForPassthrough(originalParentName);
1862 assert(!altBasePaths.empty() && "expected passthrough base paths");
1863
1864 // Find the base path passthrough that was associated with this path.
1865 for (auto [i, altBasePath] : llvm::enumerate(altBasePaths)) {
1866 if (altBasePathModule == altBasePath) {
1867 // + 1 to skip default base path
1868 auto basePathArg = op->getBlock()->getArgument(1 + i);
1869 assert(isa<om::BasePathType>(basePathArg.getType()) &&
1870 "expected a passthrough base path");
1871 basePath = basePathArg;
1872 }
1873 }
1874 }
1875
1876 rewriter.replaceOpWithNewOp<om::PathCreateOp>(
1877 op, pathType, om::TargetKindAttr::get(op.getContext(), targetKind),
1878 basePath, symbol);
1879 return success();
1880 }
1881
1882 const PathInfoTable &pathInfoTable;
1883};
1884
1885struct WireOpConversion : public OpConversionPattern<WireOp> {
1886 using OpConversionPattern::OpConversionPattern;
1887
1888 LogicalResult
1889 matchAndRewrite(WireOp wireOp, OpAdaptor adaptor,
1890 ConversionPatternRewriter &rewriter) const override {
1891 auto wireValue = dyn_cast<FIRRTLPropertyValue>(wireOp.getResult());
1892
1893 // If the wire isn't a Property, not much we can do here.
1894 if (!wireValue)
1895 return failure();
1896
1897 // If the wire isn't inside a graph region, we can't trivially remove it. In
1898 // practice, this pattern does run for wires in graph regions, so this check
1899 // should pass and we can proceed with the trivial rewrite.
1900 auto regionKindInterface = wireOp->getParentOfType<RegionKindInterface>();
1901 if (!regionKindInterface)
1902 return failure();
1903 if (regionKindInterface.getRegionKind(0) != RegionKind::Graph)
1904 return failure();
1905
1906 // Find the assignment to the wire.
1907 PropAssignOp propAssign = getPropertyAssignment(wireValue);
1908 if (!propAssign)
1909 return failure();
1910
1911 // Use the source of the assignment instead of the wire.
1912 rewriter.replaceOp(wireOp, propAssign.getSrc());
1913
1914 // Erase the source of the assignment.
1915 rewriter.eraseOp(propAssign);
1916
1917 return success();
1918 }
1919};
1920
1921struct AnyCastOpConversion : public OpConversionPattern<ObjectAnyRefCastOp> {
1922 using OpConversionPattern::OpConversionPattern;
1923
1924 LogicalResult
1925 matchAndRewrite(ObjectAnyRefCastOp op, OpAdaptor adaptor,
1926 ConversionPatternRewriter &rewriter) const override {
1927 rewriter.replaceOpWithNewOp<om::AnyCastOp>(op, adaptor.getInput());
1928 return success();
1929 }
1930};
1931
1932struct ObjectSubfieldOpConversion
1933 : public OpConversionPattern<firrtl::ObjectSubfieldOp> {
1934 using OpConversionPattern::OpConversionPattern;
1935
1936 ObjectSubfieldOpConversion(
1937 const TypeConverter &typeConverter, MLIRContext *context,
1938 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable)
1939 : OpConversionPattern(typeConverter, context),
1940 classTypeTable(classTypeTable) {}
1941
1942 LogicalResult
1943 matchAndRewrite(firrtl::ObjectSubfieldOp op, OpAdaptor adaptor,
1944 ConversionPatternRewriter &rewriter) const override {
1945 auto omClassType = dyn_cast<om::ClassType>(adaptor.getInput().getType());
1946 if (!omClassType)
1947 return failure();
1948
1949 // Convert the field-index used by the firrtl implementation, to a symbol,
1950 // as used by the om implementation.
1951 auto firrtlClassType =
1952 classTypeTable.lookup(omClassType.getClassName().getAttr());
1953 if (!firrtlClassType)
1954 return failure();
1955
1956 const auto &element = firrtlClassType.getElement(op.getIndex());
1957 // We cannot convert input ports to fields.
1958 if (element.direction == Direction::In)
1959 return failure();
1960
1961 auto field = FlatSymbolRefAttr::get(element.name);
1962 auto path = rewriter.getArrayAttr({field});
1963 auto type = typeConverter->convertType(element.type);
1964 rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(op, type, adaptor.getInput(),
1965 path);
1966 return success();
1967 }
1968
1969 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable;
1970};
1971
1972struct ClassFieldsOpConversion : public OpConversionPattern<om::ClassFieldsOp> {
1973 using OpConversionPattern::OpConversionPattern;
1974
1975 LogicalResult
1976 matchAndRewrite(om::ClassFieldsOp op, OpAdaptor adaptor,
1977 ConversionPatternRewriter &rewriter) const override {
1978 rewriter.replaceOpWithNewOp<om::ClassFieldsOp>(op, adaptor.getOperands(),
1979 adaptor.getFieldLocsAttr());
1980 return success();
1981 }
1982};
1983
1984struct ObjectOpConversion : public OpConversionPattern<om::ObjectOp> {
1985 using OpConversionPattern::OpConversionPattern;
1986
1987 LogicalResult
1988 matchAndRewrite(om::ObjectOp objectOp, OpAdaptor adaptor,
1989 ConversionPatternRewriter &rewriter) const override {
1990 // Replace the object with a new object using the converted actual parameter
1991 // types from the adaptor.
1992 rewriter.replaceOpWithNewOp<om::ObjectOp>(objectOp, objectOp.getType(),
1993 adaptor.getClassNameAttr(),
1994 adaptor.getActualParams());
1995 return success();
1996 }
1997};
1998
1999static LogicalResult convertClassLike(om::ClassLike classOp,
2000 TypeConverter typeConverter,
2001 ConversionPatternRewriter &rewriter) {
2002 Block *body = classOp.getBodyBlock();
2003 TypeConverter::SignatureConversion result(body->getNumArguments());
2004
2005 // Convert block argument types.
2006 if (failed(
2007 typeConverter.convertSignatureArgs(body->getArgumentTypes(), result)))
2008 return failure();
2009
2010 // Convert the body.
2011 if (failed(rewriter.convertRegionTypes(body->getParent(), typeConverter,
2012 &result)))
2013 return failure();
2014
2015 rewriter.modifyOpInPlace(classOp, [&]() {
2016 mlir::AttrTypeReplacer replacer;
2017 replacer.addReplacement([&](TypeAttr typeAttr) {
2018 return mlir::TypeAttr::get(
2019 typeConverter.convertType(typeAttr.getValue()));
2020 });
2021 classOp.replaceFieldTypes(replacer);
2022 });
2023
2024 return success();
2025}
2026
2027struct ClassOpSignatureConversion : public OpConversionPattern<om::ClassOp> {
2028 using OpConversionPattern::OpConversionPattern;
2029
2030 LogicalResult
2031 matchAndRewrite(om::ClassOp classOp, OpAdaptor adaptor,
2032 ConversionPatternRewriter &rewriter) const override {
2033 return convertClassLike(classOp, *typeConverter, rewriter);
2034 }
2035};
2036
2037struct ClassExternOpSignatureConversion
2038 : public OpConversionPattern<om::ClassExternOp> {
2039 using OpConversionPattern::OpConversionPattern;
2040
2041 LogicalResult
2042 matchAndRewrite(om::ClassExternOp classOp, OpAdaptor adaptor,
2043 ConversionPatternRewriter &rewriter) const override {
2044 return convertClassLike(classOp, *typeConverter, rewriter);
2045 }
2046};
2047
2048struct ObjectFieldOpConversion : public OpConversionPattern<om::ObjectFieldOp> {
2049 using OpConversionPattern::OpConversionPattern;
2050
2051 LogicalResult
2052 matchAndRewrite(om::ObjectFieldOp op, OpAdaptor adaptor,
2053 ConversionPatternRewriter &rewriter) const override {
2054 // Replace the object field with a new object field of the appropriate
2055 // result type based on the type converter.
2056 auto type = typeConverter->convertType(op.getType());
2057 if (!type)
2058 return failure();
2059
2060 rewriter.replaceOpWithNewOp<om::ObjectFieldOp>(
2061 op, type, adaptor.getObject(), adaptor.getFieldPathAttr());
2062
2063 return success();
2064 }
2065};
2066
2067/// Replace OM-to-FIRRTL casts with the OM value.
2068struct UnrealizedConversionCastOpConversion
2069 : public OpConversionPattern<UnrealizedConversionCastOp> {
2070 using OpConversionPattern::OpConversionPattern;
2071
2072 LogicalResult
2073 matchAndRewrite(UnrealizedConversionCastOp op, OpAdaptor adaptor,
2074 ConversionPatternRewriter &rewriter) const override {
2075 if (op.getNumOperands() != 1 || op.getNumResults() != 1)
2076 return failure();
2077 auto type = typeConverter->convertType(op.getResult(0));
2078 if (!type || type != adaptor.getOperands()[0].getType())
2079 return failure();
2080 rewriter.replaceOp(op, adaptor.getOperands()[0]);
2081 return success();
2082 }
2083};
2084
2085struct UnknownValueOpConversion : public OpConversionPattern<UnknownValueOp> {
2086 using OpConversionPattern::OpConversionPattern;
2087
2088 LogicalResult
2089 matchAndRewrite(UnknownValueOp op, OpAdaptor adaptor,
2090 ConversionPatternRewriter &rewriter) const override {
2091 auto convertedType = typeConverter->convertType(op.getType());
2092 if (!convertedType)
2093 return failure();
2094 rewriter.replaceOpWithNewOp<om::UnknownValueOp>(op, convertedType);
2095 return success();
2096 }
2097};
2098
2099} // namespace
2100
2101//===----------------------------------------------------------------------===//
2102// Conversion Setup
2103//===----------------------------------------------------------------------===//
2104
2105static void populateConversionTarget(ConversionTarget &target) {
2106 // FIRRTL dialect operations inside ClassOps or not using only OM types must
2107 // be legalized.
2108 target.addDynamicallyLegalDialect<FIRRTLDialect>(
2109 [](Operation *op) { return !op->getParentOfType<om::ClassLike>(); });
2110
2111 // OM dialect operations are legal if they don't use FIRRTL types.
2112 target.addDynamicallyLegalDialect<om::OMDialect>([](Operation *op) {
2113 auto containsFIRRTLType = [](Type type) {
2114 return type
2115 .walk([](Type type) {
2116 return failure(isa<FIRRTLDialect>(type.getDialect()));
2117 })
2118 .wasInterrupted();
2119 };
2120 auto noFIRRTLOperands =
2121 llvm::none_of(op->getOperandTypes(), [&containsFIRRTLType](Type type) {
2122 return containsFIRRTLType(type);
2123 });
2124 auto noFIRRTLResults =
2125 llvm::none_of(op->getResultTypes(), [&containsFIRRTLType](Type type) {
2126 return containsFIRRTLType(type);
2127 });
2128 return noFIRRTLOperands && noFIRRTLResults;
2129 });
2130
2131 // OM Class ops are legal if they don't use FIRRTL types for block arguments.
2132 target.addDynamicallyLegalOp<om::ClassOp, om::ClassExternOp>(
2133 [](Operation *op) -> std::optional<bool> {
2134 auto classLike = dyn_cast<om::ClassLike>(op);
2135 if (!classLike)
2136 return std::nullopt;
2137 auto fieldNames = classLike.getFieldNames();
2138 if (!llvm::all_of(fieldNames, [&](auto field) {
2139 std::optional<Type> type =
2140 classLike.getFieldType(cast<StringAttr>(field));
2141 return type.has_value() && !isa<FIRRTLType>(type.value());
2142 }))
2143 return false;
2144
2145 return llvm::none_of(
2146 classLike.getBodyBlock()->getArgumentTypes(),
2147 [](Type type) { return isa<FIRRTLDialect>(type.getDialect()); });
2148 });
2149}
2150
2151static void populateTypeConverter(TypeConverter &converter) {
2152 // Convert FIntegerType to IntegerType.
2153 converter.addConversion([](IntegerType type) {
2154 return om::OMIntegerType::get(type.getContext());
2155 });
2156 converter.addConversion([](FIntegerType type) {
2157 // The actual width of the IntegerType doesn't actually get used; it will be
2158 // folded away by the dialect conversion infrastructure to the type of the
2159 // APSIntAttr used in the FIntegerConstantOp.
2160 return om::OMIntegerType::get(type.getContext());
2161 });
2162
2163 // Convert FIRRTL StringType to OM StringType.
2164 converter.addConversion([](om::StringType type) { return type; });
2165 converter.addConversion([](firrtl::StringType type) {
2166 return om::StringType::get(type.getContext());
2167 });
2168
2169 // Convert FIRRTL PathType to OM PathType.
2170 converter.addConversion([](om::PathType type) { return type; });
2171 converter.addConversion([](om::BasePathType type) { return type; });
2172 converter.addConversion([](om::FrozenPathType type) { return type; });
2173 converter.addConversion([](om::FrozenBasePathType type) { return type; });
2174 converter.addConversion([](firrtl::PathType type) {
2175 return om::PathType::get(type.getContext());
2176 });
2177
2178 // Convert FIRRTL Class type to OM Class type.
2179 converter.addConversion([](om::ClassType type) { return type; });
2180 converter.addConversion([](firrtl::ClassType type) {
2181 return om::ClassType::get(type.getContext(), type.getNameAttr());
2182 });
2183
2184 // Convert FIRRTL AnyRef type to OM Any type.
2185 converter.addConversion([](om::AnyType type) { return type; });
2186 converter.addConversion([](firrtl::AnyRefType type) {
2187 return om::AnyType::get(type.getContext());
2188 });
2189
2190 // Convert FIRRTL List type to OM List type.
2191 auto convertListType = [&converter](auto type) -> std::optional<mlir::Type> {
2192 // If the element type is already in the OM dialect, there's nothing to do.
2193 if (isa<om::OMDialect>(type.getElementType().getDialect()))
2194 return type;
2195 auto elementType = converter.convertType(type.getElementType());
2196 if (!elementType)
2197 return {};
2198 return om::ListType::get(elementType);
2199 };
2200
2201 converter.addConversion(
2202 [convertListType](om::ListType type) -> std::optional<mlir::Type> {
2203 // Convert any om.list<firrtl> -> om.list<om>
2204 return convertListType(type);
2205 });
2206
2207 converter.addConversion(
2208 [convertListType](firrtl::ListType type) -> std::optional<mlir::Type> {
2209 // Convert any firrtl.list<firrtl> -> om.list<om>
2210 return convertListType(type);
2211 });
2212
2213 // Convert FIRRTL Bool type to OM
2214 converter.addConversion(
2215 [](BoolType type) { return IntegerType::get(type.getContext(), 1); });
2216
2217 // Convert FIRRTL double type to OM.
2218 converter.addConversion(
2219 [](DoubleType type) { return Float64Type::get(type.getContext()); });
2220
2221 // Add a target materialization such that the conversion does not fail when a
2222 // type conversion could not be reconciled automatically by the framework.
2223 converter.addTargetMaterialization(
2224 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2225 assert(values.size() == 1);
2226 return UnrealizedConversionCastOp::create(builder, loc, type, values[0])
2227 ->getResult(0);
2228 });
2229
2230 // Add a source materialization such that the conversion does not fail when a
2231 // type conversion could not be reconciled automatically by the framework.
2232 converter.addSourceMaterialization(
2233 [](OpBuilder &builder, Type type, ValueRange values, Location loc) {
2234 assert(values.size() == 1);
2235 return UnrealizedConversionCastOp::create(builder, loc, type, values[0])
2236 ->getResult(0);
2237 });
2238}
2239
2241 RewritePatternSet &patterns, TypeConverter &converter,
2242 const PathInfoTable &pathInfoTable,
2243 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2244 patterns.add<FIntegerConstantOpConversion>(converter, patterns.getContext());
2245 patterns.add<StringConstantOpConversion>(converter, patterns.getContext());
2246 patterns.add<PathOpConversion>(converter, patterns.getContext(),
2247 pathInfoTable);
2248 patterns.add<WireOpConversion>(converter, patterns.getContext());
2249 patterns.add<AnyCastOpConversion>(converter, patterns.getContext());
2250 patterns.add<ObjectSubfieldOpConversion>(converter, patterns.getContext(),
2251 classTypeTable);
2252 patterns.add<ClassFieldsOpConversion>(converter, patterns.getContext());
2253 patterns.add<ClassOpSignatureConversion>(converter, patterns.getContext());
2254 patterns.add<ClassExternOpSignatureConversion>(converter,
2255 patterns.getContext());
2256 patterns.add<ObjectOpConversion>(converter, patterns.getContext());
2257 patterns.add<ObjectFieldOpConversion>(converter, patterns.getContext());
2258 patterns.add<ListCreateOpConversion>(converter, patterns.getContext());
2259 patterns.add<ListConcatOpConversion>(converter, patterns.getContext());
2260 patterns.add<BoolConstantOpConversion>(converter, patterns.getContext());
2261 patterns.add<PropertyAssertOpConversion>(converter, patterns.getContext());
2262 patterns.add<DoubleConstantOpConversion>(converter, patterns.getContext());
2263 patterns.add<IntegerAddOpConversion>(converter, patterns.getContext());
2264 patterns.add<IntegerMulOpConversion>(converter, patterns.getContext());
2265 patterns.add<IntegerShrOpConversion>(converter, patterns.getContext());
2266 patterns.add<IntegerShlOpConversion>(converter, patterns.getContext());
2267 patterns.add<StringConcatOpConversion>(converter, patterns.getContext());
2268 patterns.add<PropEqOpConversion>(converter, patterns.getContext());
2269 patterns.add<UnrealizedConversionCastOpConversion>(converter,
2270 patterns.getContext());
2271 patterns.add<UnknownValueOpConversion>(converter, patterns.getContext());
2272}
2273
2274// Convert to OM ops and types in Classes or Modules.
2275LogicalResult LowerClassesPass::dialectConversion(
2276 Operation *op, const PathInfoTable &pathInfoTable,
2277 const DenseMap<StringAttr, firrtl::ClassType> &classTypeTable) {
2278 ConversionTarget target(getContext());
2280
2281 TypeConverter typeConverter;
2282 populateTypeConverter(typeConverter);
2283
2284 RewritePatternSet patterns(&getContext());
2285 populateRewritePatterns(patterns, typeConverter, pathInfoTable,
2286 classTypeTable);
2287
2288 return applyPartialConversion(op, target, std::move(patterns));
2289}
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:56
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:879
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.