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