CIRCT 20.0.0git
Loading...
Searching...
No Matches
KanagawaContainersToHW.cpp
Go to the documentation of this file.
1//===- KanagawaContainersToHW.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
18
20#include "mlir/IR/OperationSupport.h"
21#include "mlir/Transforms/DialectConversion.h"
22#include "llvm/ADT/TypeSwitch.h"
23
24namespace circt {
25namespace kanagawa {
26#define GEN_PASS_DEF_KANAGAWACONTAINERSTOHW
27#include "circt/Dialect/Kanagawa/KanagawaPasses.h.inc"
28} // namespace kanagawa
29} // namespace circt
30
31using namespace circt;
32using namespace kanagawa;
33
34namespace {
35
36// Analysis result for generating the port interface of a container + a bit of
37// port op caching.
38struct ContainerPortInfo {
39 std::unique_ptr<hw::ModulePortInfo> hwPorts;
40
41 // A mapping between the port name and the port op within the container.
42 llvm::DenseMap<StringAttr, InputPortOp> opInputs;
43
44 // A mapping between the port name and the port op within the container.
45 llvm::DenseMap<StringAttr, OutputPortOp> opOutputs;
46
47 // A mapping between port symbols and their corresponding port name.
48 llvm::DenseMap<StringAttr, StringAttr> portSymbolsToPortName;
49
50 ContainerPortInfo() = default;
51 ContainerPortInfo(ContainerOp container) {
52 SmallVector<hw::PortInfo, 4> inputs, outputs;
53 auto *ctx = container.getContext();
54
55 // Copies all attributes from a port, except for the port symbol, name, and
56 // type.
57 auto copyPortAttrs = [ctx](auto port) {
58 llvm::DenseSet<StringAttr> elidedAttrs;
59 elidedAttrs.insert(port.getInnerSymAttrName());
60 elidedAttrs.insert(port.getTypeAttrName());
61 elidedAttrs.insert(port.getNameAttrName());
62 llvm::SmallVector<NamedAttribute> attrs;
63 for (NamedAttribute namedAttr : port->getAttrs()) {
64 if (elidedAttrs.contains(namedAttr.getName()))
65 continue;
66 attrs.push_back(namedAttr);
67 }
68 return DictionaryAttr::get(ctx, attrs);
69 };
70
71 // Gather in and output port ops to define the hw.module interface. Here, we
72 // also perform uniqueing of the port names.
73 Namespace portNs;
74 for (auto input : container.getBodyBlock()->getOps<InputPortOp>()) {
75 auto uniquePortName =
76 StringAttr::get(ctx, portNs.newName(input.getNameHint()));
77 opInputs[uniquePortName] = input;
78 hw::PortInfo portInfo;
79 portInfo.name = uniquePortName;
80 portSymbolsToPortName[input.getInnerSym().getSymName()] = uniquePortName;
81 portInfo.type = cast<PortOpInterface>(input.getOperation()).getPortType();
82 portInfo.dir = hw::ModulePort::Direction::Input;
83 portInfo.attrs = copyPortAttrs(input);
84 inputs.push_back(portInfo);
85 }
86
87 for (auto output : container.getBodyBlock()->getOps<OutputPortOp>()) {
88 auto uniquePortName =
89 StringAttr::get(ctx, portNs.newName(output.getNameAttr().getValue()));
90 opOutputs[uniquePortName] = output;
91
92 hw::PortInfo portInfo;
93 portInfo.name = uniquePortName;
94 portSymbolsToPortName[output.getInnerSym().getSymName()] = uniquePortName;
95 portInfo.type =
96 cast<PortOpInterface>(output.getOperation()).getPortType();
97 portInfo.dir = hw::ModulePort::Direction::Output;
98 portInfo.attrs = copyPortAttrs(output);
99 outputs.push_back(portInfo);
100 }
101 hwPorts = std::make_unique<hw::ModulePortInfo>(inputs, outputs);
102 }
103};
104
105using ContainerPortInfoMap =
106 llvm::DenseMap<hw::InnerRefAttr, ContainerPortInfo>;
107using ContainerHWModSymbolMap = llvm::DenseMap<hw::InnerRefAttr, StringAttr>;
108
109static StringAttr concatNames(mlir::StringAttr lhs, mlir::StringAttr rhs) {
110 return StringAttr::get(lhs.getContext(), lhs.strref() + "_" + rhs.strref());
111}
112
113struct ContainerOpConversionPattern : public OpConversionPattern<ContainerOp> {
114 ContainerOpConversionPattern(MLIRContext *ctx, Namespace &modNamespace,
115 ContainerPortInfoMap &portOrder,
116 ContainerHWModSymbolMap &modSymMap)
117 : OpConversionPattern<ContainerOp>(ctx), modNamespace(modNamespace),
118 portOrder(portOrder), modSymMap(modSymMap) {}
119
120 LogicalResult
121 matchAndRewrite(ContainerOp op, OpAdaptor adaptor,
122 ConversionPatternRewriter &rewriter) const override {
123 auto design = op->getParentOfType<DesignOp>();
124 rewriter.setInsertionPoint(design);
125
126 // Generate and de-alias the hw.module name.
127 // If the container is a top level container, ignore the design name.
128 StringAttr hwmodName;
129 if (op.getIsTopLevel())
130 hwmodName = op.getNameHintAttr();
131 else
132 hwmodName =
133 concatNames(op.getInnerRef().getModule(), op.getNameHintAttr());
134
135 hwmodName = StringAttr::get(op.getContext(),
136 modNamespace.newName(hwmodName.getValue()));
137
138 const ContainerPortInfo &cpi = portOrder.at(op.getInnerRef());
139 auto hwMod =
140 rewriter.create<hw::HWModuleOp>(op.getLoc(), hwmodName, *cpi.hwPorts);
141 modSymMap[op.getInnerRef()] = hwMod.getSymNameAttr();
142
143 hw::OutputOp outputOp =
144 cast<hw::OutputOp>(hwMod.getBodyBlock()->getTerminator());
145
146 // Replace all of the reads of the inputs to use the input block arguments.
147 for (auto [idx, input] : llvm::enumerate(cpi.hwPorts->getInputs())) {
148 Value barg = hwMod.getBodyBlock()->getArgument(idx);
149 InputPortOp inputPort = cpi.opInputs.at(input.name);
150 // Replace all reads of the input port with the input block argument.
151 for (auto *user : inputPort.getOperation()->getUsers()) {
152 auto reader = dyn_cast<PortReadOp>(user);
153 if (!reader)
154 return rewriter.notifyMatchFailure(
155 user, "expected only kanagawa.port.read ops of the input port");
156
157 rewriter.replaceOp(reader, barg);
158 }
159
160 rewriter.eraseOp(inputPort);
161 }
162
163 // Adjust the hw.output op to use kanagawa.port.write values
164 llvm::SmallVector<Value> outputValues;
165 for (auto [idx, output] : llvm::enumerate(cpi.hwPorts->getOutputs())) {
166 auto outputPort = cpi.opOutputs.at(output.name);
167 // Locate the write to the output op.
168 auto users = outputPort->getUsers();
169 size_t nUsers = std::distance(users.begin(), users.end());
170 if (nUsers != 1)
171 return outputPort->emitOpError()
172 << "expected exactly one kanagawa.port.write op of the output "
173 "port: "
174 << output.name.str() << " found: " << nUsers;
175 auto writer = cast<PortWriteOp>(*users.begin());
176 outputValues.push_back(writer.getValue());
177 rewriter.eraseOp(outputPort);
178 rewriter.eraseOp(writer);
179 }
180
181 rewriter.mergeBlocks(&op.getBodyRegion().front(), hwMod.getBodyBlock());
182
183 // Rewrite the hw.output op.
184 rewriter.eraseOp(outputOp);
185 rewriter.setInsertionPointToEnd(hwMod.getBodyBlock());
186 outputOp = rewriter.create<hw::OutputOp>(op.getLoc(), outputValues);
187 rewriter.eraseOp(op);
188 return success();
189 }
190
191 Namespace &modNamespace;
192 ContainerPortInfoMap &portOrder;
193 ContainerHWModSymbolMap &modSymMap;
194};
195
196struct ContainerInstanceOpConversionPattern
197 : public OpConversionPattern<ContainerInstanceOp> {
198
199 ContainerInstanceOpConversionPattern(MLIRContext *ctx,
200 ContainerPortInfoMap &portOrder,
201 ContainerHWModSymbolMap &modSymMap)
202 : OpConversionPattern<ContainerInstanceOp>(ctx), portOrder(portOrder),
203 modSymMap(modSymMap) {}
204
205 LogicalResult
206 matchAndRewrite(ContainerInstanceOp op, OpAdaptor adaptor,
207 ConversionPatternRewriter &rewriter) const override {
208 rewriter.setInsertionPoint(op);
209 llvm::SmallVector<Value> operands;
210
211 const ContainerPortInfo &cpi =
212 portOrder.at(op.getResult().getType().getScopeRef());
213
214 // Gather the get_port ops that target the instance
215 llvm::DenseMap<StringAttr, PortReadOp> outputReadsToReplace;
216 llvm::DenseMap<StringAttr, PortWriteOp> inputWritesToUse;
217 llvm::SmallVector<Operation *> getPortsToErase;
218 for (auto *user : op->getUsers()) {
219 auto getPort = dyn_cast<GetPortOp>(user);
220 if (!getPort)
221 return rewriter.notifyMatchFailure(
222 user, "expected only kanagawa.get_port op usage of the instance");
223
224 for (auto *user : getPort->getUsers()) {
225 auto res =
226 llvm::TypeSwitch<Operation *, LogicalResult>(user)
227 .Case<PortReadOp>([&](auto read) {
228 auto [it, inserted] = outputReadsToReplace.insert(
229 {cpi.portSymbolsToPortName.at(
230 getPort.getPortSymbolAttr().getAttr()),
231 read});
232 if (!inserted)
233 return rewriter.notifyMatchFailure(
234 read, "expected only one kanagawa.port.read op of the "
235 "output port");
236 return success();
237 })
238 .Case<PortWriteOp>([&](auto write) {
239 auto [it, inserted] = inputWritesToUse.insert(
240 {cpi.portSymbolsToPortName.at(
241 getPort.getPortSymbolAttr().getAttr()),
242 write});
243 if (!inserted)
244 return rewriter.notifyMatchFailure(
245 write,
246 "expected only one kanagawa.port.write op of the input "
247 "port");
248 return success();
249 })
250 .Default([&](auto op) {
251 return rewriter.notifyMatchFailure(
252 op, "expected only kanagawa.port.read or "
253 "kanagawa.port.write ops "
254 "of the "
255 "instance");
256 });
257 if (failed(res))
258 return failure();
259 }
260 getPortsToErase.push_back(getPort);
261 }
262
263 // Grab the operands in the order of the hw.module ports.
264 size_t nInputPorts = std::distance(cpi.hwPorts->getInputs().begin(),
265 cpi.hwPorts->getInputs().end());
266 if (nInputPorts != inputWritesToUse.size()) {
267 std::string errMsg;
268 llvm::raw_string_ostream ers(errMsg);
269 ers << "Error when lowering instance ";
270 op.print(ers, mlir::OpPrintingFlags().printGenericOpForm());
271
272 ers << "\nexpected exactly one kanagawa.port.write op of each input "
273 "port. "
274 "Mising port assignments were:\n";
275 for (auto input : cpi.hwPorts->getInputs()) {
276 if (inputWritesToUse.find(input.name) == inputWritesToUse.end())
277 ers << "\t" << input.name << "\n";
278 }
279 return rewriter.notifyMatchFailure(op, errMsg);
280 }
281 for (auto input : cpi.hwPorts->getInputs()) {
282 auto writeOp = inputWritesToUse.at(input.name);
283 operands.push_back(writeOp.getValue());
284 rewriter.eraseOp(writeOp);
285 }
286
287 // Determine the result types.
288 llvm::SmallVector<Type> retTypes;
289 for (auto output : cpi.hwPorts->getOutputs())
290 retTypes.push_back(output.type);
291
292 // Gather arg and res names
293 // TODO: @mortbopet - this should be part of ModulePortInfo
294 llvm::SmallVector<Attribute> argNames, resNames;
295 llvm::transform(cpi.hwPorts->getInputs(), std::back_inserter(argNames),
296 [](auto port) { return port.name; });
297 llvm::transform(cpi.hwPorts->getOutputs(), std::back_inserter(resNames),
298 [](auto port) { return port.name; });
299
300 // Create the hw.instance op.
301 StringRef moduleName = modSymMap[op.getTargetNameAttr()];
302 auto hwInst = rewriter.create<hw::InstanceOp>(
303 op.getLoc(), retTypes, op.getInnerSym().getSymName(), moduleName,
304 operands, rewriter.getArrayAttr(argNames),
305 rewriter.getArrayAttr(resNames),
306 /*parameters*/ rewriter.getArrayAttr({}), /*innerSym*/ nullptr);
307
308 // Replace the reads of the output ports with the hw.instance results.
309 for (auto [output, value] :
310 llvm::zip(cpi.hwPorts->getOutputs(), hwInst.getResults())) {
311 auto outputReadIt = outputReadsToReplace.find(output.name);
312 if (outputReadIt == outputReadsToReplace.end())
313 continue;
314 // TODO: RewriterBase::replaceAllUsesWith is not currently supported by
315 // DialectConversion. Using it may lead to assertions about mutating
316 // replaced/erased ops. For now, do this RAUW directly, until
317 // ConversionPatternRewriter properly supports RAUW.
318 // See https://github.com/llvm/circt/issues/6795.
319 outputReadIt->second.getResult().replaceAllUsesWith(value);
320 rewriter.eraseOp(outputReadIt->second);
321 }
322
323 // Erase the get_port ops.
324 for (auto *getPort : getPortsToErase)
325 rewriter.eraseOp(getPort);
326
327 // And finally erase the instance op.
328 rewriter.eraseOp(op);
329 return success();
330 }
331
332 ContainerPortInfoMap &portOrder;
333 ContainerHWModSymbolMap &modSymMap;
334}; // namespace
335
336struct ContainersToHWPass
337 : public circt::kanagawa::impl::KanagawaContainersToHWBase<
338 ContainersToHWPass> {
339 void runOnOperation() override;
340};
341} // anonymous namespace
342
343void ContainersToHWPass::runOnOperation() {
344 auto *ctx = &getContext();
345
346 // Generate module signatures.
347 ContainerPortInfoMap portOrder;
348 for (auto design : getOperation().getOps<DesignOp>())
349 for (auto container : design.getOps<ContainerOp>())
350 portOrder.try_emplace(container.getInnerRef(),
351 ContainerPortInfo(container));
352
353 ConversionTarget target(*ctx);
354 ContainerHWModSymbolMap modSymMap;
355 SymbolCache modSymCache;
356 modSymCache.addDefinitions(getOperation());
357 Namespace modNamespace;
358 modNamespace.add(modSymCache);
359 target.addIllegalOp<ContainerOp, ContainerInstanceOp>();
360 target.markUnknownOpDynamicallyLegal([](Operation *) { return true; });
361
362 // Remove the name of the kanagawa.design's from the namespace - The
363 // kanagawa.design op will be removed after this pass, and there may be
364 // kanagawa.component's inside the design that have the same name as the
365 // design; we want that name to persist, and not be falsely considered a
366 // duplicate.
367 for (auto designOp : getOperation().getOps<DesignOp>())
368 modNamespace.erase(designOp.getSymName());
369
370 // Parts of the conversion patterns will update operations in place, which in
371 // turn requires the updated operations to be legalizeable. These in-place ops
372 // also include kanagawa ops that eventually will get replaced once all of the
373 // patterns apply.
374 target.addLegalDialect<KanagawaDialect>();
375
376 RewritePatternSet patterns(ctx);
377 patterns.add<ContainerOpConversionPattern>(ctx, modNamespace, portOrder,
378 modSymMap);
379 patterns.add<ContainerInstanceOpConversionPattern>(ctx, portOrder, modSymMap);
380
381 if (failed(
382 applyPartialConversion(getOperation(), target, std::move(patterns))))
383 signalPassFailure();
384
385 // Delete empty design ops.
386 for (auto design :
387 llvm::make_early_inc_range(getOperation().getOps<DesignOp>()))
388 if (design.getBody().front().empty())
389 design.erase();
390}
391
393 return std::make_unique<ContainersToHWPass>();
394}
static PortInfo getPort(ModuleTy &mod, size_t idx)
Definition HWOps.cpp:1440
static InstancePath empty
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
void add(mlir::ModuleOp module)
Definition Namespace.h:48
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:85
void addDefinitions(mlir::Operation *top)
Populate the symbol cache with all symbol-defining operations within the 'top' operation.
Definition SymCache.cpp:23
Default symbol cache implementation; stores associations between names (StringAttr's) to mlir::Operat...
Definition SymCache.h:85
std::unique_ptr< mlir::Pass > createContainersToHWPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
mlir::Type type
Definition HWTypes.h:31
mlir::StringAttr name
Definition HWTypes.h:30
This holds the name, type, direction of a module's ports.
DictionaryAttr attrs
The optional symbol for this port.