diff --git a/xls/contrib/mlir/IR/xls_ops.td b/xls/contrib/mlir/IR/xls_ops.td index 67cdb716da..8056354736 100644 --- a/xls/contrib/mlir/IR/xls_ops.td +++ b/xls/contrib/mlir/IR/xls_ops.td @@ -2349,6 +2349,29 @@ def Xls_AssertOp : Xls_Op<"assert"> { }]; } +def Xls_CoverOp : Xls_Op<"cover"> { + let summary = "Records every time the condition evaluates to true"; + let description = [{ + Records the number of times the given condition evaluates to true. The label + uniquely identifies the coverage point within its scope of emission (though + uniqueness is not enforced by either the MLIR verifier or the XLS compiler). + + The op has no return value, which is noteworthy for two reasons: (1) Unlike + `xls.assert`, `xls.cover` does not take a token input and does not produce a + token output. (2) In XLS IR, the op returns an empty tuple but does so only + because there is no other way to express "no return value" type. This design + is more idiomatic in MLIR, at the cost of a small structural difference from + XLS IR. + }]; + let arguments = (ins + I1:$condition, + StrAttr:$label + ); + let assemblyFormat = [{ + $condition `,` $label attr-dict + }]; +} + def Xls_GateOp : Xls_Op<"gate", [ ShapesAreConsistent<["data", "result"]>, diff --git a/xls/contrib/mlir/testdata/translate_cover.mlir b/xls/contrib/mlir/testdata/translate_cover.mlir new file mode 100644 index 0000000000..f033979763 --- /dev/null +++ b/xls/contrib/mlir/testdata/translate_cover.mlir @@ -0,0 +1,10 @@ +// RUN: xls_translate --mlir-xls-to-xls %s -- 2>&1 \ +// RUN: | FileCheck %s --dump-input-filter=all + +// CHECK-LABEL: top fn test_cover( +// CHECK-SAME: [[ARG0:.*]]: bits[1] +// CHECK: cover([[ARG0]], label="my_coverpoint" +func.func @test_cover(%arg0: i1) { + xls.cover %arg0, "my_coverpoint" + func.return +} diff --git a/xls/contrib/mlir/testdata/translate_from_xls/cover.ir b/xls/contrib/mlir/testdata/translate_from_xls/cover.ir new file mode 100644 index 0000000000..9dd6d69b31 --- /dev/null +++ b/xls/contrib/mlir/testdata/translate_from_xls/cover.ir @@ -0,0 +1,11 @@ +// RUN: xls_translate --xls-to-mlir-xls %s 2>&1 | FileCheck %s + +package translate_ops + +// CHECK-LABEL: func @test_cover( +// CHECK-SAME: %[[COND:.*]]: i1) +fn test_cover(cond: bits[1] id=1) -> bits[1] { + // CHECK: xls.cover %[[COND]], "my_coverpoint" + cover.2: () = cover(cond, label="my_coverpoint", id=2) + ret not.3: bits[1] = not(cond, id=3) +} diff --git a/xls/contrib/mlir/tools/xls_translate/xls_translate_from_mlir.cc b/xls/contrib/mlir/tools/xls_translate/xls_translate_from_mlir.cc index 82970f1ce1..e02762afa7 100644 --- a/xls/contrib/mlir/tools/xls_translate/xls_translate_from_mlir.cc +++ b/xls/contrib/mlir/tools/xls_translate/xls_translate_from_mlir.cc @@ -513,6 +513,11 @@ BValue convertOp(AssertOp op, const TranslationState& state, BuilderBase& fb) { state.getLoc(op)); } +BValue convertOp(CoverOp op, const TranslationState& state, BuilderBase& fb) { + return fb.Cover(state.getXlsValue(op.getCondition()), op.getLabel(), + state.getLoc(op)); +} + // Tuple operations BValue convertOp(TupleOp op, const TranslationState& state, BuilderBase& fb) { std::vector values; @@ -1208,7 +1213,7 @@ FailureOr convertFunction(TranslationState& translation_state, // Debugging ops TraceOp, // Misc. side-effecting ops - AssertOp, GateOp>( + AssertOp, CoverOp, GateOp>( [&](auto t) { return convertOp(t, translation_state, fb); }) .Case([&](auto ret) { if (ret.getNumOperands() == 1) { diff --git a/xls/contrib/mlir/tools/xls_translate/xls_translate_to_mlir.cc b/xls/contrib/mlir/tools/xls_translate/xls_translate_to_mlir.cc index d88bdda644..704570de87 100644 --- a/xls/contrib/mlir/tools/xls_translate/xls_translate_to_mlir.cc +++ b/xls/contrib/mlir/tools/xls_translate/xls_translate_to_mlir.cc @@ -1320,6 +1320,22 @@ absl::StatusOr translateOp(::xls::Assert& node, OpBuilder& builder, : nullptr); } +absl::StatusOr translateOp(::xls::Cover& node, OpBuilder& builder, + TranslationState& state) { + auto condition = state.getMlirValue(node.condition()->id()); + if (!condition.ok()) { + return condition.status(); + } + + auto loc = translateLoc(node.loc(), builder, state); + if (!loc.ok()) { + return loc.status(); + } + + return CoverOp::create(builder, *loc, *condition, + builder.getStringAttr(node.label())); +} + absl::StatusOr translateAnyOp(::xls::Node& xls_node, OpBuilder& builder, TranslationState& state) { @@ -1402,8 +1418,9 @@ absl::StatusOr translateAnyOp(::xls::Node& xls_node, "StateRead not handeled during proc translation."); } else if (dynamic_cast<::xls::Next*>(&xls_node)) { return absl::InternalError("Next not handeled during proc translation."); - } else if (dynamic_cast<::xls::Cover*>(&xls_node) || - dynamic_cast<::xls::MinDelay*>(&xls_node)) { + } else if (auto* xls_op = dynamic_cast<::xls::Cover*>(&xls_node)) { + op = translateOp(*xls_op, builder, state); + } else if (dynamic_cast<::xls::MinDelay*>(&xls_node)) { return absl::InternalError(absl::StrCat( "Unsupported operation: ", ::xls::OpToString(xls_node.op()), " - Not yet available in XLS MLIR!")); @@ -1416,9 +1433,13 @@ absl::StatusOr translateAnyOp(::xls::Node& xls_node, return op.status(); } - if (auto err = state.setMlirValue(xls_node.id(), (*op)->getResult(0)); - !err.ok()) { - return err; + // Only map the result if the op produces one (e.g., xls.cover has no + // results). + if ((*op)->getNumResults() > 0) { + if (auto err = state.setMlirValue(xls_node.id(), (*op)->getResult(0)); + !err.ok()) { + return err; + } } return *op;