From bcfaf7843e8944d6faacd62325121060ca51001d Mon Sep 17 00:00:00 2001 From: Andrew Lenharth Date: Wed, 13 Oct 2021 05:48:17 -0700 Subject: [PATCH] [HW] Add an attribute to make file lists (#1986) Add an attribute to indicate the file containing the op should be put in a specific filelist at emission time. This attribute allows files to exist in multiple filelists. --- include/circt/Dialect/HW/HWAttributes.td | 38 ++++++++++++ .../ExportVerilog/ExportVerilog.cpp | 59 ++++++++++++++++--- lib/Dialect/HW/HWAttributes.cpp | 25 ++++++++ test/Dialect/HW/basic.mlir | 2 + test/ExportVerilog/output-file.mlir | 19 +++++- 5 files changed, 131 insertions(+), 12 deletions(-) diff --git a/include/circt/Dialect/HW/HWAttributes.td b/include/circt/Dialect/HW/HWAttributes.td index 72598f24f0..2cf4c09d16 100644 --- a/include/circt/Dialect/HW/HWAttributes.td +++ b/include/circt/Dialect/HW/HWAttributes.td @@ -81,6 +81,44 @@ def OutputFileAttr : AttrDef { }]; } +// An attribute to indicate which filelist an operation's file should be +// included in. +def FileListAttr : AttrDef { + let summary = "Ouput filelist attribute"; + let description = [{ + This attribute represents an output filelist for something which will be + printed. The `filename` string is the file which the filename of the + operation to be output to. + + When ExportVerilog runs, some of the files produced are lists of other files + which are produced. Each filelist exported contains entities' output file + with `FileListAttr` marked. + + + Examples: + ```mlir + #hw.ouput_filelist<"/home/tester/t.F"> + #hw.ouput_filelist<"t.f"> + ``` + }]; + let mnemonic = "output_filelist"; + let parameters = (ins "::mlir::StringAttr":$filename); + let builders = [ + AttrBuilderWithInferredContext<(ins + "::mlir::StringAttr":$filename), [{ + return get(filename.getContext(), filename); + }]>, + ]; + + let extraClassDeclaration = [{ + /// Get an OutputFileAttr from a string filename, canonicalizing the + /// filename. + static FileListAttr getFromFilename(::mlir::MLIRContext *context, + const ::mlir::Twine &filename); + }]; + +} + /// An attribute describing a module parameter, or instance parameter /// specification. def ParamDeclAttr : AttrDef { diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index ef73a42ef2..d61feae8a6 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -3655,6 +3655,9 @@ struct SharedEmitterState { /// the map. llvm::MapVector files; + /// The various file lists and their contents to emit + llvm::StringMap> fileLists; + /// A list of operations replicated in each output file (e.g., `sv.verbatim` /// or `sv.ifdef` without dedicated output file). SmallVector replicatedOps; @@ -3733,6 +3736,14 @@ void SharedEmitterState::gatherFiles(bool separateModules) { addToFilelist = !attr.getExcludeFromFilelist().getValue(); } + // Collect extra file lists to output the file to. + SmallVector opFileList; + if (auto fl = op.getAttrOfType("output_filelist")) + opFileList.push_back(fl.getFilename()); + if (auto fla = op.getAttrOfType("output_filelist")) + for (auto fl : fla) + opFileList.push_back(fl.cast().getFilename()); + auto separateFile = [&](Operation *op, Twine defaultFileName = "") { // If we're emitting to a separate file and the output_file attribute // didn't specify a filename, take the default one if present or emit an @@ -3747,10 +3758,13 @@ void SharedEmitterState::gatherFiles(bool separateModules) { } } - auto &file = files[Identifier::get(outputPath, op->getContext())]; + auto destFile = Identifier::get(outputPath, op->getContext()); + auto &file = files[destFile]; file.ops.push_back(info); file.emitReplicatedOps = emitReplicatedOps; file.addToFilelist = addToFilelist; + for (auto fl : opFileList) + fileLists[fl.getValue()].push_back(destFile); }; // Separate the operation into dedicated output file, or emit into the @@ -3960,6 +3974,15 @@ LogicalResult circt::exportVerilog(ModuleOp module, llvm::raw_ostream &os) { emitter.collectOpsForFile(it.second, list); } + // Emit the filelists. + for (auto &it : emitter.fileLists) { + std::string contents("\n// ----- 8< ----- FILE \"" + it.first().str() + + "\" ----- 8< -----\n\n"); + for (auto &name : it.second) + contents += name.str() + "\n"; + list.emplace_back(contents); + } + // Finally, emit all the ops we collected. emitter.emitOps(list, os, /*parallelize=*/true); return failure(emitter.encounteredError); @@ -3997,32 +4020,40 @@ std::unique_ptr circt::createExportVerilogPass() { // Split Emitter //===----------------------------------------------------------------------===// -static void createSplitOutputFile(Identifier fileName, FileInfo &file, - StringRef dirname, - SharedEmitterState &emitter) { +static std::unique_ptr +createOutputFile(StringRef fileName, StringRef dirname, + SharedEmitterState &emitter) { // Determine the output path from the output directory and filename. SmallString<128> outputFilename(dirname); - appendPossiblyAbsolutePath(outputFilename, fileName.strref()); + appendPossiblyAbsolutePath(outputFilename, fileName); auto outputDir = llvm::sys::path::parent_path(outputFilename); // Create the output directory if needed. std::error_code error = llvm::sys::fs::create_directories(outputDir); if (error) { - mlir::emitError(file.ops[0].op->getLoc(), + mlir::emitError(emitter.rootOp.getLoc(), "cannot create output directory \"" + outputDir + "\": " + error.message()); emitter.encounteredError = true; - return; + return {}; } // Open the output file. std::string errorMessage; auto output = mlir::openOutputFile(outputFilename, &errorMessage); if (!output) { - llvm::errs() << errorMessage << "\n"; + mlir::emitError(emitter.rootOp.getLoc(), errorMessage); emitter.encounteredError = true; - return; } + return output; +} + +static void createSplitOutputFile(Identifier fileName, FileInfo &file, + StringRef dirname, + SharedEmitterState &emitter) { + auto output = createOutputFile(fileName, dirname, emitter); + if (!output) + return; SharedEmitterState::EmissionList list; emitter.collectOpsForFile(file, list); @@ -4067,6 +4098,16 @@ LogicalResult circt::exportSplitVerilog(ModuleOp module, StringRef dirname) { } output->keep(); + // Emit the filelists. + for (auto &it : emitter.fileLists) { + auto output = createOutputFile(it.first(), dirname, emitter); + if (!output) + continue; + for (auto &name : it.second) + output->os() << name.str() << "\n"; + output->keep(); + } + return failure(emitter.encounteredError); } diff --git a/lib/Dialect/HW/HWAttributes.cpp b/lib/Dialect/HW/HWAttributes.cpp index 4458755958..6bdaf7450e 100644 --- a/lib/Dialect/HW/HWAttributes.cpp +++ b/lib/Dialect/HW/HWAttributes.cpp @@ -156,6 +156,31 @@ void OutputFileAttr::print(DialectAsmPrinter &p) const { p << ">"; } +//===----------------------------------------------------------------------===// +// FileListAttr +//===----------------------------------------------------------------------===// + +/// Option ::= 'includeReplicatedOp' +/// OutputFileAttr ::= 'output_file<' name (',' Option)* '>' +Attribute FileListAttr::parse(DialectAsmParser &p, Type type) { + StringAttr filename; + if (p.parseLess() || p.parseAttribute(filename) || + p.parseGreater()) + return Attribute(); + auto *context = p.getContext(); + return FileListAttr::get(context, filename); +} + +void FileListAttr::print(DialectAsmPrinter &p) const { + p << "output_filelist<" << getFilename() << ">"; +} + +FileListAttr FileListAttr::getFromFilename(MLIRContext *context, + const Twine &filename) { + auto canonicalized = canonicalizeFilename("", filename); + return FileListAttr::get(StringAttr::get(context, canonicalized)); +} + //===----------------------------------------------------------------------===// // ParamDeclAttr //===----------------------------------------------------------------------===// diff --git a/test/Dialect/HW/basic.mlir b/test/Dialect/HW/basic.mlir index efa54037b6..519efd974a 100644 --- a/test/Dialect/HW/basic.mlir +++ b/test/Dialect/HW/basic.mlir @@ -146,3 +146,5 @@ hw.module @signed_arrays(%arg0: si8) -> (out: !hw.array<2xsi8>) { hw.module @argRenames(%arg1: i32) attributes {argNames = [""]} { } +hw.module @fileListTest(%arg1: i32) attributes {output_filelist = #hw.output_filelist<"foo.f">} { +} diff --git a/test/ExportVerilog/output-file.mlir b/test/ExportVerilog/output-file.mlir index 0729c63c67..d22827ba9a 100644 --- a/test/ExportVerilog/output-file.mlir +++ b/test/ExportVerilog/output-file.mlir @@ -3,7 +3,8 @@ // CHECK-LABEL: module B #file1 = #hw.output_file<"dir1/file1.sv", includeReplicatedOps> -hw.module @foo1(%a: i1) -> (b: i1) attributes {output_file = #file1} { +#filelist1 = #hw.output_filelist<"dir5/foo.f"> +hw.module @foo1(%a: i1) -> (b: i1) attributes {output_file = #file1, output_filelist = #filelist1} { hw.output %a : i1 } // CHECK-LABEL: FILE "dir1{{.}}file1.sv" @@ -11,14 +12,16 @@ hw.module @foo1(%a: i1) -> (b: i1) attributes {output_file = #file1} { // CHECK-LABEL: module B #file2 = #hw.output_file<"dir1/file2.sv"> -hw.module @foo2(%a: i1) -> (b: i1) attributes {output_file = #file2} { +#filelist2 = [#hw.output_filelist<"dir5/foo.f"> ,#hw.output_filelist<"dir5/bar.f">] +hw.module @foo2(%a: i1) -> (b: i1) attributes {output_file = #file2, output_filelist = #filelist2} { hw.output %a : i1 } // CHECK-LABEL: FILE "dir1{{.}}file2.sv" // CHECK-LABEL: module foo2 #file3 = #hw.output_file<"dir2/", includeReplicatedOps> -hw.module @foo3(%a: i1) -> (b: i1) attributes {output_file = #file3} { +#filelist3 = [#hw.output_filelist<"dir5/bar.f">] +hw.module @foo3(%a: i1) -> (b: i1) attributes {output_file = #file3, output_filelist = #filelist3} { hw.output %a : i1 } // CHECK-LABEL: FILE "dir2{{.}}foo3.sv" @@ -37,3 +40,13 @@ sv.verbatim "module C; endmodule" {output_file = #hw.output_file<"/tmp/dummy.sv" // CHECK-LABEL: FILE "/tmp/dummy.sv" // CHECK-LABEL: module C +// Filelists +// CHECK-LABEL: FILE "dir5/foo.f" +// CHECK-EMPTY: +// CHECK-NEXT: dir1/file1.sv +// CHECK-NEXT: dir1/file2.sv + +// CHECK-LABEL: FILE "dir5/bar.f" +// CHECK-EMPTY: +// CHECK-NEXT: dir1/file2.sv +// CHECK-NEXT: dir2/foo3.sv