[SV][LowerToHW] Add ExportModuleHierarchy pass. (#1792)

* [LowerToHW] Lower mainModule and DesignUnderTest attributes into moduleHierarchyFile attribute.

Since these are currently only used for the export module hierarchy
pass, these have been replaced with a more generic attribute marking
which modules should have their hierarchies exported to output files.

* [SV] Add ExportModuleHierarchy pass.

This adds a pass that collects the full module hierarchy into a JSON
string, which is then exported as part of an sv.verbatim op. The pass
collects a hierarchy for each module with the firrtl.moduleHierarchyFile
attribute.

The pass can be enabled in firtool by providing the
--export-module-hierarchy option.
This commit is contained in:
Richard Xia 2021-09-18 12:14:58 -07:00 committed by GitHub
parent e0cd23fe85
commit e97cff3fd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 166 additions and 12 deletions

View File

@ -34,6 +34,7 @@ std::unique_ptr<mlir::Pass> createHWLegalizeModulesPass();
std::unique_ptr<mlir::Pass> createHWGeneratorCalloutPass();
std::unique_ptr<mlir::Pass> createHWMemSimImplPass();
std::unique_ptr<mlir::Pass> createSVExtractTestCodePass();
std::unique_ptr<mlir::Pass> createHWExportModuleHierarchyPass();
/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/SV/SVPasses.h.inc"

View File

@ -117,4 +117,17 @@ def SVExtractTestCode : Pass<"sv-extract-test-code", "ModuleOp"> {
let dependentDialects = ["circt::sv::SVDialect"];
}
def HWExportModuleHierarchy : Pass<"hw-export-module-hierarchy",
"mlir::ModuleOp"> {
let summary = "Export module and instance hierarchy information";
let description = [{
This pass exports the module and instance hierarchy tree for each module
with the firrtl.moduleHierarchyFile attribute. These are lowered to
sv.verbatim ops with the output_file attribute.
}];
let constructor = "circt::sv::createHWExportModuleHierarchyPass()";
let dependentDialects = ["circt::sv::SVDialect"];
}
#endif // CIRCT_DIALECT_SV_SVPASSES

View File

@ -41,6 +41,10 @@ static const char coverAnnoClass[] =
"sifive.enterprise.firrtl.ExtractCoverageAnnotation";
static const char dutAnnoClass[] = "sifive.enterprise.firrtl.MarkDUTAnnotation";
/// Attribute that indicates that the module hierarchy starting at the annotated
/// module should be dumped to a file.
static const char moduleHierarchyFileAttrName[] = "firrtl.moduleHierarchyFile";
/// Given a FIRRTL type, return the corresponding type for the HW dialect.
/// This returns a null type if it cannot be lowered.
static Type lowerType(Type type) {
@ -425,19 +429,27 @@ void FIRRTLModuleLowering::runOnOperation() {
bind->moveBefore(bind->getParentOfType<hw::HWModuleOp>());
}
// Add attributes specific to the new main module, since the notion of a
// "main" module goes away after lowering to HW.
auto *newMainModule = state.oldToNewModuleMap[circuit.getMainModule()];
newMainModule->setAttr(
moduleHierarchyFileAttrName,
hw::OutputFileAttr::get(
StringAttr::get(circuit.getContext(), ""),
StringAttr::get(circuit.getContext(), "testharness_hier.json"),
/*exclude_from_filelist=*/
BoolAttr::get(circuit.getContext(), true),
/*exclude_replicated_ops=*/
BoolAttr::get(circuit.getContext(), true), circuit.getContext()));
// Finally delete all the old modules.
for (auto oldNew : state.oldToNewModuleMap)
oldNew.first->erase();
// Now that the modules are moved over, remove the Circuit. We pop the 'main
// module' specified in the Circuit into an attribute on the top level module.
getOperation()->setAttr(
"firrtl.mainModule",
StringAttr::get(circuit.getContext(), circuit.name()));
// Emit all the macros and preprocessor gunk at the start of the file.
lowerFileHeader(circuit, state);
// Now that the modules are moved over, remove the Circuit.
circuit.erase();
}
@ -724,8 +736,16 @@ FIRRTLModuleLowering::lowerModule(FModuleOp oldModule, Block *topLevelModule,
builder.create<hw::HWModuleOp>(oldModule.getLoc(), nameAttr, ports);
if (auto outputFile = oldModule->getAttr("output_file"))
newModule->setAttr("output_file", outputFile);
// Mark the design under test as a module of interest for exporting module
// hierarchy information.
if (AnnotationSet::removeAnnotations(oldModule, dutAnnoClass))
newModule->setAttr("firrtl.DesignUnderTest", builder.getUnitAttr());
newModule->setAttr(moduleHierarchyFileAttrName,
hw::OutputFileAttr::get(
builder.getStringAttr(""),
builder.getStringAttr("module_hier.json"),
/*exclude_from_filelist=*/builder.getBoolAttr(true),
/*exclude_replicated_ops=*/builder.getBoolAttr(true),
&getContext()));
loweringState.processRemainingAnnotations(oldModule,
AnnotationSet(oldModule));
return newModule;

View File

@ -7,6 +7,7 @@ add_circt_dialect_library(CIRCTSVTransforms
HWMemSimImpl.cpp
PrettifyVerilog.cpp
SVExtractTestCode.cpp
HWExportModuleHierarchy.cpp
DEPENDS
CIRCTSVTransformsIncGen

View File

@ -0,0 +1,98 @@
//===- HWExportModuleHierarchy.cpp - Export Module 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
//===----------------------------------------------------------------------===//
//
// Export the module and instance hierarchy information to JSON. This pass looks
// for modules with the firrtl.moduleHierarchyFile attribute and collects the
// hierarchy starting at those modules. The hierarchy information is then
// encoded as JSON in an sv.verbatim op with the output_file attribute set.
//
//===----------------------------------------------------------------------===//
#include "PassDetail.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/SV/SVPasses.h"
#include "mlir/IR/Builders.h"
#include "llvm/Support/JSON.h"
using namespace circt;
//===----------------------------------------------------------------------===//
// Pass Implementation
//===----------------------------------------------------------------------===//
struct HWExportModuleHierarchyPass
: public sv::HWExportModuleHierarchyBase<HWExportModuleHierarchyPass> {
void runOnOperation() override;
};
/// Recursively print the module hierarchy as serialized as JSON.
static void printHierarchy(hw::InstanceOp &inst, SymbolTable &symbolTable,
llvm::json::OStream &J) {
J.object([&] {
J.attribute("instance_name", inst.instanceName());
J.attribute("module_name", inst.moduleName());
J.attributeArray("instances", [&] {
auto moduleOp =
symbolTable.lookup<hw::HWModuleOp>(inst.moduleNameAttr().getValue());
// Only recurse on module ops, not extern or generated ops, whose internal
// are opaque.
if (moduleOp) {
for (auto op : moduleOp.getOps<hw::InstanceOp>()) {
printHierarchy(op, symbolTable, J);
}
}
});
});
}
/// Return the JSON-serialized module hierarchy for the given module as the top
/// of the hierarchy.
static std::string extractHierarchyFromTop(hw::HWModuleOp op,
SymbolTable &symbolTable) {
std::string resultBuffer;
llvm::raw_string_ostream os(resultBuffer);
llvm::json::OStream J(os);
// As a special case for top-level module, set instance name to module name,
// since the top-level module is not instantiated.
J.object([&] {
J.attribute("instance_name", op.getName());
J.attribute("module_name", op.getName());
J.attributeArray("instances", [&] {
for (auto op : op.getOps<hw::InstanceOp>())
printHierarchy(op, symbolTable, J);
});
});
return resultBuffer;
}
/// Find the modules corresponding to the firrtl mainModule and DesignUnderTest,
/// and if they exist, emit a verbatim op with the module hierarchy for each.
void HWExportModuleHierarchyPass::runOnOperation() {
mlir::ModuleOp mlirModule = getOperation();
auto builder = OpBuilder::atBlockEnd(mlirModule.getBody());
SymbolTable symbolTable(mlirModule);
for (auto op : mlirModule.getOps<hw::HWModuleOp>()) {
if (auto attr = op->getAttr("firrtl.moduleHierarchyFile")) {
auto verbatimOp = builder.create<sv::VerbatimOp>(
builder.getUnknownLoc(), extractHierarchyFromTop(op, symbolTable));
verbatimOp->setAttr("output_file", attr);
op->removeAttr("firrtl.moduleHierarchyFile");
}
}
}
//===----------------------------------------------------------------------===//
// Pass Creation
//===----------------------------------------------------------------------===//
std::unique_ptr<mlir::Pass> sv::createHWExportModuleHierarchyPass() {
return std::make_unique<HWExportModuleHierarchyPass>();
}

View File

@ -1,8 +1,6 @@
// RUN: circt-opt -lower-firrtl-to-hw %s -verify-diagnostics | FileCheck %s
// The firrtl.circuit should be removed, the main module name moved to an
// attribute on the module.
// CHECK-LABEL: {{^}}module attributes {firrtl.mainModule = "Simple"} {
// The firrtl.circuit should be removed.
// CHECK-NOT: firrtl.circuit
// We should get a large header boilerplate.
@ -20,7 +18,7 @@ firrtl.circuit "Simple" {
FORMAT = "xyz_timeout=%d\0A",
WIDTH = 32 : i8}}
// CHECK-LABEL: hw.module @Simple(%in1: i4, %in2: i2, %in3: i8) -> (out4: i4) {
// CHECK-LABEL: hw.module @Simple(%in1: i4, %in2: i2, %in3: i8) -> (out4: i4) attributes {firrtl.moduleHierarchyFile
firrtl.module @Simple(in %in1: !firrtl.uint<4>,
in %in2: !firrtl.uint<2>,
in %in3: !firrtl.sint<8>,

View File

@ -900,7 +900,7 @@ firrtl.circuit "Simple" attributes {annotations = [{class =
}
// CHECK-LABEL: hw.module @FooDUT
// CHECK: attributes {firrtl.DesignUnderTest}
// CHECK: attributes {firrtl.moduleHierarchyFile
firrtl.module @FooDUT() attributes {annotations = [
{class = "sifive.enterprise.firrtl.MarkDUTAnnotation"}]} {}
}

View File

@ -0,0 +1,17 @@
// RUN: circt-opt -pass-pipeline=hw-export-module-hierarchy %s | FileCheck %s
// CHECK: sv.verbatim "{\22instance_name\22:\22TestHarness\22,\22module_name\22:\22TestHarness\22,\22instances\22:[{\22instance_name\22:\22main_design\22,\22module_name\22:\22MainDesign\22,\22instances\22:[{\22instance_name\22:\22inner\22,\22module_name\22:\22InnerModule\22,\22instances\22:[]}]}]}" {output_file = {directory = "", exclude_from_filelist = true, exclude_replicated_ops = true, name = "testharness_hier.json"}}
hw.module @InnerModule(%in: i1) -> (out: i1) {
hw.output %in : i1
}
hw.module @MainDesign(%in: i1) -> (out: i1) {
%0 = hw.instance "inner" @InnerModule(in: %in: i1) -> (out: i1)
hw.output %0 : i1
}
hw.module @TestHarness() attributes {firrtl.moduleHierarchyFile = {directory = "", exclude_from_filelist = true, exclude_replicated_ops = true, name = "testharness_hier.json"}} {
%0 = hw.constant 1 : i1
hw.instance "main_design" @MainDesign(in: %0: i1) -> (out: i1)
}

View File

@ -150,6 +150,9 @@ static cl::opt<bool>
cl::desc("create interfaces and data/memory taps from SiFive "
"Grand Central annotations"),
cl::init(false));
static cl::opt<bool> exportModuleHierarchy(
"export-module-hierarchy",
cl::desc("export module and instance hierarchy as JSON"), cl::init(false));
static cl::opt<bool>
checkCombCycles("firrtl-check-comb-cycles",
@ -359,6 +362,9 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
auto &modulePM = pm.nest<hw::HWModuleOp>();
modulePM.addPass(sv::createPrettifyVerilogPass());
}
if (exportModuleHierarchy)
pm.addPass(sv::createHWExportModuleHierarchyPass());
}
// Load the emitter options from the command line. Command line options if