[FIRRTL] Fix handling of new-style NLAs in ExtractInstances (#3345)

Adapt `ExtractInstances` such that it can properly handle the new flavor
of NLAs, where non-local annotations would carry a symbol to a hierpath
that generally only points at their parent module.

The `ExtractInstances` pass had a few assumptions in its implementation
about hierpaths pointing at the annotation target op, and each anno
having a unique hierpath op associated with it. The implementation can
be simplified quite a bit once support for the old-style NLAs can be
ripped out.

This commit also adds explicit tests to check that the old *and* new
style of NLAs are working in parallel.

A nasty corner case still exists when extraction requires an NLA to be
re-routed because the instance has been extracted past the NLA's
original root. If the instance is multiply-extracted, one hierpath gets
turned into multiple hierpaths, which will require special handling of
*everything* with that hierpath. This commit detects the common case
which we can handle and ensures that the hierpath is re-rooted in place,
and produces a warning about a potential breakage of hierpath references
in the corner case.
This commit is contained in:
Fabian Schuiki 2022-06-15 18:50:51 +02:00 committed by GitHub
parent 9be1e8cd3f
commit 23ae1a7391
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 175 additions and 87 deletions

View File

@ -93,6 +93,17 @@ struct ExtractInstancesPass
getOrAddInnerSym(op));
}
/// Create a clone of a `HierPathOp` with a new uniquified name.
HierPathOp cloneWithNewNameAndPath(HierPathOp pathOp,
ArrayRef<Attribute> newPath) {
OpBuilder builder(pathOp);
auto newPathOp = builder.cloneWithoutRegions(pathOp);
newPathOp.sym_nameAttr(
builder.getStringAttr(circuitNamespace.newName(newPathOp.sym_name())));
newPathOp.namepathAttr(builder.getArrayAttr(newPath));
return newPathOp;
}
bool anythingChanged;
bool anyFailures;
@ -431,6 +442,22 @@ void ExtractInstancesPass::collectAnno(InstanceOp inst, Annotation anno) {
}
}
/// Find the location in an NLA that corresponds to a given instance (either by
/// mentioning exactly the instance, or the instance's parent module). Returns a
/// position within the NLA's path, or the length of the path if the instances
/// was not found.
static unsigned findInstanceInNLA(InstanceOp inst, HierPathOp nla) {
unsigned nlaLen = nla.namepath().size();
auto instName = inst.inner_symAttr();
auto parentName = cast<FModuleOp>(inst->getParentOp()).moduleNameAttr();
for (unsigned nlaIdx = 0; nlaIdx < nlaLen; ++nlaIdx) {
auto refPart = nla.refPart(nlaIdx);
if (nla.modPart(nlaIdx) == parentName && (!refPart || refPart == instName))
return nlaIdx;
}
return nlaLen;
}
/// Move instances in the extraction worklist upwards in the hierarchy. This
/// iteratively pushes instances up one level of hierarchy until they have
/// arrived in the desired container module.
@ -528,28 +555,33 @@ void ExtractInstancesPass::extractInstances() {
nlaTable.getInstanceNLAs(inst, instanceNLAs);
// Map of the NLAs, that are applied to the InstanceOp. That is the NLA
// terminates on the InstanceOp.
DenseMap<HierPathOp, Annotation> instNonlocalAnnos;
// Get the NLAs that are applied on the InstanceOp, since the NLATable does
// not return them.
DenseMap<HierPathOp, SmallVector<Annotation>> instNonlocalAnnos;
AnnotationSet::removeAnnotations(inst, [&](Annotation anno) {
// Only consider annotations with a `circt.nonlocal` field.
auto nlaName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
// Ignore any other anno.
if (!nlaName)
return false;
// Ignore the breadcrumb annos, since NLATable already tracks them.
if (!anno.isClass("circt.nonlocal"))
if (HierPathOp nla = nlaTable.getNLA(nlaName.getAttr())) {
instNonlocalAnnos[nla] = anno;
instanceNLAs.insert(nla);
}
// Track the NLA.
if (HierPathOp nla = nlaTable.getNLA(nlaName.getAttr())) {
instNonlocalAnnos[nla].push_back(anno);
instanceNLAs.insert(nla);
}
return true;
});
// Sort the instance NLAs we've collected by the NLA name to have a
// deterministic output.
SmallVector<HierPathOp> sortedInstanceNLAs(instanceNLAs.begin(),
instanceNLAs.end());
llvm::sort(sortedInstanceNLAs,
[](auto a, auto b) { return a.sym_name() < b.sym_name(); });
// Move the original instance one level up such that it is right next to
// the instances of the parent module, and wire the instance ports up to
// the newly added parent module ports.
for (auto *instRecord :
instanceGraph->lookup(cast<hw::HWModuleLike>(*parent))->uses()) {
auto *instParentNode =
instanceGraph->lookup(cast<hw::HWModuleLike>(*parent));
for (auto *instRecord : instParentNode->uses()) {
auto oldParentInst = cast<InstanceOp>(*instRecord->getInstance());
auto newParent = oldParentInst->getParentOfType<FModuleLike>();
LLVM_DEBUG(llvm::dbgs() << "- Updating " << oldParentInst << "\n");
@ -610,23 +642,14 @@ void ExtractInstancesPass::extractInstances() {
SmallVector<Annotation> newInstNonlocalAnnos;
// Update all NLAs that touch the moved instance.
for (auto nla : instanceNLAs) {
for (auto nla : sortedInstanceNLAs) {
LLVM_DEBUG(llvm::dbgs() << " - Updating " << nla << "\n");
// Find the position of the instance in the NLA path. This is going to
// be the position at which we have to modify the NLA.
SmallVector<Attribute> nlaPath(nla.namepath().begin(),
nla.namepath().end());
unsigned nlaIdx;
unsigned nlaLen = nlaPath.size();
for (nlaIdx = 0; nlaIdx < nlaLen; ++nlaIdx) {
auto innerRef = nlaPath[nlaIdx].dyn_cast<InnerRefAttr>();
if (!innerRef)
continue;
if (innerRef.getModule() == parent.moduleNameAttr() &&
innerRef.getName() == inst.inner_symAttr())
break;
}
unsigned nlaIdx = findInstanceInNLA(inst, nla);
// Handle the case where the instance no longer shows up in the NLA's
// path. This usually happens if the instance is extracted into multiple
@ -634,7 +657,7 @@ void ExtractInstancesPass::extractInstances() {
// In that case NLAs that were specific to one instance may have been
// moved when we arrive at the second instance, and the NLA is already
// updated.
if (nlaIdx >= nlaLen) {
if (nlaIdx >= nlaPath.size()) {
LLVM_DEBUG(llvm::dbgs() << " - Instance no longer in path\n");
continue;
}
@ -670,26 +693,48 @@ void ExtractInstancesPass::extractInstances() {
// was rooted at.
if (nlaIdx == 0) {
LLVM_DEBUG(llvm::dbgs() << " - Re-rooting " << nlaPath[0] << "\n");
assert(nlaPath[0].isa<InnerRefAttr>() &&
"head of hierpath must be an InnerRefAttr");
nlaPath[0] =
InnerRefAttr::get(newParent.moduleNameAttr(),
nlaPath[0].cast<InnerRefAttr>().getName());
auto builder = OpBuilder::atBlockBegin(getOperation().getBody());
auto newNla = builder.create<HierPathOp>(
newInst.getLoc(), circuitNamespace.newName(nla.sym_name()),
builder.getArrayAttr(nlaPath));
auto nlaAnno = instNonlocalAnnos.find(nla);
if (nlaAnno != instNonlocalAnnos.end()) {
Annotation newAnno = nlaAnno->getSecond();
newAnno.setMember("circt.nonlocal",
FlatSymbolRefAttr::get(newNla.sym_nameAttr()));
newInstNonlocalAnnos.push_back(newAnno);
if (instParentNode->hasOneUse()) {
// Simply update the existing NLA since our parent is only
// instantiated once, and we therefore are not creating multiple
// instances through the extraction.
nlaTable.erase(nla);
nla.namepathAttr(builder.getArrayAttr(nlaPath));
for (auto anno : instNonlocalAnnos.lookup(nla))
newInstNonlocalAnnos.push_back(anno);
nlaTable.addNLA(nla);
LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
} else {
// Since we are extracting to multiple parent locations, create a
// new NLA for each instantiation site.
auto newNla = cloneWithNewNameAndPath(nla, nlaPath);
for (auto anno : instNonlocalAnnos.lookup(nla)) {
anno.setMember("circt.nonlocal",
FlatSymbolRefAttr::get(newNla.sym_nameAttr()));
newInstNonlocalAnnos.push_back(anno);
}
nlaTable.addNLA(newNla);
LLVM_DEBUG(llvm::dbgs() << " - Created " << newNla << "\n");
// CAVEAT(fschuiki): This results in annotations in the subhierarchy
// below `inst` with the old NLA symbol name, instead of those
// annotations duplicated for each of the newly-created NLAs. This
// shouldn't come up in our current use cases, but is a weakness of
// the current implementation. Instead, we should keep an NLA
// replication table that we fill with mappings from old NLA names
// to lists of new NLA names. A post-pass would then traverse the
// entire subhierarchy and go replicate all annotations with the old
// names.
inst.emitWarning("extraction of instance `")
<< inst.instanceName()
<< "` could break non-local annotations rooted at `"
<< parent.moduleName() << "`";
}
nlaTable.erase(nla);
nlasToRemove.insert(nla);
nlaTable.addNLA(newNla);
LLVM_DEBUG(llvm::dbgs() << " - Created " << newNla << "\n");
continue;
}
@ -700,9 +745,7 @@ void ExtractInstancesPass::extractInstances() {
// its path. If that is the case we have to convert the NLA into a
// regular local annotation.
if (nlaPath.size() == 2) {
auto nlaAnno = instNonlocalAnnos.find(nla);
if (nlaAnno != instNonlocalAnnos.end()) {
auto anno = nlaAnno->getSecond();
for (auto anno : instNonlocalAnnos.lookup(nla)) {
anno.removeMember("circt.nonlocal");
newInstNonlocalAnnos.push_back(anno);
LLVM_DEBUG(llvm::dbgs() << " - Converted to local "
@ -717,20 +760,39 @@ void ExtractInstancesPass::extractInstances() {
// the `nlaIdx` points at `OldParent::BB`. To make our lives easier,
// since we know that `nlaIdx` is a `InnerRefAttr`, we'll modify
// `OldParent::BB` to be `NewParent::BB` and delete `NewParent::X`.
auto newInnerRef = InnerRefAttr::get(
nlaPath[nlaIdx - 1].cast<InnerRefAttr>().getModule(),
newInst.inner_symAttr());
StringAttr parentName =
nlaPath[nlaIdx - 1].cast<InnerRefAttr>().getModule();
Attribute newRef;
if (nlaPath[nlaIdx].isa<InnerRefAttr>())
newRef = InnerRefAttr::get(parentName, newInst.inner_symAttr());
else
newRef = FlatSymbolRefAttr::get(parentName);
LLVM_DEBUG(llvm::dbgs()
<< " - Replacing " << nlaPath[nlaIdx - 1] << " and "
<< nlaPath[nlaIdx] << " with " << newInnerRef << "\n");
nlaPath[nlaIdx] = newInnerRef;
<< nlaPath[nlaIdx] << " with " << newRef << "\n");
nlaPath[nlaIdx] = newRef;
nlaPath.erase(nlaPath.begin() + nlaIdx - 1);
nla.namepathAttr(builder.getArrayAttr(nlaPath));
auto nlaAnno = instNonlocalAnnos.find(nla);
if (nlaAnno != instNonlocalAnnos.end())
newInstNonlocalAnnos.push_back(nlaAnno->getSecond());
LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
if (newRef.isa<FlatSymbolRefAttr>()) {
// Since the original NLA ended at the instance's parent module, there
// is no guarantee that the instance is the sole user of the NLA (as
// opposed to the original NLA explicitly naming the instance). Create
// a new NLA.
auto newNla = cloneWithNewNameAndPath(nla, nlaPath);
nlaTable.addNLA(newNla);
LLVM_DEBUG(llvm::dbgs() << " - Created " << newNla << "\n");
for (auto anno : instNonlocalAnnos.lookup(nla)) {
anno.setMember("circt.nonlocal",
FlatSymbolRefAttr::get(newNla.sym_nameAttr()));
newInstNonlocalAnnos.push_back(anno);
}
} else {
nla.namepathAttr(builder.getArrayAttr(nlaPath));
LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
for (auto anno : instNonlocalAnnos.lookup(nla))
newInstNonlocalAnnos.push_back(anno);
}
// No update to NLATable required, since it will be deleted from the
// parent, and it should already exist in the new parent module.
continue;
@ -843,34 +905,30 @@ void ExtractInstancesPass::groupInstances() {
// be the position at which we have to modify the NLA.
SmallVector<Attribute> nlaPath(nla.namepath().begin(),
nla.namepath().end());
unsigned nlaIdx;
unsigned nlaLen = nlaPath.size();
for (nlaIdx = 0; nlaIdx < nlaLen; ++nlaIdx) {
auto innerRef = nlaPath[nlaIdx].dyn_cast<InnerRefAttr>();
if (!innerRef)
continue;
if (innerRef.getModule() == parent.moduleNameAttr() &&
innerRef.getName() == inst.inner_symAttr())
break;
}
assert(nlaIdx < nlaLen && "instance not found in its own NLA");
unsigned nlaIdx = findInstanceInNLA(inst, nla);
assert(nlaIdx < nlaPath.size() && "instance not found in its own NLA");
LLVM_DEBUG(llvm::dbgs() << " - Position " << nlaIdx << "\n");
// The relevant part of the NLA is of the form `Top::bb`, which we want
// to expand to `Top::wrapperInst` and `Wrapper::bb`.
auto ref1 = InnerRefAttr::get(
nlaPath[nlaIdx].cast<InnerRefAttr>().getModule(), wrapperInstName);
auto ref2 =
InnerRefAttr::get(builder.getStringAttr(wrapperName),
nlaPath[nlaIdx].cast<InnerRefAttr>().getName());
auto wrapperNameAttr = builder.getStringAttr(wrapperName);
auto ref1 = InnerRefAttr::get(parent.moduleNameAttr(), wrapperInstName);
Attribute ref2;
if (auto innerRef = nlaPath[nlaIdx].dyn_cast<InnerRefAttr>())
ref2 = InnerRefAttr::get(wrapperNameAttr, innerRef.getName());
else
ref2 = FlatSymbolRefAttr::get(wrapperNameAttr);
LLVM_DEBUG(llvm::dbgs() << " - Expanding " << nlaPath[nlaIdx]
<< " to (" << ref1 << ", " << ref2 << ")\n");
nlaPath[nlaIdx] = ref1;
nlaPath.insert(nlaPath.begin() + nlaIdx + 1, ref2);
// CAVEAT: This is likely to conflict with additional users of `nla`
// that have nothing to do with this instance. Might need some NLATable
// machinery at some point to allow for these things to be updated.
nla.namepathAttr(builder.getArrayAttr(nlaPath));
LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
// Add the NLA to the wrapper module.
nlaTable.addNLAtoModule(nla, ref2.getModule());
nlaTable.addNLAtoModule(nla, wrapperNameAttr);
}
}

View File

@ -65,17 +65,27 @@ firrtl.circuit "ExtractBlackBoxesSimple" attributes {annotations = [{class = "fi
// CHECK: firrtl.circuit "ExtractBlackBoxesSimple2"
firrtl.circuit "ExtractBlackBoxesSimple2" attributes {annotations = [{class = "firrtl.transforms.BlackBoxTargetDirAnno", targetDir = "BlackBoxes"}]} {
// CHECK: firrtl.hierpath @nla_1{{.*}} [@ExtractBlackBoxesSimple2::@bb, @MyBlackBox]
// CHECK-NOT: firrtl.hierpath @nla_2
// CHECK-NOT: firrtl.hierpath @nla_3
firrtl.hierpath @nla_1 [@BBWrapper::@bb, @MyBlackBox]
firrtl.hierpath @nla_2 [@DUTModule::@mod, @BBWrapper::@bb]
firrtl.hierpath @nla_3 [@ExtractBlackBoxesSimple2::@dut, @DUTModule::@mod, @BBWrapper::@bb]
// Old style NLAs
firrtl.hierpath @nla_old1 [@DUTModule::@mod, @BBWrapper::@bb]
firrtl.hierpath @nla_old2 [@ExtractBlackBoxesSimple2::@dut, @DUTModule::@mod, @BBWrapper::@bb]
// New style NLAs on extracted instance
firrtl.hierpath @nla_on1 [@DUTModule::@mod, @BBWrapper]
firrtl.hierpath @nla_on2 [@ExtractBlackBoxesSimple2::@dut, @DUTModule::@mod, @BBWrapper]
// New style NLAs through extracted instance
firrtl.hierpath @nla_thru1 [@BBWrapper::@bb, @MyBlackBox]
firrtl.hierpath @nla_thru2 [@DUTModule::@mod, @BBWrapper::@bb, @MyBlackBox]
firrtl.hierpath @nla_thru3 [@ExtractBlackBoxesSimple2::@dut, @DUTModule::@mod, @BBWrapper::@bb, @MyBlackBox]
// CHECK: firrtl.hierpath [[THRU1:@nla_thru1]] [@ExtractBlackBoxesSimple2::@bb, @MyBlackBox]
// CHECK: firrtl.hierpath [[THRU2:@nla_thru2]] [@ExtractBlackBoxesSimple2::@bb, @MyBlackBox]
// CHECK: firrtl.hierpath [[THRU3:@nla_thru3]] [@ExtractBlackBoxesSimple2::@bb, @MyBlackBox]
// Annotation on the extmodule itself
// CHECK-LABEL: firrtl.extmodule private @MyBlackBox
firrtl.extmodule private @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>) attributes {annotations = [
{class = "sifive.enterprise.firrtl.ExtractBlackBoxAnnotation", filename = "BlackBoxes.txt", prefix = "prefix"},
{circt.nonlocal = @nla_1, class = "DummyA"}
{circt.nonlocal = @nla_thru1, class = "Thru1"},
{circt.nonlocal = @nla_thru2, class = "Thru2"},
{circt.nonlocal = @nla_thru3, class = "Thru3"}
], defname = "MyBlackBox"}
// Annotation will be on the instance
// CHECK-LABEL: firrtl.extmodule private @MyBlackBox2
@ -90,8 +100,10 @@ firrtl.circuit "ExtractBlackBoxesSimple2" attributes {annotations = [{class = "f
// CHECK-NOT: firrtl.instance bb @MyBlackBox
// CHECK-NOT: firrtl.instance bb2 @MyBlackBox2
%bb_in, %bb_out = firrtl.instance bb sym @bb {annotations = [
{circt.nonlocal = @nla_2, class = "DummyB"},
{circt.nonlocal = @nla_3, class = "DummyC"}
{circt.nonlocal = @nla_old1, class = "Old1"},
{circt.nonlocal = @nla_old2, class = "Old2"},
{circt.nonlocal = @nla_on1, class = "On1"},
{circt.nonlocal = @nla_on2, class = "On2"}
]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
%bb2_in, %bb2_out = firrtl.instance bb2 {annotations = [{class = "sifive.enterprise.firrtl.ExtractBlackBoxAnnotation", filename = "BlackBoxes.txt", prefix = "prefix"}]} @MyBlackBox2(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
// CHECK: firrtl.connect %out, %prefix_0_out
@ -125,7 +137,7 @@ firrtl.circuit "ExtractBlackBoxesSimple2" attributes {annotations = [{class = "f
// CHECK: %dut_in, %dut_out, %dut_prefix_0_in, %dut_prefix_0_out, %dut_prefix_1_in, %dut_prefix_1_out = firrtl.instance dut
// CHECK-NOT: annotations =
// CHECK-SAME: sym {{@.+}} @DUTModule
// CHECK-NEXT: %bb_in, %bb_out = firrtl.instance bb sym [[BB_SYM:@.+]] {annotations = [{class = "DummyB"}, {class = "DummyC"}]} @MyBlackBox
// CHECK-NEXT: %bb_in, %bb_out = firrtl.instance bb sym [[BB_SYM:@.+]] {annotations = [{class = "Old1"}, {class = "On1"}, {class = "Old2"}, {class = "On2"}]} @MyBlackBox
// CHECK-NEXT: firrtl.strictconnect %bb_in, %dut_prefix_1_in
// CHECK-NEXT: firrtl.strictconnect %dut_prefix_1_out, %bb_out
// CHECK-NEXT: %bb2_in, %bb2_out = firrtl.instance bb2 sym [[BB2_SYM:@.+]] @MyBlackBox2
@ -153,25 +165,43 @@ firrtl.circuit "ExtractBlackBoxesSimple2" attributes {annotations = [{class = "f
// CHECK: firrtl.circuit "ExtractBlackBoxesIntoDUTSubmodule"
firrtl.circuit "ExtractBlackBoxesIntoDUTSubmodule" {
// CHECK-LABEL: firrtl.hierpath @nla_1 [
// CHECK-LABEL: firrtl.hierpath @nla_new_0 [
// CHECK-SAME: @ExtractBlackBoxesIntoDUTSubmodule::@tb
// CHECK-SAME: @TestHarness::@dut
// CHECK-SAME: @DUTModule::@BlackBoxes
// CHECK-SAME: @BlackBoxes
// CHECK-SAME: ]
// CHECK-LABEL: firrtl.hierpath @nla_new_1 [
// CHECK-SAME: @ExtractBlackBoxesIntoDUTSubmodule::@tb
// CHECK-SAME: @TestHarness::@dut
// CHECK-SAME: @DUTModule::@BlackBoxes
// CHECK-SAME: @BlackBoxes
// CHECK-SAME: ]
firrtl.hierpath @nla_new [
@ExtractBlackBoxesIntoDUTSubmodule::@tb,
@TestHarness::@dut,
@DUTModule::@mod,
@BBWrapper
]
// CHECK-LABEL: firrtl.hierpath @nla_old1 [
// CHECK-SAME: @ExtractBlackBoxesIntoDUTSubmodule::@tb
// CHECK-SAME: @TestHarness::@dut
// CHECK-SAME: @DUTModule::@BlackBoxes
// CHECK-SAME: @BlackBoxes::@bb1
// CHECK-SAME: ]
firrtl.hierpath @nla_1 [
firrtl.hierpath @nla_old1 [
@ExtractBlackBoxesIntoDUTSubmodule::@tb,
@TestHarness::@dut,
@DUTModule::@mod,
@BBWrapper::@bb1
]
// CHECK-LABEL: firrtl.hierpath @nla_2 [
// CHECK-LABEL: firrtl.hierpath @nla_old2 [
// CHECK-SAME: @ExtractBlackBoxesIntoDUTSubmodule::@tb
// CHECK-SAME: @TestHarness::@dut
// CHECK-SAME: @DUTModule::@BlackBoxes
// CHECK-SAME: @BlackBoxes::@bb2
// CHECK-SAME: ]
firrtl.hierpath @nla_2 [
firrtl.hierpath @nla_old2 [
@ExtractBlackBoxesIntoDUTSubmodule::@tb,
@TestHarness::@dut,
@DUTModule::@mod,
@ -179,8 +209,8 @@ firrtl.circuit "ExtractBlackBoxesIntoDUTSubmodule" {
]
firrtl.extmodule private @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>) attributes {annotations = [{class = "sifive.enterprise.firrtl.ExtractBlackBoxAnnotation", dest = "BlackBoxes", filename = "BlackBoxes.txt", prefix = "bb"}], defname = "MyBlackBox"}
firrtl.module private @BBWrapper(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
%bb1_in, %bb1_out = firrtl.instance bb1 sym @bb1 {annotations = [{circt.nonlocal = @nla_1, class = "Dummy1"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
%bb2_in, %bb2_out = firrtl.instance bb2 sym @bb2 {annotations = [{circt.nonlocal = @nla_2, class = "Dummy2"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
%bb1_in, %bb1_out = firrtl.instance bb1 sym @bb1 {annotations = [{circt.nonlocal = @nla_old1, class = "Dummy1"}, {circt.nonlocal = @nla_new, class = "Dummy3"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
%bb2_in, %bb2_out = firrtl.instance bb2 sym @bb2 {annotations = [{circt.nonlocal = @nla_old2, class = "Dummy2"}, {circt.nonlocal = @nla_new, class = "Dummy4"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %bb2_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %bb2_in, %bb1_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %bb1_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
@ -191,10 +221,10 @@ firrtl.circuit "ExtractBlackBoxesIntoDUTSubmodule" {
// CHECK-SAME: in %bb_1_in: !firrtl.uint<8>
// CHECK-SAME: out %bb_1_out: !firrtl.uint<8>
// CHECK-SAME: ) {
// CHECK-NEXT: %bb2_in, %bb2_out = firrtl.instance bb2 sym [[BB2_SYM:@.+]] {annotations = [{circt.nonlocal = @nla_2, class = "Dummy2"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
// CHECK-NEXT: %bb2_in, %bb2_out = firrtl.instance bb2 sym [[BB2_SYM:@.+]] {annotations = [{circt.nonlocal = @nla_new_0, class = "Dummy4"}, {circt.nonlocal = @nla_old2, class = "Dummy2"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
// CHECK-NEXT: firrtl.strictconnect %bb2_in, %bb_0_in : !firrtl.uint<8>
// CHECK-NEXT: firrtl.strictconnect %bb_0_out, %bb2_out : !firrtl.uint<8>
// CHECK-NEXT: %bb1_in, %bb1_out = firrtl.instance bb1 sym [[BB1_SYM:@.+]] {annotations = [{circt.nonlocal = @nla_1, class = "Dummy1"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
// CHECK-NEXT: %bb1_in, %bb1_out = firrtl.instance bb1 sym [[BB1_SYM:@.+]] {annotations = [{circt.nonlocal = @nla_new_1, class = "Dummy3"}, {circt.nonlocal = @nla_old1, class = "Dummy1"}]} @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
// CHECK-NEXT: firrtl.strictconnect %bb1_in, %bb_1_in : !firrtl.uint<8>
// CHECK-NEXT: firrtl.strictconnect %bb_1_out, %bb1_out : !firrtl.uint<8>
// CHECK-NEXT: }