[Debug] Add basic DebugInfo analysis and emission (#6148)

CIRCT will eventually want to be able to emit debugging information
alongside its Verilog and other outputs. This commit adds a basic
`DebugInfo` analysis that traverses the IR, collects information in
whichever format we chose to annotate it in the IR, and forms a separate
graph of DI nodes that can be inspected easily.

The rationale behind a separate DI graph is to decouple debug info
formats from in-IR storage. For example, we may want to emit the DI into
a JSON file (which is what the experimental HGLDD does), or dump the DI
in a human-readable form for testing. In the IR, it is not yet clear if
we respresent DI as separate ops, attributes on existing ops, and how
attributes express the DI graph. For example, do modules list their
variables, which makes removal hard, or do variables point to their
parent modules? The `DebugInfo` analysis abstracts over this IR encoding
matter and presents the DI in a format that is easily emitted. (Modules
list their contained variables.)

This commit also adds a `DebugInfo` MLIR translation library to CIRCT.
It implements DI emission as straightforward translations, allowing for
DI to be emitted as:

- `circt-translate --dump-di` for human-readable testing; and
- `circt-translate --emit-hgldd` and
  `circt-translate --emit-split-hgldd` for an experimental JSON format

When compiling a design with firtool, the `--emit-hgldd` option can be
used to generate `*.dd` companion files alongside its SV output. This
relies on Verilog emission locations to be annotated in the IR, which
is done separately in PR #6092.
This commit is contained in:
Fabian Schuiki 2023-10-04 12:51:05 -07:00 committed by GitHub
parent 0eaf230847
commit 5cb43e185d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1016 additions and 1 deletions

View File

@ -0,0 +1,79 @@
//===- DebugInfo.h - Debug info analysis ------------------------*- 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
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_ANALYSIS_DEBUGINFO_H
#define CIRCT_ANALYSIS_DEBUGINFO_H
#include "circt/Support/LLVM.h"
#include "mlir/IR/BuiltinAttributes.h"
#include "mlir/IR/Operation.h"
#include "llvm/ADT/MapVector.h"
namespace circt {
struct DIInstance;
struct DIVariable;
namespace detail {
struct DebugInfoBuilder;
} // namespace detail
struct DIModule {
/// The operation that generated this level of hierarchy.
Operation *op = nullptr;
/// The name of this level of hierarchy.
StringAttr name;
/// Levels of hierarchy nested under this module.
SmallVector<DIInstance *, 0> instances;
/// Variables declared within this module.
SmallVector<DIVariable *, 0> variables;
/// If this is an extern declaration.
bool isExtern = false;
};
struct DIInstance {
/// The operation that generated this instance.
Operation *op = nullptr;
/// The name of this instance.
StringAttr name;
/// The instantiated module.
DIModule *module;
};
struct DIVariable {
/// The name of this variable.
StringAttr name;
/// The location of the variable's declaration.
LocationAttr loc;
/// The SSA value representing the value of this variable.
Value value = nullptr;
};
/// Debug information attached to an operation and the operations nested within.
///
/// This is an analysis that gathers debug information for a piece of IR, either
/// from attributes attached to operations or the general structure of the IR.
struct DebugInfo {
/// Collect the debug information nested under the given operation.
DebugInfo(Operation *op);
/// The operation that was passed to the constructor.
Operation *operation;
/// A mapping from module name to module debug info.
llvm::MapVector<StringAttr, DIModule *> moduleNodes;
protected:
friend struct detail::DebugInfoBuilder;
llvm::SpecificBumpPtrAllocator<DIModule> moduleAllocator;
llvm::SpecificBumpPtrAllocator<DIInstance> instanceAllocator;
llvm::SpecificBumpPtrAllocator<DIVariable> variableAllocator;
};
} // namespace circt
#endif // CIRCT_ANALYSIS_DEBUGINFO_H

View File

@ -16,6 +16,7 @@
#include "circt/Dialect/FIRRTL/FIREmitter.h"
#include "circt/Dialect/FIRRTL/FIRParser.h"
#include "circt/Dialect/MSFT/ExportTcl.h"
#include "circt/Target/DebugInfo.h"
#include "circt/Target/ExportSystemC.h"
#ifndef CIRCT_INITALLTRANSLATIONS_H
@ -33,6 +34,7 @@ inline void registerAllTranslations() {
firrtl::registerFromFIRFileTranslation();
firrtl::registerToFIRFileTranslation();
ExportSystemC::registerExportSystemCTranslation();
debug::registerTranslations();
return true;
}();
(void)initOnce;

View File

@ -0,0 +1,57 @@
//===- DebugInfo.h - Debug info emission ------------------------*- 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 declares entry points to emit debug information.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_TARGET_DEBUGINFO_H
#define CIRCT_TARGET_DEBUGINFO_H
#include "circt/Support/LLVM.h"
#include "llvm/Support/raw_ostream.h"
namespace circt {
namespace debug {
/// Register all debug information emission flavors as from-MLIR translations.
void registerTranslations();
/// Dump the debug information in the given `module` in a human-readable format.
LogicalResult dumpDebugInfo(Operation *module, llvm::raw_ostream &os);
/// Options for HGLDD emission.
struct EmitHGLDDOptions {
/// A prefix prepended to all source file locations. This is useful if the
/// tool ingesting the HGLDD file is run from a different directory and
/// requires help finding the source files.
StringRef sourceFilePrefix = "";
/// A prefix prepended to all output file locations. This is useful if the
/// tool ingesting the HGLDD file expects generated output files to be
/// reported relative to a different directory.
StringRef outputFilePrefix = "";
/// The directory in which to place HGLDD output files.
StringRef outputDirectory = "";
};
/// Serialize the debug information in the given `module` into the HGLDD format
/// and writes it to `output`.
LogicalResult emitHGLDD(Operation *module, llvm::raw_ostream &os,
const EmitHGLDDOptions &options = {});
/// Serialize the debug information in the given `module` into the HGLDD format
/// and emit one companion HGLDD file per emitted HDL file. This requires that
/// a prior emission pass such as `ExportVerilog` has annotated emission
/// locations on the operations in `module`.
LogicalResult emitSplitHGLDD(Operation *module,
const EmitHGLDDOptions &options = {});
} // namespace debug
} // namespace circt
#endif // CIRCT_TARGET_DEBUGINFO_H

View File

@ -1,4 +1,5 @@
set(LLVM_OPTIONAL_SOURCES
DebugInfo.cpp
DependenceAnalysis.cpp
SchedulingAnalysis.cpp
TestPasses.cpp
@ -32,3 +33,11 @@ add_circt_library(CIRCTAnalysisTestPasses
CIRCTHW
MLIRPass
)
add_circt_library(CIRCTDebugInfoAnalysis
DebugInfo.cpp
LINK_LIBS PUBLIC
CIRCTHW
MLIRIR
)

132
lib/Analysis/DebugInfo.cpp Normal file
View File

@ -0,0 +1,132 @@
//===- DebugInfo.cpp - Debug info analysis --------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "circt/Analysis/DebugInfo.h"
#include "circt/Dialect/HW/HWOps.h"
#include "mlir/IR/BuiltinOps.h"
#include "llvm/Support/Debug.h"
#define DEBUG_TYPE "di"
using namespace mlir;
using namespace circt;
namespace circt {
namespace detail {
struct DebugInfoBuilder {
DebugInfoBuilder(DebugInfo &di) : di(di) {}
DebugInfo &di;
void visitRoot(Operation *op);
void visitModule(Operation *op, DIModule &module);
DIModule *createModule() {
return new (di.moduleAllocator.Allocate()) DIModule;
}
DIInstance *createInstance() {
return new (di.instanceAllocator.Allocate()) DIInstance;
}
DIVariable *createVariable() {
return new (di.variableAllocator.Allocate()) DIVariable;
}
DIModule &getOrCreateModule(StringAttr moduleName) {
auto &slot = di.moduleNodes[moduleName];
if (!slot) {
slot = createModule();
slot->name = moduleName;
}
return *slot;
}
};
void DebugInfoBuilder::visitRoot(Operation *op) {
op->walk<WalkOrder::PreOrder>([&](Operation *op) {
if (auto moduleOp = dyn_cast<hw::HWModuleOp>(op)) {
LLVM_DEBUG(llvm::dbgs()
<< "Collect DI for module " << moduleOp.getNameAttr() << "\n");
auto &module = getOrCreateModule(moduleOp.getNameAttr());
module.op = op;
// Add variables for each of the ports.
auto inputValues = moduleOp.getBody().getArguments();
auto outputValues =
moduleOp.getBodyBlock()->getTerminator()->getOperands();
for (auto &port : moduleOp.getPortList()) {
auto value = port.isOutput() ? outputValues[port.argNum]
: inputValues[port.argNum];
auto *var = createVariable();
var->name = port.name;
var->loc = port.loc;
var->value = value;
module.variables.push_back(var);
}
// Visit the ops in the module.
visitModule(op, module);
return WalkResult::skip();
}
if (auto moduleOp = dyn_cast<hw::HWModuleExternOp>(op)) {
LLVM_DEBUG(llvm::dbgs() << "Collect DI for extern module "
<< moduleOp.getNameAttr() << "\n");
auto &module = getOrCreateModule(moduleOp.getNameAttr());
module.op = op;
module.isExtern = true;
// Add variables for each of the ports.
for (auto &port : moduleOp.getPortList()) {
auto *var = createVariable();
var->name = port.name;
var->loc = port.loc;
module.variables.push_back(var);
}
return WalkResult::skip();
}
return WalkResult::advance();
});
}
void DebugInfoBuilder::visitModule(Operation *op, DIModule &module) {
op->walk([&](Operation *op) {
if (auto instOp = dyn_cast<hw::InstanceOp>(op)) {
auto &childModule =
getOrCreateModule(instOp.getModuleNameAttr().getAttr());
auto *instance = createInstance();
instance->name = instOp.getInstanceNameAttr();
instance->op = instOp;
instance->module = &childModule;
module.instances.push_back(instance);
// TODO: What do we do with the port assignments? These should be tracked
// somewhere.
return;
}
if (auto wireOp = dyn_cast<hw::WireOp>(op)) {
auto *var = createVariable();
var->name = wireOp.getNameAttr();
var->loc = wireOp.getLoc();
var->value = wireOp;
module.variables.push_back(var);
return;
}
});
}
} // namespace detail
} // namespace circt
DebugInfo::DebugInfo(Operation *op) : operation(op) {
detail::DebugInfoBuilder(*this).visitRoot(op);
}

View File

@ -1 +1,2 @@
add_subdirectory(DebugInfo)
add_subdirectory(ExportSystemC)

View File

@ -0,0 +1,18 @@
add_circt_translation_library(CIRCTTargetDebugInfo
DumpDebugInfo.cpp
EmitHGLDD.cpp
TranslateRegistration.cpp
LINK_COMPONENTS
Core
LINK_LIBS PUBLIC
CIRCTComb
CIRCTDebugInfoAnalysis
CIRCTHW
CIRCTSeq
CIRCTSupport
CIRCTSV
MLIRIR
MLIRTranslateLib
)

View File

@ -0,0 +1,70 @@
//===- DumpDebugInfo.cpp - Human-readable debug info dump -----------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "circt/Analysis/DebugInfo.h"
#include "circt/Target/DebugInfo.h"
#include "mlir/Support/IndentedOstream.h"
using namespace mlir;
using namespace circt;
static void dump(DIVariable &variable, raw_indented_ostream &os) {
os << "Variable " << variable.name;
if (variable.loc)
os << " at " << variable.loc;
os << "\n";
os.indent();
if (variable.value) {
if (auto blockArg = dyn_cast_or_null<BlockArgument>(variable.value)) {
os << "Arg " << blockArg.getArgNumber() << " of "
<< blockArg.getOwner()->getParentOp()->getName();
} else if (auto result = dyn_cast_or_null<OpResult>(variable.value)) {
os << "Result " << result.getResultNumber() << " of "
<< result.getDefiningOp()->getName();
}
os << " of type " << variable.value.getType() << " at "
<< variable.value.getLoc() << "\n";
}
os.unindent();
}
static void dump(DIInstance &instance, raw_indented_ostream &os) {
os << "Instance " << instance.name << " of " << instance.module->name;
if (instance.op)
os << " for " << instance.op->getName() << " at " << instance.op->getLoc();
os << "\n";
}
static void dump(DIModule &module, raw_indented_ostream &os) {
os << "Module " << module.name;
if (module.op)
os << " for " << module.op->getName() << " at " << module.op->getLoc();
os << "\n";
os.indent();
for (auto *variable : module.variables)
dump(*variable, os);
for (auto *instance : module.instances)
dump(*instance, os);
os.unindent();
}
static void dump(DebugInfo &di, raw_indented_ostream &os) {
os << "DebugInfo for " << di.operation->getName() << " at "
<< di.operation->getLoc() << "\n";
os.indent();
for (auto nameAndModule : di.moduleNodes)
dump(*nameAndModule.second, os);
os.unindent();
}
LogicalResult debug::dumpDebugInfo(Operation *module, llvm::raw_ostream &os) {
DebugInfo di(module);
raw_indented_ostream indentedOs(os);
dump(di, indentedOs);
return success();
}

View File

@ -0,0 +1,391 @@
//===- EmitHGLDD.cpp - HGLDD debug info emission --------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "circt/Analysis/DebugInfo.h"
#include "circt/Dialect/HW/HWOps.h"
#include "circt/Target/DebugInfo.h"
#include "mlir/IR/Threading.h"
#include "mlir/Support/FileUtilities.h"
#include "mlir/Support/IndentedOstream.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ToolOutputFile.h"
#define DEBUG_TYPE "di"
using namespace mlir;
using namespace circt;
using namespace debug;
using llvm::MapVector;
using llvm::SmallMapVector;
/// Walk the given `loc` and collect file-line-column locations that we want to
/// report as source ("HGL") locations or as emitted Verilog ("HDL") locations.
///
/// This function treats locations inside a `NameLoc` called "emitted" or a
/// `FusedLoc` with the metadata attribute string "verilogLocations" as emitted
/// Verilog locations. All other locations are considered to be source
/// locations.
///
/// The `level` parameter is used to track into how many "emitted" or
/// "verilogLocations" we have already descended. For every one of those we look
/// through the level gets decreased by one. File-line-column locations are only
/// collected at level 0. We don't descend into "emitted" or "verilogLocations"
/// once we've reached level 0. This effectively makes the `level` parameter
/// decide behind how many layers of "emitted" or "verilogLocations" we want to
/// collect file-line-column locations. Setting this to 0 effectively collects
/// source locations, i.e., everything not marked as emitted. Setting this to 1
/// effectively collects emitted locations, i.e., nothing that isn't behind
/// exactly one layer of "emitted" or "verilogLocations".
static void findLocations(Location loc, unsigned level,
SmallVectorImpl<FileLineColLoc> &locs) {
if (auto nameLoc = dyn_cast<NameLoc>(loc)) {
if (nameLoc.getName() == "emitted")
if (level-- == 0)
return;
findLocations(nameLoc.getChildLoc(), level, locs);
} else if (auto fusedLoc = dyn_cast<FusedLoc>(loc)) {
auto strAttr = dyn_cast_or_null<StringAttr>(fusedLoc.getMetadata());
if (strAttr && strAttr.getValue() == "verilogLocations")
if (level-- == 0)
return;
for (auto innerLoc : fusedLoc.getLocations())
findLocations(innerLoc, level, locs);
} else if (auto fileLoc = dyn_cast<FileLineColLoc>(loc)) {
if (level == 0)
locs.push_back(fileLoc);
}
}
/// Find the best location to report as source location ("HGL", emitted = false)
/// or as emitted location ("HDL", emitted = true). Returns any non-FIR file it
/// finds, and only falls back to FIR files if nothing else is found.
static FileLineColLoc findBestLocation(Location loc, bool emitted) {
SmallVector<FileLineColLoc> locs;
findLocations(loc, emitted ? 1 : 0, locs);
for (auto loc : locs)
if (!loc.getFilename().getValue().endswith(".fir"))
return loc;
for (auto loc : locs)
if (loc.getFilename().getValue().endswith(".fir"))
return loc;
return {};
}
//===----------------------------------------------------------------------===//
// HGLDD File Emission
//===----------------------------------------------------------------------===//
namespace {
/// Contextual information for a single HGLDD file to be emitted.
struct FileEmitter {
const EmitHGLDDOptions *options = nullptr;
SmallVector<DIModule *> modules;
SmallString<64> outputFileName;
StringAttr hdlFile;
SmallMapVector<StringAttr, unsigned, 8> sourceFiles;
void emit(llvm::raw_ostream &os);
void emit(llvm::json::OStream &json);
void emitLoc(llvm::json::OStream &json, FileLineColLoc loc, bool emitted);
void emitModule(llvm::json::OStream &json, DIModule *module);
void emitInstance(llvm::json::OStream &json, DIInstance *instance);
void emitVariable(llvm::json::OStream &json, DIVariable *variable);
/// Get a numeric index for the given `sourceFile`. Populates `sourceFiles`
/// with a unique ID assignment for each source file.
unsigned getSourceFile(StringAttr sourceFile, bool emitted) {
// Apply the source file prefix if this is a source file (emitted = false).
if (!emitted && !options->sourceFilePrefix.empty() &&
!llvm::sys::path::is_absolute(sourceFile.getValue())) {
SmallString<64> buffer;
buffer = options->sourceFilePrefix;
llvm::sys::path::append(buffer, sourceFile.getValue());
sourceFile = StringAttr::get(sourceFile.getContext(), buffer);
}
// Apply the output file prefix if this is an outpu file (emitted = true).
if (emitted && !options->outputFilePrefix.empty() &&
!llvm::sys::path::is_absolute(sourceFile.getValue())) {
SmallString<64> buffer;
buffer = options->outputFilePrefix;
llvm::sys::path::append(buffer, sourceFile.getValue());
sourceFile = StringAttr::get(sourceFile.getContext(), buffer);
}
auto &slot = sourceFiles[sourceFile];
if (slot == 0)
slot = sourceFiles.size();
return slot;
}
/// Find the best location and, if one is found, emit it under the given
/// `fieldName`.
void findAndEmitLoc(llvm::json::OStream &json, StringRef fieldName,
Location loc, bool emitted) {
if (auto fileLoc = findBestLocation(loc, emitted))
json.attributeObject(fieldName, [&] { emitLoc(json, fileLoc, emitted); });
}
};
} // namespace
void FileEmitter::emit(llvm::raw_ostream &os) {
llvm::json::OStream json(os, 2);
emit(json);
os << "\n";
}
void FileEmitter::emit(llvm::json::OStream &json) {
// The "HGLDD" header field needs to be the first in the JSON file (which
// violates the JSON spec, but what can you do). But we only know after module
// emission what the contents of the header will be.
std::string rawObjects;
{
llvm::raw_string_ostream objectsOS(rawObjects);
llvm::json::OStream objectsJson(objectsOS, 2);
objectsJson.arrayBegin(); // dummy for indentation
objectsJson.arrayBegin();
for (auto *module : modules)
emitModule(objectsJson, module);
objectsJson.arrayEnd();
objectsJson.arrayEnd(); // dummy for indentation
}
std::optional<unsigned> hdlFileIndex;
if (hdlFile)
hdlFileIndex = getSourceFile(hdlFile, true);
json.objectBegin();
json.attributeObject("HGLDD", [&] {
json.attribute("version", "1.0");
json.attributeArray("file_info", [&] {
for (auto [file, index] : sourceFiles)
json.value(file.getValue());
});
if (hdlFileIndex)
json.attribute("hdl_file_index", *hdlFileIndex);
});
json.attributeBegin("objects");
json.rawValue(StringRef(rawObjects).drop_front().drop_back().trim());
json.attributeEnd();
json.objectEnd();
}
void FileEmitter::emitLoc(llvm::json::OStream &json, FileLineColLoc loc,
bool emitted) {
json.attribute("file", getSourceFile(loc.getFilename(), emitted));
if (auto line = loc.getLine()) {
json.attribute("begin_line", line);
json.attribute("end_line", line);
}
if (auto col = loc.getColumn()) {
json.attribute("begin_column", col);
json.attribute("end_column", col);
}
}
StringAttr getVerilogModuleName(DIModule &module) {
if (auto *op = module.op)
if (auto attr = op->getAttrOfType<StringAttr>("verilogName"))
return attr;
return module.name;
}
/// Emit the debug info for a `DIModule`.
void FileEmitter::emitModule(llvm::json::OStream &json, DIModule *module) {
json.objectBegin();
json.attribute("kind", "module");
json.attribute("obj_name", module->name.getValue()); // HGL
json.attribute("module_name",
getVerilogModuleName(*module).getValue()); // HDL
if (module->isExtern)
json.attribute("isExtModule", 1);
if (auto *op = module->op) {
findAndEmitLoc(json, "hgl_loc", op->getLoc(), false);
findAndEmitLoc(json, "hdl_loc", op->getLoc(), true);
}
json.attributeArray("port_vars", [&] {
for (auto *var : module->variables)
emitVariable(json, var);
});
json.attributeArray("children", [&] {
for (auto *instance : module->instances)
emitInstance(json, instance);
});
json.objectEnd();
}
/// Emit the debug info for a `DIInstance`.
void FileEmitter::emitInstance(llvm::json::OStream &json,
DIInstance *instance) {
json.objectBegin();
json.attribute("name", instance->name.getValue());
json.attribute("obj_name", instance->module->name.getValue()); // HGL
json.attribute("module_name",
getVerilogModuleName(*instance->module).getValue()); // HDL
if (auto *op = instance->op) {
findAndEmitLoc(json, "hgl_loc", op->getLoc(), false);
findAndEmitLoc(json, "hdl_loc", op->getLoc(), true);
}
json.objectEnd();
}
/// Emit the debug info for a `DIVariable`.
void FileEmitter::emitVariable(llvm::json::OStream &json,
DIVariable *variable) {
json.objectBegin();
json.attribute("var_name", variable->name.getValue());
findAndEmitLoc(json, "hgl_loc", variable->loc, false);
findAndEmitLoc(json, "hdl_loc", variable->loc, true);
if (auto value = variable->value) {
StringAttr portName;
auto *defOp = value.getParentBlock()->getParentOp();
auto module = dyn_cast<hw::HWModuleOp>(defOp);
if (!module)
module = defOp->getParentOfType<hw::HWModuleOp>();
if (module) {
if (auto arg = dyn_cast<BlockArgument>(value)) {
portName = dyn_cast_or_null<StringAttr>(
module.getInputNames()[arg.getArgNumber()]);
} else if (auto wireOp = value.getDefiningOp<hw::WireOp>()) {
portName = wireOp.getNameAttr();
} else {
for (auto &use : value.getUses()) {
if (auto outputOp = dyn_cast<hw::OutputOp>(use.getOwner())) {
portName = dyn_cast_or_null<StringAttr>(
module.getOutputNames()[use.getOperandNumber()]);
break;
}
}
}
}
if (auto intType = dyn_cast<IntegerType>(value.getType())) {
json.attribute("type_name", "logic");
if (intType.getIntOrFloatBitWidth() != 1) {
json.attributeArray("packed_range", [&] {
json.value(intType.getIntOrFloatBitWidth() - 1);
json.value(0);
});
}
}
if (portName) {
json.attributeObject(
"value", [&] { json.attribute("sig_name", portName.getValue()); });
}
}
json.objectEnd();
}
//===----------------------------------------------------------------------===//
// Output Splitting
//===----------------------------------------------------------------------===//
namespace {
/// Contextual information for HGLDD emission shared across multiple HGLDD
/// files. This struct is used to determine an initial split of debug info files
/// and to distribute work.
struct Emitter {
DebugInfo di;
SmallVector<FileEmitter, 0> files;
Emitter(Operation *module, const EmitHGLDDOptions &options);
};
} // namespace
Emitter::Emitter(Operation *module, const EmitHGLDDOptions &options)
: di(module) {
// Group the DI modules according to their emitted file path. Modules that
// don't have an emitted file path annotated are collected in a separate
// group. That group, with a null `StringAttr` key, is emitted into a separate
// "global.dd" file.
MapVector<StringAttr, FileEmitter> groups;
for (auto [moduleName, module] : di.moduleNodes) {
StringAttr hdlFile;
if (module->op)
if (auto fileLoc = findBestLocation(module->op->getLoc(), true))
hdlFile = fileLoc.getFilename();
groups[hdlFile].modules.push_back(module);
}
// Determine the output file names and move the emitters into the `files`
// member.
files.reserve(groups.size());
for (auto &[hdlFile, emitter] : groups) {
emitter.options = &options;
emitter.hdlFile = hdlFile;
emitter.outputFileName = options.outputDirectory;
StringRef fileName = hdlFile ? hdlFile.getValue() : "global";
if (llvm::sys::path::is_absolute(fileName))
emitter.outputFileName = fileName;
else
llvm::sys::path::append(emitter.outputFileName, fileName);
llvm::sys::path::replace_extension(emitter.outputFileName, "dd");
files.push_back(std::move(emitter));
}
// Dump some information about the files to be created.
LLVM_DEBUG({
llvm::dbgs() << "HGLDD files:\n";
for (auto &emitter : files) {
llvm::dbgs() << "- " << emitter.outputFileName << " (from "
<< emitter.hdlFile << ")\n";
for (auto *module : emitter.modules)
llvm::dbgs() << " - " << module->name << "\n";
}
});
}
//===----------------------------------------------------------------------===//
// Emission Entry Points
//===----------------------------------------------------------------------===//
LogicalResult debug::emitHGLDD(Operation *module, llvm::raw_ostream &os,
const EmitHGLDDOptions &options) {
Emitter emitter(module, options);
for (auto &fileEmitter : emitter.files) {
os << "\n// ----- 8< ----- FILE \"" + fileEmitter.outputFileName +
"\" ----- 8< -----\n\n";
fileEmitter.emit(os);
}
return success();
}
LogicalResult debug::emitSplitHGLDD(Operation *module,
const EmitHGLDDOptions &options) {
Emitter emitter(module, options);
auto emit = [&](auto &fileEmitter) {
// Open the output file for writing.
std::string errorMessage;
auto output =
mlir::openOutputFile(fileEmitter.outputFileName, &errorMessage);
if (!output) {
module->emitError(errorMessage);
return failure();
}
// Emit the debug information and keep the file around.
fileEmitter.emit(output->os());
output->keep();
return success();
};
return mlir::failableParallelForEach(module->getContext(), emitter.files,
emit);
}

View File

@ -0,0 +1,78 @@
//===- TranslateRegistration.cpp - Register translation -------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/Comb/CombDialect.h"
#include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/SV/SVDialect.h"
#include "circt/Dialect/Seq/SeqDialect.h"
#include "circt/Target/DebugInfo.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/Tools/mlir-translate/Translation.h"
using namespace mlir;
namespace circt {
namespace debug {
static void registerDialects(DialectRegistry &registry) {
registry.insert<comb::CombDialect>();
registry.insert<hw::HWDialect>();
registry.insert<seq::SeqDialect>();
registry.insert<sv::SVDialect>();
}
void registerDumpTranslation() {
TranslateFromMLIRRegistration reg(
"dump-di", "dump debug information in human-readable form",
[](ModuleOp op, raw_ostream &output) {
return dumpDebugInfo(op, output);
},
registerDialects);
}
void registerHGLDDTranslation() {
static llvm::cl::opt<std::string> directory(
"hgldd-output-dir", llvm::cl::desc("Output directory for HGLDD files"),
llvm::cl::init(""));
static llvm::cl::opt<std::string> sourcePrefix(
"hgldd-source-prefix", llvm::cl::desc("Prefix for source file locations"),
llvm::cl::init(""));
static llvm::cl::opt<std::string> outputPrefix(
"hgldd-output-prefix", llvm::cl::desc("Prefix for output file locations"),
llvm::cl::init(""));
auto getOptions = [] {
EmitHGLDDOptions opts;
opts.sourceFilePrefix = sourcePrefix;
opts.outputFilePrefix = outputPrefix;
opts.outputDirectory = directory;
return opts;
};
static TranslateFromMLIRRegistration reg1(
"emit-hgldd", "emit HGLDD debug information",
[=](ModuleOp op, raw_ostream &output) {
return emitHGLDD(op, output, getOptions());
},
registerDialects);
static TranslateFromMLIRRegistration reg2(
"emit-split-hgldd", "emit HGLDD debug information as separate files",
[=](ModuleOp op, raw_ostream &output) {
return emitSplitHGLDD(op, getOptions());
},
registerDialects);
}
void registerTranslations() {
registerDumpTranslation();
registerHGLDDTranslation();
}
} // namespace debug
} // namespace circt

View File

@ -0,0 +1,27 @@
// RUN: circt-translate --dump-di %s | FileCheck %s
// CHECK-LABEL: Module "Foo" for hw.module
// CHECK: Variable "a"
// CHECK: Arg 0 of hw.module of type i32
// CHECK: Variable "b"
// CHECK: Result 0 of hw.instance of type i32
// CHECK: Instance "b0" of "Bar" for hw.instance
// CHECK: Instance "b1" of "Bar" for hw.instance
hw.module @Foo(in %a: i32, out b: i32) {
%b0.y = hw.instance "b0" @Bar(x: %a: i32) -> (y: i32)
%b1.y = hw.instance "b1" @Bar(x: %b0.y: i32) -> (y: i32)
hw.output %b1.y : i32
}
// CHECK-LABEL: Module "Bar" for hw.module
// CHECK: Variable "x"
// CHECK: Arg 0 of hw.module of type i32
// CHECK: Variable "y"
// CHECK: Result 0 of hw.wire of type i32
// CHECK: Variable "z"
// CHECK: Result 0 of hw.wire of type i32
hw.module @Bar(in %x: i32, out y: i32) {
%0 = comb.mul %x, %x : i32
%z = hw.wire %0 : i32
hw.output %z : i32
}

View File

@ -0,0 +1,91 @@
// RUN: circt-translate %s --emit-hgldd | FileCheck %s
// RUN: circt-translate %s --emit-split-hgldd --hgldd-output-dir=%T --hgldd-source-prefix=my/source --hgldd-output-prefix=my/verilog
// RUN: cat %T/Foo.dd | FileCheck %s --check-prefix=CHECK-FOO
// RUN: cat %T/Bar.dd | FileCheck %s --check-prefix=CHECK-BAR
#loc1 = loc("InputFoo.scala":4:10)
#loc2 = loc("InputFoo.scala":5:11)
#loc3 = loc("InputFoo.scala":6:12)
#loc4 = loc("InputBar.scala":8:5)
#loc5 = loc("InputBar.scala":14:5)
#loc6 = loc("InputBar.scala":21:10)
#loc7 = loc("InputBar.scala":22:11)
#loc8 = loc("InputBar.scala":23:12)
#loc9 = loc("InputBar.scala":25:15)
#loc10 = loc("Foo.sv":42:10)
#loc11 = loc("Bar.sv":49:10)
// CHECK-FOO: "file_info": [
// CHECK-FOO-NEXT: "my/source/InputFoo.scala"
// CHECK-FOO-NEXT: "my/verilog/Foo.sv"
// CHECK-FOO-NEXT: "my/source/InputBar.scala"
// CHECK-FOO-NEXT: ]
// CHECK-FOO-NEXT: "hdl_file_index": 2
// CHECK-FOO: "kind": "module"
// CHECK-FOO: "obj_name": "Foo"
// CHECK-BAR: "file_info": [
// CHECK-BAR-NEXT: "my/source/InputBar.scala"
// CHECK-BAR-NEXT: "my/verilog/Bar.sv"
// CHECK-BAR-NEXT: ]
// CHECK-BAR-NEXT: "hdl_file_index": 2
// CHECK-BAR: "kind": "module"
// CHECK-BAR: "obj_name": "Bar"
// CHECK-LABEL: FILE "Foo.dd"
// CHECK: "HGLDD"
// CHECK: "version": "1.0"
// CHECK: "file_info": [
// CHECK: "InputFoo.scala"
// CHECK: "Foo.sv"
// CHECK: "InputBar.scala"
// CHECK: ]
// CHECK: "hdl_file_index": 2
// CHECK: "obj_name": "Foo"
// CHECK: "module_name": "Foo"
// CHECK: "hgl_loc"
// CHECK: "file": 1,
// CHECK: "begin_line": 4
// CHECK: "begin_column": 10
// CHECK: "hdl_loc"
// CHECK: "file": 2,
// CHECK: "begin_line": 42
// CHECK: "begin_column": 10
// CHECK: "port_vars"
// CHECK: "var_name": "a"
// CHECK: "var_name": "b"
// CHECK: "children"
// CHECK: "name": "b0"
// CHECK: "obj_name": "Bar"
// CHECK: "module_name": "Bar"
// CHECK: "hgl_loc"
// CHECK: "file": 3,
// CHECK: "name": "b1"
// CHECK: "obj_name": "Bar"
// CHECK: "module_name": "Bar"
// CHECK: "hgl_loc"
// CHECK: "file": 3,
hw.module @Foo(in %a: i32 loc(#loc2), out b: i32 loc(#loc3)) {
%b0.y = hw.instance "b0" @Bar(x: %a: i32) -> (y: i32) loc(#loc4)
%b1.y = hw.instance "b1" @Bar(x: %b0.y: i32) -> (y: i32) loc(#loc5)
hw.output %b1.y : i32 loc(#loc1)
} loc(fused[#loc1, "emitted"(#loc10)])
// CHECK-LABEL: FILE "Bar.dd"
// CHECK: "module_name": "Bar"
// CHECK: "port_vars"
// CHECK: "var_name": "x"
// CHECK: "var_name": "y"
// CHECK: "var_name": "z"
hw.module private @Bar(in %x: i32 loc(#loc7), out y: i32 loc(#loc8)) {
%0 = comb.mul %x, %x : i32 loc(#loc9)
%z = hw.wire %0 : i32 loc(#loc9)
hw.output %z : i32 loc(#loc6)
} loc(fused[#loc6, "emitted"(#loc11)])
// CHECK-LABEL: "obj_name": "SingleResult"
// CHECK: "module_name": "CustomSingleResult123"
// CHECK: "isExtModule": 1
hw.module.extern @SingleResult(out outPort: i1) attributes {verilogName = "CustomSingleResult123"}

View File

@ -16,6 +16,7 @@ target_link_libraries(firtool PRIVATE
CIRCTSVTransforms
CIRCTTransforms
CIRCTFirtool
CIRCTTargetDebugInfo
MLIRBytecodeReader
MLIRBytecodeWriter

View File

@ -35,6 +35,7 @@
#include "circt/Support/LoweringOptionsParser.h"
#include "circt/Support/Passes.h"
#include "circt/Support/Version.h"
#include "circt/Target/DebugInfo.h"
#include "circt/Transforms/Passes.h"
#include "mlir/Bytecode/BytecodeReader.h"
#include "mlir/Bytecode/BytecodeWriter.h"
@ -177,6 +178,25 @@ static cl::opt<std::string>
cl::init(""), cl::value_desc("filename"),
cl::cat(mainCategory));
static cl::opt<bool> emitHGLDD("emit-hgldd", cl::desc("Emit HGLDD debug info"),
cl::init(false), cl::cat(mainCategory));
static cl::opt<std::string>
hglddSourcePrefix("hgldd-source-prefix",
cl::desc("Prefix for source file paths in HGLDD output"),
cl::init(""), cl::value_desc("path"),
cl::cat(mainCategory));
static cl::opt<std::string>
hglddOutputPrefix("hgldd-output-prefix",
cl::desc("Prefix for output file paths in HGLDD output"),
cl::init(""), cl::value_desc("path"),
cl::cat(mainCategory));
static cl::opt<std::string> hglddOutputDirectory(
"hgldd-output-dir", cl::desc("Directory into which to emit HGLDD files"),
cl::init(""), cl::value_desc("path"), cl::cat(mainCategory));
static cl::opt<bool>
emitBytecode("emit-bytecode",
cl::desc("Emit bytecode when generating MLIR output"),
@ -215,6 +235,36 @@ static LogicalResult printOp(Operation *op, raw_ostream &os) {
return success();
}
static debug::EmitHGLDDOptions getHGLDDOptions() {
debug::EmitHGLDDOptions opts;
opts.sourceFilePrefix = hglddSourcePrefix;
opts.outputFilePrefix = hglddOutputPrefix;
opts.outputDirectory = hglddOutputDirectory;
return opts;
}
/// Wrapper pass to call the `emitHGLDD` translation.
struct EmitHGLDDPass
: public PassWrapper<EmitHGLDDPass, OperationPass<mlir::ModuleOp>> {
llvm::raw_ostream &os;
EmitHGLDDPass(llvm::raw_ostream &os) : os(os) {}
void runOnOperation() override {
markAllAnalysesPreserved();
if (failed(debug::emitHGLDD(getOperation(), os, getHGLDDOptions())))
return signalPassFailure();
}
};
/// Wrapper pass to call the `emitSplitHGLDD` translation.
struct EmitSplitHGLDDPass
: public PassWrapper<EmitSplitHGLDDPass, OperationPass<mlir::ModuleOp>> {
void runOnOperation() override {
markAllAnalysesPreserved();
if (failed(debug::emitSplitHGLDD(getOperation(), getHGLDDOptions())))
return signalPassFailure();
}
};
/// Process a single buffer of the input.
static LogicalResult processBuffer(
MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr,
@ -313,9 +363,14 @@ static LogicalResult processBuffer(
return failure();
}
// If the user requested HGLDD debug info emission, enable Verilog location
// tracking.
if (emitHGLDD)
loweringOptions.emitVerilogLocations = true;
// Load the emitter options from the command line. Command line options if
// specified will override any module options.
if (loweringOptions.getNumOccurrences())
if (loweringOptions.toString() != LoweringOptions().toString())
loweringOptions.setAsAttribute(module.get());
// Add passes specific to Verilog emission if we're going there.
@ -330,11 +385,15 @@ static LogicalResult processBuffer(
if (failed(firtool::populateExportVerilog(pm, firtoolOptions,
(*outputFile)->os())))
return failure();
if (emitHGLDD)
pm.addPass(std::make_unique<EmitHGLDDPass>((*outputFile)->os()));
break;
case OutputSplitVerilog:
if (failed(firtool::populateExportSplitVerilog(
pm, firtoolOptions, firtoolOptions.outputFilename)))
return failure();
if (emitHGLDD)
pm.addPass(std::make_unique<EmitSplitHGLDDPass>());
break;
case OutputIRVerilog:
// Run the ExportVerilog pass to get its lowering, but discard the output.