Skip to content

Commit

Permalink
[ImportVerilog] Add $display/$write/$info/$warning/$error/$fatal (#7642)
Browse files Browse the repository at this point in the history
Add support for the `$display`, `$write`, `$info`, `$warning`, `$error`,
and `$fatal` system tasks.

These tasks are pretty complicated since they involve string formatting.
Verilog has quite a few format specifiers and very strange rules for
them. Luckily, Slang contains a handy utility that parses format strings
and delegates handling of arguments and intermittent pieces of text to
callbacks.

I've decided to follow the same IR design as the printing op
in the Sim dialect: each format specifier is turned into a dedicated op
that captures all relevant format options, plus additional literals for
the text in between interpolated values, and concatenate everything into
a single `!moore.format_string`. (Shoutout to @fzi-hielscher for
trailblazing the nice design for `sim.print`!) This is handled in a new
`FormatStrings.cpp` file inside of ImportVerilog.

The actual system tasks are mapped to new `moore.builtin.display` and
`moore.builtin.severity` ops. These handle only the printing of the
message in question, plus potential error bookkeeping. The `$fatal`
system task creates additional `moore.builtin.finish_message` and
`moore.builtin.finish` ops to represent its implicit call to `$finish`.

The implementation also handles the strange `$displayb`, `$displayo`,
`$displayh`, `$writeb`, `$writeo`, and `$writeh` flavors of the tasks,
where the suffix indicates the default format to use for arguments that
are not covered by a format string literal. SystemVerilog is weird.

Thanks @hailongSun2000 for your prior work on this!

Co-authored-by: Hailong Sun <hailong.sun@terapines.com>
  • Loading branch information
fabianschuiki and hailongSun2000 authored Sep 27, 2024
1 parent 640dd3b commit 224bc57
Show file tree
Hide file tree
Showing 10 changed files with 675 additions and 53 deletions.
124 changes: 124 additions & 0 deletions include/circt/Dialect/Moore/MooreOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1357,6 +1357,84 @@ def CoverOp : ImmediateAssertOp<"cover">{
let summary = "Monitor the coverage information.";
}

//===----------------------------------------------------------------------===//
// Format Strings
//===----------------------------------------------------------------------===//

def FormatLiteralOp : MooreOp<"fmt.literal", [Pure]> {
let summary = "A constant string fragment";
let description = [{
Creates a constant string fragment to be used as a format string. The
literal is printed as is, without any further escaping or processing of its
characters.
}];
let arguments = (ins StrAttr:$literal);
let results = (outs FormatStringType:$result);
let assemblyFormat = "$literal attr-dict";
}

def FormatConcatOp : MooreOp<"fmt.concat", [Pure]> {
let summary = "Concatenate string fragments";
let description = [{
Concatenates an arbitrary number of format string into one larger format
string. The strings are concatenated from left to right, with the first
operand appearing at the left start of the result string, and the last
operand appearing at the right end. Produces an empty string if no inputs
are provided.
}];
let arguments = (ins Variadic<FormatStringType>:$inputs);
let results = (outs FormatStringType:$result);
let assemblyFormat = "` ` `(` $inputs `)` attr-dict";
}

def FmtDec : I32EnumAttrCase<"Decimal", 0, "decimal">;
def FmtBin : I32EnumAttrCase<"Binary", 1, "binary">;
def FmtOct : I32EnumAttrCase<"Octal", 2, "octal">;
def FmtHexL : I32EnumAttrCase<"HexLower", 3, "hex_lower">;
def FmtHexU : I32EnumAttrCase<"HexUpper", 4, "hex_upper">;
def IntFormatAttr : I32EnumAttr<"IntFormat", "Integer format",
[FmtDec, FmtBin, FmtOct, FmtHexL, FmtHexU]> {
let cppNamespace = "circt::moore";
}

def AlignRight : I32EnumAttrCase<"Right", 0, "right">;
def AlignLeft : I32EnumAttrCase<"Left", 1, "left">;
def IntAlignAttr : I32EnumAttr<"IntAlign", "Integer alignment",
[AlignRight, AlignLeft]> {
let cppNamespace = "circt::moore";
}

def PadSpace : I32EnumAttrCase<"Space", 0, "space">;
def PadZero : I32EnumAttrCase<"Zero", 1, "zero">;
def IntPaddingAttr : I32EnumAttr<"IntPadding", "Integer alignment",
[PadSpace, PadZero]> {
let cppNamespace = "circt::moore";
}

def FormatIntOp : MooreOp<"fmt.int", [Pure]> {
let summary = "Format an integer value";
let description = [{
Format an integer value as a string according to the specified format.

See IEEE 1800-2017 § 21.2.1.2 "Format specifications".
}];
let arguments = (ins
IntType:$value,
IntFormatAttr:$format,
I32Attr:$width,
IntAlignAttr:$alignment,
IntPaddingAttr:$padding
);
let results = (outs FormatStringType:$result);
let assemblyFormat = [{
$format $value `,`
`width` $width `,`
`align` $alignment `,`
`pad` $padding
attr-dict `:` type($value)
}];
}

//===----------------------------------------------------------------------===//
// Builtin System Tasks and Functions
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -1420,4 +1498,50 @@ def FinishMessageBIOp : Builtin<"finish_message"> {
let assemblyFormat = "$withStats attr-dict";
}

//===----------------------------------------------------------------------===//
// Severity and Display
//===----------------------------------------------------------------------===//

def DisplayBIOp : Builtin<"display"> {
let summary = "Print a text message";
let description = [{
Prints the given format string to the standard text output of the simulator.
In most cases this should be stdout. This corresponds to the `$display` and
`$write` system tasks. Message formatting is handled by `moore.fmt.*` ops.

See IEEE 1800-2017 § 21.2 "Display system tasks".
}];
let arguments = (ins FormatStringType:$message);
let assemblyFormat = "$message attr-dict";
}

def SeverityInfo : I32EnumAttrCase<"Info", 0, "info">;
def SeverityWarning : I32EnumAttrCase<"Warning", 1, "warning">;
def SeverityError : I32EnumAttrCase<"Error", 2, "error">;
def SeverityFatal : I32EnumAttrCase<"Fatal", 3, "fatal">;
def SeverityAttr : I32EnumAttr<"Severity", "Diagnostic severity", [
SeverityInfo, SeverityWarning, SeverityError, SeverityFatal
]> {
let cppNamespace = "circt::moore";
}

def SeverityBIOp : Builtin<"severity"> {
let summary = "Print a diagnostic message";
let description = [{
Prints the given format string to the standard diagnostic output of the
simulator. In most cases this should be stderr. This corresponds to the
`$info`, `$warning`, `$error`, and `$fatal` system tasks. Message formatting
is handled by `moore.fmt.*` ops. This only handles the message printing of
`$fatal`; printing of the additional statistics and the call to `$finish`
must be done through the `finish_message` and `finish` ops.

See IEEE 1800-2017 § 20.10 "Severity tasks".
}];
let arguments = (ins
SeverityAttr:$severity,
FormatStringType:$message
);
let assemblyFormat = "$severity $message attr-dict";
}

#endif // CIRCT_DIALECT_MOORE_MOOREOPS
15 changes: 15 additions & 0 deletions include/circt/Dialect/Moore/MooreTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,21 @@ def RefType : MooreTypeDef<"Ref", [
}];
}

//===----------------------------------------------------------------------===//
// Format String
//===----------------------------------------------------------------------===//

def FormatStringType : MooreTypeDef<"FormatString"> {
let mnemonic = "format_string";
let summary = "a format string type";
let description = [{
An interpolated string produced by one of the string formatting operations.
It is used to parse format strings present in Verilog source text and
represent them as a sequence of IR operations that specify the formatting of
individual arguments.
}];
}

//===----------------------------------------------------------------------===//
// Constraints
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions lib/Conversion/ImportVerilog/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ endif ()

add_circt_translation_library(CIRCTImportVerilog
Expressions.cpp
FormatStrings.cpp
ImportVerilog.cpp
Statements.cpp
Structure.cpp
Expand Down
91 changes: 38 additions & 53 deletions lib/Conversion/ImportVerilog/Expressions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,31 +37,6 @@ struct RvalueExprVisitor {
RvalueExprVisitor(Context &context, Location loc)
: context(context), loc(loc), builder(context.builder) {}

/// Helper function to convert a value to its simple bit vector
/// representation, if it has one. Otherwise returns null.
Value convertToSimpleBitVector(Value value) {
if (!value)
return {};
if (isa<moore::IntType>(value.getType()))
return value;

// Some operations in Slang's AST, for example bitwise or `|`, don't cast
// packed struct/array operands to simple bit vectors but directly operate
// on the struct/array. Since the corresponding IR ops operate only on
// simple bit vectors, insert a conversion in this case.
if (auto packed = dyn_cast<moore::PackedType>(value.getType())) {
if (auto bits = packed.getBitSize()) {
auto sbvType =
moore::IntType::get(value.getContext(), *bits, packed.getDomain());
return builder.create<moore::ConversionOp>(loc, sbvType, value);
}
}

mlir::emitError(loc, "expression of type ")
<< value.getType() << " cannot be cast to a simple bit vector";
return {};
}

// Handle references to the left-hand side of a parent assignment.
Value visit(const slang::ast::LValueReferenceExpression &expr) {
assert(!context.lvalueStack.empty() && "parent assignments push lvalue");
Expand Down Expand Up @@ -132,7 +107,7 @@ struct RvalueExprVisitor {
// to a reduction op, and optionally invert the result.
template <class ConcreteOp>
Value createReduction(Value arg, bool invert) {
arg = convertToSimpleBitVector(arg);
arg = context.convertToSimpleBitVector(arg);
if (!arg)
return {};
Value result = builder.create<ConcreteOp>(loc, arg);
Expand Down Expand Up @@ -173,16 +148,16 @@ struct RvalueExprVisitor {
// `+a` is simply `a`, but converted to a simple bit vector type since
// this is technically an arithmetic operation.
case UnaryOperator::Plus:
return convertToSimpleBitVector(arg);
return context.convertToSimpleBitVector(arg);

case UnaryOperator::Minus:
arg = convertToSimpleBitVector(arg);
arg = context.convertToSimpleBitVector(arg);
if (!arg)
return {};
return builder.create<moore::NegOp>(loc, arg);

case UnaryOperator::BitwiseNot:
arg = convertToSimpleBitVector(arg);
arg = context.convertToSimpleBitVector(arg);
if (!arg)
return {};
return builder.create<moore::NotOp>(loc, arg);
Expand Down Expand Up @@ -224,10 +199,10 @@ struct RvalueExprVisitor {
// pass them into a binary op.
template <class ConcreteOp>
Value createBinary(Value lhs, Value rhs) {
lhs = convertToSimpleBitVector(lhs);
lhs = context.convertToSimpleBitVector(lhs);
if (!lhs)
return {};
rhs = convertToSimpleBitVector(rhs);
rhs = context.convertToSimpleBitVector(rhs);
if (!rhs)
return {};
return builder.create<ConcreteOp>(loc, lhs, rhs);
Expand Down Expand Up @@ -384,8 +359,8 @@ struct RvalueExprVisitor {
case BinaryOperator::ArithmeticShiftRight: {
// The `>>>` operator is an arithmetic right shift if the LHS operand is
// signed, or a logical right shift if the operand is unsigned.
lhs = convertToSimpleBitVector(lhs);
rhs = convertToSimpleBitVector(rhs);
lhs = context.convertToSimpleBitVector(lhs);
rhs = context.convertToSimpleBitVector(rhs);
if (!lhs || !rhs)
return {};
if (expr.type->isSigned())
Expand Down Expand Up @@ -415,7 +390,7 @@ struct RvalueExprVisitor {
auto value = context.convertRvalueExpression(*operand);
if (!value)
continue;
value = convertToSimpleBitVector(value);
value = context.convertToSimpleBitVector(value);
operands.push_back(value);
}
return builder.create<moore::ConcatOp>(loc, operands);
Expand Down Expand Up @@ -538,8 +513,8 @@ struct RvalueExprVisitor {

// Handle set membership operator.
Value visit(const slang::ast::InsideExpression &expr) {
auto lhs =
convertToSimpleBitVector(context.convertRvalueExpression(expr.left()));
auto lhs = context.convertToSimpleBitVector(
context.convertRvalueExpression(expr.left()));
if (!lhs)
return {};
// All conditions for determining whether it is inside.
Expand All @@ -553,9 +528,9 @@ struct RvalueExprVisitor {
if (const auto *openRange =
listExpr->as_if<slang::ast::OpenRangeExpression>()) {
// Handle ranges.
auto lowBound = convertToSimpleBitVector(
auto lowBound = context.convertToSimpleBitVector(
context.convertRvalueExpression(openRange->left()));
auto highBound = convertToSimpleBitVector(
auto highBound = context.convertToSimpleBitVector(
context.convertRvalueExpression(openRange->right()));
if (!lowBound || !highBound)
return {};
Expand Down Expand Up @@ -587,7 +562,7 @@ struct RvalueExprVisitor {
loc, "only simple bit vectors supported in 'inside' expressions");
return {};
}
auto value = convertToSimpleBitVector(
auto value = context.convertToSimpleBitVector(
context.convertRvalueExpression(*listExpr));
if (!value)
return {};
Expand Down Expand Up @@ -818,19 +793,6 @@ struct LvalueExprVisitor {
LvalueExprVisitor(Context &context, Location loc)
: context(context), loc(loc), builder(context.builder) {}

/// Helper function to convert a value to its simple bit vector
/// representation, if it has one. Otherwise returns null.
Value convertToSimpleBitVector(Value value) {
if (!value)
return {};
if (isa<moore::IntType>(
cast<moore::RefType>(value.getType()).getNestedType()))
return value;
mlir::emitError(loc, "expression of type ")
<< value.getType() << " cannot be cast to a simple bit vector";
return {};
}

// Handle named values, such as references to declared variables.
Value visit(const slang::ast::NamedValueExpression &expr) {
if (auto value = context.valueSymbols.lookup(&expr.symbol))
Expand All @@ -848,7 +810,6 @@ struct LvalueExprVisitor {
auto value = context.convertLvalueExpression(*operand);
if (!value)
continue;
value = convertToSimpleBitVector(value);
operands.push_back(value);
}
return builder.create<moore::ConcatRefOp>(loc, operands);
Expand Down Expand Up @@ -1057,3 +1018,27 @@ Value Context::convertToBool(Value value, Domain domain) {
return value;
return builder.create<moore::ConversionOp>(value.getLoc(), type, value);
}

Value Context::convertToSimpleBitVector(Value value) {
if (!value)
return {};
if (isa<moore::IntType>(value.getType()))
return value;

// Some operations in Slang's AST, for example bitwise or `|`, don't cast
// packed struct/array operands to simple bit vectors but directly operate
// on the struct/array. Since the corresponding IR ops operate only on
// simple bit vectors, insert a conversion in this case.
if (auto packed = dyn_cast<moore::PackedType>(value.getType())) {
if (auto bits = packed.getBitSize()) {
auto sbvType =
moore::IntType::get(value.getContext(), *bits, packed.getDomain());
return builder.create<moore::ConversionOp>(value.getLoc(), sbvType,
value);
}
}

mlir::emitError(value.getLoc()) << "expression of type " << value.getType()
<< " cannot be cast to a simple bit vector";
return {};
}
Loading

0 comments on commit 224bc57

Please sign in to comment.