CIRCT 20.0.0git
Loading...
Searching...
No Matches
LowerXMR.cpp
Go to the documentation of this file.
1//===- LowerXMR.cpp - FIRRTL Lower to XMR -----------------------*- C++ -*-===//
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 implements FIRRTL XMR Lowering.
10//
11//===----------------------------------------------------------------------===//
12
21#include "mlir/IR/ImplicitLocOpBuilder.h"
22#include "mlir/Pass/Pass.h"
23#include "llvm/ADT/BitVector.h"
24#include "llvm/ADT/DenseMap.h"
25#include "llvm/ADT/EquivalenceClasses.h"
26#include "llvm/ADT/PostOrderIterator.h"
27#include "llvm/Support/Debug.h"
28
29#define DEBUG_TYPE "firrtl-lower-xmr"
30
31namespace circt {
32namespace firrtl {
33#define GEN_PASS_DEF_LOWERXMR
34#include "circt/Dialect/FIRRTL/Passes.h.inc"
35} // namespace firrtl
36} // namespace circt
37
38using namespace circt;
39using namespace firrtl;
40using hw::InnerRefAttr;
41
42/// The LowerXMRPass will replace every RefResolveOp with an XMR encoded within
43/// a verbatim expr op. This also removes every RefType port from the modules
44/// and corresponding instances. This is a dataflow analysis over a very
45/// constrained RefType. Domain of the dataflow analysis is the set of all
46/// RefSendOps. It computes an interprocedural reaching definitions (of
47/// RefSendOp) analysis. Essentially every RefType value must be mapped to one
48/// and only one RefSendOp. The analysis propagates the dataflow from every
49/// RefSendOp to every value of RefType across modules. The RefResolveOp is the
50/// final leaf into which the dataflow must reach.
51///
52/// Since there can be multiple readers, multiple RefResolveOps can be reachable
53/// from a single RefSendOp. To support multiply instantiated modules and
54/// multiple readers, it is essential to track the path to the RefSendOp, other
55/// than just the RefSendOp. For example, if there exists a wire `xmr_wire` in
56/// module `Foo`, the algorithm needs to support generating Top.Bar.Foo.xmr_wire
57/// and Top.Foo.xmr_wire and Top.Zoo.Foo.xmr_wire for different instance paths
58/// that exist in the circuit.
59
60namespace {
61struct XMRNode {
62 using NextNodeOnPath = std::optional<size_t>;
63 using SymOrIndexOp = PointerUnion<Attribute, Operation *>;
64 SymOrIndexOp info;
65 NextNodeOnPath next;
66};
67[[maybe_unused]] llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
68 const XMRNode &node) {
69 os << "node(";
70 if (auto attr = dyn_cast<Attribute>(node.info))
71 os << "path=" << attr;
72 else {
73 auto subOp = cast<RefSubOp>(cast<Operation *>(node.info));
74 os << "index=" << subOp.getIndex() << " (-> " << subOp.getType() << ")";
75 }
76 os << ", next=" << node.next << ")";
77 return os;
78}
79
80/// Track information about operations being created in a module. This is used
81/// to generate more compact code and reuse operations where possible.
82class ModuleState {
83
84public:
85 ModuleState(FModuleOp &moduleOp) : body(moduleOp.getBodyBlock()) {}
86
87 /// Return the existing XMRRefOp for this type, symbol, and suffix for this
88 /// module. Otherwise, create a new one. The first XMRRefOp will be created
89 /// at the beginning of the module. Subsequent XMRRefOps will be created
90 /// immediately following the first one.
91 Value getOrCreateXMRRefOp(Type type, FlatSymbolRefAttr symbol,
92 StringAttr suffix, ImplicitLocOpBuilder &builder) {
93 // Return the saved XMRRefOp.
94 auto it = xmrRefCache.find({type, symbol, suffix});
95 if (it != xmrRefCache.end())
96 return it->getSecond();
97
98 // Create a new XMRRefOp.
99 OpBuilder::InsertionGuard guard(builder);
100 if (xmrRefPoint.isSet())
101 builder.restoreInsertionPoint(xmrRefPoint);
102 else
103 builder.setInsertionPointToStart(body);
104
105 Value xmr = builder.create<XMRRefOp>(type, symbol, suffix);
106 xmrRefCache.insert({{type, symbol, suffix}, xmr});
107
108 xmrRefPoint = builder.saveInsertionPoint();
109 return xmr;
110 };
111
112private:
113 /// The module's body. This is used to set the insertion point for the first
114 /// created operation.
115 Block *body;
116
117 /// Map used to know if we created this XMRRefOp before.
118 DenseMap<std::tuple<Type, SymbolRefAttr, StringAttr>, Value> xmrRefCache;
119
120 /// The saved insertion point for XMRRefOps.
121 OpBuilder::InsertPoint xmrRefPoint;
122};
123} // end anonymous namespace
124
126
127 void runOnOperation() override {
128 // Populate a CircuitNamespace that can be used to generate unique
129 // circuit-level symbols.
130 CircuitNamespace ns(getOperation());
131 circuitNamespace = &ns;
132
133 llvm::EquivalenceClasses<Value, ValueComparator> eq;
134 dataFlowClasses = &eq;
135
136 InstanceGraph &instanceGraph = getAnalysis<InstanceGraph>();
137 SmallVector<RefResolveOp> resolveOps;
138 SmallVector<RefSubOp> indexingOps;
139 SmallVector<Operation *> forceAndReleaseOps;
140 // The dataflow function, that propagates the reachable RefSendOp across
141 // RefType Ops.
142 auto transferFunc = [&](Operation *op) -> LogicalResult {
143 return TypeSwitch<Operation *, LogicalResult>(op)
144 .Case<RefSendOp>([&](RefSendOp send) {
145 // Get a reference to the actual signal to which the XMR will be
146 // generated.
147 Value xmrDef = send.getBase();
148 if (isZeroWidth(send.getType().getType())) {
149 markForRemoval(send);
150 return success();
151 }
152
153 if (auto verbExpr = xmrDef.getDefiningOp<VerbatimExprOp>())
154 if (verbExpr.getSymbolsAttr().empty() && verbExpr->hasOneUse()) {
155 // This represents the internal path into a module. For
156 // generating the correct XMR, no node can be created in this
157 // module. Create a null InnerRef and ensure the hierarchical
158 // path ends at the parent that instantiates this module.
159 auto inRef = InnerRefAttr();
160 auto ind = addReachingSendsEntry(send.getResult(), inRef);
161 xmrPathSuffix[ind] = verbExpr.getText();
162 markForRemoval(verbExpr);
163 markForRemoval(send);
164 return success();
165 }
166 // Get an InnerRefAttr to the value being sent.
167
168 // Add a node, don't need to have symbol on defining operation,
169 // just a way to send out the value.
170 ImplicitLocOpBuilder b(xmrDef.getLoc(), &getContext());
171 b.setInsertionPointAfterValue(xmrDef);
172 SmallString<32> opName;
173 auto nameKind = NameKindEnum::DroppableName;
174
175 if (auto [name, rootKnown] = getFieldName(
176 getFieldRefFromValue(xmrDef, /*lookThroughCasts=*/true),
177 /*nameSafe=*/true);
178 rootKnown) {
179 opName = name + "_probe";
180 nameKind = NameKindEnum::InterestingName;
181 } else if (auto *xmrDefOp = xmrDef.getDefiningOp()) {
182 // Inspect "name" directly for ops that aren't named by above.
183 // (e.g., firrtl.constant)
184 if (auto name = xmrDefOp->getAttrOfType<StringAttr>("name")) {
185 (Twine(name.strref()) + "_probe").toVector(opName);
186 nameKind = NameKindEnum::InterestingName;
187 }
188 }
189 xmrDef = b.create<NodeOp>(xmrDef, opName, nameKind).getResult();
190
191 // Create a new entry for this RefSendOp. The path is currently
192 // local.
193 addReachingSendsEntry(send.getResult(), getInnerRefTo(xmrDef));
194 markForRemoval(send);
195 return success();
196 })
197 .Case<RWProbeOp>([&](RWProbeOp rwprobe) {
198 if (!isZeroWidth(rwprobe.getType().getType()))
199 addReachingSendsEntry(rwprobe.getResult(), rwprobe.getTarget());
200 markForRemoval(rwprobe);
201 return success();
202 })
203 .Case<MemOp>([&](MemOp mem) {
204 // MemOp can produce debug ports of RefType. Each debug port
205 // represents the RefType for the corresponding register of the
206 // memory. Since the memory is not yet generated the register name
207 // is assumed to be "Memory". Note that MemOp creates RefType
208 // without a RefSend.
209 for (const auto &res : llvm::enumerate(mem.getResults()))
210 if (isa<RefType>(mem.getResult(res.index()).getType())) {
211 auto inRef = getInnerRefTo(mem);
212 auto ind = addReachingSendsEntry(res.value(), inRef);
213 xmrPathSuffix[ind] = "Memory";
214 // Just node that all the debug ports of memory must be removed.
215 // So this does not record the port index.
216 refPortsToRemoveMap[mem].resize(1);
217 }
218 return success();
219 })
220 .Case<InstanceOp>(
221 [&](auto inst) { return handleInstanceOp(inst, instanceGraph); })
222 .Case<FConnectLike>([&](FConnectLike connect) {
223 // Ignore BaseType.
224 if (!isa<RefType>(connect.getSrc().getType()))
225 return success();
226 markForRemoval(connect);
227 if (isZeroWidth(
228 type_cast<RefType>(connect.getSrc().getType()).getType()))
229 return success();
230 // Merge the dataflow classes of destination into the source of the
231 // Connect. This handles two cases:
232 // 1. If the dataflow at the source is known, then the
233 // destination is also inferred. By merging the dataflow class of
234 // destination with source, every value reachable from the
235 // destination automatically infers a reaching RefSend.
236 // 2. If dataflow at source is unkown, then just record that both
237 // source and destination will have the same dataflow information.
238 // Later in the pass when the reaching RefSend is inferred at the
239 // leader of the dataflowClass, then we automatically infer the
240 // dataflow at this connect and every value reachable from the
241 // destination.
242 dataFlowClasses->unionSets(connect.getSrc(), connect.getDest());
243 return success();
244 })
245 .Case<RefSubOp>([&](RefSubOp op) -> LogicalResult {
246 markForRemoval(op);
247 if (isZeroWidth(op.getType().getType()))
248 return success();
249
250 // Enqueue for processing after visiting other operations.
251 indexingOps.push_back(op);
252 return success();
253 })
254 .Case<RefResolveOp>([&](RefResolveOp resolve) {
255 // Merge dataflow, under the same conditions as above for Connect.
256 // 1. If dataflow at the resolve.getRef is known, propagate that to
257 // the result. This is true for downward scoped XMRs, that is,
258 // RefSendOp must be visited before the corresponding RefResolveOp
259 // is visited.
260 // 2. Else, just record that both result and ref should have the
261 // same reaching RefSend. This condition is true for upward scoped
262 // XMRs. That is, RefResolveOp can be visited before the
263 // corresponding RefSendOp is recorded.
264
265 markForRemoval(resolve);
266 if (!isZeroWidth(resolve.getType()))
267 dataFlowClasses->unionSets(resolve.getRef(), resolve.getResult());
268 resolveOps.push_back(resolve);
269 return success();
270 })
271 .Case<RefCastOp>([&](RefCastOp op) {
272 markForRemoval(op);
273 if (!isZeroWidth(op.getType().getType()))
274 dataFlowClasses->unionSets(op.getInput(), op.getResult());
275 return success();
276 })
277 .Case<Forceable>([&](Forceable op) {
278 // Handle declarations containing refs as "data".
279 if (type_isa<RefType>(op.getDataRaw().getType())) {
280 markForRemoval(op);
281 return success();
282 }
283
284 // Otherwise, if forceable track the rwprobe result.
285 if (!op.isForceable() || op.getDataRef().use_empty() ||
286 isZeroWidth(op.getDataType()))
287 return success();
288
289 addReachingSendsEntry(op.getDataRef(), getInnerRefTo(op));
290 return success();
291 })
292 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp,
293 RefReleaseInitialOp>([&](auto op) {
294 forceAndReleaseOps.push_back(op);
295 return success();
296 })
297 .Default([&](auto) { return success(); });
298 };
299
300 SmallVector<FModuleOp> publicModules;
301
302 // Traverse the modules in post order.
303 for (auto node : llvm::post_order(&instanceGraph)) {
304 auto module = dyn_cast<FModuleOp>(*node->getModule());
305 if (!module)
306 continue;
307 LLVM_DEBUG(llvm::dbgs()
308 << "Traversing module:" << module.getModuleNameAttr() << "\n");
309
310 moduleStates.insert({module, ModuleState(module)});
311
312 if (module.isPublic())
313 publicModules.push_back(module);
314
315 auto result = module.walk([&](Operation *op) {
316 if (transferFunc(op).failed())
317 return WalkResult::interrupt();
318 return WalkResult::advance();
319 });
320
321 if (result.wasInterrupted())
322 return signalPassFailure();
323
324 // Clear any enabled layers.
325 module.setLayersAttr(ArrayAttr::get(module.getContext(), {}));
326
327 // Since we walk operations pre-order and not along dataflow edges,
328 // ref.sub may not be resolvable when we encounter them (they're not just
329 // unification). This can happen when refs go through an output port or
330 // input instance result and back into the design. Handle these by walking
331 // them, resolving what we can, until all are handled or nothing can be
332 // resolved.
333 while (!indexingOps.empty()) {
334 // Grab the set of unresolved ref.sub's.
335 decltype(indexingOps) worklist;
336 worklist.swap(indexingOps);
337
338 for (auto op : worklist) {
339 auto inputEntry =
340 getRemoteRefSend(op.getInput(), /*errorIfNotFound=*/false);
341 // If we can't resolve, add back and move on.
342 if (!inputEntry)
343 indexingOps.push_back(op);
344 else
345 addReachingSendsEntry(op.getResult(), op.getOperation(),
346 inputEntry);
347 }
348 // If nothing was resolved, give up.
349 if (worklist.size() == indexingOps.size()) {
350 auto op = worklist.front();
351 getRemoteRefSend(op.getInput());
352 op.emitError(
353 "indexing through probe of unknown origin (input probe?)")
354 .attachNote(op.getInput().getLoc())
355 .append("indexing through this reference");
356 return signalPassFailure();
357 }
358 }
359
360 // Record all the RefType ports to be removed later.
361 size_t numPorts = module.getNumPorts();
362 for (size_t portNum = 0; portNum < numPorts; ++portNum)
363 if (isa<RefType>(module.getPortType(portNum))) {
364 setPortToRemove(module, portNum, numPorts);
365 }
366 }
367
368 LLVM_DEBUG({
369 for (auto I = dataFlowClasses->begin(), E = dataFlowClasses->end();
370 I != E; ++I) { // Iterate over all of the equivalence sets.
371 if (!I->isLeader())
372 continue; // Ignore non-leader sets.
373 // Print members in this set.
374 llvm::interleave(llvm::make_range(dataFlowClasses->member_begin(I),
375 dataFlowClasses->member_end()),
376 llvm::dbgs(), "\n");
377 llvm::dbgs() << "\n dataflow at leader::" << I->getData() << "\n =>";
378 auto iter = dataflowAt.find(I->getData());
379 if (iter != dataflowAt.end()) {
380 for (auto init = refSendPathList[iter->getSecond()]; init.next;
381 init = refSendPathList[*init.next])
382 llvm::dbgs() << "\n " << init;
383 }
384 llvm::dbgs() << "\n Done\n"; // Finish set.
385 }
386 });
387 for (auto refResolve : resolveOps)
388 if (handleRefResolve(refResolve).failed())
389 return signalPassFailure();
390 for (auto *op : forceAndReleaseOps)
391 if (failed(handleForceReleaseOp(op)))
392 return signalPassFailure();
393 for (auto module : publicModules) {
394 if (failed(handlePublicModuleRefPorts(module)))
395 return signalPassFailure();
396 }
398
399 // Clean up
400 moduleNamespaces.clear();
401 visitedModules.clear();
402 dataflowAt.clear();
403 refSendPathList.clear();
404 dataFlowClasses = nullptr;
405 refPortsToRemoveMap.clear();
406 opsToRemove.clear();
407 xmrPathSuffix.clear();
408 circuitNamespace = nullptr;
410 pathInsertPoint = {};
411 }
412
413 /// Generate the ABI ref_<module> prefix string into `prefix`.
414 void getRefABIPrefix(FModuleLike mod, SmallVectorImpl<char> &prefix) {
415 auto modName = mod.getModuleName();
416 if (auto ext = dyn_cast<FExtModuleOp>(*mod)) {
417 // Use defName for module portion, if set.
418 if (auto defname = ext.getDefname(); defname && !defname->empty())
419 modName = *defname;
420 }
421 (Twine("ref_") + modName).toVector(prefix);
422 }
423
424 /// Get full macro name as StringAttr for the specified ref port.
425 /// Uses existing 'prefix', optionally preprends the backtick character.
426 StringAttr getRefABIMacroForPort(FModuleLike mod, size_t portIndex,
427 const Twine &prefix, bool backTick = false) {
428 return StringAttr::get(&getContext(), Twine(backTick ? "`" : "") + prefix +
429 "_" + mod.getPortName(portIndex));
430 }
431
432 LogicalResult resolveReferencePath(mlir::TypedValue<RefType> refVal,
433 ImplicitLocOpBuilder builder,
434 mlir::FlatSymbolRefAttr &ref,
435 SmallString<128> &stringLeaf) {
436 assert(stringLeaf.empty());
437
438 auto remoteOpPath = getRemoteRefSend(refVal);
439 if (!remoteOpPath)
440 return failure();
441 SmallVector<Attribute> refSendPath;
442 SmallVector<RefSubOp> indexing;
443 size_t lastIndex;
444 while (remoteOpPath) {
445 lastIndex = *remoteOpPath;
446 auto entr = refSendPathList[*remoteOpPath];
447 if (entr.info)
448 TypeSwitch<XMRNode::SymOrIndexOp>(entr.info)
449 .Case<Attribute>([&](auto attr) {
450 // If the path is a singular verbatim expression, the attribute of
451 // the send path list entry will be null.
452 if (attr)
453 refSendPath.push_back(attr);
454 })
455 .Case<Operation *>(
456 [&](auto *op) { indexing.push_back(cast<RefSubOp>(op)); });
457 remoteOpPath = entr.next;
458 }
459 auto iter = xmrPathSuffix.find(lastIndex);
460
461 // If this xmr has a suffix string (internal path into a module, that is not
462 // yet generated).
463 if (iter != xmrPathSuffix.end()) {
464 if (!refSendPath.empty())
465 stringLeaf.append(".");
466 stringLeaf.append(iter->getSecond());
467 }
468
469 assert(!(refSendPath.empty() && stringLeaf.empty()) &&
470 "nothing to index through");
471
472 // All indexing done as the ref is plumbed around indexes through
473 // the target/referent, not the current point of the path which
474 // describes how to access the referent we're indexing through.
475 // Above we gathered all indexing operations, so now append them
476 // to the path (after any relevant `xmrPathSuffix`) to reach
477 // the target element.
478 // Generating these strings here (especially if ref is sent
479 // out from a different design) is fragile but should get this
480 // working well enough while sorting out how to do this better.
481 // Some discussion of this can be found here:
482 // https://github.com/llvm/circt/pull/5551#discussion_r1258908834
483 for (auto subOp : llvm::reverse(indexing)) {
484 TypeSwitch<FIRRTLBaseType>(subOp.getInput().getType().getType())
485 .Case<FVectorType, OpenVectorType>([&](auto vecType) {
486 (Twine("[") + Twine(subOp.getIndex()) + "]").toVector(stringLeaf);
487 })
488 .Case<BundleType, OpenBundleType>([&](auto bundleType) {
489 auto fieldName = bundleType.getElementName(subOp.getIndex());
490 stringLeaf.append({".", fieldName});
491 });
492 }
493
494 if (!refSendPath.empty())
495 // Compute the HierPathOp that stores the path.
496 ref = FlatSymbolRefAttr::get(
497 getOrCreatePath(builder.getArrayAttr(refSendPath), builder)
498 .getSymNameAttr());
499
500 return success();
501 }
502
503 LogicalResult resolveReference(mlir::TypedValue<RefType> refVal,
504 ImplicitLocOpBuilder &builder,
505 FlatSymbolRefAttr &ref, StringAttr &xmrAttr) {
506 auto remoteOpPath = getRemoteRefSend(refVal);
507 if (!remoteOpPath)
508 return failure();
509
510 SmallString<128> xmrString;
511 if (failed(resolveReferencePath(refVal, builder, ref, xmrString)))
512 return failure();
513 xmrAttr =
514 xmrString.empty() ? StringAttr{} : builder.getStringAttr(xmrString);
515
516 return success();
517 }
518
519 // Replace the Force/Release's ref argument with a resolved XMRRef.
520 LogicalResult handleForceReleaseOp(Operation *op) {
521 return TypeSwitch<Operation *, LogicalResult>(op)
522 .Case<RefForceOp, RefForceInitialOp, RefReleaseOp, RefReleaseInitialOp>(
523 [&](auto op) {
524 // Drop if zero-width target.
525 auto destType = op.getDest().getType();
526 if (isZeroWidth(destType.getType())) {
527 op.erase();
528 return success();
529 }
530
531 ImplicitLocOpBuilder builder(op.getLoc(), op);
532 FlatSymbolRefAttr ref;
533 StringAttr str;
534 if (failed(resolveReference(op.getDest(), builder, ref, str)))
535 return failure();
536
537 Value xmr =
538 moduleStates.find(op->template getParentOfType<FModuleOp>())
539 ->getSecond()
540 .getOrCreateXMRRefOp(destType, ref, str, builder);
541 op.getDestMutable().assign(xmr);
542 return success();
543 })
544 .Default([](auto *op) {
545 return op->emitError("unexpected operation kind");
546 });
547 }
548
549 // Replace the RefResolveOp with verbatim op representing the XMR.
550 LogicalResult handleRefResolve(RefResolveOp resolve) {
551 auto resWidth = getBitWidth(resolve.getType());
552 if (resWidth.has_value() && *resWidth == 0) {
553 // Donot emit 0 width XMRs, replace it with constant 0.
554 ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
555 auto zeroUintType = UIntType::get(builder.getContext(), 0);
556 auto zeroC = builder.createOrFold<BitCastOp>(
557 resolve.getType(), builder.create<ConstantOp>(
558 zeroUintType, getIntZerosAttr(zeroUintType)));
559 resolve.getResult().replaceAllUsesWith(zeroC);
560 return success();
561 }
562
563 FlatSymbolRefAttr ref;
564 StringAttr str;
565 ImplicitLocOpBuilder builder(resolve.getLoc(), resolve);
566 if (failed(resolveReference(resolve.getRef(), builder, ref, str)))
567 return failure();
568
569 Value result = builder.create<XMRDerefOp>(resolve.getType(), ref, str);
570 resolve.getResult().replaceAllUsesWith(result);
571 return success();
572 }
573
574 void setPortToRemove(Operation *op, size_t index, size_t numPorts) {
575 if (refPortsToRemoveMap[op].size() < numPorts)
576 refPortsToRemoveMap[op].resize(numPorts);
577 refPortsToRemoveMap[op].set(index);
578 }
579
580 // Propagate the reachable RefSendOp across modules.
581 LogicalResult handleInstanceOp(InstanceOp inst,
582 InstanceGraph &instanceGraph) {
583 Operation *mod = inst.getReferencedModule(instanceGraph);
584 if (auto extRefMod = dyn_cast<FExtModuleOp>(mod)) {
585 // Extern modules can generate RefType ports, they have an attached
586 // attribute which specifies the internal path into the extern module.
587 // This string attribute will be used to generate the final xmr.
588 auto internalPaths = extRefMod.getInternalPaths();
589 auto numPorts = inst.getNumResults();
590 SmallString<128> circuitRefPrefix;
591
592 /// Get the resolution string for this ref-type port.
593 auto getPath = [&](size_t portNo) {
594 // If there's an internal path specified (with path), use that.
595 if (internalPaths)
596 if (auto path =
597 cast<InternalPathAttr>(internalPaths->getValue()[portNo])
598 .getPath())
599 return path;
600
601 // Otherwise, we're using the ref ABI. Generate the prefix string
602 // and return the macro for the specified port.
603 if (circuitRefPrefix.empty())
604 getRefABIPrefix(extRefMod, circuitRefPrefix);
605
606 return getRefABIMacroForPort(extRefMod, portNo, circuitRefPrefix, true);
607 };
608
609 for (const auto &res : llvm::enumerate(inst.getResults())) {
610 if (!isa<RefType>(inst.getResult(res.index()).getType()))
611 continue;
612
613 auto inRef = getInnerRefTo(inst);
614 auto ind = addReachingSendsEntry(res.value(), inRef);
615
616 xmrPathSuffix[ind] = getPath(res.index());
617 // The instance result and module port must be marked for removal.
618 setPortToRemove(inst, res.index(), numPorts);
619 setPortToRemove(extRefMod, res.index(), numPorts);
620 }
621 return success();
622 }
623 auto refMod = dyn_cast<FModuleOp>(mod);
624 bool multiplyInstantiated = !visitedModules.insert(refMod).second;
625 for (size_t portNum = 0, numPorts = inst.getNumResults();
626 portNum < numPorts; ++portNum) {
627 auto instanceResult = inst.getResult(portNum);
628 if (!isa<RefType>(instanceResult.getType()))
629 continue;
630 if (!refMod)
631 return inst.emitOpError("cannot lower ext modules with RefType ports");
632 // Reference ports must be removed.
633 setPortToRemove(inst, portNum, numPorts);
634 // Drop the dead-instance-ports.
635 if (instanceResult.use_empty() ||
636 isZeroWidth(type_cast<RefType>(instanceResult.getType()).getType()))
637 continue;
638 auto refModuleArg = refMod.getArgument(portNum);
639 if (inst.getPortDirection(portNum) == Direction::Out) {
640 // For output instance ports, the dataflow is into this module.
641 // Get the remote RefSendOp, that flows through the module ports.
642 // If dataflow at remote module argument does not exist, error out.
643 auto remoteOpPath = getRemoteRefSend(refModuleArg);
644 if (!remoteOpPath)
645 return failure();
646 // Get the path to reaching refSend at the referenced module argument.
647 // Now append this instance to the path to the reaching refSend.
648 addReachingSendsEntry(instanceResult, getInnerRefTo(inst),
649 remoteOpPath);
650 } else {
651 // For input instance ports, the dataflow is into the referenced module.
652 // Input RefType port implies, generating an upward scoped XMR.
653 // No need to add the instance context, since downward reference must be
654 // through single instantiated modules.
655 if (multiplyInstantiated)
656 return refMod.emitOpError(
657 "multiply instantiated module with input RefType port '")
658 << refMod.getPortName(portNum) << "'";
659 dataFlowClasses->unionSets(
660 dataFlowClasses->getOrInsertLeaderValue(refModuleArg),
661 dataFlowClasses->getOrInsertLeaderValue(instanceResult));
662 }
663 }
664 return success();
665 }
666
667 LogicalResult handlePublicModuleRefPorts(FModuleOp module) {
668 auto *body = getOperation().getBodyBlock();
669
670 // Find all the output reference ports.
671 SmallString<128> circuitRefPrefix;
672 SmallVector<std::tuple<StringAttr, StringAttr, ArrayAttr>> ports;
673 auto declBuilder =
674 ImplicitLocOpBuilder::atBlockBegin(module.getLoc(), body);
675 for (size_t portIndex = 0, numPorts = module.getNumPorts();
676 portIndex != numPorts; ++portIndex) {
677 auto refType = type_dyn_cast<RefType>(module.getPortType(portIndex));
678 if (!refType || isZeroWidth(refType.getType()) ||
679 module.getPortDirection(portIndex) != Direction::Out)
680 continue;
681 auto portValue =
682 cast<mlir::TypedValue<RefType>>(module.getArgument(portIndex));
683 mlir::FlatSymbolRefAttr ref;
684 SmallString<128> stringLeaf;
685 if (failed(resolveReferencePath(portValue, declBuilder, ref, stringLeaf)))
686 return failure();
687
688 SmallString<128> formatString;
689 if (ref)
690 formatString += "{{0}}";
691 formatString += stringLeaf;
692
693 // Insert a macro with the format:
694 // ref_<module-name>_<ref-name> <path>
695 if (circuitRefPrefix.empty())
696 getRefABIPrefix(module, circuitRefPrefix);
697 auto macroName =
698 getRefABIMacroForPort(module, portIndex, circuitRefPrefix);
699 declBuilder.create<sv::MacroDeclOp>(macroName, ArrayAttr(), StringAttr());
700 ports.emplace_back(macroName, declBuilder.getStringAttr(formatString),
701 ref ? declBuilder.getArrayAttr({ref}) : ArrayAttr{});
702 }
703
704 // Create a file only if the module has at least one ref port.
705 if (ports.empty())
706 return success();
707
708 // The macros will be exported to a `ref_<module-name>.sv` file.
709 // In the IR, the file is inserted before the module.
710 auto fileBuilder = ImplicitLocOpBuilder(module.getLoc(), module);
711 fileBuilder.create<emit::FileOp>(circuitRefPrefix + ".sv", [&] {
712 for (auto [macroName, formatString, symbols] : ports) {
713 fileBuilder.create<sv::MacroDefOp>(FlatSymbolRefAttr::get(macroName),
714 formatString, symbols);
715 }
716 });
717
718 return success();
719 }
720
721 /// Get the cached namespace for a module.
723 return moduleNamespaces.try_emplace(module, module).first->second;
724 }
725
726 InnerRefAttr getInnerRefTo(Value val) {
727 if (auto arg = dyn_cast<BlockArgument>(val))
728 return ::getInnerRefTo(
729 cast<FModuleLike>(arg.getParentBlock()->getParentOp()),
730 arg.getArgNumber(),
731 [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
732 return getModuleNamespace(mod);
733 });
734 return getInnerRefTo(val.getDefiningOp());
735 }
736
737 InnerRefAttr getInnerRefTo(Operation *op) {
738 return ::getInnerRefTo(op,
739 [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
740 return getModuleNamespace(mod);
741 });
742 }
743
744 void markForRemoval(Operation *op) { opsToRemove.push_back(op); }
745
746 std::optional<size_t> getRemoteRefSend(Value val,
747 bool errorIfNotFound = true) {
748 auto iter = dataflowAt.find(dataFlowClasses->getOrInsertLeaderValue(val));
749 if (iter != dataflowAt.end())
750 return iter->getSecond();
751 if (!errorIfNotFound)
752 return std::nullopt;
753 // The referenced module must have already been analyzed, error out if the
754 // dataflow at the child module is not resolved.
755 if (BlockArgument arg = dyn_cast<BlockArgument>(val))
756 arg.getOwner()->getParentOp()->emitError(
757 "reference dataflow cannot be traced back to the remote read op "
758 "for module port '")
759 << dyn_cast<FModuleOp>(arg.getOwner()->getParentOp())
760 .getPortName(arg.getArgNumber())
761 << "'";
762 else
763 val.getDefiningOp()->emitOpError(
764 "reference dataflow cannot be traced back to the remote read op");
765 signalPassFailure();
766 return std::nullopt;
767 }
768
769 size_t
770 addReachingSendsEntry(Value atRefVal, XMRNode::SymOrIndexOp info,
771 std::optional<size_t> continueFrom = std::nullopt) {
772 auto leader = dataFlowClasses->getOrInsertLeaderValue(atRefVal);
773 auto indx = refSendPathList.size();
774 dataflowAt[leader] = indx;
775 refSendPathList.push_back({info, continueFrom});
776 return indx;
777 }
778
780 // Now erase all the Ops and ports of RefType.
781 // This needs to be done as the last step to ensure uses are erased before
782 // the def is erased.
783 for (Operation *op : llvm::reverse(opsToRemove))
784 op->erase();
785 for (auto iter : refPortsToRemoveMap)
786 if (auto mod = dyn_cast<FModuleOp>(iter.getFirst()))
787 mod.erasePorts(iter.getSecond());
788 else if (auto mod = dyn_cast<FExtModuleOp>(iter.getFirst()))
789 mod.erasePorts(iter.getSecond());
790 else if (auto inst = dyn_cast<InstanceOp>(iter.getFirst())) {
791 ImplicitLocOpBuilder b(inst.getLoc(), inst);
792 inst.erasePorts(b, iter.getSecond());
793 inst.erase();
794 } else if (auto mem = dyn_cast<MemOp>(iter.getFirst())) {
795 // Remove all debug ports of the memory.
796 ImplicitLocOpBuilder builder(mem.getLoc(), mem);
797 SmallVector<Attribute, 4> resultNames;
798 SmallVector<Type, 4> resultTypes;
799 SmallVector<Attribute, 4> portAnnotations;
800 SmallVector<Value, 4> oldResults;
801 for (const auto &res : llvm::enumerate(mem.getResults())) {
802 if (isa<RefType>(mem.getResult(res.index()).getType()))
803 continue;
804 resultNames.push_back(mem.getPortName(res.index()));
805 resultTypes.push_back(res.value().getType());
806 portAnnotations.push_back(mem.getPortAnnotation(res.index()));
807 oldResults.push_back(res.value());
808 }
809 auto newMem = builder.create<MemOp>(
810 resultTypes, mem.getReadLatency(), mem.getWriteLatency(),
811 mem.getDepth(), RUWAttr::Undefined,
812 builder.getArrayAttr(resultNames), mem.getNameAttr(),
813 mem.getNameKind(), mem.getAnnotations(),
814 builder.getArrayAttr(portAnnotations), mem.getInnerSymAttr(),
815 mem.getInitAttr(), mem.getPrefixAttr());
816 for (const auto &res : llvm::enumerate(oldResults))
817 res.value().replaceAllUsesWith(newMem.getResult(res.index()));
818 mem.erase();
819 }
820 opsToRemove.clear();
821 refPortsToRemoveMap.clear();
822 dataflowAt.clear();
823 refSendPathList.clear();
824 moduleStates.clear();
825 }
826
828
829 /// Return a HierPathOp for the provided pathArray. This will either return
830 /// an existing HierPathOp or it will create and return a new one.
831 hw::HierPathOp getOrCreatePath(ArrayAttr pathArray,
832 ImplicitLocOpBuilder &builder) {
833 assert(pathArray && !pathArray.empty());
834 // Return an existing HierPathOp if one exists with the same path.
835 auto pathIter = pathCache.find(pathArray);
836 if (pathIter != pathCache.end())
837 return pathIter->second;
838
839 // Reset the insertion point after this function returns.
840 OpBuilder::InsertionGuard guard(builder);
841
842 // Set the insertion point to either the known location where the pass
843 // inserts HierPathOps or to the start of the circuit.
844 if (pathInsertPoint.isSet())
845 builder.restoreInsertionPoint(pathInsertPoint);
846 else
847 builder.setInsertionPointToStart(getOperation().getBodyBlock());
848
849 // Create the new HierPathOp and insert it into the pathCache.
850 hw::HierPathOp path =
851 pathCache
852 .insert({pathArray,
853 builder.create<hw::HierPathOp>(
854 circuitNamespace->newName("xmrPath"), pathArray)})
855 .first->second;
856 path.setVisibility(SymbolTable::Visibility::Private);
857
858 // Save the insertion point so other unique HierPathOps will be created
859 // after this one.
860 pathInsertPoint = builder.saveInsertionPoint();
861
862 // Return the new path.
863 return path;
864 }
865
866private:
867 /// Cached module namespaces.
868 DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
869
870 DenseSet<Operation *> visitedModules;
871 /// Map of a reference value to an entry into refSendPathList. Each entry in
872 /// refSendPathList represents the path to RefSend.
873 /// The path is required since there can be multiple paths to the RefSend and
874 /// we need to identify a unique path.
875 DenseMap<Value, size_t> dataflowAt;
876
877 /// refSendPathList is used to construct a path to the RefSendOp. Each entry
878 /// is an XMRNode, with an InnerRefAttr or indexing op, and a pointer to the
879 /// next node in the path. The InnerRefAttr can be to an InstanceOp or to the
880 /// XMR defining op, the index op records narrowing along path. All the nodes
881 /// representing an InstanceOp or indexing operation must have a valid
882 /// NextNodeOnPath. Only the node representing the final XMR defining op has
883 /// no NextNodeOnPath, which denotes a leaf node on the path.
884 SmallVector<XMRNode> refSendPathList;
885
886 /// llvm::EquivalenceClasses wants comparable elements. This comparator uses
887 /// uses pointer comparison on the Impl.
889 bool operator()(const Value &lhs, const Value &rhs) const {
890 return lhs.getImpl() < rhs.getImpl();
891 }
892 };
893
894 llvm::EquivalenceClasses<Value, ValueComparator> *dataFlowClasses;
895 // Instance and module ref ports that needs to be removed.
896 DenseMap<Operation *, llvm::BitVector> refPortsToRemoveMap;
897
898 /// RefResolve, RefSend, and Connects involving them that will be removed.
899 SmallVector<Operation *> opsToRemove;
900
901 /// Record the internal path to an external module or a memory.
902 DenseMap<size_t, SmallString<128>> xmrPathSuffix;
903
905
906 /// A cache of already created HierPathOps. This is used to avoid repeatedly
907 /// creating the same HierPathOp.
908 DenseMap<Attribute, hw::HierPathOp> pathCache;
909
910 /// The insertion point where the pass inserts HierPathOps.
911 OpBuilder::InsertPoint pathInsertPoint = {};
912
913 /// Per-module helpers for creating operations within modules.
914 DenseMap<FModuleOp, ModuleState> moduleStates;
915};
916
917std::unique_ptr<mlir::Pass> circt::firrtl::createLowerXMRPass() {
918 return std::make_unique<LowerXMRPass>();
919}
assert(baseType &&"element must be base type")
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
static Block * getBodyBlock(FModuleLike mod)
LogicalResult resolveReference(mlir::TypedValue< RefType > refVal, ImplicitLocOpBuilder &builder, FlatSymbolRefAttr &ref, StringAttr &xmrAttr)
Definition LowerXMR.cpp:503
DenseMap< Operation *, hw::InnerSymbolNamespace > moduleNamespaces
Cached module namespaces.
Definition LowerXMR.cpp:868
DenseMap< size_t, SmallString< 128 > > xmrPathSuffix
Record the internal path to an external module or a memory.
Definition LowerXMR.cpp:902
InnerRefAttr getInnerRefTo(Value val)
Definition LowerXMR.cpp:726
size_t addReachingSendsEntry(Value atRefVal, XMRNode::SymOrIndexOp info, std::optional< size_t > continueFrom=std::nullopt)
Definition LowerXMR.cpp:770
DenseMap< FModuleOp, ModuleState > moduleStates
Per-module helpers for creating operations within modules.
Definition LowerXMR.cpp:914
hw::HierPathOp getOrCreatePath(ArrayAttr pathArray, ImplicitLocOpBuilder &builder)
Return a HierPathOp for the provided pathArray.
Definition LowerXMR.cpp:831
LogicalResult resolveReferencePath(mlir::TypedValue< RefType > refVal, ImplicitLocOpBuilder builder, mlir::FlatSymbolRefAttr &ref, SmallString< 128 > &stringLeaf)
Definition LowerXMR.cpp:432
DenseMap< Value, size_t > dataflowAt
Map of a reference value to an entry into refSendPathList.
Definition LowerXMR.cpp:875
void setPortToRemove(Operation *op, size_t index, size_t numPorts)
Definition LowerXMR.cpp:574
hw::InnerSymbolNamespace & getModuleNamespace(FModuleLike module)
Get the cached namespace for a module.
Definition LowerXMR.cpp:722
llvm::EquivalenceClasses< Value, ValueComparator > * dataFlowClasses
Definition LowerXMR.cpp:894
void markForRemoval(Operation *op)
Definition LowerXMR.cpp:744
void garbageCollect()
Definition LowerXMR.cpp:779
LogicalResult handlePublicModuleRefPorts(FModuleOp module)
Definition LowerXMR.cpp:667
void getRefABIPrefix(FModuleLike mod, SmallVectorImpl< char > &prefix)
Generate the ABI ref_<module> prefix string into prefix.
Definition LowerXMR.cpp:414
void runOnOperation() override
Definition LowerXMR.cpp:127
DenseMap< Attribute, hw::HierPathOp > pathCache
A cache of already created HierPathOps.
Definition LowerXMR.cpp:908
LogicalResult handleRefResolve(RefResolveOp resolve)
Definition LowerXMR.cpp:550
DenseMap< Operation *, llvm::BitVector > refPortsToRemoveMap
Definition LowerXMR.cpp:896
SmallVector< XMRNode > refSendPathList
refSendPathList is used to construct a path to the RefSendOp.
Definition LowerXMR.cpp:884
LogicalResult handleInstanceOp(InstanceOp inst, InstanceGraph &instanceGraph)
Definition LowerXMR.cpp:581
LogicalResult handleForceReleaseOp(Operation *op)
Definition LowerXMR.cpp:520
std::optional< size_t > getRemoteRefSend(Value val, bool errorIfNotFound=true)
Definition LowerXMR.cpp:746
DenseSet< Operation * > visitedModules
Definition LowerXMR.cpp:870
InnerRefAttr getInnerRefTo(Operation *op)
Definition LowerXMR.cpp:737
OpBuilder::InsertPoint pathInsertPoint
The insertion point where the pass inserts HierPathOps.
Definition LowerXMR.cpp:911
StringAttr getRefABIMacroForPort(FModuleLike mod, size_t portIndex, const Twine &prefix, bool backTick=false)
Get full macro name as StringAttr for the specified ref port.
Definition LowerXMR.cpp:426
CircuitNamespace * circuitNamespace
Definition LowerXMR.cpp:904
bool isZeroWidth(FIRRTLBaseType t)
Definition LowerXMR.cpp:827
SmallVector< Operation * > opsToRemove
RefResolve, RefSend, and Connects involving them that will be removed.
Definition LowerXMR.cpp:899
void clear()
Empty the namespace.
Definition Namespace.h:73
int32_t getBitWidthOrSentinel()
If this is an IntType, AnalogType, or sugar type for a single bit (Clock, Reset, etc) then return the...
This graph tracks modules and where they are instantiated.
FieldRef getFieldRefFromValue(Value value, bool lookThroughCasts=false)
Get the FieldRef from a value.
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.
llvm::raw_ostream & operator<<(llvm::raw_ostream &os, const InstanceInfo::LatticeValue &value)
std::pair< std::string, bool > getFieldName(const FieldRef &fieldRef, bool nameSafe=false)
Get a string identifier representing the FieldRef.
std::unique_ptr< mlir::Pass > createLowerXMRPass()
Definition LowerXMR.cpp:917
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
IntegerAttr getIntZerosAttr(Type type)
Utility for generating a constant zero attribute.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
llvm::EquivalenceClasses wants comparable elements.
Definition LowerXMR.cpp:888
bool operator()(const Value &lhs, const Value &rhs) const
Definition LowerXMR.cpp:889
The namespace of a CircuitOp, generally inhabited by modules.
Definition Namespace.h:24