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