CIRCT  19.0.0git
ESILowerBundles.cpp
Go to the documentation of this file.
1 //===- ESILowerBundles.cpp - Lower ESI bundles pass -------------*- C++ -*-===//
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 
12 #include "circt/Dialect/HW/HWOps.h"
15 #include "circt/Support/LLVM.h"
16 #include "circt/Support/SymCache.h"
17 
18 #include "mlir/Transforms/GreedyPatternRewriteDriver.h"
19 
20 namespace circt {
21 namespace esi {
22 #define GEN_PASS_DEF_LOWERESIBUNDLES
23 #include "circt/Dialect/ESI/ESIPasses.h.inc"
24 } // namespace esi
25 } // namespace circt
26 
27 using namespace circt;
28 using namespace circt::esi;
29 using namespace circt::esi::detail;
30 using namespace circt::hw;
31 
32 namespace {
33 
34 /// Lower channel bundles into the constituent channels. The workhorse of this
35 /// pass. Works by adding channel ports, using [un]pack operations to recreate
36 /// the original value. (Pretty standard in MLIR for type conversions.) The new
37 /// [un]pack operations get lowered away later on.
38 class BundlePort : public PortConversion {
39 public:
40  BundlePort(PortConverterImpl &converter, hw::PortInfo origPort)
41  : PortConversion(converter, origPort) {}
42 
43 protected:
44  // Modifies the instance signals.
45  void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue,
46  SmallVectorImpl<Value> &newOperands,
47  ArrayRef<Backedge> newResults) override;
48  void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue,
49  SmallVectorImpl<Value> &newOperands,
50  ArrayRef<Backedge> newResults) override;
51 
52  // Modifies the module ports.
53  void buildInputSignals() override;
54  void buildOutputSignals() override;
55 
56 private:
57  SmallVector<hw::PortInfo, 4> newInputChannels;
58  SmallVector<hw::PortInfo, 4> newOutputChannels;
59 };
60 
61 class ESIBundleConversionBuilder : public PortConversionBuilder {
62 public:
64  FailureOr<std::unique_ptr<PortConversion>> build(hw::PortInfo port) override {
65  return llvm::TypeSwitch<Type, FailureOr<std::unique_ptr<PortConversion>>>(
66  port.type)
67  .Case([&](esi::ChannelBundleType)
68  -> FailureOr<std::unique_ptr<PortConversion>> {
69  return {std::make_unique<BundlePort>(converter, port)};
70  })
71  .Default([&](auto) { return PortConversionBuilder::build(port); });
72  }
73 };
74 } // namespace
75 
76 /// When replacing an instance with an input bundle, we must unpack the
77 /// individual channels and feed/consume them into/from the new instance.
78 void BundlePort::mapInputSignals(OpBuilder &b, Operation *inst, Value,
79  SmallVectorImpl<Value> &newOperands,
80  ArrayRef<Backedge> newResults) {
81  // Assemble the operands/result types and build the op.
82  SmallVector<Value, 4> fromChannels(
83  llvm::map_range(newOutputChannels, [&](hw::PortInfo port) {
84  return newResults[port.argNum];
85  }));
86  SmallVector<Type, 5> toChannelTypes(llvm::map_range(
87  newInputChannels, [](hw::PortInfo port) { return port.type; }));
88  auto unpack = b.create<UnpackBundleOp>(
89  origPort.loc,
90  /*bundle=*/inst->getOperand(origPort.argNum), fromChannels);
91 
92  // Connect the new instance inputs to the results of the unpack.
93  for (auto [idx, inPort] : llvm::enumerate(newInputChannels))
94  newOperands[inPort.argNum] = unpack.getResult(idx);
95 }
96 
97 /// When replacing an instance with an output bundle, we must pack the
98 /// individual channels in a bundle to recreate the original Value.
99 void BundlePort::mapOutputSignals(OpBuilder &b, Operation *inst, Value,
100  SmallVectorImpl<Value> &newOperands,
101  ArrayRef<Backedge> newResults) {
102  // Assemble the operands/result types and build the op.
103  SmallVector<Value, 4> toChannels(
104  llvm::map_range(newOutputChannels, [&](hw::PortInfo port) {
105  return newResults[port.argNum];
106  }));
107  SmallVector<Type, 5> fromChannelTypes(llvm::map_range(
108  newInputChannels, [](hw::PortInfo port) { return port.type; }));
109  auto pack = b.create<PackBundleOp>(
110  origPort.loc, cast<ChannelBundleType>(origPort.type), toChannels);
111 
112  // Feed the fromChannels into the new instance.
113  for (auto [idx, inPort] : llvm::enumerate(newInputChannels))
114  newOperands[inPort.argNum] = pack.getFromChannels()[idx];
115  // Replace the users of the old bundle Value with the new one.
116  inst->getResult(origPort.argNum).replaceAllUsesWith(pack.getBundle());
117 }
118 
119 /// When replacing an instance with an input bundle, we must unpack the
120 /// bundle into its individual channels.
121 void BundlePort::buildInputSignals() {
122  auto bundleType = cast<ChannelBundleType>(origPort.type);
123  SmallVector<Value, 4> newInputValues;
124  SmallVector<BundledChannel, 4> outputChannels;
125 
126  for (BundledChannel ch : bundleType.getChannels()) {
127  // 'to' on an input bundle becomes an input channel.
128  if (ch.direction == ChannelDirection::to) {
129  hw::PortInfo newPort;
130  newInputValues.push_back(converter.createNewInput(
131  origPort, "_" + ch.name.getValue(), ch.type, newPort));
132  newInputChannels.push_back(newPort);
133  } else {
134  // 'from' on an input bundle becomes an output channel.
135  outputChannels.push_back(ch);
136  }
137  }
138 
139  // On an input port, new channels must be packed to recreate the original
140  // Value.
141  PackBundleOp pack;
142  if (body) {
143  ImplicitLocOpBuilder b(origPort.loc, body, body->begin());
144  pack = b.create<PackBundleOp>(bundleType, newInputValues);
145  body->getArgument(origPort.argNum).replaceAllUsesWith(pack.getBundle());
146  }
147 
148  // Build new ports and put the new port info directly into the member
149  // variable.
150  newOutputChannels.resize(outputChannels.size());
151  for (auto [idx, ch] : llvm::enumerate(outputChannels))
152  converter.createNewOutput(origPort, "_" + ch.name.getValue(), ch.type,
153  pack ? pack.getFromChannels()[idx] : nullptr,
154  newOutputChannels[idx]);
155 }
156 
157 /// For an output port, we need to unpack the results from the original value
158 /// into the new channel ports.
159 void BundlePort::buildOutputSignals() {
160  auto bundleType = cast<ChannelBundleType>(origPort.type);
161  SmallVector<Value, 4> unpackChannels;
162  SmallVector<BundledChannel, 4> outputChannels;
163 
164  SmallVector<Type, 4> unpackOpResultTypes;
165  for (BundledChannel ch : bundleType.getChannels()) {
166  // 'from' on an input bundle becomes an input channel.
167  if (ch.direction == ChannelDirection::from) {
168  hw::PortInfo newPort;
169  unpackChannels.push_back(converter.createNewInput(
170  origPort, "_" + ch.name.getValue(), ch.type, newPort));
171  newInputChannels.push_back(newPort);
172  } else {
173  // 'to' on an input bundle becomes an output channel.
174  unpackOpResultTypes.push_back(ch.type);
175  outputChannels.push_back(ch);
176  }
177  }
178 
179  // For an output port, the original bundle must be unpacked into the
180  // individual channel ports.
181  UnpackBundleOp unpack;
182  if (body)
183  unpack = OpBuilder::atBlockTerminator(body).create<UnpackBundleOp>(
184  origPort.loc, body->getTerminator()->getOperand(origPort.argNum),
185  unpackChannels);
186 
187  // Build new ports and put the new port info directly into the member
188  // variable.
189  newOutputChannels.resize(outputChannels.size());
190  for (auto [idx, ch] : llvm::enumerate(outputChannels))
191  converter.createNewOutput(origPort, "_" + ch.name.getValue(), ch.type,
192  unpack ? unpack.getToChannels()[idx] : nullptr,
193  newOutputChannels[idx]);
194 }
195 
196 namespace {
197 /// Convert all the ESI bundle ports on modules to channel ports.
198 struct ESIBundlesPass
199  : public circt::esi::impl::LowerESIBundlesBase<ESIBundlesPass> {
200  void runOnOperation() override;
201 };
202 } // anonymous namespace
203 
204 /// Iterate through the `hw.module[.extern]`s and lower their ports.
205 void ESIBundlesPass::runOnOperation() {
206  MLIRContext &ctxt = getContext();
207  ModuleOp top = getOperation();
208 
209  // Find all modules and run port conversion on them.
210  circt::hw::InstanceGraph &instanceGraph =
211  getAnalysis<circt::hw::InstanceGraph>();
212  for (auto mod : top.getOps<HWMutableModuleLike>()) {
213  if (failed(PortConverter<ESIBundleConversionBuilder>(instanceGraph, mod)
214  .run()))
215  return signalPassFailure();
216  }
217 
218  // Canonicalize away bundle packs and unpacks. Any non-back-to-back [un]packs
219  // need to be gone by now.
220  RewritePatternSet patterns(&ctxt);
221  PackBundleOp::getCanonicalizationPatterns(patterns, &ctxt);
222  UnpackBundleOp::getCanonicalizationPatterns(patterns, &ctxt);
223  if (failed(mlir::applyPatternsAndFoldGreedily(getOperation(),
224  std::move(patterns))))
225  signalPassFailure();
226 
227  top.walk([&](PackBundleOp pack) {
228  pack.emitError("PackBundleOp should have been canonicalized away by now");
229  signalPassFailure();
230  });
231 }
232 
233 std::unique_ptr<OperationPass<ModuleOp>>
235  return std::make_unique<ESIBundlesPass>();
236 }
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
PortConversionBuilder(PortConverterImpl &converter)
virtual FailureOr< std::unique_ptr< PortConversion > > build(hw::PortInfo port)
Base class for the port conversion of a particular port.
Definition: PortConverter.h:97
std::unique_ptr< OperationPass< ModuleOp > > createESIBundleLoweringPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: esi.py:1