[SV] Cleanup HWExportModuleHierarchyPass

Cleanup the `HWExportModuleHierarchyPass` pass in the following ways:

  1. Do not emit JSON directly, but rely use `sv.verbatim` operations with
  substitutions.

  2. Move `HWExportModuleHierarchyPass` before `ExportVerilog` (again).
  Given that (1) is happening, this must happen.  Both (1) and (2)
  effectively undo the work in #1931.

  3. Use a `hw::ModuleNamespace` to ensure unique symbols are created for
  substitutions.  This motivated some refactoring of the
  `HWExportModuleHierarchyPass` from a `struct` to a `class` since it now
  has private members. (This aligns with LLVM coding guidelines.)

  4. Update existing tests to expect the new `sv.verbatim` JSON.

  5. Add a new `firtool` end-to-end test baed on #5478.

Signed-off-by: Schuyler Eldridge <schuyler.eldridge@sifive.com>
This commit is contained in:
Schuyler Eldridge 2023-06-26 20:05:27 -04:00
parent 13bdb371f3
commit 4740516f57
No known key found for this signature in database
GPG Key ID: 50C5E9936AAD536D
5 changed files with 175 additions and 85 deletions

View File

@ -171,11 +171,6 @@ def HWExportModuleHierarchy : Pass<"hw-export-module-hierarchy",
let constructor = "circt::sv::createHWExportModuleHierarchyPass()";
let dependentDialects = ["circt::sv::SVDialect"];
let options = [
Option<"directoryName", "dir-name", "std::string", "\"./\"",
"Directory to emit into">
];
}
#endif // CIRCT_DIALECT_SV_SVPASSES

View File

@ -15,6 +15,7 @@
#include "PassDetail.h"
#include "circt/Dialect/HW/HWAttributes.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Dialect/HW/Namespace.h"
#include "circt/Dialect/SV/SVPasses.h"
#include "circt/Support/Path.h"
#include "mlir/IR/Builders.h"
@ -30,33 +31,52 @@ using namespace circt;
// Pass Implementation
//===----------------------------------------------------------------------===//
struct HWExportModuleHierarchyPass
class HWExportModuleHierarchyPass
: public sv::HWExportModuleHierarchyBase<HWExportModuleHierarchyPass> {
HWExportModuleHierarchyPass(std::optional<std::string> directory) {
if (directory)
directoryName = *directory;
}
private:
DenseMap<Operation *, hw::ModuleNamespace> moduleNamespaces;
void printHierarchy(hw::InstanceOp &inst, SymbolTable &symbolTable,
llvm::json::OStream &j,
SmallVectorImpl<Attribute> &symbols, unsigned &id);
void extractHierarchyFromTop(hw::HWModuleOp op, SymbolTable &symbolTable,
llvm::raw_ostream &os,
SmallVectorImpl<Attribute> &symbols);
void runOnOperation() override;
};
/// Recursively print the module hierarchy as serialized as JSON.
static void printHierarchy(hw::InstanceOp &inst, SymbolTable &symbolTable,
llvm::json::OStream &j) {
auto moduleOp = symbolTable.lookup(inst.getModuleNameAttr().getValue());
StringRef instanceName = inst.getInstanceName();
if (auto verilogNameAttr = inst->getAttrOfType<StringAttr>("hw.verilogName"))
instanceName = verilogNameAttr.getValue();
void HWExportModuleHierarchyPass::printHierarchy(
hw::InstanceOp &inst, SymbolTable &symbolTable, llvm::json::OStream &j,
SmallVectorImpl<Attribute> &symbols, unsigned &id) {
auto moduleOp = inst->getParentOfType<hw::HWModuleOp>();
auto innerSym = inst.getInnerSymAttr();
if (!innerSym) {
if (moduleNamespaces.find(moduleOp) == moduleNamespaces.end())
moduleNamespaces.insert({moduleOp, hw::ModuleNamespace(moduleOp)});
hw::ModuleNamespace &ns = moduleNamespaces[moduleOp];
innerSym =
StringAttr::get(inst.getContext(), ns.newName(inst.getInstanceName()));
inst->setAttr("inner_sym", innerSym);
}
j.object([&] {
j.attribute("instance_name", instanceName);
j.attribute("module_name", hw::getVerilogModuleName(moduleOp));
j.attribute("instance_name", ("{{" + Twine(id++) + "}}").str());
symbols.push_back(
hw::InnerRefAttr::get(moduleOp.getModuleNameAttr(), innerSym));
j.attribute("module_name", ("{{" + Twine(id++) + "}}").str());
symbols.push_back(inst.getModuleNameAttr());
j.attributeArray("instances", [&] {
// Only recurse on module ops, not extern or generated ops, whose internal
// are opaque.
if (auto module = dyn_cast<hw::HWModuleOp>(moduleOp)) {
auto *nextModuleOp =
symbolTable.lookup(inst.getModuleNameAttr().getValue());
if (auto module = dyn_cast<hw::HWModuleOp>(nextModuleOp)) {
for (auto op : module.getOps<hw::InstanceOp>()) {
printHierarchy(op, symbolTable, j);
printHierarchy(op, symbolTable, j, symbols, id);
}
}
});
@ -65,18 +85,21 @@ static void printHierarchy(hw::InstanceOp &inst, SymbolTable &symbolTable,
/// Return the JSON-serialized module hierarchy for the given module as the top
/// of the hierarchy.
static void extractHierarchyFromTop(hw::HWModuleOp op, SymbolTable &symbolTable,
llvm::raw_ostream &os) {
void HWExportModuleHierarchyPass::extractHierarchyFromTop(
hw::HWModuleOp op, SymbolTable &symbolTable, llvm::raw_ostream &os,
SmallVectorImpl<Attribute> &symbols) {
llvm::json::OStream j(os, 2);
// 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", hw::getVerilogModuleName(op));
j.attribute("instance_name", "{{0}}");
j.attribute("module_name", "{{0}}");
symbols.push_back(FlatSymbolRefAttr::get(op.getNameAttr()));
j.attributeArray("instances", [&] {
unsigned id = 1;
for (auto op : op.getOps<hw::InstanceOp>())
printHierarchy(op, symbolTable, j);
printHierarchy(op, symbolTable, j, symbols, id);
});
});
}
@ -86,7 +109,6 @@ static void extractHierarchyFromTop(hw::HWModuleOp op, SymbolTable &symbolTable,
void HWExportModuleHierarchyPass::runOnOperation() {
mlir::ModuleOp mlirModule = getOperation();
std::optional<SymbolTable> symbolTable;
bool directoryCreated = false;
for (auto op : mlirModule.getOps<hw::HWModuleOp>()) {
auto attr = op->getAttrOfType<ArrayAttr>("firrtl.moduleHierarchyFile");
@ -96,31 +118,17 @@ void HWExportModuleHierarchyPass::runOnOperation() {
if (!symbolTable)
symbolTable = SymbolTable(mlirModule);
if (!directoryCreated) {
auto error = llvm::sys::fs::create_directories(directoryName);
if (error) {
op->emitError("Error creating directory in HWExportModuleHierarchy: ")
<< error.message();
signalPassFailure();
return;
}
directoryCreated = true;
}
SmallString<128> outputPath(directoryName);
appendPossiblyAbsolutePath(outputPath, file.getFilename().getValue());
std::string errorMessage;
std::unique_ptr<llvm::ToolOutputFile> outputFile(
mlir::openOutputFile(outputPath, &errorMessage));
if (!outputFile) {
op->emitError("Error creating file in HWExportModuleHierarchy: ")
<< errorMessage;
signalPassFailure();
return;
}
std::string jsonBuffer;
llvm::raw_string_ostream os(jsonBuffer);
SmallVector<Attribute> symbols;
extractHierarchyFromTop(op, *symbolTable, outputFile->os());
extractHierarchyFromTop(op, *symbolTable, os, symbols);
outputFile->keep();
auto builder = ImplicitLocOpBuilder::atBlockEnd(
UnknownLoc::get(mlirModule.getContext()), mlirModule.getBody());
auto verbatim = builder.create<sv::VerbatimOp>(
jsonBuffer, ValueRange{}, builder.getArrayAttr(symbols));
verbatim->setAttr("output_file", file);
}
}
@ -133,5 +141,5 @@ void HWExportModuleHierarchyPass::runOnOperation() {
std::unique_ptr<mlir::Pass>
sv::createHWExportModuleHierarchyPass(std::optional<std::string> directory) {
return std::make_unique<HWExportModuleHierarchyPass>(directory);
return std::make_unique<HWExportModuleHierarchyPass>();
}

View File

@ -1,35 +1,42 @@
// RUN: rm -rf %t
// RUN: circt-opt -pass-pipeline='builtin.module(hw-export-module-hierarchy{dir-name=%t})' %s
// RUN: FileCheck %s < %t/testharness_hier.json
// RUN: circt-opt -pass-pipeline='builtin.module(hw-export-module-hierarchy)' %s | FileCheck %s
// CHECK: {
// CHECK-NEXT: "instance_name": "TestHarness",
// CHECK-NEXT: "module_name": "TestHarness",
// CHECK-NEXT: "instances": [
// CHECK-NEXT: {
// CHECK-NEXT: "instance_name": "main_design",
// CHECK-NEXT: "module_name": "MainDesign",
// CHECK-NEXT: "instances": [
// CHECK-NEXT: {
// CHECK-NEXT: "instance_name": "INNER",
// CHECK-NEXT: "module_name": "InnerModule",
// CHECK-NEXT: "instances": []
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: }
hw.module @InnerModule() -> () {}
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.verilogName = "INNER"}
hw.output %0 : i1
hw.module @MainDesign() -> () {
hw.instance "inner" @InnerModule() -> ()
}
hw.module @TestHarness() attributes {firrtl.moduleHierarchyFile = [#hw.output_file<"testharness_hier.json", excludeFromFileList>]} {
%0 = hw.constant 1 : i1
hw.instance "main_design" @MainDesign(in: %0: i1) -> (out: i1)
hw.instance "main_design" @MainDesign() -> ()
}
// CHECK: hw.module @MainDesign()
// CHECK-NEXT: hw.instance "inner"
// CHECK-SAME: sym @[[MainDesign_inner_sym:[_a-zA-Z0-9]+]]
// CHECK: hw.module @TestHarness()
// CHECK-NEXT: hw.instance "main_design"
// CHECK-SAME: sym @[[TestHarness_main_design_sym:[_a-zA-Z0-9]+]]
// CHECK: sv.verbatim "{
// CHECK-SAME{LITERAL}: \22instance_name\22: \22{{0}}\22,
// CHECK-SAME{LITERAL}: \22module_name\22: \22{{0}}\22,
// CHECK-SAME: \22instances\22: [
// CHECK-SAME: {
// CHECK-SAME{LITERAL}: \22instance_name\22: \22{{1}}\22,
// CHECK-SAME{LITERAL}: \22module_name\22: \22{{2}}\22,
// CHECK-SAME: \22instances\22: [
// CHECK-SAME: {
// CHECK-SAME{LITERAL}: \22instance_name\22: \22{{3}}\22,
// CHECK-SAME{LITERAL}: \22module_name\22: \22{{4}}\22,
// CHECK-SAME: \22instances\22: []
// CHECK-SAME: }
// CHECK-SAME: ]
// CHECK-SAME: }
// CHECK-SAME: ]
// CHECK-SAME: }"
// CHECK-SAME: symbols = [@TestHarness,
// CHECK-SAME: #hw.innerNameRef<@TestHarness::@[[TestHarness_main_design_sym]]>,
// CHECK-SAME: @MainDesign,
// CHECK-SAME: #hw.innerNameRef<@MainDesign::@[[MainDesign_inner_sym]]>,
// CHECK-SAME: @InnerModule]

View File

@ -0,0 +1,81 @@
; RUN: firtool -export-module-hierarchy %s | FileCheck %s
; This test checks for interactions between hierarchy files generated by the
; HWExportModuleHierarchy pass and any renaming which may happen in the circuit.
;
; See: https://github.com/llvm/circt/issues/5478
circuit Foo: %[[
{
"class": "sifive.enterprise.firrtl.ModuleHierarchyAnnotation",
"filename": "dut_hier.json"
},
{
"class": "sifive.enterprise.firrtl.TestHarnessHierarchyAnnotation",
"filename": "tb_hier.json"
},
{
"class": "sifive.enterprise.firrtl.MarkDUTAnnotation",
"target": "~Foo|Bar"
},
{
"class": "firrtl.transforms.DontTouchAnnotation",
"target": "~Foo|Baz>a"
},
{
"class": "chisel3.util.experimental.ForceNameAnnotation",
"target": "~Foo|Foo>bar",
"name": "PREFIX_bar"
},
{
"class": "chisel3.util.experimental.ForceNameAnnotation",
"target": "~Foo|Bar>baz",
"name": "PREFIX_baz"
}
]]
module Baz:
wire a: UInt<1>
a is invalid
module Bar:
inst baz of Baz
module Foo:
inst bar of Bar
; CHECK-LABEL: module Baz()
; CHECK-LABEL: module Bar()
; CHECK-NEXT: Baz [[Bar_baz:[_a-zA-Z0-9]+]]
; CHECK-LABEL: module Foo()
; CHECK-NEXT: Bar [[Foo_bar:[_a-zA-Z0-9]+]]
; CHECK-LABEL: FILE "dut_hier.json"
; CHECK: {
; CHECK-NEXT: "instance_name": "Bar",
; CHECK-NEXT: "module_name": "Bar",
; CHECK-NEXT: "instances": [
; CHECK-NEXT: {
; CHECK-NEXT: "instance_name": "[[Bar_baz]]",
; CHECK-NEXT: "module_name": "Baz",
; CHECK-NEXT: "instances": []
; CHECK-NEXT: }
; CHECK-NEXT: ]
; CHECK-NEXT: }
; CHECK-LABEL: FILE "tb_hier.json"
; CHECK: {
; CHECK-NEXT: "instance_name": "Foo",
; CHECK-NEXT: "module_name": "Foo",
; CHECK-NEXT: "instances": [
; CHECK-NEXT: {
; CHECK-NEXT: "instance_name": "[[Foo_bar]]",
; CHECK-NEXT: "module_name": "Bar",
; CHECK-NEXT: "instances": [
; CHECK-NEXT: {
; CHECK-NEXT: "instance_name": "[[Bar_baz]]",
; CHECK-NEXT: "module_name": "Baz",
; CHECK-NEXT: "instances": []
; CHECK-NEXT: }
; CHECK-NEXT: ]
; CHECK-NEXT: }
; CHECK-NEXT: ]
; CHECK-NEXT: }

View File

@ -387,6 +387,10 @@ static LogicalResult processBuffer(
exportPm.addPass(circt::createStripDebugInfoWithPredPass(
[](mlir::Location loc) { return true; }));
// Emit module and testbench hierarchy JSON files.
if (exportModuleHierarchy)
exportPm.addPass(sv::createHWExportModuleHierarchyPass(outputFilename));
// Emit a single file or multiple files depending on the output format.
switch (outputFormat) {
default:
@ -403,11 +407,6 @@ static LogicalResult processBuffer(
break;
}
// Run module hierarchy emission after verilog emission, which ensures we
// pick up any changes that verilog emission made.
if (exportModuleHierarchy)
exportPm.addPass(sv::createHWExportModuleHierarchyPass(outputFilename));
// Run final IR mutations to clean it up after ExportVerilog and before
// emitting the final MLIR.
if (!mlirOutFile.empty())