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