CIRCT  20.0.0git
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 
11 #include "circt/Dialect/HW/HWOps.h"
14 #include "circt/Dialect/SV/SVOps.h"
16 #include "mlir/IR/Builders.h"
17 #include "mlir/Pass/Pass.h"
18 #include "llvm/ADT/PostOrderIterator.h"
19 
20 namespace circt {
21 namespace sv {
22 #define GEN_PASS_DEF_HWELIMINATEINOUTPORTS
23 #include "circt/Dialect/SV/SVPasses.h.inc"
24 } // namespace sv
25 } // namespace circt
26 
27 using namespace circt;
28 using namespace sv;
29 using namespace hw;
30 using namespace igraph;
31 
32 namespace {
33 
34 struct HWEliminateInOutPortsPass
35  : public circt::sv::impl::HWEliminateInOutPortsBase<
36  HWEliminateInOutPortsPass> {
37  using HWEliminateInOutPortsBase<
38  HWEliminateInOutPortsPass>::HWEliminateInOutPortsBase;
39  void runOnOperation() override;
40 };
41 
42 class HWInOutPortConversion : public PortConversion {
43 public:
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 
57 private:
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 
78 HWInOutPortConversion::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 
85 LogicalResult 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 
105 void 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 
130 void 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 
139 void 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 
159 void 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 
171 class HWInoutPortConversionBuilder : public PortConversionBuilder {
172 public:
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 
186 private:
187  llvm::StringRef readSuffix;
188  llvm::StringRef writeSuffix;
189 };
190 
191 } // namespace
192 
193 void 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")
@ InOut
Definition: HW.h:35
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.
Definition: PortConverter.h:97
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: DebugAnalysis.h:21
Definition: hw.py:1
Definition: sv.py:1
This holds the name, type, direction of a module's ports.