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