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 
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 ibis {
22 #define GEN_PASS_DEF_IBISTUNNELING
23 #include "circt/Dialect/Ibis/IbisPasses.h.inc"
24 } // namespace ibis
25 } // namespace circt
26 
27 using namespace mlir;
28 using namespace circt;
29 using namespace circt::ibis;
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 IbisTunnelingOptions &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 - ibis.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 IbisTunnelingOptions &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 IbisTunnelingOptions &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 ibis.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 ibis.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 ibis.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 ibis.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 ibis.container.instance operations in the parent.
242 static FailureOr<ContainerInstanceOp> locateInstanceIn(Operation *parentOp,
243  FlatSymbolRefAttr name) {
244  if (parentOp->hasTrait<OpTrait::SymbolTable>()) {
245  auto *tunnelInstanceOp = SymbolTable::lookupSymbolIn(parentOp, name);
246  if (!tunnelInstanceOp)
247  return failure();
248  return cast<ContainerInstanceOp>(tunnelInstanceOp);
249  }
250 
251  // Default: scan the container instances.
252  for (auto instanceOp : parentOp->getRegion(0).getOps<ContainerInstanceOp>()) {
253  if (instanceOp.getInnerSym().getSymName() == name.getValue())
254  return instanceOp;
255  }
256 
257  return failure();
258 }
259 
260 // NOLINTNEXTLINE(misc-no-recursion)
261 LogicalResult Tunneler::tunnelDown(InstanceGraphNode *currentContainer,
262  FlatSymbolRefAttr tunnelInto,
263  llvm::ArrayRef<PathStepAttr> path,
264  PortRefMapping &portMapping) {
265  // Locate the instance that we're tunneling into
266  Operation *parentOp = currentContainer->getModule().getOperation();
267  auto parentSymbolOp = dyn_cast<hw::InnerSymbolOpInterface>(parentOp);
268  assert(parentSymbolOp && "expected current container to be a symbol op");
269  FailureOr<ContainerInstanceOp> locateRes =
270  locateInstanceIn(parentOp, tunnelInto);
271  if (failed(locateRes))
272  return op->emitOpError()
273  << "expected an instance named " << tunnelInto << " in @"
274  << parentSymbolOp.getInnerSymAttr().getSymName().getValue()
275  << " but found none";
276  ContainerInstanceOp tunnelInstance = *locateRes;
277 
278  if (path.empty()) {
279  // Tunneling ended with a 'child' step - create get_ports of all of the
280  // requested ports.
281  rewriter.setInsertionPointAfter(tunnelInstance);
282  for (PortInfo &pi : portInfos) {
283  auto targetGetPortOp =
284  rewriter.create<GetPortOp>(op.getLoc(), pi.getType(), tunnelInstance,
285  pi.getPortOp.getPortSymbol());
286  portMapping[&pi] = targetGetPortOp.getResult();
287  }
288  return success();
289  }
290 
291  // We're not in the target, but tunneling into a child instance.
292  // Create output ports in the child instance for the requested ports.
293  auto *tunnelScopeNode =
294  ig.lookup(tunnelInstance.getTargetNameAttr().getName());
295  auto tunnelScope = tunnelScopeNode->getModule<ScopeOpInterface>();
296 
297  rewriter.setInsertionPointToEnd(tunnelScope.getBodyBlock());
298  llvm::DenseMap<StringAttr, OutputPortOp> outputPortOps;
299  for (PortInfo &pi : portInfos) {
300  outputPortOps[pi.portName] = rewriter.create<OutputPortOp>(
301  op.getLoc(), circt::hw::InnerSymAttr::get(pi.portName), pi.getType(),
302  pi.portName);
303  }
304 
305  // Recurse into the tunnel instance container.
306  PortRefMapping childMapping;
307  if (failed(tunnelDispatch(tunnelScopeNode, path, childMapping)))
308  return failure();
309 
310  for (auto [pi, res] : childMapping) {
311  PortInfo &portInfo = *pi;
312 
313  // Write the target value to the output port.
314  rewriter.setInsertionPointToEnd(tunnelScope.getBodyBlock());
315  rewriter.create<PortWriteOp>(op.getLoc(), outputPortOps[portInfo.portName],
316  res);
317 
318  // Back in the current container, read the new output port of the child
319  // instance and assign it to the port mapping.
320  rewriter.setInsertionPointAfter(tunnelInstance);
321  auto getPortOp = rewriter.create<GetPortOp>(
322  op.getLoc(), tunnelInstance, portInfo.portName, portInfo.getType(),
324  portMapping[pi] =
325  rewriter.create<PortReadOp>(op.getLoc(), getPortOp).getResult();
326  }
327 
328  return success();
329 }
330 
331 // NOLINTNEXTLINE(misc-no-recursion)
332 LogicalResult Tunneler::tunnelUp(InstanceGraphNode *currentContainer,
333  llvm::ArrayRef<PathStepAttr> path,
334  PortRefMapping &portMapping) {
335  auto scopeOp = currentContainer->getModule<ScopeOpInterface>();
336  if (currentContainer->noUses())
337  return op->emitOpError()
338  << "cannot tunnel up from " << scopeOp.getScopeName()
339  << " because it has no uses";
340 
341  for (auto *use : currentContainer->uses()) {
342  InstanceGraphNode *parentScopeNode =
343  ig.lookup(use->getParent()->getModule());
344  auto parentScope = parentScopeNode->getModule<ScopeOpInterface>();
345  PortRefMapping targetPortMapping;
346  if (path.empty()) {
347  // Tunneling ended with a 'parent' step - all of the requested ports
348  // should be available right here in the parent scope.
349  for (PortInfo &pi : portInfos) {
350  StringRef targetPortName = pi.getPortOp.getPortSymbol();
351  PortOpInterface portLikeOp = parentScope.lookupPort(targetPortName);
352  if (!portLikeOp)
353  return op->emitOpError()
354  << "expected a port named " << targetPortName << " in "
355  << parentScope.getScopeName() << " but found none";
356 
357  // "Port forwarding" check - see comment in portForwardIfNeeded.
358  targetPortMapping[&pi] = portForwardIfNeeded(portLikeOp, pi);
359  }
360  } else {
361  // recurse into the parents, which will define the target value that
362  // we can write to the input port of the current container instance.
363  if (failed(tunnelDispatch(parentScopeNode, path, targetPortMapping)))
364  return failure();
365  }
366 
367  auto instance = use->getInstance<ContainerInstanceOp>();
368  rewriter.setInsertionPointAfter(instance);
369  for (PortInfo &pi : portInfos) {
370  auto getPortOp = rewriter.create<GetPortOp>(
371  op.getLoc(), instance, pi.portName, pi.getType(), Direction::Input);
372  rewriter.create<PortWriteOp>(op.getLoc(), getPortOp,
373  targetPortMapping[&pi]);
374  }
375  }
376 
377  // Create input ports for the requested portrefs.
378  rewriter.setInsertionPointToEnd(scopeOp.getBodyBlock());
379  for (PortInfo &pi : portInfos) {
380  auto inputPort = rewriter.create<InputPortOp>(
381  op.getLoc(), hw::InnerSymAttr::get(pi.portName), pi.getType(),
382  pi.portName);
383  // Read the input port of the current container to forward the portref.
384 
385  portMapping[&pi] =
386  rewriter.create<PortReadOp>(op.getLoc(), inputPort.getResult())
387  .getResult();
388  }
389 
390  return success();
391 }
392 
393 class TunnelingConversionPattern : public OpConversionPattern<PathOp> {
394 public:
395  TunnelingConversionPattern(MLIRContext *context, InstanceGraph &ig,
396  IbisTunnelingOptions options)
397  : OpConversionPattern<PathOp>(context), ig(ig),
398  options(std::move(options)) {}
399 
400  LogicalResult
401  matchAndRewrite(PathOp op, OpAdaptor adaptor,
402  ConversionPatternRewriter &rewriter) const override {
403  return Tunneler(options, op, rewriter, ig).go();
404  }
405 
406 protected:
407  InstanceGraph &ig;
408  IbisTunnelingOptions options;
409 };
410 
411 struct TunnelingPass
412  : public circt::ibis::impl::IbisTunnelingBase<TunnelingPass> {
413  using IbisTunnelingBase<TunnelingPass>::IbisTunnelingBase;
414  void runOnOperation() override;
415 };
416 
417 } // anonymous namespace
418 
419 void TunnelingPass::runOnOperation() {
420  auto &ig = getAnalysis<InstanceGraph>();
421  auto *ctx = &getContext();
422  ConversionTarget target(*ctx);
423  target.addIllegalOp<PathOp>();
424  target.addLegalOp<InputPortOp, OutputPortOp, PortReadOp, PortWriteOp,
425  GetPortOp, InputWireOp, OutputWireOp>();
426 
427  RewritePatternSet patterns(ctx);
428  patterns.add<TunnelingConversionPattern>(
429  ctx, ig, IbisTunnelingOptions{readSuffix, writeSuffix});
430 
431  if (failed(
432  applyPartialConversion(getOperation(), target, std::move(patterns))))
433  signalPassFailure();
434 }
435 
436 std::unique_ptr<Pass>
437 circt::ibis::createTunnelingPass(const IbisTunnelingOptions &options) {
438  return std::make_unique<TunnelingPass>(options);
439 }
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:3929
@ 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.