[ImportVerilog] Add support for $stop/$finish/$exit (#7634)

Add support for the simulation control system tasks `$stop`, `$finish`,
and `$exit`. Also add corresponding ops to the Moore dialect that handle
the orthogonal pieces of functionality represented by these tasks.
This commit is contained in:
Fabian Schuiki 2024-09-26 22:06:24 -07:00 committed by GitHub
parent 5c2520e4af
commit d42d00e6ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 195 additions and 8 deletions

View File

@ -187,6 +187,18 @@ def ReturnOp : MooreOp<"return", [
let assemblyFormat = [{ attr-dict }];
}
def UnreachableOp : MooreOp<"unreachable", [Terminator]> {
let summary = "Terminates a block as unreachable";
let description = [{
The `moore.unreachable` op is used to indicate that control flow never
reaches the end of a block. This is useful for operations such as `$fatal`
which never return as they cause the simulator to shut down. Behavior is
undefined if control actually _does_ reach this terminator, but should
probably crash the process with a useful error message.
}];
let assemblyFormat = "attr-dict";
}
//===----------------------------------------------------------------------===//
// Declarations
//===----------------------------------------------------------------------===//
@ -1345,4 +1357,67 @@ def CoverOp : ImmediateAssertOp<"cover">{
let summary = "Monitor the coverage information.";
}
//===----------------------------------------------------------------------===//
// Builtin System Tasks and Functions
//===----------------------------------------------------------------------===//
class Builtin<string mnemonic, list<Trait> traits = []> :
MooreOp<"builtin." # mnemonic, traits>;
//===----------------------------------------------------------------------===//
// Simulation Control
//===----------------------------------------------------------------------===//
def StopBIOp : Builtin<"stop"> {
let summary = "Suspend simulation";
let description = [{
Corresponds to the `$stop` system task. Causes the simulation to be
suspended but the simulator does not exit. Printing of the optional
diagnostic message is handled by the `finish_message` op.
See IEEE 1800-2017 § 20.2 "Simulation control system tasks".
}];
let assemblyFormat = "attr-dict";
}
def FinishBIOp : Builtin<"finish"> {
let summary = "Exit simulation";
let description = [{
Corresponds to the `$finish` system task. Causes the simulator to exit and
pass control back to the host operating system. Printing of the optional
diagnostic message is handled by the `finish_message` op.
The exit code argument of this op is not directly accessible from Verilog,
but is used to distinguish between the implicit `$finish` call in `$fatal`
and an explicit `$finish` called by the user.
See IEEE 1800-2017 § 20.2 "Simulation control system tasks".
}];
let arguments = (ins I8Attr:$exitCode);
let assemblyFormat = "$exitCode attr-dict";
}
def FinishMessageBIOp : Builtin<"finish_message"> {
let summary = "Print diagnostic message for the finish system task";
let description = [{
Prints the diagnostic message for `$stop`, `$finish`, `$exit`, and `$fatal`
mandated by the SystemVerilog standard. The exact message is controlled by
the verbosity parameter as specified in the standard:
- The absence of this op corresponds to `$finish(0)`.
- `moore.builtin.finish_message false` corresponds to `$finish(1)`.
- `moore.builtin.finish_message true` corresponds to `$finish(2)`.
The `withStats` argument controls how detailed the printed message is:
- **false**: Print simulation time and location.
- **true**: Print simulation time, location, and statistics about the memory
and CPU usage of the simulator.
See IEEE 1800-2017 § 20.2 "Simulation control system tasks".
}];
let arguments = (ins I1Attr:$withStats);
let assemblyFormat = "$withStats attr-dict";
}
#endif // CIRCT_DIALECT_MOORE_MOOREOPS

View File

@ -82,11 +82,7 @@ struct RvalueExprVisitor {
}
// Try to materialize constant values directly.
using slang::ast::EvalFlags;
slang::ast::EvalContext evalContext(context.compilation,
EvalFlags::CacheResults |
EvalFlags::SpecparamsAllowed);
auto constant = expr.eval(evalContext);
auto constant = context.evaluateConstant(expr);
if (auto value = context.materializeConstant(constant, *expr.type, loc))
return value;
@ -792,9 +788,8 @@ struct RvalueExprVisitor {
}
Value visit(const slang::ast::ReplicatedAssignmentPatternExpression &expr) {
slang::ast::EvalContext evalContext(context.compilation,
slang::ast::EvalFlags::CacheResults);
auto count = expr.count().eval(evalContext).integer().as<unsigned>();
auto count =
context.evaluateConstant(expr.count()).integer().as<unsigned>();
assert(count && "Slang guarantees constant non-zero replication count");
return visitAssignmentPattern(expr, *count);
}
@ -1043,6 +1038,14 @@ Value Context::materializeConstant(const slang::ConstantValue &constant,
return {};
}
slang::ConstantValue
Context::evaluateConstant(const slang::ast::Expression &expr) {
using slang::ast::EvalFlags;
slang::ast::EvalContext evalContext(
compilation, EvalFlags::CacheResults | EvalFlags::SpecparamsAllowed);
return expr.eval(evalContext);
}
/// Helper function to convert a value to its "truthy" boolean value and
/// convert it to the given domain.
Value Context::convertToBool(Value value, Domain domain) {

View File

@ -125,6 +125,9 @@ struct Context {
Value materializeConstant(const slang::ConstantValue &constant,
const slang::ast::Type &type, Location loc);
/// Evaluate the constant value of an expression.
slang::ConstantValue evaluateConstant(const slang::ast::Expression &expr);
const ImportVerilogOptions &options;
slang::ast::Compilation &compilation;
mlir::ModuleOp intoModuleOp;

View File

@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "ImportVerilogInternals.h"
#include "slang/ast/SystemSubroutine.h"
#include "llvm/ADT/ScopeExit.h"
using namespace mlir;
@ -63,6 +64,19 @@ struct StmtVisitor {
// Handle expression statements.
LogicalResult visit(const slang::ast::ExpressionStatement &stmt) {
// Special handling for calls to system tasks that return no result value.
if (const auto *call = stmt.expr.as_if<slang::ast::CallExpression>()) {
if (const auto *info =
std::get_if<slang::ast::CallExpression::SystemCallInfo>(
&call->subroutine)) {
auto handled = visitSystemCall(stmt, *call, *info);
if (failed(handled))
return failure();
if (handled == true)
return success();
}
}
auto value = context.convertRvalueExpression(stmt.expr);
if (!value)
return failure();
@ -533,6 +547,56 @@ struct StmtVisitor {
return success();
}
/// Handle the subset of system calls that return no result value. Return
/// true if the called system task could be handled, false otherwise. Return
/// failure if an error occurred.
FailureOr<bool>
visitSystemCall(const slang::ast::ExpressionStatement &stmt,
const slang::ast::CallExpression &expr,
const slang::ast::CallExpression::SystemCallInfo &info) {
const auto &subroutine = *info.subroutine;
auto args = expr.arguments();
if (subroutine.name == "$stop") {
createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
builder.create<moore::StopBIOp>(loc);
return true;
}
if (subroutine.name == "$finish") {
createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
builder.create<moore::FinishBIOp>(loc, 0);
builder.create<moore::UnreachableOp>(loc);
setTerminated();
return true;
}
if (subroutine.name == "$exit") {
// Calls to `$exit` from outside a `program` are ignored. Since we don't
// yet support programs, there is nothing to do here.
// TODO: Fix this once we support programs.
return true;
}
// Give up on any other system tasks. These will be tried again as an
// expression later.
return false;
}
/// Create the optional diagnostic message print for finish-like ops.
void createFinishMessage(const slang::ast::Expression *verbosityExpr) {
unsigned verbosity = 1;
if (verbosityExpr) {
auto value =
context.evaluateConstant(*verbosityExpr).integer().as<unsigned>();
assert(value && "Slang guarantees constant verbosity parameter");
verbosity = *value;
}
if (verbosity == 0)
return;
builder.create<moore::FinishMessageBIOp>(loc, verbosity > 1);
}
/// Emit an error for all other statements.
template <typename T>
LogicalResult visit(T &&stmt) {

View File

@ -2025,3 +2025,33 @@ package ParamPackage;
// CHECK: dbg.variable "ParamPackage::param2", [[TMP]] : !moore.i32
localparam int param2 = 9001;
endpackage
// CHECK-LABEL: func.func private @SimulationControlBuiltins(
function void SimulationControlBuiltins(bit x);
// CHECK: moore.builtin.finish_message false
// CHECK: moore.builtin.stop
$stop;
// CHECK-NOT: moore.builtin.finish_message
// CHECK: moore.builtin.stop
$stop(0);
// CHECK: moore.builtin.finish_message true
// CHECK: moore.builtin.stop
$stop(2);
// CHECK: moore.builtin.finish_message false
// CHECK: moore.builtin.finish 0
// CHECK: moore.unreachable
if (x) $finish;
// CHECK-NOT: moore.builtin.finish_message
// CHECK: moore.builtin.finish 0
// CHECK: moore.unreachable
if (x) $finish(0);
// CHECK: moore.builtin.finish_message true
// CHECK: moore.builtin.finish 0
// CHECK: moore.unreachable
if (x) $finish(2);
// Ignore `$exit` until we have support for programs.
// CHECK-NOT: moore.builtin.finish
$exit;
endfunction

View File

@ -329,3 +329,15 @@ func.func @WaitEvent(%arg0: !moore.i1, %arg1: !moore.i1) {
// CHECK: }
return
}
// CHECK-LABEL: func.func @SimulationControlBuiltins
func.func @SimulationControlBuiltins() {
// CHECK: moore.builtin.stop
moore.builtin.stop
// CHECK: moore.builtin.finish 42
moore.builtin.finish 42
// CHECK: moore.builtin.finish_message false
moore.builtin.finish_message false
// CHECK: moore.unreachable
moore.unreachable
}