mirror of https://github.com/llvm/circt.git
[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:
parent
13bdb371f3
commit
4740516f57
|
@ -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
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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: }
|
|
@ -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())
|
||||
|
|
Loading…
Reference in New Issue