Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIRRTL] Add a new pass to detect static asserts #6341

Merged
merged 4 commits into from
Oct 26, 2023
Merged
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
2 changes: 2 additions & 0 deletions include/circt/Dialect/FIRRTL/Passes.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ std::unique_ptr<mlir::Pass> createGroupSinkPass();

std::unique_ptr<mlir::Pass> createMaterializeDebugInfoPass();

std::unique_ptr<mlir::Pass> createLintingPass();

/// Generate the code for registering passes.
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/FIRRTL/Passes.h.inc"
Expand Down
11 changes: 11 additions & 0 deletions include/circt/Dialect/FIRRTL/Passes.td
Original file line number Diff line number Diff line change
Expand Up @@ -806,4 +806,15 @@ def MaterializeDebugInfo :
let dependentDialects = ["debug::DebugDialect"];
}

def Lint :
Pass<"firrtl-lint", "firrtl::FModuleOp"> {
let summary = "An analysis pass to detect static simulation failures.";
let description = [{
This pass detects operations that will trivially fail any simulation.
Currently it detects assertions whose predicate condition can be statically
inferred to be false. The pass emits error on such failing ops.
}];
let constructor = "circt::firrtl::createLintingPass()";
}

#endif // CIRCT_DIALECT_FIRRTL_PASSES_TD
1 change: 1 addition & 0 deletions lib/Dialect/FIRRTL/Transforms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ add_circt_dialect_library(CIRCTFIRRTLTransforms
InjectDUTHierarchy.cpp
InnerSymbolDCE.cpp
LegacyWiring.cpp
Lint.cpp
LowerAnnotations.cpp
LowerCHIRRTL.cpp
LowerClasses.cpp
Expand Down
71 changes: 71 additions & 0 deletions lib/Dialect/FIRRTL/Transforms/Lint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//===- Lint.cpp -------------------------------------------------*- 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
//
//===----------------------------------------------------------------------===//

#include "PassDetails.h"
#include "mlir/IR/Threading.h"
#include "llvm/ADT/APSInt.h"

using namespace mlir;
using namespace circt;
using namespace firrtl;

namespace {
struct LintPass : public LintBase<LintPass> {
void runOnOperation() override {
auto fModule = getOperation();
auto walkResult = fModule.walk<WalkOrder::PreOrder>([&](Operation *op) {
if (isa<WhenOp>(op))
return WalkResult::skip();
if (isa<AssertOp, VerifAssertIntrinsicOp>(op))
if (checkAssert(op).failed())
return WalkResult::interrupt();

return WalkResult::advance();
});
if (walkResult.wasInterrupted())
return signalPassFailure();

markAllAnalysesPreserved();
};
prithayan marked this conversation as resolved.
Show resolved Hide resolved

LogicalResult checkAssert(Operation *op) {
Value predicate;
if (auto a = dyn_cast<AssertOp>(op)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be an actual analysis that is inspected by the pass, where diagnostics are emitted?
Fine as-is, but consider especially as this grows.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, this was simple enough, but we could consider it if an analysis is required for other uses.

if (auto constant = a.getEnable().getDefiningOp<firrtl::ConstantOp>())
if (constant.getValue().isOne()) {
predicate = a.getPredicate();
}
} else if (auto a = dyn_cast<VerifAssertIntrinsicOp>(op))
predicate = a.getProperty();

if (!predicate)
return success();
if (auto constant = predicate.getDefiningOp<firrtl::ConstantOp>())
if (constant.getValue().isZero())
return op->emitOpError(
"is guaranteed to fail simulation, as the predicate is "
"constant false")
.attachNote(constant.getLoc())
<< "constant defined here";

if (auto reset = predicate.getDefiningOp<firrtl::AsUIntPrimOp>())
if (firrtl::type_isa<ResetType, AsyncResetType>(
reset.getInput().getType()))
return op->emitOpError("is guaranteed to fail simulation, as the "
"predicate is a reset signal")
.attachNote(reset.getInput().getLoc())
<< "reset signal defined here";

return success();
}
};
} // namespace

std::unique_ptr<Pass> firrtl::createLintingPass() {
return std::make_unique<LintPass>();
}
4 changes: 4 additions & 0 deletions lib/Firtool/Firtool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,10 @@ LogicalResult firtool::populateLowFIRRTLToHW(mlir::PassManager &pm,

pm.nest<firrtl::CircuitOp>().addPass(firrtl::createLowerClassesPass());

// Check for static asserts.
pm.nest<firrtl::CircuitOp>().nest<firrtl::FModuleOp>().addPass(
circt::firrtl::createLintingPass());

pm.addPass(createLowerFIRRTLToHWPass(
opt.enableAnnotationWarning.getValue(),
opt.emitChiselAssertsAsSVA.getValue(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ circuit Foo : %[[

module Foo :
input clock: Clock
input a_in: UInt<1>

wire a: UInt<1>
a is invalid
a <= a_in

inst companion of Companion
companion.clock <= clock
Expand Down
82 changes: 82 additions & 0 deletions test/Dialect/FIRRTL/lint.mlir
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// RUN: circt-opt --pass-pipeline='builtin.module(firrtl.circuit(firrtl.module(firrtl-lint)))' --verify-diagnostics --split-input-file %s | FileCheck %s

firrtl.circuit "lint_tests" {
// CHECK: firrtl.module @lint_tests
firrtl.module @lint_tests(in %en: !firrtl.uint<1>, in %pred: !firrtl.uint<1>, in %reset: !firrtl.reset, in %clock: !firrtl.clock) {
%0 = firrtl.asUInt %reset : (!firrtl.reset) -> !firrtl.uint<1>
// CHECK: firrtl.assert
firrtl.assert %clock, %pred, %en, "valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false}
// CHECK: firrtl.assert
firrtl.assert %clock, %0, %en, "valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false}
%false = firrtl.constant 0 : !firrtl.uint<1>
// CHECK: firrtl.assert
firrtl.assert %clock, %false, %en, "valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false}
// CHECK: firrtl.int.verif.assert
firrtl.int.verif.assert %pred : !firrtl.uint<1>
// CHECK: firrtl.int.verif.assert
firrtl.when %en : !firrtl.uint<1> {
firrtl.int.verif.assert %false : !firrtl.uint<1>
}
}
}

// -----

firrtl.circuit "assert_const" {
firrtl.module @assert_const(in %clock: !firrtl.clock) {
%true = firrtl.constant 1 : !firrtl.uint<1>
// expected-note @below {{constant defined here}}
%false = firrtl.constant 0 : !firrtl.uint<1>
// expected-error @below {{'firrtl.assert' op is guaranteed to fail simulation, as the predicate is constant false}}
firrtl.assert %clock, %false, %true, "valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false}
}
}

// -----

firrtl.circuit "assert_reset" {
// expected-note @below {{reset signal defined here}}
firrtl.module @assert_reset(in %en: !firrtl.uint<1>, in %pred: !firrtl.uint<1>, in %reset: !firrtl.reset, in %reset_async: !firrtl.asyncreset, in %clock: !firrtl.clock) {
%0 = firrtl.asUInt %reset : (!firrtl.reset) -> !firrtl.uint<1>
%true = firrtl.constant 1 : !firrtl.uint<1>
%false = firrtl.constant 0 : !firrtl.uint<1>
// expected-error @below {{op is guaranteed to fail simulation, as the predicate is a reset signal}}
firrtl.assert %clock, %0, %true, "valid" : !firrtl.clock, !firrtl.uint<1>, !firrtl.uint<1> {eventControl = 0 : i32, isConcurrent = false}
}
}

// -----

firrtl.circuit "assert_const2" {
firrtl.module @assert_const2() {
// expected-note @below {{constant defined here}}
%false = firrtl.constant 0 : !firrtl.uint<1>
// expected-error @below {{op is guaranteed to fail simulation, as the predicate is constant false}}
firrtl.int.verif.assert %false : !firrtl.uint<1>
}
}

// -----

firrtl.circuit "assert_reset2" {
// expected-note @below {{reset signal defined here}}
firrtl.module @assert_reset2(in %en: !firrtl.uint<1>, in %pred: !firrtl.uint<1>, in %reset: !firrtl.reset, in %reset_async: !firrtl.asyncreset, in %clock: !firrtl.clock) {
%0 = firrtl.asUInt %reset : (!firrtl.reset) -> !firrtl.uint<1>
// expected-error @below {{op is guaranteed to fail simulation, as the predicate is a reset signal}}
firrtl.int.verif.assert %0 : !firrtl.uint<1>
}
}

// -----

firrtl.circuit "assert_reset3" {
firrtl.declgroup @GroupFoo bind {}
// expected-note @below {{reset signal defined here}}
firrtl.module @assert_reset3(in %en: !firrtl.uint<1>, in %pred: !firrtl.uint<1>, in %reset: !firrtl.reset, in %reset_async: !firrtl.asyncreset, in %clock: !firrtl.clock) {
%0 = firrtl.asUInt %reset : (!firrtl.reset) -> !firrtl.uint<1>
firrtl.group @GroupFoo {
// expected-error @below {{op is guaranteed to fail simulation, as the predicate is a reset signal}}
firrtl.int.verif.assert %0 : !firrtl.uint<1>
}
}
}