CIRCT 20.0.0git
Loading...
Searching...
No Matches
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
20namespace circt {
21namespace kanagawa {
22#define GEN_PASS_DEF_KANAGAWATUNNELING
23#include "circt/Dialect/Kanagawa/KanagawaPasses.h.inc"
24} // namespace kanagawa
25} // namespace circt
26
27using namespace mlir;
28using namespace circt;
29using namespace circt::kanagawa;
30using namespace circt::igraph;
31
32namespace {
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.
36struct 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
50struct Tunneler {
51public:
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
63private:
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
111Tunneler::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
121void 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
145LogicalResult 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)
178LogicalResult 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
198Value 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(),
234 hw::InnerSymAttr::get(
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.
243static 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)
262LogicalResult 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(),
324 Direction::Output);
325 portMapping[pi] =
326 rewriter.create<PortReadOp>(op.getLoc(), getPortOp).getResult();
327 }
328
329 return success();
330}
331
332// NOLINTNEXTLINE(misc-no-recursion)
333LogicalResult 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
394class TunnelingConversionPattern : public OpConversionPattern<PathOp> {
395public:
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
407protected:
408 InstanceGraph &ig;
409 KanagawaTunnelingOptions options;
410};
411
412struct TunnelingPass
413 : public circt::kanagawa::impl::KanagawaTunnelingBase<TunnelingPass> {
414 using KanagawaTunnelingBase<TunnelingPass>::KanagawaTunnelingBase;
415 void runOnOperation() override;
416};
417
418} // anonymous namespace
419
420void 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
437std::unique_ptr<Pass>
438circt::kanagawa::createTunnelingPass(const KanagawaTunnelingOptions &options) {
439 return std::make_unique<TunnelingPass>(options);
440}
assert(baseType &&"element must be base type")
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.
InstanceGraphNode * lookup(ModuleOpInterface op)
Look up an InstanceGraphNode for a module.
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.
This holds the name, type, direction of a module's ports.