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