15 #include "mlir/IR/Builders.h"
16 #include "mlir/Pass/Pass.h"
17 #include "mlir/Transforms/DialectConversion.h"
18 #include "llvm/ADT/TypeSwitch.h"
22 #define GEN_PASS_DEF_IBISTUNNELING
23 #include "circt/Dialect/Ibis/IbisPasses.h.inc"
28 using namespace circt;
38 mlir::StringAttr portName;
43 PortRefType getType() {
44 return cast<PortRefType>(getPortOp.getPort().getType());
46 Type getInnerType() {
return getType().getPortType(); }
47 Direction getRequestedDirection() {
return getType().getDirection(); }
52 Tunneler(
const IbisTunnelingOptions &options, PathOp op,
58 using PortRefMapping = llvm::MapVector<PortInfo *, Value>;
67 llvm::ArrayRef<PathStepAttr> path,
68 PortRefMapping &mapping);
75 Value portForwardIfNeeded(PortOpInterface actualPort,
PortInfo &portInfo);
82 llvm::ArrayRef<PathStepAttr> path,
83 PortRefMapping &portMapping);
88 FlatSymbolRefAttr tunnelInto,
89 llvm::ArrayRef<PathStepAttr> path,
90 PortRefMapping &portMapping);
93 void genPortNames(llvm::SmallVectorImpl<PortInfo> &portInfos);
96 ConversionPatternRewriter &rewriter;
98 const IbisTunnelingOptions &options;
99 mlir::StringAttr pathName;
100 llvm::SmallVector<PathStepAttr> path;
103 llvm::SmallVector<PortInfo> portInfos;
108 FlatSymbolRefAttr targetName;
111 Tunneler::Tunneler(
const IbisTunnelingOptions &options, PathOp op,
113 : op(op), rewriter(rewriter), ig(ig), options(options) {
114 llvm::copy(op.getPathAsRange(), std::back_inserter(path));
116 "empty paths should never occur - illegal for ibis.path ops");
117 target = path.back();
118 targetName = target.getChild();
121 void Tunneler::genPortNames(llvm::SmallVectorImpl<PortInfo> &portInfos) {
122 std::string pathName;
123 llvm::raw_string_ostream ss(pathName);
125 op.getPathAsRange(), ss,
126 [&](PathStepAttr step) {
127 if (step.getDirection() == PathDirection::Parent)
130 ss << step.getChild().getValue();
138 ? options.writeSuffix
139 : options.readSuffix;
140 pi.portName = rewriter.getStringAttr(pathName +
"_" +
141 pi.portName.getValue() + suffix);
145 LogicalResult Tunneler::go() {
147 for (
auto *user : op.getResult().getUsers()) {
148 auto getPortOp = dyn_cast<GetPortOp>(user);
150 return user->emitOpError() <<
"unknown user of a PathOp result - "
151 "tunneling only supports ibis.get_port";
155 genPortNames(portInfos);
158 ig.
lookup(cast<ModuleOpInterface>(op.getOperation()->getParentOp()));
160 PortRefMapping mapping;
161 if (failed(tunnelDispatch(currentContainer, path, mapping)))
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);
173 rewriter.eraseOp(op);
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);
189 FlatSymbolRefAttr tunnelInto = currentStep.getChild();
190 LogicalResult downRes =
191 tunnelDown(currentContainer, tunnelInto, path, mapping);
198 Value Tunneler::portForwardIfNeeded(PortOpInterface actualPort,
201 cast<PortRefType>(actualPort.getPort().getType()).getDirection();
202 Direction requestedDir = portInfo.getRequestedDirection();
205 if (actualDir == requestedDir)
206 return actualPort.getPort();
209 OpBuilder::InsertionGuard guard(rewriter);
210 rewriter.setInsertionPointAfter(actualPort);
217 auto wireOp = rewriter.create<InputWireOp>(
218 op.getLoc(), rewriter.getStringAttr(*actualPort.getInnerName() +
".wr"),
219 portInfo.getInnerType());
221 rewriter.create<PortWriteOp>(op.getLoc(), actualPort.getPort(),
223 return wireOp.getPort();
231 rewriter.create<PortReadOp>(op.getLoc(), actualPort.getPort());
232 auto wireOp = rewriter.create<OutputWireOp>(
235 rewriter.getStringAttr(*actualPort.getInnerName() +
".rd")),
236 inputValue, rewriter.getStringAttr(actualPort.getNameHint() +
".rd"));
237 return wireOp.getPort();
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)
248 return cast<ContainerInstanceOp>(tunnelInstanceOp);
252 for (
auto instanceOp : parentOp->getRegion(0).getOps<ContainerInstanceOp>()) {
253 if (instanceOp.getInnerSym().getSymName() == name.getValue())
262 FlatSymbolRefAttr tunnelInto,
263 llvm::ArrayRef<PathStepAttr> path,
264 PortRefMapping &portMapping) {
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;
281 rewriter.setInsertionPointAfter(tunnelInstance);
283 auto targetGetPortOp =
284 rewriter.create<GetPortOp>(op.getLoc(), pi.getType(), tunnelInstance,
285 pi.getPortOp.getPortSymbol());
286 portMapping[&pi] = targetGetPortOp.getResult();
293 auto *tunnelScopeNode =
294 ig.
lookup(tunnelInstance.getTargetNameAttr().getName());
295 auto tunnelScope = tunnelScopeNode->
getModule<ScopeOpInterface>();
297 rewriter.setInsertionPointToEnd(tunnelScope.getBodyBlock());
298 llvm::DenseMap<StringAttr, OutputPortOp> outputPortOps;
300 outputPortOps[pi.portName] = rewriter.create<OutputPortOp>(
306 PortRefMapping childMapping;
307 if (failed(tunnelDispatch(tunnelScopeNode, path, childMapping)))
310 for (
auto [pi, res] : childMapping) {
314 rewriter.setInsertionPointToEnd(tunnelScope.getBodyBlock());
315 rewriter.create<PortWriteOp>(op.getLoc(), outputPortOps[portInfo.portName],
320 rewriter.setInsertionPointAfter(tunnelInstance);
321 auto getPortOp = rewriter.create<GetPortOp>(
322 op.getLoc(), tunnelInstance, portInfo.portName, portInfo.getType(),
325 rewriter.create<PortReadOp>(op.getLoc(), getPortOp).getResult();
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";
341 for (
auto *use : currentContainer->
uses()) {
343 ig.
lookup(use->getParent()->getModule());
344 auto parentScope = parentScopeNode->
getModule<ScopeOpInterface>();
345 PortRefMapping targetPortMapping;
350 StringRef targetPortName = pi.getPortOp.getPortSymbol();
351 PortOpInterface portLikeOp = parentScope.lookupPort(targetPortName);
353 return op->emitOpError()
354 <<
"expected a port named " << targetPortName <<
" in "
355 << parentScope.getScopeName() <<
" but found none";
358 targetPortMapping[&pi] = portForwardIfNeeded(portLikeOp, pi);
363 if (failed(tunnelDispatch(parentScopeNode, path, targetPortMapping)))
367 auto instance = use->getInstance<ContainerInstanceOp>();
368 rewriter.setInsertionPointAfter(instance);
370 auto getPortOp = rewriter.create<GetPortOp>(
372 rewriter.create<PortWriteOp>(op.getLoc(), getPortOp,
373 targetPortMapping[&pi]);
378 rewriter.setInsertionPointToEnd(scopeOp.getBodyBlock());
380 auto inputPort = rewriter.create<InputPortOp>(
386 rewriter.create<PortReadOp>(op.getLoc(), inputPort.getResult())
395 TunnelingConversionPattern(MLIRContext *context,
InstanceGraph &ig,
396 IbisTunnelingOptions options)
398 options(std::move(options)) {}
401 matchAndRewrite(PathOp op, OpAdaptor adaptor,
402 ConversionPatternRewriter &rewriter)
const override {
403 return Tunneler(options, op, rewriter, ig).go();
408 IbisTunnelingOptions options;
412 :
public circt::ibis::impl::IbisTunnelingBase<TunnelingPass> {
413 using IbisTunnelingBase<TunnelingPass>::IbisTunnelingBase;
414 void runOnOperation()
override;
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>();
428 patterns.add<TunnelingConversionPattern>(
429 ctx, ig, IbisTunnelingOptions{readSuffix, writeSuffix});
432 applyPartialConversion(getOperation(), target, std::move(
patterns))))
436 std::unique_ptr<Pass>
438 return std::make_unique<TunnelingPass>(options);
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.
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.
Direction
The direction of a Component or Cell port.
std::unique_ptr< mlir::Pass > createTunnelingPass(const IbisTunnelingOptions &={})
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
This holds the name, type, direction of a module's ports.