CIRCT  20.0.0git
LowerMemory.cpp
Go to the documentation of this file.
1 //===- LowerMemory.cpp - Lower Memories -------------------------*- C++ -*-===//
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 // This file defines the LowerMemories pass.
9 //
10 //===----------------------------------------------------------------------===//
11 
18 #include "circt/Dialect/HW/HWOps.h"
21 #include "mlir/IR/Dominance.h"
22 #include "mlir/Pass/Pass.h"
23 #include "llvm/ADT/DepthFirstIterator.h"
24 #include "llvm/ADT/STLExtras.h"
25 #include "llvm/ADT/STLFunctionalExtras.h"
26 #include "llvm/ADT/SmallPtrSet.h"
27 #include "llvm/Support/Parallel.h"
28 #include <optional>
29 #include <set>
30 
31 namespace circt {
32 namespace firrtl {
33 #define GEN_PASS_DEF_LOWERMEMORY
34 #include "circt/Dialect/FIRRTL/Passes.h.inc"
35 } // namespace firrtl
36 } // namespace circt
37 
38 using namespace circt;
39 using namespace firrtl;
40 
41 // Extract all the relevant attributes from the MemOp and return the FirMemory.
42 FirMemory getSummary(MemOp op) {
43  size_t numReadPorts = 0;
44  size_t numWritePorts = 0;
45  size_t numReadWritePorts = 0;
47  SmallVector<int32_t> writeClockIDs;
48 
49  for (size_t i = 0, e = op.getNumResults(); i != e; ++i) {
50  auto portKind = op.getPortKind(i);
51  if (portKind == MemOp::PortKind::Read)
52  ++numReadPorts;
53  else if (portKind == MemOp::PortKind::Write) {
54  for (auto *a : op.getResult(i).getUsers()) {
55  auto subfield = dyn_cast<SubfieldOp>(a);
56  if (!subfield || subfield.getFieldIndex() != 2)
57  continue;
58  auto clockPort = a->getResult(0);
59  for (auto *b : clockPort.getUsers()) {
60  if (auto connect = dyn_cast<FConnectLike>(b)) {
61  if (connect.getDest() == clockPort) {
62  auto result =
63  clockToLeader.insert({connect.getSrc(), numWritePorts});
64  if (result.second) {
65  writeClockIDs.push_back(numWritePorts);
66  } else {
67  writeClockIDs.push_back(result.first->second);
68  }
69  }
70  }
71  }
72  break;
73  }
74  ++numWritePorts;
75  } else
76  ++numReadWritePorts;
77  }
78 
79  auto width = op.getDataType().getBitWidthOrSentinel();
80  if (width <= 0) {
81  op.emitError("'firrtl.mem' should have simple type and known width");
82  width = 0;
83  }
84  return {numReadPorts,
85  numWritePorts,
86  numReadWritePorts,
87  (size_t)width,
88  op.getDepth(),
89  op.getReadLatency(),
90  op.getWriteLatency(),
91  op.getMaskBits(),
92  *seq::symbolizeRUW(unsigned(op.getRuw())),
93  seq::WUW::PortOrder,
94  writeClockIDs,
95  op.getNameAttr(),
96  op.getMaskBits() > 1,
97  op.getInitAttr(),
98  op.getPrefixAttr(),
99  op.getLoc()};
100 }
101 
102 namespace {
103 struct LowerMemoryPass
104  : public circt::firrtl::impl::LowerMemoryBase<LowerMemoryPass> {
105 
106  /// Get the cached namespace for a module.
107  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike moduleOp) {
108  return moduleNamespaces.try_emplace(moduleOp, moduleOp).first->second;
109  }
110 
111  SmallVector<PortInfo> getMemoryModulePorts(const FirMemory &mem);
112  FMemModuleOp emitMemoryModule(MemOp op, const FirMemory &summary,
113  const SmallVectorImpl<PortInfo> &ports);
114  FMemModuleOp getOrCreateMemModule(MemOp op, const FirMemory &summary,
115  const SmallVectorImpl<PortInfo> &ports,
116  bool shouldDedup);
117  FModuleOp createWrapperModule(MemOp op, const FirMemory &summary,
118  bool shouldDedup);
119  InstanceOp emitMemoryInstance(MemOp op, FModuleOp moduleOp,
120  const FirMemory &summary);
121  void lowerMemory(MemOp mem, const FirMemory &summary, bool shouldDedup);
122  LogicalResult runOnModule(FModuleOp moduleOp, bool shouldDedup);
123  void runOnOperation() override;
124 
125  /// Cached module namespaces.
126  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
127  CircuitNamespace circuitNamespace;
128  SymbolTable *symbolTable;
129 
130  /// The set of all memories seen so far. This is used to "deduplicate"
131  /// memories by emitting modules one module for equivalent memories.
132  std::map<FirMemory, FMemModuleOp> memories;
133 
134  /// A sequence of operations that should be erased later.
135  SetVector<Operation *> operationsToErase;
136 };
137 } // end anonymous namespace
138 
139 SmallVector<PortInfo>
140 LowerMemoryPass::getMemoryModulePorts(const FirMemory &mem) {
141  auto *context = &getContext();
142 
143  // We don't need a single bit mask, it can be combined with enable. Create
144  // an unmasked memory if maskBits = 1.
145  FIRRTLType u1Type = UIntType::get(context, 1);
146  FIRRTLType dataType = UIntType::get(context, mem.dataWidth);
147  FIRRTLType maskType = UIntType::get(context, mem.maskBits);
148  FIRRTLType addrType =
149  UIntType::get(context, std::max(1U, llvm::Log2_64_Ceil(mem.depth)));
150  FIRRTLType clockType = ClockType::get(context);
151  Location loc = UnknownLoc::get(context);
152  AnnotationSet annotations = AnnotationSet(context);
153 
154  SmallVector<PortInfo> ports;
155  auto addPort = [&](const Twine &name, FIRRTLType type, Direction direction) {
156  auto nameAttr = StringAttr::get(context, name);
157  ports.push_back(
158  {nameAttr, type, direction, hw::InnerSymAttr{}, loc, annotations});
159  };
160 
161  auto makePortCommon = [&](StringRef prefix, size_t idx, FIRRTLType addrType) {
162  addPort(prefix + Twine(idx) + "_addr", addrType, Direction::In);
163  addPort(prefix + Twine(idx) + "_en", u1Type, Direction::In);
164  addPort(prefix + Twine(idx) + "_clk", clockType, Direction::In);
165  };
166 
167  for (size_t i = 0, e = mem.numReadPorts; i != e; ++i) {
168  makePortCommon("R", i, addrType);
169  addPort("R" + Twine(i) + "_data", dataType, Direction::Out);
170  }
171  for (size_t i = 0, e = mem.numReadWritePorts; i != e; ++i) {
172  makePortCommon("RW", i, addrType);
173  addPort("RW" + Twine(i) + "_wmode", u1Type, Direction::In);
174  addPort("RW" + Twine(i) + "_wdata", dataType, Direction::In);
175  addPort("RW" + Twine(i) + "_rdata", dataType, Direction::Out);
176  // Ignore mask port, if maskBits =1
177  if (mem.isMasked)
178  addPort("RW" + Twine(i) + "_wmask", maskType, Direction::In);
179  }
180 
181  for (size_t i = 0, e = mem.numWritePorts; i != e; ++i) {
182  makePortCommon("W", i, addrType);
183  addPort("W" + Twine(i) + "_data", dataType, Direction::In);
184  // Ignore mask port, if maskBits =1
185  if (mem.isMasked)
186  addPort("W" + Twine(i) + "_mask", maskType, Direction::In);
187  }
188 
189  return ports;
190 }
191 
192 FMemModuleOp
193 LowerMemoryPass::emitMemoryModule(MemOp op, const FirMemory &mem,
194  const SmallVectorImpl<PortInfo> &ports) {
195  // Get a non-colliding name for the memory module, and update the summary.
196  StringRef prefix = "";
197  if (mem.prefix)
198  prefix = mem.prefix.getValue();
199  auto newName =
200  circuitNamespace.newName(prefix + mem.modName.getValue(), "ext");
201  auto moduleName = StringAttr::get(&getContext(), newName);
202 
203  // Insert the memory module just above the current module.
204  OpBuilder b(op->getParentOfType<FModuleOp>());
205  ++numCreatedMemModules;
206  auto moduleOp = b.create<FMemModuleOp>(
207  mem.loc, moduleName, ports, mem.numReadPorts, mem.numWritePorts,
208  mem.numReadWritePorts, mem.dataWidth, mem.maskBits, mem.readLatency,
209  mem.writeLatency, mem.depth);
210  SymbolTable::setSymbolVisibility(moduleOp, SymbolTable::Visibility::Private);
211  return moduleOp;
212 }
213 
214 FMemModuleOp
215 LowerMemoryPass::getOrCreateMemModule(MemOp op, const FirMemory &summary,
216  const SmallVectorImpl<PortInfo> &ports,
217  bool shouldDedup) {
218  // Try to find a matching memory blackbox that we already created. If
219  // shouldDedup is true, we will just generate a new memory module.
220  if (shouldDedup) {
221  auto it = memories.find(summary);
222  if (it != memories.end())
223  return it->second;
224  }
225 
226  // Create a new module for this memory. This can update the name recorded in
227  // the memory's summary.
228  auto moduleOp = emitMemoryModule(op, summary, ports);
229 
230  // Record the memory module. We don't want to use this module for other
231  // memories, then we don't add it to the table.
232  if (shouldDedup)
233  memories[summary] = moduleOp;
234 
235  return moduleOp;
236 }
237 
238 void LowerMemoryPass::lowerMemory(MemOp mem, const FirMemory &summary,
239  bool shouldDedup) {
240  auto *context = &getContext();
241  auto ports = getMemoryModulePorts(summary);
242 
243  // Get a non-colliding name for the memory module, and update the summary.
244  StringRef prefix = "";
245  if (summary.prefix)
246  prefix = summary.prefix.getValue();
247  auto newName = circuitNamespace.newName(prefix + mem.getName());
248 
249  auto wrapperName = StringAttr::get(&getContext(), newName);
250 
251  // Create the wrapper module, inserting it just before the current module.
252  OpBuilder b(mem->getParentOfType<FModuleOp>());
253  auto wrapper = b.create<FModuleOp>(
254  mem->getLoc(), wrapperName,
255  ConventionAttr::get(context, Convention::Internal), ports);
256  SymbolTable::setSymbolVisibility(wrapper, SymbolTable::Visibility::Private);
257 
258  // Create an instance of the external memory module. The instance has the
259  // same name as the target module.
260  auto memModule = getOrCreateMemModule(mem, summary, ports, shouldDedup);
261  b.setInsertionPointToStart(wrapper.getBodyBlock());
262  auto memInst = b.create<InstanceOp>(
263  mem->getLoc(), memModule, (mem.getName() + "_ext").str(),
264  mem.getNameKind(), mem.getAnnotations().getValue());
265 
266  // Wire all the ports together.
267  for (auto [dst, src] : llvm::zip(wrapper.getBodyBlock()->getArguments(),
268  memInst.getResults())) {
269  if (wrapper.getPortDirection(dst.getArgNumber()) == Direction::Out)
270  b.create<MatchingConnectOp>(mem->getLoc(), dst, src);
271  else
272  b.create<MatchingConnectOp>(mem->getLoc(), src, dst);
273  }
274 
275  // Create an instance of the wrapper memory module, which will replace the
276  // original mem op.
277  auto inst = emitMemoryInstance(mem, wrapper, summary);
278 
279  // We fixup the annotations here. We will be copying all annotations on to the
280  // module op, so we have to fix up the NLA to have the module as the leaf
281  // element.
282 
283  auto leafSym = memModule.getModuleNameAttr();
284  auto leafAttr = FlatSymbolRefAttr::get(wrapper.getModuleNameAttr());
285 
286  // NLAs that we have already processed.
288  auto nonlocalAttr = StringAttr::get(context, "circt.nonlocal");
289  bool nlaUpdated = false;
290  SmallVector<Annotation> newMemModAnnos;
291  OpBuilder nlaBuilder(context);
292 
293  AnnotationSet::removeAnnotations(memInst, [&](Annotation anno) -> bool {
294  // We're only looking for non-local annotations.
295  auto nlaSym = anno.getMember<FlatSymbolRefAttr>(nonlocalAttr);
296  if (!nlaSym)
297  return false;
298  // If we have already seen this NLA, don't re-process it.
299  auto newNLAIter = processedNLAs.find(nlaSym.getAttr());
300  StringAttr newNLAName;
301  if (newNLAIter == processedNLAs.end()) {
302 
303  // Update the NLA path to have the additional wrapper module.
304  auto nla =
305  dyn_cast<hw::HierPathOp>(symbolTable->lookup(nlaSym.getAttr()));
306  auto namepath = nla.getNamepath().getValue();
307  SmallVector<Attribute> newNamepath(namepath.begin(), namepath.end());
308  if (!nla.isComponent())
309  newNamepath.back() =
310  getInnerRefTo(inst, [&](auto mod) -> hw::InnerSymbolNamespace & {
311  return getModuleNamespace(mod);
312  });
313  newNamepath.push_back(leafAttr);
314 
315  nlaBuilder.setInsertionPointAfter(nla);
316  auto newNLA = cast<hw::HierPathOp>(nlaBuilder.clone(*nla));
317  newNLA.setSymNameAttr(StringAttr::get(
318  context, circuitNamespace.newName(nla.getNameAttr().getValue())));
319  newNLA.setNamepathAttr(ArrayAttr::get(context, newNamepath));
320  newNLAName = newNLA.getNameAttr();
321  processedNLAs[nlaSym.getAttr()] = newNLAName;
322  } else
323  newNLAName = newNLAIter->getSecond();
324  anno.setMember("circt.nonlocal", FlatSymbolRefAttr::get(newNLAName));
325  nlaUpdated = true;
326  newMemModAnnos.push_back(anno);
327  return true;
328  });
329  if (nlaUpdated) {
330  memInst.setInnerSymAttr(hw::InnerSymAttr::get(leafSym));
331  AnnotationSet newAnnos(memInst);
332  newAnnos.addAnnotations(newMemModAnnos);
333  newAnnos.applyToOperation(memInst);
334  }
335  operationsToErase.insert(mem);
336  ++numLoweredMems;
337 }
338 
339 static SmallVector<SubfieldOp> getAllFieldAccesses(Value structValue,
340  StringRef field) {
341  SmallVector<SubfieldOp> accesses;
342  for (auto *op : structValue.getUsers()) {
343  assert(isa<SubfieldOp>(op));
344  auto fieldAccess = cast<SubfieldOp>(op);
345  auto elemIndex =
346  fieldAccess.getInput().getType().base().getElementIndex(field);
347  if (elemIndex && *elemIndex == fieldAccess.getFieldIndex())
348  accesses.push_back(fieldAccess);
349  }
350  return accesses;
351 }
352 
353 InstanceOp LowerMemoryPass::emitMemoryInstance(MemOp op, FModuleOp module,
354  const FirMemory &summary) {
355  OpBuilder builder(op);
356  auto *context = &getContext();
357  auto memName = op.getName();
358  if (memName.empty())
359  memName = "mem";
360 
361  // Process each port in turn.
362  SmallVector<Type, 8> portTypes;
363  SmallVector<Direction> portDirections;
364  SmallVector<Attribute> portNames;
365  DenseMap<Operation *, size_t> returnHolder;
366  mlir::DominanceInfo domInfo(op->getParentOfType<FModuleOp>());
367 
368  // The result values of the memory are not necessarily in the same order as
369  // the memory module that we're lowering to. We need to lower the read
370  // ports before the read/write ports, before the write ports.
371  for (unsigned memportKindIdx = 0; memportKindIdx != 3; ++memportKindIdx) {
372  MemOp::PortKind memportKind = MemOp::PortKind::Read;
373  auto *portLabel = "R";
374  switch (memportKindIdx) {
375  default:
376  break;
377  case 1:
378  memportKind = MemOp::PortKind::ReadWrite;
379  portLabel = "RW";
380  break;
381  case 2:
382  memportKind = MemOp::PortKind::Write;
383  portLabel = "W";
384  break;
385  }
386 
387  // This is set to the count of the kind of memport we're emitting, for
388  // label names.
389  unsigned portNumber = 0;
390 
391  // Get an unsigned type with the specified width.
392  auto getType = [&](size_t width) { return UIntType::get(context, width); };
393  auto ui1Type = getType(1);
394  auto addressType = getType(std::max(1U, llvm::Log2_64_Ceil(summary.depth)));
395  auto dataType = UIntType::get(context, summary.dataWidth);
396  auto clockType = ClockType::get(context);
397 
398  // Memories return multiple structs, one for each port, which means we
399  // have two layers of type to split apart.
400  for (size_t i = 0, e = op.getNumResults(); i != e; ++i) {
401  // Process all of one kind before the next.
402  if (memportKind != op.getPortKind(i))
403  continue;
404 
405  auto addPort = [&](Direction direction, StringRef field, Type portType) {
406  // Map subfields of the memory port to module ports.
407  auto accesses = getAllFieldAccesses(op.getResult(i), field);
408  for (auto a : accesses)
409  returnHolder[a] = portTypes.size();
410  // Record the new port information.
411  portTypes.push_back(portType);
412  portDirections.push_back(direction);
413  portNames.push_back(
414  builder.getStringAttr(portLabel + Twine(portNumber) + "_" + field));
415  };
416 
417  auto getDriver = [&](StringRef field) -> Operation * {
418  auto accesses = getAllFieldAccesses(op.getResult(i), field);
419  for (auto a : accesses) {
420  for (auto *user : a->getUsers()) {
421  // If this is a connect driving a value to the field, return it.
422  if (auto connect = dyn_cast<FConnectLike>(user);
423  connect && connect.getDest() == a)
424  return connect;
425  }
426  }
427  return nullptr;
428  };
429 
430  // Find the value connected to the enable and 'and' it with the mask,
431  // and then remove the mask entirely. This is used to remove the mask when
432  // it is 1 bit.
433  auto removeMask = [&](StringRef enable, StringRef mask) {
434  // Get the connect which drives a value to the mask element.
435  auto *maskConnect = getDriver(mask);
436  if (!maskConnect)
437  return;
438  // Get the connect which drives a value to the en element
439  auto *enConnect = getDriver(enable);
440  if (!enConnect)
441  return;
442  // Find the proper place to create the And operation. The mask and en
443  // signals must both dominate the new operation.
444  OpBuilder b(maskConnect);
445  if (domInfo.dominates(maskConnect, enConnect))
446  b.setInsertionPoint(enConnect);
447  // 'and' the enable and mask signals together and use it as the enable.
448  auto andOp = b.create<AndPrimOp>(
449  op->getLoc(), maskConnect->getOperand(1), enConnect->getOperand(1));
450  enConnect->setOperand(1, andOp);
451  enConnect->moveAfter(andOp);
452  // Erase the old mask connect.
453  auto *maskField = maskConnect->getOperand(0).getDefiningOp();
454  operationsToErase.insert(maskConnect);
455  operationsToErase.insert(maskField);
456  };
457 
458  if (memportKind == MemOp::PortKind::Read) {
459  addPort(Direction::In, "addr", addressType);
460  addPort(Direction::In, "en", ui1Type);
461  addPort(Direction::In, "clk", clockType);
462  addPort(Direction::Out, "data", dataType);
463  } else if (memportKind == MemOp::PortKind::ReadWrite) {
464  addPort(Direction::In, "addr", addressType);
465  addPort(Direction::In, "en", ui1Type);
466  addPort(Direction::In, "clk", clockType);
467  addPort(Direction::In, "wmode", ui1Type);
468  addPort(Direction::In, "wdata", dataType);
469  addPort(Direction::Out, "rdata", dataType);
470  // Ignore mask port, if maskBits =1
471  if (summary.isMasked)
472  addPort(Direction::In, "wmask", getType(summary.maskBits));
473  else
474  removeMask("wmode", "wmask");
475  } else {
476  addPort(Direction::In, "addr", addressType);
477  addPort(Direction::In, "en", ui1Type);
478  addPort(Direction::In, "clk", clockType);
479  addPort(Direction::In, "data", dataType);
480  // Ignore mask port, if maskBits == 1
481  if (summary.isMasked)
482  addPort(Direction::In, "mask", getType(summary.maskBits));
483  else
484  removeMask("en", "mask");
485  }
486 
487  ++portNumber;
488  }
489  }
490 
491  // Create the instance to replace the memop. The instance name matches the
492  // name of the original memory module before deduplication.
493  // TODO: how do we lower port annotations?
494  auto inst = builder.create<InstanceOp>(
495  op.getLoc(), portTypes, module.getNameAttr(), summary.getFirMemoryName(),
496  op.getNameKind(), portDirections, portNames,
497  /*annotations=*/ArrayRef<Attribute>(),
498  /*portAnnotations=*/ArrayRef<Attribute>(),
499  /*layers=*/ArrayRef<Attribute>(), /*lowerToBind=*/false,
500  op.getInnerSymAttr());
501 
502  // Update all users of the result of read ports
503  for (auto [subfield, result] : returnHolder) {
504  subfield->getResult(0).replaceAllUsesWith(inst.getResult(result));
505  operationsToErase.insert(subfield);
506  }
507 
508  return inst;
509 }
510 
511 LogicalResult LowerMemoryPass::runOnModule(FModuleOp moduleOp,
512  bool shouldDedup) {
513  assert(operationsToErase.empty() && "operationsToErase must be empty");
514 
515  auto result = moduleOp.walk([&](MemOp op) {
516  // Check that the memory has been properly lowered already.
517  if (!type_isa<UIntType>(op.getDataType())) {
518  op->emitError("memories should be flattened before running LowerMemory");
519  return WalkResult::interrupt();
520  }
521 
522  auto summary = getSummary(op);
523  if (summary.isSeqMem())
524  lowerMemory(op, summary, shouldDedup);
525 
526  return WalkResult::advance();
527  });
528 
529  if (result.wasInterrupted())
530  return failure();
531 
532  for (Operation *op : operationsToErase)
533  op->erase();
534 
535  operationsToErase.clear();
536 
537  return success();
538 }
539 
540 void LowerMemoryPass::runOnOperation() {
541  auto circuit = getOperation();
542  auto &instanceInfo = getAnalysis<InstanceInfo>();
543  symbolTable = &getAnalysis<SymbolTable>();
544  circuitNamespace.add(circuit);
545 
546  // We iterate the circuit from top-to-bottom. This ensures that we get
547  // consistent memory names. (Memory modules will be inserted before the
548  // module we are processing to prevent these being unnecessarily visited.)
549  // Deduplication of memories is allowed if the module is under the "effective"
550  // design-under-test (DUT).
551  for (auto moduleOp : circuit.getBodyBlock()->getOps<FModuleOp>()) {
552  auto shouldDedup = instanceInfo.anyInstanceInEffectiveDesign(moduleOp);
553  if (failed(runOnModule(moduleOp, shouldDedup)))
554  return signalPassFailure();
555  }
556 
557  circuitNamespace.clear();
558  symbolTable = nullptr;
559  memories.clear();
560 }
561 
562 std::unique_ptr<mlir::Pass> circt::firrtl::createLowerMemoryPass() {
563  return std::make_unique<LowerMemoryPass>();
564 }
assert(baseType &&"element must be base type")
static SmallVector< SubfieldOp > getAllFieldAccesses(Value structValue, StringRef field)
FirMemory getSummary(MemOp op)
Definition: LowerMemory.cpp:42
This class provides a read-only projection over the MLIR attributes that represent a set of annotatio...
bool removeAnnotations(llvm::function_ref< bool(Annotation)> predicate)
Remove all annotations from this annotation set for which predicate returns true.
This class provides a read-only projection of an annotation.
AttrClass getMember(StringAttr name) const
Return a member of the annotation.
void setMember(StringAttr name, Attribute value)
Add or set a member of the annotation to a value.
def connect(destination, source)
Definition: support.py:39
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:55
Direction
This represents the direction of a single port.
hw::InnerRefAttr getInnerRefTo(const hw::InnerSymTarget &target, GetNamespaceCallback getNamespace)
Obtain an inner reference to the target (operation or port), adding an inner symbol as necessary.
std::unique_ptr< mlir::Pass > createLowerMemoryPass()
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
Definition: DebugAnalysis.h:21
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
bool isSeqMem() const
Check whether the memory is a seq mem.
Definition: FIRRTLOps.h:214
StringAttr getFirMemoryName() const
Definition: FIRRTLOps.cpp:3318