CIRCT  19.0.0git
IbisCleanSelfdrivers.cpp
Go to the documentation of this file.
1 //===- IbisCleanSelfdrivers.cpp -------------------------------------------===//
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 
11 #include "circt/Dialect/HW/HWOps.h"
17 #include "llvm/Support/Debug.h"
18 
19 #include "mlir/Transforms/DialectConversion.h"
20 #include "llvm/ADT/TypeSwitch.h"
21 
22 #define DEBUG_TYPE "ibis-clean-selfdrivers"
23 
24 using namespace circt;
25 using namespace ibis;
26 using namespace circt::igraph;
27 
28 // Returns true if the given input port is self-driven, i.e. there exists
29 // a PortWriteOp that writes to it.
30 static bool isSelfDriven(InputPortOp op) {
31  return llvm::any_of(op->getUsers(), [&](Operation *user) {
32  auto writer = dyn_cast<PortWriteOp>(user);
33  return writer && writer.getPort() == op.getPort();
34  });
35 }
36 
37 namespace {
38 
39 // Rewrites cases where an input port is being read in the instantiating module.
40 // Replaces the input port read by the assignment value of the input port.
41 static LogicalResult replaceReadsOfWrites(ContainerOp containerOp) {
42  // Partition out all of the get_port's wrt. their target port symbol.
43  struct PortAccesses {
44  GetPortOp getAsInput;
45  GetPortOp getAsOutput;
46  llvm::SmallVector<PortReadOp> reads;
47  PortWriteOp writer;
48  };
49  llvm::DenseMap</*instance*/ Value,
50  /*portName*/ llvm::DenseMap<StringAttr, PortAccesses>>
51  instancePortAccessMap;
52 
53  for (auto getPortOp : containerOp.getOps<GetPortOp>()) {
54  PortAccesses &portAccesses =
55  instancePortAccessMap[getPortOp.getInstance()]
56  [getPortOp.getPortSymbolAttr().getAttr()];
57  if (getPortOp.getDirection() == Direction::Input) {
58  if (portAccesses.getAsInput)
59  return containerOp.emitError(
60  "multiple input get_ports - please CSE the input IR");
61  portAccesses.getAsInput = getPortOp;
62  for (auto *user : getPortOp->getUsers()) {
63  if (auto writer = dyn_cast<PortWriteOp>(user)) {
64  if (portAccesses.writer)
65  return getPortOp.emitError(
66  "multiple writers of the same input port");
67  portAccesses.writer = writer;
68  }
69  }
70  } else {
71  if (portAccesses.getAsOutput)
72  return containerOp.emitError(
73  "multiple get_port as output - please CSE the input IR");
74  portAccesses.getAsOutput = getPortOp;
75 
76  for (auto *user : getPortOp->getUsers()) {
77  if (auto reader = dyn_cast<PortReadOp>(user))
78  portAccesses.reads.push_back(reader);
79  }
80  }
81  }
82 
83  for (auto &[instance, portAccessMap] : instancePortAccessMap) {
84  for (auto &[portName, portAccesses] : portAccessMap) {
85  // If the port is not written to, nothing to do.
86  if (!portAccesses.writer)
87  continue;
88 
89  // if the port is not read, nothing to do.
90  if (!portAccesses.getAsOutput)
91  continue;
92 
93  // If the input port is self-driven, we need to replace all reads of the
94  // input port with the value that is being written to it.
95  LLVM_DEBUG(llvm::dbgs() << "Writer is: " << portAccesses.writer << "\n";);
96  for (auto reader : portAccesses.reads) {
97  LLVM_DEBUG(llvm::dbgs() << "Replacing: " << reader << "\n";);
98  reader.replaceAllUsesWith(portAccesses.writer.getValue());
99  reader.erase();
100  }
101  portAccesses.getAsOutput.erase();
102  }
103  }
104 
105  return success();
106 }
107 
108 struct InputPortOpConversionPattern : public OpConversionPattern<InputPortOp> {
109  InputPortOpConversionPattern(MLIRContext *context, InstanceGraph &ig)
110  : OpConversionPattern<InputPortOp>(context), ig(ig) {}
111 
112  LogicalResult
113  matchAndRewrite(InputPortOp op, OpAdaptor adaptor,
114  ConversionPatternRewriter &rewriter) const override {
115  // Locate the readers and writers of the port.
116  PortWriteOp writer = nullptr;
117  llvm::SmallVector<PortReadOp> readers;
118  for (auto *user : op->getUsers()) {
119  auto res = llvm::TypeSwitch<Operation *, LogicalResult>(user)
120  .Case<PortWriteOp>([&](auto op) {
121  if (writer)
122  return rewriter.notifyMatchFailure(
123  user, "found multiple drivers of the self-driven "
124  "input port");
125  writer = op;
126  return success();
127  })
128  .Case<PortReadOp>([&](auto op) {
129  readers.push_back(op);
130  return success();
131  })
132  .Default([&](auto) {
133  return rewriter.notifyMatchFailure(
134  user, "unhandled user of the self-driven "
135  "input port");
136  });
137 
138  if (failed(res))
139  return failure();
140  }
141 
142  // Create a `hw.wire` to ensure that the input port name is maintained.
143  auto wire = rewriter.create<hw::WireOp>(op.getLoc(), writer.getValue(),
144  op.getInnerSymAttrName());
145 
146  // Replace all reads of the input port with the wire.
147  for (auto reader : readers)
148  rewriter.replaceOp(reader, wire);
149 
150  // Since Ibis allows for input ports to be read from outside the container,
151  // we need to check the instance graph to see whether this is the case.
152  // If so, we need to add an output port to the container and connect it to
153  // the assigned value.
154  auto parentModuleOp = dyn_cast<ModuleOpInterface>(op->getParentOp());
155  if (parentModuleOp) {
156  InstanceGraphNode *node = ig.lookup(parentModuleOp);
157  bool anyOutsideReads = llvm::any_of(node->uses(), [&](auto use) {
158  Block *userBlock = use->getInstance()->getBlock();
159  for (auto getPortOp : userBlock->getOps<GetPortOp>()) {
160  if (getPortOp.getPortSymbol() == op.getPortName())
161  return true;
162  }
163  return false;
164  });
165 
166  if (anyOutsideReads) {
167  auto outputPort = rewriter.create<OutputPortOp>(
168  op.getLoc(), op.getNameAttr(), op.getInnerSym(), op.getType());
169  rewriter.create<PortWriteOp>(op.getLoc(), outputPort, wire);
170  }
171  }
172 
173  // Finally, erase the writer and input port.
174  rewriter.eraseOp(op);
175  rewriter.eraseOp(writer);
176  return success();
177  }
178 
179 protected:
180  InstanceGraph &ig;
181 };
182 
183 struct CleanSelfdriversPass
184  : public IbisCleanSelfdriversBase<CleanSelfdriversPass> {
185  void runOnOperation() override;
186 
187  LogicalResult cleanInstanceSide();
188  LogicalResult cleanContainerSide();
189 };
190 } // anonymous namespace
191 
192 LogicalResult CleanSelfdriversPass::cleanInstanceSide() {
193  for (ContainerOp containerOp : getOperation().getOps<ContainerOp>())
194  if (failed(replaceReadsOfWrites(containerOp)))
195  return failure();
196 
197  return success();
198 }
199 
200 LogicalResult CleanSelfdriversPass::cleanContainerSide() {
201  auto *ctx = &getContext();
202  ConversionTarget target(*ctx);
203  target.addLegalDialect<IbisDialect>();
204  target.addLegalOp<hw::WireOp>();
205  target.addDynamicallyLegalOp<InputPortOp>(
206  [](InputPortOp op) { return !isSelfDriven(op); });
207 
208  auto &ig = getAnalysis<InstanceGraph>();
209  RewritePatternSet patterns(ctx);
210  patterns.add<InputPortOpConversionPattern>(ctx, ig);
211 
212  if (failed(
213  applyPartialConversion(getOperation(), target, std::move(patterns))))
214  return failure();
215 
216  return success();
217 }
218 
219 void CleanSelfdriversPass::runOnOperation() {
220  if (failed(cleanInstanceSide()) || failed(cleanContainerSide()))
221  return signalPassFailure();
222 }
223 
225  return std::make_unique<CleanSelfdriversPass>();
226 }
@ Input
Definition: HW.h:35
static bool isSelfDriven(InputPortOp op)
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
std::unique_ptr< mlir::Pass > createCleanSelfdriversPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21