Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/circt/Dialect/Debug/DebugOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,23 @@ def VariableOp : DebugOp<"variable"> {
}];
}

def TraceOp : DebugOp<"trace"> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not totally sure having this in the debug dialect is the right move when it's very specific to BMC. Maybe the verif dialect would make more sense?

let summary = "Record a named runtime value for counterexample tracing";
let description = [{
Records a named value at a specific step for later counterexample
materialization. This is intended as an internal bridge from the BMC flow
to the final runtime-backed text/VCD emission path.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the motivation for this op is entirely clear to me; what's the aim for its eventual lowering?

Would the aim be to lower this op to some call that reaches out to the BMCTrace runtime with a value and a cycle number? If that's the case then I'm not sure it's completely clear to me why we need a dedicated op for this instead of building this IR directly as we build the for loop that it sits in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intended lowering is a later counterexample-materialization step that consumes dbg.trace(step, name, value) and lowers it to the BMC runtime trace path. I introduced a dedicated op to keep VerifToSMT backend-neutral instead of constructing runtime-specific IR directly in the loop body. You’re right that this isn’t clear from the current patch.

}];
let arguments = (ins
I32:$step,
StrAttr:$name,
AnyType:$value
);
let assemblyFormat = [{
$step `,` $name `,` $value attr-dict `:` type($step) `,` type($value)
}];
}


def ValueOp : DebugOp<"value"> {
let summary = "Attach source-language metadata to a value";
Expand Down
16 changes: 15 additions & 1 deletion lib/Conversion/SMTToZ3LLVM/LowerSMTToZ3LLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1282,6 +1282,18 @@ struct IntAbsOpLowering : public SMTLoweringPattern<IntAbsOp> {
// For now we want to ignore Debug variable and scope ops - eventually we'll
// give this debug info to Z3

/// Strip dbg.trace ops.
struct DbgTraceLowering : public OpConversionPattern<debug::TraceOp> {
using OpConversionPattern::OpConversionPattern;

LogicalResult
matchAndRewrite(debug::TraceOp op, OpAdaptor adaptor,
ConversionPatternRewriter &rewriter) const final {
rewriter.eraseOp(op);
return success();
}
};

/// Strip dbg.variable ops.
struct DbgVariableLowering : public OpConversionPattern<debug::VariableOp> {
using OpConversionPattern::OpConversionPattern;
Expand Down Expand Up @@ -1546,7 +1558,9 @@ void circt::populateSMTToZ3LLVMConversionPatterns(
BV2IntOpLowering, QuantifierLowering<ForallOp>,
QuantifierLowering<ExistsOp>>(converter, patterns.getContext(),
globals, options);
patterns.add<DbgVariableLowering, DbgScopeLowering>(patterns.getContext());
patterns.add<DbgTraceLowering>(patterns.getContext());
patterns.add<DbgVariableLowering>(patterns.getContext());
patterns.add<DbgScopeLowering>(patterns.getContext());
}

void LowerSMTToZ3LLVMPass::runOnOperation() {
Expand Down
17 changes: 17 additions & 0 deletions lib/Conversion/VerifToSMT/VerifToSMT.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,20 @@ static void attachDebugVariables(
}
}

static void attachTraceRecords(
OpBuilder &builder, Location loc, Value step, ArrayRef<Type> originalTypes,
ValueRange values,
const llvm::SmallDenseMap<unsigned, StringAttr> &debugNames) {
for (auto [argIndex, value] : llvm::enumerate(values)) {
if (isa<seq::ClockType>(originalTypes[argIndex]))
continue;
auto it = debugNames.find(argIndex);
if (it == debugNames.end())
continue;
debug::TraceOp::create(builder, loc, step, it->second, value);
}
}

/// Lower a verif::AssertOp operation with an i1 operand to a smt::AssertOp,
/// negated to check for unsatisfiability.
struct VerifAssertOpConversion : OpConversionPattern<verif::AssertOp> {
Expand Down Expand Up @@ -559,6 +573,9 @@ struct VerifBoundedModelCheckingOpConversion
attachDebugVariables(
builder, loc, oldCircuitInputTy,
iterArgs.take_front(circuitFuncOp.getNumArguments()), debugNames);
attachTraceRecords(
builder, loc, i, oldCircuitInputTy,
iterArgs.take_front(circuitFuncOp.getNumArguments()), debugNames);

// Drop existing assertions
smt::PopOp::create(builder, loc, 1);
Expand Down
3 changes: 3 additions & 0 deletions test/Conversion/SMTToZ3LLVM/smt-to-z3-llvm.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ func.func @test(%arg0: i32) {
// CHECK: llvm.call @Z3_mk_bv2int({{%[0-9a-zA-Z_]+}}, [[BV0]], [[SIGNEDCONST]]) : (!llvm.ptr, !llvm.ptr, i1) -> !llvm.ptr
smt.bv2int %c0_bv4 signed : !smt.bv<4>

// CHECK-NOT: dbg.trace
dbg.trace %arg1, "var0", %c0_bv4 : i32, !smt.bv<4>

// CHECK-NOT: dbg.variable
dbg.variable "var1", %c0_bv4 : !smt.bv<4>

Expand Down
4 changes: 3 additions & 1 deletion test/Conversion/VerifToSMT/bmc-debug-names.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
// CHECK: [[REG_DECL:%.+]] = smt.declare_fun "state_q" : !smt.bv<8>
// CHECK: dbg.variable "data_in", [[INPUT_DECL]] : !smt.bv<8>
// CHECK: dbg.variable "state_q", [[REG_DECL]] : !smt.bv<8>
// CHECK: scf.for {{%.+}} iter_args([[CLK_ARG:%.+]] = {{%.+}}, [[INPUT_ARG:%.+]] = [[INPUT_DECL]], [[REG_ARG:%.+]] = [[REG_DECL]], {{%.+}})
// CHECK: scf.for [[STEP:%.+]] = {{%.+}} iter_args([[CLK_ARG:%.+]] = {{%.+}}, [[INPUT_ARG:%.+]] = [[INPUT_DECL]], [[REG_ARG:%.+]] = [[REG_DECL]], {{%.+}})
// CHECK: dbg.variable "data_in", [[INPUT_ARG]] : !smt.bv<8>
// CHECK: dbg.variable "state_q", [[REG_ARG]] : !smt.bv<8>
// CHECK: dbg.trace [[STEP]], "data_in", [[INPUT_ARG]] : i32, !smt.bv<8>
// CHECK: dbg.trace [[STEP]], "state_q", [[REG_ARG]] : i32, !smt.bv<8>
// CHECK: [[CIRCUIT_RESULT:%.+]] = func.call @bmc_circuit{{(_[0-9]+)?}}([[CLK_ARG]], [[INPUT_ARG]], [[REG_ARG]]) : (!smt.bv<1>, !smt.bv<8>, !smt.bv<8>) -> !smt.bv<8>
// CHECK-NOT: smt.declare_fun "input_1"
// CHECK-NOT: smt.declare_fun "reg_0"
Expand Down
3 changes: 2 additions & 1 deletion test/Dialect/Debug/basic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ func.func @Foo(%arg0: i32, %arg1: index, %arg2: f64) {
// CHECK-NEXT: dbg.variable "foo", %arg0 : i32
// CHECK-NEXT: dbg.variable "bar", %arg1 : index
// CHECK-NEXT: dbg.variable "baz", %arg2 : f64
// CHECK-NEXT: dbg.trace %arg0, "tracefoo", %arg2 : i32, f64
dbg.variable "foo", %arg0 : i32
dbg.variable "bar", %arg1 : index
dbg.variable "baz", %arg2 : f64
dbg.trace %arg0, "tracefoo", %arg2 : i32, f64

// CHECK-NEXT: [[TMP:%.+]] = dbg.struct {"foo": %arg0, "bar": %arg1, "baz": %arg2} : i32, index, f64
// CHECK-NEXT: dbg.variable "megafoo", [[TMP]] : !dbg.struct
Expand All @@ -28,4 +30,3 @@ func.func @Foo(%arg0: i32, %arg1: index, %arg2: f64) {

return
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: unrelated change