[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,10 +1,22 @@
set(LLVM_OPTIONAL_SOURCES
DebugAnalysis.cpp
DebugInfo.cpp
DependenceAnalysis.cpp
SchedulingAnalysis.cpp
TestPasses.cpp
)
add_circt_library(CIRCTDebugAnalysis
DebugAnalysis.cpp
DebugInfo.cpp
LINK_LIBS PUBLIC
CIRCTComb
CIRCTDebug
CIRCTHW
MLIRIR
)
add_circt_library(CIRCTDependenceAnalysis
DependenceAnalysis.cpp
@ -28,17 +40,9 @@ add_circt_library(CIRCTAnalysisTestPasses
TestPasses.cpp
LINK_LIBS PUBLIC
CIRCTDebugAnalysis
CIRCTDependenceAnalysis
CIRCTSchedulingAnalysis
CIRCTHW
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/SchedulingAnalysis.h"
#include "circt/Dialect/HW/HWInstanceGraph.h"
@ -18,15 +19,42 @@
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/Value.h"
#include "mlir/Pass/Pass.h"
#include "llvm/Support/Debug.h"
using namespace mlir;
using namespace mlir::affine;
using namespace circt;
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 {
@ -77,7 +105,7 @@ void TestDependenceAnalysisPass::runOnOperation() {
}
//===----------------------------------------------------------------------===//
// DependenceAnalysis passes.
// SchedulingAnalysis
//===----------------------------------------------------------------------===//
namespace {
@ -114,7 +142,7 @@ void TestSchedulingAnalysisPass::runOnOperation() {
}
//===----------------------------------------------------------------------===//
// InferTopModule passes.
// InstanceGraph
//===----------------------------------------------------------------------===//
namespace {
@ -154,13 +182,16 @@ void InferTopModulePass::runOnOperation() {
namespace circt {
namespace test {
void registerAnalysisTestPasses() {
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
registerPass([]() -> std::unique_ptr<Pass> {
return std::make_unique<TestDependenceAnalysisPass>();
});
mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
registerPass([]() -> std::unique_ptr<Pass> {
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>();
});
}

View File

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

View File

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