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