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