CIRCT  20.0.0git
KanagawaTunneling.cpp
Go to the documentation of this file.
1 //===- KanagawaTunneling.cpp - Implementation of tunneling ----------------===//
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 
13 
15 #include "mlir/IR/Builders.h"
16 #include "mlir/Pass/Pass.h"
17 #include "mlir/Transforms/DialectConversion.h"
18 #include "llvm/ADT/TypeSwitch.h"
19 
20 namespace circt {
21 namespace kanagawa {
22 #define GEN_PASS_DEF_KANAGAWATUNNELING
23 #include "circt/Dialect/Kanagawa/KanagawaPasses.h.inc"
24 } // namespace kanagawa
25 } // namespace circt
26 
27 using namespace mlir;
28 using namespace circt;
29 using namespace circt::kanagawa;
30 using namespace circt::igraph;
31 
32 namespace {
33 
34 // The PortInfo struct is used to keep track of the get_port ops that
35 // specify which ports needs to be tunneled through the hierarchy.
36 struct PortInfo {
37  // Name used for portrefs of this get_port in the instance hierarchy.
38  mlir::StringAttr portName;
39 
40  // Source get_port op.
41  GetPortOp getPortOp;
42 
43  PortRefType getType() {
44  return cast<PortRefType>(getPortOp.getPort().getType());
45  }
46  Type getInnerType() { return getType().getPortType(); }
47  Direction getRequestedDirection() { return getType().getDirection(); }
48 };
49 
50 struct Tunneler {
51 public:
52  Tunneler(const KanagawaTunnelingOptions &options, PathOp op,
53  ConversionPatternRewriter &rewriter, InstanceGraph &ig);
54 
55  // A mapping between requested port names from a ScopeRef and the actual
56  // portref SSA values that are used to replace the get_port ops.
57  // MapVector to ensure determinism.
58  using PortRefMapping = llvm::MapVector<PortInfo *, Value>;
59 
60  // Launch the tunneling process.
61  LogicalResult go();
62 
63 private:
64  // Dispatches tunneling in the current container and returns a value of the
65  // target scoperef inside the current container.
66  LogicalResult tunnelDispatch(InstanceGraphNode *currentContainer,
67  llvm::ArrayRef<PathStepAttr> path,
68  PortRefMapping &mapping);
69 
70  // "Port forwarding" check - kanagawa.get_port specifies the intended
71  // direction which a port is accessed by from within the hierarchy.
72  // If the intended direction is not the same as the actual port
73  // direction, we need to insert a wire to flip the direction of the
74  // mapped port.
75  Value portForwardIfNeeded(PortOpInterface actualPort, PortInfo &portInfo);
76 
77  // Tunnels up relative to the current container. This will write to the
78  // target input port of the current container from any parent
79  // (instantiating) containers, and return the value of the target scoperef
80  // inside the current container.
81  LogicalResult tunnelUp(InstanceGraphNode *currentContainer,
82  llvm::ArrayRef<PathStepAttr> path,
83  PortRefMapping &portMapping);
84 
85  // Tunnels down relative to the current container, and returns the value of
86  // the target scoperef inside the current container.
87  LogicalResult tunnelDown(InstanceGraphNode *currentContainer,
88  FlatSymbolRefAttr tunnelInto,
89  llvm::ArrayRef<PathStepAttr> path,
90  PortRefMapping &portMapping);
91 
92  // Generates names for the port refs to be created.
93  void genPortNames(llvm::SmallVectorImpl<PortInfo> &portInfos);
94 
95  PathOp op;
96  ConversionPatternRewriter &rewriter;
97  InstanceGraph &ig;
98  const KanagawaTunnelingOptions &options;
99  mlir::StringAttr pathName;
100  llvm::SmallVector<PathStepAttr> path;
101 
102  // MapVector to ensure determinism.
103  llvm::SmallVector<PortInfo> portInfos;
104 
105  // "Target" refers to the last step in the path which is the scoperef that
106  // all port requests are tunneling towards.
107  PathStepAttr target;
108  FlatSymbolRefAttr targetName;
109 };
110 
111 Tunneler::Tunneler(const KanagawaTunnelingOptions &options, PathOp op,
112  ConversionPatternRewriter &rewriter, InstanceGraph &ig)
113  : op(op), rewriter(rewriter), ig(ig), options(options) {
114  llvm::copy(op.getPathAsRange(), std::back_inserter(path));
115  assert(!path.empty() &&
116  "empty paths should never occur - illegal for kanagawa.path ops");
117  target = path.back();
118  targetName = target.getChild();
119 }
120 
121 void Tunneler::genPortNames(llvm::SmallVectorImpl<PortInfo> &portInfos) {
122  std::string pathName;
123  llvm::raw_string_ostream ss(pathName);
124  llvm::interleave(
125  op.getPathAsRange(), ss,
126  [&](PathStepAttr step) {
127  if (step.getDirection() == PathDirection::Parent)
128  ss << "p"; // use 'p' instead of 'parent' to avoid long SSA names.
129  else
130  ss << step.getChild().getValue();
131  },
132  "_");
133 
134  for (PortInfo &pi : portInfos) {
135  // Suffix the ports by the intended usage (read/write). This also de-aliases
136  // cases where one both reads and writes from the same input port.
137  std::string suffix = pi.getRequestedDirection() == Direction::Input
138  ? options.writeSuffix
139  : options.readSuffix;
140  pi.portName = rewriter.getStringAttr(pathName + "_" +
141  pi.portName.getValue() + suffix);
142  }
143 }
144 
145 LogicalResult Tunneler::go() {
146  // Gather the required port accesses of the ScopeRef.
147  for (auto *user : op.getResult().getUsers()) {
148  auto getPortOp = dyn_cast<GetPortOp>(user);
149  if (!getPortOp)
150  return user->emitOpError() << "unknown user of a PathOp result - "
151  "tunneling only supports kanagawa.get_port";
152  portInfos.push_back(
153  PortInfo{getPortOp.getPortSymbolAttr().getAttr(), getPortOp});
154  }
155  genPortNames(portInfos);
156 
157  InstanceGraphNode *currentContainer =
158  ig.lookup(cast<ModuleOpInterface>(op.getOperation()->getParentOp()));
159 
160  PortRefMapping mapping;
161  if (failed(tunnelDispatch(currentContainer, path, mapping)))
162  return failure();
163 
164  // Replace the get_port ops with the target value.
165  for (PortInfo &pi : portInfos) {
166  auto *it = mapping.find(&pi);
167  assert(it != mapping.end() &&
168  "expected to find a portref mapping for all get_port ops");
169  rewriter.replaceOp(pi.getPortOp, it->second);
170  }
171 
172  // And finally erase the path.
173  rewriter.eraseOp(op);
174  return success();
175 }
176 
177 // NOLINTNEXTLINE(misc-no-recursion)
178 LogicalResult Tunneler::tunnelDispatch(InstanceGraphNode *currentContainer,
179  llvm::ArrayRef<PathStepAttr> path,
180  PortRefMapping &mapping) {
181  PathStepAttr currentStep = path.front();
182  PortRefMapping targetValue;
183  path = path.drop_front();
184  if (currentStep.getDirection() == PathDirection::Parent) {
185  LogicalResult upRes = tunnelUp(currentContainer, path, mapping);
186  if (failed(upRes))
187  return failure();
188  } else {
189  FlatSymbolRefAttr tunnelInto = currentStep.getChild();
190  LogicalResult downRes =
191  tunnelDown(currentContainer, tunnelInto, path, mapping);
192  if (failed(downRes))
193  return failure();
194  }
195  return success();
196 }
197 
198 Value Tunneler::portForwardIfNeeded(PortOpInterface actualPort,
199  PortInfo &portInfo) {
200  Direction actualDir =
201  cast<PortRefType>(actualPort.getPort().getType()).getDirection();
202  Direction requestedDir = portInfo.getRequestedDirection();
203 
204  // Match - just return the port itself.
205  if (actualDir == requestedDir)
206  return actualPort.getPort();
207 
208  // Mismatch...
209  OpBuilder::InsertionGuard guard(rewriter);
210  rewriter.setInsertionPointAfter(actualPort);
211 
212  // If the requested direction was an input, this means that someone tried
213  // to write to an output port. We need to insert an kanagawa.wire.input that
214  // provides a writeable input port, and assign the wire output to the
215  // output port.
216  if (requestedDir == Direction::Input) {
217  auto wireOp = rewriter.create<InputWireOp>(
218  op.getLoc(), rewriter.getStringAttr(*actualPort.getInnerName() + ".wr"),
219  portInfo.getInnerType());
220 
221  rewriter.create<PortWriteOp>(op.getLoc(), actualPort.getPort(),
222  wireOp.getOutput());
223  return wireOp.getPort();
224  }
225 
226  // If the requested direction was an output, this means that someone tried
227  // to read from an input port. We need to insert an kanagawa.wire.output that
228  // provides a readable output port, and read the input port as the value
229  // of the wire.
230  Value inputValue =
231  rewriter.create<PortReadOp>(op.getLoc(), actualPort.getPort());
232  auto wireOp = rewriter.create<OutputWireOp>(
233  op.getLoc(),
235  rewriter.getStringAttr(*actualPort.getInnerName() + ".rd")),
236  inputValue, rewriter.getStringAttr(actualPort.getNameHint() + ".rd"));
237  return wireOp.getPort();
238 }
239 
240 // Lookup an instance in the parent op. If the parent op is a symbol table, will
241 // use that - else, scan the kanagawa.container.instance operations in the
242 // parent.
243 static FailureOr<ContainerInstanceOp> locateInstanceIn(Operation *parentOp,
244  FlatSymbolRefAttr name) {
245  if (parentOp->hasTrait<OpTrait::SymbolTable>()) {
246  auto *tunnelInstanceOp = SymbolTable::lookupSymbolIn(parentOp, name);
247  if (!tunnelInstanceOp)
248  return failure();
249  return cast<ContainerInstanceOp>(tunnelInstanceOp);
250  }
251 
252  // Default: scan the container instances.
253  for (auto instanceOp : parentOp->getRegion(0).getOps<ContainerInstanceOp>()) {
254  if (instanceOp.getInnerSym().getSymName() == name.getValue())
255  return instanceOp;
256  }
257 
258  return failure();
259 }
260 
261 // NOLINTNEXTLINE(misc-no-recursion)
262 LogicalResult Tunneler::tunnelDown(InstanceGraphNode *currentContainer,
263  FlatSymbolRefAttr tunnelInto,
264  llvm::ArrayRef<PathStepAttr> path,
265  PortRefMapping &portMapping) {
266  // Locate the instance that we're tunneling into
267  Operation *parentOp = currentContainer->getModule().getOperation();
268  auto parentSymbolOp = dyn_cast<hw::InnerSymbolOpInterface>(parentOp);
269  assert(parentSymbolOp && "expected current container to be a symbol op");
270  FailureOr<ContainerInstanceOp> locateRes =
271  locateInstanceIn(parentOp, tunnelInto);
272  if (failed(locateRes))
273  return op->emitOpError()
274  << "expected an instance named " << tunnelInto << " in @"
275  << parentSymbolOp.getInnerSymAttr().getSymName().getValue()
276  << " but found none";
277  ContainerInstanceOp tunnelInstance = *locateRes;
278 
279  if (path.empty()) {
280  // Tunneling ended with a 'child' step - create get_ports of all of the
281  // requested ports.
282  rewriter.setInsertionPointAfter(tunnelInstance);
283  for (PortInfo &pi : portInfos) {
284  auto targetGetPortOp =
285  rewriter.create<GetPortOp>(op.getLoc(), pi.getType(), tunnelInstance,
286  pi.getPortOp.getPortSymbol());
287  portMapping[&pi] = targetGetPortOp.getResult();
288  }
289  return success();
290  }
291 
292  // We're not in the target, but tunneling into a child instance.
293  // Create output ports in the child instance for the requested ports.
294  auto *tunnelScopeNode =
295  ig.lookup(tunnelInstance.getTargetNameAttr().getName());
296  auto tunnelScope = tunnelScopeNode->getModule<ScopeOpInterface>();
297 
298  rewriter.setInsertionPointToEnd(tunnelScope.getBodyBlock());
299  llvm::DenseMap<StringAttr, OutputPortOp> outputPortOps;
300  for (PortInfo &pi : portInfos) {
301  outputPortOps[pi.portName] = rewriter.create<OutputPortOp>(
302  op.getLoc(), circt::hw::InnerSymAttr::get(pi.portName), pi.getType(),
303  pi.portName);
304  }
305 
306  // Recurse into the tunnel instance container.
307  PortRefMapping childMapping;
308  if (failed(tunnelDispatch(tunnelScopeNode, path, childMapping)))
309  return failure();
310 
311  for (auto [pi, res] : childMapping) {
312  PortInfo &portInfo = *pi;
313 
314  // Write the target value to the output port.
315  rewriter.setInsertionPointToEnd(tunnelScope.getBodyBlock());
316  rewriter.create<PortWriteOp>(op.getLoc(), outputPortOps[portInfo.portName],
317  res);
318 
319  // Back in the current container, read the new output port of the child
320  // instance and assign it to the port mapping.
321  rewriter.setInsertionPointAfter(tunnelInstance);
322  auto getPortOp = rewriter.create<GetPortOp>(
323  op.getLoc(), tunnelInstance, portInfo.portName, portInfo.getType(),
325  portMapping[pi] =
326  rewriter.create<PortReadOp>(op.getLoc(), getPortOp).getResult();
327  }
328 
329  return success();
330 }
331 
332 // NOLINTNEXTLINE(misc-no-recursion)
333 LogicalResult Tunneler::tunnelUp(InstanceGraphNode *currentContainer,
334  llvm::ArrayRef<PathStepAttr> path,
335  PortRefMapping &portMapping) {
336  auto scopeOp = currentContainer->getModule<ScopeOpInterface>();
337  if (currentContainer->noUses())
338  return op->emitOpError()
339  << "cannot tunnel up from " << scopeOp.getScopeName()
340  << " because it has no uses";
341 
342  for (auto *use : currentContainer->uses()) {
343  InstanceGraphNode *parentScopeNode =
344  ig.lookup(use->getParent()->getModule());
345  auto parentScope = parentScopeNode->getModule<ScopeOpInterface>();
346  PortRefMapping targetPortMapping;
347  if (path.empty()) {
348  // Tunneling ended with a 'parent' step - all of the requested ports
349  // should be available right here in the parent scope.
350  for (PortInfo &pi : portInfos) {
351  StringRef targetPortName = pi.getPortOp.getPortSymbol();
352  PortOpInterface portLikeOp = parentScope.lookupPort(targetPortName);
353  if (!portLikeOp)
354  return op->emitOpError()
355  << "expected a port named " << targetPortName << " in "
356  << parentScope.getScopeName() << " but found none";
357 
358  // "Port forwarding" check - see comment in portForwardIfNeeded.
359  targetPortMapping[&pi] = portForwardIfNeeded(portLikeOp, pi);
360  }
361  } else {
362  // recurse into the parents, which will define the target value that
363  // we can write to the input port of the current container instance.
364  if (failed(tunnelDispatch(parentScopeNode, path, targetPortMapping)))
365  return failure();
366  }
367 
368  auto instance = use->getInstance<ContainerInstanceOp>();
369  rewriter.setInsertionPointAfter(instance);
370  for (PortInfo &pi : portInfos) {
371  auto getPortOp = rewriter.create<GetPortOp>(
372  op.getLoc(), instance, pi.portName, pi.getType(), Direction::Input);
373  rewriter.create<PortWriteOp>(op.getLoc(), getPortOp,
374  targetPortMapping[&pi]);
375  }
376  }
377 
378  // Create input ports for the requested portrefs.
379  rewriter.setInsertionPointToEnd(scopeOp.getBodyBlock());
380  for (PortInfo &pi : portInfos) {
381  auto inputPort = rewriter.create<InputPortOp>(
382  op.getLoc(), hw::InnerSymAttr::get(pi.portName), pi.getType(),
383  pi.portName);
384  // Read the input port of the current container to forward the portref.
385 
386  portMapping[&pi] =
387  rewriter.create<PortReadOp>(op.getLoc(), inputPort.getResult())
388  .getResult();
389  }
390 
391  return success();
392 }
393 
394 class TunnelingConversionPattern : public OpConversionPattern<PathOp> {
395 public:
396  TunnelingConversionPattern(MLIRContext *context, InstanceGraph &ig,
397  KanagawaTunnelingOptions options)
398  : OpConversionPattern<PathOp>(context), ig(ig),
399  options(std::move(options)) {}
400 
401  LogicalResult
402  matchAndRewrite(PathOp op, OpAdaptor adaptor,
403  ConversionPatternRewriter &rewriter) const override {
404  return Tunneler(options, op, rewriter, ig).go();
405  }
406 
407 protected:
408  InstanceGraph &ig;
409  KanagawaTunnelingOptions options;
410 };
411 
412 struct TunnelingPass
413  : public circt::kanagawa::impl::KanagawaTunnelingBase<TunnelingPass> {
414  using KanagawaTunnelingBase<TunnelingPass>::KanagawaTunnelingBase;
415  void runOnOperation() override;
416 };
417 
418 } // anonymous namespace
419 
420 void TunnelingPass::runOnOperation() {
421  auto &ig = getAnalysis<InstanceGraph>();
422  auto *ctx = &getContext();
423  ConversionTarget target(*ctx);
424  target.addIllegalOp<PathOp>();
425  target.addLegalOp<InputPortOp, OutputPortOp, PortReadOp, PortWriteOp,
426  GetPortOp, InputWireOp, OutputWireOp>();
427 
428  RewritePatternSet patterns(ctx);
429  patterns.add<TunnelingConversionPattern>(
430  ctx, ig, KanagawaTunnelingOptions{readSuffix, writeSuffix});
431 
432  if (failed(
433  applyPartialConversion(getOperation(), target, std::move(patterns))))
434  signalPassFailure();
435 }
436 
437 std::unique_ptr<Pass>
438 circt::kanagawa::createTunnelingPass(const KanagawaTunnelingOptions &options) {
439  return std::make_unique<TunnelingPass>(options);
440 }
assert(baseType &&"element must be base type")
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
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.
llvm::iterator_range< UseIterator > uses()
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Direction
The direction of a Component or Cell port.
Definition: CalyxOps.h:76
std::unique_ptr< mlir::Pass > createTunnelingPass(const KanagawaTunnelingOptions &={})
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
This holds the name, type, direction of a module's ports.