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