CIRCT 20.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 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 // Handles to port info of the newly created ports.
70 PortInfo readPort, writePort;
71
72 // Suffix to be used when creating read ports.
73 llvm::StringRef readSuffix;
74 // Suffix to be used when creating write ports.
75 llvm::StringRef writeSuffix;
76};
77
78HWInOutPortConversion::HWInOutPortConversion(PortConverterImpl &converter,
79 hw::PortInfo port,
80 llvm::StringRef readSuffix,
81 llvm::StringRef writeSuffix)
82 : PortConversion(converter, port), readSuffix(readSuffix),
83 writeSuffix(writeSuffix) {}
84
85LogicalResult HWInOutPortConversion::init() {
86 // Gather readers and writers (how to handle sv.passign?)
87 for (auto *user : body->getArgument(origPort.argNum).getUsers()) {
88 if (auto read = dyn_cast<sv::ReadInOutOp>(user))
89 readers.push_back(read);
90 else if (auto write = dyn_cast<sv::AssignOp>(user))
91 writers.push_back(write);
92 else
93 return user->emitOpError() << "uses hw.inout port " << origPort.name
94 << " but the operation itself is unsupported.";
95 }
96
97 if (writers.size() > 1)
98 return converter.getModule()->emitOpError()
99 << "multiple writers of inout port " << origPort.name
100 << " is unsupported.";
101
102 return success();
103}
104
105void HWInOutPortConversion::buildInputSignals() {
106 if (hasReaders()) {
107 // Replace all sv::ReadInOutOp's with the new input.
108 Value readValue =
109 converter.createNewInput(origPort, readSuffix, origPort.type, readPort);
110 Value origInput = body->getArgument(origPort.argNum);
111 for (auto *user : llvm::make_early_inc_range(origInput.getUsers())) {
112 sv::ReadInOutOp read = dyn_cast<sv::ReadInOutOp>(user);
113 if (!read)
114 continue;
115
116 read.replaceAllUsesWith(readValue);
117 read.erase();
118 }
119 }
120
121 if (hasWriters()) {
122 // Replace the sv::AssignOp with the new output.
123 sv::AssignOp write = writers.front();
124 converter.createNewOutput(origPort, writeSuffix, origPort.type,
125 write.getSrc(), writePort);
126 write.erase();
127 }
128}
129
130void HWInOutPortConversion::buildOutputSignals() {
131 assert(false &&
132 "`hw.inout` outputs not yet supported. Currently, `hw.inout` "
133 "outputs are handled by UntouchedPortConversion, given that "
134 "output `hw.inout` ports have a `ModulePort::Direction::Output` "
135 "direction instead of `ModulePort::Direction::InOut`. If this for "
136 "some reason changes, then this assert will fire.");
137}
138
139void HWInOutPortConversion::mapInputSignals(OpBuilder &b, Operation *inst,
140 Value instValue,
141 SmallVectorImpl<Value> &newOperands,
142 ArrayRef<Backedge> newResults) {
143
144 if (hasReaders()) {
145 // Create a read_inout op at the instantiation point. This effectively
146 // pushes the read_inout op from the module to the instantiation site.
147 newOperands[readPort.argNum] =
148 b.create<ReadInOutOp>(inst->getLoc(), instValue).getResult();
149 }
150
151 if (hasWriters()) {
152 // Create a sv::AssignOp at the instantiation point. This effectively
153 // pushes the write op from the module to the instantiation site.
154 Value writeFromInsideMod = newResults[writePort.argNum];
155 b.create<sv::AssignOp>(inst->getLoc(), instValue, writeFromInsideMod);
156 }
157}
158
159void HWInOutPortConversion::mapOutputSignals(
160 OpBuilder &b, Operation *inst, Value instValue,
161 SmallVectorImpl<Value> &newOperands, ArrayRef<Backedge> newResults) {
162 // FIXME: hw.inout cannot be used in outputs.
163 assert(false &&
164 "`hw.inout` outputs not yet supported. Currently, `hw.inout` "
165 "outputs are handled by UntouchedPortConversion, given that "
166 "output `hw.inout` ports have a `ModulePort::Direction::Output` "
167 "direction instead of `ModulePort::Direction::InOut`. If this for "
168 "some reason changes, then this assert will fire.");
169}
170
171class HWInoutPortConversionBuilder : public PortConversionBuilder {
172public:
173 HWInoutPortConversionBuilder(PortConverterImpl &converter,
174 llvm::StringRef readSuffix,
175 llvm::StringRef writeSuffix)
176 : PortConversionBuilder(converter), readSuffix(readSuffix),
177 writeSuffix(writeSuffix) {}
178
179 FailureOr<std::unique_ptr<PortConversion>> build(hw::PortInfo port) override {
180 if (port.dir == hw::ModulePort::Direction::InOut)
181 return {std::make_unique<HWInOutPortConversion>(converter, port,
182 readSuffix, writeSuffix)};
183 return PortConversionBuilder::build(port);
184 }
185
186private:
187 llvm::StringRef readSuffix;
188 llvm::StringRef writeSuffix;
189};
190
191} // namespace
192
193void HWEliminateInOutPortsPass::runOnOperation() {
194 // Find all modules and run port conversion on them.
195 circt::hw::InstanceGraph &instanceGraph =
196 getAnalysis<circt::hw::InstanceGraph>();
197 llvm::DenseSet<InstanceGraphNode *> visited;
198 FailureOr<llvm::ArrayRef<InstanceGraphNode *>> res =
199 instanceGraph.getInferredTopLevelNodes();
200
201 if (failed(res)) {
202 signalPassFailure();
203 return;
204 }
205
206 // Visit the instance hierarchy in a depth-first manner, modifying child
207 // modules and their ports before their parents.
208
209 // Doing this DFS ensures that all module instance uses of an inout value has
210 // been converted before the current instance use. E.g. say you have m1 -> m2
211 // -> m3 where both m3 and m2 reads an inout value defined in m1. If we don't
212 // do DFS, and we just randomly pick a module, we have to e.g. select m2, see
213 // that it also passes that inout value to other module instances, processes
214 // those first (which may bubble up read/writes to that hw.inout op), and then
215 // process m2... which in essence is a DFS traversal. So we just go ahead and
216 // do the DFS to begin with, ensuring the invariant that all module instance
217 // uses of an inout value have been converted before converting any given
218 // module.
219
220 for (InstanceGraphNode *topModule : res.value()) {
221 for (InstanceGraphNode *node : llvm::post_order(topModule)) {
222 if (visited.count(node))
223 continue;
224 auto mutableModule =
225 dyn_cast_or_null<hw::HWMutableModuleLike>(*node->getModule());
226 if (!mutableModule)
227 continue;
229 instanceGraph, mutableModule, readSuffix.getValue(),
230 writeSuffix.getValue())
231 .run()))
232 return signalPassFailure();
233 }
234 }
235}
236
238 const HWEliminateInOutPortsOptions &options) {
239 return std::make_unique<HWEliminateInOutPortsPass>(options);
240}
assert(baseType &&"element must be base type")
static void writePort(uint16_t port)
Write the port number to a file.
Definition RpcServer.cpp:37
HW-specific instance graph with a virtual entry node linking to all publicly visible modules.
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 LogicalResult init()
virtual void mapOutputSignals(OpBuilder &b, Operation *inst, Value instValue, SmallVectorImpl< Value > &newOperands, ArrayRef< Backedge > newResults)=0
virtual void buildOutputSignals()=0
hw::HWMutableModuleLike getModule()
LogicalResult run()
Run port conversion.
This is a Node in the InstanceGraph.
FailureOr< llvm::ArrayRef< InstanceGraphNode * > > getInferredTopLevelNodes()
Get the nodes corresponding to the inferred top-level modules of a circuit.
std::unique_ptr< mlir::Pass > createHWEliminateInOutPortsPass(const HWEliminateInOutPortsOptions &options={})
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition hw.py:1
Definition sv.py:1
This holds the name, type, direction of a module's ports.