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