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