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