[FIRRTL] Add ExtractInstances pass (#3017)

Add a `ExtractInstances` pass which implements a combined version of the
Scala `ExtractBlackBoxes`, `ExtractClockGates`, and `ExtractSeqMems`
passes. The pass is split into two phases:

As a first step it traverses modules and instances in the design and
marks the ones appropriately annotated for extraction.

In a second step it repeatedly pushes the marked instances up one level
of the hierarchy until they reach the desired parent module (usually
indicated with a `MarkDUTAnnotation`). The extracted instances are then
optionally grouped into a submodule at their extracted location. Finally
the pass also emits a text file indicating where the extracted instances
were originally located in the design.
This commit is contained in:
Fabian Schuiki 2022-05-06 09:02:58 +02:00 committed by GitHub
parent da0c16a8ac
commit 30dad68da3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1471 additions and 0 deletions

View File

@ -876,6 +876,90 @@ Example:
}
```
### Instance Extraction
#### ExtractBlackBoxAnnotation
| Property | Type | Description |
| -------- | ---- | ----------- |
| class | string | `sifive.enterprise.firrtl.ExtractBlackBoxAnnotation` |
| target | string | Reference target to the instance to be extracted |
| filename | string | Output file to be filled with the applied hierarchy changes |
| prefix | string | Prefix for the extracted instance |
| dest | string | Name of an optional wrapper module under which to group extracted instances |
This annotation causes the `ExtractInstances` pass to move the annotated
instance, or all instances if the annotation is on a module, upwards in the
hierarchy. If the `dest` field is present and non-empty, the instances are
placed in a module underneath the DUT (marked by `MarkDUTAnnotation`) with the
name provided in that field. If the `dest` field is empty, the instances are
extracted out of the DUT, such that the DUT gains additional ports that
correspond to the extracted instance ports. This allows the DUT to be
instantiated and custom implementations for the extracted instances to be
provided at the instantiation site. Instances are never extracted out of the
root module of the design.
Applies to modules and instances.
Example:
```json
{
"class": "sifive.enterprise.firrtl.ExtractBlackBoxAnnotation",
"target": "~TestHarness|MyBlackBox",
"filename": "BlackBoxes.txt",
"prefix": "bb",
"dest": "BlackBoxes" // optional
}
```
#### ExtractClockGatesFileAnnotation
| Property | Type | Description |
| -------- | ---- | ----------- |
| class | string | `sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation` |
| filename | string | Output file to be filled with the applied hierarchy changes |
| group | string | Name of an optional wrapper module under which to group extracted instances |
This annotation causes the `ExtractInstances` pass to move instances of
extmodules with defname `EICG_wrapper` upwards in the hierarchy, either out of
the DUT if `group` is omitted or empty, or into a submodule of the DUT with the
name given in `group`. The wiring prefix is hard-coded to `clock_gate`.
Applies to the circuit.
Example:
```json
{
"class": "sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation",
"filename": "ClockGates.txt",
"group": "ClockGates" // optional
}
```
#### ExtractSeqMemsFileAnnotation
| Property | Type | Description |
| -------- | ---- | ----------- |
| class | string | `sifive.enterprise.firrtl.ExtractSeqMemsFileAnnotation` |
| filename | string | Output file to be filled with the applied hierarchy changes |
| group | string | Name of an optional wrapper module under which to group extracted instances |
This annotation causes the `ExtractInstances` pass to move memory instances
upwards in the hierarchy, either out of the DUT if `group` is omitted or empty,
or into a submodule of the DUT with the name given in `group`. The wiring
prefix is hard-coded to `mem_wiring`.
Applies to the circuit.
Example:
```json
{
"class": "sifive.enterprise.firrtl.ExtractSeqMemsFileAnnotation",
"filename": "SeqMems.txt",
"group": "SeqMems" // optional
}
```
## FIRRTL specific attributes applied to HW Modules
### Design Under Test

View File

@ -65,6 +65,14 @@ constexpr const char *testbenchDirAnnoClass =
constexpr const char *injectDUTHierarchyAnnoClass =
"sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation";
// Instance Extraction
constexpr const char *extractBlackBoxAnnoClass =
"sifive.enterprise.firrtl.ExtractBlackBoxAnnotation";
constexpr const char *extractClockGatesAnnoClass =
"sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation";
constexpr const char *extractSeqMemsAnnoClass =
"sifive.enterprise.firrtl.ExtractSeqMemsFileAnnotation";
} // namespace firrtl
} // namespace circt

View File

@ -93,6 +93,8 @@ createMergeConnectionsPass(bool enableAggressiveMerging = false);
std::unique_ptr<mlir::Pass> createInjectDUTHierarchyPass();
std::unique_ptr<mlir::Pass> createExtractInstancesPass();
/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/FIRRTL/Passes.h.inc"

View File

@ -431,4 +431,20 @@ def InjectDUTHierarchy : Pass<"firrtl-inject-dut-hier", "firrtl::CircuitOp"> {
let constructor = "circt::firrtl::createInjectDUTHierarchyPass()";
}
def ExtractInstances : Pass<"firrtl-extract-instances", "firrtl::CircuitOp"> {
let summary = "Move annotated instances upwards in the module hierarchy";
let description = [{
This pass takes instances in the design annotated with one out of a
particular set of annotations and pulls them upwards to a location further
up in the module hierarchy.
The annotations that control the behaviour of this pass are:
- `MarkDUTAnnotation`
- `ExtractBlackBoxAnnotation`
- `ExtractClockGatesFileAnnotation`
}];
let constructor = "circt::firrtl::createExtractInstancesPass()";
let dependentDialects = ["sv::SVDialect", "circt::hw::HWDialect"];
}
#endif // CIRCT_DIALECT_FIRRTL_PASSES_TD

View File

@ -6,6 +6,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms
Dedup.cpp
EmitOMIR.cpp
ExpandWhens.cpp
ExtractInstances.cpp
FlattenMemory.cpp
GrandCentral.cpp
GrandCentralTaps.cpp

View File

@ -0,0 +1,930 @@
//===- ExtractInstances.cpp - Move instances up the hierarchy ---*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Moves annotated instances upwards in the module hierarchy. Corresponds to the
// `ExtractBlackBoxes`, `ExtractClockGates`, and `ExtractSeqMems` passes in the
// Scala FIRRTL implementation.
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/FIRRTL/AnnotationDetails.h"
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
#include "circt/Dialect/FIRRTL/FIRRTLInstanceGraph.h"
#include "circt/Dialect/FIRRTL/NLATable.h"
#include "circt/Dialect/FIRRTL/Namespace.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/SV/SVOps.h"
#include "circt/Support/Path.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/ImplicitLocOpBuilder.h"
#include "mlir/Support/FileUtilities.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#define DEBUG_TYPE "firrtl-extract-instances"
using namespace circt;
using namespace firrtl;
using hw::InnerRefAttr;
//===----------------------------------------------------------------------===//
// Pass Implementation
//===----------------------------------------------------------------------===//
namespace {
/// All information necessary to move instances about.
struct ExtractionInfo {
/// A filename into which the performed hierarchy modifications are emitted.
StringRef traceFilename;
/// A prefix to attach to the wiring generated by the extraction.
StringRef prefix;
/// Optional name of the wrapper module that will hold the moved instance.
StringRef wrapperModule;
/// Whether the extraction should stop at the root of the DUT instead of going
/// past that and extracting into the test harness.
bool stopAtDUT;
};
struct ExtractInstancesPass
: public ExtractInstancesBase<ExtractInstancesPass> {
void runOnOperation() override;
void collectAnnos();
void collectAnno(InstanceOp inst, Annotation anno);
void extractInstances();
void groupInstances();
void createTraceFiles();
/// Get the cached namespace for a module.
ModuleNamespace &getModuleNamespace(FModuleLike module) {
auto it = moduleNamespaces.find(module);
if (it != moduleNamespaces.end())
return it->second;
return moduleNamespaces.try_emplace(module, ModuleNamespace(module))
.first->second;
}
/// Returns an operation's `inner_sym`, adding one if necessary.
StringAttr getOrAddInnerSym(Operation *op) {
auto attr = op->getAttrOfType<StringAttr>("inner_sym");
if (attr)
return attr;
auto module = op->getParentOfType<FModuleOp>();
auto name = getModuleNamespace(module).newName("extraction_sym");
attr = StringAttr::get(op->getContext(), name);
op->setAttr("inner_sym", attr);
return attr;
}
/// Obtain an inner reference to an operation, possibly adding an `inner_sym`
/// to that operation.
InnerRefAttr getInnerRefTo(Operation *op) {
return InnerRefAttr::get(
SymbolTable::getSymbolName(op->getParentOfType<FModuleOp>()),
getOrAddInnerSym(op));
}
bool anythingChanged;
bool anyFailures;
InstanceGraph *instanceGraph = nullptr;
/// The modules in the design that are annotated with one or more annotations
/// relevant for instance extraction.
DenseMap<Operation *, SmallVector<Annotation, 1>> annotatedModules;
/// All modules that are marked as DUT themselves. Realistically this is only
/// ever one module in the design.
DenseSet<Operation *> dutRootModules;
/// All modules that are marked as DUT themselves, or that have a DUT parent
/// module.
DenseSet<Operation *> dutModules;
/// All DUT module names.
DenseSet<Attribute> dutModuleNames;
/// A worklist of instances that need to be moved.
SmallVector<std::pair<InstanceOp, ExtractionInfo>> extractionWorklist;
/// The path along which instances have been extracted. This essentially
/// documents the original location of the instance in reverse. Every push
/// upwards in the hierarchy adds another entry to this path documenting along
/// which instantiation path each instance was extracted.
DenseMap<Operation *, SmallVector<InnerRefAttr>> extractionPaths;
/// A map of the original parent modules of instances before they were
/// extracted. This is used in a corner case during trace file emission.
DenseMap<Operation *, StringAttr> originalInstanceParents;
/// All extracted instances in their position after moving upwards in the
/// hierarchy, but before being grouped into an optional submodule.
SmallVector<std::pair<InstanceOp, ExtractionInfo>> extractedInstances;
// The uniquified wiring prefix for each instance.
DenseMap<Operation *, SmallString<16>> instPrefices;
/// The current circuit namespace valid within the call to `runOnOperation`.
CircuitNamespace circuitNamespace;
/// Cached module namespaces.
DenseMap<Operation *, ModuleNamespace> moduleNamespaces;
};
} // end anonymous namespace
/// Emit the annotated source code for black boxes in a circuit.
void ExtractInstancesPass::runOnOperation() {
CircuitOp circuitOp = getOperation();
anythingChanged = false;
anyFailures = false;
annotatedModules.clear();
dutRootModules.clear();
dutModules.clear();
extractionWorklist.clear();
extractionPaths.clear();
originalInstanceParents.clear();
extractedInstances.clear();
instPrefices.clear();
moduleNamespaces.clear();
circuitNamespace.clear();
circuitNamespace.add(circuitOp);
// Walk the IR and gather all the annotations relevant for extraction that
// appear on instances and the instantiated modules.
instanceGraph = &getAnalysis<InstanceGraph>();
collectAnnos();
if (anyFailures)
return signalPassFailure();
// Actually move instances upwards.
extractInstances();
if (anyFailures)
return signalPassFailure();
// Group instances into submodules, if requested.
groupInstances();
if (anyFailures)
return signalPassFailure();
// Generate the trace files that list where each instance was extracted from.
createTraceFiles();
if (anyFailures)
return signalPassFailure();
// If nothing has changed we can preseve the analysis.
LLVM_DEBUG(llvm::dbgs() << "\n");
if (!anythingChanged)
markAllAnalysesPreserved();
markAnalysesPreserved<InstanceGraph>();
}
static bool isAnnoInteresting(Annotation anno) {
return anno.isClass(extractBlackBoxAnnoClass);
}
/// Gather the modules and instances annotated to be moved by this pass. This
/// populates the corresponding lists and maps of the pass.
void ExtractInstancesPass::collectAnnos() {
CircuitOp circuit = getOperation();
// Grab the clock gate extraction annotation on the circuit.
StringRef clkgateFileName;
StringRef clkgateWrapperModule;
AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
if (!anno.isClass(extractClockGatesAnnoClass))
return false;
LLVM_DEBUG(llvm::dbgs()
<< "Clock gate extraction config: " << anno.getDict() << "\n");
auto filenameAttr = anno.getMember<StringAttr>("filename");
auto groupAttr = anno.getMember<StringAttr>("group");
if (!filenameAttr) {
circuit.emitError("missing `filename` attribute in `")
<< anno.getClass() << "` annotation";
anyFailures = true;
return true;
}
if (!clkgateFileName.empty()) {
circuit.emitError("multiple `")
<< anno.getClass() << "` annotations on circuit";
anyFailures = true;
return true;
}
clkgateFileName = filenameAttr.getValue();
if (groupAttr)
clkgateWrapperModule = groupAttr.getValue();
return true;
});
// Grab the memory extraction annotation on the circuit.
StringRef memoryFileName;
StringRef memoryWrapperModule;
AnnotationSet::removeAnnotations(circuit, [&](Annotation anno) {
if (!anno.isClass(extractSeqMemsAnnoClass))
return false;
LLVM_DEBUG(llvm::dbgs()
<< "Memory extraction config: " << anno.getDict() << "\n");
auto filenameAttr = anno.getMember<StringAttr>("filename");
auto groupAttr = anno.getMember<StringAttr>("group");
if (!filenameAttr) {
circuit.emitError("missing `filename` attribute in `")
<< anno.getClass() << "` annotation";
anyFailures = true;
return true;
}
if (!memoryFileName.empty()) {
circuit.emitError("multiple `")
<< anno.getClass() << "` annotations on circuit";
anyFailures = true;
return true;
}
memoryFileName = filenameAttr.getValue();
if (groupAttr)
memoryWrapperModule = groupAttr.getValue();
return true;
});
// Gather the annotations on modules. These complement the later per-instance
// annotations.
for (auto module : circuit.getOps<FModuleLike>()) {
AnnotationSet::removeAnnotations(module, [&](Annotation anno) {
if (anno.isClass(dutAnnoClass)) {
LLVM_DEBUG(llvm::dbgs()
<< "Marking DUT `" << module.moduleName() << "`\n");
dutRootModules.insert(module);
dutModules.insert(module);
return false; // other passes may rely on this anno; keep it
}
if (!isAnnoInteresting(anno))
return false;
LLVM_DEBUG(llvm::dbgs() << "Annotated module `" << module.moduleName()
<< "`:\n " << anno.getDict() << "\n");
annotatedModules[module].push_back(anno);
return true;
});
}
// Gather the annotations on instances to be extracted.
circuit.walk([&](InstanceOp inst) {
SmallVector<Annotation, 1> instAnnos;
Operation *module = instanceGraph->getReferencedModule(inst);
// Module-level annotations.
auto it = annotatedModules.find(module);
if (it != annotatedModules.end())
instAnnos.append(it->second);
// Instance-level annotations.
AnnotationSet::removeAnnotations(inst, [&](Annotation anno) {
if (!isAnnoInteresting(anno))
return false;
LLVM_DEBUG(llvm::dbgs() << "Annotated instance `" << inst.name()
<< "`:\n " << anno.getDict() << "\n");
instAnnos.push_back(anno);
return true;
});
// No need to do anything about unannotated instances.
if (instAnnos.empty())
return;
// Ensure there are no conflicting annotations.
if (instAnnos.size() > 1) {
auto d = inst.emitError("multiple extraction annotations on instance `")
<< inst.name() << "`";
d.attachNote(inst.getLoc()) << "instance has the following annotations, "
"but at most one is allowed:";
for (auto anno : instAnnos)
d.attachNote(inst.getLoc()) << anno.getDict();
anyFailures = true;
return;
}
// Process the annotation.
collectAnno(inst, instAnnos[0]);
});
// Propagate the DUT marking to all arbitrarily nested submodules of the DUT.
LLVM_DEBUG(llvm::dbgs() << "Marking DUT hierarchy\n");
SmallVector<InstanceGraphNode *> worklist;
for (Operation *op : dutModules)
worklist.push_back(instanceGraph->lookup(cast<hw::HWModuleLike>(op)));
while (!worklist.empty()) {
auto *module = worklist.pop_back_val();
dutModuleNames.insert(module->getModule().moduleNameAttr());
LLVM_DEBUG(llvm::dbgs()
<< "- " << module->getModule().moduleName() << "\n");
for (auto *instRecord : *module) {
auto *target = instRecord->getTarget();
if (dutModules.insert(target->getModule()).second)
worklist.push_back(target);
}
}
// If clock gate extraction is requested, find instances of extmodules with
// the corresponding `defname` and mark them as to be extracted.
// TODO: This defname really shouldn't be hardcoded here. Make this at least
// somewhat configurable.
if (!clkgateFileName.empty()) {
auto clkgateDefNameAttr = StringAttr::get(&getContext(), "EICG_wrapper");
for (auto module : circuit.getOps<FExtModuleOp>()) {
if (module.defnameAttr() != clkgateDefNameAttr)
continue;
LLVM_DEBUG(llvm::dbgs()
<< "Clock gate `" << module.moduleName() << "`\n");
if (!dutModules.contains(module)) {
LLVM_DEBUG(llvm::dbgs() << "- Ignored (outside DUT)\n");
continue;
}
ExtractionInfo info;
info.traceFilename = clkgateFileName;
info.prefix = "clock_gate"; // TODO: Don't hardcode this
info.wrapperModule = clkgateWrapperModule;
info.stopAtDUT = !info.wrapperModule.empty();
for (auto *instRecord :
instanceGraph->lookup(cast<hw::HWModuleLike>(*module))->uses()) {
if (auto inst = dyn_cast<InstanceOp>(*instRecord->getInstance())) {
LLVM_DEBUG(llvm::dbgs()
<< "- Marking `"
<< inst->getParentOfType<FModuleLike>().moduleName() << "."
<< inst.name() << "`\n");
extractionWorklist.push_back({inst, info});
}
}
}
}
}
/// Process an extraction annotation on an instance into a corresponding
/// `ExtractionInfo` and a spot on the worklist for later moving things around.
void ExtractInstancesPass::collectAnno(InstanceOp inst, Annotation anno) {
LLVM_DEBUG(llvm::dbgs() << "Processing instance `" << inst.name() << "` "
<< anno.getDict() << "\n");
auto getStringOrError = [&](StringRef member) {
auto attr = anno.getMember<StringAttr>(member);
if (!attr) {
inst.emitError("missing `")
<< member << "` attribute in `" << anno.getClass() << "` annotation";
anyFailures = true;
}
return attr;
};
if (anno.isClass(extractBlackBoxAnnoClass)) {
auto filename = getStringOrError("filename");
auto prefix = getStringOrError("prefix");
auto dest = anno.getMember<StringAttr>("dest"); // optional
if (anyFailures)
return;
ExtractionInfo info;
info.traceFilename = filename;
info.prefix = prefix;
info.wrapperModule = (dest ? dest.getValue() : "");
// CAVEAT: If the instance has a wrapper module configured then extraction
// should stop at the DUT module instead of extracting past the DUT into the
// surrounding test harness. This is all very ugly and hacky.
info.stopAtDUT = !info.wrapperModule.empty();
extractionWorklist.push_back({inst, info});
return;
}
}
/// 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.
void ExtractInstancesPass::extractInstances() {
// The list of ports to be added to an instance's parent module. Cleared and
// reused across instances.
SmallVector<std::pair<unsigned, PortInfo>> newPorts;
// The number of instances with the same prefix. Used to uniquify prefices.
DenseMap<StringRef, unsigned> prefixUniqueIDs;
SmallPtrSet<Operation *, 4> nlasToRemove;
SmallPtrSet<Attribute, 4> nlasToRemoveFromParent;
auto &nlaTable = getAnalysis<NLATable>();
// Keep track of where the instance was originally.
for (auto &[inst, info] : extractionWorklist)
originalInstanceParents[inst] =
inst->getParentOfType<FModuleLike>().moduleNameAttr();
while (!extractionWorklist.empty()) {
auto [inst, info] = extractionWorklist.pop_back_val();
auto parent = inst->getParentOfType<FModuleOp>();
// Figure out the wiring prefix to use for this instance. If we are supposed
// to use a wiring prefix (`info.prefix` is non-empty), we assemble a
// `<prefix>_<N>` string, where `N` is an unsigned integer used to uniquifiy
// the prefix. This is very close to what the original Scala implementation
// of the pass does, which would group instances to be extracted by prefix
// and then iterate over them with the index in the group being used as `N`.
StringRef prefix;
if (!info.prefix.empty()) {
auto &prefixSlot = instPrefices[inst];
if (prefixSlot.empty()) {
auto idx = prefixUniqueIDs[info.prefix]++;
(Twine(info.prefix) + "_" + Twine(idx)).toVector(prefixSlot);
}
prefix = prefixSlot;
}
// If the instance is already in the right place (outside the DUT or already
// in the root module), there's nothing left for us to do. Otherwise we
// proceed to bubble it up one level in the hierarchy and add the resulting
// instances back to the worklist.
if (!dutModules.contains(parent) ||
instanceGraph->lookup(parent)->noUses() ||
(info.stopAtDUT && dutRootModules.contains(parent))) {
LLVM_DEBUG(llvm::dbgs() << "\nNo need to further move " << inst << "\n");
extractedInstances.push_back({inst, info});
continue;
}
LLVM_DEBUG({
llvm::dbgs() << "\nMoving ";
if (!prefix.empty())
llvm::dbgs() << "`" << prefix << "` ";
llvm::dbgs() << inst << "\n";
});
// Add additional ports to the parent module as a replacement for the
// instance port signals once the instance is extracted.
unsigned numParentPorts = parent.getNumPorts();
unsigned numInstPorts = inst.getNumResults();
for (unsigned portIdx = 0; portIdx < numInstPorts; ++portIdx) {
// Assemble the new port name as "<prefix>_<name>", where the prefix is
// provided by the extraction annotation.
auto name = inst.getPortNameStr(portIdx);
auto nameAttr = StringAttr::get(
&getContext(),
prefix.empty() ? Twine(name) : Twine(prefix) + "_" + name);
PortInfo newPort{nameAttr,
inst.getResult(portIdx).getType().cast<FIRRTLType>(),
direction::flip(inst.getPortDirection(portIdx))};
newPort.loc = inst.getResult(portIdx).getLoc();
newPorts.push_back({numParentPorts, newPort});
LLVM_DEBUG(llvm::dbgs()
<< "- Adding port " << newPort.direction << " "
<< newPort.name.getValue() << ": " << newPort.type << "\n");
}
parent.insertPorts(newPorts);
anythingChanged = true;
// Replace all uses of the existing instance ports with the newly-created
// module ports.
for (unsigned portIdx = 0; portIdx < numInstPorts; ++portIdx) {
inst.getResult(portIdx).replaceAllUsesWith(
parent.getArgument(numParentPorts + portIdx));
}
assert(inst.use_empty() && "instance ports should have been detached");
// 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 oldParentInst = cast<InstanceOp>(*instRecord->getInstance());
auto newParent = oldParentInst->getParentOfType<FModuleLike>();
LLVM_DEBUG(llvm::dbgs() << "- Updating " << oldParentInst << "\n");
auto newParentInst = oldParentInst.cloneAndInsertPorts(newPorts);
// Migrate connections to existing ports.
for (unsigned portIdx = 0; portIdx < numParentPorts; ++portIdx)
oldParentInst.getResult(portIdx).replaceAllUsesWith(
newParentInst.getResult(portIdx));
// Add the moved instance and hook it up to the added ports.
ImplicitLocOpBuilder builder(inst.getLoc(), newParentInst);
auto newInst = inst.cloneAndInsertPorts({});
newInst->remove();
builder.setInsertionPointAfter(newParentInst);
builder.insert(newInst);
for (unsigned portIdx = 0; portIdx < numInstPorts; ++portIdx) {
auto dst = newInst.getResult(portIdx);
auto src = newParentInst.getResult(numParentPorts + portIdx);
if (newPorts[portIdx].second.direction == Direction::In)
std::swap(src, dst);
builder.create<StrictConnectOp>(dst, src);
}
// Move the wiring prefix from the old to the new instance. We just look
// up the prefix for the old instance and if it exists, we remove it and
// assign it to the new instance. This has the effect of making the first
// new instance we create inherit the wiring prefix, and all additional
// new instances (e.g. through multiple instantiation of the parent) will
// pick a new prefix.
auto oldPrefix = instPrefices.find(inst);
if (oldPrefix != instPrefices.end()) {
LLVM_DEBUG(llvm::dbgs()
<< " - Reusing prefix `" << oldPrefix->second << "`\n");
auto newPrefix = std::move(oldPrefix->second);
instPrefices.erase(oldPrefix);
instPrefices.insert({newInst, newPrefix});
}
// Inherit the old instance's extraction path.
auto &extractionPath = (extractionPaths[newInst] = extractionPaths[inst]);
extractionPath.push_back(getInnerRefTo(newParentInst));
originalInstanceParents[newInst] = originalInstanceParents[inst];
// Update all NLAs that touch the moved instance. We do this through
// `removeAnnotations` which gives us an opportunity to drop annotations
// that we want to replace with an updated version. We add the new ones to
// `newInstAnnos` since we cannot modify the `AnnotationSet` within
// `removeAnnotations`.
AnnotationSet annos(newInst);
SmallVector<Attribute> newInstAnnos;
annos.removeAnnotations([&](Annotation anno) {
auto nlaName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
if (!nlaName)
return false;
auto nla = nlaTable.getNLA(nlaName.getAttr());
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() == newInst.inner_symAttr())
break;
}
assert(nlaIdx < nlaLen && "instance not found in its own NLA");
LLVM_DEBUG(llvm::dbgs() << " - Position " << nlaIdx << "\n");
// Since we're moving the instance out of its parent module, this NLA
// will no longer pass through that parent module. Mark it to be removed
// from the parent later.
nlasToRemoveFromParent.insert(nla.sym_nameAttr());
// There are two interesting cases now:
// - If `nlaIdx == 0`, the NLA is rooted at the module the instance was
// located in prior to extraction. This indicates that the NLA applies
// to all instances of that parent module. Since we are extracting
// *out* of that module, we have to create a new NLA rooted at the new
// parent module after extraction.
// - If `nlaIdx > 0`, the NLA is rooted further up in the hierarchy and
// we can simply remove the old parent module from the path.
// Handle the case where we need to come up with a new NLA for this
// instance since we've moved it past the module at which the old NLA
// was rooted at.
if (nlaIdx == 0) {
LLVM_DEBUG(llvm::dbgs()
<< " - Re-rooting " << nlaPath[nlaIdx] << "\n");
nlaPath[nlaIdx] =
InnerRefAttr::get(newParent.moduleNameAttr(),
nlaPath[nlaIdx].cast<InnerRefAttr>().getName());
auto builder = OpBuilder::atBlockBegin(getOperation().getBody());
// CAVEAT(fschuiki): There are likely cases where the following
// creates multiple NLAs with the same name. This is going to happen
// if an NLA has to be re-rooted and the parent is instantiated
// multiple times, requiring us to create a new NLA for each of the
// parent instances. To fix this you'll have to pick a name through
// `circuitNamespace.newName(...)` and visit every module/instance in
// the old NLA, remove the reference to the old NLA, and add
// references for all the newly-created ones.
auto newNla = builder.create<NonLocalAnchor>(
newInst.getLoc(), nla.sym_nameAttr(),
builder.getArrayAttr(nlaPath));
nlasToRemove.insert(nla);
nlaTable.addNLA(newNla);
// This modifies our local copy of the anno, not the actual anno on
// the operation.
anno.setMember("circt.nonlocal",
FlatSymbolRefAttr::get(newNla.sym_nameAttr()));
newInstAnnos.push_back(anno.getDict());
LLVM_DEBUG(llvm::dbgs() << " - Created " << newNla << "\n");
return true; // delete the old anno
}
// In the subequent code block we are going to remove one element from
// the NLA path, corresponding to the fact that the extracted instance
// has moved up in the hierarchy by one level. Removing that element may
// leave the NLA in a degenerate state, with only a single element in
// its path. If that is the case we have to convert the NLA into a
// regular local annotation.
if (nlaPath.size() == 2) {
anno.removeMember("circt.nonlocal");
newInstAnnos.push_back(anno.getDict());
nlasToRemove.insert(nla);
LLVM_DEBUG(llvm::dbgs()
<< " - Converted to local " << anno.getDict() << "\n");
return true; // delete the old anno
}
// At this point the NLA looks like `NewParent::X, OldParent::BB`, and
// 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`.
LLVM_DEBUG(llvm::dbgs() << " - Merging " << nlaPath[nlaIdx - 1]
<< " and " << nlaPath[nlaIdx] << "\n");
nlaPath[nlaIdx] = InnerRefAttr::get(
nlaPath[nlaIdx - 1].cast<InnerRefAttr>().getModule(),
nlaPath[nlaIdx].cast<InnerRefAttr>().getName());
nlaPath.erase(nlaPath.begin() + nlaIdx - 1);
nla.namepathAttr(builder.getArrayAttr(nlaPath));
LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
return false;
});
annos.addAnnotations(newInstAnnos);
annos.applyToOperation(newInst);
// Add the moved instance to the extraction worklist such that it gets
// bubbled up further if needed.
extractionWorklist.push_back({newInst, info});
LLVM_DEBUG(llvm::dbgs() << " - Updated to " << newInst << "\n");
// Remove the obsolete NLAs from the instance of the parent module, since
// the extracted instance no longer resides in that module and any NLAs to
// it no longer go through the parent module.
AnnotationSet::removeAnnotations(newParentInst, [&](Annotation anno) {
auto nlaName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
if (nlaName && nlasToRemoveFromParent.contains(nlaName.getAttr())) {
LLVM_DEBUG(llvm::dbgs()
<< " - Remove from parent: " << anno.getDict() << "\n");
return true;
}
return false;
});
nlasToRemoveFromParent.clear();
// Keep instance graph up-to-date.
instanceGraph->replaceInstance(oldParentInst, newParentInst);
oldParentInst.erase();
}
// Clean up the original instance.
inst.erase();
newPorts.clear();
}
// Remove unused NLAs.
for (Operation *op : nlasToRemove) {
LLVM_DEBUG(llvm::dbgs() << "Removing obsolete " << *op << "\n");
op->erase();
}
}
/// Group instances into submodules after they have been moved upwards. This
/// only occurs for instances that had the corresponding `dest` field of the
/// annotation set.
void ExtractInstancesPass::groupInstances() {
// Group the extracted instances by their wrapper module name and their parent
// module. Note that we cannot group instances that landed in different parent
// modules into the same submodule, so we use that parent module as a grouping
// key.
SmallDenseMap<std::pair<Operation *, StringRef>, SmallVector<InstanceOp>>
instsByWrapper;
for (auto &[inst, info] : extractedInstances) {
if (!info.wrapperModule.empty())
instsByWrapper[{inst->getParentOfType<FModuleOp>(), info.wrapperModule}]
.push_back(inst);
}
if (instsByWrapper.empty())
return;
LLVM_DEBUG(llvm::dbgs() << "\nGrouping instances into wrappers\n");
// Generate the wrappers.
SmallVector<PortInfo> ports;
SmallVector<Attribute> wrapperInstAnnos;
auto &nlaTable = getAnalysis<NLATable>();
for (auto &[parentAndWrapperName, insts] : instsByWrapper) {
auto [parentOp, wrapperName] = parentAndWrapperName;
auto parent = cast<FModuleOp>(parentOp);
LLVM_DEBUG(llvm::dbgs() << "- Wrapper `" << wrapperName << "` in `"
<< parent.moduleName() << "` with " << insts.size()
<< " instances\n");
OpBuilder builder(parentOp);
// Uniquify the wrapper name.
wrapperName = circuitNamespace.newName(wrapperName);
auto wrapperInstName =
builder.getStringAttr(getModuleNamespace(parent).newName(wrapperName));
// Assemble a list of ports for the wrapper module, which is basically just
// a concatenation of the wrapped instance ports. Also keep track of the
// NLAs that target the grouped instances since these will have to pass
// through the wrapper module.
ports.clear();
wrapperInstAnnos.clear();
for (auto inst : insts) {
// Determine the ports for the wrapper.
StringRef prefix(instPrefices[inst]);
unsigned portNum = inst.getNumResults();
for (unsigned portIdx = 0; portIdx < portNum; ++portIdx) {
auto name = inst.getPortNameStr(portIdx);
auto nameAttr = builder.getStringAttr(
prefix.empty() ? Twine(name) : Twine(prefix) + "_" + name);
PortInfo port{nameAttr,
inst.getResult(portIdx).getType().cast<FIRRTLType>(),
inst.getPortDirection(portIdx)};
port.loc = inst.getResult(portIdx).getLoc();
ports.push_back(port);
}
// Determine the additional NLA breadcrumbs to put on the wrapper instance
// and update the NLA's path to include the wrapper instance we're about
// to construct.
for (auto anno : AnnotationSet(inst)) {
auto nlaName = anno.getMember<FlatSymbolRefAttr>("circt.nonlocal");
if (!nlaName)
continue;
auto nla = nlaTable.getNLA(nlaName.getAttr());
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;
}
assert(nlaIdx < nlaLen && "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());
LLVM_DEBUG(llvm::dbgs() << " - Expanding " << nlaPath[nlaIdx]
<< " to (" << ref1 << ", " << ref2 << ")\n");
nlaPath[nlaIdx] = ref1;
nlaPath.insert(nlaPath.begin() + nlaIdx + 1, ref2);
nla.namepathAttr(builder.getArrayAttr(nlaPath));
LLVM_DEBUG(llvm::dbgs() << " - Modified to " << nla << "\n");
// Create a breadcrumb annotation for the wrapper instance which we'll
// create further down.
wrapperInstAnnos.push_back(builder.getDictionaryAttr(
{{builder.getStringAttr("class"),
builder.getStringAttr("circt.nonlocal")},
{builder.getStringAttr("circt.nonlocal"),
FlatSymbolRefAttr::get(nla.sym_nameAttr())}}));
}
}
// Create the wrapper module.
auto wrapper = builder.create<FModuleOp>(
builder.getUnknownLoc(), builder.getStringAttr(wrapperName), ports);
// Instantiate the wrapper module in the parent and replace uses of the
// extracted instances' ports with the corresponding wrapper module ports.
// This will essentially disconnect the extracted instances.
builder.setInsertionPointToStart(parent.getBody());
auto wrapperInst = builder.create<InstanceOp>(
wrapper.getLoc(), wrapper, wrapperName, wrapperInstAnnos,
/*portAnnotations=*/ArrayRef<Attribute>{}, /*lowerToBind=*/false,
wrapperInstName);
unsigned portIdx = 0;
for (auto inst : insts)
for (auto result : inst.getResults())
result.replaceAllUsesWith(wrapperInst.getResult(portIdx++));
// Move all instances into the wrapper module and wire them up to the
// wrapper ports.
portIdx = 0;
builder.setInsertionPointToStart(wrapper.getBody());
for (auto inst : insts) {
inst->remove();
builder.insert(inst);
for (auto result : inst.getResults()) {
Value dst = result;
Value src = wrapper.getArgument(portIdx);
if (ports[portIdx].direction == Direction::Out)
std::swap(dst, src);
builder.create<StrictConnectOp>(result.getLoc(), dst, src);
++portIdx;
}
}
}
}
/// Generate trace files, which are plain text metadata files that list the
/// hierarchical path where each instance was extracted from. The file lists one
/// instance per line in the form `<prefix> -> <original-path>`.
void ExtractInstancesPass::createTraceFiles() {
LLVM_DEBUG(llvm::dbgs() << "\nGenerating trace files\n");
auto builder = OpBuilder::atBlockEnd(getOperation().getBody());
// Group the extracted instances by their trace file name.
SmallDenseMap<StringRef, SmallVector<InstanceOp>> instsByTraceFile;
for (auto &[inst, info] : extractedInstances)
if (!info.traceFilename.empty())
instsByTraceFile[info.traceFilename].push_back(inst);
// Generate the trace files.
SmallVector<Attribute> symbols;
SmallDenseMap<Attribute, unsigned> symbolIndices;
for (auto &[fileName, insts] : instsByTraceFile) {
LLVM_DEBUG(llvm::dbgs() << "- " << fileName << "\n");
std::string buffer;
llvm::raw_string_ostream os(buffer);
symbols.clear();
symbolIndices.clear();
auto addSymbol = [&](Attribute symbol) {
unsigned id;
auto it = symbolIndices.find(symbol);
if (it != symbolIndices.end()) {
id = it->second;
} else {
id = symbols.size();
symbols.push_back(symbol);
symbolIndices.insert({symbol, id});
}
os << "{{" << id << "}}";
};
for (auto inst : insts) {
StringRef prefix(instPrefices[inst]);
if (prefix.empty()) {
LLVM_DEBUG(llvm::dbgs() << " - Skipping `" << inst.name()
<< "` since it has no extraction prefix\n");
continue;
}
ArrayRef<InnerRefAttr> path(extractionPaths[inst]);
if (path.empty()) {
LLVM_DEBUG(llvm::dbgs() << " - Skipping `" << inst.name()
<< "` since it has not been moved\n");
continue;
}
LLVM_DEBUG(llvm::dbgs()
<< " - " << prefix << ": " << inst.name() << "\n");
os << prefix << " -> ";
// HACK: To match the Scala implementation, we strip all non-DUT modules
// from the path and make the path look like it's rooted at the first DUT
// module (so `TestHarness.dut.foo.bar` becomes `DUTModule.foo.bar`).
while (!path.empty() &&
!dutModuleNames.contains(path.back().getModule())) {
LLVM_DEBUG(llvm::dbgs()
<< " - Dropping non-DUT segment " << path.back() << "\n");
path = path.drop_back();
}
// HACK: This is extremely ugly. In case the instance was just moved by a
// single level, the path may become empty. In that case we simply use the
// instance's original parent before it was moved.
addSymbol(FlatSymbolRefAttr::get(path.empty()
? originalInstanceParents[inst]
: path.back().getModule()));
for (auto sym : llvm::reverse(path)) {
os << ".";
addSymbol(sym);
}
os << ".";
addSymbol(getInnerRefTo(inst));
os << "\n";
}
// Put the information in a verbatim operation.
auto verbatimOp = builder.create<sv::VerbatimOp>(
builder.getUnknownLoc(), buffer, ValueRange{},
builder.getArrayAttr(symbols));
auto fileAttr = hw::OutputFileAttr::getFromFilename(
builder.getContext(), fileName, /*excludeFromFilelist=*/true);
verbatimOp->setAttr("output_file", fileAttr);
}
}
//===----------------------------------------------------------------------===//
// Pass Creation
//===----------------------------------------------------------------------===//
std::unique_ptr<mlir::Pass> circt::firrtl::createExtractInstancesPass() {
return std::make_unique<ExtractInstancesPass>();
}

View File

@ -0,0 +1,60 @@
// RUN: circt-opt --firrtl-inject-dut-hier --firrtl-extract-instances --verify-diagnostics %s | FileCheck %s
// Tests extracted from:
// - test/scala/firrtl/ExtractClockGates.scala
//===----------------------------------------------------------------------===//
// ExtractClockGates Multigrouping
//===----------------------------------------------------------------------===//
// CHECK: firrtl.circuit "ExtractClockGatesMultigrouping"
firrtl.circuit "ExtractClockGatesMultigrouping" attributes {annotations = [{class = "sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation", filename = "ClockGates.txt", group = "ClockGatesGroup"}, {class = "sifive.enterprise.firrtl.InjectDUTHierarchyAnnotation", name = "InjectedSubmodule"}]} {
firrtl.extmodule private @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper"}
// CHECK-LABEL: firrtl.module private @SomeModule
firrtl.module private @SomeModule(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) {
// CHECK-NOT: firrtl.instance gate @EICG_wrapper
%gate_in, %gate_en, %gate_out = firrtl.instance gate @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock)
firrtl.connect %gate_in, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %gate_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
}
// CHECK-LABEL: firrtl.module private @InjectedSubmodule
// CHECK: firrtl.instance inst0 sym [[INST0_SYM:@.+]] @SomeModule
// CHECK: firrtl.instance inst1 sym [[INST1_SYM:@.+]] @SomeModule
// CHECK-LABEL: firrtl.module @ClockGatesGroup
// CHECK: firrtl.instance gate sym [[CKG0_SYM:@.+]] @EICG_wrapper
// CHECK: firrtl.instance gate sym [[CKG1_SYM:@.+]] @EICG_wrapper
// CHECK-LABEL: firrtl.module private @DUTModule
firrtl.module private @DUTModule(in %clock: !firrtl.clock, in %foo_en: !firrtl.uint<1>, in %bar_en: !firrtl.uint<1>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
// CHECK-NOT: firrtl.instance gate @EICG_wrapper
// CHECK-NOT: firrtl.instance gate @EICG_wrapper
%inst0_clock, %inst0_en = firrtl.instance inst0 @SomeModule(in clock: !firrtl.clock, in en: !firrtl.uint<1>)
%inst1_clock, %inst1_en = firrtl.instance inst1 @SomeModule(in clock: !firrtl.clock, in en: !firrtl.uint<1>)
// CHECK: firrtl.instance ClockGatesGroup sym [[CLKGRP_SYM:@.+]] @ClockGatesGroup
// CHECK: firrtl.instance InjectedSubmodule sym [[INJMOD_SYM:@.+]] @InjectedSubmodule
firrtl.connect %inst0_clock, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %inst1_clock, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %inst0_en, %foo_en : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.connect %inst1_en, %bar_en : !firrtl.uint<1>, !firrtl.uint<1>
}
// CHECK-LABEL: firrtl.module @ExtractClockGatesMultigrouping
firrtl.module @ExtractClockGatesMultigrouping(in %clock: !firrtl.clock, in %foo_en: !firrtl.uint<1>, in %bar_en: !firrtl.uint<1>) {
%dut_clock, %dut_foo_en, %dut_bar_en = firrtl.instance dut @DUTModule(in clock: !firrtl.clock, in foo_en: !firrtl.uint<1>, in bar_en: !firrtl.uint<1>)
firrtl.connect %dut_clock, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %dut_bar_en, %bar_en : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.connect %dut_foo_en, %foo_en : !firrtl.uint<1>, !firrtl.uint<1>
}
// CHECK: sv.verbatim "
// CHECK-SAME{LITERAL}: clock_gate_1 -> {{0}}.{{1}}.{{2}}.{{3}}\0A
// CHECK-SAME{LITERAL}: clock_gate_0 -> {{0}}.{{1}}.{{4}}.{{5}}\0A
// CHECK-SAME: output_file = #hw.output_file<"ClockGates.txt", excludeFromFileList>
// CHECK-SAME: symbols = [
// CHECK-SAME: @DUTModule
// CHECK-SAME: #hw.innerNameRef<@DUTModule::[[INJMOD_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@InjectedSubmodule::[[INST0_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@ClockGatesGroup::[[CKG0_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@InjectedSubmodule::[[INST1_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@ClockGatesGroup::[[CKG1_SYM]]>
// CHECK-SAME: ]
}

View File

@ -0,0 +1,362 @@
// RUN: circt-opt --firrtl-extract-instances --verify-diagnostics %s | FileCheck %s
// Tests extracted from:
// - test/scala/firrtl/ExtractBlackBoxes.scala
// - test/scala/firrtl/ExtractClockGates.scala
//===----------------------------------------------------------------------===//
// ExtractBlackBoxes Simple
//===----------------------------------------------------------------------===//
// CHECK: firrtl.circuit "ExtractBlackBoxesSimple"
firrtl.circuit "ExtractBlackBoxesSimple" attributes {annotations = [{class = "firrtl.transforms.BlackBoxTargetDirAnno", targetDir = "BlackBoxes"}]} {
// 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 = "bb"}], defname = "MyBlackBox"}
// CHECK-LABEL: firrtl.module private @BBWrapper
// CHECK-SAME: out %bb_0_in: !firrtl.uint<8>
// CHECK-SAME: in %bb_0_out: !firrtl.uint<8>
firrtl.module private @BBWrapper(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
// CHECK-NOT: firrtl.instance bb @MyBlackBox
%bb_in, %bb_out = firrtl.instance bb @MyBlackBox(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
%invalid_ui8 = firrtl.invalidvalue : !firrtl.uint<8>
firrtl.strictconnect %bb_in, %invalid_ui8 : !firrtl.uint<8>
// CHECK: firrtl.connect %out, %bb_0_out
// CHECK: firrtl.connect %bb_0_in, %in
firrtl.connect %out, %bb_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %bb_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-LABEL: firrtl.module private @DUTModule
// CHECK-SAME: out %bb_0_in: !firrtl.uint<8>
// CHECK-SAME: in %bb_0_out: !firrtl.uint<8>
firrtl.module private @DUTModule(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
// CHECK-NOT: firrtl.instance bb @MyBlackBox
// CHECK: %mod_in, %mod_out, %mod_bb_0_in, %mod_bb_0_out = firrtl.instance mod sym [[WRAPPER_SYM:@.+]] @BBWrapper
// CHECK-NEXT: firrtl.strictconnect %bb_0_in, %mod_bb_0_in
// CHECK-NEXT: firrtl.strictconnect %mod_bb_0_out, %bb_0_out
%mod_in, %mod_out = firrtl.instance mod @BBWrapper(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %mod_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %mod_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-LABEL: firrtl.module @ExtractBlackBoxesSimple
firrtl.module @ExtractBlackBoxesSimple(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
// CHECK: %dut_in, %dut_out, %dut_bb_0_in, %dut_bb_0_out = firrtl.instance dut sym {{@.+}} @DUTModule
// CHECK-NEXT: %bb_in, %bb_out = firrtl.instance bb sym [[BB_SYM:@.+]] @MyBlackBox
// CHECK-NEXT: firrtl.strictconnect %bb_in, %dut_bb_0_in
// CHECK-NEXT: firrtl.strictconnect %dut_bb_0_out, %bb_out
%dut_in, %dut_out = firrtl.instance dut @DUTModule(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %dut_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %dut_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK: sv.verbatim "
// CHECK-SAME{LITERAL}: bb_0 -> {{0}}.{{1}}.{{2}}\0A
// CHECK-SAME: output_file = #hw.output_file<"BlackBoxes.txt", excludeFromFileList>
// CHECK-SAME: symbols = [
// CHECK-SAME: @DUTModule
// CHECK-SAME: #hw.innerNameRef<@DUTModule::[[WRAPPER_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@ExtractBlackBoxesSimple::[[BB_SYM]]>
// CHECK-SAME: ]
}
//===----------------------------------------------------------------------===//
// ExtractBlackBoxes Simple (modified)
// ExtractBlackBoxes RenameTargets (modified)
//===----------------------------------------------------------------------===//
// CHECK: firrtl.circuit "ExtractBlackBoxesSimple2"
firrtl.circuit "ExtractBlackBoxesSimple2" attributes {annotations = [{class = "firrtl.transforms.BlackBoxTargetDirAnno", targetDir = "BlackBoxes"}]} {
// CHECK: firrtl.nla @nla_1 [#hw.innerNameRef<@ExtractBlackBoxesSimple2::@bb>, @MyBlackBox]
// CHECK-NOT: firrtl.nla @nla_2
// CHECK-NOT: firrtl.nla @nla_3
firrtl.nla @nla_1 [
#hw.innerNameRef<@BBWrapper::@bb>,
@MyBlackBox
]
firrtl.nla @nla_2 [
#hw.innerNameRef<@DUTModule::@mod>,
#hw.innerNameRef<@BBWrapper::@bb>
]
firrtl.nla @nla_3 [
#hw.innerNameRef<@ExtractBlackBoxesSimple2::@dut>,
#hw.innerNameRef<@DUTModule::@mod>,
#hw.innerNameRef<@BBWrapper::@bb>
]
// 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"}
], defname = "MyBlackBox"}
// Annotation will be on the instance
// CHECK-LABEL: firrtl.extmodule private @MyBlackBox2
firrtl.extmodule private @MyBlackBox2(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>) attributes {defname = "MyBlackBox"}
// CHECK-LABEL: firrtl.module private @BBWrapper
// CHECK-SAME: out %prefix_0_in: !firrtl.uint<8>
// CHECK-SAME: in %prefix_0_out: !firrtl.uint<8>
// CHECK-SAME: out %prefix_1_in: !firrtl.uint<8>
// CHECK-SAME: in %prefix_1_out: !firrtl.uint<8>
firrtl.module private @BBWrapper(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
// 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_1, class = "circt.nonlocal"},
{circt.nonlocal = @nla_2, class = "DummyB"},
{circt.nonlocal = @nla_3, class = "DummyC"}
]} @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
// CHECK: firrtl.connect %prefix_0_in, %prefix_1_out
// CHECK: firrtl.connect %prefix_1_in, %in
firrtl.connect %out, %bb2_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %bb2_in, %bb_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %bb_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-LABEL: firrtl.module private @DUTModule
// CHECK-SAME: out %prefix_0_in: !firrtl.uint<8>
// CHECK-SAME: in %prefix_0_out: !firrtl.uint<8>
// CHECK-SAME: out %prefix_1_in: !firrtl.uint<8>
// CHECK-SAME: in %prefix_1_out: !firrtl.uint<8>
firrtl.module private @DUTModule(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
// CHECK-NOT: firrtl.instance bb @MyBlackBox
// CHECK-NOT: firrtl.instance bb2 @MyBlackBox2
// CHECK: %mod_in, %mod_out, %mod_prefix_0_in, %mod_prefix_0_out, %mod_prefix_1_in, %mod_prefix_1_out = firrtl.instance mod
// CHECK-NOT: annotations =
// CHECK-SAME: sym [[WRAPPER_SYM:@.+]] @BBWrapper
// CHECK-NEXT: firrtl.strictconnect %prefix_1_in, %mod_prefix_1_in
// CHECK-NEXT: firrtl.strictconnect %mod_prefix_1_out, %prefix_1_out
// CHECK-NEXT: firrtl.strictconnect %prefix_0_in, %mod_prefix_0_in
// CHECK-NEXT: firrtl.strictconnect %mod_prefix_0_out, %prefix_0_out
%mod_in, %mod_out = firrtl.instance mod sym @mod {annotations = [
{circt.nonlocal = @nla_2, class = "circt.nonlocal"},
{circt.nonlocal = @nla_3, class = "circt.nonlocal"}
]} @BBWrapper(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %mod_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %mod_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-LABEL: firrtl.module @ExtractBlackBoxesSimple2
firrtl.module @ExtractBlackBoxesSimple2(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
// 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"}, {circt.nonlocal = @nla_1, class = "circt.nonlocal"}]} @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
// CHECK-NEXT: firrtl.strictconnect %bb2_in, %dut_prefix_0_in
// CHECK-NEXT: firrtl.strictconnect %dut_prefix_0_out, %bb2_out
%dut_in, %dut_out = firrtl.instance dut sym @dut {annotations = [
{circt.nonlocal = @nla_3, class = "circt.nonlocal"}
]} @DUTModule(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %dut_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %dut_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK: sv.verbatim "
// CHECK-SAME{LITERAL}: prefix_0 -> {{0}}.{{1}}.{{2}}\0A
// CHECK-SAME{LITERAL}: prefix_1 -> {{0}}.{{1}}.{{3}}\0A
// CHECK-SAME: output_file = #hw.output_file<"BlackBoxes.txt", excludeFromFileList>
// CHECK-SAME: symbols = [
// CHECK-SAME: @DUTModule
// CHECK-SAME: #hw.innerNameRef<@DUTModule::[[WRAPPER_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@ExtractBlackBoxesSimple2::[[BB2_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@ExtractBlackBoxesSimple2::[[BB_SYM]]>
// CHECK-SAME: ]
}
//===----------------------------------------------------------------------===//
// ExtractBlackBoxes IntoDUTSubmodule
//===----------------------------------------------------------------------===//
// CHECK: firrtl.circuit "ExtractBlackBoxesIntoDUTSubmodule"
firrtl.circuit "ExtractBlackBoxesIntoDUTSubmodule" {
// CHECK-LABEL: firrtl.nla @nla_1 [
// CHECK-SAME: #hw.innerNameRef<@ExtractBlackBoxesIntoDUTSubmodule::@tb>
// CHECK-SAME: #hw.innerNameRef<@TestHarness::@dut>
// CHECK-SAME: #hw.innerNameRef<@DUTModule::@BlackBoxes>
// CHECK-SAME: #hw.innerNameRef<@BlackBoxes::@bb1>
// CHECK-SAME: ]
firrtl.nla @nla_1 [
#hw.innerNameRef<@ExtractBlackBoxesIntoDUTSubmodule::@tb>,
#hw.innerNameRef<@TestHarness::@dut>,
#hw.innerNameRef<@DUTModule::@mod>,
#hw.innerNameRef<@BBWrapper::@bb1>
]
// CHECK-LABEL: firrtl.nla @nla_2 [
// CHECK-SAME: #hw.innerNameRef<@ExtractBlackBoxesIntoDUTSubmodule::@tb>
// CHECK-SAME: #hw.innerNameRef<@TestHarness::@dut>
// CHECK-SAME: #hw.innerNameRef<@DUTModule::@BlackBoxes>
// CHECK-SAME: #hw.innerNameRef<@BlackBoxes::@bb2>
// CHECK-SAME: ]
firrtl.nla @nla_2 [
#hw.innerNameRef<@ExtractBlackBoxesIntoDUTSubmodule::@tb>,
#hw.innerNameRef<@TestHarness::@dut>,
#hw.innerNameRef<@DUTModule::@mod>,
#hw.innerNameRef<@BBWrapper::@bb2>
]
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>)
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>
}
// CHECK-LABEL: firrtl.module @BlackBoxes(
// CHECK-SAME: in %bb_0_in: !firrtl.uint<8>
// CHECK-SAME: out %bb_0_out: !firrtl.uint<8>
// 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: 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: firrtl.strictconnect %bb1_in, %bb_1_in : !firrtl.uint<8>
// CHECK-NEXT: firrtl.strictconnect %bb_1_out, %bb1_out : !firrtl.uint<8>
// CHECK-NEXT: }
// CHECK-LABEL: firrtl.module private @DUTModule
firrtl.module private @DUTModule(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
// CHECK: %BlackBoxes_bb_0_in, %BlackBoxes_bb_0_out, %BlackBoxes_bb_1_in, %BlackBoxes_bb_1_out = firrtl.instance BlackBoxes sym @BlackBoxes
// CHECK-SAME: {circt.nonlocal = @nla_2, class = "circt.nonlocal"}
// CHECK-SAME: {circt.nonlocal = @nla_1, class = "circt.nonlocal"}
// CHECK-SAME: @BlackBoxes
// CHECK-NEXT: %mod_in, %mod_out, %mod_bb_0_in, %mod_bb_0_out, %mod_bb_1_in, %mod_bb_1_out = firrtl.instance mod
// CHECK-NOT: annotations =
// CHECK-SAME: sym [[WRAPPER_SYM:@.+]] @BBWrapper
// CHECK-NEXT: firrtl.strictconnect %BlackBoxes_bb_1_in, %mod_bb_1_in
// CHECK-NEXT: firrtl.strictconnect %mod_bb_1_out, %BlackBoxes_bb_1_out
// CHECK-NEXT: firrtl.strictconnect %BlackBoxes_bb_0_in, %mod_bb_0_in
// CHECK-NEXT: firrtl.strictconnect %mod_bb_0_out, %BlackBoxes_bb_0_out
%mod_in, %mod_out = firrtl.instance mod sym @mod {annotations = [{circt.nonlocal = @nla_1, class = "circt.nonlocal"}, {circt.nonlocal = @nla_2, class = "circt.nonlocal"}]} @BBWrapper(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %mod_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %mod_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
firrtl.module @TestHarness(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
%dut_in, %dut_out = firrtl.instance dut sym @dut {annotations = [{circt.nonlocal = @nla_1, class = "circt.nonlocal"}, {circt.nonlocal = @nla_2, class = "circt.nonlocal"}]} @DUTModule(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %dut_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %dut_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
firrtl.module @ExtractBlackBoxesIntoDUTSubmodule(in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>) {
%tb_in, %tb_out = firrtl.instance tb sym @tb {annotations = [{circt.nonlocal = @nla_1, class = "circt.nonlocal"}, {circt.nonlocal = @nla_2, class = "circt.nonlocal"}]} @TestHarness(in in: !firrtl.uint<8>, out out: !firrtl.uint<8>)
firrtl.connect %out, %tb_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %tb_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK: sv.verbatim "
// CHECK-SAME{LITERAL}: bb_0 -> {{0}}.{{1}}.{{2}}\0A
// CHECK-SAME{LITERAL}: bb_1 -> {{0}}.{{1}}.{{3}}\0A
// CHECK-SAME: output_file = #hw.output_file<"BlackBoxes.txt", excludeFromFileList>
// CHECK-SAME: symbols = [
// CHECK-SAME: @DUTModule
// CHECK-SAME: #hw.innerNameRef<@DUTModule::[[WRAPPER_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@BlackBoxes::[[BB2_SYM]]>
// CHECK-SAME: #hw.innerNameRef<@BlackBoxes::[[BB1_SYM]]>
// CHECK-SAME: ]
}
//===----------------------------------------------------------------------===//
// ExtractClockGates Simple
//===----------------------------------------------------------------------===//
// CHECK: firrtl.circuit "ExtractClockGatesSimple"
firrtl.circuit "ExtractClockGatesSimple" attributes {annotations = [{class = "sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation", filename = "ClockGates.txt"}]} {
firrtl.extmodule private @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper"}
firrtl.module private @DUTModule(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
%gate_in, %gate_en, %gate_out = firrtl.instance gate @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock)
firrtl.connect %gate_in, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %gate_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
}
// CHECK-LABEL: firrtl.module @ExtractClockGatesSimple
firrtl.module @ExtractClockGatesSimple(in %clock: !firrtl.clock, in %en: !firrtl.uint<1>) {
// CHECK: firrtl.instance gate sym [[CKG_SYM:@.+]] @EICG_wrapper
%dut_clock, %dut_en = firrtl.instance dut @DUTModule(in clock: !firrtl.clock, in en: !firrtl.uint<1>)
firrtl.connect %dut_clock, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %dut_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
}
// CHECK: sv.verbatim "
// CHECK-SAME{LITERAL}: clock_gate_0 -> {{0}}.{{1}}\0A
// CHECK-SAME: output_file = #hw.output_file<"ClockGates.txt", excludeFromFileList>
// CHECK-SAME: symbols = [
// CHECK-SAME: @DUTModule
// CHECK-SAME: #hw.innerNameRef<@ExtractClockGatesSimple::[[CKG_SYM]]>
// CHECK-SAME: ]
}
//===----------------------------------------------------------------------===//
// ExtractClockGates TestHarnessOnly
//===----------------------------------------------------------------------===//
// CHECK: firrtl.circuit "ExtractClockGatesTestHarnessOnly"
firrtl.circuit "ExtractClockGatesTestHarnessOnly" attributes {annotations = [{class = "sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation", filename = "ClockGates.txt"}]} {
firrtl.extmodule private @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper"}
firrtl.module private @DUTModule(in %clock: !firrtl.clock, in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>, in %en: !firrtl.uint<1>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
%0 = firrtl.add %in, %en : (!firrtl.uint<8>, !firrtl.uint<1>) -> !firrtl.uint<9>
%_io_out_T = firrtl.node %0 : !firrtl.uint<9>
%1 = firrtl.tail %_io_out_T, 1 : (!firrtl.uint<9>) -> !firrtl.uint<8>
%_io_out_T_1 = firrtl.node %1 : !firrtl.uint<8>
firrtl.connect %out, %_io_out_T_1 : !firrtl.uint<8>, !firrtl.uint<8>
}
firrtl.module @ExtractClockGatesTestHarnessOnly(in %clock: !firrtl.clock, in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>, in %en: !firrtl.uint<1>) {
%gate_in, %gate_en, %gate_out = firrtl.instance gate @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock)
firrtl.connect %gate_in, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %gate_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
%dut_clock, %dut_in, %dut_out, %dut_en = firrtl.instance dut @DUTModule(in clock: !firrtl.clock, in in: !firrtl.uint<8>, out out: !firrtl.uint<8>, in en: !firrtl.uint<1>)
firrtl.connect %dut_clock, %gate_out : !firrtl.clock, !firrtl.clock
firrtl.connect %dut_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.connect %out, %dut_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %dut_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-NOT: sv.verbatim "clock_gate
}
//===----------------------------------------------------------------------===//
// ExtractClockGates Mixed
//===----------------------------------------------------------------------===//
// Mixed ClockGate extraction should only extract clock gates in the DUT
// CHECK: firrtl.circuit "ExtractClockGatesMixed"
firrtl.circuit "ExtractClockGatesMixed" attributes {annotations = [{class = "sifive.enterprise.firrtl.ExtractClockGatesFileAnnotation", filename = "ClockGates.txt"}]} {
firrtl.extmodule private @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock) attributes {defname = "EICG_wrapper"}
// CHECK-LABEL: firrtl.module private @Child
firrtl.module private @Child(in %clock: !firrtl.clock, in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>, in %en: !firrtl.uint<1>) {
// CHECK-NOT: firrtl.instance gate sym @ckg1 @EICG_wrapper
%gate_in, %gate_en, %gate_out = firrtl.instance gate sym @ckg1 @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock)
firrtl.connect %gate_in, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %gate_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.connect %out, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-LABEL: firrtl.module private @DUTModule
firrtl.module private @DUTModule(in %clock: !firrtl.clock, in %in: !firrtl.uint<8>, out %out: !firrtl.uint<8>, in %en: !firrtl.uint<1>) attributes {annotations = [{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {
%inst_clock, %inst_in, %inst_out, %inst_en = firrtl.instance inst sym @inst @Child(in clock: !firrtl.clock, in in: !firrtl.uint<8>, out out: !firrtl.uint<8>, in en: !firrtl.uint<1>)
firrtl.connect %inst_clock, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %inst_in, %in : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %inst_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
// CHECK-NOT: firrtl.instance gate sym @ckg2 @EICG_wrapper
%gate_in, %gate_en, %gate_out = firrtl.instance gate sym @ckg2 @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock)
firrtl.connect %gate_in, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %gate_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.connect %out, %in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK-LABEL: firrtl.module @ExtractClockGatesMixed
firrtl.module @ExtractClockGatesMixed(in %clock: !firrtl.clock, in %intf_in: !firrtl.uint<8>, out %intf_out: !firrtl.uint<8>, in %intf_en: !firrtl.uint<1>, in %en: !firrtl.uint<1>) {
// CHECK: firrtl.instance gate sym @ckg3 @EICG_wrapper
%gate_in, %gate_en, %gate_out = firrtl.instance gate sym @ckg3 @EICG_wrapper(in in: !firrtl.clock, in en: !firrtl.uint<1>, out out: !firrtl.clock)
firrtl.connect %gate_in, %clock : !firrtl.clock, !firrtl.clock
firrtl.connect %gate_en, %en : !firrtl.uint<1>, !firrtl.uint<1>
%dut_clock, %dut_in, %dut_out, %dut_en = firrtl.instance dut sym @dut @DUTModule(in clock: !firrtl.clock, in in: !firrtl.uint<8>, out out: !firrtl.uint<8>, in en: !firrtl.uint<1>)
// CHECK: firrtl.instance gate sym @ckg2 @EICG_wrapper
// CHECK: firrtl.instance gate sym @ckg1 @EICG_wrapper
firrtl.connect %dut_clock, %gate_out : !firrtl.clock, !firrtl.clock
firrtl.connect %dut_en, %intf_en : !firrtl.uint<1>, !firrtl.uint<1>
firrtl.connect %intf_out, %dut_out : !firrtl.uint<8>, !firrtl.uint<8>
firrtl.connect %dut_in, %intf_in : !firrtl.uint<8>, !firrtl.uint<8>
}
// CHECK: sv.verbatim "
// CHECK-SAME{LITERAL}: clock_gate_0 -> {{0}}.{{1}}.{{2}}\0A
// CHECK-SAME{LITERAL}: clock_gate_1 -> {{0}}.{{3}}\0A
// CHECK-SAME: output_file = #hw.output_file<"ClockGates.txt", excludeFromFileList>
// CHECK-SAME: symbols = [
// CHECK-SAME: @DUTModule
// CHECK-SAME: #hw.innerNameRef<@DUTModule::@inst>
// CHECK-SAME: #hw.innerNameRef<@ExtractClockGatesMixed::@ckg1>
// CHECK-SAME: #hw.innerNameRef<@ExtractClockGatesMixed::@ckg2>
// CHECK-SAME: ]
}

View File

@ -204,6 +204,11 @@ static cl::opt<bool>
cl::desc("add a level of hierarchy to the DUT"),
cl::init(true), cl::cat(mainCategory));
static cl::opt<bool>
extractInstances("extract-instances",
cl::desc("extract black boxes, seq mems, and clock gates"),
cl::init(true), cl::cat(mainCategory));
static cl::opt<bool>
prefixModules("prefix-modules",
cl::desc("prefix modules with NestedPrefixAnnotation"),
@ -550,6 +555,9 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
firrtl::createRemoveUnusedPortsPass());
}
if (extractInstances)
pm.addNestedPass<firrtl::CircuitOp>(firrtl::createExtractInstancesPass());
if (emitMetadata)
pm.nest<firrtl::CircuitOp>().addPass(firrtl::createCreateSiFiveMetadataPass(
replSeqMem, replSeqMemCircuit, replSeqMemFile));