diff --git a/docs/Dialects/Debug.md b/docs/Dialects/Debug.md index 4d9dc37d2e..cf461ec55b 100644 --- a/docs/Dialects/Debug.md +++ b/docs/Dialects/Debug.md @@ -140,6 +140,45 @@ still expose the constant values under the name `Depth` and `Width` in the debug info. +## Tracking Inlined Modules + +The `dbg.scope` op can be used to track debug information about inlined modules. +By default, operations such as `hw.module` in conjunction with `hw.instance` +introduce an implicit module scope. All debug operations within a module are +added to that implicit scope, unless they have an explicit `scope` operand. This +explicit scope operand can be used to group the DI of an inlined module. +Consider the following modules: + +``` +hw.module @Foo(in %a: i42) { + dbg.variable "a", %a : i42 + hw.instance "bar" @Bar(x: %a: i42) +} +hw.module @Bar(in %x: i42) { + dbg.variable "x", %x : i42 + %0 = comb.mul %x, %x : i42 + dbg.variable "squared", %0 : i42 +} +``` + +If we inline module `Bar`, we can introduce a `dbg.scope` operation to represent +the original instance, and group all debug variables in `Bar` under this +explicit scope: + +``` +hw.module @Foo(in %a: i42) { + dbg.variable "a", %a : i42 + %0 = dbg.scope "bar", "Bar" + dbg.variable "x", %a scope %0 : i42 + %1 = comb.mul %a, %a : i42 + dbg.variable "squared", %1 scope %0 : i42 +} +``` + +Despite the fact that the instance op no longer exists, the explicit `dbg.scope` +op models the additional levle of hierarchy that used to exist in the input. + + ## Types diff --git a/include/circt/Analysis/DebugInfo.h b/include/circt/Analysis/DebugInfo.h index 141df0f4a5..1eb1fc3526 100644 --- a/include/circt/Analysis/DebugInfo.h +++ b/include/circt/Analysis/DebugInfo.h @@ -34,6 +34,8 @@ struct DIModule { SmallVector variables; /// If this is an extern declaration. bool isExtern = false; + /// If this is an inline scope created by a `dbg.scope` operation. + bool isInline = false; }; struct DIInstance { diff --git a/include/circt/Dialect/Debug/DebugOps.td b/include/circt/Dialect/Debug/DebugOps.td index 712d6f53ab..99a5c6cb3b 100644 --- a/include/circt/Dialect/Debug/DebugOps.td +++ b/include/circt/Dialect/Debug/DebugOps.td @@ -19,6 +19,37 @@ class DebugOp traits = []> : Op; +def ScopeOp : DebugOp<"scope"> { + let summary = "Define a scope for debug values"; + let description = [{ + Creates an additional level of hierarchy in the DI, a "scope", which can be + used to group variables and other scopes. + + Operations such as `hw.module` introduce an implicit scope. All debug + operations within a module are added to that implicit scope, unless they + have an explicit `scope` operand. Providing an explicit scope can be used to + represent inlined modules. + + Scopes in DI do not necessarily have to correspond to levels of a module + hierarchy. They can also be used to model things like control flow scopes, + call stacks, and other source-language concepts. + + The `scope` operand of any debug dialect operation must be defined locally + by a `dbg.scope` operation. It cannot be a block argument. (This is intended + as a temporary restriction, to be lifted in the future.) + }]; + let arguments = (ins + StrAttr:$instanceName, + StrAttr:$moduleName, + Optional:$scope + ); + let results = (outs ScopeType:$result); + let assemblyFormat = [{ + $instanceName `,` $moduleName (`scope` $scope^)? attr-dict + }]; +} + + def VariableOp : DebugOp<"variable"> { let summary = "A named value to be captured in debug info"; let description = [{ @@ -34,10 +65,17 @@ def VariableOp : DebugOp<"variable"> { these source language values can be reconstituted from the actual IR values present at the end of compilation. - See the rationale for examples and details. + See the rationale for examples and details. See the `dbg.scope` operation + for additional details on how to use the `scope` operand. + }]; + let arguments = (ins + StrAttr:$name, + AnyType:$value, + Optional:$scope + ); + let assemblyFormat = [{ + $name `,` $value (`scope` $scope^)? attr-dict `:` type($value) }]; - let arguments = (ins StrAttr:$name, AnyType:$value); - let assemblyFormat = [{ $name `,` $value attr-dict `:` type($value) }]; } diff --git a/include/circt/Dialect/Debug/DebugTypes.td b/include/circt/Dialect/Debug/DebugTypes.td index 37faa171a3..48c99f2eb8 100644 --- a/include/circt/Dialect/Debug/DebugTypes.td +++ b/include/circt/Dialect/Debug/DebugTypes.td @@ -14,6 +14,12 @@ include "mlir/IR/AttrTypeBase.td" class DebugTypeDef : TypeDef { } +def ScopeType : DebugTypeDef<"Scope"> { + let mnemonic = "scope"; + let summary = "debug scope"; + let description = "The result of a `dbg.scope` operation."; +} + def StructType : DebugTypeDef<"Struct"> { let mnemonic = "struct"; let summary = "debug struct aggregate"; diff --git a/lib/Analysis/DebugInfo.cpp b/lib/Analysis/DebugInfo.cpp index c69fa5b83f..66877d7955 100644 --- a/lib/Analysis/DebugInfo.cpp +++ b/lib/Analysis/DebugInfo.cpp @@ -88,14 +88,34 @@ void DebugInfoBuilder::visitModule(hw::HWModuleOp moduleOp, DIModule &module) { // return. Otherwise collect ports, instances, and variables as a // fallback. - // Check what kind of DI is present in the module. + // Check what kind of DI is present in the module. Also create additional + // `DIModule` hierarchy levels for each explicit scope op in the module. + SmallDenseMap scopes; bool hasVariables = false; bool hasInstances = false; moduleOp.walk([&](Operation *op) { if (isa(op)) hasVariables = true; + if (auto scopeOp = dyn_cast(op)) { + auto *node = createModule(); + node->isInline = true; + node->name = scopeOp.getModuleNameAttr(); + node->op = scopeOp; + scopes.insert({scopeOp, node}); + } }); + // Helper function to resolve a `scope` operand on a variable to the + // `DIModule` into which the variable should be collected. If the `scope` is + // not set, or it isn't a valid `dbg.scope` op, returns the `module` argument + // of this function. + auto getScope = [&](Value scopeValue) -> DIModule & { + if (scopeValue) + if (auto scopeOp = scopeValue.getDefiningOp()) + return *scopes.lookup(scopeOp); + return module; + }; + // If the module has no DI for variables, add variables for each of the ports // as a fallback. if (!hasVariables) { @@ -119,10 +139,18 @@ void DebugInfoBuilder::visitModule(hw::HWModuleOp moduleOp, DIModule &module) { var->name = varOp.getNameAttr(); var->loc = varOp.getLoc(); var->value = varOp.getValue(); - module.variables.push_back(var); + getScope(varOp.getScope()).variables.push_back(var); return; } + if (auto scopeOp = dyn_cast(op)) { + auto *instance = createInstance(); + instance->name = scopeOp.getInstanceNameAttr(); + instance->op = scopeOp; + instance->module = scopes.lookup(scopeOp); + getScope(scopeOp.getScope()).instances.push_back(instance); + } + // Fallback if the module has no DI for its instances. if (!hasInstances) { if (auto instOp = dyn_cast(op)) { diff --git a/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp b/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp index 3c391cf230..26ab68b39a 100644 --- a/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp +++ b/lib/Dialect/FIRRTL/Transforms/MaterializeDebugInfo.cpp @@ -57,7 +57,8 @@ void MaterializeDebugInfoPass::materializeVariable(OpBuilder &builder, if (name.getValue().starts_with("_")) return; if (auto dbgValue = convertToDebugAggregates(builder, value)) - builder.create(value.getLoc(), name, dbgValue); + builder.create(value.getLoc(), name, dbgValue, + /*scope=*/Value{}); } /// Unpack all aggregates in a FIRRTL value and repack them as debug aggregates. diff --git a/lib/Target/DebugInfo/DumpDebugInfo.cpp b/lib/Target/DebugInfo/DumpDebugInfo.cpp index b59026cc0e..fc63c354b8 100644 --- a/lib/Target/DebugInfo/DumpDebugInfo.cpp +++ b/lib/Target/DebugInfo/DumpDebugInfo.cpp @@ -13,6 +13,8 @@ using namespace mlir; using namespace circt; +static void dump(DIModule &module, raw_indented_ostream &os); + static void dump(DIVariable &variable, raw_indented_ostream &os) { os << "Variable " << variable.name; if (variable.loc) @@ -38,6 +40,11 @@ static void dump(DIInstance &instance, raw_indented_ostream &os) { if (instance.op) os << " for " << instance.op->getName() << " at " << instance.op->getLoc(); os << "\n"; + if (instance.module->isInline) { + os.indent(); + dump(*instance.module, os); + os.unindent(); + } } static void dump(DIModule &module, raw_indented_ostream &os) { diff --git a/test/Analysis/debug-info.mlir b/test/Analysis/debug-info.mlir index 20adaf862f..72256ce01a 100644 --- a/test/Analysis/debug-info.mlir +++ b/test/Analysis/debug-info.mlir @@ -48,3 +48,29 @@ hw.module @Aggregates(in %data_a: i32, in %data_b: index, in %data_c_0: i17, in %1 = dbg.struct {"a": %data_a, "b": %data_b, "c": %0} : i32, index, !dbg.array dbg.variable "data", %1 : !dbg.struct> } + +// CHECK-LABEL: Module "InlineScopes" for hw.module +// CHECK: Variable "a" +// CHECK-NEXT: Arg 0 of hw.module of type i42 +// CHECK: Instance "inner" of "InnerModule" for dbg.scope +// CHECK: Module "InnerModule" for dbg.scope +// CHECK: Variable "b" +// CHECK-NEXT: Result 0 of comb.mul of type i42 +hw.module @InlineScopes(in %a: i42) { + dbg.variable "a", %a : i42 + %0 = comb.mul %a, %a : i42 + %1 = dbg.scope "inner", "InnerModule" + dbg.variable "b", %0 scope %1 : i42 +} + +// CHECK-LABEL: Module "NestedScopes" for hw.module +// CHECK: Instance "foo" of "Foo" for dbg.scope +// CHECK: Module "Foo" for dbg.scope +// CHECK: Instance "bar" of "Bar" for dbg.scope +// CHECK: Module "Bar" for dbg.scope +// CHECK: Variable "a" +hw.module @NestedScopes(in %a: i42) { + %0 = dbg.scope "foo", "Foo" + %1 = dbg.scope "bar", "Bar" scope %0 + dbg.variable "a", %a scope %1 : i42 +} diff --git a/test/Dialect/Debug/basic.mlir b/test/Dialect/Debug/basic.mlir index 83428f3401..251ff6b825 100644 --- a/test/Dialect/Debug/basic.mlir +++ b/test/Dialect/Debug/basic.mlir @@ -19,6 +19,13 @@ func.func @Foo(%arg0: i32, %arg1: index, %arg2: f64) { %1 = dbg.array [%arg1, %arg1] : index dbg.variable "megabar", %1 : !dbg.array + // CHECK-NEXT: [[TMP:%.+]] = dbg.scope "inlined", "Bar" + // CHECK-NEXT: dbg.variable {{.+}} scope [[TMP]] + // CHECK-NEXT: dbg.scope {{.+}} scope [[TMP]] + %2 = dbg.scope "inlined", "Bar" + dbg.variable "x", %arg0 scope %2 : i32 + dbg.scope "y", "Baz" scope %2 + return }