diff --git a/include/circt/Debug/HWDebug.h b/include/circt/Debug/HWDebug.h new file mode 100644 index 000000000000..e20290e014a8 --- /dev/null +++ b/include/circt/Debug/HWDebug.h @@ -0,0 +1,10 @@ +#ifndef CIRCT_DEBUG_HWDEBUG_H +#define CIRCT_DEBUG_HWDEBUG_H + +#include "mlir/Pass/Pass.h" + +namespace circt::debug { +std::unique_ptr createExportHGDBPass(std::string filename); +} // namespace circt::debug + +#endif // CIRCT_DEBUG_HWDEBUG_H diff --git a/include/circt/Dialect/FIRRTL/Passes.h b/include/circt/Dialect/FIRRTL/Passes.h index d62403aec2f4..34732a1a8a41 100644 --- a/include/circt/Dialect/FIRRTL/Passes.h +++ b/include/circt/Dialect/FIRRTL/Passes.h @@ -28,10 +28,9 @@ std::unique_ptr createLowerFIRRTLAnnotationsPass(bool ignoreUnhandledAnnotations = false, bool ignoreClasslessAnnotations = false); -std::unique_ptr -createLowerFIRRTLTypesPass(bool replSeqMem = false, - bool preserveAggregate = false, - bool preservePublicTypes = true); +std::unique_ptr createLowerFIRRTLTypesPass( + bool replSeqMem = false, bool preserveAggregate = false, + bool preservePublicTypes = true, bool insertDebugInfo = false); std::unique_ptr createLowerBundleVectorTypesPass(); diff --git a/include/circt/Dialect/FIRRTL/Passes.td b/include/circt/Dialect/FIRRTL/Passes.td index 289f9676d606..4bd1b3b3b304 100644 --- a/include/circt/Dialect/FIRRTL/Passes.td +++ b/include/circt/Dialect/FIRRTL/Passes.td @@ -51,7 +51,9 @@ def LowerFIRRTLTypes : Pass<"firrtl-lower-types", "firrtl::CircuitOp"> { "Preserve passive aggregate types in the module.">, Option<"preservePublicTypes", "preserve-public-types", "bool", "true", "Force to lower ports of toplevel and external modules even" - "when aggregate preservation mode."> + "when aggregate preservation mode.">, + Option<"insertDebugInfo", "insert-debug-info", "bool", "false", + "Insert debug information."> ]; let dependentDialects = ["hw::HWDialect"]; } diff --git a/include/circt/Dialect/HW/HWOps.h b/include/circt/Dialect/HW/HWOps.h index 125ac0067862..b66a87685784 100644 --- a/include/circt/Dialect/HW/HWOps.h +++ b/include/circt/Dialect/HW/HWOps.h @@ -48,6 +48,9 @@ struct PortInfo { /// The optional symbol for this port. StringAttr sym = {}; + // The optional debug attribute for this port + StringAttr debugAttr = {}; + StringRef getName() const { return name.getValue(); } bool isOutput() const { return direction == OUTPUT; } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 062ce89533f6..fba4d43b0439 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory(Analysis) add_subdirectory(Bindings) add_subdirectory(CAPI) add_subdirectory(Conversion) +add_subdirectory(Debug) add_subdirectory(Dialect) add_subdirectory(Scheduling) add_subdirectory(Support) diff --git a/lib/Conversion/ExportVerilog/ExportVerilog.cpp b/lib/Conversion/ExportVerilog/ExportVerilog.cpp index d575ad455869..03a388db11cf 100644 --- a/lib/Conversion/ExportVerilog/ExportVerilog.cpp +++ b/lib/Conversion/ExportVerilog/ExportVerilog.cpp @@ -148,7 +148,7 @@ static bool isDuplicatableExpression(Operation *op) { /// Return the verilog name of the operations that can define a symbol. /// Except for , check global state /// `getDeclarationVerilogName` for them. -static StringRef getSymOpName(Operation *symOp) { +StringRef getSymOpName(Operation *symOp) { // Typeswitch of operation types which can define a symbol. // If legalizeNames has renamed it, then the attribute must be set. if (auto attr = symOp->getAttrOfType("hw.verilogName")) diff --git a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp index ad277743e5b8..2d0987099766 100644 --- a/lib/Conversion/FIRRTLToHW/LowerToHW.cpp +++ b/lib/Conversion/FIRRTLToHW/LowerToHW.cpp @@ -790,6 +790,17 @@ LogicalResult FIRRTLModuleLowering::lowerPorts( hwPort.type = lowerType(firrtlPort.type); hwPort.sym = firrtlPort.sym; + // Pass down the debug attr if any + // Notice that hasAnnotation does not work since the debug attribute + // is not inserted as a class + for (auto anno : firrtlPort.annotations) { + auto annoDict = anno.getDict(); + if (auto debugAttr = annoDict.get("hw.debug.name")) { + hwPort.debugAttr = debugAttr.cast(); + firrtlPort.annotations.removeAnnotation(anno); + break; + } + } // We can't lower all types, so make sure to cleanly reject them. if (!hwPort.type) { moduleOp->emitError("cannot lower this port type to HW"); @@ -989,7 +1000,6 @@ FIRRTLModuleLowering::lowerModule(FModuleOp oldModule, Block *topLevelModule, if (failed) return {}; - loweringState.processRemainingAnnotations(oldModule, annos); return newModule; } @@ -1068,6 +1078,15 @@ static Value tryEliminatingConnectsToValue(Value flipValue, auto connectSrc = theConnect.src(); + if (auto *srcOp = connectSrc.getDefiningOp()) { + if (srcOp->hasAttr("hw.debug.name")) { + // If the source has debug attribute, we don't do any optimization + // because otherwise we will lose this symbol + auto str = srcOp->getAttr("hw.debug.name").cast().str(); + return {}; + } + } + // Convert fliped sources to passive sources. if (!connectSrc.getType().cast().isPassive()) connectSrc = @@ -1557,6 +1576,11 @@ void FIRRTLLowering::optimizeTemporaryWire(sv::WireOp wire) { SmallVector reads; sv::AssignOp write; + // if the wire has debug attributes, skip it + if (wire->hasAttr("hw.debug.name")) { + return; + } + for (auto *user : wire->getUsers()) { if (auto read = dyn_cast(user)) { reads.push_back(read); @@ -1936,6 +1960,10 @@ template LogicalResult FIRRTLLowering::setLoweringTo(Operation *orig, CtorArgTypes... args) { auto result = builder.createOrFold(args...); + if (auto debugAttr = orig->getAttr("hw.debug.name")) { + auto *op = result.getDefiningOp(); + op->setAttr("hw.debug.name", debugAttr); + } if (auto *op = result.getDefiningOp()) tryCopyName(op, orig); return setPossiblyFoldedLowering(orig->getResult(0), result); @@ -1974,6 +2002,11 @@ Value FIRRTLLowering::getReadInOutOp(Value v) { } result = builder.createOrFold(v); + if (auto *vOp = v.getDefiningOp()) { + if (auto debugAttr = vOp->getAttr("hw.debug.name")) { + result.getDefiningOp()->setAttr("hw.debug.name", debugAttr); + } + } builder.restoreInsertionPoint(oldIP); return result; } @@ -2280,10 +2313,12 @@ LogicalResult FIRRTLLowering::visitDecl(NodeOp op) { // Node operations are logical noops, but may carry annotations or be // referred to through an inner name. If a don't touch is present, ensure // that we have a symbol name so we can keep the node as a wire. + // Or we have debug attribute attached auto symName = op.inner_symAttr(); auto name = op.nameAttr(); - if (AnnotationSet::removeAnnotations( - op, "firrtl.transforms.DontTouchAnnotation") && + if ((AnnotationSet::removeAnnotations( + op, "firrtl.transforms.DontTouchAnnotation") || + op->hasAttr("hw.debug.name")) && !symName) { // name may be empty auto moduleName = cast(op->getParentOp()).getName(); @@ -2293,7 +2328,11 @@ LogicalResult FIRRTLLowering::visitDecl(NodeOp op) { if (symName) { auto wire = builder.create(operand.getType(), name, symName); - builder.create(wire, operand); + auto assign = builder.create(wire, operand); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assign->setAttr("hw.debug.name", debugAttr); + wire->setAttr("hw.debug.name", debugAttr); + } } return setLowering(op, operand); @@ -2499,10 +2538,15 @@ LogicalResult FIRRTLLowering::visitDecl(RegResetOp op) { symName = op.nameAttr(); auto regResult = builder.create(resultType, op.nameAttr(), symName); + if (auto debugAttr = op->getAttr("hw.debug.name")) + regResult->setAttr("hw.debug.name", debugAttr); (void)setLowering(op, regResult); auto resetFn = [&]() { - builder.create(regResult, resetValue); + auto assignOP = builder.create(regResult, resetValue); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assignOP->setAttr("hw.debug.name", debugAttr); + } }; if (op.resetSignal().getType().isa()) { @@ -2751,8 +2795,20 @@ LogicalResult FIRRTLLowering::visitDecl(InstanceOp oldInstance) { // Create a wire for each input/inout operand, so there is // something to connect to. - Value wire = - createTmpWireOp(portType, "." + port.getName().str() + ".wire"); + auto w = createTmpWireOp(portType, "." + port.getName().str() + ".wire"); + Value wire = w; + + // migrate the renaming process + auto *op = wire.getDefiningOp(); + auto &annos = port.annotations; + if (op) { + for (auto const &attr : annos) { + auto const &dict = attr.getDict(); + if (auto name = dict.get("hw.debug.name")) { + w->setAttr("hw.debug.name", name); + } + } + } // Know that the argument FIRRTL value is equal to this wire, allowing // connects to it to be lowered. @@ -3290,8 +3346,13 @@ LogicalResult FIRRTLLowering::visitStmt(ConnectOp op) { if (!clockVal) return failure(); - addToAlwaysBlock(clockVal, - [&]() { builder.create(destVal, srcVal); }); + addToAlwaysBlock(clockVal, [&]() { + auto assignOp = builder.create(destVal, srcVal); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assignOp->setAttr("hw.debug.name", debugAttr); + } + return assignOp; + }); return success(); } @@ -3307,12 +3368,26 @@ LogicalResult FIRRTLLowering::visitStmt(ConnectOp op) { regResetOp.resetSignal().getType().isa() ? ::ResetType::AsyncReset : ::ResetType::SyncReset, - sv::EventControl::AtPosEdge, resetSignal, - [&]() { builder.create(destVal, srcVal); }); + sv::EventControl::AtPosEdge, resetSignal, [&]() { + auto assignOp = + builder.create(destVal, srcVal); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assignOp->setAttr("hw.debug.name", debugAttr); + } + + return assignOp; + }); return success(); } - builder.create(destVal, srcVal); + auto newAssignOp = builder.create(destVal, srcVal); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + newAssignOp->setAttr("hw.debug.name", debugAttr); + } else if (auto *destOp = op.dest().getDefiningOp()) { + if ((debugAttr = destOp->getAttr("hw.debug.name"))) { + newAssignOp->setAttr("hw.debug.name", debugAttr); + } + } return success(); } @@ -3343,8 +3418,13 @@ LogicalResult FIRRTLLowering::visitStmt(PartialConnectOp op) { if (!clockVal) return failure(); - addToAlwaysBlock(clockVal, - [&]() { builder.create(destVal, srcVal); }); + addToAlwaysBlock(clockVal, [&]() { + auto assignOp = builder.create(destVal, srcVal); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assignOp->setAttr("hw.debug.name", debugAttr); + } + return assignOp; + }); return success(); } @@ -3360,8 +3440,14 @@ LogicalResult FIRRTLLowering::visitStmt(PartialConnectOp op) { ? ::ResetType::AsyncReset : ::ResetType::SyncReset; addToAlwaysBlock(sv::EventControl::AtPosEdge, clockVal, resetStyle, - sv::EventControl::AtPosEdge, resetSignal, - [&]() { builder.create(destVal, srcVal); }); + sv::EventControl::AtPosEdge, resetSignal, [&]() { + auto assignOp = + builder.create(destVal, srcVal); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assignOp->setAttr("hw.debug.name", debugAttr); + } + return assignOp; + }); return success(); } @@ -3390,7 +3476,13 @@ LogicalResult FIRRTLLowering::visitStmt(PartialConnectOp op) { srcVectorType.getElementType()); } }) - .Case([&](auto) { builder.create(dest, src); }) + .Case([&](auto) { + auto assignOp = builder.create(dest, src); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + assignOp->setAttr("hw.debug.name", debugAttr); + } + return assignOp; + }) .Default([&](auto) { llvm_unreachable("must fail before"); }); }; recurse(destVal, srcVal, destType, op.src().getType()); diff --git a/lib/Debug/CMakeLists.txt b/lib/Debug/CMakeLists.txt new file mode 100644 index 000000000000..0d8b4aff7132 --- /dev/null +++ b/lib/Debug/CMakeLists.txt @@ -0,0 +1,14 @@ +add_circt_translation_library(CIRCTDebug + HWDebug.cpp + + ADDITIONAL_HEADER_DIRS + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + CIRCTExportVerilog + ) \ No newline at end of file diff --git a/lib/Debug/HWDebug.cpp b/lib/Debug/HWDebug.cpp new file mode 100644 index 000000000000..d59086ec2975 --- /dev/null +++ b/lib/Debug/HWDebug.cpp @@ -0,0 +1,1021 @@ +#define GET_OP_CLASSES + +#include + +#include "circt/Dialect/Comb/CombVisitors.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/HW/HWVisitors.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/SV/SVVisitors.h" +#include "mlir/IR/Value.h" +#include "mlir/Pass/Pass.h" +#include "llvm/Support/JSON.h" + +mlir::StringRef getSymOpName(mlir::Operation *symOp); +mlir::StringRef getPortVerilogName(mlir::Operation *module, + circt::hw::PortInfo port); + +namespace circt::hw { +mlir::StringAttr getVerilogModuleNameAttr(mlir::Operation *module); +} // namespace circt::hw + +namespace circt::ExportVerilog { +bool isVerilogExpression(Operation *op); +} // namespace circt::ExportVerilog + +namespace circt::debug { + +struct HWDebugScope; +class HWDebugContext; +class HWDebugBuilder; + +void setEntryLocation(HWDebugScope &scope, const mlir::Location &location); + +enum class HWDebugScopeType { None, Assign, Declare, Module, Block }; + +mlir::StringRef toString(HWDebugScopeType type) { + switch (type) { + case HWDebugScopeType::None: + return "none"; + case HWDebugScopeType::Assign: + return "assign"; + case HWDebugScopeType::Declare: + return "decl"; + case HWDebugScopeType::Module: + return "module"; + case HWDebugScopeType::Block: + return "block"; + } + llvm_unreachable("unknown scope type"); +} + +struct HWDebugScope { +public: + explicit HWDebugScope(HWDebugContext &context, mlir::Operation *op) + : context(context), op(op) {} + + llvm::SmallVector scopes; + + mlir::StringRef filename; + uint32_t line = 0; + uint32_t column = 0; + mlir::StringAttr condition; + + HWDebugScope *parent = nullptr; + + HWDebugContext &context; + + mlir::Operation *op; + + // NOLINTNEXTLINE + [[nodiscard]] virtual llvm::json::Value toJSON() const { + auto res = getScopeJSON(type() == HWDebugScopeType::Block); + return res; + } + + [[nodiscard]] virtual HWDebugScopeType type() const { + return scopes.empty() ? HWDebugScopeType::None : HWDebugScopeType::Block; + } + + virtual ~HWDebugScope() = default; + +protected: + // NOLINTNEXTLINE + [[nodiscard]] llvm::json::Object getScopeJSON(bool includeScope) const { + llvm::json::Object res; + auto scopeType = type(); + if (scopeType != HWDebugScopeType::Block && + scopeType != HWDebugScopeType::Module) { + // block and module does not have line number + res["line"] = line; + if (column > 0) { + res["column"] = column; + } + } + + res["type"] = toString(scopeType); + if (includeScope) { + setScope(res); + } + if (type() == HWDebugScopeType::Block && !filename.empty()) { + res["filename"] = filename; + } + if (condition && condition.size() != 0) { + res["condition"] = condition.strref(); + } + return res; + } + + // NOLINTNEXTLINE + void setScope(llvm::json::Object &obj) const { + llvm::json::Array array; + array.reserve(scopes.size()); + for (auto const *scope : scopes) { + if (scope) + array.emplace_back(scope->toJSON()); + } + obj["scope"] = std::move(array); + } +}; + +struct HWDebugLineInfo : HWDebugScope { + enum class LineType { + None = static_cast(HWDebugScopeType::None), + Assign = static_cast(HWDebugScopeType::Assign), + Declare = static_cast(HWDebugScopeType::Declare), + }; + + LineType lineType; + + HWDebugLineInfo(HWDebugContext &context, LineType type, mlir::Operation *op) + : HWDebugScope(context, op), lineType(type) {} + + [[nodiscard]] llvm::json::Value toJSON() const override { + auto res = getScopeJSON(false); + if (condition && condition.size() != 0) { + res["condition"] = condition.strref(); + } + return res; + } + + [[nodiscard]] HWDebugScopeType type() const override { + return static_cast(lineType); + } +}; + +struct HWDebugVarDef { + mlir::StringRef name; + mlir::StringRef value; + // for how it's always RTL value + bool rtl = true; + mlir::StringAttr id; + + HWDebugVarDef(mlir::StringRef name, mlir::StringRef value, bool rtl, + mlir::StringAttr id) + : name(name), value(value), rtl(rtl), id(id) {} + + [[nodiscard]] llvm::json::Value toJSON() const { + if (id) { + return llvm::json::Value(id); + } + return llvm::json::Object({{"name", name}, {"value", value}, {"rtl", rtl}}); + } + + [[nodiscard]] llvm::json::Value toJSONDefinition() const { + return llvm::json::Object( + {{"name", name}, {"value", value}, {"rtl", rtl}, {"id", id.strref()}}); + } +}; + +struct HWModuleInfo : public HWDebugScope { +public: + // module names + mlir::StringRef name; + + mlir::SmallVector variables; + llvm::DenseMap instances; + + explicit HWModuleInfo(HWDebugContext &context, + circt::hw::HWModuleOp *moduleOp) + : HWDebugScope(context, moduleOp->getOperation()) { + // index the port names by value + auto portInfo = moduleOp->getAllPorts(); + for (auto &port : portInfo) { + StringRef n = getPortVerilogName(*moduleOp, port); + mlir::Value value; + if (!port.isOutput()) { + value = moduleOp->getArgument(port.argNum); + portNames[value] = n; + } + // also add to the generator variables + if (port.debugAttr) { + + outputPorts.emplace_back(std::make_unique( + port.debugAttr.strref(), n, true, mlir::StringAttr{})); + variables.emplace_back(outputPorts.back().get()); + } + } + } + + [[nodiscard]] llvm::json::Value toJSON() const override { + auto res = getScopeJSON(true); + res["name"] = name; + + llvm::json::Array vars; + vars.reserve(variables.size()); + for (auto const *varDef : variables) { + vars.emplace_back(varDef->toJSON()); + } + res["variables"] = std::move(vars); + + if (!instances.empty()) { + llvm::json::Array insts; + insts.reserve(instances.size()); + for (auto const &[n, def] : instances) { + insts.emplace_back(llvm::json::Object{{"name", n}, {"module", def}}); + } + res["instances"] = std::move(insts); + } + + return res; + } + + [[nodiscard]] HWDebugScopeType type() const override { + return HWDebugScopeType::Module; + } + + mlir::StringRef getPortName(mlir::Value value) const { + return portNames.lookup(value); + } + +private: + llvm::DenseMap portNames; + llvm::SmallVector> outputPorts; +}; + +struct HWDebugVarDeclareLineInfo : public HWDebugLineInfo { + HWDebugVarDeclareLineInfo(HWDebugContext &context, mlir::Operation *op) + : HWDebugLineInfo(context, LineType::Declare, op) {} + + HWDebugVarDef *variable; + + [[nodiscard]] llvm::json::Value toJSON() const override { + auto res = HWDebugLineInfo::toJSON(); + (*res.getAsObject())["variable"] = variable->toJSON(); + return res; + } +}; + +struct HWDebugVarAssignLineInfo : public HWDebugLineInfo { + // This also encodes mapping information + HWDebugVarAssignLineInfo(HWDebugContext &context, mlir::Operation *op) + : HWDebugLineInfo(context, LineType::Assign, op) {} + + HWDebugVarDef *variable; + + [[nodiscard]] llvm::json::Value toJSON() const override { + auto res = HWDebugLineInfo::toJSON(); + (*res.getAsObject())["variable"] = variable->toJSON(); + return res; + } +}; + +mlir::StringRef findTop(const llvm::SmallVector &modules) { + llvm::SmallDenseSet names; + for (auto const *mod : modules) { + names.insert(mod->name); + } + for (auto const *m : modules) { + for (auto const &[_, name] : m->instances) { + names.erase(name); + } + } + assert(names.size() == 1); + return *names.begin(); +} + +class HWDebugContext { +public: + [[nodiscard]] llvm::json::Value toJSON() const { + llvm::json::Object res{{"generator", "circt"}}; + llvm::json::Array array; + array.reserve(modules.size()); + for (auto const *module : modules) { + array.emplace_back(std::move(module->toJSON())); + } + res["table"] = std::move(array); + // if we have variable reference in the context + if (!vars.empty()) { + array.clear(); + array.reserve(vars.size()); + for (auto const &[_, var] : vars) { + array.emplace_back(std::move(var->toJSONDefinition())); + } + res["variables"] = std::move(array); + } + auto top = findTop(modules); + res["top"] = top; + return res; + } + + template + T *createScope(Args &&...args) { + auto ptr = std::make_unique(std::forward(args)...); + auto *res = ptr.get(); + scopes.emplace_back(std::move(ptr)); + if constexpr (std::is_same::value) { + modules.emplace_back(res); + } + return res; + } + + HWDebugScope *getNormalScope(::mlir::Operation *op) { + auto *ptr = createScope(*this, op); + if (op) + setEntryLocation(*ptr, op->getLoc()); + return ptr; + } + + [[nodiscard]] const llvm::SmallVector &getModules() const { + return modules; + } + +private: + llvm::SmallVector modules; + llvm::SmallVector> scopes; + llvm::DenseMap> vars; + + friend HWDebugBuilder; +}; + +// NOLINTNEXTLINE +mlir::FileLineColLoc getFileLineColLocFromLoc(const mlir::Location &location) { + // if it's a fused location, the first one will be used + if (auto const fileLoc = location.dyn_cast()) { + return fileLoc; + } + if (auto const fusedLoc = location.dyn_cast()) { + auto const &locs = fusedLoc.getLocations(); + for (auto const &loc : locs) { + auto res = getFileLineColLocFromLoc(loc); + if (res) + return res; + } + } + return {}; +} + +void setEntryLocation(HWDebugScope &scope, const mlir::Location &location) { + // need to get the containing module, as well as the line number + // information + auto const fileLoc = getFileLineColLocFromLoc(location); + if (fileLoc) { + auto const line = fileLoc.getLine(); + auto const column = fileLoc.getColumn(); + + scope.line = line; + scope.column = column; + } +} + +class HWDebugBuilder { +public: + HWDebugBuilder(HWDebugContext &context) : context(context) {} + + HWDebugVarDeclareLineInfo *createVarDeclaration(::mlir::Value value) { + auto loc = value.getLoc(); + auto *op = value.getDefiningOp(); + auto *targetOp = getDebugOp(value, op); + if (!targetOp) + return nullptr; + + // need to get the containing module, as well as the line number + // information + auto *info = context.createScope(context, op); + setEntryLocation(*info, loc); + info->variable = createVarDef(targetOp); + return info; + } + + HWDebugVarAssignLineInfo *createAssign(::mlir::Value value, + ::mlir::Operation *op) { + // only create assign if the target has frontend variable + auto *targetOp = getDebugOp(value, op); + if (!targetOp) + return nullptr; + + auto loc = op->getLoc(); + + auto *assign = context.createScope(context, op); + setEntryLocation(*assign, loc); + + assign->variable = createVarDef(targetOp); + + return assign; + } + + HWDebugVarDef *createVarDef(::mlir::Operation *op) { + auto it = context.vars.find(op); + if (it == context.vars.end()) { + // The OP has to have this attr. need to check before calling this + // function + auto frontEndName = + op->getAttr("hw.debug.name").cast().strref(); + auto rtlName = ::getSymOpName(op); + // For now, we use the size of the map as ID + auto id = mlir::StringAttr::get(op->getContext(), + std::to_string(context.vars.size())); + auto var = + std::make_unique(frontEndName, rtlName, true, id); + + it = context.vars.insert(std::make_pair(op, std::move(var))).first; + } + + return it->second.get(); + } + + HWModuleInfo *createModule(circt::hw::HWModuleOp *op) { + auto *info = context.createScope(context, op); + setEntryLocation(*info, op->getLoc()); + return info; + } + + HWDebugScope *createScope(::mlir::Operation *op, + HWDebugScope *parent = nullptr) { + auto *res = context.getNormalScope(op); + if (parent) { + res->parent = parent; + parent->scopes.emplace_back(res); + } + return res; + } + +private: + HWDebugContext &context; + + static mlir::Operation *getDebugOp(mlir::Value value, mlir::Operation *op) { + auto *valueOP = value.getDefiningOp(); + if (valueOP && valueOP->hasAttr("hw.debug.name")) { + return valueOP; + } + if (op && op->hasAttr("hw.debug.name")) { + return op; + } + return nullptr; + } +}; + +// hgdb only supports a subsets of operations, nor does it support sign +// conversion. if the expression is complex we need to insert another pass that +// generate a temp wire that holds the expression and use that in hgdb instead. +// for now this is not an issue since Chisel is already doing this (somewhat) +class DebugExprPrinter + : public circt::comb::CombinationalVisitor, + public circt::hw::TypeOpVisitor, + public circt::sv::Visitor { +public: + explicit DebugExprPrinter(llvm::raw_ostream &os, HWModuleInfo *module) + : os(os), module(module) {} + + void printExpr(mlir::Value value) { + auto *op = value.getDefiningOp(); + if (op) { + auto name = getSymOpName(op); + if (!name.empty()) { + os << name; + return; + } + } + if (op) { + dispatchCombinationalVisitor(op); + } else { + auto ref = module->getPortName(value); + os << ref; + } + } + // comb ops + using CombinationalVisitor::visitComb; + // supported + void visitComb(circt::comb::AddOp op) { visitBinary(op, "+"); } + void visitComb(circt::comb::SubOp op) { visitBinary(op, "-"); } + void visitComb(circt::comb::MulOp op) { + assert(op.getNumOperands() == 2 && "prelowering should handle variadics"); + return visitBinary(op, "*"); + } + void visitComb(circt::comb::DivUOp op) { visitBinary(op, "/"); } + void visitComb(circt::comb::DivSOp op) { visitBinary(op, "/"); } + void visitComb(circt::comb::ModUOp op) { visitBinary(op, "%"); } + void visitComb(circt::comb::ModSOp op) { visitBinary(op, "%"); } + + void visitComb(circt::comb::AndOp op) { visitBinary(op, "&"); } + void visitComb(circt::comb::OrOp op) { visitBinary(op, "|"); } + void visitComb(circt::comb::XorOp op) { + if (op.isBinaryNot()) + visitUnary(op, "~"); + else + visitBinary(op, "^"); + } + + void visitComb(circt::comb::ICmpOp op) { + // this is copied from ExportVerilog.cpp. probably need some + // code refactoring since this is duplicated logic + const char *symop[] = {"==", "!=", "<", "<=", ">", + ">=", "<", "<=", ">", ">="}; + auto pred = static_cast(op.predicate()); + visitBinary(op, symop[pred]); + } + + // type ops + using TypeOpVisitor::visitTypeOp; + // supported + void visitTypeOp(circt::hw::ConstantOp op) { printConstant(op.value()); } + + // SV ops + using Visitor::visitSV; + void visitSV(circt::sv::ReadInOutOp op) { + auto value = op->getOperand(0); + printExpr(value); + } + + // dispatch + void visitInvalidComb(Operation *op) { return dispatchTypeOpVisitor(op); } + void visitInvalidTypeOp(Operation *op) { return dispatchSVVisitor(op); } + + // handle some sv construct + void visitInvalidSV(Operation *op) { + op->emitOpError("Unsupported operation in debug expression"); + abort(); + } + + void visitBinary(mlir::Operation *op, mlir::StringRef opStr) { + // always emit paraphrases + os << '('; + auto left = op->getOperand(0); + printExpr(left); + os << ' ' << opStr << ' '; + auto right = op->getOperand(1); + printExpr(right); + os << ')'; + } + + void visitUnary(mlir::Operation *op, mlir::StringRef opStr) { + // always emit paraphrases + os << '('; + os << opStr; + auto target = op->getOperand(0); + printExpr(target); + os << ')'; + } + +private: + llvm::raw_ostream &os; + HWModuleInfo *module; + + void printConstant(const llvm::APInt &value) { + bool isNegated = false; + if (value.isNegative() && !value.isMinSignedValue()) { + os << '-'; + isNegated = true; + } + SmallString<32> valueStr; + if (isNegated) { + (-value).toStringUnsigned(valueStr, 10); + } else { + value.toStringUnsigned(valueStr, 10); + } + os << valueStr; + } +}; + +class DebugStmtVisitor : public circt::hw::StmtVisitor, + public circt::sv::Visitor { +public: + DebugStmtVisitor(HWDebugBuilder &builder, HWModuleInfo *module) + : builder(builder), module(module), currentScope(module) {} + + using StmtVisitor::visitStmt; + + void visitStmt(circt::hw::InstanceOp op) { + auto instNameRef = ::getSymOpName(op); + // need to find definition names + auto *mod = op.getReferencedModule(nullptr); + auto moduleNameStr = circt::hw::getVerilogModuleNameAttr(mod).strref(); + module->instances.insert(std::make_pair(instNameRef, moduleNameStr)); + } + + using Visitor::visitSV; + + void visitSV(circt::sv::RegOp op) { + // we treat this as a generator variable + // only generate if we have annotated in the frontend + if (hasDebug(op)) { + auto *var = builder.createVarDef(op); + module->variables.emplace_back(var); + + if (currentScope) { + auto *varDecl = builder.createVarDeclaration(op); + currentScope->scopes.emplace_back(varDecl); + } + } + } + + void visitSV(circt::sv::WireOp op) { + if (hasDebug(op)) { + auto *var = builder.createVarDef(op); + module->variables.emplace_back(var); + + if (currentScope) { + auto *varDecl = builder.createVarDeclaration(op); + currentScope->scopes.emplace_back(varDecl); + } + } + } + + // assignment + // we only care about the target of the assignment + void visitSV(circt::sv::AssignOp op) { + if (hasDebug(op)) { + handleAssign(op.dest(), op.src(), op.src().getDefiningOp(), op); + } + } + + void visitSV(circt::sv::BPAssignOp op) { + if (hasDebug(op)) { + handleAssign(op.dest(), op.src(), op.src().getDefiningOp(), op); + } + } + + void visitSV(circt::sv::PAssignOp op) { + if (hasDebug(op)) { + handleAssign(op.dest(), op.src(), op.src().getDefiningOp(), op); + } + } + + void visitStmt(circt::hw::OutputOp op) { + hw::HWModuleOp parent = op->getParentOfType(); + for (auto i = 0u; i < parent.getPorts().outputs.size(); i++) { + auto operand = op.getOperand(i); + auto *assign = builder.createAssign(operand, op); + if (assign) + currentScope->scopes.emplace_back(assign); + } + } + + // visit blocks + void visitSV(circt::sv::AlwaysOp op) { + // creating a scope + auto *scope = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = scope; + visitBlock(*op.getBodyBlock()); + currentScope = temp; + } + + void visitSV(circt::sv::AlwaysCombOp op) { // creating a scope + auto *scope = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = scope; + visitBlock(*op.getBodyBlock()); + currentScope = temp; + } + + void visitSV(circt::sv::AlwaysFFOp op) { + if (op.getResetBlock()) { + // creating a scope + auto *scope = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = scope; + visitBlock(*op.getResetBlock()); + currentScope = temp; + } + { + // creating a scope + auto *scope = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = scope; + visitBlock(*op.getBodyBlock()); + currentScope = temp; + } + } + + void visitSV(circt::sv::InitialOp op) { // creating a scope + auto *scope = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = scope; + visitBlock(*op.getBodyBlock()); + currentScope = temp; + } + + void visitSV(circt::sv::IfOp op) { + // first, the statement itself is a line + auto cond = getCondString(op.cond()); + if (cond.empty()) { + op.cond().getDefiningOp()->emitError( + "Unsupported if statement condition"); + cond = getCondString(op.cond()); + return; + } + builder.createScope(op, currentScope); + if (auto *body = op.getThenBlock()) { + // true + if (!body->empty()) { + auto *trueBlock = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = trueBlock; + trueBlock->condition = mlir::StringAttr::get(op.getContext(), cond); + visitBlock(*body); + currentScope = temp; + } + } + if (op.hasElse()) { + auto *elseBody = op.getElseBlock(); + // false + if (!elseBody->empty()) { + auto *elseBlock = builder.createScope(op, currentScope); + auto *temp = currentScope; + currentScope = elseBlock; + elseBlock->condition = + mlir::StringAttr::get(op->getContext(), "!" + cond); + visitBlock(*elseBody); + currentScope = temp; + } + } + } + + void visitSV(circt::sv::CaseZOp op) { + auto cond = getCondString(op.cond()); + if (cond.empty()) { + op->emitError("Unsupported case statement condition"); + return; + } + + auto addScope = [op, this](const std::string caseCond, mlir::Block *block) { + // use nullptr since it's auxiliary + auto *scope = builder.createScope(nullptr, currentScope); + auto *temp = currentScope; + currentScope = scope; + scope->condition = StringAttr::get(op->getContext(), caseCond); + visitBlock(*block); + currentScope = temp; + }; + + mlir::Block *defaultBlock = nullptr; + llvm::SmallVector values; + for (auto caseInfo : op.getCases()) { + auto pattern = caseInfo.pattern; + if (pattern.isDefault()) { + defaultBlock = caseInfo.block; + } else { + // Currently, hgdb doesn't support z or ? + // so we have to turn the pattern into an integer + auto value = pattern.attr.getUInt(); + auto caseCond = cond + " == " + std::to_string(value); + addScope(caseCond, caseInfo.block); + values.emplace_back(value); + } + } + if (defaultBlock) { + // negate all values + std::string defaultCond; + for (auto i = 0u; i < values.size(); i++) { + defaultCond.append("(" + cond + " != " + std::to_string(values[i]) + + ")"); + if (i != (values.size() - 1)) { + defaultCond.append(" && "); + } + } + addScope(defaultCond, defaultBlock); + } + } + + // ignore invalid stuff + void visitInvalidStmt(Operation *op) { dispatchSVVisitor(op); } + void visitInvalidSV(Operation *) {} + void visitUnhandledStmt(Operation *) {} + void visitUnhandledSV(Operation *) {} + + void dispatch(mlir::Operation *op) { dispatchStmtVisitor(op); } + + void visitBlock(mlir::Block &block) { + for (auto &op : block) { + dispatch(&op); + } + } + +private: + HWDebugBuilder &builder; + HWModuleInfo *module; + HWDebugScope *currentScope; + + bool hasDebug(mlir::Operation *op) { + auto r = op && op->hasAttr("hw.debug.name"); + return r; + } + + std::string getCondString(mlir::Value value) const { + std::string cond; + llvm::raw_string_ostream os(cond); + DebugExprPrinter p(os, module); + p.printExpr(value); + return cond; + } + + // NOLINTNEXTLINE + void handleAssign(mlir::Value target, mlir::Value value, mlir::Operation *op, + mlir::Operation *assignOp) { + bool handled = false; + if (op) { + mlir::TypeSwitch(op).Case( + [&](circt::comb::MuxOp mux) { + // create a new scope, which can be merged later on + auto *temp = currentScope; + // true + { + auto *scope = + builder.createScope(mux.getOperation(), currentScope); + auto cond = getCondString(mux.cond()); + if (cond.empty()) { + mux->emitError("Unable to obtain mux condition expression"); + } + scope->condition = StringAttr::get(op->getContext(), cond); + currentScope = scope; + handleAssign(target, mux.trueValue(), + mux.trueValue().getDefiningOp(), assignOp); + } + currentScope = temp; + + { + auto *scope = + builder.createScope(mux.getOperation(), currentScope); + auto cond = getCondString(mux.cond()); + if (cond.empty()) { + mux->emitError("Unable to obtain mux condition expression"); + } + scope->condition = + mlir::StringAttr::get(op->getContext(), "!" + cond); + currentScope = scope; + handleAssign(target, mux.falseValue(), + mux.falseValue().getDefiningOp(), assignOp); + } + currentScope = temp; + handled = true; + }); + } + // not handled yet, create an assignment + if (!handled) { + auto *assign = builder.createAssign(target, op ? op : assignOp); + if (assign) + currentScope->scopes.emplace_back(assign); + } + } +}; + +mlir::StringAttr getFilenameFromScopeOp(HWDebugScope *scope) { + // if it's fused location, the + auto loc = getFileLineColLocFromLoc(scope->op->getLoc()); + return loc ? loc.getFilename() : mlir::StringAttr{}; +} + +// NOLINTNEXTLINE +void setScopeFilename(HWDebugScope *scope, HWDebugBuilder &builder) { + // assuming the current scope is already fixed + auto scopeFilename = getFilenameFromScopeOp(scope); + for (auto i = 0u; i < scope->scopes.size(); i++) { + auto *entry = scope->scopes[i]; + auto entryFilename = getFilenameFromScopeOp(entry); + // unable to determine this scope's filename + if (!entryFilename) { + // delete it from the scope + scope->scopes[i] = nullptr; + continue; + } + if (entryFilename != scopeFilename || + scope->type() != HWDebugScopeType::Block) { + // need to set this entry's filename + if (entry->type() == HWDebugScopeType::Block) { + // set its filename + entry->filename = entryFilename; + } else { + // need to create a scope to contain this one + auto *newScope = builder.createScope(entry->op); + // set the line to 0 since it's an artificial scope + newScope->line = 0; + newScope->filename = entryFilename; + newScope->scopes.emplace_back(entry); + entry->parent = newScope; + newScope->parent = scope; + scope->scopes[i] = newScope; + } + } + } + // merge scopes with the same filename + // we assume at this stage most of the entries are block entry now + // we can only merge + llvm::DenseMap, HWDebugScope *> + filenameMapping; + for (auto i = 0u; i < scope->scopes.size(); i++) { + auto *entry = scope->scopes[i]; + if (!entry) + continue; + if (entry->type() == HWDebugScopeType::Block) { + auto filename = entry->filename; + auto cond = entry->condition; + auto keyEntry = std::make_pair(filename, cond); + if (filenameMapping.find(keyEntry) == filenameMapping.end()) { + filenameMapping[keyEntry] = entry; + } else { + auto *parent = filenameMapping[keyEntry]; + // merge + parent->scopes.reserve(entry->scopes.size() + parent->scopes.size()); + for (auto *p : entry->scopes) { + parent->scopes.emplace_back(p); + p->parent = p; + } + + // delete the old entry + scope->scopes[i] = nullptr; + } + } + } + // clear the nullptr + scope->scopes.erase(std::remove_if(scope->scopes.begin(), scope->scopes.end(), + [](auto *p) { return !p; }), + scope->scopes.end()); + + // recursively setting filenames + for (auto *entry : scope->scopes) { + setScopeFilename(entry, builder); + } +} + +void exportDebugTable(mlir::ModuleOp moduleOp, const std::string &filename) { + // collect all the files + HWDebugContext context; + HWDebugBuilder builder(context); + for (auto &op : *moduleOp.getBody()) { + mlir::TypeSwitch(&op).Case( + [&builder](circt::hw::HWModuleOp mod) { + // get verilog name + auto defName = circt::hw::getVerilogModuleNameAttr(mod); + auto *module = builder.createModule(&mod); + module->name = defName; + DebugStmtVisitor visitor(builder, module); + auto *body = mod.getBodyBlock(); + visitor.visitBlock(*body); + }); + } + // Fixing filenames and other scope ordering + auto const &modules = context.getModules(); + for (auto *m : modules) { + setScopeFilename(m, builder); + } + auto json = context.toJSON(); + + std::error_code error; + llvm::raw_fd_ostream os(filename, error); + if (!error) { + os << json; + } + os.close(); +} + +struct ExportDebugTablePass : public ::mlir::OperationPass { + ExportDebugTablePass(std::string filename) + : ::mlir::OperationPass( + ::mlir::TypeID::get()), + filename(filename) {} + ExportDebugTablePass(const ExportDebugTablePass &other) + : ::mlir::OperationPass(other), filename(other.filename) { + } + + void runOnOperation() override { + exportDebugTable(getOperation(), filename); + markAllAnalysesPreserved(); + } + + /// Returns the command-line argument attached to this pass. + static constexpr ::llvm::StringLiteral getArgumentName() { + return ::llvm::StringLiteral("export-hgdb"); + } + ::llvm::StringRef getArgument() const override { return "export-hgdb"; } + + ::llvm::StringRef getDescription() const override { + return "Generate symbol table for HGDB"; + } + + /// Returns the derived pass name. + static constexpr ::llvm::StringLiteral getPassName() { + return ::llvm::StringLiteral("ExportHGDB"); + } + ::llvm::StringRef getName() const override { return "ExportHGDB"; } + + /// Support isa/dyn_cast functionality for the derived pass class. + static bool classof(const ::mlir::Pass *pass) { + return pass->getTypeID() == ::mlir::TypeID::get(); + } + + /// A clone method to create a copy of this pass. + std::unique_ptr<::mlir::Pass> clonePass() const override { + return std::make_unique( + *static_cast(this)); + } + + /// Return the dialect that must be loaded in the context before this pass. + void getDependentDialects(::mlir::DialectRegistry ®istry) const override { + + registry.insert(); + + registry.insert(); + + registry.insert(); + } + +private: + std::string filename; +}; + +std::unique_ptr createExportHGDBPass(std::string filename) { + return std::make_unique(std::move(filename)); +} + +} // namespace circt::debug \ No newline at end of file diff --git a/lib/Dialect/FIRRTL/Transforms/ExpandWhens.cpp b/lib/Dialect/FIRRTL/Transforms/ExpandWhens.cpp index cbace9a4c6b8..7f1207770ef8 100644 --- a/lib/Dialect/FIRRTL/Transforms/ExpandWhens.cpp +++ b/lib/Dialect/FIRRTL/Transforms/ExpandWhens.cpp @@ -203,7 +203,16 @@ class LastConnectResolver : public FIRRTLVisitor { newValue = b.createOrFold(loc, cond, whenTrue, whenFalse); else if (trueIsInvalid) newValue = whenFalse; - return b.create(loc, dest, newValue); + auto connectOp = b.create(loc, dest, newValue); + + if (auto debugAttr = whenTrueConn->getAttr("hw.debug.name")) { + connectOp->setAttr("hw.debug.name", debugAttr); + } else if (whenFalseConn && whenFalseConn->hasAttr("hw.debug.name")) { + connectOp->setAttr("hw.debug.name", + whenFalseConn->getAttr("hw.debug.name")); + } + + return connectOp; } void visitDecl(WireOp op) { declareSinks(op.result(), Flow::Duplex); } @@ -247,6 +256,9 @@ class LastConnectResolver : public FIRRTLVisitor { auto builder = OpBuilder(op->getBlock(), ++Block::iterator(op)); auto fn = [&](Value value) { auto connect = builder.create(value.getLoc(), value, value); + if (auto debugAttr = op->getAttr("hw.debug.name")) { + connect->setAttr("hw.debug.name", debugAttr); + } driverMap[getFieldRefFromValue(value)] = connect; }; foreachSubelement(builder, op.result(), fn); diff --git a/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp b/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp index c802b2be8f4a..f1780d12503a 100644 --- a/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp +++ b/lib/Dialect/FIRRTL/Transforms/LowerTypes.cpp @@ -295,11 +295,12 @@ struct TypeLoweringVisitor : public FIRRTLVisitor { TypeLoweringVisitor(MLIRContext *context, bool flattenAggregateMemData, bool preserveAggregate, bool preservePublicTypes, - SymbolTable &symTbl, const AttrCache &cache) + SymbolTable &symTbl, const AttrCache &cache, + bool insertDebugInfo) : context(context), flattenAggregateMemData(flattenAggregateMemData), preserveAggregate(preserveAggregate), - preservePublicTypes(preservePublicTypes), symTbl(symTbl), cache(cache) { - } + preservePublicTypes(preservePublicTypes), symTbl(symTbl), cache(cache), + insertDebugInfo(insertDebugInfo) {} using FIRRTLVisitor::visitDecl; using FIRRTLVisitor::visitExpr; using FIRRTLVisitor::visitStmt; @@ -401,6 +402,9 @@ struct TypeLoweringVisitor : public FIRRTLVisitor { // Cache some attributes const AttrCache &cache; + + // Inserts debug information to keep track of name changes + bool insertDebugInfo; }; } // namespace @@ -539,8 +543,18 @@ bool TypeLoweringVisitor::lowerProducer( auto srcType = op->getResult(0).getType().cast(); SmallVector fieldTypes; - if (!peelType(srcType, fieldTypes, preserveAggregate)) + if (!peelType(srcType, fieldTypes, preserveAggregate)) { + if (insertDebugInfo && !op->hasAttr("hw.debug.name")) { + // If it's not a temp nodes produced by Chisel. For now, we use a + // naming heuristics: all temp nodes are prefixed with _. However, in + // case where users create such node, this hack will fail + if (auto nameAttr = op->getAttrOfType("name")) { + if (nameAttr.size() > 0 && nameAttr.data()[0] != '_') + op->setAttr("hw.debug.name", nameAttr); + } + } return false; + } SmallVector lowered; // Loop over the leaf aggregates. @@ -555,11 +569,22 @@ bool TypeLoweringVisitor::lowerProducer( loweredSymName = loweredName; if (loweredSymName.empty()) loweredSymName = uniqueName(); + + auto baseName = loweredName; auto baseNameLen = loweredName.size(); auto baseSymNameLen = loweredSymName.size(); auto oldAnno = op->getAttr("annotations").dyn_cast_or_null(); - for (auto field : fieldTypes) { + bool isArray = srcType.isa(); + + for (auto fieldIdx = 0u; fieldIdx < fieldTypes.size(); fieldIdx++) { + auto field = fieldTypes[fieldIdx]; + // if it's an array, need to add array idx + SmallString<16> targetName = baseName; + if (isArray) { + targetName.append("."); + targetName.append(std::to_string(fieldIdx)); + } if (!loweredName.empty()) { loweredName.resize(baseNameLen); loweredName += field.suffix; @@ -584,6 +609,10 @@ bool TypeLoweringVisitor::lowerProducer( StringAttr::get(context, loweredSymName)); assert(!loweredSymName.empty()); } + + if (insertDebugInfo) + newOp->setAttr("hw.debug.name", StringAttr::get(context, targetName)); + lowered.push_back(newOp->getResult(0)); } @@ -666,12 +695,36 @@ bool TypeLoweringVisitor::lowerArg(Operation *module, size_t argIndex, SmallVector fieldTypes; auto srcType = newArgs[argIndex].type.cast(); if (!peelType(srcType, fieldTypes, - isModuleAllowedToPreserveAggregate(module))) + isModuleAllowedToPreserveAggregate(module))) { + // Use its default name instead + if (insertDebugInfo) { + auto attrs = mlir::DictionaryAttr::get( + context, {{StringAttr::get(context, "hw.debug.name"), + newArgs[argIndex].name}}); + newArgs[argIndex].annotations.addAnnotations(attrs); + } + return false; + } + + // Get original arg name + auto originalName = newArgs[argIndex].name; for (auto field : llvm::enumerate(fieldTypes)) { auto newValue = addArg(module, 1 + argIndex + field.index(), srcType, field.value(), newArgs[argIndex]); + // Insert renaming attributes to keep track of the naming changes + // We store the original name as argName.subFieldName. subFieldName can + // be either a name or a number + if (insertDebugInfo) { + auto attrs = mlir::DictionaryAttr::get( + context, + {{StringAttr::get(context, "hw.debug.name"), + StringAttr::get(context, originalName.str() + "." + + field.value().suffix.substr(1))}}); + + newValue.second.annotations.addAnnotations(attrs); + } newArgs.insert(newArgs.begin() + 1 + argIndex + field.index(), newValue.second); // Lower any other arguments by copying them to keep the relative order. @@ -727,8 +780,39 @@ bool TypeLoweringVisitor::visitStmt(ConnectOp op) { // We have to expand connections even if the aggregate preservation is true. if (!peelType(op.dest().getType(), fields, - /* allowedToPreserveAggregate */ false)) + /* allowedToPreserveAggregate */ false)) { + // Store the name as well if enabled + if (!insertDebugInfo) + return false; + auto dest = op.dest(); + if (auto *destOp = dest.getDefiningOp()) { + if (auto nameAttr = destOp->getAttr("name")) { + if (auto instOp = dest.getDefiningOp()) { + // this is an instance connection, need to treat differently + // since the defining OP would be the instance and therefore the + // naming would be wrong + auto const &results = instOp.results(); + for (auto const &res : results) { + if (res == dest) { + // Need to obtain the name. a little hacky, + // but it's the best way I know of + auto resultNo = res.getResultNumber(); + auto portName = instOp.getPortName(resultNo); + auto nameStr = nameAttr.cast().str() + "_" + + portName.str(); + op->setAttr("hw.debug.name", + mlir::StringAttr::get(context, nameStr)); + break; + } + } + } else { + op->setAttr("hw.debug.name", nameAttr); + } + } + } + return false; + } // Loop over the leaf aggregates. for (auto field : llvm::enumerate(fields)) { @@ -1108,6 +1192,8 @@ bool TypeLoweringVisitor::visitDecl(FExtModuleOp extModule) { // Update the module's attributes. extModule->setAttrs(newModuleAttrs); + + AnnotationSet set(extModule); return false; } @@ -1178,6 +1264,7 @@ bool TypeLoweringVisitor::visitDecl(FModuleOp module) { // Update the module's attributes. module->setAttrs(newModuleAttrs); + return false; } @@ -1186,7 +1273,13 @@ bool TypeLoweringVisitor::visitDecl(WireOp op) { auto clone = [&](FlatBundleFieldEntry field, ArrayAttr attrs) -> Operation * { return builder->create(field.type, "", attrs, StringAttr{}); }; - return lowerProducer(op, clone); + auto handled = lowerProducer(op, clone); + if (insertDebugInfo && !handled && !op->hasAttr("hw.debug.name")) { + if (auto nameAttr = op->getAttr("name")) { + op->setAttr("hw.debug.name", nameAttr); + } + } + return handled; } /// Lower a reg op with a bundle to multiple non-bundled regs. @@ -1206,7 +1299,14 @@ bool TypeLoweringVisitor::visitDecl(RegResetOp op) { op.resetSignal(), resetVal, "", attrs, StringAttr{}); }; - return lowerProducer(op, clone); + auto handled = lowerProducer(op, clone); + if (insertDebugInfo && !handled) { + // not a bundle type. op not changed + if (auto name = op->getAttr("name")) { + op->setAttr("hw.debug.name", name); + } + } + return handled; } /// Lower a wire op with a bundle to multiple non-bundled wires. @@ -1437,10 +1537,11 @@ bool TypeLoweringVisitor::visitExpr(MultibitMuxOp op) { namespace { struct LowerTypesPass : public LowerFIRRTLTypesBase { LowerTypesPass(bool flattenAggregateMemDataFlag, bool preserveAggregateFlag, - bool preservePublicTypesFlag) { + bool preservePublicTypesFlag, bool insertDebugInfoFlag) { flattenAggregateMemData = flattenAggregateMemDataFlag; preserveAggregate = preserveAggregateFlag; preservePublicTypes = preservePublicTypesFlag; + insertDebugInfo = insertDebugInfoFlag; } void runOnOperation() override; }; @@ -1474,7 +1575,7 @@ void LowerTypesPass::runOnOperation() { auto lowerModules = [&](auto op) { auto tl = TypeLoweringVisitor(&getContext(), flattenAggregateMemData, preserveAggregate, preservePublicTypes, - symTbl, cache); + symTbl, cache, insertDebugInfo); tl.lowerModule(op); std::lock_guard lg(nlaAppendLock); nlaToNewSymList.append(tl.getNLAs()); @@ -1552,8 +1653,9 @@ void LowerTypesPass::runOnOperation() { /// This is the pass constructor. std::unique_ptr circt::firrtl::createLowerFIRRTLTypesPass( - bool replSeqMem, bool preserveAggregate, bool preservePublicTypes) { + bool replSeqMem, bool preserveAggregate, bool preservePublicTypes, + bool insertDebugInfo) { return std::make_unique(replSeqMem, preserveAggregate, - preservePublicTypes); + preservePublicTypes, insertDebugInfo); } diff --git a/lib/Dialect/HW/HWOps.cpp b/lib/Dialect/HW/HWOps.cpp index 9971ae10ffa9..9980305f444a 100644 --- a/lib/Dialect/HW/HWOps.cpp +++ b/lib/Dialect/HW/HWOps.cpp @@ -359,8 +359,9 @@ static void buildModule(OpBuilder &builder, OperationState &result, SmallVector argNames, resultNames; SmallVector argTypes, resultTypes; - SmallVector argAttrs, resultAttrs; + SmallVector argAttrs, resultAttrs, debugAttrs; auto exportPortIdent = StringAttr::get(builder.getContext(), "hw.exportPort"); + bool hasDebugAttr = false; for (auto elt : ports.inputs) { if (elt.direction == PortDirection::INOUT && !elt.type.isa()) @@ -374,6 +375,9 @@ static void buildModule(OpBuilder &builder, OperationState &result, else attr = builder.getDictionaryAttr({}); argAttrs.push_back(attr); + debugAttrs.push_back(elt.debugAttr); + if (elt.debugAttr) + hasDebugAttr = true; } for (auto elt : ports.outputs) { @@ -386,6 +390,9 @@ static void buildModule(OpBuilder &builder, OperationState &result, else attr = builder.getDictionaryAttr({}); resultAttrs.push_back(attr); + debugAttrs.push_back(elt.debugAttr); + if (elt.debugAttr) + hasDebugAttr = true; } // Allow clients to pass in null for the parameters list. @@ -406,6 +413,8 @@ static void buildModule(OpBuilder &builder, OperationState &result, comment = builder.getStringAttr(""); result.addAttribute("comment", comment); result.addAttributes(attributes); + if (hasDebugAttr && !debugAttrs.empty()) + result.addAttribute("hw.debug.name", builder.getArrayAttr(debugAttrs)); result.addRegion(); } @@ -537,6 +546,7 @@ SmallVector hw::getAllModulePortInfos(Operation *op) { SmallVector results; auto argTypes = getModuleType(op).getInputs(); auto argNames = op->getAttrOfType("argNames"); + auto debugAttrs = op->getAttrOfType("hw.debug.name"); for (unsigned i = 0, e = argTypes.size(); i < e; ++i) { bool isInOut = false; auto type = argTypes[i]; @@ -547,15 +557,21 @@ SmallVector hw::getAllModulePortInfos(Operation *op) { } auto direction = isInOut ? PortDirection::INOUT : PortDirection::INPUT; - results.push_back( - {argNames[i].cast(), direction, type, i, getArgSym(op, i)}); + auto debugAttr = debugAttrs && debugAttrs[i] + ? debugAttrs[i].cast() + : StringAttr{}; + results.push_back({argNames[i].cast(), direction, type, i, + getArgSym(op, i), debugAttr}); } auto resultNames = op->getAttrOfType("resultNames"); auto resultTypes = getModuleType(op).getResults(); for (unsigned i = 0, e = resultTypes.size(); i < e; ++i) { + auto debugAttr = debugAttrs && debugAttrs[i + argTypes.size()] + ? debugAttrs[i + argTypes.size()].cast() + : StringAttr{}; results.push_back({resultNames[i].cast(), PortDirection::OUTPUT, - resultTypes[i], i, getResultSym(op, i)}); + resultTypes[i], i, getResultSym(op, i), debugAttr}); } return results; } diff --git a/tools/firtool/CMakeLists.txt b/tools/firtool/CMakeLists.txt index cd97e86bac03..5433921816f6 100644 --- a/tools/firtool/CMakeLists.txt +++ b/tools/firtool/CMakeLists.txt @@ -12,6 +12,7 @@ target_link_libraries(firtool PRIVATE CIRCTFIRRTLToHW CIRCTFIRRTLTransforms CIRCTSVTransforms + CIRCTDebug MLIRParser MLIRSupport diff --git a/tools/firtool/firtool.cpp b/tools/firtool/firtool.cpp index 407829fdb1e9..b0fab6c281a8 100644 --- a/tools/firtool/firtool.cpp +++ b/tools/firtool/firtool.cpp @@ -13,6 +13,7 @@ #include "circt/Conversion/ExportVerilog.h" #include "circt/Conversion/Passes.h" +#include "circt/Debug/HWDebug.h" #include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/FIRRTL/CHIRRTLDialect.h" #include "circt/Dialect/FIRRTL/FIRParser.h" @@ -232,6 +233,11 @@ static cl::opt cl::desc("enable infer read write ports for memory"), cl::init(true)); +// generate hgdb symbol table +static cl::opt + hgdbDebugFile("hgdb", cl::desc("file name for hgdb debugger file"), + cl::init("")); + enum OutputFormatKind { OutputParseOnly, OutputIRFir, @@ -437,7 +443,8 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr, // things up. if (lowerTypes) { pm.addNestedPass(firrtl::createLowerFIRRTLTypesPass( - replSeqMem, preserveAggregate, preservePublicTypes)); + replSeqMem, preserveAggregate, preservePublicTypes, + !hgdbDebugFile.empty())); // Only enable expand whens if lower types is also enabled. if (expandWhens) { auto &modulePM = pm.nest().nest(); @@ -559,6 +566,11 @@ processBuffer(MLIRContext &context, TimingScope &ts, llvm::SourceMgr &sourceMgr, // pick up any changes that verilog emission made. if (exportModuleHierarchy) pm.addPass(sv::createHWExportModuleHierarchyPass(outputFilename)); + + // If specified, output HGDB debug table as well + if (!hgdbDebugFile.empty()) { + pm.addPass(circt::debug::createExportHGDBPass(hgdbDebugFile)); + } } // Load the emitter options from the command line. Command line options if