CIRCT  18.0.0git
ExtractInstances.cpp
Go to the documentation of this file.
1 //===- ExtractInstances.cpp - Move instances up the hierarchy ---*- 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 //
9 // Moves annotated instances upwards in the module hierarchy. Corresponds to the
10 // `ExtractBlackBoxes`, `ExtractClockGates`, and `ExtractSeqMems` passes in the
11 // Scala FIRRTL implementation.
12 //
13 //===----------------------------------------------------------------------===//
14 
15 #include "PassDetails.h"
26 #include "circt/Dialect/SV/SVOps.h"
27 #include "circt/Support/Path.h"
28 #include "mlir/IR/Attributes.h"
29 #include "mlir/IR/ImplicitLocOpBuilder.h"
30 #include "mlir/Support/FileUtilities.h"
31 #include "llvm/ADT/SmallPtrSet.h"
32 #include "llvm/Support/Debug.h"
33 #include "llvm/Support/MemoryBuffer.h"
34 #include "llvm/Support/Path.h"
35 
36 #define DEBUG_TYPE "firrtl-extract-instances"
37 
38 using namespace circt;
39 using namespace firrtl;
40 using hw::InnerRefAttr;
41 
42 //===----------------------------------------------------------------------===//
43 // Pass Implementation
44 //===----------------------------------------------------------------------===//
45 
46 namespace {
47 /// All information necessary to move instances about.
48 struct ExtractionInfo {
49  /// A filename into which the performed hierarchy modifications are emitted.
50  StringRef traceFilename;
51  /// A prefix to attach to the wiring generated by the extraction.
52  StringRef prefix;
53  /// Optional name of the wrapper module that will hold the moved instance.
54  StringRef wrapperModule;
55  /// Whether the extraction should stop at the root of the DUT instead of going
56  /// past that and extracting into the test harness.
57  bool stopAtDUT;
58 };
59 
60 struct ExtractInstancesPass
61  : public ExtractInstancesBase<ExtractInstancesPass> {
62  void runOnOperation() override;
63  void collectAnnos();
64  void collectAnno(InstanceOp inst, Annotation anno);
65  void extractInstances();
66  void groupInstances();
67  void createTraceFiles();
68 
69  /// Get the cached namespace for a module.
70  hw::InnerSymbolNamespace &getModuleNamespace(FModuleLike module) {
71  return moduleNamespaces.try_emplace(module, module).first->second;
72  }
73 
74  /// Obtain an inner reference to an operation, possibly adding an `inner_sym`
75  /// to that operation.
76  InnerRefAttr getInnerRefTo(Operation *op) {
78  [&](FModuleLike mod) -> hw::InnerSymbolNamespace & {
79  return getModuleNamespace(mod);
80  });
81  }
82 
83  /// Create a clone of a `HierPathOp` with a new uniquified name.
84  hw::HierPathOp cloneWithNewNameAndPath(hw::HierPathOp pathOp,
85  ArrayRef<Attribute> newPath) {
86  OpBuilder builder(pathOp);
87  auto newPathOp = builder.cloneWithoutRegions(pathOp);
88  newPathOp.setSymNameAttr(builder.getStringAttr(
89  circuitNamespace.newName(newPathOp.getSymName())));
90  newPathOp.setNamepathAttr(builder.getArrayAttr(newPath));
91  return newPathOp;
92  }
93 
94  bool anythingChanged;
95  bool anyFailures;
96 
97  InstanceGraph *instanceGraph = nullptr;
98 
99  /// The modules in the design that are annotated with one or more annotations
100  /// relevant for instance extraction.
101  DenseMap<Operation *, SmallVector<Annotation, 1>> annotatedModules;
102 
103  /// All modules that are marked as DUT themselves. Realistically this is only
104  /// ever one module in the design.
105  DenseSet<Operation *> dutRootModules;
106  /// All modules that are marked as DUT themselves, or that have a DUT parent
107  /// module.
108  DenseSet<Operation *> dutModules;
109  /// All DUT module names.
110  DenseSet<Attribute> dutModuleNames;
111  /// The prefix of the DUT module. This is used when creating new modules
112  /// under the DUT.
113  StringRef dutPrefix = "";
114 
115  /// A worklist of instances that need to be moved.
116  SmallVector<std::pair<InstanceOp, ExtractionInfo>> extractionWorklist;
117 
118  /// The path along which instances have been extracted. This essentially
119  /// documents the original location of the instance in reverse. Every push
120  /// upwards in the hierarchy adds another entry to this path documenting along
121  /// which instantiation path each instance was extracted.
122  DenseMap<Operation *, SmallVector<InnerRefAttr>> extractionPaths;
123 
124  /// A map of the original parent modules of instances before they were
125  /// extracted. This is used in a corner case during trace file emission.
126  DenseMap<Operation *, StringAttr> originalInstanceParents;
127 
128  /// All extracted instances in their position after moving upwards in the
129  /// hierarchy, but before being grouped into an optional submodule.
130  SmallVector<std::pair<InstanceOp, ExtractionInfo>> extractedInstances;
131 
132  // The uniquified wiring prefix for each instance.
133  DenseMap<Operation *, SmallString<16>> instPrefices;
134 
135  /// The current circuit namespace valid within the call to `runOnOperation`.
136  CircuitNamespace circuitNamespace;
137  /// Cached module namespaces.
138  DenseMap<Operation *, hw::InnerSymbolNamespace> moduleNamespaces;
139 };
140 } // end anonymous namespace
141 
142 /// Emit the annotated source code for black boxes in a circuit.
143 void ExtractInstancesPass::runOnOperation() {
144  CircuitOp circuitOp = getOperation();
145  anythingChanged = false;
146  anyFailures = false;
147  annotatedModules.clear();
148  dutRootModules.clear();
149  dutModules.clear();
150  extractionWorklist.clear();
151  extractionPaths.clear();
152  originalInstanceParents.clear();
153  extractedInstances.clear();
154  instPrefices.clear();
155  moduleNamespaces.clear();
156  circuitNamespace.clear();
157  circuitNamespace.add(circuitOp);
158 
159  // Walk the IR and gather all the annotations relevant for extraction that
160  // appear on instances and the instantiated modules.
161  instanceGraph = &getAnalysis<InstanceGraph>();
162  collectAnnos();
163  if (anyFailures)
164  return signalPassFailure();
165 
166  // Actually move instances upwards.
167  extractInstances();
168  if (anyFailures)
169  return signalPassFailure();
170 
171  // Group instances into submodules, if requested.
172  groupInstances();
173  if (anyFailures)
174  return signalPassFailure();
175 
176  // Generate the trace files that list where each instance was extracted from.
177  createTraceFiles();
178  if (anyFailures)
179  return signalPassFailure();
180 
181  // If nothing has changed we can preserve the analysis.
182  LLVM_DEBUG(llvm::dbgs() << "\n");
183  if (!anythingChanged)
184  markAllAnalysesPreserved();
185 }
186 
187 static bool isAnnoInteresting(Annotation anno) {
188  return anno.isClass(extractBlackBoxAnnoClass);
189 }
190 
191 /// Gather the modules and instances annotated to be moved by this pass. This
192 /// populates the corresponding lists and maps of the pass.
193 void ExtractInstancesPass::collectAnnos() {
194  CircuitOp circuit = getOperation();
195 
196  // Grab the clock gate extraction annotation on the circuit.
197  StringRef clkgateFileName;
198  StringRef clkgateWrapperModule;
199  AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
201  return false;
202  LLVM_DEBUG(llvm::dbgs()
203  << "Clock gate extraction config: " << anno.getDict() << "\n");
204  auto filenameAttr = anno.getMember<StringAttr>("filename");
205  auto groupAttr = anno.getMember<StringAttr>("group");
206  if (!filenameAttr) {
207  circuit.emitError("missing `filename` attribute in `")
208  << anno.getClass() << "` annotation";
209  anyFailures = true;
210  return true;
211  }
212 
213  if (!clkgateFileName.empty()) {
214  circuit.emitError("multiple `")
215  << anno.getClass() << "` annotations on circuit";
216  anyFailures = true;
217  return true;
218  }
219 
220  clkgateFileName = filenameAttr.getValue();
221  if (groupAttr)
222  clkgateWrapperModule = groupAttr.getValue();
223  return true;
224  });
225 
226  // Grab the memory extraction annotation on the circuit.
227  StringRef memoryFileName;
228  StringRef memoryWrapperModule;
229  AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
230  if (!anno.isClass(extractSeqMemsAnnoClass))
231  return false;
232  LLVM_DEBUG(llvm::dbgs()
233  << "Memory extraction config: " << anno.getDict() << "\n");
234  auto filenameAttr = anno.getMember<StringAttr>("filename");
235  auto groupAttr = anno.getMember<StringAttr>("group");
236  if (!filenameAttr) {
237  circuit.emitError("missing `filename` attribute in `")
238  << anno.getClass() << "` annotation";
239  anyFailures = true;
240  return true;
241  }
242 
243  if (!memoryFileName.empty()) {
244  circuit.emitError("multiple `")
245  << anno.getClass() << "` annotations on circuit";
246  anyFailures = true;
247  return true;
248  }
249 
250  memoryFileName = filenameAttr.getValue();
251  if (groupAttr)
252  memoryWrapperModule = groupAttr.getValue();
253  return true;
254  });
255 
256  // Gather the annotations on modules. These complement the later per-instance
257  // annotations.
258  for (auto module : circuit.getOps<FModuleLike>()) {
259  AnnotationSet::removeAnnotations(module, [&](Annotation anno) {
260  if (anno.isClass(dutAnnoClass)) {
261  LLVM_DEBUG(llvm::dbgs()
262  << "Marking DUT `" << module.getModuleName() << "`\n");
263  dutRootModules.insert(module);
264  dutModules.insert(module);
265  if (auto prefix = anno.getMember<StringAttr>("prefix"))
266  dutPrefix = prefix;
267  return false; // other passes may rely on this anno; keep it
268  }
269  if (!isAnnoInteresting(anno))
270  return false;
271  LLVM_DEBUG(llvm::dbgs() << "Annotated module `" << module.getModuleName()
272  << "`:\n " << anno.getDict() << "\n");
273  annotatedModules[module].push_back(anno);
274  return true;
275  });
276  }
277 
278  // Gather the annotations on instances to be extracted.
279  circuit.walk([&](InstanceOp inst) {
280  SmallVector<Annotation, 1> instAnnos;
281  Operation *module = instanceGraph->getReferencedModule(inst);
282 
283  // Module-level annotations.
284  auto it = annotatedModules.find(module);
285  if (it != annotatedModules.end())
286  instAnnos.append(it->second);
287 
288  // Instance-level annotations.
290  if (!isAnnoInteresting(anno))
291  return false;
292  LLVM_DEBUG(llvm::dbgs() << "Annotated instance `" << inst.getName()
293  << "`:\n " << anno.getDict() << "\n");
294  instAnnos.push_back(anno);
295  return true;
296  });
297 
298  // No need to do anything about unannotated instances.
299  if (instAnnos.empty())
300  return;
301 
302  // Ensure there are no conflicting annotations.
303  if (instAnnos.size() > 1) {
304  auto d = inst.emitError("multiple extraction annotations on instance `")
305  << inst.getName() << "`";
306  d.attachNote(inst.getLoc()) << "instance has the following annotations, "
307  "but at most one is allowed:";
308  for (auto anno : instAnnos)
309  d.attachNote(inst.getLoc()) << anno.getDict();
310  anyFailures = true;
311  return;
312  }
313 
314  // Process the annotation.
315  collectAnno(inst, instAnnos[0]);
316  });
317 
318  // Propagate the DUT marking to all arbitrarily nested submodules of the DUT.
319  LLVM_DEBUG(llvm::dbgs() << "Marking DUT hierarchy\n");
320  SmallVector<InstanceGraphNode *> worklist;
321  for (Operation *op : dutModules)
322  worklist.push_back(
323  instanceGraph->lookup(cast<igraph::ModuleOpInterface>(op)));
324  while (!worklist.empty()) {
325  auto *module = worklist.pop_back_val();
326  dutModuleNames.insert(module->getModule().getModuleNameAttr());
327  LLVM_DEBUG(llvm::dbgs()
328  << "- " << module->getModule().getModuleName() << "\n");
329  for (auto *instRecord : *module) {
330  auto *target = instRecord->getTarget();
331  if (dutModules.insert(target->getModule()).second)
332  worklist.push_back(target);
333  }
334  }
335 
336  // If clock gate extraction is requested, find instances of extmodules with
337  // the corresponding `defname` and mark them as to be extracted.
338  // TODO: This defname really shouldn't be hardcoded here. Make this at least
339  // somewhat configurable.
340  if (!clkgateFileName.empty()) {
341  auto clkgateDefNameAttr = StringAttr::get(&getContext(), "EICG_wrapper");
342  for (auto module : circuit.getOps<FExtModuleOp>()) {
343  if (module.getDefnameAttr() != clkgateDefNameAttr)
344  continue;
345  LLVM_DEBUG(llvm::dbgs()
346  << "Clock gate `" << module.getModuleName() << "`\n");
347  if (!dutModules.contains(module)) {
348  LLVM_DEBUG(llvm::dbgs() << "- Ignored (outside DUT)\n");
349  continue;
350  }
351 
352  ExtractionInfo info;
353  info.traceFilename = clkgateFileName;
354  info.prefix = "clock_gate"; // TODO: Don't hardcode this
355  info.wrapperModule = clkgateWrapperModule;
356  info.stopAtDUT = !info.wrapperModule.empty();
357  for (auto *instRecord : instanceGraph->lookup(module)->uses()) {
358  if (auto inst = dyn_cast<InstanceOp>(*instRecord->getInstance())) {
359  LLVM_DEBUG(llvm::dbgs()
360  << "- Marking `"
361  << inst->getParentOfType<FModuleLike>().getModuleName()
362  << "." << inst.getName() << "`\n");
363  extractionWorklist.push_back({inst, info});
364  }
365  }
366  }
367  }
368 
369  // If memory extraction is requested, find instances of `FMemModuleOp` and
370  // mark them as to be extracted.
371  // somewhat configurable.
372  if (!memoryFileName.empty()) {
373  // Create an empty verbatim to guarantee that this file will exist even if
374  // no memories are found. This is done to align with the SFC implementation
375  // of this pass where the file is always created. This does introduce an
376  // additional leading newline in the file.
377  auto *context = circuit.getContext();
378  auto builder = ImplicitLocOpBuilder::atBlockEnd(UnknownLoc::get(context),
379  circuit.getBodyBlock());
380  builder.create<sv::VerbatimOp>("")->setAttr(
381  "output_file",
382  hw::OutputFileAttr::getFromFilename(context, memoryFileName,
383  /*excludeFromFilelist=*/true));
384 
385  for (auto module : circuit.getOps<FMemModuleOp>()) {
386  LLVM_DEBUG(llvm::dbgs() << "Memory `" << module.getModuleName() << "`\n");
387  if (!dutModules.contains(module)) {
388  LLVM_DEBUG(llvm::dbgs() << "- Ignored (outside DUT)\n");
389  continue;
390  }
391 
392  ExtractionInfo info;
393  info.traceFilename = memoryFileName;
394  info.prefix = "mem_wiring"; // TODO: Don't hardcode this
395  info.wrapperModule = memoryWrapperModule;
396  info.stopAtDUT = !info.wrapperModule.empty();
397  for (auto *instRecord : instanceGraph->lookup(module)->uses()) {
398  if (auto inst = dyn_cast<InstanceOp>(*instRecord->getInstance())) {
399  LLVM_DEBUG(llvm::dbgs()
400  << "- Marking `"
401  << inst->getParentOfType<FModuleLike>().getModuleName()
402  << "." << inst.getName() << "`\n");
403  extractionWorklist.push_back({inst, info});
404  }
405  }
406  }
407  }
408 }
409 
410 /// Process an extraction annotation on an instance into a corresponding
411 /// `ExtractionInfo` and a spot on the worklist for later moving things around.
412 void ExtractInstancesPass::collectAnno(InstanceOp inst, Annotation anno) {
413  LLVM_DEBUG(llvm::dbgs() << "Processing instance `" << inst.getName() << "` "
414  << anno.getDict() << "\n");
415 
416  auto getStringOrError = [&](StringRef member) {
417  auto attr = anno.getMember<StringAttr>(member);
418  if (!attr) {
419  inst.emitError("missing `")
420  << member << "` attribute in `" << anno.getClass() << "` annotation";
421  anyFailures = true;
422  }
423  return attr;
424  };
425 
426  if (anno.isClass(extractBlackBoxAnnoClass)) {
427  auto filename = getStringOrError("filename");
428  auto prefix = getStringOrError("prefix");
429  auto dest = anno.getMember<StringAttr>("dest"); // optional
430  if (anyFailures)
431  return;
432 
433  ExtractionInfo info;
434  info.traceFilename = filename;
435  info.prefix = prefix;
436  info.wrapperModule = (dest ? dest.getValue() : "");
437 
438  // CAVEAT: If the instance has a wrapper module configured then extraction
439  // should stop at the DUT module instead of extracting past the DUT into the
440  // surrounding test harness. This is all very ugly and hacky.
441  info.stopAtDUT = !info.wrapperModule.empty();
442 
443  extractionWorklist.push_back({inst, info});
444  return;
445  }
446 }
447 
448 /// Find the location in an NLA that corresponds to a given instance (either by
449 /// mentioning exactly the instance, or the instance's parent module). Returns a
450 /// position within the NLA's path, or the length of the path if the instances
451 /// was not found.
452 static unsigned findInstanceInNLA(InstanceOp inst, hw::HierPathOp nla) {
453  unsigned nlaLen = nla.getNamepath().size();
454  auto instName = getInnerSymName(inst);
455  auto parentName = cast<FModuleOp>(inst->getParentOp()).getModuleNameAttr();
456  for (unsigned nlaIdx = 0; nlaIdx < nlaLen; ++nlaIdx) {
457  auto refPart = nla.refPart(nlaIdx);
458  if (nla.modPart(nlaIdx) == parentName && (!refPart || refPart == instName))
459  return nlaIdx;
460  }
461  return nlaLen;
462 }
463 
464 /// Move instances in the extraction worklist upwards in the hierarchy. This
465 /// iteratively pushes instances up one level of hierarchy until they have
466 /// arrived in the desired container module.
467 void ExtractInstancesPass::extractInstances() {
468  // The list of ports to be added to an instance's parent module. Cleared and
469  // reused across instances.
470  SmallVector<std::pair<unsigned, PortInfo>> newPorts;
471  // The number of instances with the same prefix. Used to uniquify prefices.
472  DenseMap<StringRef, unsigned> prefixUniqueIDs;
473 
474  SmallPtrSet<Operation *, 4> nlasToRemove;
475 
476  auto &nlaTable = getAnalysis<NLATable>();
477 
478  // Keep track of where the instance was originally.
479  for (auto &[inst, info] : extractionWorklist)
480  originalInstanceParents[inst] =
481  inst->getParentOfType<FModuleLike>().getModuleNameAttr();
482 
483  while (!extractionWorklist.empty()) {
484  InstanceOp inst;
485  ExtractionInfo info;
486  std::tie(inst, info) = extractionWorklist.pop_back_val();
487  auto parent = inst->getParentOfType<FModuleOp>();
488 
489  // Figure out the wiring prefix to use for this instance. If we are supposed
490  // to use a wiring prefix (`info.prefix` is non-empty), we assemble a
491  // `<prefix>_<N>` string, where `N` is an unsigned integer used to uniquifiy
492  // the prefix. This is very close to what the original Scala implementation
493  // of the pass does, which would group instances to be extracted by prefix
494  // and then iterate over them with the index in the group being used as `N`.
495  StringRef prefix;
496  if (!info.prefix.empty()) {
497  auto &prefixSlot = instPrefices[inst];
498  if (prefixSlot.empty()) {
499  auto idx = prefixUniqueIDs[info.prefix]++;
500  (Twine(info.prefix) + "_" + Twine(idx)).toVector(prefixSlot);
501  }
502  prefix = prefixSlot;
503  }
504 
505  // If the instance is already in the right place (outside the DUT or already
506  // in the root module), there's nothing left for us to do. Otherwise we
507  // proceed to bubble it up one level in the hierarchy and add the resulting
508  // instances back to the worklist.
509  if (!dutModules.contains(parent) ||
510  instanceGraph->lookup(parent)->noUses() ||
511  (info.stopAtDUT && dutRootModules.contains(parent))) {
512  LLVM_DEBUG(llvm::dbgs() << "\nNo need to further move " << inst << "\n");
513  extractedInstances.push_back({inst, info});
514  continue;
515  }
516  LLVM_DEBUG({
517  llvm::dbgs() << "\nMoving ";
518  if (!prefix.empty())
519  llvm::dbgs() << "`" << prefix << "` ";
520  llvm::dbgs() << inst << "\n";
521  });
522 
523  // Add additional ports to the parent module as a replacement for the
524  // instance port signals once the instance is extracted.
525  unsigned numParentPorts = parent.getNumPorts();
526  unsigned numInstPorts = inst.getNumResults();
527 
528  for (unsigned portIdx = 0; portIdx < numInstPorts; ++portIdx) {
529  // Assemble the new port name as "<prefix>_<name>", where the prefix is
530  // provided by the extraction annotation.
531  auto name = inst.getPortNameStr(portIdx);
532  auto nameAttr = StringAttr::get(
533  &getContext(),
534  prefix.empty() ? Twine(name) : Twine(prefix) + "_" + name);
535 
536  PortInfo newPort{nameAttr,
537  type_cast<FIRRTLType>(inst.getResult(portIdx).getType()),
538  direction::flip(inst.getPortDirection(portIdx))};
539  newPort.loc = inst.getResult(portIdx).getLoc();
540  newPorts.push_back({numParentPorts, newPort});
541  LLVM_DEBUG(llvm::dbgs()
542  << "- Adding port " << newPort.direction << " "
543  << newPort.name.getValue() << ": " << newPort.type << "\n");
544  }
545  parent.insertPorts(newPorts);
546  anythingChanged = true;
547 
548  // Replace all uses of the existing instance ports with the newly-created
549  // module ports.
550  for (unsigned portIdx = 0; portIdx < numInstPorts; ++portIdx) {
551  inst.getResult(portIdx).replaceAllUsesWith(
552  parent.getArgument(numParentPorts + portIdx));
553  }
554  assert(inst.use_empty() && "instance ports should have been detached");
555  DenseSet<hw::HierPathOp> instanceNLAs;
556  // Get the NLAs that pass through the InstanceOp `inst`.
557  // This does not returns NLAs that have the `inst` as the leaf.
558  nlaTable.getInstanceNLAs(inst, instanceNLAs);
559  // Map of the NLAs, that are applied to the InstanceOp. That is the NLA
560  // terminates on the InstanceOp.
561  DenseMap<hw::HierPathOp, SmallVector<Annotation>> instNonlocalAnnos;
563  // Only consider annotations with a `circt.nonlocal` field.
564  auto nlaName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
565  if (!nlaName)
566  return false;
567  // Track the NLA.
568  if (hw::HierPathOp nla = nlaTable.getNLA(nlaName.getAttr())) {
569  instNonlocalAnnos[nla].push_back(anno);
570  instanceNLAs.insert(nla);
571  }
572  return true;
573  });
574 
575  // Sort the instance NLAs we've collected by the NLA name to have a
576  // deterministic output.
577  SmallVector<hw::HierPathOp> sortedInstanceNLAs(instanceNLAs.begin(),
578  instanceNLAs.end());
579  llvm::sort(sortedInstanceNLAs,
580  [](auto a, auto b) { return a.getSymName() < b.getSymName(); });
581 
582  // Move the original instance one level up such that it is right next to
583  // the instances of the parent module, and wire the instance ports up to
584  // the newly added parent module ports.
585  auto *instParentNode =
586  instanceGraph->lookup(cast<igraph::ModuleOpInterface>(*parent));
587  for (auto *instRecord : instParentNode->uses()) {
588  auto oldParentInst = cast<InstanceOp>(*instRecord->getInstance());
589  auto newParent = oldParentInst->getParentOfType<FModuleLike>();
590  LLVM_DEBUG(llvm::dbgs() << "- Updating " << oldParentInst << "\n");
591  auto newParentInst = oldParentInst.cloneAndInsertPorts(newPorts);
592 
593  // Migrate connections to existing ports.
594  for (unsigned portIdx = 0; portIdx < numParentPorts; ++portIdx)
595  oldParentInst.getResult(portIdx).replaceAllUsesWith(
596  newParentInst.getResult(portIdx));
597 
598  // Clone the existing instance and remove it from its current parent, such
599  // that we can insert it at its extracted location.
600  auto newInst = inst.cloneAndInsertPorts({});
601  newInst->remove();
602 
603  // Ensure that the `inner_sym` of the instance is unique within the parent
604  // module we're extracting it to.
605  if (auto instSym = getInnerSymName(inst)) {
606  auto newName =
607  getModuleNamespace(newParent).newName(instSym.getValue());
608  if (newName != instSym.getValue())
609  newInst.setInnerSymAttr(
610  hw::InnerSymAttr::get(StringAttr::get(&getContext(), newName)));
611  }
612 
613  // Add the moved instance and hook it up to the added ports.
614  ImplicitLocOpBuilder builder(inst.getLoc(), newParentInst);
615  builder.setInsertionPointAfter(newParentInst);
616  builder.insert(newInst);
617  for (unsigned portIdx = 0; portIdx < numInstPorts; ++portIdx) {
618  auto dst = newInst.getResult(portIdx);
619  auto src = newParentInst.getResult(numParentPorts + portIdx);
620  if (newPorts[portIdx].second.direction == Direction::In)
621  std::swap(src, dst);
622  builder.create<StrictConnectOp>(dst, src);
623  }
624 
625  // Move the wiring prefix from the old to the new instance. We just look
626  // up the prefix for the old instance and if it exists, we remove it and
627  // assign it to the new instance. This has the effect of making the first
628  // new instance we create inherit the wiring prefix, and all additional
629  // new instances (e.g. through multiple instantiation of the parent) will
630  // pick a new prefix.
631  auto oldPrefix = instPrefices.find(inst);
632  if (oldPrefix != instPrefices.end()) {
633  LLVM_DEBUG(llvm::dbgs()
634  << " - Reusing prefix `" << oldPrefix->second << "`\n");
635  auto newPrefix = std::move(oldPrefix->second);
636  instPrefices.erase(oldPrefix);
637  instPrefices.insert({newInst, newPrefix});
638  }
639 
640  // Inherit the old instance's extraction path.
641  extractionPaths.try_emplace(newInst); // (create entry first)
642  auto &extractionPath = (extractionPaths[newInst] = extractionPaths[inst]);
643  extractionPath.push_back(getInnerRefTo(newParentInst));
644  originalInstanceParents.try_emplace(newInst); // (create entry first)
645  originalInstanceParents[newInst] = originalInstanceParents[inst];
646  // Record the Nonlocal annotations that need to be applied to the new
647  // Inst.
648  SmallVector<Annotation> newInstNonlocalAnnos;
649 
650  // Update all NLAs that touch the moved instance.
651  for (auto nla : sortedInstanceNLAs) {
652  LLVM_DEBUG(llvm::dbgs() << " - Updating " << nla << "\n");
653 
654  // Find the position of the instance in the NLA path. This is going to
655  // be the position at which we have to modify the NLA.
656  SmallVector<Attribute> nlaPath(nla.getNamepath().begin(),
657  nla.getNamepath().end());
658  unsigned nlaIdx = findInstanceInNLA(inst, nla);
659 
660  // Handle the case where the instance no longer shows up in the NLA's
661  // path. This usually happens if the instance is extracted into multiple
662  // parents (because the current parent module is multiply instantiated).
663  // In that case NLAs that were specific to one instance may have been
664  // moved when we arrive at the second instance, and the NLA is already
665  // updated.
666  if (nlaIdx >= nlaPath.size()) {
667  LLVM_DEBUG(llvm::dbgs() << " - Instance no longer in path\n");
668  continue;
669  }
670  LLVM_DEBUG(llvm::dbgs() << " - Position " << nlaIdx << "\n");
671 
672  // Handle the case where the NLA's path doesn't go through the
673  // instance's new parent module, which happens if the current parent
674  // module is multiply instantiated. In that case, we only move over NLAs
675  // that actually affect the instance through the new parent module.
676  if (nlaIdx > 0) {
677  auto innerRef = dyn_cast<InnerRefAttr>(nlaPath[nlaIdx - 1]);
678  if (innerRef &&
679  !(innerRef.getModule() == newParent.getModuleNameAttr() &&
680  innerRef.getName() == getInnerSymName(newParentInst))) {
681  LLVM_DEBUG(llvm::dbgs()
682  << " - Ignored since NLA parent " << innerRef
683  << " does not pass through extraction parent\n");
684  continue;
685  }
686  }
687 
688  // There are two interesting cases now:
689  // - If `nlaIdx == 0`, the NLA is rooted at the module the instance was
690  // located in prior to extraction. This indicates that the NLA applies
691  // to all instances of that parent module. Since we are extracting
692  // *out* of that module, we have to create a new NLA rooted at the new
693  // parent module after extraction.
694  // - If `nlaIdx > 0`, the NLA is rooted further up in the hierarchy and
695  // we can simply remove the old parent module from the path.
696 
697  // Handle the case where we need to come up with a new NLA for this
698  // instance since we've moved it past the module at which the old NLA
699  // was rooted at.
700  if (nlaIdx == 0) {
701  LLVM_DEBUG(llvm::dbgs() << " - Re-rooting " << nlaPath[0] << "\n");
702  assert(isa<InnerRefAttr>(nlaPath[0]) &&
703  "head of hierpath must be an InnerRefAttr");
704  nlaPath[0] = InnerRefAttr::get(newParent.getModuleNameAttr(),
705  getInnerSymName(newInst));
706 
707  if (instParentNode->hasOneUse()) {
708  // Simply update the existing NLA since our parent is only
709  // instantiated once, and we therefore are not creating multiple
710  // instances through the extraction.
711  nlaTable.erase(nla);
712  nla.setNamepathAttr(builder.getArrayAttr(nlaPath));
713  for (auto anno : instNonlocalAnnos.lookup(nla))
714  newInstNonlocalAnnos.push_back(anno);
715  nlaTable.addNLA(nla);
716  LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
717  } else {
718  // Since we are extracting to multiple parent locations, create a
719  // new NLA for each instantiation site.
720  auto newNla = cloneWithNewNameAndPath(nla, nlaPath);
721  for (auto anno : instNonlocalAnnos.lookup(nla)) {
722  anno.setMember("circt.nonlocal",
723  FlatSymbolRefAttr::get(newNla.getSymNameAttr()));
724  newInstNonlocalAnnos.push_back(anno);
725  }
726 
727  nlaTable.addNLA(newNla);
728  LLVM_DEBUG(llvm::dbgs() << " - Created " << newNla << "\n");
729  // CAVEAT(fschuiki): This results in annotations in the subhierarchy
730  // below `inst` with the old NLA symbol name, instead of those
731  // annotations duplicated for each of the newly-created NLAs. This
732  // shouldn't come up in our current use cases, but is a weakness of
733  // the current implementation. Instead, we should keep an NLA
734  // replication table that we fill with mappings from old NLA names
735  // to lists of new NLA names. A post-pass would then traverse the
736  // entire subhierarchy and go replicate all annotations with the old
737  // names.
738  inst.emitWarning("extraction of instance `")
739  << inst.getInstanceName()
740  << "` could break non-local annotations rooted at `"
741  << parent.getModuleName() << "`";
742  }
743  continue;
744  }
745 
746  // In the subequent code block we are going to remove one element from
747  // the NLA path, corresponding to the fact that the extracted instance
748  // has moved up in the hierarchy by one level. Removing that element may
749  // leave the NLA in a degenerate state, with only a single element in
750  // its path. If that is the case we have to convert the NLA into a
751  // regular local annotation.
752  if (nlaPath.size() == 2) {
753  for (auto anno : instNonlocalAnnos.lookup(nla)) {
754  anno.removeMember("circt.nonlocal");
755  newInstNonlocalAnnos.push_back(anno);
756  LLVM_DEBUG(llvm::dbgs() << " - Converted to local "
757  << anno.getDict() << "\n");
758  }
759  nlaTable.erase(nla);
760  nlasToRemove.insert(nla);
761  continue;
762  }
763 
764  // At this point the NLA looks like `NewParent::X, OldParent::BB`, and
765  // the `nlaIdx` points at `OldParent::BB`. To make our lives easier,
766  // since we know that `nlaIdx` is a `InnerRefAttr`, we'll modify
767  // `OldParent::BB` to be `NewParent::BB` and delete `NewParent::X`.
768  StringAttr parentName =
769  cast<InnerRefAttr>(nlaPath[nlaIdx - 1]).getModule();
770  Attribute newRef;
771  if (isa<InnerRefAttr>(nlaPath[nlaIdx]))
772  newRef = InnerRefAttr::get(parentName, getInnerSymName(newInst));
773  else
774  newRef = FlatSymbolRefAttr::get(parentName);
775  LLVM_DEBUG(llvm::dbgs()
776  << " - Replacing " << nlaPath[nlaIdx - 1] << " and "
777  << nlaPath[nlaIdx] << " with " << newRef << "\n");
778  nlaPath[nlaIdx] = newRef;
779  nlaPath.erase(nlaPath.begin() + nlaIdx - 1);
780 
781  if (isa<FlatSymbolRefAttr>(newRef)) {
782  // Since the original NLA ended at the instance's parent module, there
783  // is no guarantee that the instance is the sole user of the NLA (as
784  // opposed to the original NLA explicitly naming the instance). Create
785  // a new NLA.
786  auto newNla = cloneWithNewNameAndPath(nla, nlaPath);
787  nlaTable.addNLA(newNla);
788  LLVM_DEBUG(llvm::dbgs() << " - Created " << newNla << "\n");
789  for (auto anno : instNonlocalAnnos.lookup(nla)) {
790  anno.setMember("circt.nonlocal",
791  FlatSymbolRefAttr::get(newNla.getSymNameAttr()));
792  newInstNonlocalAnnos.push_back(anno);
793  }
794  } else {
795  nla.setNamepathAttr(builder.getArrayAttr(nlaPath));
796  LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
797  for (auto anno : instNonlocalAnnos.lookup(nla))
798  newInstNonlocalAnnos.push_back(anno);
799  }
800 
801  // No update to NLATable required, since it will be deleted from the
802  // parent, and it should already exist in the new parent module.
803  continue;
804  }
805  AnnotationSet newInstAnnos(newInst);
806  newInstAnnos.addAnnotations(newInstNonlocalAnnos);
807  newInstAnnos.applyToOperation(newInst);
808 
809  // Add the moved instance to the extraction worklist such that it gets
810  // bubbled up further if needed.
811  extractionWorklist.push_back({newInst, info});
812  LLVM_DEBUG(llvm::dbgs() << " - Updated to " << newInst << "\n");
813 
814  // Keep instance graph up-to-date.
815  instanceGraph->replaceInstance(oldParentInst, newParentInst);
816  oldParentInst.erase();
817  }
818  // Remove the obsolete NLAs from the instance of the parent module, since
819  // the extracted instance no longer resides in that module and any NLAs to
820  // it no longer go through the parent module.
821  nlaTable.removeNLAsfromModule(instanceNLAs, parent.getNameAttr());
822 
823  // Clean up the original instance.
824  inst.erase();
825  newPorts.clear();
826  }
827 
828  // Remove unused NLAs.
829  for (Operation *op : nlasToRemove) {
830  LLVM_DEBUG(llvm::dbgs() << "Removing obsolete " << *op << "\n");
831  op->erase();
832  }
833 }
834 
835 /// Group instances into submodules after they have been moved upwards. This
836 /// only occurs for instances that had the corresponding `dest` field of the
837 /// annotation set.
838 void ExtractInstancesPass::groupInstances() {
839  // Group the extracted instances by their wrapper module name and their parent
840  // module. Note that we cannot group instances that landed in different parent
841  // modules into the same submodule, so we use that parent module as a grouping
842  // key.
843  llvm::MapVector<std::pair<Operation *, StringRef>, SmallVector<InstanceOp>>
844  instsByWrapper;
845  for (auto &[inst, info] : extractedInstances) {
846  if (!info.wrapperModule.empty())
847  instsByWrapper[{inst->getParentOfType<FModuleOp>(), info.wrapperModule}]
848  .push_back(inst);
849  }
850  if (instsByWrapper.empty())
851  return;
852  LLVM_DEBUG(llvm::dbgs() << "\nGrouping instances into wrappers\n");
853 
854  // Generate the wrappers.
855  SmallVector<PortInfo> ports;
856  auto &nlaTable = getAnalysis<NLATable>();
857 
858  for (auto &[parentAndWrapperName, insts] : instsByWrapper) {
859  auto [parentOp, wrapperName] = parentAndWrapperName;
860  auto parent = cast<FModuleOp>(parentOp);
861  LLVM_DEBUG(llvm::dbgs() << "- Wrapper `" << wrapperName << "` in `"
862  << parent.getModuleName() << "` with "
863  << insts.size() << " instances\n");
864  OpBuilder builder(parentOp);
865 
866  // Uniquify the wrapper name.
867  auto wrapperModuleName = builder.getStringAttr(
868  circuitNamespace.newName(dutPrefix + wrapperName));
869  auto wrapperInstName =
870  builder.getStringAttr(getModuleNamespace(parent).newName(wrapperName));
871 
872  // Assemble a list of ports for the wrapper module, which is basically just
873  // a concatenation of the wrapped instance ports. Also keep track of the
874  // NLAs that target the grouped instances since these will have to pass
875  // through the wrapper module.
876  ports.clear();
877  for (auto inst : insts) {
878  // Determine the ports for the wrapper.
879  StringRef prefix(instPrefices[inst]);
880  unsigned portNum = inst.getNumResults();
881  for (unsigned portIdx = 0; portIdx < portNum; ++portIdx) {
882  auto name = inst.getPortNameStr(portIdx);
883  auto nameAttr = builder.getStringAttr(
884  prefix.empty() ? Twine(name) : Twine(prefix) + "_" + name);
885  PortInfo port{nameAttr,
886  type_cast<FIRRTLType>(inst.getResult(portIdx).getType()),
887  inst.getPortDirection(portIdx)};
888  port.loc = inst.getResult(portIdx).getLoc();
889  ports.push_back(port);
890  }
891 
892  // Set of NLAs that have a reference to this InstanceOp `inst`.
893  DenseSet<hw::HierPathOp> instNlas;
894  // Get the NLAs that pass through the `inst`, and not end at it.
895  nlaTable.getInstanceNLAs(inst, instNlas);
896  AnnotationSet instAnnos(inst);
897  // Get the NLAs that end at the InstanceOp, that is the Nonlocal
898  // annotations that apply to the InstanceOp.
899  for (auto anno : instAnnos) {
900  auto nlaName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
901  if (!nlaName)
902  continue;
903  hw::HierPathOp nla = nlaTable.getNLA(nlaName.getAttr());
904  if (nla)
905  instNlas.insert(nla);
906  }
907  for (auto nla : instNlas) {
908  LLVM_DEBUG(llvm::dbgs() << " - Updating " << nla << "\n");
909 
910  // Find the position of the instance in the NLA path. This is going to
911  // be the position at which we have to modify the NLA.
912  SmallVector<Attribute> nlaPath(nla.getNamepath().begin(),
913  nla.getNamepath().end());
914  unsigned nlaIdx = findInstanceInNLA(inst, nla);
915  assert(nlaIdx < nlaPath.size() && "instance not found in its own NLA");
916  LLVM_DEBUG(llvm::dbgs() << " - Position " << nlaIdx << "\n");
917 
918  // The relevant part of the NLA is of the form `Top::bb`, which we want
919  // to expand to `Top::wrapperInst` and `Wrapper::bb`.
920  auto ref1 =
921  InnerRefAttr::get(parent.getModuleNameAttr(), wrapperInstName);
922  Attribute ref2;
923  if (auto innerRef = dyn_cast<InnerRefAttr>(nlaPath[nlaIdx]))
924  ref2 = InnerRefAttr::get(wrapperModuleName, innerRef.getName());
925  else
926  ref2 = FlatSymbolRefAttr::get(wrapperModuleName);
927  LLVM_DEBUG(llvm::dbgs() << " - Expanding " << nlaPath[nlaIdx]
928  << " to (" << ref1 << ", " << ref2 << ")\n");
929  nlaPath[nlaIdx] = ref1;
930  nlaPath.insert(nlaPath.begin() + nlaIdx + 1, ref2);
931  // CAVEAT: This is likely to conflict with additional users of `nla`
932  // that have nothing to do with this instance. Might need some NLATable
933  // machinery at some point to allow for these things to be updated.
934  nla.setNamepathAttr(builder.getArrayAttr(nlaPath));
935  LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
936  // Add the NLA to the wrapper module.
937  nlaTable.addNLAtoModule(nla, wrapperModuleName);
938  }
939  }
940 
941  // Create the wrapper module.
942  auto wrapper = builder.create<FModuleOp>(
943  builder.getUnknownLoc(), wrapperModuleName,
944  ConventionAttr::get(builder.getContext(), Convention::Internal), ports);
945  SymbolTable::setSymbolVisibility(wrapper, SymbolTable::Visibility::Private);
946 
947  // Instantiate the wrapper module in the parent and replace uses of the
948  // extracted instances' ports with the corresponding wrapper module ports.
949  // This will essentially disconnect the extracted instances.
950  builder.setInsertionPointToStart(parent.getBodyBlock());
951  auto wrapperInst = builder.create<InstanceOp>(
952  wrapper.getLoc(), wrapper, wrapperName, NameKindEnum::DroppableName,
953  ArrayRef<Attribute>{},
954  /*portAnnotations=*/ArrayRef<Attribute>{}, /*lowerToBind=*/false,
955  wrapperInstName);
956  unsigned portIdx = 0;
957  for (auto inst : insts)
958  for (auto result : inst.getResults())
959  result.replaceAllUsesWith(wrapperInst.getResult(portIdx++));
960 
961  // Move all instances into the wrapper module and wire them up to the
962  // wrapper ports.
963  portIdx = 0;
964  builder.setInsertionPointToStart(wrapper.getBodyBlock());
965  for (auto inst : insts) {
966  inst->remove();
967  builder.insert(inst);
968  for (auto result : inst.getResults()) {
969  Value dst = result;
970  Value src = wrapper.getArgument(portIdx);
971  if (ports[portIdx].direction == Direction::Out)
972  std::swap(dst, src);
973  builder.create<StrictConnectOp>(result.getLoc(), dst, src);
974  ++portIdx;
975  }
976  }
977  }
978 }
979 
980 /// Generate trace files, which are plain text metadata files that list the
981 /// hierarchical path where each instance was extracted from. The file lists one
982 /// instance per line in the form `<prefix> -> <original-path>`.
983 void ExtractInstancesPass::createTraceFiles() {
984  LLVM_DEBUG(llvm::dbgs() << "\nGenerating trace files\n");
985  auto builder = OpBuilder::atBlockEnd(getOperation().getBodyBlock());
986 
987  // Group the extracted instances by their trace file name.
989  for (auto &[inst, info] : extractedInstances)
990  if (!info.traceFilename.empty())
991  instsByTraceFile[info.traceFilename].push_back(inst);
992 
993  // Generate the trace files.
994  SmallVector<Attribute> symbols;
996 
997  for (auto &[fileName, insts] : instsByTraceFile) {
998  LLVM_DEBUG(llvm::dbgs() << "- " << fileName << "\n");
999  std::string buffer;
1000  llvm::raw_string_ostream os(buffer);
1001  symbols.clear();
1002  symbolIndices.clear();
1003 
1004  auto addSymbol = [&](Attribute symbol) {
1005  unsigned id;
1006  auto it = symbolIndices.find(symbol);
1007  if (it != symbolIndices.end()) {
1008  id = it->second;
1009  } else {
1010  id = symbols.size();
1011  symbols.push_back(symbol);
1012  symbolIndices.insert({symbol, id});
1013  }
1014  os << "{{" << id << "}}";
1015  };
1016 
1017  for (auto inst : insts) {
1018  StringRef prefix(instPrefices[inst]);
1019  if (prefix.empty()) {
1020  LLVM_DEBUG(llvm::dbgs() << " - Skipping `" << inst.getName()
1021  << "` since it has no extraction prefix\n");
1022  continue;
1023  }
1024  ArrayRef<InnerRefAttr> path(extractionPaths[inst]);
1025  if (path.empty()) {
1026  LLVM_DEBUG(llvm::dbgs() << " - Skipping `" << inst.getName()
1027  << "` since it has not been moved\n");
1028  continue;
1029  }
1030  LLVM_DEBUG(llvm::dbgs()
1031  << " - " << prefix << ": " << inst.getName() << "\n");
1032  os << prefix << " -> ";
1033 
1034  // HACK: To match the Scala implementation, we strip all non-DUT modules
1035  // from the path and make the path look like it's rooted at the first DUT
1036  // module (so `TestHarness.dut.foo.bar` becomes `DUTModule.foo.bar`).
1037  while (!path.empty() &&
1038  !dutModuleNames.contains(path.back().getModule())) {
1039  LLVM_DEBUG(llvm::dbgs()
1040  << " - Dropping non-DUT segment " << path.back() << "\n");
1041  path = path.drop_back();
1042  }
1043  // HACK: This is extremely ugly. In case the instance was just moved by a
1044  // single level, the path may become empty. In that case we simply use the
1045  // instance's original parent before it was moved.
1046  addSymbol(FlatSymbolRefAttr::get(path.empty()
1047  ? originalInstanceParents[inst]
1048  : path.back().getModule()));
1049  for (auto sym : llvm::reverse(path)) {
1050  os << ".";
1051  addSymbol(sym);
1052  }
1053  // The final instance name is excluded as this does not provide useful
1054  // additional information and could conflict with a name inside the final
1055  // module.
1056  os << "\n";
1057  }
1058 
1059  // Put the information in a verbatim operation.
1060  auto verbatimOp = builder.create<sv::VerbatimOp>(
1061  builder.getUnknownLoc(), buffer, ValueRange{},
1062  builder.getArrayAttr(symbols));
1063  auto fileAttr = hw::OutputFileAttr::getFromFilename(
1064  builder.getContext(), fileName, /*excludeFromFilelist=*/true);
1065  verbatimOp->setAttr("output_file", fileAttr);
1066  }
1067 }
1068 
1069 //===----------------------------------------------------------------------===//
1070 // Pass Creation
1071 //===----------------------------------------------------------------------===//
1072 
1073 std::unique_ptr<mlir::Pass> circt::firrtl::createExtractInstancesPass() {
1074  return std::make_unique<ExtractInstancesPass>();
1075 }
assert(baseType &&"element must be base type")
static unsigned findInstanceInNLA(InstanceOp inst, hw::HierPathOp nla)
Find the location in an NLA that corresponds to a given instance (either by mentioning exactly the in...
static bool isAnnoInteresting(Annotation anno)
static std::vector< mlir::Value > toVector(mlir::ValueRange range)
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.
This class provides a read-only projection of an annotation.
DictionaryAttr getDict() const
Get the data dictionary of this attribute.
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.
void removeMember(StringAttr name)
Remove a member of the annotation.
StringRef getClass() const
Return the 'class' that this annotation is representing.
bool isClass(Args... names) const
Return true if this annotation matches any of the specified class names.
This graph tracks modules and where they are instantiated.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition: CalyxOps.cpp:53
Direction flip(Direction direction)
Flip a port direction.
constexpr const char * extractBlackBoxAnnoClass
constexpr const char * dutAnnoClass
constexpr const char * extractSeqMemsAnnoClass
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 > createExtractInstancesPass()
constexpr const char * extractClockGatesAnnoClass
StringAttr getInnerSymName(Operation *op)
Return the StringAttr for the inner_sym name, if it exists.
Definition: FIRRTLOps.h:108
This file defines an intermediate representation for circuits acting as an abstraction for constraint...
Definition: DebugAnalysis.h:21
mlir::raw_indented_ostream & dbgs()
Definition: Utility.h:28
The namespace of a CircuitOp, generally inhabited by modules.
Definition: Namespace.h:24
This holds the name and type that describes the module's ports.