diff --git a/include/circt/Conversion/LTLToCore.h b/include/circt/Conversion/LTLToCore.h new file mode 100644 index 000000000000..3622df4b0a0a --- /dev/null +++ b/include/circt/Conversion/LTLToCore.h @@ -0,0 +1,30 @@ +//===- LTLToCore.h - LTL to Core conversion pass ----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file declares passes which together will convert the LTL and Verif +// operations to Core operations. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_CONVERSION_LTLTOCORE_H +#define CIRCT_CONVERSION_LTLTOCORE_H + +#include "circt/Support/LLVM.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/Pass/Pass.h" +#include "mlir/Transforms/DialectConversion.h" + +using namespace mlir; + +namespace circt { + +std::unique_ptr createLowerLTLToCorePass(); + +} // namespace circt + +#endif // CIRCT_CONVERSION_LTLTOCORE_H diff --git a/include/circt/Conversion/Passes.h b/include/circt/Conversion/Passes.h index fa70820fcbcb..f540554e2780 100644 --- a/include/circt/Conversion/Passes.h +++ b/include/circt/Conversion/Passes.h @@ -37,6 +37,7 @@ #include "circt/Conversion/HandshakeToDC.h" #include "circt/Conversion/HandshakeToHW.h" #include "circt/Conversion/LLHDToLLVM.h" +#include "circt/Conversion/LTLToCore.h" #include "circt/Conversion/LoopScheduleToCalyx.h" #include "circt/Conversion/MooreToCore.h" #include "circt/Conversion/PipelineToHW.h" diff --git a/include/circt/Conversion/Passes.td b/include/circt/Conversion/Passes.td index 28bdbdfc54ee..eb7b4affa572 100644 --- a/include/circt/Conversion/Passes.td +++ b/include/circt/Conversion/Passes.td @@ -617,6 +617,23 @@ def ConvertHWToBTOR2 : Pass<"convert-hw-to-btor2", "hw::HWModuleOp"> { "seq::SeqDialect"]; } +//===----------------------------------------------------------------------===// +// LTLToCore +//===----------------------------------------------------------------------===// + +def LowerLTLToCore : Pass<"lower-ltl-to-core", "hw::HWModuleOp"> { + let summary = "Convert LTL and Verif to Core"; + let description = [{ + This pass converts ltl and verif operations to core ones. This can be done directly + without going through FSM if we're only working with overlapping properties (no delays). + }]; + let constructor = "circt::createLowerLTLToCorePass()"; + let dependentDialects = [ + "hw::HWDialect", "sv::SVDialect", "comb::CombDialect", + "seq::SeqDialect" + ]; +} + //===----------------------------------------------------------------------===// // VerifToSV //===----------------------------------------------------------------------===// diff --git a/lib/CAPI/Conversion/CMakeLists.txt b/lib/CAPI/Conversion/CMakeLists.txt index 405a529e865d..242462714798 100644 --- a/lib/CAPI/Conversion/CMakeLists.txt +++ b/lib/CAPI/Conversion/CMakeLists.txt @@ -27,6 +27,7 @@ add_mlir_public_c_api_library(CIRCTCAPIConversion CIRCTHWToSystemC CIRCTLLHDToLLVM CIRCTLoopScheduleToCalyx + CIRCTLTLToCore CIRCTMooreToCore CIRCTPipelineToHW CIRCTSCFToCalyx diff --git a/lib/Conversion/CMakeLists.txt b/lib/Conversion/CMakeLists.txt index ba123b2e8e0b..2d487427d889 100644 --- a/lib/Conversion/CMakeLists.txt +++ b/lib/Conversion/CMakeLists.txt @@ -32,6 +32,7 @@ add_subdirectory(VerifToSMT) add_subdirectory(SMTToZ3LLVM) add_subdirectory(VerifToSV) add_subdirectory(CalyxNative) +add_subdirectory(LTLToCore) if(CIRCT_SLANG_FRONTEND_ENABLED) add_subdirectory(ImportVerilog) diff --git a/lib/Conversion/LTLToCore/CMakeLists.txt b/lib/Conversion/LTLToCore/CMakeLists.txt new file mode 100644 index 000000000000..5d0482e77eb2 --- /dev/null +++ b/lib/Conversion/LTLToCore/CMakeLists.txt @@ -0,0 +1,18 @@ +add_circt_conversion_library(CIRCTLTLToCore + LTLToCore.cpp + + DEPENDS + CIRCTConversionPassIncGen + + LINK_COMPONENTS + Core + + LINK_LIBS PUBLIC + CIRCTHW + CIRCTSV + CIRCTComb + CIRCTSeq + CIRCTLTL + CIRCTVerif + MLIRTransforms +) diff --git a/lib/Conversion/LTLToCore/LTLToCore.cpp b/lib/Conversion/LTLToCore/LTLToCore.cpp new file mode 100644 index 000000000000..329491933e9c --- /dev/null +++ b/lib/Conversion/LTLToCore/LTLToCore.cpp @@ -0,0 +1,278 @@ +//===- LTLToCore.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 +// +//===----------------------------------------------------------------------===// +// +// Converts LTL and Verif operations to Core operations +// +//===----------------------------------------------------------------------===// + +#include "circt/Conversion/LTLToCore.h" +#include "../PassDetail.h" +#include "circt/Conversion/HWToSV.h" +#include "circt/Dialect/Comb/CombOps.h" +#include "circt/Dialect/HW/HWOps.h" +#include "circt/Dialect/LTL/LTLDialect.h" +#include "circt/Dialect/LTL/LTLOps.h" +#include "circt/Dialect/SV/SVDialect.h" +#include "circt/Dialect/SV/SVOps.h" +#include "circt/Dialect/Seq/SeqOps.h" +#include "circt/Dialect/Verif/VerifOps.h" +#include "circt/Support/BackedgeBuilder.h" +#include "circt/Support/Namespace.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/SCF/IR/SCF.h" +#include "mlir/Transforms/DialectConversion.h" +#include "llvm/Support/MathExtras.h" + +using namespace mlir; +using namespace circt; +using namespace hw; + +static sv::EventControl ltlToSVEventControl(ltl::ClockEdge ce) { + switch (ce) { + case ltl::ClockEdge::Pos: + return sv::EventControl::AtPosEdge; + case ltl::ClockEdge::Neg: + return sv::EventControl::AtNegEdge; + case ltl::ClockEdge::Both: + return sv::EventControl::AtEdge; + } + llvm_unreachable("Unknown event control kind"); +} + +//===----------------------------------------------------------------------===// +// Conversion patterns +//===----------------------------------------------------------------------===// + +namespace { + +// Custom pattern matchers + +// Matches and records a boolean attribute +struct I1ValueMatcher { + Value *what; + I1ValueMatcher(Value *what) : what(what) {} + bool match(Value op) const { + if (!op.getType().isSignlessInteger(1)) + return false; + *what = op; + return true; + } +}; + +static inline I1ValueMatcher mBool(Value *const val) { + return I1ValueMatcher(val); +} + +// Matches and records an arbitrary op +template +struct BindingRecursivePatternMatcher + : mlir::detail::RecursivePatternMatcher { + + using BaseMatcher = + mlir::detail::RecursivePatternMatcher; + BindingRecursivePatternMatcher(OpType *bop, OperandMatchers... matchers) + : BaseMatcher(matchers...), opBind(bop) {} + + bool match(Operation *op) { + if (BaseMatcher::match(op)) { + *opBind = llvm::cast(op); + return true; + } + return false; + } + + OpType *opBind; +}; + +template +static inline auto mOpWithBind(OpType *op, Matchers... matchers) { + return BindingRecursivePatternMatcher(op, matchers...); +} + +struct HasBeenResetOpConversion : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + // HasBeenReset generates a 1 bit register that is set to one once the reset + // has been raised and lowered at at least once. + LogicalResult + matchAndRewrite(verif::HasBeenResetOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + auto i1 = rewriter.getI1Type(); + // Generate the constant used to set the register value + Value constZero = rewriter.create(op.getLoc(), i1, 0); + + // Generate the constant used to enegate the + Value constOne = rewriter.create(op.getLoc(), i1, 1); + + // Create a backedge for the register to be used in the OrOp + circt::BackedgeBuilder bb(rewriter, op.getLoc()); + circt::Backedge reg = bb.get(rewriter.getI1Type()); + + // Generate an or between the reset and the register's value to store + // whether or not the reset has been active at least once + Value orReset = + rewriter.create(op.getLoc(), adaptor.getReset(), reg); + + // This register should not be reset, so we give it dummy reset and resetval + // operands to fit the build signature + Value reset, resetval; + + // Finally generate the register to set the backedge + reg.setValue(rewriter.create( + op.getLoc(), orReset, + rewriter.createOrFold(op.getLoc(), adaptor.getClock()), + rewriter.getStringAttr("hbr"), reset, resetval, constZero, + InnerSymAttr{} // inner_sym + )); + + // We also need to consider the case where we are currently in a reset cycle + // in which case our hbr register should be down- + // Practically this means converting it to (and hbr (not reset)) + Value notReset = + rewriter.create(op.getLoc(), adaptor.getReset(), constOne); + rewriter.replaceOpWithNewOp(op, reg, notReset); + + return success(); + } +}; + +struct AssertOpConversionPattern : OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + Value visit(ltl::DisableOp op, ConversionPatternRewriter &rewriter, + Value operand = nullptr) const { + // Replace the ltl::DisableOp with an OR op as it represents a disabling + // implication: (implies (not condition) input) is equivalent to + // (or (not (not condition)) input) which becomes (or condition input) + return rewriter.replaceOpWithNewOp( + op, op.getCondition(), operand ? operand : op.getInput()); + } + + // Special case : we want to detect the Non-overlapping implication, + // Overlapping Implication or simply AssertProperty patterns and reject + // everything else for now: antecedent : ltl::concatOp || immediate predicate + // consequent : any other non-sequence op + // We want to support a ##n true |-> b and a |-> b + LogicalResult + matchAndRewrite(verif::AssertOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + + Value ltlClock, disableCond, disableInput, disableVal; + ltl::ClockOp clockOp; + ltl::DisableOp disableOp; + + // Look for the Assert Property pattern + bool matchedProperty = matchPattern( + op.getProperty(), + mOpWithBind( + &clockOp, + mOpWithBind(&disableOp, mBool(&disableInput), + mBool(&disableCond)), + mBool(<lClock))); + + if (!matchedProperty) + return rewriter.notifyMatchFailure( + op, " verif.assert used outside of an assert property!"); + + // Then visit the disable op + disableVal = visit(disableOp, rewriter, disableInput); + + // Generate the parenting sv.always posedge clock from the ltl + // clock, containing the generated sv.assert + rewriter.create( + clockOp.getLoc(), ltlToSVEventControl(clockOp.getEdge()), ltlClock, + [&] { + // Generate the sv assertion using the input to the + // parenting clock + rewriter.replaceOpWithNewOp( + op, disableVal, + sv::DeferAssertAttr::get(getContext(), + sv::DeferAssert::Immediate), + op.getLabelAttr()); + }); + + // Erase Converted Ops + rewriter.eraseOp(clockOp); + + return success(); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// Lower LTL To Core pass +//===----------------------------------------------------------------------===// + +namespace { +struct LowerLTLToCorePass : public LowerLTLToCoreBase { + LowerLTLToCorePass() = default; + void runOnOperation() override; +}; +} // namespace + +// Simply applies the conversion patterns defined above +void LowerLTLToCorePass::runOnOperation() { + + // Set target dialects: We don't want to see any ltl or verif that might + // come from an AssertProperty left in the result + ConversionTarget target(getContext()); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addLegalDialect(); + target.addIllegalDialect(); + + // Create type converters, mostly just to convert an ltl property to a bool + mlir::TypeConverter converter; + + // Convert the ltl property type to a built-in type + converter.addConversion([](IntegerType type) { return type; }); + converter.addConversion([](ltl::PropertyType type) { + return IntegerType::get(type.getContext(), 1); + }); + converter.addConversion([](ltl::SequenceType type) { + return IntegerType::get(type.getContext(), 1); + }); + + // Basic materializations + converter.addTargetMaterialization( + [&](mlir::OpBuilder &builder, mlir::Type resultType, + mlir::ValueRange inputs, + mlir::Location loc) -> std::optional { + if (inputs.size() != 1) + return std::nullopt; + return inputs[0]; + }); + + converter.addSourceMaterialization( + [&](mlir::OpBuilder &builder, mlir::Type resultType, + mlir::ValueRange inputs, + mlir::Location loc) -> std::optional { + if (inputs.size() != 1) + return std::nullopt; + return inputs[0]; + }); + + // Create the operation rewrite patters + RewritePatternSet patterns(&getContext()); + patterns.add( + converter, patterns.getContext()); + + // Apply the conversions + if (failed( + applyPartialConversion(getOperation(), target, std::move(patterns)))) + return signalPassFailure(); +} + +// Basic default constructor +std::unique_ptr circt::createLowerLTLToCorePass() { + return std::make_unique(); +} diff --git a/lib/Conversion/PassDetail.h b/lib/Conversion/PassDetail.h index e92ed028afcc..e8ba9cf6f9c5 100644 --- a/lib/Conversion/PassDetail.h +++ b/lib/Conversion/PassDetail.h @@ -146,6 +146,10 @@ namespace verif { class VerifDialect; } // namespace verif +namespace ltl { +class LTLDialect; +} // namespace ltl + // Generate the classes which represent the passes #define GEN_PASS_CLASSES #include "circt/Conversion/Passes.h.inc" diff --git a/test/Conversion/LTLToCore/assertproperty.mlir b/test/Conversion/LTLToCore/assertproperty.mlir new file mode 100644 index 000000000000..5c4454b9220f --- /dev/null +++ b/test/Conversion/LTLToCore/assertproperty.mlir @@ -0,0 +1,39 @@ +// RUN: circt-opt %s --lower-ltl-to-core | FileCheck %s +// Tests that an Assert Property high level statement can be converted correctly + +module { + //CHECK: hw.module @test(in %clock : !seq.clock, in %reset : i1, in %0 "" : i1) + hw.module @test(in %clock : !seq.clock, in %reset : i1, in %8 : i1) { + //CHECK: %1 = seq.from_clock %clock + %0 = seq.from_clock %clock + //CHECK: %true = hw.constant true + %true = hw.constant true + + //CHECK: %false = hw.constant false + //CHECK: %true_0 = hw.constant true + //CHECK: %2 = comb.or %reset, %hbr : i1 + //CHECK: %hbr = seq.compreg %2, %clock powerOn %false : i1 + %9 = verif.has_been_reset %0, sync %reset + + //CHECK: %3 = comb.xor %reset, %true_0 : i1 + //CHECK: %4 = comb.and %hbr, %3 : i1 + //CHECK: %5 = comb.xor bin %4, %true : i1 + %10 = comb.xor bin %9, %true : i1 + + //CHECK: %6 = hw.wire %5 : i1 + %12 = hw.wire %10 : i1 + + //CHECK: %7 = comb.or %6, %0 : i1 + %13 = ltl.disable %8 if %12 : i1 + + //CHECK: sv.always posedge %1 { + //CHECK: sv.assert %7, immediate + //CHECK: } + %14 = ltl.clock %13, posedge %0 : !ltl.property + verif.assert %14 : !ltl.property + + //CHECK: hw.output + hw.output + } +} + diff --git a/tools/circt-opt/CMakeLists.txt b/tools/circt-opt/CMakeLists.txt index c1e8642794f5..de3435532e82 100644 --- a/tools/circt-opt/CMakeLists.txt +++ b/tools/circt-opt/CMakeLists.txt @@ -94,6 +94,7 @@ target_link_libraries(circt-opt CIRCTVerif CIRCTVerifToSMT CIRCTVerifToSV + CIRCTLTLToCore MLIRIR MLIRLLVMDialect