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