18 #include "mlir/IR/OperationSupport.h"
19 #include "mlir/Transforms/DialectConversion.h"
20 #include "llvm/ADT/TypeSwitch.h"
22 using namespace circt;
29 struct ContainerPortInfo {
30 std::unique_ptr<hw::ModulePortInfo> hwPorts;
33 llvm::DenseMap<StringAttr, InputPortOp> opInputs;
36 llvm::DenseMap<StringAttr, OutputPortOp> opOutputs;
39 llvm::DenseMap<StringAttr, StringAttr> portSymbolsToPortName;
41 ContainerPortInfo() =
default;
42 ContainerPortInfo(ContainerOp container) {
44 auto *ctx = container.getContext();
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()))
57 attrs.push_back(namedAttr);
65 for (
auto input : container.getBodyBlock()->getOps<InputPortOp>()) {
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();
74 portInfo.attrs = copyPortAttrs(input);
75 inputs.push_back(portInfo);
78 for (
auto output : container.getBodyBlock()->getOps<OutputPortOp>()) {
81 opOutputs[uniquePortName] = output;
83 hw::PortInfo portInfo;
84 portInfo.name = uniquePortName;
85 portSymbolsToPortName[output.getInnerSym().getSymName()] = uniquePortName;
87 cast<PortOpInterface>(output.getOperation()).getPortType();
89 portInfo.attrs = copyPortAttrs(output);
92 hwPorts = std::make_unique<hw::ModulePortInfo>(
inputs,
outputs);
96 using ContainerPortInfoMap =
97 llvm::DenseMap<hw::InnerRefAttr, ContainerPortInfo>;
98 using ContainerHWModSymbolMap = llvm::DenseMap<hw::InnerRefAttr, StringAttr>;
100 static StringAttr concatNames(hw::InnerRefAttr ref) {
101 return StringAttr::get(ref.getContext(), ref.getModule().getValue() +
"_" +
102 ref.getName().getValue());
106 ContainerOpConversionPattern(MLIRContext *ctx,
107 ContainerPortInfoMap &portOrder,
108 ContainerHWModSymbolMap &modSymMap)
110 modSymMap(modSymMap) {}
113 matchAndRewrite(ContainerOp op, OpAdaptor adaptor,
114 ConversionPatternRewriter &rewriter)
const override {
115 auto design = op->getParentOfType<DesignOp>();
116 rewriter.setInsertionPoint(design);
119 StringAttr hwmodName;
120 if (op.getIsTopLevel())
121 hwmodName = op.getInnerNameAttr();
123 hwmodName = concatNames(op.getInnerRef());
125 const ContainerPortInfo &cpi = portOrder.at(op.getInnerRef());
127 rewriter.create<
hw::HWModuleOp>(op.getLoc(), hwmodName, *cpi.hwPorts);
128 modSymMap[op.getInnerRef()] = hwMod.getSymNameAttr();
130 hw::OutputOp outputOp =
131 cast<hw::OutputOp>(hwMod.getBodyBlock()->getTerminator());
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);
138 for (
auto *user : inputPort.getOperation()->getUsers()) {
139 auto reader = dyn_cast<PortReadOp>(user);
141 return rewriter.notifyMatchFailure(
142 user,
"expected only ibis.port.read ops of the input port");
144 rewriter.replaceOp(reader, barg);
147 rewriter.eraseOp(inputPort);
151 llvm::SmallVector<Value> outputValues;
152 for (
auto [idx, output] : llvm::enumerate(cpi.hwPorts->getOutputs())) {
153 auto outputPort = cpi.opOutputs.at(output.name);
155 auto users = outputPort->getUsers();
156 size_t nUsers = std::distance(users.begin(), users.end());
158 return outputPort->emitOpError()
159 <<
"expected exactly one ibis.port.write op of the output "
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);
168 rewriter.mergeBlocks(&op.getBodyRegion().front(), hwMod.getBodyBlock());
171 rewriter.eraseOp(outputOp);
172 rewriter.setInsertionPointToEnd(hwMod.getBodyBlock());
173 outputOp = rewriter.create<hw::OutputOp>(op.getLoc(), outputValues);
174 rewriter.eraseOp(op);
178 ContainerPortInfoMap &portOrder;
179 ContainerHWModSymbolMap &modSymMap;
183 ThisOpConversionPattern(MLIRContext *ctx)
187 matchAndRewrite(ThisOp op, OpAdaptor adaptor,
188 ConversionPatternRewriter &rewriter)
const override {
190 rewriter.eraseOp(op);
195 struct ContainerInstanceOpConversionPattern
198 ContainerInstanceOpConversionPattern(MLIRContext *ctx,
199 ContainerPortInfoMap &portOrder,
200 ContainerHWModSymbolMap &modSymMap)
202 modSymMap(modSymMap) {}
205 matchAndRewrite(ContainerInstanceOp op, OpAdaptor adaptor,
206 ConversionPatternRewriter &rewriter)
const override {
207 rewriter.setInsertionPoint(op);
208 llvm::SmallVector<Value> operands;
210 const ContainerPortInfo &cpi =
211 portOrder.at(op.getResult().getType().getScopeRef());
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);
220 return rewriter.notifyMatchFailure(
221 user,
"expected only ibis.get_port op usage of the instance");
223 for (
auto *user :
getPort->getUsers()) {
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()),
232 return rewriter.notifyMatchFailure(
233 read,
"expected only one ibis.port.read op of the "
237 .Case<PortWriteOp>([&](
auto write) {
238 auto [it, inserted] = inputWritesToUse.insert(
239 {cpi.portSymbolsToPortName.at(
240 getPort.getPortSymbolAttr().getAttr()),
243 return rewriter.notifyMatchFailure(
245 "expected only one ibis.port.write op of the input "
249 .Default([&](
auto op) {
250 return rewriter.notifyMatchFailure(
251 op,
"expected only ibis.port.read or ibis.port.write ops "
258 getPortsToErase.push_back(
getPort);
262 size_t nInputPorts = std::distance(cpi.hwPorts->getInputs().begin(),
263 cpi.hwPorts->getInputs().end());
264 if (nInputPorts != inputWritesToUse.size()) {
266 llvm::raw_string_ostream ers(errMsg);
267 ers <<
"Error when lowering instance ";
268 op.print(ers, mlir::OpPrintingFlags().printGenericOpForm());
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";
276 return rewriter.notifyMatchFailure(op, errMsg);
278 for (
auto input : cpi.hwPorts->getInputs()) {
279 auto writeOp = inputWritesToUse.at(input.name);
280 operands.push_back(writeOp.getValue());
281 rewriter.eraseOp(writeOp);
285 llvm::SmallVector<Type> retTypes;
286 for (
auto output : cpi.hwPorts->getOutputs())
287 retTypes.push_back(output.type);
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; });
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 rewriter.getArrayAttr({}),
nullptr);
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())
316 outputReadIt->second.getResult().replaceAllUsesWith(value);
317 rewriter.eraseOp(outputReadIt->second);
321 for (
auto *
getPort : getPortsToErase)
325 rewriter.eraseOp(op);
329 ContainerPortInfoMap &portOrder;
330 ContainerHWModSymbolMap &modSymMap;
333 struct ContainersToHWPass :
public IbisContainersToHWBase<ContainersToHWPass> {
334 void runOnOperation()
override;
338 void ContainersToHWPass::runOnOperation() {
339 auto *ctx = &getContext();
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));
348 ConversionTarget target(*ctx);
349 ContainerHWModSymbolMap modSymMap;
350 target.addIllegalOp<ContainerOp, ContainerInstanceOp, ThisOp>();
351 target.markUnknownOpDynamicallyLegal([](Operation *) {
return true; });
357 target.addLegalDialect<IbisDialect>();
361 .add<ContainerOpConversionPattern, ContainerInstanceOpConversionPattern>(
362 ctx, portOrder, modSymMap);
363 patterns.add<ThisOpConversionPattern>(ctx);
366 applyPartialConversion(getOperation(), target, std::move(
patterns))))
371 llvm::make_early_inc_range(getOperation().getOps<DesignOp>()))
372 if (design.getBody().front().empty())
377 return std::make_unique<ContainersToHWPass>();
static PortInfo getPort(ModuleTy &mod, size_t idx)
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.
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
std::unique_ptr< mlir::Pass > createContainersToHWPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.