mirror of https://github.com/llvm/circt.git
[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:
parent
5c2520e4af
commit
d42d00e6ee
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue