mirror of https://github.com/llvm/circt.git
[ImportVerilog] Add $display/$write/$info/$warning/$error/$fatal (#7642)
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>
This commit is contained in:
parent
640dd3ba13
commit
224bc57c2c
|
@ -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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -30,6 +30,7 @@ endif ()
|
|||
|
||||
add_circt_translation_library(CIRCTImportVerilog
|
||||
Expressions.cpp
|
||||
FormatStrings.cpp
|
||||
ImportVerilog.cpp
|
||||
Statements.cpp
|
||||
Structure.cpp
|
||||
|
|
|
@ -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");
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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())
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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 {};
|
||||
|
@ -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 {};
|
||||
|
@ -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))
|
||||
|
@ -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);
|
||||
|
@ -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 {};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
//===- FormatStrings.cpp - Verilog format string conversion ---------------===//
|
||||
//
|
||||
// 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 "ImportVerilogInternals.h"
|
||||
#include "slang/text/SFormat.h"
|
||||
|
||||
using namespace mlir;
|
||||
using namespace circt;
|
||||
using namespace ImportVerilog;
|
||||
using moore::IntAlign;
|
||||
using moore::IntFormat;
|
||||
using moore::IntPadding;
|
||||
using slang::SFormat::FormatOptions;
|
||||
|
||||
namespace {
|
||||
struct FormatStringParser {
|
||||
Context &context;
|
||||
OpBuilder &builder;
|
||||
/// The remaining arguments to be parsed.
|
||||
ArrayRef<const slang::ast::Expression *> arguments;
|
||||
/// The current location to use for ops and diagnostics.
|
||||
Location loc;
|
||||
/// The default format for integer arguments not covered by a format string
|
||||
/// literal.
|
||||
IntFormat defaultFormat;
|
||||
/// The interpolated string fragments that will be concatenated using a
|
||||
/// `moore.fmt.concat` op.
|
||||
SmallVector<Value> fragments;
|
||||
|
||||
FormatStringParser(Context &context,
|
||||
ArrayRef<const slang::ast::Expression *> arguments,
|
||||
Location loc, IntFormat defaultFormat)
|
||||
: context(context), builder(context.builder), arguments(arguments),
|
||||
loc(loc), defaultFormat(defaultFormat) {}
|
||||
|
||||
/// Entry point to the format string parser.
|
||||
FailureOr<Value> parse(bool appendNewline) {
|
||||
while (!arguments.empty()) {
|
||||
const auto &arg = *arguments[0];
|
||||
arguments = arguments.drop_front();
|
||||
if (arg.kind == slang::ast::ExpressionKind::EmptyArgument)
|
||||
continue;
|
||||
loc = context.convertLocation(arg.sourceRange);
|
||||
if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
|
||||
if (failed(parseFormat(lit->getValue())))
|
||||
return failure();
|
||||
} else {
|
||||
if (failed(emitDefault(arg)))
|
||||
return failure();
|
||||
}
|
||||
}
|
||||
|
||||
// Append the optional newline.
|
||||
if (appendNewline)
|
||||
emitLiteral("\n");
|
||||
|
||||
// Concatenate all string fragments into one formatted string, or return an
|
||||
// empty literal if no fragments were generated.
|
||||
if (fragments.empty())
|
||||
return Value{};
|
||||
if (fragments.size() == 1)
|
||||
return fragments[0];
|
||||
return builder.create<moore::FormatConcatOp>(loc, fragments).getResult();
|
||||
}
|
||||
|
||||
/// Parse a format string literal and consume and format the arguments
|
||||
/// corresponding to the format specifiers it contains.
|
||||
LogicalResult parseFormat(StringRef format) {
|
||||
bool anyFailure = false;
|
||||
auto onText = [&](auto text) {
|
||||
if (anyFailure)
|
||||
return;
|
||||
emitLiteral(text);
|
||||
};
|
||||
auto onArg = [&](auto specifier, auto offset, auto len,
|
||||
const auto &options) {
|
||||
if (anyFailure)
|
||||
return;
|
||||
if (failed(emitArgument(specifier, format.substr(offset, len), options)))
|
||||
anyFailure = true;
|
||||
};
|
||||
auto onError = [&](auto, auto, auto, auto) {
|
||||
assert(false && "Slang should have already reported all errors");
|
||||
};
|
||||
slang::SFormat::parse(format, onText, onArg, onError);
|
||||
return failure(anyFailure);
|
||||
}
|
||||
|
||||
/// Emit a string literal that requires no additional formatting.
|
||||
void emitLiteral(StringRef literal) {
|
||||
fragments.push_back(builder.create<moore::FormatLiteralOp>(loc, literal));
|
||||
}
|
||||
|
||||
/// Consume the next argument from the list and emit it according to the given
|
||||
/// format specifier.
|
||||
LogicalResult emitArgument(char specifier, StringRef fullSpecifier,
|
||||
const FormatOptions &options) {
|
||||
auto specifierLower = std::tolower(specifier);
|
||||
|
||||
// Special handling for format specifiers that consume no argument.
|
||||
if (specifierLower == 'm' || specifierLower == 'l')
|
||||
return mlir::emitError(loc)
|
||||
<< "unsupported format specifier `" << fullSpecifier << "`";
|
||||
|
||||
// Consume the next argument, which will provide the value to be
|
||||
// formatted.
|
||||
assert(!arguments.empty() && "Slang guarantees correct arg count");
|
||||
const auto &arg = *arguments[0];
|
||||
arguments = arguments.drop_front();
|
||||
auto argLoc = context.convertLocation(arg.sourceRange);
|
||||
|
||||
// Handle the different formatting options.
|
||||
// See IEEE 1800-2017 § 21.2.1.2 "Format specifications".
|
||||
switch (specifierLower) {
|
||||
case 'b':
|
||||
return emitInteger(arg, options, IntFormat::Binary);
|
||||
case 'o':
|
||||
return emitInteger(arg, options, IntFormat::Octal);
|
||||
case 'd':
|
||||
return emitInteger(arg, options, IntFormat::Decimal);
|
||||
case 'h':
|
||||
case 'x':
|
||||
return emitInteger(arg, options,
|
||||
std::isupper(specifier) ? IntFormat::HexUpper
|
||||
: IntFormat::HexLower);
|
||||
|
||||
case 's':
|
||||
// Simplified handling for literals.
|
||||
if (auto *lit = arg.as_if<slang::ast::StringLiteral>()) {
|
||||
if (options.width)
|
||||
return mlir::emitError(loc)
|
||||
<< "string format specifier with width not supported";
|
||||
emitLiteral(lit->getValue());
|
||||
return success();
|
||||
}
|
||||
return mlir::emitError(argLoc)
|
||||
<< "expression cannot be formatted as string";
|
||||
|
||||
default:
|
||||
return mlir::emitError(loc)
|
||||
<< "unsupported format specifier `" << fullSpecifier << "`";
|
||||
}
|
||||
}
|
||||
|
||||
/// Emit an integer value with the given format.
|
||||
LogicalResult emitInteger(const slang::ast::Expression &arg,
|
||||
const FormatOptions &options, IntFormat format) {
|
||||
auto value =
|
||||
context.convertToSimpleBitVector(context.convertRvalueExpression(arg));
|
||||
if (!value)
|
||||
return failure();
|
||||
|
||||
// Determine the width to which the formatted integer should be padded.
|
||||
unsigned width;
|
||||
if (options.width) {
|
||||
width = *options.width;
|
||||
} else {
|
||||
width = cast<moore::IntType>(value.getType()).getWidth();
|
||||
if (format == IntFormat::Octal)
|
||||
// 3 bits per octal digit
|
||||
width = (width + 2) / 3;
|
||||
else if (format == IntFormat::HexLower || format == IntFormat::HexUpper)
|
||||
// 4 bits per hex digit
|
||||
width = (width + 3) / 4;
|
||||
else if (format == IntFormat::Decimal)
|
||||
// ca. 3.322 bits per decimal digit (ln(10)/ln(2))
|
||||
width = std::ceil(width * std::log(2) / std::log(10));
|
||||
}
|
||||
|
||||
// Determine the alignment and padding.
|
||||
auto alignment = options.leftJustify ? IntAlign::Left : IntAlign::Right;
|
||||
auto padding =
|
||||
format == IntFormat::Decimal ? IntPadding::Space : IntPadding::Zero;
|
||||
|
||||
fragments.push_back(builder.create<moore::FormatIntOp>(
|
||||
loc, value, format, width, alignment, padding));
|
||||
return success();
|
||||
}
|
||||
|
||||
/// Emit an expression argument with the appropriate default formatting.
|
||||
LogicalResult emitDefault(const slang::ast::Expression &expr) {
|
||||
FormatOptions options;
|
||||
return emitInteger(expr, options, defaultFormat);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
FailureOr<Value> Context::convertFormatString(
|
||||
slang::span<const slang::ast::Expression *const> arguments, Location loc,
|
||||
IntFormat defaultFormat, bool appendNewline) {
|
||||
FormatStringParser parser(*this, ArrayRef(arguments.data(), arguments.size()),
|
||||
loc, defaultFormat);
|
||||
return parser.parse(appendNewline);
|
||||
}
|
|
@ -116,6 +116,11 @@ struct Context {
|
|||
/// convert it to the given domain.
|
||||
Value convertToBool(Value value, Domain domain);
|
||||
|
||||
/// Helper function to convert a value to its simple bit vector
|
||||
/// representation, if it has one. Otherwise returns null. Also returns null
|
||||
/// if the given value is null.
|
||||
Value convertToSimpleBitVector(Value value);
|
||||
|
||||
/// Helper function to materialize an `SVInt` as an SSA value.
|
||||
Value materializeSVInt(const slang::SVInt &svint,
|
||||
const slang::ast::Type &type, Location loc);
|
||||
|
@ -125,6 +130,15 @@ struct Context {
|
|||
Value materializeConstant(const slang::ConstantValue &constant,
|
||||
const slang::ast::Type &type, Location loc);
|
||||
|
||||
/// Convert a list of string literal arguments with formatting specifiers and
|
||||
/// arguments to be interpolated into a `!moore.format_string` value. Returns
|
||||
/// failure if an error occurs. Returns a null value if the formatted string
|
||||
/// is trivially empty. Otherwise returns the formatted string.
|
||||
FailureOr<Value> convertFormatString(
|
||||
slang::span<const slang::ast::Expression *const> arguments, Location loc,
|
||||
moore::IntFormat defaultFormat = moore::IntFormat::Decimal,
|
||||
bool appendNewline = false);
|
||||
|
||||
/// Evaluate the constant value of an expression.
|
||||
slang::ConstantValue evaluateConstant(const slang::ast::Expression &expr);
|
||||
|
||||
|
|
|
@ -557,6 +557,8 @@ struct StmtVisitor {
|
|||
const auto &subroutine = *info.subroutine;
|
||||
auto args = expr.arguments();
|
||||
|
||||
// Simulation Control Tasks
|
||||
|
||||
if (subroutine.name == "$stop") {
|
||||
createFinishMessage(args.size() >= 1 ? args[0] : nullptr);
|
||||
builder.create<moore::StopBIOp>(loc);
|
||||
|
@ -578,6 +580,83 @@ struct StmtVisitor {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Display and Write Tasks (`$display[boh]?` or `$write[boh]?`)
|
||||
|
||||
// Check for a `$display` or `$write` prefix.
|
||||
bool isDisplay = false; // display or write
|
||||
bool appendNewline = false; // display
|
||||
StringRef remainingName = subroutine.name;
|
||||
if (remainingName.consume_front("$display")) {
|
||||
isDisplay = true;
|
||||
appendNewline = true;
|
||||
} else if (remainingName.consume_front("$write")) {
|
||||
isDisplay = true;
|
||||
}
|
||||
|
||||
// Check for optional `b`, `o`, or `h` suffix indicating default format.
|
||||
using moore::IntFormat;
|
||||
IntFormat defaultFormat = IntFormat::Decimal;
|
||||
if (isDisplay && !remainingName.empty()) {
|
||||
if (remainingName == "b")
|
||||
defaultFormat = IntFormat::Binary;
|
||||
else if (remainingName == "o")
|
||||
defaultFormat = IntFormat::Octal;
|
||||
else if (remainingName == "h")
|
||||
defaultFormat = IntFormat::HexLower;
|
||||
else
|
||||
isDisplay = false;
|
||||
}
|
||||
|
||||
if (isDisplay) {
|
||||
auto message =
|
||||
context.convertFormatString(args, loc, defaultFormat, appendNewline);
|
||||
if (failed(message))
|
||||
return failure();
|
||||
if (*message == Value{})
|
||||
return true;
|
||||
builder.create<moore::DisplayBIOp>(loc, *message);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Severity Tasks
|
||||
using moore::Severity;
|
||||
std::optional<Severity> severity;
|
||||
if (subroutine.name == "$info")
|
||||
severity = Severity::Info;
|
||||
else if (subroutine.name == "$warning")
|
||||
severity = Severity::Warning;
|
||||
else if (subroutine.name == "$error")
|
||||
severity = Severity::Error;
|
||||
else if (subroutine.name == "$fatal")
|
||||
severity = Severity::Fatal;
|
||||
|
||||
if (severity) {
|
||||
// The `$fatal` task has an optional leading verbosity argument.
|
||||
const slang::ast::Expression *verbosityExpr = nullptr;
|
||||
if (severity == Severity::Fatal && args.size() >= 1) {
|
||||
verbosityExpr = args[0];
|
||||
args = args.subspan(1);
|
||||
}
|
||||
|
||||
// Handle the string formatting.
|
||||
auto message = context.convertFormatString(args, loc);
|
||||
if (failed(message))
|
||||
return failure();
|
||||
if (*message == Value{})
|
||||
*message = builder.create<moore::FormatLiteralOp>(loc, "");
|
||||
|
||||
builder.create<moore::SeverityBIOp>(loc, *severity, *message);
|
||||
|
||||
// Handle the `$fatal` case which behaves like a `$finish`.
|
||||
if (severity == Severity::Fatal) {
|
||||
createFinishMessage(verbosityExpr);
|
||||
builder.create<moore::FinishBIOp>(loc, 1);
|
||||
builder.create<moore::UnreachableOp>(loc);
|
||||
setTerminated();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Give up on any other system tasks. These will be tried again as an
|
||||
// expression later.
|
||||
return false;
|
||||
|
|
|
@ -2055,3 +2055,150 @@ function void SimulationControlBuiltins(bit x);
|
|||
// CHECK-NOT: moore.builtin.finish
|
||||
$exit;
|
||||
endfunction
|
||||
|
||||
// CHECK-LABEL: func.func private @DisplayAndSeverityBuiltins(
|
||||
// CHECK-SAME: [[X:%.+]]: !moore.i32
|
||||
function void DisplayAndSeverityBuiltins(int x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.literal "\0A"
|
||||
// CHECK: moore.builtin.display [[TMP]]
|
||||
$display;
|
||||
// CHECK: [[TMP1:%.+]] = moore.fmt.literal "hello"
|
||||
// CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A"
|
||||
// CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]])
|
||||
// CHECK: moore.builtin.display [[TMP3]]
|
||||
$display("hello");
|
||||
|
||||
// CHECK-NOT: moore.builtin.display
|
||||
$write;
|
||||
$write(,);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.literal "hello\0A world \\ foo ! bar % \22"
|
||||
// CHECK: moore.builtin.display [[TMP]]
|
||||
$write("hello\n world \\ foo \x21 bar %% \042");
|
||||
|
||||
// CHECK: [[TMP1:%.+]] = moore.fmt.literal "foo "
|
||||
// CHECK: [[TMP2:%.+]] = moore.fmt.literal "bar"
|
||||
// CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]])
|
||||
// CHECK: moore.builtin.display [[TMP3]]
|
||||
$write("foo %s", "bar");
|
||||
|
||||
// CHECK: moore.fmt.int binary [[X]], width 32, align right, pad zero : i32
|
||||
$write("%b", x);
|
||||
// CHECK: moore.fmt.int binary [[X]], width 32, align right, pad zero : i32
|
||||
$write("%B", x);
|
||||
// CHECK: moore.fmt.int binary [[X]], width 0, align right, pad zero : i32
|
||||
$write("%0b", x);
|
||||
// CHECK: moore.fmt.int binary [[X]], width 42, align right, pad zero : i32
|
||||
$write("%42b", x);
|
||||
// CHECK: moore.fmt.int binary [[X]], width 42, align left, pad zero : i32
|
||||
$write("%-42b", x);
|
||||
|
||||
// CHECK: moore.fmt.int octal [[X]], width 11, align right, pad zero : i32
|
||||
$write("%o", x);
|
||||
// CHECK: moore.fmt.int octal [[X]], width 11, align right, pad zero : i32
|
||||
$write("%O", x);
|
||||
// CHECK: moore.fmt.int octal [[X]], width 0, align right, pad zero : i32
|
||||
$write("%0o", x);
|
||||
// CHECK: moore.fmt.int octal [[X]], width 19, align right, pad zero : i32
|
||||
$write("%19o", x);
|
||||
// CHECK: moore.fmt.int octal [[X]], width 19, align left, pad zero : i32
|
||||
$write("%-19o", x);
|
||||
|
||||
// CHECK: moore.fmt.int decimal [[X]], width 10, align right, pad space : i32
|
||||
$write("%d", x);
|
||||
// CHECK: moore.fmt.int decimal [[X]], width 10, align right, pad space : i32
|
||||
$write("%D", x);
|
||||
// CHECK: moore.fmt.int decimal [[X]], width 0, align right, pad space : i32
|
||||
$write("%0d", x);
|
||||
// CHECK: moore.fmt.int decimal [[X]], width 19, align right, pad space : i32
|
||||
$write("%19d", x);
|
||||
// CHECK: moore.fmt.int decimal [[X]], width 19, align left, pad space : i32
|
||||
$write("%-19d", x);
|
||||
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 8, align right, pad zero : i32
|
||||
$write("%h", x);
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 8, align right, pad zero : i32
|
||||
$write("%x", x);
|
||||
// CHECK: moore.fmt.int hex_upper [[X]], width 8, align right, pad zero : i32
|
||||
$write("%H", x);
|
||||
// CHECK: moore.fmt.int hex_upper [[X]], width 8, align right, pad zero : i32
|
||||
$write("%X", x);
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 0, align right, pad zero : i32
|
||||
$write("%0h", x);
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 19, align right, pad zero : i32
|
||||
$write("%19h", x);
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 19, align right, pad zero : i32
|
||||
$write("%019h", x);
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 19, align left, pad zero : i32
|
||||
$write("%-19h", x);
|
||||
// CHECK: moore.fmt.int hex_lower [[X]], width 19, align left, pad zero : i32
|
||||
$write("%-019h", x);
|
||||
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int decimal [[X]], width 10, align right, pad space : i32
|
||||
// CHECK: moore.builtin.display [[TMP]]
|
||||
$write(x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int binary [[X]], width 32, align right, pad zero : i32
|
||||
// CHECK: moore.builtin.display [[TMP]]
|
||||
$writeb(x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int octal [[X]], width 11, align right, pad zero : i32
|
||||
// CHECK: moore.builtin.display [[TMP]]
|
||||
$writeo(x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int hex_lower [[X]], width 8, align right, pad zero : i32
|
||||
// CHECK: moore.builtin.display [[TMP]]
|
||||
$writeh(x);
|
||||
|
||||
// CHECK: [[TMP1:%.+]] = moore.fmt.int decimal [[X]], width 10, align right, pad space : i32
|
||||
// CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A"
|
||||
// CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]])
|
||||
// CHECK: moore.builtin.display [[TMP3]]
|
||||
$display(x);
|
||||
// CHECK: [[TMP1:%.+]] = moore.fmt.int binary [[X]], width 32, align right, pad zero : i32
|
||||
// CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A"
|
||||
// CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]])
|
||||
// CHECK: moore.builtin.display [[TMP3]]
|
||||
$displayb(x);
|
||||
// CHECK: [[TMP1:%.+]] = moore.fmt.int octal [[X]], width 11, align right, pad zero : i32
|
||||
// CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A"
|
||||
// CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]])
|
||||
// CHECK: moore.builtin.display [[TMP3]]
|
||||
$displayo(x);
|
||||
// CHECK: [[TMP1:%.+]] = moore.fmt.int hex_lower [[X]], width 8, align right, pad zero : i32
|
||||
// CHECK: [[TMP2:%.+]] = moore.fmt.literal "\0A"
|
||||
// CHECK: [[TMP3:%.+]] = moore.fmt.concat ([[TMP1]], [[TMP2]])
|
||||
// CHECK: moore.builtin.display [[TMP3]]
|
||||
$displayh(x);
|
||||
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.literal ""
|
||||
// CHECK: moore.builtin.severity info [[TMP]]
|
||||
$info;
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int
|
||||
// CHECK: moore.builtin.severity info [[TMP]]
|
||||
$info("%d", x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.literal ""
|
||||
// CHECK: moore.builtin.severity warning [[TMP]]
|
||||
$warning;
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int
|
||||
// CHECK: moore.builtin.severity warning [[TMP]]
|
||||
$warning("%d", x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.literal ""
|
||||
// CHECK: moore.builtin.severity error [[TMP]]
|
||||
$error;
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int
|
||||
// CHECK: moore.builtin.severity error [[TMP]]
|
||||
$error("%d", x);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.literal ""
|
||||
// CHECK: moore.builtin.severity fatal [[TMP]]
|
||||
// CHECK: moore.builtin.finish_message false
|
||||
// CHECK: moore.builtin.finish 1
|
||||
// CHECK: moore.unreachable
|
||||
if (0) $fatal;
|
||||
// CHECK-NOT: moore.builtin.finish_message
|
||||
// CHECK: moore.unreachable
|
||||
if (0) $fatal(0);
|
||||
// CHECK: moore.builtin.finish_message true
|
||||
// CHECK: moore.unreachable
|
||||
if (0) $fatal(2);
|
||||
// CHECK: [[TMP:%.+]] = moore.fmt.int
|
||||
// CHECK: moore.builtin.severity fatal [[TMP]]
|
||||
// CHECK: moore.unreachable
|
||||
if (0) $fatal(1, "%d", x);
|
||||
endfunction
|
||||
|
|
|
@ -92,3 +92,17 @@ module Foo;
|
|||
// expected-error @below {{hello}}
|
||||
$fatal(0, "hello");
|
||||
endmodule
|
||||
|
||||
// -----
|
||||
module Top; endmodule
|
||||
function Foo;
|
||||
// expected-error @below {{unsupported format specifier `%l`}}
|
||||
$write("%l");
|
||||
endfunction
|
||||
|
||||
// -----
|
||||
module Top; endmodule
|
||||
function Foo;
|
||||
// expected-error @below {{string format specifier with width not supported}}
|
||||
$write("%42s", "foo");
|
||||
endfunction
|
||||
|
|
|
@ -330,6 +330,35 @@ func.func @WaitEvent(%arg0: !moore.i1, %arg1: !moore.i1) {
|
|||
return
|
||||
}
|
||||
|
||||
|
||||
// CHECK-LABEL: func.func @FormatStrings
|
||||
// CHECK-SAME: %arg0: !moore.format_string
|
||||
func.func @FormatStrings(%arg0: !moore.format_string, %arg1: !moore.i42) {
|
||||
// CHECK: moore.fmt.literal "hello"
|
||||
moore.fmt.literal "hello"
|
||||
// CHECK: moore.fmt.concat ()
|
||||
moore.fmt.concat ()
|
||||
// CHECK: moore.fmt.concat (%arg0)
|
||||
moore.fmt.concat (%arg0)
|
||||
// CHECK: moore.fmt.concat (%arg0, %arg0)
|
||||
moore.fmt.concat (%arg0, %arg0)
|
||||
// CHECK: moore.fmt.int binary %arg1, width 42, align left, pad zero : i42
|
||||
moore.fmt.int binary %arg1, width 42, align left, pad zero : i42
|
||||
// CHECK: moore.fmt.int binary %arg1, width 42, align right, pad zero : i42
|
||||
moore.fmt.int binary %arg1, width 42, align right, pad zero : i42
|
||||
// CHECK: moore.fmt.int binary %arg1, width 42, align right, pad space : i42
|
||||
moore.fmt.int binary %arg1, width 42, align right, pad space : i42
|
||||
// CHECK: moore.fmt.int octal %arg1, width 42, align left, pad zero : i42
|
||||
moore.fmt.int octal %arg1, width 42, align left, pad zero : i42
|
||||
// CHECK: moore.fmt.int decimal %arg1, width 42, align left, pad zero : i42
|
||||
moore.fmt.int decimal %arg1, width 42, align left, pad zero : i42
|
||||
// CHECK: moore.fmt.int hex_lower %arg1, width 42, align left, pad zero : i42
|
||||
moore.fmt.int hex_lower %arg1, width 42, align left, pad zero : i42
|
||||
// CHECK: moore.fmt.int hex_upper %arg1, width 42, align left, pad zero : i42
|
||||
moore.fmt.int hex_upper %arg1, width 42, align left, pad zero : i42
|
||||
return
|
||||
}
|
||||
|
||||
// CHECK-LABEL: func.func @SimulationControlBuiltins
|
||||
func.func @SimulationControlBuiltins() {
|
||||
// CHECK: moore.builtin.stop
|
||||
|
@ -341,3 +370,18 @@ func.func @SimulationControlBuiltins() {
|
|||
// CHECK: moore.unreachable
|
||||
moore.unreachable
|
||||
}
|
||||
|
||||
// CHECK-LABEL: func.func @SeverityAndDisplayBuiltins
|
||||
func.func @SeverityAndDisplayBuiltins(%arg0: !moore.format_string) {
|
||||
// CHECK: moore.builtin.display %arg0
|
||||
moore.builtin.display %arg0
|
||||
// CHECK: moore.builtin.severity info %arg0
|
||||
moore.builtin.severity info %arg0
|
||||
// CHECK: moore.builtin.severity warning %arg0
|
||||
moore.builtin.severity warning %arg0
|
||||
// CHECK: moore.builtin.severity error %arg0
|
||||
moore.builtin.severity error %arg0
|
||||
// CHECK: moore.builtin.severity fatal %arg0
|
||||
moore.builtin.severity fatal %arg0
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue