From f4bfc260a2d66d9a67f878e7bce91b0f1670269e Mon Sep 17 00:00:00 2001 From: Andrew Young Date: Tue, 21 Sep 2021 18:49:59 -0700 Subject: [PATCH] [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. --- docs/FIRRTLAnnotations.md | 36 +++++ include/circt/Dialect/FIRRTL/Passes.h | 2 + include/circt/Dialect/FIRRTL/Passes.td | 8 + lib/Dialect/FIRRTL/Transforms/CMakeLists.txt | 8 +- .../FIRRTL/Transforms/EmitMetadata.cpp | 150 ++++++++++++++++++ test/Dialect/FIRRTL/emit-metadata-errors.mlir | 36 +++++ test/Dialect/FIRRTL/emit-metadata.mlir | 39 +++++ 7 files changed, 276 insertions(+), 3 deletions(-) create mode 100644 lib/Dialect/FIRRTL/Transforms/EmitMetadata.cpp create mode 100644 test/Dialect/FIRRTL/emit-metadata-errors.mlir create mode 100644 test/Dialect/FIRRTL/emit-metadata.mlir diff --git a/docs/FIRRTLAnnotations.md b/docs/FIRRTLAnnotations.md index 733eaedd7d..44a9df1681 100644 --- a/docs/FIRRTLAnnotations.md +++ b/docs/FIRRTLAnnotations.md @@ -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 diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index 4d17a52569..c2edfb8a98 100755 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -40,6 +40,8 @@ std::unique_ptr createInlinerPass(); std::unique_ptr createBlackBoxMemoryPass(); +std::unique_ptr createEmitMetadataPass(); + std::unique_ptr createExpandWhensPass(); std::unique_ptr createInferWidthsPass(); diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index dc822a37d1..ce318991c8 100755 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -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 = [{ diff --git a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt index 1571219d10..6a5859d853 100755 --- a/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt +++ b/lib/Dialect/FIRRTL/Transforms/CMakeLists.txt @@ -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 diff --git a/lib/Dialect/FIRRTL/Transforms/EmitMetadata.cpp b/lib/Dialect/FIRRTL/Transforms/EmitMetadata.cpp new file mode 100644 index 0000000000..74556e1648 --- /dev/null +++ b/lib/Dialect/FIRRTL/Transforms/EmitMetadata.cpp @@ -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 { + LogicalResult emitRetimeModulesMetadata(); + void getDependentDialects(mlir::DialectRegistry ®istry) 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("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 symbols; + SmallString<3> placeholder; + j.array([&] { + for (auto module : circuitOp.getBody()->getOps()) { + // 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( + 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 ®istry) const { + // We need this for SV verbatim and HW attributes. + registry.insert(); +} + +void EmitMetadataPass::runOnOperation() { + if (failed(emitRetimeModulesMetadata())) + return signalPassFailure(); + + // This pass does not modify the hierarchy. + markAnalysesPreserved(); +} + +std::unique_ptr circt::firrtl::createEmitMetadataPass() { + return std::make_unique(); +} diff --git a/test/Dialect/FIRRTL/emit-metadata-errors.mlir b/test/Dialect/FIRRTL/emit-metadata-errors.mlir new file mode 100644 index 0000000000..679e6919e5 --- /dev/null +++ b/test/Dialect/FIRRTL/emit-metadata-errors.mlir @@ -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() { } +} + diff --git a/test/Dialect/FIRRTL/emit-metadata.mlir b/test/Dialect/FIRRTL/emit-metadata.mlir new file mode 100644 index 0000000000..6e5b3181a4 --- /dev/null +++ b/test/Dialect/FIRRTL/emit-metadata.mlir @@ -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] + +