CIRCT 24.0.0git
Loading...
Searching...
No Matches
HWEliminateInOutPorts.cpp
Go to the documentation of this file.
1//===- HWEliminateInOutPorts.cpp - Generator Callout Pass
2//---------------------===//
3//
4// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5// See https://llvm.org/LICENSE.txt for license information.
6// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7//
8//===----------------------------------------------------------------------===//
9
16#include "mlir/IR/Builders.h"
17#include "mlir/Pass/Pass.h"
18#include "llvm/ADT/PostOrderIterator.h"
19
20namespace circt {
21namespace sv {
22#define GEN_PASS_DEF_HWELIMINATEINOUTPORTS
23#include "circt/Dialect/SV/SVPasses.h.inc"
24} // namespace sv
25} // namespace circt
26
27using namespace circt;
28using namespace sv;
29using namespace hw;
30using namespace igraph;
31
32namespace {
33
34struct HWEliminateInOutPortsPass
35 : public circt::sv::impl::HWEliminateInOutPortsBase<
36 HWEliminateInOutPortsPass> {
37 using HWEliminateInOutPortsBase<
38 HWEliminateInOutPortsPass>::HWEliminateInOutPortsBase;
39 void runOnOperation() override;
40};
41
42class HWInOutPortConversion : public PortConversion {
43public:
44 HWInOutPortConversion(PortConverterImpl &converter, hw::PortInfo port,
45 Type type, llvm::StringRef readSuffix,
46 llvm::StringRef writeSuffix);
47
48 void mapInputSignals(OpBuilder &b, Operation *inst, Value instValue,
49 SmallVectorImpl<Value> &newOperands,
50 ArrayRef<Backedge> newResults) override;
51 void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue,
52 SmallVectorImpl<Value> &newOperands,
53 ArrayRef<Backedge> newResults) override;
54
55 LogicalResult init() override;
56
57private:
58 void buildInputSignals() override;
59 void buildOutputSignals() override;
60
61 // Readers of this port internal in the module.
62 llvm::SmallVector<sv::ReadInOutOp, 4> readers;
63 // Writers of this port internal in the module.
64 llvm::SmallVector<sv::AssignOp, 4> writers;
65
66 bool hasReaders() { return !readers.empty(); }
67 bool hasWriters() { return !writers.empty(); }
68
69 // The type of the port.
70 Type type;
71
72 // Handles to port info of the newly created ports.
73 PortInfo readPort, writePort;
74
75 // Suffix to be used when creating read ports.
76 llvm::StringRef readSuffix;
77 // Suffix to be used when creating write ports.
78 llvm::StringRef writeSuffix;
79};
80
81HWInOutPortConversion::HWInOutPortConversion(PortConverterImpl &converter,
82 hw::PortInfo port, Type type,
83 llvm::StringRef readSuffix,
84 llvm::StringRef writeSuffix)
85 : PortConversion(converter, port), type(type), readSuffix(readSuffix),
86 writeSuffix(writeSuffix) {}
87
88LogicalResult HWInOutPortConversion::init() {
89 // Gather readers and writers (how to handle sv.passign?)
90 for (auto *user : body->getArgument(origPort.argNum).getUsers()) {
91 if (auto read = dyn_cast<sv::ReadInOutOp>(user))
92 readers.push_back(read);
93 else if (auto write = dyn_cast<sv::AssignOp>(user))
94 writers.push_back(write);
95 else
96 return user->emitOpError() << "uses hw.inout port " << origPort.name
97 << " but the operation itself is unsupported.";
98 }
99
100 if (writers.size() > 1)
101 return converter.getModule()->emitOpError()
102 << "multiple writers of inout port " << origPort.name
103 << " is unsupported.";
104
105 return success();
106}
107
108void HWInOutPortConversion::buildInputSignals() {
109 if (hasReaders()) {
110 // Replace all sv::ReadInOutOp's with the new input.
111 Value readValue =
112 converter.createNewInput(origPort, readSuffix, type, readPort);
113 Value origInput = body->getArgument(origPort.argNum);
114 for (auto *user : llvm::make_early_inc_range(origInput.getUsers())) {
115 sv::ReadInOutOp read = dyn_cast<sv::ReadInOutOp>(user);
116 if (!read)
117 continue;
118
119 read.replaceAllUsesWith(readValue);
120 read.erase();
121 }
122 }
123
124 if (hasWriters()) {
125 // Replace the sv::AssignOp with the new output.
126 sv::AssignOp write = writers.front();
127 converter.createNewOutput(origPort, writeSuffix, type, write.getSrc(),
128 writePort);
129 write.erase();
130 }
131}
132
133void HWInOutPortConversion::buildOutputSignals() {
134 assert(false &&
135 "`hw.inout` outputs not yet supported. Currently, `hw.inout` "
136 "outputs are handled by UntouchedPortConversion, given that "
137 "output `hw.inout` ports have a `ModulePort::Direction::Output` "
138 "direction instead of `ModulePort::Direction::InOut`. If this for "
139 "some reason changes, then this assert will fire.");
140}
141
142void HWInOutPortConversion::mapInputSignals(OpBuilder &b, Operation *inst,
143 Value instValue,
144 SmallVectorImpl<Value> &newOperands,
145 ArrayRef<Backedge> newResults) {
146
147 if (hasReaders()) {
148 // Create a read_inout op at the instantiation point. This effectively
149 // pushes the read_inout op from the module to the instantiation site.
150 newOperands[readPort.argNum] =
151 ReadInOutOp::create(b, inst->getLoc(), instValue).getResult();
152 }
153
154 if (hasWriters()) {
155 // Create a sv::AssignOp at the instantiation point. This effectively
156 // pushes the write op from the module to the instantiation site.
157 Value writeFromInsideMod = newResults[writePort.argNum];
158 sv::AssignOp::create(b, inst->getLoc(), instValue, writeFromInsideMod);
159 }
160}
161
162void HWInOutPortConversion::mapOutputSignals(
163 OpBuilder &b, Operation *inst, Value instValue,
164 SmallVectorImpl<Value> &newOperands, ArrayRef<Backedge> newResults) {
165 // FIXME: hw.inout cannot be used in outputs.
166 assert(false &&
167 "`hw.inout` outputs not yet supported. Currently, `hw.inout` "
168 "outputs are handled by UntouchedPortConversion, given that "
169 "output `hw.inout` ports have a `ModulePort::Direction::Output` "
170 "direction instead of `ModulePort::Direction::InOut`. If this for "
171 "some reason changes, then this assert will fire.");
172}
173
174class HWInoutPortConversionBuilder : public PortConversionBuilder {
175public:
176 HWInoutPortConversionBuilder(PortConverterImpl &converter,
177 llvm::StringRef readSuffix,
178 llvm::StringRef writeSuffix)
179 : PortConversionBuilder(converter), readSuffix(readSuffix),
180 writeSuffix(writeSuffix) {}
181
182 FailureOr<std::unique_ptr<PortConversion>> build(hw::PortInfo port) override {
183 if (auto inout = type_dyn_cast<hw::InOutType>(port.type))
184 return {std::make_unique<HWInOutPortConversion>(
185 converter, port, inout.getElementType(), readSuffix, writeSuffix)};
186 return PortConversionBuilder::build(port);
187 }
188
189private:
190 llvm::StringRef readSuffix;
191 llvm::StringRef writeSuffix;
192};
193
194} // namespace
195
196void HWEliminateInOutPortsPass::runOnOperation() {
197 // Find all modules and run port conversion on them.
198 circt::hw::InstanceGraph &instanceGraph =
199 getAnalysis<circt::hw::InstanceGraph>();
200 llvm::DenseSet<InstanceGraphNode *> visited;
201 FailureOr<llvm::ArrayRef<InstanceGraphNode *>> res =
202 instanceGraph.getInferredTopLevelNodes();
203
204 if (failed(res)) {
205 signalPassFailure();
206 return;
207 }
208
209 // Visit the instance hierarchy in a depth-first manner, modifying child
210 // modules and their ports before their parents.
211
212 // Doing this DFS ensures that all module instance uses of an inout value has
213 // been converted before the current instance use. E.g. say you have m1 -> m2
214 // -> m3 where both m3 and m2 reads an inout value defined in m1. If we don't
215 // do DFS, and we just randomly pick a module, we have to e.g. select m2, see
216 // that it also passes that inout value to other module instances, processes
217 // those first (which may bubble up read/writes to that hw.inout op), and then
218 // process m2... which in essence is a DFS traversal. So we just go ahead and
219 // do the DFS to begin with, ensuring the invariant that all module instance
220 // uses of an inout value have been converted before converting any given
221 // module.
222
223 for (InstanceGraphNode *topModule : res.value()) {
224 for (InstanceGraphNode *node : llvm::post_order(topModule)) {
225 if (visited.count(node))
226 continue;
227 auto mutableModule =
228 dyn_cast_or_null<hw::HWMutableModuleLike>(*node->getModule());
229 if (!mutableModule)
230 continue;
231 if (failed(PortConverter<HWInoutPortConversionBuilder>(
232 instanceGraph, mutableModule, readSuffix.getValue(),
233 writeSuffix.getValue())
234 .run()))
235 return signalPassFailure();
236 }
237 }
238}
assert(baseType &&"element must be base type")
return success()
virtual FailureOr< std::unique_ptr< PortConversion > > build(hw::PortInfo port)
Base class for the port conversion of a particular port.
FailureOr< llvm::ArrayRef< InstanceGraphNode * > > getInferredTopLevelNodes()
Get the nodes corresponding to the inferred top-level modules of a circuit.
create(dest, src)
Definition sv.py:135
create(value)
Definition sv.py:151
BaseTy type_dyn_cast(Type type)
Definition HWTypes.h:234
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
Definition sv.py:1
write(addr, data)
Definition xrt_cosim.py:30
read(addr)
Definition xrt_cosim.py:23
size_t argNum
This is the argument index or the result index depending on the direction.