CIRCT  20.0.0git
PortConverter.cpp
Go to the documentation of this file.
1 //===- PortConverter.cpp - Module I/O rewriting utility ---------*- 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 
10 
11 #include <numeric>
12 
13 using namespace circt;
14 using namespace hw;
15 
16 /// Return a attribute with the specified suffix appended.
17 static StringAttr append(StringAttr base, const Twine &suffix) {
18  if (suffix.isTriviallyEmpty())
19  return base;
20  auto *context = base.getContext();
21  return StringAttr::get(context, base.getValue() + suffix);
22 }
23 
24 namespace {
25 
26 /// We consider non-caught ports to be ad-hoc signaling or 'untouched'. (Which
27 /// counts as a signaling protocol if one squints pretty hard). We mostly do
28 /// this since it allows us a more consistent internal API.
29 class UntouchedPortConversion : public PortConversion {
30 public:
31  UntouchedPortConversion(PortConverterImpl &converter, hw::PortInfo origPort)
32  : PortConversion(converter, origPort) {
33  // Set the "RTTI flag" to true (see comment in header for this variable).
34  isUntouchedFlag = true;
35  }
36 
37  void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue,
38  SmallVectorImpl<Value> &newOperands,
39  ArrayRef<Backedge> newResults) override {
40  newOperands[portInfo.argNum] = instValue;
41  }
42  void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue,
43  SmallVectorImpl<Value> &newOperands,
44  ArrayRef<Backedge> newResults) override {
45  instValue.replaceAllUsesWith(newResults[portInfo.argNum]);
46  }
47 
48 private:
49  void buildInputSignals() override {
50  Value newValue =
51  converter.createNewInput(origPort, "", origPort.type, portInfo);
52  if (body)
53  body->getArgument(origPort.argNum).replaceAllUsesWith(newValue);
54  }
55 
56  void buildOutputSignals() override {
57  Value output;
58  if (body)
59  output = body->getTerminator()->getOperand(origPort.argNum);
60  converter.createNewOutput(origPort, "", origPort.type, output, portInfo);
61  }
62 
63  hw::PortInfo portInfo;
64 };
65 
66 } // namespace
67 
68 FailureOr<std::unique_ptr<PortConversion>>
70  // Default builder is the 'untouched' port conversion which will simply
71  // pass ports through unmodified.
72  return {std::make_unique<UntouchedPortConversion>(converter, port)};
73 }
74 
76  : moduleNode(moduleNode), b(moduleNode->getModule()->getContext()) {
77  mod = dyn_cast<hw::HWMutableModuleLike>(*moduleNode->getModule());
78  assert(mod && "PortConverter only works on HWMutableModuleLike");
79 
80  if (mod->getNumRegions() == 1 && mod->getRegion(0).hasOneBlock()) {
81  body = &mod->getRegion(0).front();
82  terminator = body->getTerminator();
83  }
84 }
85 
86 Value PortConverterImpl::createNewInput(PortInfo origPort, const Twine &suffix,
87  Type type, PortInfo &newPort) {
88  newPort = PortInfo{
89  {append(origPort.name, suffix), type, ModulePort::Direction::Input},
90  newInputs.size(),
91  {},
92  origPort.loc};
93  newInputs.emplace_back(0, newPort);
94 
95  if (!body)
96  return {};
97  return body->addArgument(type, origPort.loc);
98 }
99 
100 void PortConverterImpl::createNewOutput(PortInfo origPort, const Twine &suffix,
101  Type type, Value output,
102  PortInfo &newPort) {
103  newPort = PortInfo{
104  {append(origPort.name, suffix), type, ModulePort::Direction::Output},
105  newOutputs.size(),
106  {},
107  origPort.loc};
108  newOutputs.emplace_back(0, newPort);
109 
110  if (!body)
111  return;
112 
113  OpBuilder::InsertionGuard g(b);
114  b.setInsertionPointToStart(body);
115  terminator->insertOperands(terminator->getNumOperands(), output);
116 }
117 
118 LogicalResult PortConverterImpl::run() {
119  ModulePortInfo ports(mod.getPortList());
120 
121  bool foundLoweredPorts = false;
122 
123  auto createPortLowering = [&](PortInfo port) {
124  auto &loweredPorts = port.dir == ModulePort::Direction::Output
126  : loweredInputs;
127 
128  auto loweredPort = ssb->build(port);
129  if (failed(loweredPort))
130  return failure();
131 
132  foundLoweredPorts |= !(*loweredPort)->isUntouched();
133  loweredPorts.emplace_back(std::move(*loweredPort));
134 
135  if (failed(loweredPorts.back()->init()))
136  return failure();
137 
138  return success();
139  };
140 
141  // Dispatch the port conversion builder on the I/O of the module.
142  for (PortInfo port : ports)
143  if (failed(createPortLowering(port)))
144  return failure();
145 
146  // Bail early if we didn't find anything to convert.
147  if (!foundLoweredPorts) {
148  // Memory optimization.
149  loweredInputs.clear();
150  loweredOutputs.clear();
151  return success();
152  }
153 
154  // Lower the ports -- this mutates the body directly and builds the port
155  // lists.
156  for (auto &lowering : loweredInputs)
157  lowering->lowerPort();
158  for (auto &lowering : loweredOutputs)
159  lowering->lowerPort();
160 
161  // Set up vectors to erase _all_ the ports. It's easier to rebuild everything
162  // than reason about interleaving the newly lowered ports with the non lowered
163  // ports. Also, the 'modifyPorts' method ends up rebuilding the port lists
164  // anyway, so this isn't nearly as expensive as it may seem.
165  SmallVector<unsigned> inputsToErase(mod.getNumInputPorts());
166  std::iota(inputsToErase.begin(), inputsToErase.end(), 0);
167  SmallVector<unsigned> outputsToErase(mod.getNumOutputPorts());
168  std::iota(outputsToErase.begin(), outputsToErase.end(), 0);
169 
170  mod.modifyPorts(newInputs, newOutputs, inputsToErase, outputsToErase);
171 
172  if (body) {
173  // We should only erase the original arguments. New ones were appended
174  // with the `createInput` method call.
175  body->eraseArguments([&ports](BlockArgument arg) {
176  return arg.getArgNumber() < ports.sizeInputs();
177  });
178 
179  // And erase the first ports.sizeOutputs operands from the terminator.
180  terminator->eraseOperands(0, ports.sizeOutputs());
181  }
182 
183  // Rewrite instances pointing to this module.
184  for (auto *instance : moduleNode->uses()) {
185  auto instanceLike = instance->getInstance<hw::HWInstanceLike>();
186  if (!instanceLike)
187  continue;
188  hw::InstanceOp hwInstance = dyn_cast_or_null<hw::InstanceOp>(*instanceLike);
189  if (!hwInstance) {
190  return instanceLike->emitOpError(
191  "This code only converts hw.instance instances - ask your friendly "
192  "neighborhood compiler engineers to implement support for something "
193  "like an hw::HWMutableInstanceLike interface");
194  }
195  updateInstance(hwInstance);
196  }
197 
198  // Memory optimization -- we don't need these anymore.
199  newInputs.clear();
200  newOutputs.clear();
201  return success();
202 }
203 
204 void PortConverterImpl::updateInstance(hw::InstanceOp inst) {
205  ImplicitLocOpBuilder b(inst.getLoc(), inst);
206  BackedgeBuilder beb(b, inst.getLoc());
207  ModulePortInfo ports(mod.getPortList());
208 
209  // Create backedges for the future instance results so the signal mappers can
210  // use the future results as values.
211  SmallVector<Backedge> newResults;
212  for (PortInfo outputPort : ports.getOutputs())
213  newResults.push_back(beb.get(outputPort.type));
214 
215  // Map the operands.
216  SmallVector<Value> newOperands(ports.sizeInputs(), {});
217  for (size_t oldOpIdx = 0, e = inst.getNumOperands(); oldOpIdx < e; ++oldOpIdx)
218  loweredInputs[oldOpIdx]->mapInputSignals(
219  b, inst, inst->getOperand(oldOpIdx), newOperands, newResults);
220 
221  // Map the results.
222  for (size_t oldResIdx = 0, e = inst.getNumResults(); oldResIdx < e;
223  ++oldResIdx)
224  loweredOutputs[oldResIdx]->mapOutputSignals(
225  b, inst, inst->getResult(oldResIdx), newOperands, newResults);
226 
227  // Clone the instance. We cannot just modifiy the existing one since the
228  // result types might have changed types and number of them.
229  assert(llvm::none_of(newOperands, [](Value v) { return !v; }));
230  b.setInsertionPointAfter(inst);
231  auto newInst =
232  b.create<InstanceOp>(mod, inst.getInstanceNameAttr(), newOperands,
233  inst.getParameters(), inst.getInnerSymAttr());
234  newInst->setDialectAttrs(inst->getDialectAttrs());
235 
236  // Assign the backedges to the new results.
237  for (auto [idx, be] : llvm::enumerate(newResults))
238  be.setValue(newInst.getResult(idx));
239 
240  // Erase the old instance.
241  inst.erase();
242 }
assert(baseType &&"element must be base type")
@ Input
Definition: HW.h:35
@ Output
Definition: HW.h:35
static StringAttr append(StringAttr base, const Twine &suffix)
Return a attribute with the specified suffix appended.
Instantiate one of these and use it to build typed backedges.
Backedge get(mlir::Type resultType, mlir::LocationAttr optionalLoc={})
Create a typed backedge.
virtual FailureOr< std::unique_ptr< PortConversion > > build(hw::PortInfo port)
Base class for the port conversion of a particular port.
Definition: PortConverter.h:97
LogicalResult run()
Run port conversion.
SmallVector< std::unique_ptr< PortConversion > > loweredOutputs
Definition: PortConverter.h:79
SmallVector< std::pair< unsigned, hw::PortInfo >, 0 > newInputs
Definition: PortConverter.h:85
igraph::InstanceGraphNode * moduleNode
Definition: PortConverter.h:71
SmallVector< std::unique_ptr< PortConversion > > loweredInputs
Definition: PortConverter.h:78
hw::HWMutableModuleLike mod
Definition: PortConverter.h:72
void createNewOutput(hw::PortInfo origPort, const Twine &suffix, Type type, Value output, hw::PortInfo &newPort)
Same as above.
void updateInstance(hw::InstanceOp)
Updates an instance of the module.
std::unique_ptr< PortConversionBuilder > ssb
Definition: PortConverter.h:60
PortConverterImpl(igraph::InstanceGraphNode *moduleNode)
SmallVector< std::pair< unsigned, hw::PortInfo >, 0 > newOutputs
Definition: PortConverter.h:86
Value createNewInput(hw::PortInfo origPort, const Twine &suffix, Type type, hw::PortInfo &newPort)
These two methods take care of allocating new ports in the correct place based on the position of 'or...
This is a Node in the InstanceGraph.
auto getModule()
Get the module that this node is tracking.
llvm::iterator_range< UseIterator > uses()
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
Definition: hw.py:1
This holds a decoded list of input/inout and output ports for a module or instance.
PortDirectionRange getOutputs()
mlir::StringAttr name
Definition: HWTypes.h:30
This holds the name, type, direction of a module's ports.