[Debug] Add debug-only value/op analysis (#6335)

Add the `DebugAnalysis` which marks values and operations that only feed
into debug info. This will later allow ExportVerilog to skip expressions
and statements that are exclusively used by debug ops and are irrelevant
for synthesis and simulation.

The analysis is fairly straightforward at the moment. In the future, we
may want to be more clever about what we mark as debug-only. For
example, certain debug info output formats may allow us to emit entire
expressions and state machines, such that those can be stripped from the
Verilog output. In that case, we'd want to look through expressions,
wires, and registers when marking ops as debug-only. But some other
formats may only allow us to point at named signals in the Verilog, such
that any expression or register has to remain in Verilog, and possibly
be visible under a name for the debug info to make use of it.

This commit also bundles the `DebugInfo` analysis, which extracts the
source language hierarchy and variable layout from the IR, together with
this new `DebugAnalysis`, which marks ops as debug-only.
This commit is contained in:
Fabian Schuiki 2023-10-26 13:53:49 -07:00 committed by GitHub
parent 34c320f474
commit a50b0bb1cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 254 additions and 19 deletions

View File

@ -0,0 +1,34 @@
//===- DebugAnalysis.h ----------------------------------------------------===//
//
// 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_DEBUGANALYSIS_H
#define CIRCT_ANALYSIS_DEBUGANALYSIS_H
#include "circt/Support/LLVM.h"
#include "mlir/IR/Value.h"
#include "llvm/ADT/DenseSet.h"
namespace mlir {
class Operation;
class OpOperand;
} // namespace mlir
namespace circt {
/// Identify operations and values that are only used for debug info.
struct DebugAnalysis {
DebugAnalysis(Operation *op);
DenseSet<Operation *> debugOps;
DenseSet<Value> debugValues;
DenseSet<OpOperand *> debugOperands;
};
} // namespace circt
#endif // CIRCT_ANALYSIS_DEBUGANALYSIS_H

View File

@ -1,9 +1,21 @@
set(LLVM_OPTIONAL_SOURCES set(LLVM_OPTIONAL_SOURCES
DebugAnalysis.cpp
DebugInfo.cpp DebugInfo.cpp
DependenceAnalysis.cpp DependenceAnalysis.cpp
SchedulingAnalysis.cpp SchedulingAnalysis.cpp
TestPasses.cpp TestPasses.cpp
) )
add_circt_library(CIRCTDebugAnalysis
DebugAnalysis.cpp
DebugInfo.cpp
LINK_LIBS PUBLIC
CIRCTComb
CIRCTDebug
CIRCTHW
MLIRIR
)
add_circt_library(CIRCTDependenceAnalysis add_circt_library(CIRCTDependenceAnalysis
DependenceAnalysis.cpp DependenceAnalysis.cpp
@ -12,7 +24,7 @@ add_circt_library(CIRCTDependenceAnalysis
MLIRIR MLIRIR
MLIRAffineUtils MLIRAffineUtils
MLIRTransformUtils MLIRTransformUtils
) )
add_circt_library(CIRCTSchedulingAnalysis add_circt_library(CIRCTSchedulingAnalysis
SchedulingAnalysis.cpp SchedulingAnalysis.cpp
@ -22,23 +34,15 @@ add_circt_library(CIRCTSchedulingAnalysis
MLIRIR MLIRIR
CIRCTDependenceAnalysis CIRCTDependenceAnalysis
CIRCTScheduling CIRCTScheduling
) )
add_circt_library(CIRCTAnalysisTestPasses add_circt_library(CIRCTAnalysisTestPasses
TestPasses.cpp TestPasses.cpp
LINK_LIBS PUBLIC LINK_LIBS PUBLIC
CIRCTDebugAnalysis
CIRCTDependenceAnalysis CIRCTDependenceAnalysis
CIRCTSchedulingAnalysis CIRCTSchedulingAnalysis
CIRCTHW CIRCTHW
MLIRPass MLIRPass
)
add_circt_library(CIRCTDebugInfoAnalysis
DebugInfo.cpp
LINK_LIBS PUBLIC
CIRCTDebug
CIRCTHW
MLIRIR
) )

View File

@ -0,0 +1,128 @@
//===- DebugAnalysis.cpp --------------------------------------------------===//
//
// 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/DebugAnalysis.h"
#include "circt/Dialect/Comb/CombOps.h"
#include "circt/Dialect/Debug/DebugOps.h"
#include "circt/Dialect/HW/HWOps.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Support/Debug.h"
using namespace circt;
using namespace debug;
using namespace mlir;
namespace {
struct DebugAnalysisBuilder {
DebugAnalysisBuilder(Operation *rootOp) : rootOp(rootOp) {}
void run();
void addDebugOp(Operation *op);
void addDebugValue(Value value);
void addDebugOperand(OpOperand *operand);
void maybeDebugOp(Operation *op);
Operation *rootOp;
SetVector<Operation *> worklist;
DenseSet<Operation *> debugOps;
DenseSet<Value> debugValues;
DenseSet<OpOperand *> debugOperands;
};
} // namespace
void DebugAnalysisBuilder::run() {
// Find all debug ops nested under the root op and mark them as debug-only
// to kickstart the analysis.
rootOp->walk([&](Operation *op) {
if (isa<debug::DebugDialect>(op->getDialect())) {
addDebugOp(op);
return;
}
for (auto &region : op->getRegions())
for (auto &block : region)
for (auto arg : block.getArguments())
if (isa<debug::DebugDialect>(arg.getType().getDialect()))
addDebugValue(arg);
for (auto result : op->getResults())
if (isa<debug::DebugDialect>(result.getType().getDialect()))
addDebugValue(result);
});
// Visit operations and check if all their operands or all their uses are
// marked as debug-only. If they are, mark the op itself as debug-only.
while (!worklist.empty()) {
auto *op = worklist.pop_back_val();
if (debugOps.contains(op))
continue;
// Do not propagate through stateful elements. This should probably be
// configurable, since certain forms of debug info extraction would be able
// to pull entire state machines out of the design. For now this just
// represents the common denominator across all debug infos.
if (!isa<hw::HWDialect, comb::CombDialect>(op->getDialect()))
continue;
if (op->hasAttr("name"))
continue;
if (op->getNumResults() > 0) {
auto allUsesDebug = llvm::all_of(op->getUses(), [&](auto &use) {
return debugOperands.contains(&use);
});
if (allUsesDebug) {
addDebugOp(op);
continue;
}
}
if (op->getNumOperands() > 0) {
auto allOperandsDebug =
llvm::all_of(op->getOperands(), [&](auto operand) {
return debugValues.contains(operand);
});
if (allOperandsDebug) {
addDebugOp(op);
continue;
}
}
}
}
void DebugAnalysisBuilder::addDebugOp(Operation *op) {
if (debugOps.insert(op).second) {
for (auto &operand : op->getOpOperands())
addDebugOperand(&operand);
for (auto result : op->getResults())
addDebugValue(result);
}
}
void DebugAnalysisBuilder::addDebugValue(Value value) {
if (debugValues.insert(value).second) {
for (auto *user : value.getUsers())
maybeDebugOp(user);
}
}
void DebugAnalysisBuilder::addDebugOperand(OpOperand *operand) {
if (debugOperands.insert(operand).second)
maybeDebugOp(operand->get().getDefiningOp());
}
void DebugAnalysisBuilder::maybeDebugOp(Operation *op) {
if (!op || debugOps.contains(op))
return;
worklist.insert(op);
}
DebugAnalysis::DebugAnalysis(Operation *op) {
DebugAnalysisBuilder builder(op);
builder.run();
debugOps = std::move(builder.debugOps);
debugValues = std::move(builder.debugValues);
debugOperands = std::move(builder.debugOperands);
}

View File

@ -10,6 +10,7 @@
// //
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
#include "circt/Analysis/DebugAnalysis.h"
#include "circt/Analysis/DependenceAnalysis.h" #include "circt/Analysis/DependenceAnalysis.h"
#include "circt/Analysis/SchedulingAnalysis.h" #include "circt/Analysis/SchedulingAnalysis.h"
#include "circt/Dialect/HW/HWInstanceGraph.h" #include "circt/Dialect/HW/HWInstanceGraph.h"
@ -18,15 +19,42 @@
#include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h" #include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinOps.h" #include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Value.h"
#include "mlir/Pass/Pass.h" #include "mlir/Pass/Pass.h"
#include "llvm/Support/Debug.h" #include "llvm/Support/Debug.h"
using namespace mlir; using namespace mlir;
using namespace mlir::affine; using namespace mlir::affine;
using namespace circt;
using namespace circt::analysis; using namespace circt::analysis;
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// DependenceAnalysis passes. // DebugAnalysis
//===----------------------------------------------------------------------===//
namespace {
struct TestDebugAnalysisPass
: public PassWrapper<TestDebugAnalysisPass, OperationPass<mlir::ModuleOp>> {
MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID(TestDebugAnalysisPass)
void runOnOperation() override;
StringRef getArgument() const override { return "test-debug-analysis"; }
StringRef getDescription() const override {
return "Perform debug analysis and emit results as attributes";
}
};
} // namespace
void TestDebugAnalysisPass::runOnOperation() {
auto *context = &getContext();
auto &analysis = getAnalysis<DebugAnalysis>();
for (auto *op : analysis.debugOps) {
op->setAttr("debug.only", UnitAttr::get(context));
}
}
//===----------------------------------------------------------------------===//
// DependenceAnalysis
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
namespace { namespace {
@ -77,7 +105,7 @@ void TestDependenceAnalysisPass::runOnOperation() {
} }
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// DependenceAnalysis passes. // SchedulingAnalysis
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
namespace { namespace {
@ -114,7 +142,7 @@ void TestSchedulingAnalysisPass::runOnOperation() {
} }
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// InferTopModule passes. // InstanceGraph
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
namespace { namespace {
@ -154,13 +182,16 @@ void InferTopModulePass::runOnOperation() {
namespace circt { namespace circt {
namespace test { namespace test {
void registerAnalysisTestPasses() { void registerAnalysisTestPasses() {
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { registerPass([]() -> std::unique_ptr<Pass> {
return std::make_unique<TestDependenceAnalysisPass>(); return std::make_unique<TestDependenceAnalysisPass>();
}); });
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { registerPass([]() -> std::unique_ptr<Pass> {
return std::make_unique<TestSchedulingAnalysisPass>(); return std::make_unique<TestSchedulingAnalysisPass>();
}); });
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { registerPass([]() -> std::unique_ptr<Pass> {
return std::make_unique<TestDebugAnalysisPass>();
});
registerPass([]() -> std::unique_ptr<Pass> {
return std::make_unique<InferTopModulePass>(); return std::make_unique<InferTopModulePass>();
}); });
} }

View File

@ -9,8 +9,9 @@ add_circt_translation_library(CIRCTTargetDebugInfo
LINK_LIBS PUBLIC LINK_LIBS PUBLIC
CIRCTComb CIRCTComb
CIRCTDebug CIRCTDebug
CIRCTDebugInfoAnalysis CIRCTDebugAnalysis
CIRCTHW CIRCTHW
CIRCTOM
CIRCTSeq CIRCTSeq
CIRCTSupport CIRCTSupport
CIRCTSV CIRCTSV

View File

@ -9,6 +9,7 @@
#include "circt/Dialect/Comb/CombDialect.h" #include "circt/Dialect/Comb/CombDialect.h"
#include "circt/Dialect/Debug/DebugDialect.h" #include "circt/Dialect/Debug/DebugDialect.h"
#include "circt/Dialect/HW/HWDialect.h" #include "circt/Dialect/HW/HWDialect.h"
#include "circt/Dialect/OM/OMDialect.h"
#include "circt/Dialect/SV/SVDialect.h" #include "circt/Dialect/SV/SVDialect.h"
#include "circt/Dialect/Seq/SeqDialect.h" #include "circt/Dialect/Seq/SeqDialect.h"
#include "circt/Target/DebugInfo.h" #include "circt/Target/DebugInfo.h"
@ -26,6 +27,7 @@ static void registerDialects(DialectRegistry &registry) {
registry.insert<hw::HWDialect>(); registry.insert<hw::HWDialect>();
registry.insert<seq::SeqDialect>(); registry.insert<seq::SeqDialect>();
registry.insert<sv::SVDialect>(); registry.insert<sv::SVDialect>();
registry.insert<om::OMDialect>();
} }
void registerDumpTranslation() { void registerDumpTranslation() {

View File

@ -0,0 +1,35 @@
// RUN: circt-opt %s --test-debug-analysis | FileCheck %s
// CHECK-LABEL: @Foo(
hw.module @Foo(out z: i42) {
// CHECK: hw.constant 0 : i42 {debug.only}
// CHECK: dbg.variable "a", {{%.+}} {debug.only}
%c0_i42 = hw.constant 0 : i42
dbg.variable "a", %c0_i42 : i42
// CHECK: hw.constant 1 : i42
// CHECK-NOT: debug.only
// CHECK: dbg.variable "b", {{%.+}} {debug.only}
%c1_i42 = hw.constant 1 : i42
dbg.variable "b", %c1_i42 : i42
hw.output %c1_i42 : i42
}
// CHECK-LABEL: @Empty(
// CHECK-NOT: debug.only
hw.module @Empty() {}
// CHECK-LABEL: @DebugOnlyBody(
hw.module @DebugOnlyBody(in %a: i1, in %b: i1) {
// CHECK: comb.and {{.+}} {debug.only}
// CHECK: dbg.struct {{.+}} {debug.only}
// CHECK: dbg.variable "a", {{%.+}} {debug.only}
// CHECK: dbg.variable "b", {{%.+}} {debug.only}
// CHECK: dbg.variable "c", {{%.+}} {debug.only}
%0 = comb.and %a, %b : i1
%1 = dbg.struct {"x": %0} : i1
dbg.variable "a", %a : i1
dbg.variable "b", %b : i1
dbg.variable "c", %1 : !dbg.struct
}