CIRCT 24.0.0git
Loading...
Searching...
No Matches
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
13using namespace circt;
14using namespace hw;
15
16/// Return a attribute with the specified suffix appended.
17static 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
24namespace {
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.
29class UntouchedPortConversion : public PortConversion {
30public:
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
48private:
49 void buildInputSignals() override {
50 Value newValue = converter.createNewInput(
51 origPort, "", origPort.type, portInfo, PortAttrPolicy::Preserve);
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 PortAttrPolicy::Preserve);
62 }
63
64 hw::PortInfo portInfo;
65};
66
67} // namespace
68
69FailureOr<std::unique_ptr<PortConversion>>
71 // Default builder is the 'untouched' port conversion which will simply
72 // pass ports through unmodified.
73 return {std::make_unique<UntouchedPortConversion>(converter, port)};
74}
75
77 : moduleNode(moduleNode), b(moduleNode->getModule()->getContext()) {
78 mod = dyn_cast<hw::HWMutableModuleLike>(*moduleNode->getModule());
79 assert(mod && "PortConverter only works on HWMutableModuleLike");
80
81 if (mod->getNumRegions() == 1 && mod->getRegion(0).hasOneBlock()) {
82 body = &mod->getRegion(0).front();
83 terminator = body->getTerminator();
84 }
85}
86
87Value PortConverterImpl::createNewInput(PortInfo origPort, const Twine &suffix,
88 Type type, PortInfo &newPort,
89 PortAttrPolicy attrPolicy) {
90 DictionaryAttr attrs = attrPolicy == PortAttrPolicy::Preserve
91 ? origPort.attrs
92 : DictionaryAttr();
93 newPort = PortInfo{
94 {append(origPort.name, suffix), type, ModulePort::Direction::Input},
95 newInputs.size(),
96 attrs,
97 origPort.loc};
98 newInputs.emplace_back(0, newPort);
99
100 if (!body)
101 return {};
102 return body->addArgument(type, origPort.loc);
103}
104
105void PortConverterImpl::createNewOutput(PortInfo origPort, const Twine &suffix,
106 Type type, Value output,
107 PortInfo &newPort,
108 PortAttrPolicy attrPolicy) {
109 DictionaryAttr attrs = attrPolicy == PortAttrPolicy::Preserve
110 ? origPort.attrs
111 : DictionaryAttr();
112 newPort = PortInfo{
113 {append(origPort.name, suffix), type, ModulePort::Direction::Output},
114 newOutputs.size(),
115 attrs,
116 origPort.loc};
117 newOutputs.emplace_back(0, newPort);
118
119 if (!body)
120 return;
121
122 OpBuilder::InsertionGuard g(b);
123 b.setInsertionPointToStart(body);
124 terminator->insertOperands(terminator->getNumOperands(), output);
125}
126
127LogicalResult PortConverterImpl::run() {
128 ModulePortInfo ports(mod.getPortList());
129
130 bool foundLoweredPorts = false;
131
132 auto createPortLowering = [&](PortInfo port) {
133 auto &loweredPorts = port.dir == ModulePort::Direction::Output
136
137 auto loweredPort = ssb->build(port);
138 if (failed(loweredPort))
139 return failure();
140
141 foundLoweredPorts |= !(*loweredPort)->isUntouched();
142 loweredPorts.emplace_back(std::move(*loweredPort));
143
144 if (failed(loweredPorts.back()->init()))
145 return failure();
146
147 return success();
148 };
149
150 // Dispatch the port conversion builder on the I/O of the module.
151 for (PortInfo port : ports)
152 if (failed(createPortLowering(port)))
153 return failure();
154
155 // Bail early if we didn't find anything to convert.
156 if (!foundLoweredPorts) {
157 // Memory optimization.
158 loweredInputs.clear();
159 loweredOutputs.clear();
160 return success();
161 }
162
163 // Lower the ports -- this mutates the body directly and builds the port
164 // lists.
165 for (auto &lowering : loweredInputs)
166 lowering->lowerPort();
167 for (auto &lowering : loweredOutputs)
168 lowering->lowerPort();
169
170 // Set up vectors to erase _all_ the ports. It's easier to rebuild everything
171 // than reason about interleaving the newly lowered ports with the non lowered
172 // ports. Also, the 'modifyPorts' method ends up rebuilding the port lists
173 // anyway, so this isn't nearly as expensive as it may seem.
174 SmallVector<unsigned> inputsToErase(mod.getNumInputPorts());
175 std::iota(inputsToErase.begin(), inputsToErase.end(), 0);
176 SmallVector<unsigned> outputsToErase(mod.getNumOutputPorts());
177 std::iota(outputsToErase.begin(), outputsToErase.end(), 0);
178
179 mod.modifyPorts(newInputs, newOutputs, inputsToErase, outputsToErase);
180
181 if (body) {
182 // We should only erase the original arguments. New ones were appended
183 // with the `createInput` method call.
184 body->eraseArguments([&ports](BlockArgument arg) {
185 return arg.getArgNumber() < ports.sizeInputs();
186 });
187
188 // And erase the first ports.sizeOutputs operands from the terminator.
189 terminator->eraseOperands(0, ports.sizeOutputs());
190 }
191
192 // Rewrite instances pointing to this module.
193 for (auto *instance : moduleNode->uses()) {
194 auto instanceLike = instance->getInstance<hw::HWInstanceLike>();
195 if (!instanceLike)
196 continue;
197 hw::InstanceOp hwInstance = dyn_cast_or_null<hw::InstanceOp>(*instanceLike);
198 if (!hwInstance) {
199 return instanceLike->emitOpError(
200 "This code only converts hw.instance instances - ask your friendly "
201 "neighborhood compiler engineers to implement support for something "
202 "like an hw::HWMutableInstanceLike interface");
203 }
204 updateInstance(hwInstance);
205 }
206
207 // Memory optimization -- we don't need these anymore.
208 newInputs.clear();
209 newOutputs.clear();
210 return success();
211}
212
213void PortConverterImpl::updateInstance(hw::InstanceOp inst) {
214 ImplicitLocOpBuilder b(inst.getLoc(), inst);
215 BackedgeBuilder beb(b, inst.getLoc());
216 ModulePortInfo ports(mod.getPortList());
217
218 // Create backedges for the future instance results so the signal mappers can
219 // use the future results as values.
220 SmallVector<Backedge> newResults;
221 for (PortInfo outputPort : ports.getOutputs())
222 newResults.push_back(beb.get(outputPort.type));
223
224 // Map the operands.
225 SmallVector<Value> newOperands(ports.sizeInputs(), {});
226 for (size_t oldOpIdx = 0, e = inst.getNumOperands(); oldOpIdx < e; ++oldOpIdx)
227 loweredInputs[oldOpIdx]->mapInputSignals(
228 b, inst, inst->getOperand(oldOpIdx), newOperands, newResults);
229
230 // Map the results.
231 for (size_t oldResIdx = 0, e = inst.getNumResults(); oldResIdx < e;
232 ++oldResIdx)
233 loweredOutputs[oldResIdx]->mapOutputSignals(
234 b, inst, inst->getResult(oldResIdx), newOperands, newResults);
235
236 // Clone the instance. We cannot just modifiy the existing one since the
237 // result types might have changed types and number of them.
238 assert(llvm::none_of(newOperands, [](Value v) { return !v; }));
239 b.setInsertionPointAfter(inst);
240 auto newInst =
241 InstanceOp::create(b, mod, inst.getInstanceNameAttr(), newOperands,
242 inst.getParameters(), inst.getInnerSymAttr());
243 newInst->setDialectAttrs(inst->getDialectAttrs());
244 if (auto doNotPrint = inst.getDoNotPrintAttr())
245 newInst.setDoNotPrintAttr(doNotPrint);
246
247 // Assign the backedges to the new results.
248 for (auto [idx, be] : llvm::enumerate(newResults))
249 be.setValue(newInst.getResult(idx));
250
251 // Erase the old instance.
252 inst.erase();
253}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
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.
virtual void buildInputSignals()=0
virtual void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue, SmallVectorImpl< Value > &newOperands, ArrayRef< Backedge > newResults)=0
Update an instance port to the new port information.
virtual void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue, SmallVectorImpl< Value > &newOperands, ArrayRef< Backedge > newResults)=0
virtual void buildOutputSignals()=0
void createNewOutput(hw::PortInfo origPort, const Twine &suffix, Type type, Value output, hw::PortInfo &newPort, PortAttrPolicy attrPolicy=PortAttrPolicy::Drop)
Same as above.
LogicalResult run()
Run port conversion.
SmallVector< std::unique_ptr< PortConversion > > loweredOutputs
SmallVector< std::pair< unsigned, hw::PortInfo >, 0 > newInputs
igraph::InstanceGraphNode * moduleNode
Value createNewInput(hw::PortInfo origPort, const Twine &suffix, Type type, hw::PortInfo &newPort, PortAttrPolicy attrPolicy=PortAttrPolicy::Drop)
These two methods take care of allocating new ports in the correct place based on the position of 'or...
SmallVector< std::unique_ptr< PortConversion > > loweredInputs
hw::HWMutableModuleLike mod
void updateInstance(hw::InstanceOp)
Updates an instance of the module.
std::unique_ptr< PortConversionBuilder > ssb
PortConverterImpl(igraph::InstanceGraphNode *moduleNode)
SmallVector< std::pair< unsigned, hw::PortInfo >, 0 > newOutputs
This is a Node in the InstanceGraph.
llvm::iterator_range< UseIterator > uses()
auto getModule()
Get the module that this node is tracking.
PortAttrPolicy
Controls whether a newly created port inherits the original port's attributes.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
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:32
This holds the name, type, direction of a module's ports.
DictionaryAttr attrs
The optional symbol for this port.