Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIRRTL] Support Prefixing of Grand Central Interfaces #2020

Merged
merged 6 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/FIRRTLAnnotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,10 @@ If `inclusive` is false, it will only rename modules instantiated underneath
the target module. If a module is required to have two different prefixes, it
will be cloned.

This annotation is also applied to any interfaces or modules generated by the
Grand Central Views/Interfaces pass. This annotation is applied _before_
`PrefixInterfacesAnnotation`.

Example:
```json
{
Expand Down Expand Up @@ -945,6 +949,29 @@ Example:
}
```

#### PrefixInterfacesAnnotation

| Property | Type | Description |
|----------|--------|-----------------------------------------------------------|
| class | string | sifive.enterprise.grandcentral.PrefixInterfacesAnnotation |
| prefix | string | A prefix to apply to all interface names |

This annotation can be used to set a global prefix for all interfaces generated
by Grand Central, including nested interfaces. The prefix will be applied
_after_ any prefixes set by `NestedPrefixModulesAnnotation`.

This annotation may only exist zero or one times. This differs from the SFC
implementation which will choose the first instance of this annotation.

Example:

``` json
{
"class": "sifive.enterprise.grandcentral.PrefixInterfacesAnnotation",
"prefix": "PREFIX_"
}
```

### Data Taps

Grand Central Taps are a tool for representing cross module references. They
Expand Down
7 changes: 6 additions & 1 deletion include/circt/Dialect/FIRRTL/FIRRTLTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -261,14 +261,19 @@ class AugmentedType<string name> : AttrDef<FIRRTLDialect, name> {
ArrayAttr getElements() { return getUnderlying().getAs<ArrayAttr>("elements"); }
}];

code hasPrefix = [{
StringAttr getPrefix() { return getUnderlying().getAs<StringAttr>("prefix"); }
}];

}

def AugmentedBundleType : AugmentedType<"AugmentedBundleType"> {
let extraClassDeclaration =
defaultClassDeclaration #
hasID #
hasElements #
hasDefName # [{
hasDefName #
hasPrefix # [{
bool isRoot() { return getID() != nullptr; }
}];
}
Expand Down
68 changes: 56 additions & 12 deletions lib/Dialect/FIRRTL/Transforms/GrandCentral.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,20 +121,38 @@ struct GrandCentralPass : public GrandCentralBase<GrandCentralPass> {
/// Mapping of ID to companion module.
DenseMap<Attribute, CompanionInfo> companionIDMap;

/// An optional prefix applied to all interfaces in the design. This is set
/// based on a PrefixInterfacesAnnotation.
StringRef interfacePrefix;

/// Return a string containing the name of an interface. Apply correct
/// prefixing from the interfacePrefix and module-level prefix parameter.
std::string getInterfaceName(StringAttr prefix,
AugmentedBundleTypeAttr bundleType) {

if (prefix)
return (prefix.getValue() + interfacePrefix +
bundleType.getDefName().getValue())
.str();
return (interfacePrefix + bundleType.getDefName().getValue()).str();
}

/// Recursively examine an AugmentedType to populate the "mappings" file
/// (generate XMRs) for this interface. This does not build new interfaces.
bool traverseField(Attribute field, IntegerAttr id, Twine path);

/// Recursively examine an AugmentedType to both build new interfaces and
/// populate a "mappings" file (generate XMRs) using `traverseField`. Return
/// the type of the field exmained.
Optional<TypeSum> computeField(Attribute field, IntegerAttr id, Twine path);
Optional<TypeSum> computeField(Attribute field, IntegerAttr id,
StringAttr prefix, Twine path);

/// Recursively examine an AugmentedBundleType to both build new interfaces
/// and populate a "mappings" file (generate XMRs). Return none if the
/// interface is invalid.
Optional<sv::InterfaceOp> traverseBundle(AugmentedBundleTypeAttr bundle,
IntegerAttr id, Twine path);
IntegerAttr id, StringAttr prefix,
Twine path);

/// Return the module associated with this value.
FModuleLike getEnclosingModule(Value value);
Expand Down Expand Up @@ -350,7 +368,9 @@ bool GrandCentralPass::traverseField(Attribute field, IntegerAttr id,
}

Optional<TypeSum> GrandCentralPass::computeField(Attribute field,
IntegerAttr id, Twine path) {
IntegerAttr id,
StringAttr prefix,
Twine path) {

auto unsupported = [&](StringRef name, StringRef kind) {
return VerbatimType({("// <unsupported " + kind + " type>").str(), false});
Expand Down Expand Up @@ -378,7 +398,7 @@ Optional<TypeSum> GrandCentralPass::computeField(Attribute field,
bool notFailed = true;
auto elements = vector.getElements();
auto firstElement = fromAttr(elements[0]);
auto elementType = computeField(firstElement.getValue(), id,
auto elementType = computeField(firstElement.getValue(), id, prefix,
path + "[" + Twine(0) + "]");
if (!elementType)
return None;
Expand All @@ -400,9 +420,9 @@ Optional<TypeSum> GrandCentralPass::computeField(Attribute field,
})
.Case<AugmentedBundleTypeAttr>(
[&](AugmentedBundleTypeAttr bundle) -> TypeSum {
auto iface = traverseBundle(bundle, id, path);
auto iface = traverseBundle(bundle, id, prefix, path);
assert(iface && iface.getValue());
return VerbatimType({(bundle.getDefName().getValue()).str(), true});
return VerbatimType({getInterfaceName(prefix, bundle), true});
})
.Case<AugmentedStringTypeAttr>([&](auto field) -> TypeSum {
return unsupported(field.getName().getValue(), "string");
Expand Down Expand Up @@ -432,12 +452,12 @@ Optional<TypeSum> GrandCentralPass::computeField(Attribute field,
/// drive the interface. Returns false on any failure and true on success.
Optional<sv::InterfaceOp>
GrandCentralPass::traverseBundle(AugmentedBundleTypeAttr bundle, IntegerAttr id,
Twine path) {
StringAttr prefix, Twine path) {
auto builder = OpBuilder::atBlockEnd(getOperation().getBody());
sv::InterfaceOp iface;
builder.setInsertionPointToEnd(getOperation().getBody());
auto loc = getOperation().getLoc();
auto iFaceName = getNamespace().newName(bundle.getDefName().getValue());
auto iFaceName = getNamespace().newName(getInterfaceName(prefix, bundle));
iface = builder.create<sv::InterfaceOp>(loc, iFaceName);
if (maybeExtractInfo)
iface->setAttr("output_file",
Expand All @@ -454,8 +474,8 @@ GrandCentralPass::traverseBundle(AugmentedBundleTypeAttr bundle, IntegerAttr id,
return None;

auto name = element.cast<DictionaryAttr>().getAs<StringAttr>("name");
auto elementType =
computeField(field.getValue(), id, path + "." + name.getValue());
auto elementType = computeField(field.getValue(), id, prefix,
path + "." + name.getValue());
if (!elementType)
return None;

Expand Down Expand Up @@ -535,6 +555,28 @@ void GrandCentralPass::runOnOperation() {
maybeExtractInfo = {directory, filename};
// Intentional fallthrough. Extraction info may be needed later.
}
if (anno.isClass(
"sifive.enterprise.grandcentral.PrefixInterfacesAnnotation")) {
if (!interfacePrefix.empty()) {
emitCircuitError("more than one 'PrefixInterfacesAnnotation' was "
"found, but zero or one may be provided");
removalError = true;
return false;
}

auto prefix = anno.getMember<StringAttr>("prefix");
if (!prefix) {
emitCircuitError()
<< "contained an invalid 'PrefixInterfacesAnnotation' that does "
"not contain a 'prefix' field: "
<< anno.getDict();
removalError = true;
return false;
}

interfacePrefix = prefix.getValue();
return true;
}
return false;
});

Expand All @@ -555,6 +597,8 @@ void GrandCentralPass::runOnOperation() {
<< "\n";
else
llvm::dbgs() << " <none>\n";
llvm::dbgs() << "Prefix Info (from PrefixInterfacesAnnotation):\n"
<< " prefix: " << interfacePrefix << "\n";
});

// Setup the builder to create ops _inside the FIRRTL circuit_. This is
Expand Down Expand Up @@ -901,7 +945,7 @@ void GrandCentralPass::runOnOperation() {
// Error out if this returns None (indicating that the annotation annotation
// is malformed in some way). A good error message is generated inside
// `traverseBundle` or the functions it calls.
auto iface = traverseBundle(bundle, bundle.getID(),
auto iface = traverseBundle(bundle, bundle.getID(), bundle.getPrefix(),
companionIDMap.lookup(bundle.getID()).name);
if (!iface) {
removalError = true;
Expand All @@ -913,7 +957,7 @@ void GrandCentralPass::runOnOperation() {
parentIDMap.lookup(bundle.getID()).second.getBody());
auto symbolName = getNamespace().newName(
"__" + companionIDMap.lookup(bundle.getID()).name + "_" +
bundle.getDefName().getValue() + "__");
getInterfaceName(bundle.getPrefix(), bundle) + "__");
auto instance = builder.create<sv::InterfaceInstanceOp>(
getOperation().getLoc(), iface.getValue().getInterfaceType(),
companionIDMap.lookup(bundle.getID()).name,
Expand Down
92 changes: 92 additions & 0 deletions lib/Dialect/FIRRTL/Transforms/PrefixModules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,18 @@ class PrefixModulesPass : public PrefixModulesBase<PrefixModulesPass> {
void renameModule(FModuleOp module);
void runOnOperation() override;

/// Mutate Grand Central Interface definitions (an Annotation on the circuit)
/// with a field "prefix" containing the prefix for that annotation. This
/// relies on information built up during renameModule and stored in
/// interfacePrefixMap.
void prefixGrandCentralInterfaces();

/// This is a map from a module name to new prefixes to be applied.
PrefixMap prefixMap;

/// A map of Grand Central interface ID to prefix.
DenseMap<Attribute, std::string> interfacePrefixMap;

/// Cached instance graph analysis.
InstanceGraph *instanceGraph = nullptr;

Expand Down Expand Up @@ -179,6 +188,42 @@ void PrefixModulesPass::renameModule(FModuleOp module) {
auto &outerPrefix = prefixes.front();
module.setName(outerPrefix + moduleName);
renameModuleBody((outerPrefix + innerPrefix).str(), module);

// If this module contains a Grand Central interface, then also apply renames
// to that, but only if there are prefixes to apply.
if (prefixes.empty())
return;
AnnotationSet annotations(module);
if (!annotations.hasAnnotation(
"sifive.enterprise.grandcentral.ViewAnnotation"))
return;
auto prefixFull = (outerPrefix + innerPrefix).str();
SmallVector<Attribute> newAnnotations;
for (auto anno : annotations) {
if (!anno.isClass("sifive.enterprise.grandcentral.ViewAnnotation")) {
newAnnotations.push_back(anno.getDict());
continue;
}

NamedAttrList newAnno;
for (auto pair : anno.getDict()) {
if (pair.first == "name") {
newAnno.append(
pair.first,
builder.getStringAttr(Twine(prefixFull) +
pair.second.cast<StringAttr>().getValue()));
continue;
}
newAnno.append(pair.first, pair.second);
}
newAnnotations.push_back(
DictionaryAttr::getWithSorted(builder.getContext(), newAnno));

// Record that we need to apply this prefix to the interface definition.
if (anno.getMember<StringAttr>("type").getValue() == "parent")
interfacePrefixMap[anno.getMember<IntegerAttr>("id")] = prefixFull;
}
AnnotationSet(newAnnotations, builder.getContext()).applyToOperation(module);
Comment on lines +196 to +226
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's sad that there is no better way to do dictionary attr mutation in MLIR yet. I totally see why you'd want something like AnnotationSet::map or filter_map here. Maybe we should start to curate a list of "nice-to-have refactorings" that people can pick up if they're bored or want to jump in. Maybe as some "help wanted" issue?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this rewrite just to mutate one value feels bad. I've seen this come up twice so far: (1) annotation scattering does this type of transformation and (2) this pass. I can open up some help wanted issues.

}

void PrefixModulesPass::runOnOperation() {
Expand All @@ -205,11 +250,58 @@ void PrefixModulesPass::runOnOperation() {
}
}

// Update any interface definitions if needed.
prefixGrandCentralInterfaces();

prefixMap.clear();
interfacePrefixMap.clear();
if (!anythingChanged)
markAllAnalysesPreserved();
}

/// Mutate circuit-level annotations to add prefix information to Grand Central
/// (SystemVerilog) interfaces. Add a "prefix" field to each interface
/// definition (an annotation with class "AugmentedBundleType") that holds the
/// prefix that was determined during runOnModule. It is assumed that this
/// field did not exist before.
void PrefixModulesPass::prefixGrandCentralInterfaces() {
// Early exit if no interfaces need prefixes.
if (interfacePrefixMap.empty())
return;

auto circuit = getOperation();
OpBuilder builder(circuit);

SmallVector<Attribute> newCircuitAnnotations;
for (auto anno : AnnotationSet(circuit)) {
// Only mutate this annotation if it is an AugmentedBundleType and
// interfacePrefixMap has prefix information for it.
StringRef prefix;
if (anno.isClass("sifive.enterprise.grandcentral.AugmentedBundleType")) {
if (auto id = anno.getMember<IntegerAttr>("id"))
prefix = interfacePrefixMap[id];
}

// Nothing to do. Copy the annotation.
if (prefix.empty()) {
newCircuitAnnotations.push_back(anno.getDict());
continue;
}

// Add a "prefix" field with the prefix for this interface. This is safe to
// put at the back and do a `getWithSorted` because the last field is
// conveniently called "name".
NamedAttrList newAnno(anno.getDict().getValue());
newAnno.append("prefix", builder.getStringAttr(prefix));
newCircuitAnnotations.push_back(
DictionaryAttr::getWithSorted(builder.getContext(), newAnno));
}

// Overwrite the old circuit annotation with the new one created here.
AnnotationSet(newCircuitAnnotations, builder.getContext())
.applyToOperation(circuit);
}

std::unique_ptr<mlir::Pass> circt::firrtl::createPrefixModulesPass() {
return std::make_unique<PrefixModulesPass>();
}
46 changes: 46 additions & 0 deletions test/Dialect/FIRRTL/grand-central.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -601,3 +601,49 @@ firrtl.circuit "MultipleGroundTypeInterfaces" attributes {
// CHECK: sv.interface {
// CHECK-SAME: output_file = #hw.output_file<"gct-dir/Bar.sv"
// CHECK-SAME: @Bar

// -----

firrtl.circuit "PrefixInterfacesAnnotation"
attributes {annotations = [
{class = "sifive.enterprise.grandcentral.AugmentedBundleType",
defName = "Foo",
elements = [
{class = "sifive.enterprise.grandcentral.AugmentedBundleType",
defName = "Bar",
elements = [],
name = "bar"}],
id = 0 : i64,
name = "MyView"},
{class = "sifive.enterprise.grandcentral.PrefixInterfacesAnnotation",
prefix = "PREFIX_"}]} {
firrtl.module @MyView_companion()
attributes {annotations = [{
class = "sifive.enterprise.grandcentral.ViewAnnotation",
id = 0 : i64,
name = "MyView",
type = "companion"}]} {}
firrtl.module @DUT()
attributes {annotations = [
{class = "sifive.enterprise.grandcentral.ViewAnnotation",
id = 0 : i64,
name = "MyView",
type = "parent"}]} {
firrtl.instance MyView_companion @MyView_companion()
}
firrtl.module @PrefixInterfacesAnnotation() {
firrtl.instance dut @DUT()
}
}

// CHECK-LABEL: firrtl.circuit "PrefixInterfacesAnnotation"
// The PrefixInterfacesAnnotation was removed from the circuit.
// CHECK-NOT: sifive.enterprise.grandcentral.PrefixInterfacesAnnotation

// Interface "Foo" is prefixed.
// CHECK: sv.interface @PREFIX_Foo {
// Interface "Bar" is prefixed, but not its name.
// CHECK-NEXT: PREFIX_Bar bar()

// Interface "Bar" is prefixed.
// CHECK: sv.interface @PREFIX_Bar
Loading