[FIRRTL] Add EmitMetada pass with RetimeModulesAnnotation support

This adds a new `EmitMetadata` pass to FIRRTL which a dumping ground for
any simple metadata generation passes that don't make sense to implement
as a standalone pass.  The first pass implemented is `RetimeModules`
which just collects the name of any annotated module and emits it into a
JSON array.
This commit is contained in:
Andrew Young 2021-09-21 18:49:59 -07:00
parent ef83a402af
commit f4bfc260a2
7 changed files with 276 additions and 3 deletions

View File

@ -418,6 +418,42 @@ Example:
"inclusive": true
}
```
### RetimeModuleAnnotation
| Property | Type | Description |
| ---------- | ------ | ------------- |
| class | string | `sifive.enterprise.firrtl.RetimeModuleAnnotation` |
This annotation is used to mark modules which should be retimed, and is
generally just passed through to other tools.
Example:
```json
{
"class": "sifive.enterprise.firrtl.RetimeModuleAnnotation"
}
```
### RetimeModulesAnnotation
| Property | Type | Description |
| ---------- | ------ | ------------- |
| class | string | `sifive.enterprise.firrtl.RetimeModulesAnnotation` |
| filename | string | The filename with full path where it will be written |
This annotation triggers the creation of a file containing a JSON array
containing the names of all modules annotated with the
`RetimeModuleAnnotation`.
Example:
```json
{
"class": "sifive.enterprise.firrtl.RetimeModuleAnnotation",
"filename": "retime_modules.json"
}
```
## FIRRTL specific attributes applied to HW Modules
### Design Under Test

View File

@ -40,6 +40,8 @@ std::unique_ptr<mlir::Pass> createInlinerPass();
std::unique_ptr<mlir::Pass> createBlackBoxMemoryPass();
std::unique_ptr<mlir::Pass> createEmitMetadataPass();
std::unique_ptr<mlir::Pass> createExpandWhensPass();
std::unique_ptr<mlir::Pass> createInferWidthsPass();

View File

@ -90,6 +90,14 @@ def BlackBoxMemory : Pass<"firrtl-blackbox-memory", "firrtl::CircuitOp"> {
];
}
def EmitMetadata : Pass<"firrtl-emit-metadata", "firrtl::CircuitOp"> {
let summary = "Emit metadata of the FIRRTL modules";
let description = [{
This pass handles the emission of several different kinds of metadata.
}];
let constructor = "circt::firrtl::createEmitMetadataPass()";
}
def ExpandWhens : Pass<"firrtl-expand-whens", "firrtl::FModuleOp"> {
let summary = "Remove all when conditional blocks.";
let description = [{

View File

@ -1,7 +1,9 @@
add_circt_dialect_library(CIRCTFIRRTLTransforms
add_circt_dialect_library(
CIRCTFIRRTLTransforms
BlackBoxMemory.cpp
BlackBoxReader.cpp
CheckCombCycles.cpp
EmitMetadata.cpp
ExpandWhens.cpp
GrandCentral.cpp
GrandCentralTaps.cpp
@ -14,10 +16,10 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms
ModuleInliner.cpp
PrefixModules.cpp
PrintInstanceGraph.cpp
DEPENDS
CIRCTFIRRTLTransformsIncGen
LINK_LIBS PUBLIC
CIRCTFIRRTL
CIRCTHW

View File

@ -0,0 +1,150 @@
//===- EmitMetadata.cpp - Emit various types of metadata --------*- 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
//
//===----------------------------------------------------------------------===//
//
// This file defines the EmitMetadata pass.
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/FIRRTL/FIRRTLAnnotations.h"
#include "circt/Dialect/FIRRTL/FIRRTLOps.h"
#include "circt/Dialect/FIRRTL/FIRRTLTypes.h"
#include "circt/Dialect/FIRRTL/Passes.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/SV/SVOps.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/JSON.h"
using namespace circt;
using namespace firrtl;
namespace {
class EmitMetadataPass : public EmitMetadataBase<EmitMetadataPass> {
LogicalResult emitRetimeModulesMetadata();
void getDependentDialects(mlir::DialectRegistry &registry) const override;
void runOnOperation() override;
};
} // end anonymous namespace
/// This will search for a target annotation and remove it from the operation.
/// If the annotation has a filename, it will be returned in the output
/// argument. If the annotation is missing the filename member, or if more than
/// one matching annotation is attached, it will print an error and return
/// failure.
static LogicalResult removeAnnotationWithFilename(Operation *op,
StringRef annoClass,
StringRef &filename) {
filename = "";
bool error = false;
AnnotationSet::removeAnnotations(op, [&](Annotation anno) {
// If there was a previous error or its not a match, continue.
if (error || !anno.isClass(annoClass))
return false;
// If we have already found a matching annotation, error.
if (!filename.empty()) {
op->emitError("More than one ") << annoClass << " annotation attached";
error = true;
return false;
}
// Get the filename from the annotation.
auto filenameAttr = anno.getMember<StringAttr>("filename");
if (!filenameAttr) {
op->emitError(annoClass) << " requires a filename";
error = true;
return false;
}
// Require a non-empty filename.
filename = filenameAttr.getValue();
if (filename.empty()) {
op->emitError(annoClass) << " requires a non-empty filename";
error = true;
return false;
}
return true;
});
// If there was a problem above, return failure.
return failure(error);
}
/// This function collects the name of each module annotated and prints them
/// all as a JSON array.
LogicalResult EmitMetadataPass::emitRetimeModulesMetadata() {
// Circuit level annotation.
auto *retimeModulesAnnoClass =
"sifive.enterprise.firrtl.RetimeModulesAnnotation";
// Per module annotation.
auto *retimeModuleAnnoClass =
"sifive.enterprise.firrtl.RetimeModuleAnnotation";
auto *context = &getContext();
auto circuitOp = getOperation();
// Get the filename, removing the annotation from the circuit.
StringRef filename;
if (failed(removeAnnotationWithFilename(circuitOp, retimeModulesAnnoClass,
filename)))
return failure();
if (filename.empty())
return success();
// Create a string buffer for the json data.
std::string buffer;
llvm::raw_string_ostream os(buffer);
llvm::json::OStream j(os);
// The output is a json array with each element a module name.
unsigned index = 0;
SmallVector<Attribute> symbols;
SmallString<3> placeholder;
j.array([&] {
for (auto module : circuitOp.getBody()->getOps<FModuleLike>()) {
// The annotation has no supplemental information, just remove it.
if (!AnnotationSet::removeAnnotations(module, retimeModuleAnnoClass))
continue;
// We use symbol substitution to make sure we output the correct thing
// when the module goes through renaming.
j.value(("{{" + Twine(index++) + "}}").str());
symbols.push_back(SymbolRefAttr::get(context, module.moduleName()));
}
});
// Put the retime information in a verbatim operation.
auto builder = OpBuilder::atBlockEnd(circuitOp.getBody());
auto verbatimOp = builder.create<sv::VerbatimOp>(
circuitOp.getLoc(), buffer, ValueRange(), builder.getArrayAttr(symbols));
auto fileAttr = hw::OutputFileAttr::getFromFilename(
context, filename, /*excludeFromFilelist=*/true);
verbatimOp->setAttr("output_file", fileAttr);
return success();
}
void EmitMetadataPass::getDependentDialects(
mlir::DialectRegistry &registry) const {
// We need this for SV verbatim and HW attributes.
registry.insert<hw::HWDialect, sv::SVDialect>();
}
void EmitMetadataPass::runOnOperation() {
if (failed(emitRetimeModulesMetadata()))
return signalPassFailure();
// This pass does not modify the hierarchy.
markAnalysesPreserved<InstanceGraph>();
}
std::unique_ptr<mlir::Pass> circt::firrtl::createEmitMetadataPass() {
return std::make_unique<EmitMetadataPass>();
}

View File

@ -0,0 +1,36 @@
// RUN: circt-opt --pass-pipeline='firrtl.circuit(firrtl-emit-metadata)' --verify-diagnostics --split-input-file %s
//===----------------------------------------------------------------------===//
// RetimeModules
//===----------------------------------------------------------------------===//
// expected-error @+1 {{sifive.enterprise.firrtl.RetimeModulesAnnotation requires a filename}}
firrtl.circuit "NoFilename" attributes { annotations = [{
class = "sifive.enterprise.firrtl.RetimeModulesAnnotation"
}]} {
firrtl.module @NoFilename() { }
}
// -----
// expected-error @+1 {{sifive.enterprise.firrtl.RetimeModulesAnnotation requires a non-empty filename}}
firrtl.circuit "EmptyFilename" attributes { annotations = [{
class = "sifive.enterprise.firrtl.RetimeModulesAnnotation",
filename = ""
}]} {
firrtl.module @EmptyFilename() { }
}
// -----
// expected-error @+1 {{more than one sifive.enterprise.firrtl.RetimeModulesAnnotation annotation attached}}
firrtl.circuit "MultipleAnnotations" attributes { annotations = [{
class = "sifive.enterprise.firrtl.RetimeModulesAnnotation",
filename = "test0.json"
}, {
class = "sifive.enterprise.firrtl.RetimeModulesAnnotation",
filename = "test1.json"
}]} {
firrtl.module @MultipleAnnotations() { }
}

View File

@ -0,0 +1,39 @@
// RUN: circt-opt --pass-pipeline='firrtl.circuit(firrtl-emit-metadata)' %s | FileCheck %s
firrtl.circuit "empty" {
firrtl.module @empty() {
}
}
// CHECK-LABEL: firrtl.circuit "empty" {
// CHECK-NEXT: firrtl.module @empty() {
// CHECK-NEXT: }
// CHECK-NEXT: }
//===----------------------------------------------------------------------===//
// RetimeModules
//===----------------------------------------------------------------------===//
firrtl.circuit "retime0" attributes { annotations = [{
class = "sifive.enterprise.firrtl.RetimeModulesAnnotation",
filename = "./tmp/retime_modules.json"
}]} {
firrtl.module @retime0() attributes { annotations = [{
class = "sifive.enterprise.firrtl.RetimeModuleAnnotation"
}]} { }
firrtl.module @retime1() { }
firrtl.module @retime2() attributes { annotations = [{
class = "sifive.enterprise.firrtl.RetimeModuleAnnotation"
}]} { }
}
// CHECK-LABEL: firrtl.circuit "retime0" {
// CHECK: firrtl.module @retime0() {
// CHECK: firrtl.module @retime1() {
// CHECK: firrtl.module @retime2() {
// CHECK{LITERAL}: sv.verbatim "[\22{{0}}\22,\22{{1}}\22]"
// CHECK-SAME: output_file = #hw.output_file<"tmp/retime_modules.json", excludeFromFileList>
// CHECK-SAME: symbols = [@retime0, @retime2]