CIRCT  19.0.0git
InferReadWrite.cpp
Go to the documentation of this file.
1 //===- InferReadWrite.cpp - Infer Read Write Memory -----------------------===//
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 //
9 // This file defines the InferReadWrite pass.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "PassDetails.h"
19 #include "mlir/IR/ImplicitLocOpBuilder.h"
20 #include "llvm/ADT/APSInt.h"
21 #include "llvm/ADT/StringSwitch.h"
22 #include "llvm/ADT/TypeSwitch.h"
23 #include "llvm/Support/Debug.h"
24 
25 #define DEBUG_TYPE "firrtl-infer-read-write"
26 
27 using namespace circt;
28 using namespace firrtl;
29 
30 namespace {
31 struct InferReadWritePass : public InferReadWriteBase<InferReadWritePass> {
32 
33  /// This pass performs two memory transformations:
34  /// 1. If the multi-bit enable port is connected to a constant 1,
35  /// then, replace with a single bit mask. Create a new memory with a
36  /// 1 bit mask, and replace the old memory with it. The single bit mask
37  /// memory is always lowered to an unmasked memory.
38  /// 2. If the read and write enable ports are trivially mutually exclusive,
39  /// then create a new memory with a single read/write port, and replace
40  /// the old memory with it.
41  void runOnOperation() override {
42  LLVM_DEBUG(llvm::dbgs() << "\n Running Infer Read Write on module:"
43  << getOperation().getName());
44  SmallVector<Operation *> opsToErase;
45  for (MemOp memOp : llvm::make_early_inc_range(
46  getOperation().getBodyBlock()->getOps<MemOp>())) {
47  inferUnmasked(memOp, opsToErase);
48  simplifyWmode(memOp);
49  size_t nReads, nWrites, nRWs, nDbgs;
50  memOp.getNumPorts(nReads, nWrites, nRWs, nDbgs);
51  // Run the analysis only for Seq memories (latency=1) and a single read
52  // and write ports.
53  if (!(nReads == 1 && nWrites == 1 && nRWs == 0) ||
54  !(memOp.getReadLatency() == 1 && memOp.getWriteLatency() == 1))
55  continue;
56  SmallVector<Attribute, 4> resultNames;
57  SmallVector<Type, 4> resultTypes;
58  SmallVector<Attribute> portAtts;
59  SmallVector<Attribute, 4> portAnnotations;
60  Value rClock, wClock;
61  // The memory has exactly two ports.
62  SmallVector<Value> readTerms, writeTerms;
63  for (const auto &portIt : llvm::enumerate(memOp.getResults())) {
64  Attribute portAnno;
65  portAnno = memOp.getPortAnnotation(portIt.index());
66  if (memOp.getPortKind(portIt.index()) == MemOp::PortKind::Debug) {
67  resultNames.push_back(memOp.getPortName(portIt.index()));
68  resultTypes.push_back(memOp.getResult(portIt.index()).getType());
69  portAnnotations.push_back(portAnno);
70  continue;
71  }
72  // Append the annotations from the two ports.
73  if (!cast<ArrayAttr>(portAnno).empty())
74  portAtts.push_back(memOp.getPortAnnotation(portIt.index()));
75  // Get the port value.
76  Value portVal = portIt.value();
77  // Get the port kind.
78  bool isReadPort =
79  memOp.getPortKind(portIt.index()) == MemOp::PortKind::Read;
80  // Iterate over all users of the port.
81  for (Operation *u : portVal.getUsers())
82  if (auto sf = dyn_cast<SubfieldOp>(u)) {
83  // Get the field name.
84  auto fName = sf.getInput().getType().base().getElementName(
85  sf.getFieldIndex());
86  // If this is the enable field, record the product terms(the And
87  // expression tree).
88  if (fName.equals("en"))
89  getProductTerms(sf, isReadPort ? readTerms : writeTerms);
90 
91  else if (fName.equals("clk")) {
92  if (isReadPort)
93  rClock = getConnectSrc(sf);
94  else
95  wClock = getConnectSrc(sf);
96  }
97  }
98  // End of loop for getting MemOp port users.
99  }
100  if (!sameDriver(rClock, wClock))
101  continue;
102 
103  rClock = wClock;
104  LLVM_DEBUG(
105  llvm::dbgs() << "\n read clock:" << rClock
106  << " --- write clock:" << wClock;
107  llvm::dbgs() << "\n Read terms==>"; for (auto t
108  : readTerms) llvm::dbgs()
109  << "\n term::" << t;
110 
111  llvm::dbgs() << "\n Write terms==>"; for (auto t
112  : writeTerms) llvm::dbgs()
113  << "\n term::" << t;
114 
115  );
116  // If the read and write clocks are the same, and if any of the write
117  // enable product terms are a complement of the read enable, then return
118  // the write enable term.
119  auto complementTerm = checkComplement(readTerms, writeTerms);
120  if (!complementTerm)
121  continue;
122 
123  // Create the merged rw port for the new memory.
124  resultNames.push_back(StringAttr::get(memOp.getContext(), "rw"));
125  // Set the type of the rw port.
126  resultTypes.push_back(MemOp::getTypeForPort(
127  memOp.getDepth(), memOp.getDataType(), MemOp::PortKind::ReadWrite,
128  memOp.getMaskBits()));
129  ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
130  portAnnotations.push_back(builder.getArrayAttr(portAtts));
131  // Create the new rw memory.
132  auto rwMem = builder.create<MemOp>(
133  resultTypes, memOp.getReadLatency(), memOp.getWriteLatency(),
134  memOp.getDepth(), RUWAttr::Undefined,
135  builder.getArrayAttr(resultNames), memOp.getNameAttr(),
136  memOp.getNameKind(), memOp.getAnnotations(),
137  builder.getArrayAttr(portAnnotations), memOp.getInnerSymAttr(),
138  memOp.getInitAttr(), memOp.getPrefixAttr());
139  ++numRWPortMemoriesInferred;
140  auto rwPort = rwMem->getResult(nDbgs);
141  // Create the subfield access to all fields of the port.
142  // The addr should be connected to read/write address depending on the
143  // read/write mode.
144  auto addr = builder.create<SubfieldOp>(rwPort, "addr");
145  // Enable is high whenever the memory is written or read.
146  auto enb = builder.create<SubfieldOp>(rwPort, "en");
147  // Read/Write clock.
148  auto clk = builder.create<SubfieldOp>(rwPort, "clk");
149  auto readData = builder.create<SubfieldOp>(rwPort, "rdata");
150  // wmode is high when the port is in write mode. That is this can be
151  // connected to the write enable.
152  auto wmode = builder.create<SubfieldOp>(rwPort, "wmode");
153  auto writeData = builder.create<SubfieldOp>(rwPort, "wdata");
154  auto mask = builder.create<SubfieldOp>(rwPort, "wmask");
155  // Temp wires to replace the original memory connects.
156  auto rAddr =
157  builder.create<WireOp>(addr.getType(), "readAddr").getResult();
158  auto wAddr =
159  builder.create<WireOp>(addr.getType(), "writeAddr").getResult();
160  auto wEnWire =
161  builder.create<WireOp>(enb.getType(), "writeEnable").getResult();
162  auto rEnWire =
163  builder.create<WireOp>(enb.getType(), "readEnable").getResult();
164  auto writeClock =
165  builder.create<WireOp>(ClockType::get(enb.getContext())).getResult();
166  // addr = Mux(WriteEnable, WriteAddress, ReadAddress).
167  builder.create<StrictConnectOp>(
168  addr, builder.create<MuxPrimOp>(wEnWire, wAddr, rAddr));
169  // Enable = Or(WriteEnable, ReadEnable).
170  builder.create<StrictConnectOp>(
171  enb, builder.create<OrPrimOp>(rEnWire, wEnWire));
172  builder.setInsertionPointToEnd(wmode->getBlock());
173  builder.create<StrictConnectOp>(wmode, complementTerm);
174  // Now iterate over the original memory read and write ports.
175  size_t dbgsIndex = 0;
176  for (const auto &portIt : llvm::enumerate(memOp.getResults())) {
177  // Get the port value.
178  Value portVal = portIt.value();
179  if (memOp.getPortKind(portIt.index()) == MemOp::PortKind::Debug) {
180  memOp.getResult(portIt.index())
181  .replaceAllUsesWith(rwMem.getResult(dbgsIndex));
182  dbgsIndex++;
183  continue;
184  }
185  // Get the port kind.
186  bool isReadPort =
187  memOp.getPortKind(portIt.index()) == MemOp::PortKind::Read;
188  // Iterate over all users of the port, which are the subfield ops, and
189  // replace them.
190  for (Operation *u : portVal.getUsers())
191  if (auto sf = dyn_cast<SubfieldOp>(u)) {
192  StringRef fName = sf.getInput().getType().base().getElementName(
193  sf.getFieldIndex());
194  Value repl;
195  if (isReadPort)
196  repl = llvm::StringSwitch<Value>(fName)
197  .Case("en", rEnWire)
198  .Case("clk", clk)
199  .Case("addr", rAddr)
200  .Case("data", readData);
201  else
202  repl = llvm::StringSwitch<Value>(fName)
203  .Case("en", wEnWire)
204  .Case("clk", writeClock)
205  .Case("addr", wAddr)
206  .Case("data", writeData)
207  .Case("mask", mask);
208  sf.replaceAllUsesWith(repl);
209  // Once all the uses of the subfield op replaced, delete it.
210  opsToErase.push_back(sf);
211  }
212  }
213  simplifyWmode(rwMem);
214  // All uses for all results of mem removed, now erase the memOp.
215  opsToErase.push_back(memOp);
216  }
217  for (auto *o : opsToErase)
218  o->erase();
219  }
220 
221 private:
222  // Get the source value which is connected to the dst.
223  Value getConnectSrc(Value dst) {
224  for (auto *c : dst.getUsers())
225  if (auto connect = dyn_cast<FConnectLike>(c))
226  if (connect.getDest() == dst)
227  return connect.getSrc();
228 
229  return nullptr;
230  }
231 
232  /// If the ports are not directly connected to the same clock, then check
233  /// if indirectly connected to the same clock.
234  bool sameDriver(Value rClock, Value wClock) {
235  if (rClock == wClock)
236  return true;
237  DenseSet<Value> rClocks, wClocks;
238  while (rClock) {
239  // Record all the values which are indirectly connected to the clock
240  // port.
241  rClocks.insert(rClock);
242  rClock = getConnectSrc(rClock);
243  }
244 
245  bool sameClock = false;
246  // Now check all the indirect connections to the write memory clock
247  // port.
248  while (wClock) {
249  if (rClocks.find(wClock) != rClocks.end()) {
250  sameClock = true;
251  break;
252  }
253  wClock = getConnectSrc(wClock);
254  }
255  return sameClock;
256  }
257 
258  void getProductTerms(Value enValue, SmallVector<Value> &terms) {
259  if (!enValue)
260  return;
261  SmallVector<Value> worklist;
262  worklist.push_back(enValue);
263  while (!worklist.empty()) {
264  auto term = worklist.back();
265  worklist.pop_back();
266  terms.push_back(term);
267  if (isa<BlockArgument>(term))
268  continue;
269  TypeSwitch<Operation *>(term.getDefiningOp())
270  .Case<NodeOp>([&](auto n) { worklist.push_back(n.getInput()); })
271  .Case<AndPrimOp>([&](AndPrimOp andOp) {
272  worklist.push_back(andOp.getOperand(0));
273  worklist.push_back(andOp.getOperand(1));
274  })
275  .Case<MuxPrimOp>([&](auto muxOp) {
276  // Check for the pattern when low is 0, which is equivalent to (sel
277  // & high)
278  // term = mux (sel, high, 0) => term = sel & high
279  if (ConstantOp cLow = dyn_cast_or_null<ConstantOp>(
280  muxOp.getLow().getDefiningOp()))
281  if (cLow.getValue().isZero()) {
282  worklist.push_back(muxOp.getSel());
283  worklist.push_back(muxOp.getHigh());
284  }
285  })
286  .Default([&](auto) {
287  if (auto src = getConnectSrc(term))
288  worklist.push_back(src);
289  });
290  }
291  }
292 
293  /// If any of the terms in the read enable, prodTerms[0] is a complement of
294  /// any of the terms in the write enable prodTerms[1], return the
295  /// corresponding write enable term. prodTerms[0], prodTerms[1] is a vector of
296  /// Value, each of which correspond to the two product terms of read and write
297  /// enable respectively.
298  Value checkComplement(const SmallVector<Value> &readTerms,
299  const SmallVector<Value> &writeTerms) {
300  // Foreach Value in first term, check if it is the complement of any of the
301  // Value in second term.
302  for (auto t1 : readTerms)
303  for (auto t2 : writeTerms) {
304  // Return t2, t1 is a Not of t2.
305  if (!isa<BlockArgument>(t1) && isa<NotPrimOp>(t1.getDefiningOp()))
306  if (cast<NotPrimOp>(t1.getDefiningOp()).getInput() == t2)
307  return t2;
308  // Else Return t2, if t2 is a Not of t1.
309  if (!isa<BlockArgument>(t2) && isa<NotPrimOp>(t2.getDefiningOp()))
310  if (cast<NotPrimOp>(t2.getDefiningOp()).getInput() == t1)
311  return t2;
312  }
313 
314  return {};
315  }
316 
317  void handleCatPrimOp(CatPrimOp defOp, SmallVectorImpl<Value> &bits) {
318 
319  long lastSize = 0;
320  // Cat the bits of both the operands.
321  for (auto operand : defOp->getOperands()) {
322  SmallVectorImpl<Value> &opBits = valueBitsSrc[operand];
323  size_t s =
324  getBitWidth(type_cast<FIRRTLBaseType>(operand.getType())).value();
325  assert(opBits.size() == s);
326  for (long i = lastSize, e = lastSize + s; i != e; ++i)
327  bits[i] = opBits[i - lastSize];
328  lastSize = s;
329  }
330  }
331 
332  void handleBitsPrimOp(BitsPrimOp bitsPrim, SmallVectorImpl<Value> &bits) {
333 
334  SmallVectorImpl<Value> &opBits = valueBitsSrc[bitsPrim.getInput()];
335  for (size_t srcIndex = bitsPrim.getLo(), e = bitsPrim.getHi(), i = 0;
336  srcIndex <= e; ++srcIndex, ++i)
337  bits[i] = opBits[srcIndex];
338  }
339 
340  // Try to extract the value assigned to each bit of `val`. This is a heuristic
341  // to determine if each bit of the `val` is assigned the same value.
342  // Common pattern that this heuristic detects,
343  // mask = {{w1,w1},{w2,w2}}}
344  // w1 = w[0]
345  // w2 = w[0]
346  bool areBitsDrivenBySameSource(Value val) {
347  SmallVector<Value> stack;
348  stack.push_back(val);
349 
350  while (!stack.empty()) {
351  auto val = stack.back();
352  if (valueBitsSrc.contains(val)) {
353  stack.pop_back();
354  continue;
355  }
356 
357  auto size = getBitWidth(type_cast<FIRRTLBaseType>(val.getType()));
358  // Cannot analyze aggregate types.
359  if (!size.has_value())
360  return false;
361 
362  auto bitsSize = size.value();
363  if (auto *defOp = val.getDefiningOp()) {
364  if (isa<CatPrimOp>(defOp)) {
365  bool operandsDone = true;
366  // If the value is a cat of other values, compute the bits of the
367  // operands.
368  for (auto operand : defOp->getOperands()) {
369  if (valueBitsSrc.contains(operand))
370  continue;
371  stack.push_back(operand);
372  operandsDone = false;
373  }
374  if (!operandsDone)
375  continue;
376 
377  valueBitsSrc[val].resize_for_overwrite(bitsSize);
378  handleCatPrimOp(cast<CatPrimOp>(defOp), valueBitsSrc[val]);
379  } else if (auto bitsPrim = dyn_cast<BitsPrimOp>(defOp)) {
380  auto input = bitsPrim.getInput();
381  if (!valueBitsSrc.contains(input)) {
382  stack.push_back(input);
383  continue;
384  }
385  valueBitsSrc[val].resize_for_overwrite(bitsSize);
386  handleBitsPrimOp(bitsPrim, valueBitsSrc[val]);
387  } else if (auto constOp = dyn_cast<ConstantOp>(defOp)) {
388  auto constVal = constOp.getValue();
389  valueBitsSrc[val].resize_for_overwrite(bitsSize);
390  if (constVal.isAllOnes() || constVal.isZero()) {
391  for (auto &b : valueBitsSrc[val])
392  b = constOp;
393  } else
394  return false;
395  } else if (auto wireOp = dyn_cast<WireOp>(defOp)) {
396  if (bitsSize != 1)
397  return false;
398  valueBitsSrc[val].resize_for_overwrite(bitsSize);
399  if (auto src = getConnectSrc(wireOp.getResult())) {
400  valueBitsSrc[val][0] = src;
401  } else
402  valueBitsSrc[val][0] = wireOp.getResult();
403  } else
404  return false;
405  } else
406  return false;
407  stack.pop_back();
408  }
409  if (!valueBitsSrc.contains(val))
410  return false;
411  return llvm::all_equal(valueBitsSrc[val]);
412  }
413 
414  // Remove redundant dependence of wmode on the enable signal. wmode can assume
415  // the enable signal be true.
416  void simplifyWmode(MemOp &memOp) {
417 
418  // Iterate over all results, and find the enable and wmode fields of the RW
419  // port.
420  for (const auto &portIt : llvm::enumerate(memOp.getResults())) {
421  auto portKind = memOp.getPortKind(portIt.index());
422  if (portKind != MemOp::PortKind::ReadWrite)
423  continue;
424  Value enableDriver, wmodeDriver;
425  Value portVal = portIt.value();
426  // Iterate over all users of the rw port.
427  for (Operation *u : portVal.getUsers())
428  if (auto sf = dyn_cast<SubfieldOp>(u)) {
429  // Get the field name.
430  auto fName =
431  sf.getInput().getType().base().getElementName(sf.getFieldIndex());
432  // Record the enable and wmode fields.
433  if (fName.contains("en"))
434  enableDriver = getConnectSrc(sf.getResult());
435  if (fName.contains("wmode"))
436  wmodeDriver = getConnectSrc(sf.getResult());
437  }
438 
439  if (enableDriver && wmodeDriver) {
440  ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
441  builder.setInsertionPointToStart(
442  memOp->getParentOfType<FModuleOp>().getBodyBlock());
443  auto constOne = builder.create<ConstantOp>(
444  UIntType::get(builder.getContext(), 1), APInt(1, 1));
445  setEnable(enableDriver, wmodeDriver, constOne);
446  }
447  }
448  }
449 
450  // Replace any occurence of enable on the expression tree of wmode with a
451  // constant one.
452  void setEnable(Value enableDriver, Value wmodeDriver, Value constOne) {
453  auto getDriverOp = [&](Value dst) -> Operation * {
454  // Look through one level of wire to get the driver op.
455  auto *defOp = dst.getDefiningOp();
456  if (defOp) {
457  if (isa<WireOp>(defOp))
458  dst = getConnectSrc(dst);
459  if (dst)
460  defOp = dst.getDefiningOp();
461  }
462  return defOp;
463  };
464  SmallVector<Value> stack;
465  llvm::SmallDenseSet<Value> visited;
466  stack.push_back(wmodeDriver);
467  while (!stack.empty()) {
468  auto driver = stack.pop_back_val();
469  if (!visited.insert(driver).second)
470  continue;
471  auto *defOp = getDriverOp(driver);
472  if (!defOp)
473  continue;
474  for (auto operand : llvm::enumerate(defOp->getOperands())) {
475  if (operand.value() == enableDriver)
476  defOp->setOperand(operand.index(), constOne);
477  else
478  stack.push_back(operand.value());
479  }
480  }
481  }
482 
483  void inferUnmasked(MemOp &memOp, SmallVector<Operation *> &opsToErase) {
484  bool isMasked = true;
485 
486  // Iterate over all results, and check if the mask field of the result is
487  // connected to a multi-bit constant 1.
488  for (const auto &portIt : llvm::enumerate(memOp.getResults())) {
489  // Read ports donot have the mask field.
490  if (memOp.getPortKind(portIt.index()) == MemOp::PortKind::Read ||
491  memOp.getPortKind(portIt.index()) == MemOp::PortKind::Debug)
492  continue;
493  Value portVal = portIt.value();
494  // Iterate over all users of the write/rw port.
495  for (Operation *u : portVal.getUsers())
496  if (auto sf = dyn_cast<SubfieldOp>(u)) {
497  // Get the field name.
498  auto fName =
499  sf.getInput().getType().base().getElementName(sf.getFieldIndex());
500  // Check if this is the mask field.
501  if (fName.contains("mask")) {
502  // Already 1 bit, nothing to do.
503  if (sf.getResult().getType().getBitWidthOrSentinel() == 1)
504  continue;
505  // Check what is the mask field directly connected to.
506  // If we can infer that all the bits of the mask are always assigned
507  // the same value, then the memory is unmasked.
508  if (auto maskVal = getConnectSrc(sf))
509  if (areBitsDrivenBySameSource(maskVal))
510  isMasked = false;
511  }
512  }
513  }
514 
515  if (!isMasked) {
516  // Replace with a new memory of 1 bit mask.
517  ImplicitLocOpBuilder builder(memOp.getLoc(), memOp);
518  // Copy the result type, except the mask bits.
519  SmallVector<Type, 4> resultTypes;
520  for (size_t i = 0, e = memOp.getNumResults(); i != e; ++i)
521  resultTypes.push_back(
522  MemOp::getTypeForPort(memOp.getDepth(), memOp.getDataType(),
523  memOp.getPortKind(i), /*maskBits=*/1));
524 
525  // Copy everything from old memory, except the result type.
526  auto newMem = builder.create<MemOp>(
527  resultTypes, memOp.getReadLatency(), memOp.getWriteLatency(),
528  memOp.getDepth(), memOp.getRuw(), memOp.getPortNames().getValue(),
529  memOp.getNameAttr(), memOp.getNameKind(),
530  memOp.getAnnotations().getValue(),
531  memOp.getPortAnnotations().getValue(), memOp.getInnerSymAttr());
532  // Now replace the result of old memory with the new one.
533  for (const auto &portIt : llvm::enumerate(memOp.getResults())) {
534  // Old result.
535  Value oldPort = portIt.value();
536  // New result.
537  auto newPortVal = newMem->getResult(portIt.index());
538  // If read port, then blindly replace.
539  if (memOp.getPortKind(portIt.index()) == MemOp::PortKind::Read ||
540  memOp.getPortKind(portIt.index()) == MemOp::PortKind::Debug) {
541  oldPort.replaceAllUsesWith(newPortVal);
542  continue;
543  }
544  // Otherwise, all fields can be blindly replaced, except mask field.
545  for (Operation *u : oldPort.getUsers()) {
546  auto oldRes = dyn_cast<SubfieldOp>(u);
547  auto sf =
548  builder.create<SubfieldOp>(newPortVal, oldRes.getFieldIndex());
549  auto fName =
550  sf.getInput().getType().base().getElementName(sf.getFieldIndex());
551  // Replace all mask fields with a one bit constant 1.
552  // Replace all other fields with the new port.
553  if (fName.contains("mask")) {
554  WireOp dummy = builder.create<WireOp>(oldRes.getType());
555  oldRes->replaceAllUsesWith(dummy);
556  builder.create<StrictConnectOp>(
557  sf, builder.create<ConstantOp>(
558  UIntType::get(builder.getContext(), 1), APInt(1, 1)));
559  } else
560  oldRes->replaceAllUsesWith(sf);
561 
562  opsToErase.push_back(oldRes);
563  }
564  }
565  opsToErase.push_back(memOp);
566  memOp = newMem;
567  }
568  }
569 
570  // Record of what are the source values that drive each bit of a value. Used
571  // to check if each bit of a value is being driven by the same source.
572  llvm::DenseMap<Value, SmallVector<Value>> valueBitsSrc;
573 };
574 } // end anonymous namespace
575 
576 std::unique_ptr<mlir::Pass> circt::firrtl::createInferReadWritePass() {
577  return std::make_unique<InferReadWritePass>();
578 }
assert(baseType &&"element must be base type")
static InstancePath empty
Builder builder
def connect(destination, source)
Definition: support.py:37
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:54
std::unique_ptr< mlir::Pass > createInferReadWritePass()
std::optional< int64_t > getBitWidth(FIRRTLBaseType type, bool ignoreFlip=false)
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21