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