CIRCT 20.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 =
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
68FailureOr<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
86Value 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
100void 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
118LogicalResult 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
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
204void 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")
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
LogicalResult run()
Run port conversion.
SmallVector< std::unique_ptr< PortConversion > > loweredOutputs
SmallVector< std::pair< unsigned, hw::PortInfo >, 0 > newInputs
igraph::InstanceGraphNode * moduleNode
SmallVector< std::unique_ptr< PortConversion > > loweredInputs
hw::HWMutableModuleLike mod
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
PortConverterImpl(igraph::InstanceGraphNode *moduleNode)
SmallVector< std::pair< unsigned, hw::PortInfo >, 0 > newOutputs
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.
llvm::iterator_range< UseIterator > uses()
auto getModule()
Get the module that this node is tracking.
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:30
This holds the name, type, direction of a module's ports.