Support $sformat with runtime format string (#7212). (#7257)

Fixes #7212.
This commit is contained in:
Wilson Snyder 2026-03-14 22:43:56 -04:00 committed by GitHub
parent 42cf5d3be2
commit 602ee384de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
32 changed files with 1389 additions and 957 deletions

View File

@ -926,20 +926,52 @@ std::string _vl_vsformat_time(char* tmp, T ld, int timeunit, bool left, size_t w
// Do a va_arg returning a quad, assuming input argument is anything less than wide
#define VL_VA_ARG_Q_(ap, bits) (((bits) <= VL_IDATASIZE) ? va_arg(ap, IData) : va_arg(ap, QData))
void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL_MT_SAFE {
void _vl_vsformat(std::string& output, const std::string& format, int argc,
va_list ap) VL_MT_SAFE {
// Format a Verilog $write style format into the output list
// The format must be pre-processed (and lower cased) by Verilator
// Arguments are in "width, arg-value (or WDataIn* if wide)" form
// The format must be pre-processed (and lower cased) by Verilator.
// Arguments are each {"VFormatAttr character, int width, arg-value (or WDataIn* if wide)"}
//
// Note uses a single buffer internally; presumes only one usage per printf
// Note also assumes variables < 64 are not wide, this assumption is
// Uses a single buffer internally; presumes only one usage per printf.
// Also assumes variables < 64 are not wide, this assumption is
// sometimes not true in low-level routines written here in verilated.cpp
// Look ahead at args to capture any %m/%t baseline information
char formatAttr = '\0'; // Fetched format for _next_ argument
bool formatAttrValid = false;
const char* modulep = nullptr;
const char* scopep = nullptr;
int timeunit = 0;
int argn = 0;
while (argn < argc) {
formatAttr = va_arg(ap, int); // Char promoted to int
switch (formatAttr) {
case VL_VFORMATATTR_TIMEUNIT:
++argn;
timeunit = va_arg(ap, int);
continue;
case VL_VFORMATATTR_SCOPE:
// No width
++argn;
modulep = va_arg(ap, const char*);
scopep = va_arg(ap, const char*);
continue;
default: // Normal arg; will consume formatAttr later
formatAttrValid = true;
break;
}
break;
}
// Parse format
static thread_local char t_tmp[VL_VALUE_STRING_MAX_WIDTH];
std::string::const_iterator pctit = format.end(); // Most recent %##.##g format
bool inPct = false;
bool widthSet = false;
bool left = false;
size_t width = 0;
output = "";
output.reserve(format.length());
for (std::string::const_iterator pos = format.cbegin(); pos != format.cend(); ++pos) {
if (!inPct && pos[0] == '%') {
pctit = pos;
@ -956,7 +988,7 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
}
} else { // Format character
inPct = false;
const char fmt = pos[0];
char fmt = std::tolower(pos[0]);
switch (fmt) {
case '0': // FALLTHRU
case '1': // FALLTHRU
@ -971,96 +1003,135 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
inPct = true; // Get more digits
widthSet = true;
width = width * 10 + (fmt - '0');
break;
continue;
case '-':
left = true;
inPct = true; // Get more digits
break;
continue;
case '.':
inPct = true; // Get more digits
break;
continue;
case '%': //
output += '%';
continue;
case 'l':
output += "----"; // Library - compile-time only
continue;
case 'm':
if (modulep) output += modulep;
if (modulep && modulep[0] && scopep && scopep[0]) output += '.';
if (scopep) output += scopep;
continue;
//--------
// Standard format handling -- all take arguments
case 'b': // FALLTHRU
case 'c': // FALLTHRU
case 'd': // FALLTHRU
case 'e': // FALLTHRU
case 'f': // FALLTHRU
case 'g': // FALLTHRU
case 'h': // FALLTHRU
case 'o': // FALLTHRU
case 'p': // FALLTHRU
case 's': // FALLTHRU
case 't': // FALLTHRU
case 'u': // FALLTHRU
case 'v': // FALLTHRU
case 'x': // FALLTHRU
case 'z': // FALLTHRU
break;
case 'N': { // "C" string with name of module, add . if needed
const char* const cstrp = va_arg(ap, const char*);
if (VL_LIKELY(*cstrp)) {
output += cstrp;
output += '.';
}
break;
//--------
default: // Bad escape, just print %letter so user sees it
output += '%';
output += fmt;
continue;
} // switch
// At this point only have escapes that expect arguments
if (++argn > argc) {
output += '%';
output += fmt;
continue; // Out of arguments
}
case 'S': { // "C" string
const char* const cstrp = va_arg(ap, const char*);
output += cstrp;
break;
}
case '@': { // Verilog/C++ string
va_arg(ap, int); // # bits is ignored
const std::string* const cstrp = va_arg(ap, const std::string*);
std::string padding;
if (width > cstrp->size()) padding.append(width - cstrp->size(), ' ');
output += left ? (*cstrp + padding) : (padding + *cstrp);
break;
}
case 'e':
case 'f':
case 'g':
case '^': { // Realtime
const int lbits = va_arg(ap, int);
const double d = va_arg(ap, double);
(void)lbits; // UNUSED - always 64
if (fmt == '^') { // Realtime
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
const int timeunit = va_arg(ap, int);
output += _vl_vsformat_time(t_tmp, d, timeunit, left, width);
} else {
const std::string fmts{pctit, pos + 1};
VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, fmts.c_str(), d);
output += t_tmp;
}
break;
}
case 'p': { // 'x' but parameter is string
const int lbits = va_arg(ap, int);
(void)lbits;
const std::string* const cstr = va_arg(ap, const std::string*);
std::ostringstream oss;
for (unsigned char c : *cstr) oss << std::hex << static_cast<int>(c);
std::string hex_str = oss.str();
if (width > 0 && widthSet) {
hex_str = hex_str.size() > width
? hex_str.substr(0, width)
: std::string(width - hex_str.size(), '0') + hex_str;
output += hex_str;
}
break;
}
default: {
// Deal with all read-and-print somethings
const int lbits = va_arg(ap, int);
QData ld = 0;
VlWide<VL_WQ_WORDS_E> qlwp;
WDataInP lwp = nullptr;
if (!formatAttrValid) formatAttr = va_arg(ap, int); // char promoted to int
formatAttrValid = false;
// Process an argument
// Similar code flow in V3Number::displayed
int lbits = 0;
void* thingp = nullptr;
QData ld = 0;
VlWide<VL_VALUE_STRING_MAX_WIDTH / 4 + 2> strwide;
WDataInP lwp = nullptr;
int lsb = 0;
double real = 0.0;
if (formatAttr == VL_VFORMATATTR_COMPLEX) { // printed as string
thingp = va_arg(ap, std::string*);
if (fmt != 'p') fmt = 's'; // Override
} else if (formatAttr == VL_VFORMATATTR_DOUBLE) {
real = va_arg(ap, double);
ld = VL_RTOIROUND_Q_D(real);
VL_SET_WQ(strwide, ld);
lwp = strwide;
lbits = 64;
// Not changint fmt == 'p' to fmt = 'g', as need fmts correct
} else if (formatAttr == VL_VFORMATATTR_STRING) {
thingp = va_arg(ap, std::string*);
if (fmt != 'p' && fmt != 'x') fmt = 's'; // Override
} else { // Numeric
lbits = va_arg(ap, int);
if (lbits <= VL_QUADSIZE) {
ld = VL_VA_ARG_Q_(ap, lbits);
VL_SET_WQ(qlwp, ld);
lwp = qlwp;
VL_SET_WQ(strwide, ld);
lwp = strwide;
} else {
lwp = va_arg(ap, WDataInP);
ld = lwp[0];
}
int lsb = lbits - 1;
if (fmt == 'p') {
if (widthSet && width == 0) { // For %0p, IEEE our choice, use 'h%0h
output += "'h";
fmt = 'h';
} else { // UVM tests require %0d
widthSet = true;
width = 0;
fmt = 'd';
}
}
lsb = lbits - 1;
if (widthSet && width == 0) {
while (lsb && !VL_BITISSET_W(lwp, lsb)) --lsb;
}
switch (fmt) {
case 'c': {
const IData charval = ld & 0xff;
output += static_cast<char>(charval);
break;
}
// fmt may have been overridden above based on formatAttr datatype passed
switch (fmt) {
case 'c': {
const IData charval = ld & 0xff;
output += static_cast<char>(charval);
break;
}
case 'e': // FALLTHRU
case 'f': // FALLTHRU
case 'g': {
if (formatAttr == VL_VFORMATATTR_SIGNED) {
real = VL_ISTOR_D_W(lbits, lwp);
} else if (formatAttr == VL_VFORMATATTR_UNSIGNED) {
real = VL_ITOR_D_W(lbits, lwp);
}
case 's': {
const std::string fmts{pctit, pos + 1};
VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, fmts.c_str(), real);
output += t_tmp;
break;
}
case 's': {
if (thingp) { // VNumber::STRING Verilog 'string'
const std::string* const strp = static_cast<const std::string*>(thingp);
std::string padding;
if (width > strp->size()) padding.append(width - strp->size(), ' ');
output += left ? (*strp + padding) : (padding + *strp);
break;
} else { // Number-based string
std::string field;
for (; lsb >= 0; --lsb) {
lsb = (lsb / 8) * 8; // Next digit
@ -1070,11 +1141,28 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
std::string padding;
if (width > field.size()) padding.append(width - field.size(), ' ');
output += left ? (field + padding) : (padding + field);
break;
}
case 'd': { // Signed decimal
int digits = 0;
std::string append;
break;
}
case 'p': { // Pattern
// 'p' with NUMBER was earlier converted to 'd'
if (formatAttr
== VL_VFORMATATTR_DOUBLE) { // Can't just change to 'g' as need fixed format
VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, "%g", real);
output += t_tmp;
} else if (formatAttr == VL_VFORMATATTR_STRING) {
const std::string* const strp = static_cast<const std::string*>(thingp);
output += '"' + *strp + '"';
} else if (formatAttr == VL_VFORMATATTR_COMPLEX) {
const std::string* const strp = static_cast<const std::string*>(thingp);
output += *strp;
}
break;
}
case 'd': { // Signed/unsigned decimal
int digits = 0;
std::string append;
if (formatAttr == VL_VFORMATATTR_SIGNED) {
if (lbits <= VL_QUADSIZE) {
digits
= VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, "%" PRId64,
@ -1090,28 +1178,7 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
}
digits = static_cast<int>(append.length());
}
const int needmore = static_cast<int>(width) - digits;
if (needmore > 0) {
std::string padding;
if (left) {
padding.append(needmore, ' '); // Pre-pad spaces
output += append + padding;
} else {
if (pctit != format.end() && pctit[0] && pctit[1] == '0') { // %0
padding.append(needmore, '0'); // Pre-pad zero
} else {
padding.append(needmore, ' '); // Pre-pad spaces
}
output += padding + append;
}
} else {
output += append;
}
break;
}
case '#': { // Unsigned decimal
int digits = 0;
std::string append;
} else { // Unsigned decimal
if (lbits <= VL_QUADSIZE) {
digits = VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, "%" PRIu64, ld);
append = t_tmp;
@ -1119,119 +1186,146 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
append = VL_DECIMAL_NW(lbits, lwp);
digits = static_cast<int>(append.length());
}
const int needmore = static_cast<int>(width) - digits;
if (needmore > 0) {
std::string padding;
if (left) {
padding.append(needmore, ' '); // Pre-pad spaces
output += append + padding;
} else {
if (pctit != format.end() && pctit[0] && pctit[1] == '0') { // %0
padding.append(needmore, '0'); // Pre-pad zero
} else {
padding.append(needmore, ' '); // Pre-pad spaces
}
output += padding + append;
}
}
if (!widthSet) {
const double mantissabits
= lbits - ((formatAttr == VL_VFORMATATTR_SIGNED) ? 1 : 0);
// This is log10(2**mantissabits) as log2(2**mantissabits)/log2(10),
// + 1.0 rounding bias.
double dchars = mantissabits / 3.321928094887362 + 1.0;
if (formatAttr == VL_VFORMATATTR_SIGNED) ++dchars; // space for sign
width = int(dchars);
}
const int needmore = static_cast<int>(width) - digits;
if (needmore > 0) {
std::string padding;
if (left) {
padding.append(needmore, ' '); // Pre-pad spaces
output += append + padding;
} else {
output += append;
}
break;
}
case 't': { // Time
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
const int timeunit = va_arg(ap, int);
output += _vl_vsformat_time(t_tmp, ld, timeunit, left, width);
break;
}
case 'b': // FALLTHRU
case 'o': // FALLTHRU
case 'x': {
if (widthSet || left) {
lsb = VL_MOSTSETBITP1_W(VL_WORDS_I(lbits), lwp);
lsb = (lsb < 1) ? 0 : (lsb - 1);
}
std::string append;
int digits;
switch (fmt) {
case 'b': {
digits = lsb + 1;
for (; lsb >= 0; --lsb) append += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0';
break;
}
case 'o': {
digits = (lsb + 1 + 2) / 3;
for (; lsb >= 0; --lsb) {
lsb = (lsb / 3) * 3; // Next digit
// Octal numbers may span more than one wide word,
// so we need to grab each bit separately and check for overrun
// Octal is rare, so we'll do it a slow simple way
append += static_cast<char>(
'0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0)
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0)
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0));
}
break;
}
default: { // 'x'
digits = (lsb + 1 + 3) / 4;
for (; lsb >= 0; --lsb) {
lsb = (lsb / 4) * 4; // Next digit
const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf;
append += "0123456789abcdef"[charval];
}
break;
}
} // switch
const int needmore = static_cast<int>(width) - digits;
if (needmore > 0) {
std::string padding;
if (left) {
padding.append(needmore, ' '); // Pre-pad spaces
output += append + padding;
} else {
if (pctit != format.end() && pctit[0] && pctit[1] == '0') { // %0
padding.append(needmore, '0'); // Pre-pad zero
output += padding + append;
} else {
padding.append(needmore, ' '); // Pre-pad spaces
}
} else {
output += append;
output += padding + append;
}
} else {
output += append;
}
break;
}
case 't': { // Time
// Timeunit was read earlier from up-front arguments
if (formatAttr == VL_VFORMATATTR_DOUBLE) { // Realtime
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
output += _vl_vsformat_time(t_tmp, real, timeunit, left, width);
} else {
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
output += _vl_vsformat_time(t_tmp, ld, timeunit, left, width);
}
break;
}
case 'b': // FALLTHRU
case 'h': // FALLTHRU
case 'o': // FALLTHRU
case 'x': {
if (formatAttr == VL_VFORMATATTR_STRING) {
// V3Width errors on const %x of string, but V3Randomize may make a %x on a
// string, or may have a runtime format
const std::string* const strp = static_cast<const std::string*>(thingp);
int chars = std::min(static_cast<int>(strp->size()),
static_cast<int>(VL_VALUE_STRING_MAX_WIDTH / 2));
int truncFront = widthSet ? (chars - (static_cast<int>(width) / 2)) : 0;
if (truncFront < 0) truncFront = 0;
lbits = chars * 8;
lwp = strwide;
lsb = lbits - 1;
VL_NTOI_W(lbits, strwide, *strp, truncFront);
}
if (widthSet || left) {
lsb = VL_MOSTSETBITP1_W(VL_WORDS_I(lbits), lwp);
lsb = (lsb < 1) ? 0 : (lsb - 1);
}
std::string append;
int digits;
switch (fmt) {
case 'b': {
digits = lsb + 1;
for (; lsb >= 0; --lsb) append += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0';
break;
} // b / o / x
case 'u':
case 'z': { // Packed 4-state
const bool is_4_state = (fmt == 'z');
output.reserve(output.size() + ((is_4_state ? 2 : 1) * VL_WORDS_I(lbits)));
int bytes_to_go = VL_BYTES_I(lbits);
int bit = 0;
while (bytes_to_go > 0) {
const int wr_bytes = std::min(4, bytes_to_go);
for (int byte = 0; byte < wr_bytes; byte++, bit += 8)
output += static_cast<char>(VL_BITRSHIFT_W(lwp, bit) & 0xff);
output.append(4 - wr_bytes, static_cast<char>(0));
if (is_4_state) output.append(4, static_cast<char>(0));
bytes_to_go -= wr_bytes;
}
case 'o': {
digits = (lsb + 1 + 2) / 3;
for (; lsb >= 0; --lsb) {
lsb = (lsb / 3) * 3; // Next digit
// Octal numbers may span more than one wide word,
// so we need to grab each bit separately and check for overrun
// Octal is rare, so we'll do it a slow simple way
append += static_cast<char>(
'0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0)
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0)
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0));
}
break;
}
case 'v': // Strength; assume always strong
for (lsb = lbits - 1; lsb >= 0; --lsb) {
if (VL_BITRSHIFT_W(lwp, lsb) & 1) {
output += "St1 ";
} else {
output += "St0 ";
}
default: { // 'x'
digits = (lsb + 1 + 3) / 4;
for (; lsb >= 0; --lsb) {
lsb = (lsb / 4) * 4; // Next digit
const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf;
append += "0123456789abcdef"[charval];
}
break;
default: { // LCOV_EXCL_START
const std::string msg = "Unknown _vl_vsformat code: "s + pos[0];
VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str());
break;
} // LCOV_EXCL_STOP
}
} // switch
const int needmore = static_cast<int>(width) - digits;
if (needmore > 0) {
std::string padding;
if (left) {
padding.append(needmore, ' '); // Pre-pad spaces
output += append + padding;
} else {
padding.append(needmore, '0'); // Pre-pad zero
output += padding + append;
}
} else {
output += append;
}
break;
} // b / o / x
case 'u':
case 'z': { // Packed 4-state
const bool is_4_state = (fmt == 'z');
output.reserve(output.size() + ((is_4_state ? 2 : 1) * VL_WORDS_I(lbits)));
int bytes_to_go = VL_BYTES_I(lbits);
int bit = 0;
while (bytes_to_go > 0) {
const int wr_bytes = std::min(4, bytes_to_go);
for (int byte = 0; byte < wr_bytes; byte++, bit += 8)
output += static_cast<char>(VL_BITRSHIFT_W(lwp, bit) & 0xff);
output.append(4 - wr_bytes, static_cast<char>(0));
if (is_4_state) output.append(4, static_cast<char>(0));
bytes_to_go -= wr_bytes;
}
break;
}
case 'v': // Strength; assume always strong
for (lsb = lbits - 1; lsb >= 0; --lsb) {
if (VL_BITRSHIFT_W(lwp, lsb) & 1) {
output += "St1 ";
} else {
output += "St0 ";
}
}
break;
default: { // LCOV_EXCL_START
VL_DEBUG_IFDEF(assert(0);); // Missing case between this case, and one above
break;
} // LCOV_EXCL_STOP
} // switch
}
}
@ -1317,7 +1411,7 @@ void _vl_vsss_based(WDataOutP owp, int obits, int baseLog2, const char* strp, si
for (int i = 0, pos = static_cast<int>(posend) - 1;
i < obits && pos >= static_cast<int>(posstart); --pos) {
// clang-format off
switch (tolower (strp[pos])) {
switch (std::tolower (strp[pos])) {
case 'x': case 'z': case '?': // FALLTHRU
case '0': lsb += baseLog2; break;
case '1': _vl_vsss_setbit(owp, obits, lsb, baseLog2, 1); lsb += baseLog2; break;
@ -1344,7 +1438,7 @@ void _vl_vsss_based(WDataOutP owp, int obits, int baseLog2, const char* strp, si
IData _vl_vsscanf(FILE* fp, // If a fscanf
int fbits, const WDataInP fromp, // Else if a sscanf
const std::string& fstr, // if a sscanf to string
const std::string& format, va_list ap) VL_MT_SAFE {
const std::string& format, int argc, va_list ap) VL_MT_SAFE {
// Read a Verilog $sscanf/$fscanf style format into the output list
// The format must be pre-processed (and lower cased) by Verilator
// Arguments are in "width, arg-value (or WDataIn* if wide)" form
@ -1353,6 +1447,25 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
IData got = 0;
bool inPct = false;
bool inIgnore = false;
int argn = 0;
char formatAttr = '\0'; // Fetched format for _next_ argument
bool formatAttrValid = false;
int timeunit = 0;
while (argn < argc) {
formatAttr = va_arg(ap, int); // Char promoted to int
switch (formatAttr) {
case VL_VFORMATATTR_TIMEUNIT:
++argn;
timeunit = va_arg(ap, int);
continue;
default: // Normal arg; will consume formatAttr later
formatAttrValid = true;
break;
}
break;
}
std::string::const_iterator pos = format.cbegin();
for (; pos != format.cend(); ++pos) {
// VL_DBG_MSGF("_vlscan fmt='%c' floc=%d file='%c'\n", pos[0], floc,
@ -1371,7 +1484,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
} else { // Format character
// Skip loading spaces
inPct = false;
const char fmt = pos[0];
const char fmt = std::tolower(pos[0]);
switch (fmt) {
case '%': {
const int c = _vl_vsss_peek(fp, floc, fromp, fstr);
@ -1399,20 +1512,22 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
default: {
// Deal with all read-and-scan somethings
// Note LSBs are preserved if there's an overflow
int obits = inIgnore ? 0 : va_arg(ap, int);
if (!inIgnore && (++argn > argc)) inIgnore = true; // Overflowed arguments
if (!inIgnore) {
if (!formatAttrValid) formatAttr = va_arg(ap, int); // char promoted to int
formatAttrValid = false;
}
int obits = (!inIgnore
&& (formatAttr == VL_VFORMATATTR_UNSIGNED
|| formatAttr == VL_VFORMATATTR_SIGNED))
? va_arg(ap, int)
: 0;
void* const thingp = inIgnore ? nullptr : va_arg(ap, void*);
double real = 0;
VlWide<VL_WQ_WORDS_E> qowp;
VL_SET_WQ(qowp, 0ULL);
WDataOutP owp = qowp;
if (obits == -1) { // string
owp = nullptr;
if (VL_UNCOVERABLE(fmt != 's')) {
VL_FATAL_MT(
__FILE__, __LINE__, "",
"Internal: format other than %s is passed to string"); // LCOV_EXCL_LINE
}
} else if (obits > VL_QUADSIZE) {
owp = va_arg(ap, WDataOutP);
}
WDataOutP owp = (obits <= 64) ? qowp : static_cast<WDataOutP>(thingp);
for (int i = 0; i < VL_WORDS_I(obits); ++i) owp[i] = 0;
switch (fmt) {
@ -1427,23 +1542,27 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, nullptr);
if (!t_tmp[0]) goto done;
if (owp) {
int lpos = (static_cast<int>(std::strlen(t_tmp))) - 1;
int lsb = 0;
for (int i = 0; i < obits && lpos >= 0; --lpos) {
_vl_vsss_setbit(owp, obits, lsb, 8, t_tmp[lpos]);
lsb += 8;
}
int lpos = (static_cast<int>(std::strlen(t_tmp))) - 1;
int lsb = 0;
for (int i = 0; i < obits && lpos >= 0; --lpos) {
_vl_vsss_setbit(owp, obits, lsb, 8, t_tmp[lpos]);
lsb += 8;
}
break;
}
case 'd': { // Signed decimal
case 'd': { // Signed/unsigned decimal
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "0123456789+-xXzZ?_");
if (!t_tmp[0]) goto done;
int64_t ld = 0;
std::sscanf(t_tmp, "%30" PRId64, &ld);
VL_SET_WQ(owp, ld);
if (formatAttr == VL_VFORMATATTR_SIGNED) {
QData ld = 0;
std::sscanf(t_tmp, "%30" PRIu64, &ld);
VL_SET_WQ(owp, ld);
} else if (formatAttr == VL_VFORMATATTR_UNSIGNED) {
int64_t ld = 0;
std::sscanf(t_tmp, "%30" PRId64, &ld);
VL_SET_WQ(owp, ld);
}
break;
}
case 'f':
@ -1456,7 +1575,8 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
double r;
int64_t ld;
} u;
u.r = std::strtod(t_tmp, nullptr);
real = std::strtod(t_tmp, nullptr);
u.r = real;
VL_SET_WQ(owp, u.ld);
break;
}
@ -1468,25 +1588,12 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
double r;
int64_t ld;
} u;
// Get pointer argument first, as proceeds the timeunit value
if (obits != 64) goto done;
QData* const realp = va_arg(ap, QData*);
const int timeunit = va_arg(ap, int);
const int userUnits
= Verilated::threadContextp()->impp()->timeFormatUnits(); // 0..-15
// Timeunit was read earlier from up-front arguments
const int userUnits = Verilated::threadContextp()->impp()->timeFormatUnits();
// 0..-15
const int shift = -userUnits + timeunit; // 0..-15
u.r = std::strtod(t_tmp, nullptr) * vl_time_multiplier(-shift);
*realp = VL_CLEAN_QQ(obits, obits, u.ld);
obits = 0; // Already loaded the value, don't read arg
break;
}
case '#': { // Unsigned decimal
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "0123456789+-xXzZ?_");
if (!t_tmp[0]) goto done;
QData ld = 0;
std::sscanf(t_tmp, "%30" PRIu64, &ld);
VL_SET_WQ(owp, ld);
real = std::strtod(t_tmp, nullptr) * vl_time_multiplier(-shift);
VL_SET_WQ(owp, static_cast<uint64_t>(real));
break;
}
case 'b': {
@ -1503,6 +1610,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
_vl_vsss_based(owp, obits, 3, t_tmp, 0, std::strlen(t_tmp));
break;
}
case 'h': // FALLTHRU
case 'x': {
_vl_vsss_skipspace(fp, floc, fromp, fstr);
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp,
@ -1548,21 +1656,24 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
if (!inIgnore) ++got;
// Reload data if non-wide (if wide, we put it in the right place directly)
if (obits == 0) { // Due to inIgnore
} else if (obits == -1) { // string
std::string* const p = va_arg(ap, std::string*);
if (inIgnore) {
} else if (formatAttr == VL_VFORMATATTR_DOUBLE) {
double* const p = static_cast<double*>(thingp);
*p = real;
} else if (formatAttr == VL_VFORMATATTR_STRING) {
std::string* const p = static_cast<std::string*>(thingp);
*p = t_tmp;
} else if (obits <= VL_BYTESIZE) {
CData* const p = va_arg(ap, CData*);
CData* const p = static_cast<CData*>(thingp);
*p = VL_CLEAN_II(obits, obits, owp[0]);
} else if (obits <= VL_SHORTSIZE) {
SData* const p = va_arg(ap, SData*);
SData* const p = static_cast<SData*>(thingp);
*p = VL_CLEAN_II(obits, obits, owp[0]);
} else if (obits <= VL_IDATASIZE) {
IData* const p = va_arg(ap, IData*);
IData* const p = static_cast<IData*>(thingp);
*p = VL_CLEAN_II(obits, obits, owp[0]);
} else if (obits <= VL_QUADSIZE) {
QData* const p = va_arg(ap, QData*);
QData* const p = static_cast<QData*>(thingp);
*p = VL_CLEAN_QQ(obits, obits, VL_SET_QW(owp));
} else {
_vl_clean_inplace_w(obits, owp);
@ -1689,7 +1800,7 @@ void VL_SFORMAT_NX(int obits, CData& destr, const std::string& format, int argc,
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
@ -1700,7 +1811,7 @@ void VL_SFORMAT_NX(int obits, SData& destr, const std::string& format, int argc,
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
@ -1711,7 +1822,7 @@ void VL_SFORMAT_NX(int obits, IData& destr, const std::string& format, int argc,
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
@ -1722,7 +1833,7 @@ void VL_SFORMAT_NX(int obits, QData& destr, const std::string& format, int argc,
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
@ -1733,7 +1844,7 @@ void VL_SFORMAT_NX(int obits, void* destp, const std::string& format, int argc,
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
_vl_string_to_vint(obits, destp, t_output.length(), t_output.c_str());
@ -1745,7 +1856,7 @@ void VL_SFORMAT_NX(int obits_ignored, std::string& output, const std::string& fo
std::string temp_output;
va_list ap;
va_start(ap, argc);
_vl_vsformat(temp_output, format, ap);
_vl_vsformat(temp_output, format, argc, ap);
va_end(ap);
output = temp_output;
}
@ -1755,7 +1866,7 @@ std::string VL_SFORMATF_N_NX(const std::string& format, int argc, ...) VL_MT_SAF
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
return t_output;
@ -1766,7 +1877,7 @@ void VL_WRITEF_NX(const std::string& format, int argc, ...) VL_MT_SAFE {
t_output = "";
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
VL_PRINTF_MT("%s", t_output.c_str());
@ -1779,7 +1890,7 @@ void VL_FWRITEF_NX(IData fpi, const std::string& format, int argc, ...) VL_MT_SA
va_list ap;
va_start(ap, argc);
_vl_vsformat(t_output, format, ap);
_vl_vsformat(t_output, format, argc, ap);
va_end(ap);
Verilated::threadContextp()->impp()->fdWrite(fpi, t_output);
@ -1792,7 +1903,7 @@ IData VL_FSCANF_INX(IData fpi, const std::string& format, int argc, ...) VL_MT_S
va_list ap;
va_start(ap, argc);
const IData got = _vl_vsscanf(fp, 0, nullptr, "", format, ap);
const IData got = _vl_vsscanf(fp, 0, nullptr, "", format, argc, ap);
va_end(ap);
return got;
}
@ -1803,7 +1914,7 @@ IData VL_SSCANF_IINX(int lbits, IData ld, const std::string& format, int argc, .
va_list ap;
va_start(ap, argc);
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, ap);
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, argc, ap);
va_end(ap);
return got;
}
@ -1813,7 +1924,7 @@ IData VL_SSCANF_IQNX(int lbits, QData ld, const std::string& format, int argc, .
va_list ap;
va_start(ap, argc);
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, ap);
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, argc, ap);
va_end(ap);
return got;
}
@ -1821,7 +1932,7 @@ IData VL_SSCANF_IWNX(int lbits, const WDataInP lwp, const std::string& format, i
...) VL_MT_SAFE {
va_list ap;
va_start(ap, argc);
const IData got = _vl_vsscanf(nullptr, lbits, lwp, "", format, ap);
const IData got = _vl_vsscanf(nullptr, lbits, lwp, "", format, argc, ap);
va_end(ap);
return got;
}
@ -1830,7 +1941,7 @@ IData VL_SSCANF_INNX(int, const std::string& ld, const std::string& format, int
va_list ap;
va_start(ap, argc);
const IData got
= _vl_vsscanf(nullptr, static_cast<int>(ld.length() * 8), nullptr, ld, format, ap);
= _vl_vsscanf(nullptr, static_cast<int>(ld.length() * 8), nullptr, ld, format, argc, ap);
va_end(ap);
return got;
}
@ -2133,13 +2244,25 @@ const char* vl_mc_scan_plusargs(const char* prefixp) VL_MT_SAFE {
//===========================================================================
// Heavy string functions
std::string VL_TO_STRING(CData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 8, lhs); }
std::string VL_TO_STRING(SData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 16, lhs); }
std::string VL_TO_STRING(IData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 32, lhs); }
std::string VL_TO_STRING(QData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 64, lhs); }
std::string VL_TO_STRING(double lhs) { return VL_SFORMATF_N_NX("%g", 0, 64, lhs); }
// TODO these could be accelerated with a dedicated to-Hex formatter
// instead of using VL_SFORMATF_N_NX
std::string VL_TO_STRING(CData lhs) {
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 8, lhs);
}
std::string VL_TO_STRING(SData lhs) {
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 16, lhs);
}
std::string VL_TO_STRING(IData lhs) {
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 32, lhs);
}
std::string VL_TO_STRING(QData lhs) {
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 64, lhs);
}
std::string VL_TO_STRING(double lhs) {
return VL_SFORMATF_N_NX("%g", 1, VL_VFORMATATTR_DOUBLE, lhs);
}
std::string VL_TO_STRING_W(int words, const WDataInP obj) {
return VL_SFORMATF_N_NX("'h%0x", 0, words * VL_EDATASIZE, obj);
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, words * VL_EDATASIZE, obj);
}
std::string VL_TOLOWER_NN(const std::string& ld) VL_PURE {
@ -2221,11 +2344,12 @@ QData VL_NTOI_Q(int obits, const std::string& str) VL_PURE {
}
return out & VL_MASK_Q(obits);
}
void VL_NTOI_W(int obits, WDataOutP owp, const std::string& str) VL_PURE {
void VL_NTOI_W(int obits, WDataOutP owp, const std::string& str, int truncFront) VL_PURE {
// Could also be called VL_CVT_PACK_STR_WN; converts string to wide
const int words = VL_WORDS_I(obits);
for (int i = 0; i < words; ++i) owp[i] = 0;
const char* const datap = str.data();
int pos = static_cast<int>(str.length()) - 1;
int pos = static_cast<int>(str.length()) - 1 - truncFront;
int bit = 0;
while (bit < obits && pos >= 0) {
owp[VL_BITWORD_I(bit)] |= static_cast<EData>(datap[pos]) << VL_BITBIT_I(bit);

View File

@ -2945,7 +2945,8 @@ inline IData VL_CMP_NN(const std::string& lhs, const std::string& rhs, bool igno
extern IData VL_ATOI_N(const std::string& str, int base) VL_PURE;
extern IData VL_NTOI_I(int obits, const std::string& str) VL_PURE;
extern QData VL_NTOI_Q(int obits, const std::string& str) VL_PURE;
extern void VL_NTOI_W(int obits, WDataOutP owp, const std::string& str) VL_PURE;
extern void VL_NTOI_W(int obits, WDataOutP owp, const std::string& str,
int truncFront = 0) VL_PURE;
extern IData VL_FGETS_NI(std::string& dest, IData fpi) VL_MT_SAFE;

View File

@ -318,7 +318,6 @@
# define VL_CONSTEXPR_CXX17
#endif
//=========================================================================
// Optimization
@ -434,6 +433,17 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
# define VL_VSNPRINTF vsnprintf
#endif
// Constants for VL_SFORMATF; see V3Number.h VFormatAttr
// Character codes are upper case so harder to confuse with format %codes.
// (...) indicates what is passed as arguments in emitted code
#define VL_VFORMATATTR_UNSIGNED '#' // (int widthMin, IData/WData/etc) Use standard format
#define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/WData/etc) Signed number; for %d showing sign
#define VL_VFORMATATTR_COMPLEX '!' // (std::string*); for non-POD; e.g. struct, requires %p typically
#define VL_VFORMATATTR_DOUBLE 'D' // (double); promote %p to %f
#define VL_VFORMATATTR_SCOPE 'M' // (char* name, char* scope); for scopes
#define VL_VFORMATATTR_STRING 'S' // (char* name, char* scope); for scopes // (std::string*); for %p/%s
#define VL_VFORMATATTR_TIMEUNIT 'T' // (int timeunit); timeunits passed from V3Emit to runtime
//=========================================================================
// File system functions

View File

@ -773,10 +773,10 @@ class AssertVisitor final : public VNVisitor {
} else if (nodep->displayType() == VDisplayType::DT_MONITOR) {
nodep->displayType(VDisplayType::DT_DISPLAY);
FileLine* const fl = nodep->fileline();
AstNode* monExprsp = nodep->fmtp()->exprsp();
AstSenItem* monSenItemsp = nullptr;
while (monExprsp) {
if (AstNodeVarRef* varrefp = VN_CAST(monExprsp, NodeVarRef)) {
if (AstNode* const monExprsp = nodep->fmtp()->exprsp()) {
monExprsp->foreachAndNext([&](AstVarRef* varrefp) {
AstSenItem* const senItemp
= new AstSenItem{fl, VEdgeType::ET_CHANGED,
// Clone so get VarRef or VarXRef as needed
@ -786,9 +786,9 @@ class AssertVisitor final : public VNVisitor {
} else {
monSenItemsp->addNext(senItemp);
}
}
monExprsp = monExprsp->nextp();
});
}
AstSenTree* const monSenTree = new AstSenTree{fl, monSenItemsp};
const auto monNum = ++m_monitorNum;
// Where $monitor was we do "__VmonitorNum = N;"

View File

@ -2150,28 +2150,64 @@ public:
bool cleanOut() const override { V3ERROR_NA_RETURN(""); }
int instrCount() const override { return widthInstrs(); }
};
class AstSFormatArg final : public AstNodeExpr {
// Information for formatting each argument to AstSFormat,
// used to pass to (potentially) runtime decoding of format arguments
// PARENT: SFormatF (or next list of expressions)
// @astgen op1 := exprp : AstNodeExpr
VFormatAttr m_formatAttr; // How to format expression
public:
AstSFormatArg(FileLine* fl, VFormatAttr formatAttr, AstNodeExpr* exprp)
: ASTGEN_SUPER_SFormatArg(fl)
, m_formatAttr{formatAttr} {
dtypeFrom(exprp);
this->exprp(exprp);
}
ASTGEN_MEMBERS_AstSFormatArg;
void dump(std::ostream& str = std::cout) const override;
void dumpJson(std::ostream& str = std::cout) const override;
int instrCount() const override { return 0; }
bool sameNode(const AstNode* samep) const override {
return formatAttr() == VN_DBG_AS(samep, SFormatArg)->formatAttr();
}
string verilogKwd() const override { return "$sformatarg"; }
string emitVerilog() override { return "%l"; }
string emitC() override { V3ERROR_NA_RETURN(""); }
bool cleanOut() const override { return true; }
const char* broken() const override {
BROKEN_RTN(!VN_IS(backp(), SFormatF) && firstAbovep()); // In list under SFormatF
return nullptr;
}
VFormatAttr formatAttr() const { return m_formatAttr; }
void formatAttr(const VFormatAttr& formatAttr) { m_formatAttr = formatAttr; }
static VFormatAttr formatAttrDefauled(const AstSFormatArg* nodep, const AstNodeDType* dtypep);
};
class AstSFormatF final : public AstNodeExpr {
// Convert format to string, generally under an AstDisplay or AstSFormat
// Also used as "real" function for /*verilator sformat*/ functions
// exprsp() once past parsing may be AstSFormatArgs
// @astgen op1 := exprsp : List[AstNodeExpr]
// @astgen op2 := scopeNamep : Optional[AstScopeName]
string m_text;
const bool m_hidden; // Under display, etc
bool m_exprFormat; // Runtime Node* format, false = text() format code, false = possibly r
bool m_exprFormat
= false; // Runtime Node* format, false = text() format code, false = possibly r
bool m_optionalFormat
= false; // With exprFormat, first argument is either format or format-implied
const char m_missingArgChar; // Format code when argument without format, 'h'/'o'/'b'
VTimescale m_timeunit; // Parent module time unit
public:
class ExprFormat {};
AstSFormatF(FileLine* fl, const string& text, bool hidden, AstNodeExpr* exprsp,
char missingArgChar = 'd')
: ASTGEN_SUPER_SFormatF(fl)
, m_text{text}
, m_hidden{hidden}
, m_exprFormat{false}
, m_missingArgChar{missingArgChar} {
dtypeSetString();
addExprsp(exprsp);
}
class ExprFormat {};
AstSFormatF(FileLine* fl, ExprFormat, AstNodeExpr* exprsp, char missingArgChar = 'd',
bool hidden = true)
: ASTGEN_SUPER_SFormatF(fl)
@ -2194,12 +2230,19 @@ public:
string verilogKwd() const override { return "$sformatf"; }
string text() const { return m_text; } // * = Text to display
void text(const string& text) { m_text = text; }
const char* broken() const override {
BROKEN_RTN(text() != "" && exprFormat()); // Expr format means no literal format
return nullptr;
}
bool formatScopeTracking() const { // Track scopeNamep(); Ok if false positive
return (name().find("%m") != string::npos || name().find("%M") != string::npos);
return exprFormat() || name().find("%m") != string::npos
|| name().find("%M") != string::npos;
}
bool hidden() const { return m_hidden; }
bool exprFormat() const { return m_exprFormat; }
void exprFormat(bool flag) { m_exprFormat = flag; }
bool optionalFormat() const { return m_optionalFormat; }
void optionalFormat(bool flag) { m_optionalFormat = flag; }
char missingArgChar() const { return m_missingArgChar; }
VTimescale timeunit() const { return m_timeunit; }
void timeunit(const VTimescale& flag) { m_timeunit = flag; }

View File

@ -595,7 +595,10 @@ public:
char missingArgChar = 'd')
: ASTGEN_SUPER_Display(fl)
, m_displayType{dispType} {
fmtp(new AstSFormatF{fl, AstSFormatF::ExprFormat{}, exprsp, missingArgChar});
AstSFormatF* const newp
= new AstSFormatF{fl, AstSFormatF::ExprFormat{}, exprsp, missingArgChar};
newp->optionalFormat(true);
fmtp(newp);
this->filep(filep);
}
ASTGEN_MEMBERS_AstDisplay;
@ -1104,15 +1107,13 @@ class AstSFormat final : public AstNodeStmt {
// @astgen op1 := fmtp : AstSFormatF
// @astgen op2 := lhsp : AstNodeExpr
public:
AstSFormat(FileLine* fl, AstNodeExpr* lhsp, const string& text, AstNodeExpr* exprsp,
AstSFormat(FileLine* fl, bool optionalFormat, AstNodeExpr* lhsp, AstNodeExpr* exprsp,
char missingArgChar = 'd')
: ASTGEN_SUPER_SFormat(fl) {
fmtp(new AstSFormatF{fl, text, true, exprsp, missingArgChar});
this->lhsp(lhsp);
}
AstSFormat(FileLine* fl, AstNodeExpr* lhsp, AstNodeExpr* exprsp, char missingArgChar = 'd')
: ASTGEN_SUPER_SFormat(fl) {
fmtp(new AstSFormatF{fl, AstSFormatF::ExprFormat{}, exprsp, missingArgChar});
AstSFormatF* const newp
= new AstSFormatF{fl, AstSFormatF::ExprFormat{}, exprsp, missingArgChar};
newp->optionalFormat(optionalFormat);
fmtp(newp);
this->lhsp(lhsp);
}
ASTGEN_MEMBERS_AstSFormat;

View File

@ -2691,13 +2691,34 @@ void AstPatMember::dumpJson(std::ostream& str) const {
}
void AstNodeTriop::dump(std::ostream& str) const { this->AstNodeExpr::dump(str); }
void AstNodeTriop::dumpJson(std::ostream& str) const { dumpJsonGen(str); }
void AstSFormatArg::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
str << " [" << formatAttr().ascii() << "]";
}
void AstSFormatArg::dumpJson(std::ostream& str) const {
dumpJsonGen(str);
dumpJsonStr(str, "formatAttr", std::string{formatAttr().ascii()});
}
VFormatAttr AstSFormatArg::formatAttrDefauled(const AstSFormatArg* nodep,
const AstNodeDType* dtypep) {
if (nodep) return nodep->formatAttr();
// Used to initially assign the formatArg
// Later, V3Randomize creates raw %s's without SFormatArg's that have string arguments
if (!dtypep) return VFormatAttr{};
const AstNodeDType* skipDtypep = dtypep->skipRefp();
if (skipDtypep->isDouble()) return VFormatAttr{VFormatAttr::DOUBLE};
if (skipDtypep->isString()) return VFormatAttr{VFormatAttr::STRING};
return VFormatAttr{};
}
void AstSFormatF::dump(std::ostream& str) const {
this->AstNodeExpr::dump(str);
if (exprFormat()) str << " [EXPRFMT]";
if (optionalFormat()) str << " [OPTFMT]";
}
void AstSFormatF::dumpJson(std::ostream& str) const {
dumpJsonGen(str);
dumpJsonBoolFuncIf(str, exprFormat);
dumpJsonBoolFuncIf(str, optionalFormat);
}
void AstSel::dump(std::ostream& str) const {
this->AstNodeBiop::dump(str);

View File

@ -3606,26 +3606,43 @@ class ConstVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(nodep->unlinkFrBack()), nodep);
return true;
}
void visit(AstSFormatArg* nodep) override { iterateChildren(nodep); }
void visit(AstSFormatF* nodep) override {
// Substitute constants into displays. The main point of this is to
// simplify assertion methodologies which call functions with display's.
// This eliminates a pile of wide temps, and makes the C a whole lot more readable.
iterateChildren(nodep);
if (nodep->exprFormat()) {
// Upconvert to text-based format?
// Similar code in V3LinkResolve::visit(AstSFormatF)
UASSERT_OBJ(nodep->text() == "", nodep,
"Non-format $sformatf should have text format == \"\"");
if (VN_IS(nodep->exprsp(), Const)
&& VN_AS(nodep->exprsp(), Const)->num().isFromString()) {
AstConst* const fmtp = VN_AS(nodep->exprsp()->unlinkFrBack(), Const);
nodep->text(fmtp->num().toString());
nodep->exprFormat(false);
VL_DO_DANGLING(pushDeletep(fmtp), fmtp);
}
}
if (nodep->exprFormat()) return; // Runtime, unfortunately (unless constify format later)
bool anyconst = false;
for (AstNode* argp = nodep->exprsp(); argp; argp = argp->nextp()) {
if (VN_IS(argp, Const)) {
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg);
AstNode* const subargp = fargp ? fargp->exprp() : argp;
if (VN_IS(subargp, Const)) {
anyconst = true;
break;
}
}
if (m_doNConst && anyconst) {
// UINFO(9, " Display in " << nodep->text());
UINFO(9, " Display in " << nodep->text());
string newFormat;
string fmt;
bool inPct = false;
AstNode* argp = nodep->exprsp();
const string text = nodep->text();
for (const char ch : text) {
for (char ch : text) {
if (!inPct && ch == '%') {
inPct = true;
fmt = ch;
@ -3634,22 +3651,30 @@ class ConstVisitor final : public VNVisitor {
} else if (inPct) {
inPct = false;
fmt += ch;
switch (std::tolower(ch)) {
ch = std::tolower(ch);
switch (ch) {
case '%': break; // %% - still %%
case 'm': break; // %m - still %m - auto insert "name"
case 'l': break; // %l - still %l - auto insert "library"
case 't': // FALLTHRU
case '^': // %t/%^ - don't know $timeformat so can't constify
case 't': // %t - don't know $timeformat so can't constify
if (argp) argp = argp->nextp();
break;
default: // Most operators, just move to next argument
if (argp) {
AstNode* const nextp = argp->nextp();
if (VN_IS(argp, Const)) { // Convert it
const string out = constNumV(argp).displayed(nodep, fmt);
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg);
AstNode* const subargp = fargp ? fargp->exprp() : argp;
const VFormatAttr formatAttr
= argp
? AstSFormatArg::formatAttrDefauled(fargp, subargp->dtypep())
: VFormatAttr{};
if (VN_IS(subargp, Const)) { // Convert it
const string out
= constNumV(subargp).displayed(nodep, fmt, formatAttr);
UINFO(9, " DispConst: " << fmt << " -> " << out << " for "
<< argp);
// fmt = out w/ replace % with %% as it must be literal.
<< subargp);
// fmt = out w/ replace % with %% as it must later when
// used as a format output as literal.
fmt = VString::quotePercent(out);
VL_DO_DANGLING(pushDeletep(argp->unlinkFrBack()), argp);
}
@ -3667,9 +3692,20 @@ class ConstVisitor final : public VNVisitor {
UINFO(9, " Display out " << nodep);
}
}
if (!nodep->exprsp() && nodep->name().find('%') == string::npos && !nodep->hidden()) {
if (!nodep->exprsp() && nodep->text().find('%') == string::npos && !nodep->hidden()) {
// Just a simple constant string - the formatting is pointless
VL_DO_DANGLING(replaceConstString(nodep, nodep->name()), nodep);
UINFO(9, "replaceConstStr");
VL_DO_DANGLING(replaceConstString(nodep, nodep->text()), nodep);
return;
}
if (VN_IS(nodep->backp(), NodeAssign) // Can't remove under Display etc
&& nodep->text() == "%s" && VN_IS(nodep->exprsp(), SFormatArg)
&& VN_AS(nodep->exprsp(), SFormatArg)->formatAttr().isString()
&& !nodep->exprsp()->nextp()) {
UINFO(9, " Display(%p, expr) -> constant expr " << nodep);
nodep->replaceWith(VN_AS(nodep->exprsp(), SFormatArg)->exprp()->unlinkFrBack());
VL_DO_DANGLING(pushDeletep(nodep), nodep);
return;
}
}
void visit(AstNodeFTask* nodep) override {

View File

@ -85,6 +85,7 @@ class DepthVisitor final : public VNVisitor {
iterateChildren(nodep);
}
void visit(AstNodeStmt* nodep) override { visitStmt(nodep); }
void visit(AstSFormatArg* nodep) override { iterateChildren(nodep); } // Don't split
// Operators
void visit(AstNodeTermop* nodep) override {}
void visit(AstNodeExpr* nodep) override {

View File

@ -177,250 +177,173 @@ void EmitCFunc::emitOpName(AstNode* nodep, const string& format, AstNode* lhsp,
putOut();
}
void EmitCFunc::displayEmit(AstNode* nodep, bool isScan) {
if (m_emitDispState.m_format == ""
&& VN_IS(nodep, Display)) { // not fscanf etc, as they need to return value
// NOP
} else {
// Format
bool isStmt = false;
if (const AstFScanF* const dispp = VN_CAST(nodep, FScanF)) {
isStmt = false;
putns(nodep, "VL_FSCANF_INX(");
bool EmitCFunc::displayEmitHeader(AstNode* nodep, bool isScan) {
bool isStmt = false;
if (const AstFScanF* const dispp = VN_CAST(nodep, FScanF)) {
isStmt = false;
putns(nodep, "VL_FSCANF_INX(");
iterateConst(dispp->filep());
puts(",");
} else if (const AstSScanF* const dispp = VN_CAST(nodep, SScanF)) {
isStmt = false;
checkMaxWords(dispp->fromp());
putns(nodep, "VL_SSCANF_I");
emitIQW(dispp->fromp());
puts("NX(");
puts(cvtToStr(dispp->fromp()->widthMin()));
puts(",");
iterateConst(dispp->fromp());
puts(",");
} else if (const AstDisplay* const dispp = VN_CAST(nodep, Display)) {
isStmt = true;
if (dispp->filep()) {
putns(nodep, "VL_FWRITEF_NX(");
iterateConst(dispp->filep());
puts(",");
} else if (const AstSScanF* const dispp = VN_CAST(nodep, SScanF)) {
isStmt = false;
checkMaxWords(dispp->fromp());
putns(nodep, "VL_SSCANF_I");
emitIQW(dispp->fromp());
puts("NX(");
puts(cvtToStr(dispp->fromp()->widthMin()));
puts(",");
iterateConst(dispp->fromp());
puts(",");
} else if (const AstDisplay* const dispp = VN_CAST(nodep, Display)) {
isStmt = true;
if (dispp->filep()) {
putns(nodep, "VL_FWRITEF_NX(");
iterateConst(dispp->filep());
puts(",");
} else {
putns(nodep, "VL_WRITEF_NX(");
}
} else if (const AstSFormat* const dispp = VN_CAST(nodep, SFormat)) {
isStmt = true;
puts("VL_SFORMAT_NX(");
puts(cvtToStr(dispp->lhsp()->widthMin()));
putbs(",");
iterateConst(dispp->lhsp());
putbs(",");
} else if (VN_IS(nodep, SFormatF)) {
isStmt = false;
putns(nodep, "VL_SFORMATF_N_NX(");
} else {
nodep->v3fatalSrc("Unknown displayEmit node type");
putns(nodep, "VL_WRITEF_NX(");
}
ofp()->putsQuoted(m_emitDispState.m_format);
ofp()->puts(",0"); // MSVC++ requires va_args to not be off reference
// Arguments
for (unsigned i = 0; i < m_emitDispState.m_argsp.size(); i++) {
const char fmt = m_emitDispState.m_argsChar[i];
AstNode* const argp = m_emitDispState.m_argsp[i];
const string func = m_emitDispState.m_argsFunc[i];
if (func != "" || argp) {
puts(",");
ofp()->indentInc();
ofp()->putbs("");
if (func != "") {
puts(func);
} else if (argp) {
const bool addrof = isScan || (fmt == '@') || (fmt == 'p');
if (addrof) puts("&(");
iterateConst(argp);
if (!addrof) emitDatap(argp);
if (addrof) puts(")");
}
ofp()->indentDec();
}
}
// End
puts(")");
if (isStmt) {
puts(";\n");
} else {
puts(" ");
}
// Prep for next
m_emitDispState.clear();
} else if (const AstSFormat* const dispp = VN_CAST(nodep, SFormat)) {
isStmt = true;
puts("VL_SFORMAT_NX(");
puts(cvtToStr(dispp->lhsp()->widthMin()));
putbs(",");
iterateConst(dispp->lhsp());
putbs(",");
} else if (VN_IS(nodep, SFormatF)) {
isStmt = false;
putns(nodep, "VL_SFORMATF_N_NX(");
} else {
nodep->v3fatalSrc("Unknown displayEmit node type");
}
return isStmt;
}
void EmitCFunc::displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const string& vfmt,
bool ignore, char fmtLetter) {
// Print display argument, edits elistp
AstNode* argp = nullptr;
if (!ignore) {
argp = *elistp;
if (VL_UNCOVERABLE(!argp)) { // LCOV_EXCL_START
// expectDisplay() checks this first, so internal error if found here
dispp->v3error("Internal: Missing arguments for $display-like format");
return;
} // LCOV_EXCL_STOP
// Prep for next parameter
*elistp = (*elistp)->nextp();
if (argp->widthMin() > VL_VALUE_STRING_MAX_WIDTH) {
dispp->v3warn(E_UNSUPPORTED, "Unsupported: Exceeded limit of "
void EmitCFunc::displayNode(AstNode* nodep, AstSFormatF* fmtp, // fmtp is nullptr for AstScan
const string& vformat, AstNode* exprsp, bool isScan) {
// Check format, if it exists
const bool exprFormat = fmtp && fmtp->exprFormat();
bool needsScope = exprFormat;
bool needsTimescale = exprFormat;
int formatArgs = 0;
if (!exprFormat) {
// Check display and argument count to head-off later runtime issues
bool inPct = false;
bool ignore = false;
for (char ch : vformat) {
// UINFO(1, "Parse '" << *pos << "' IP" << inPct << " List " << cvtToHex(elistp));
if (!inPct && ch == '%') {
inPct = true;
ignore = false;
} else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) {
} else if (!inPct) { // Normal text
} else { // Format character
inPct = false;
ch = std::tolower(ch);
switch (ch) {
case '%': break;
case '*':
inPct = true; // Get more digits
ignore = true;
break;
case 'm': needsScope = true; break;
case 't':
needsTimescale = true;
++formatArgs;
break;
default:
if (!ignore && V3Number::displayedFmtHasArg(ch, isScan)) ++formatArgs;
break;
}
}
}
}
if (vformat.empty() && VN_IS(nodep, Display)) // not fscanf etc, as they need to return value
return; // NOP
const bool isStmt = displayEmitHeader(nodep, isScan);
if (exprFormat) {
UASSERT_OBJ(exprsp, nodep, "Missing format expression");
iterateConst(exprsp);
exprsp = exprsp->nextp();
} else {
ofp()->putsQuoted(vformat);
}
int exprArgs = 0;
for (AstNode* argp = exprsp; argp; argp = argp->nextp()) ++exprArgs;
if (VL_UNCOVERABLE(!exprFormat && exprArgs != formatArgs)) { // LCOV_EXCL_START
// expectDisplay() checks this first, so probably internal error if found here
nodep->v3error("Internal: Missing or extra arguments for $display-like format, "
<< " expression had " << exprArgs << ", format had " << formatArgs);
} // LCOV_EXCL_STOP
// argc for error check. Also MSVC++ requires va_args to not be off a reference
int argc = 0;
if (needsScope) ++argc;
if (needsTimescale) ++argc;
for (AstNode* argp = exprsp; argp; argp = argp->nextp()) ++argc;
ofp()->puts("," + std::to_string(argc));
if (needsScope) {
AstScopeName* const scopenamep = fmtp ? fmtp->scopeNamep() : nullptr;
UASSERT_OBJ(scopenamep, nodep, "Display with %m but no AstScopeName");
const string suffix = scopenamep->scopePrettySymName();
ofp()->puts(", '"s + VFormatAttr{VFormatAttr::SCOPE}.ascii() + "'");
ofp()->puts(",vlSymsp->name(),");
ofp()->putsQuoted(suffix);
}
if (needsTimescale) {
VTimescale timeunit = VTimescale::NONE;
if (const AstDisplay* const anodep = VN_CAST(nodep, Display)) {
timeunit = anodep->fmtp()->timeunit();
} else if (const AstSFormat* const anodep = VN_CAST(nodep, SFormat)) {
timeunit = anodep->fmtp()->timeunit();
} else if (const AstSScanF* const anodep = VN_CAST(nodep, SScanF)) {
timeunit = anodep->timeunit();
} else if (const AstSFormatF* const anodep = VN_CAST(nodep, SFormatF)) {
timeunit = anodep->timeunit();
}
UASSERT_OBJ(!timeunit.isNone(), nodep,
"Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF, or "
"SScanF, and timeunit set");
ofp()->puts(", '"s + VFormatAttr{VFormatAttr::TIMEUNIT}.ascii() + "'");
ofp()->puts(","s + cvtToStr((int)timeunit.powerOfTen()));
}
// Arguments
for (AstNode* argp = exprsp; argp; argp = argp->nextp()) {
ofp()->indentInc();
ofp()->putbs("");
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg);
AstNode* const subargp = fargp ? fargp->exprp() : argp;
const VFormatAttr formatAttr = AstSFormatArg::formatAttrDefauled(fargp, subargp->dtypep());
if (subargp->widthMin() > VL_VALUE_STRING_MAX_WIDTH) {
nodep->v3warn(E_UNSUPPORTED, "Unsupported: Exceeded limit of "
+ cvtToStr(VL_VALUE_STRING_MAX_WIDTH)
+ " bits for any $display-like arguments");
}
puts(", '"s + formatAttr.ascii() + '\'');
if (formatAttr.isSigned() || formatAttr.isUnsigned())
puts("," + cvtToStr(subargp->widthMin()));
const bool addrof = isScan || formatAttr.isString() || formatAttr.isComplex();
puts(",");
if (addrof) puts("&(");
iterateConst(subargp);
if (addrof) puts(")");
if (!addrof) emitDatap(argp);
ofp()->indentDec();
}
// string pfmt = "%"+displayFormat(argp, vfmt, fmtLetter)+fmtLetter;
string pfmt;
if ((fmtLetter == '#' || fmtLetter == 'd') && !isScan
&& vfmt == "") { // Size decimal output. Spec says leading spaces, not zeros
const double mantissabits = ignore ? 0 : (argp->widthMin() - ((fmtLetter == 'd') ? 1 : 0));
// This is log10(2**mantissabits) as log2(2**mantissabits)/log2(10),
// + 1.0 rounding bias.
double dchars = mantissabits / 3.321928094887362 + 1.0;
if (fmtLetter == 'd') dchars++; // space for sign
const int nchars = int(dchars);
pfmt = "%"s + cvtToStr(nchars) + fmtLetter;
} else {
pfmt = "%"s + vfmt + fmtLetter;
}
m_emitDispState.pushFormat(pfmt);
if (!ignore) {
if (argp->dtypep()->basicp()
&& argp->dtypep()->basicp()->keyword() == VBasicDTypeKwd::STRING) {
// string in SystemVerilog is std::string in C++ which is not POD
m_emitDispState.pushArg(' ', nullptr, "-1");
} else {
m_emitDispState.pushArg(' ', nullptr, cvtToStr(argp->widthMin()));
}
m_emitDispState.pushArg(fmtLetter, argp, "");
if (fmtLetter == 't' || fmtLetter == '^') {
VTimescale timeunit = VTimescale::NONE;
if (const AstDisplay* const nodep = VN_CAST(dispp, Display)) {
timeunit = nodep->fmtp()->timeunit();
} else if (const AstSFormat* const nodep = VN_CAST(dispp, SFormat)) {
timeunit = nodep->fmtp()->timeunit();
} else if (const AstSScanF* const nodep = VN_CAST(dispp, SScanF)) {
timeunit = nodep->timeunit();
} else if (const AstSFormatF* const nodep = VN_CAST(dispp, SFormatF)) {
timeunit = nodep->timeunit();
}
UASSERT_OBJ(!timeunit.isNone(), dispp,
"Use of %t must be under AstDisplay, AstSFormat, or AstSFormatF, or "
"SScanF, and timeunit set");
m_emitDispState.pushArg(' ', nullptr, cvtToStr((int)timeunit.powerOfTen()));
}
} else {
m_emitDispState.pushArg(fmtLetter, nullptr, "");
}
}
void EmitCFunc::displayNode(AstNode* nodep, AstScopeName* scopenamep, const string& vformat,
AstNode* exprsp, bool isScan) {
AstNode* elistp = exprsp;
// Convert Verilog display to C printf formats
// "%0t" becomes "%d"
VL_RESTORER(m_emitDispState);
m_emitDispState.clear();
string vfmt;
string::const_iterator pos = vformat.begin();
bool inPct = false;
bool ignore = false;
for (; pos != vformat.end(); ++pos) {
// UINFO(1, "Parse '" << *pos << "' IP" << inPct << " List " << cvtToHex(elistp));
if (!inPct && pos[0] == '%') {
inPct = true;
ignore = false;
vfmt = "";
} else if (!inPct) { // Normal text
m_emitDispState.pushFormat(*pos);
} else { // Format character
inPct = false;
switch (std::tolower(pos[0])) {
case '0': // FALLTHRU
case '1': // FALLTHRU
case '2': // FALLTHRU
case '3': // FALLTHRU
case '4': // FALLTHRU
case '5': // FALLTHRU
case '6': // FALLTHRU
case '7': // FALLTHRU
case '8': // FALLTHRU
case '9': // FALLTHRU
case '.': // FALLTHRU
case '-':
// Digits, like %5d, etc.
vfmt += pos[0];
inPct = true; // Get more digits
break;
case '%':
m_emitDispState.pushFormat("%%"); // We're printf'ing it, so need to quote the %
break;
case '*':
vfmt += pos[0];
inPct = true; // Get more digits
ignore = true;
break;
// Special codes
case '~':
displayArg(nodep, &elistp, isScan, vfmt, ignore, 'd');
break; // Signed decimal
case '@':
displayArg(nodep, &elistp, isScan, vfmt, ignore, '@');
break; // Packed string
// Spec: h d o b c l
case 'b': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'b'); break;
case 'c': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'c'); break;
case 't': displayArg(nodep, &elistp, isScan, vfmt, ignore, 't'); break;
case 'd':
displayArg(nodep, &elistp, isScan, vfmt, ignore, '#');
break; // Unsigned decimal
case 'o': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'o'); break;
case 'h': // FALLTHRU
case 'x': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'x'); break;
case 'p': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'p'); break;
case 's': displayArg(nodep, &elistp, isScan, vfmt, ignore, 's'); break;
case 'e': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'e'); break;
case 'f': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'f'); break;
case 'g': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'g'); break;
case '^': displayArg(nodep, &elistp, isScan, vfmt, ignore, '^'); break; // Realtime
case 'v': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'v'); break;
case 'u': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'u'); break;
case 'z': displayArg(nodep, &elistp, isScan, vfmt, ignore, 'z'); break;
case 'm': {
UASSERT_OBJ(scopenamep, nodep, "Display with %m but no AstScopeName");
const string suffix = scopenamep->scopePrettySymName();
if (suffix == "") {
m_emitDispState.pushFormat("%S");
} else {
m_emitDispState.pushFormat("%N"); // Add a . when needed
}
m_emitDispState.pushArg(' ', nullptr, "vlSymsp->name()");
m_emitDispState.pushFormat(suffix);
break;
}
case 'l': {
// Better than not compiling
m_emitDispState.pushFormat("----");
break;
}
default:
nodep->v3error("Unknown $display-like format code: '%" << pos[0] << "'");
break;
}
}
// End
if (isStmt) {
puts(");\n");
} else {
puts(") ");
}
if (VL_UNCOVERABLE(elistp)) {
// expectFormat also checks this, and should have found it first, so internal
elistp->v3error("Internal: Extra arguments for $display-like format"); // LCOV_EXCL_LINE
}
displayEmit(nodep, isScan);
}
void EmitCFunc::emitCCallArgs(const AstNodeCCall* nodep, const string& selfPointer,

View File

@ -121,28 +121,6 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks
bool m_createdScopeHash = false; // Already created a scope hash
// State associated with processing $display style string formatting
struct EmitDispState final {
string m_format; // "%s" and text from user
std::vector<char> m_argsChar; // Format of each argument to be printed
std::vector<AstNode*> m_argsp; // Each argument to be printed
std::vector<string> m_argsFunc; // Function before each argument to be printed
EmitDispState() { clear(); }
void clear() {
m_format = "";
m_argsChar.clear();
m_argsp.clear();
m_argsFunc.clear();
}
void pushFormat(const string& fmt) { m_format += fmt; }
void pushFormat(char fmt) { m_format += fmt; }
void pushArg(char fmtChar, AstNode* nodep, const string& func) {
m_argsChar.push_back(fmtChar);
m_argsp.push_back(nodep);
m_argsFunc.push_back(func);
}
} m_emitDispState;
protected:
VL_DEFINE_DEBUG_FUNCTIONS;
@ -172,11 +150,9 @@ protected:
public:
// METHODS
void displayNode(AstNode* nodep, AstScopeName* scopenamep, const string& vformat,
AstNode* exprsp, bool isScan);
void displayEmit(AstNode* nodep, bool isScan);
void displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const string& vfmt, bool ignore,
char fmtLetter);
bool displayEmitHeader(AstNode* nodep, bool isScan);
void displayNode(AstNode* nodep, AstSFormatF* fmtp, const string& vformat, AstNode* exprsp,
bool isScan);
bool emitSimpleOk(AstNodeExpr* nodep);
void emitIQW(const AstNode* nodep) {
@ -895,7 +871,7 @@ public:
void visit(AstDisplay* nodep) override {
string text = nodep->fmtp()->text();
if (nodep->addNewline()) text += "\n";
displayNode(nodep, nodep->fmtp()->scopeNamep(), text, nodep->fmtp()->exprsp(), false);
displayNode(nodep, nodep->fmtp(), text, nodep->fmtp()->exprsp(), false);
}
void visit(AstDumpCtl* nodep) override {
switch (nodep->ctlType()) {
@ -946,11 +922,10 @@ public:
}
}
void visit(AstSFormat* nodep) override {
displayNode(nodep, nodep->fmtp()->scopeNamep(), nodep->fmtp()->text(),
nodep->fmtp()->exprsp(), false);
displayNode(nodep, nodep->fmtp(), nodep->fmtp()->text(), nodep->fmtp()->exprsp(), false);
}
void visit(AstSFormatF* nodep) override {
displayNode(nodep, nodep->scopeNamep(), nodep->text(), nodep->exprsp(), false);
displayNode(nodep, nodep, nodep->text(), nodep->exprsp(), false);
}
void visit(AstFScanF* nodep) override {
displayNode(nodep, nullptr, nodep->text(), nodep->exprsp(), true);

View File

@ -363,6 +363,7 @@ class EmitVBaseVisitorConst VL_NOT_FINAL : public VNVisitorConst {
visitNodeDisplay(nodep, nodep->lhsp(), nodep->fmtp()->text(), nodep->fmtp()->exprsp());
}
void visit(AstToStringN* nodep) override { iterateConst(nodep->lhsp()); }
void visit(AstSFormatArg* nodep) override { iterateConst(nodep->exprp()); }
void visit(AstSFormatF* nodep) override {
visitNodeDisplay(nodep, nullptr, nodep->text(), nodep->exprsp());
}

View File

@ -225,8 +225,8 @@ class LinkResolveVisitor final : public VNVisitor {
if (VN_IS(nodep->backp(), StmtExpr)) {
nodep->v3error("Expected statement, not let substitution " << letp->prettyNameQ());
}
// UINFOTREE(1, letp, "", "let-let");
// UINFOTREE(1, nodep, "", "let-ref");
// UINFOTREE(9, letp, "", "let-let");
// UINFOTREE(9, nodep, "", "let-ref");
// cppcheck-suppress constVariablePointer
AstStmtExpr* const letStmtp = VN_AS(letp->stmtsp(), StmtExpr);
AstNodeExpr* const newp = letStmtp->exprp()->cloneTree(false);
@ -250,7 +250,7 @@ class LinkResolveVisitor final : public VNVisitor {
VL_DO_DANGLING(pushDeletep(refp), refp);
}
});
// UINFOTREE(1, newp, "", "let-new");
// UINFOTREE(9, newp, "", "let-new");
nodep->replaceWith(newp);
VL_DO_DANGLING(pushDeletep(nodep), nodep);
// Iterate to expand further now, so we can look for recursions
@ -362,7 +362,7 @@ class LinkResolveVisitor final : public VNVisitor {
+ m_modp->prettyName();
break;
default: // Most operators, just move to next argument
if (!V3Number::displayedFmtLegal(ch, isScan)) {
if (!V3Number::displayedFmtHasArg(ch, isScan)) {
nodep->v3error("Unknown $display-like format code: '%" << ch << "'");
} else if (!inIgnore) {
if (!argp) {
@ -436,24 +436,29 @@ class LinkResolveVisitor final : public VNVisitor {
if (nodep->user2SetOnce()) return;
iterateChildren(nodep);
// Cleanup old-school displays without format arguments
// Similar code in V3Const::visit(AstSFormatF)
if (nodep->exprFormat()) {
UASSERT_OBJ(nodep->text() == "", nodep,
"Non-format $sformatf should have text format == \"\"");
if (VN_IS(nodep->exprsp(), Const)
&& VN_AS(nodep->exprsp(), Const)->num().isFromString()) {
AstConst* const fmtp = VN_AS(nodep->exprsp()->unlinkFrBack(), Const);
nodep->text(fmtp->num().toString());
nodep->exprFormat(false);
VL_DO_DANGLING(pushDeletep(fmtp), fmtp);
}
}
if (nodep->optionalFormat()) { // e.g. $display, $swrite; _not_ $sformat/$sformatf
nodep->optionalFormat(false);
nodep->exprFormat(false);
}
const string newFormat = expectFormat(nodep, nodep->text(), nodep->exprsp(), false);
nodep->text(newFormat);
if ((VN_IS(nodep->backp(), Display)
&& VN_AS(nodep->backp(), Display)->displayType().needScopeTracking())
|| nodep->formatScopeTracking()) {
nodep->scopeNamep(new AstScopeName{nodep->fileline(), true});
}
if (!nodep->exprFormat()) {
const string newFormat = expectFormat(nodep, nodep->text(), nodep->exprsp(), false);
nodep->text(newFormat);
}
}
void visit(AstUdpTable* nodep) override {

View File

@ -336,7 +336,8 @@ void V3Number::create(const char* sourcep) {
break;
}
case 'h': {
case 'h': // FALLTHRU
case 'x': {
base_align = 4;
switch (std::tolower(*cp)) { // clang-format off
case '0': setBit(obit++,0); setBit(obit++,0); setBit(obit++,0); setBit(obit++,0); break;
@ -610,29 +611,31 @@ string V3Number::ascii(bool prefixed, bool cleanVerilog) const VL_MT_STABLE {
return out.str();
}
bool V3Number::displayedFmtLegal(char format, bool isScan) {
// Is this a valid format letter?
bool V3Number::displayedFmtHasArg(char format, bool isScan) {
// Is this a format letter that takes an argument in runtime?
(void)isScan;
switch (std::tolower(format)) {
case 'b': return true; // Binary
case 'c': return true; // Character
case 'd': return true; // Decimal; internal: Unsigned decimal
case 'd': return true; // Decimal
case 'e': return true; // Floating
case 'f': return true; // Floating
case 'g': return true; // Floating
case 'h': return true; // Hex
case 'o': return true; // Octal
case 'p': return true; // Pattern
case 's': return true; // String; internal: number-stored string
case 's': return true; // String
case 't': return true; // Time
case 'u': return true; // Packed 2-state
case 'v': return true; // Strength
case 'x': return true; // Hex
case 'z': return true; // Packed 4-state
case '@': return true; // Internal: Packed string
case '~': return true; // Internal: Signed decimal
case '*': return isScan; // $scan ignore argument
case '?': return true; // Internal: Compute format in V3Width based on data type
default: return false;
}
// 'l' // Library - precomputed front string, so no arg
// 'm' // Module - precomputed front string, so no arg
// '*' // $scan ignore argument
}
string V3Number::displayPad(size_t fmtsize, char pad, bool left, const string& in) VL_PURE {
@ -641,12 +644,16 @@ string V3Number::displayPad(size_t fmtsize, char pad, bool left, const string& i
return left ? (in + padding) : (padding + in);
}
string V3Number::displayed(const AstNode* nodep, const string& vformat) const VL_MT_STABLE {
return displayed(nodep->fileline(), vformat);
string V3Number::displayed(const AstNode* nodep, const string& vformat,
const VFormatAttr& formatAttr) const VL_MT_STABLE {
return displayed(nodep->fileline(), vformat, formatAttr);
}
string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STABLE {
string V3Number::displayed(FileLine* fl, const string& vformat,
const VFormatAttr& formatAttr) const VL_MT_STABLE {
// Similar code flow in verilated.cpp _vl_vsformat
// Correct number of zero bits/width matters
// UINFO(9, "displayed(\"" << vformat << "\", formatAttr=" << formatAttr);
auto pos = vformat.cbegin();
UASSERT(pos != vformat.cend() && pos[0] == '%',
"$display-like function with non-format argument " << *this);
@ -661,8 +668,96 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
fmtsize += pos[0];
}
string str;
const char code = std::tolower(pos[0]);
switch (code) {
char fmt = std::tolower(pos[0]);
// Override user's code for certain data-type determined arguments
if (formatAttr.isComplex()) {
if (fmt != 'p') fmt = 's'; // override
} else if (formatAttr.isString()) {
if (fmt == 'h' || fmt == 'x') {
// V3Width errors on const %x of string, but V3Randomize may make a %x on a
// string, or may have a runtime format
V3Number strwide{fl, static_cast<int>(toString().length()) * 8, 0};
strwide.opNToI(*this);
return strwide.displayed(fl, vformat, VFormatAttr::UNSIGNED);
}
if (fmt != 'p') fmt = 's'; // override
} else if (formatAttr.isSigned() || formatAttr.isUnsigned()) {
if (fmt == 'p') {
if (fmtsize == "0") { // For %0p, IEEE our choice, use 'h%0h
str += "'h";
fmt = 'h';
} else { // UVM tests require %0d
fmtsize = "0";
fmt = 'd';
}
}
} else if (formatAttr.isDouble()) {
// 'p' not converted to 'g' here as vformat string can't be easily changed
if (!(fmt == 'e' || fmt == 'f' || fmt == 'g' || fmt == 'p'
|| VL_UNCOVERABLE(fmt == 't'))) {
// A non-float format with a float, must convert to integer then format
V3Number nonfloat{fl, 64, 0};
nonfloat.opRToIRoundS(*this);
return nonfloat.displayed(fl, vformat, VFormatAttr::SIGNED);
}
}
switch (fmt) {
case 'c': {
if (width() > 8)
fl->v3warn(WIDTHTRUNC, "$display-like format of %c format of > 8 bit value");
const unsigned int v = bitsValue(0, 8);
str.push_back(static_cast<char>(v));
return str;
}
case 'e': // FALLTHRU
case 'f': // FALLTHRU
case 'g': {
const double n = formatAttr.isDouble() ? toDouble()
: formatAttr.isSigned() ? toSQuad()
: toUQuad();
char tmp[MAX_SPRINTF_DOUBLE_SIZE];
VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, vformat.c_str(), n);
return tmp;
}
case 's': {
if (formatAttr.isString() || formatAttr.isComplex()) {
str = toString();
} else {
// Spec says always drop leading zeros, this isn't quite right, we space pad.
int bit = width() - 1;
bool start = true;
while ((bit % 8) != 7) ++bit;
for (; bit >= 0; bit -= 8) {
const int v = bitsValue(bit - 7, 8);
if (!start || v) {
str += static_cast<char>((v == 0) ? ' ' : v);
start = false; // Drop leading 0s
} else {
if (fmtsize != "0") str += ' ';
}
}
}
const size_t fmtsizen = static_cast<size_t>(std::atoi(fmtsize.c_str()));
str = displayPad(fmtsizen, ' ', left, str);
return str;
}
case 'p': { // Pattern
// 'p' with NUMBER was earlier converted to 'd'
if (formatAttr.isDouble()) {
const double n = formatAttr.isDouble() ? toDouble()
: formatAttr.isSigned() ? toSQuad()
: toUQuad();
char tmp[MAX_SPRINTF_DOUBLE_SIZE];
VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, "%g", n);
return tmp;
}
if (formatAttr.isString()) return '"' + toString() + '"';
if (formatAttr.isComplex()) return toString();
return "%p";
}
case 'b': // FALLTHRU
case 'o': // FALLTHRU
case 'h': // FALLTHRU
@ -671,7 +766,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
if (left || !fmtsize.empty()) {
while (bit && bitIs0(bit)) --bit;
}
switch (code) {
switch (fmt) {
case 'b': {
for (; bit >= 0; --bit) {
if (bitIs0(bit)) {
@ -747,34 +842,9 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
str = displayPad(fmtsizen, (left ? ' ' : '0'), left, str);
return str;
} // case b/d/x/o
case 'c': {
// V3Width has warning if > 8 bits
const unsigned int v = bitsValue(0, 8);
str.push_back(static_cast<char>(v));
return str;
}
case 's': {
// Spec says always drop leading zeros, this isn't quite right, we space pad.
int bit = width() - 1;
bool start = true;
while ((bit % 8) != 7) ++bit;
for (; bit >= 0; bit -= 8) {
const int v = bitsValue(bit - 7, 8);
if (!start || v) {
str += static_cast<char>((v == 0) ? ' ' : v);
start = false; // Drop leading 0s
} else {
if (fmtsize != "0") str += ' ';
}
}
const size_t fmtsizen = static_cast<size_t>(std::atoi(fmtsize.c_str()));
str = displayPad(fmtsizen, ' ', left, str);
return str;
}
case '~': // Signed decimal
case 't': // Time
case 'd': { // Unsigned decimal
const bool issigned = (code == '~');
const bool issigned = formatAttr.isSigned();
if (fmtsize == "" && !left) {
const double mantissabits = width() - (issigned ? 1 : 0);
// To get the number of digits required, we want to compute
@ -820,16 +890,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
str = displayPad(fmtsizen, (zeropad ? '0' : ' '), left, str);
return str;
}
case 'e':
case 'f':
case 'g':
case '^': { // Realtime
char tmp[MAX_SPRINTF_DOUBLE_SIZE];
VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, vformat.c_str(), toDouble());
return tmp;
}
// 'l' // Library - converted to text by V3LinkResolve
// 'p' // Packed - converted to another code by V3Width
case 'u': { // Packed 2-state
for (int i = 0; i < words(); ++i) {
const uint32_t v = m_data.num()[i].m_value;
@ -870,11 +931,6 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
}
return str;
}
case '@': { // Packed string
const size_t fmtsizen = static_cast<size_t>(std::atoi(fmtsize.c_str()));
str = displayPad(fmtsizen, ' ', left, toString());
return str;
}
default: fl->v3fatalSrc("Unknown $display-like format code for number: %" << pos[0]);
}
}

View File

@ -30,12 +30,53 @@
#include <limits>
#include <vector>
//============================================================================
class AstNode;
class AstNodeDType;
class AstSFormatArg;
class FileLine;
//============================================================================
class VFormatAttr final {
public:
enum en : char {
// // AstSFormatArg is typically skipped for UNSIGNED, as is the default
UNSIGNED = VL_VFORMATATTR_UNSIGNED,
SIGNED = VL_VFORMATATTR_SIGNED,
//
COMPLEX = VL_VFORMATATTR_COMPLEX,
DOUBLE = VL_VFORMATATTR_DOUBLE,
SCOPE = VL_VFORMATATTR_SCOPE,
STRING = VL_VFORMATATTR_STRING,
TIMEUNIT = VL_VFORMATATTR_TIMEUNIT
};
enum en m_e;
VFormatAttr()
: m_e{UNSIGNED} {}
// cppcheck-suppress noExplicitConstructor
constexpr VFormatAttr(en _e)
: m_e{_e} {}
explicit VFormatAttr(int _e)
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
constexpr operator en() const { return m_e; }
char ascii() const { return m_e; }
bool isComplex() const { return m_e == COMPLEX; }
bool isDouble() const { return m_e == DOUBLE; }
bool isSigned() const { return m_e == SIGNED; }
bool isString() const { return m_e == STRING; }
bool isUnsigned() const { return m_e == UNSIGNED; }
};
constexpr bool operator==(const VFormatAttr& lhs, const VFormatAttr& rhs) {
return lhs.m_e == rhs.m_e;
}
constexpr bool operator==(const VFormatAttr& lhs, VFormatAttr::en rhs) { return lhs.m_e == rhs; }
constexpr bool operator==(VFormatAttr::en lhs, const VFormatAttr& rhs) { return lhs == rhs.m_e; }
inline std::ostream& operator<<(std::ostream& os, const VFormatAttr& rhs) {
return os << rhs.ascii();
}
//============================================================================
class V3NumberData final {
public:
// TYPES
@ -300,6 +341,8 @@ private:
new (&m_string) std::string(std::forward<Args>(args)...);
}
string formatDecimal();
void destroyDynamicNumber() { m_dynamicNumber.~vector(); }
void destroyString() { m_string.~string(); }
void destroyStoredValue() {
@ -595,9 +638,11 @@ public:
// ACCESSORS
string ascii(bool prefixed = true, bool cleanVerilog = false) const VL_MT_STABLE;
string displayed(const AstNode* nodep, const string& vformat) const VL_MT_STABLE;
string displayed(FileLine* fl, const string& vformat) const VL_MT_STABLE;
static bool displayedFmtLegal(char format, bool isScan); // Is this a valid format letter?
string displayed(const AstNode* nodep, const string& vformat,
const VFormatAttr& formatAttr = VFormatAttr::UNSIGNED) const VL_MT_STABLE;
string displayed(FileLine* fl, const string& vformat,
const VFormatAttr& formatAttr = VFormatAttr::UNSIGNED) const VL_MT_STABLE;
static bool displayedFmtHasArg(char format, bool isScan);
string emitC() const VL_MT_STABLE;
int width() const VL_MT_SAFE { return m_data.width(); }
int widthToFit() const; // Minimum width that can represent this number (~== log2(num)+1)

View File

@ -319,10 +319,16 @@ class PremitVisitor final : public VNVisitor {
iterateChildren(nodep);
// Any strings sent to a display must be var of string data type,
// to avoid passing a pointer to a temporary.
for (AstNodeExpr *expp = nodep->exprsp(), *nextp; expp; expp = nextp) {
nextp = VN_AS(expp->nextp(), NodeExpr);
if (expp->isString() && !VN_IS(expp, VarRef)) {
AstVar* const varp = createTemp(expp);
AstNodeExpr* exprsp = nodep->exprsp();
if (nodep->exprFormat()) exprsp = VN_AS(exprsp->nextp(), NodeExpr);
for (AstNodeExpr *argp = exprsp, *nextp; argp; argp = nextp) {
nextp = VN_AS(argp->nextp(), NodeExpr);
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg);
AstNodeExpr* const subargp = fargp ? fargp->exprp() : argp;
// Must avoid taking address of rvalue, so even Const needs a temp
if (subargp->isString() && !VN_IS(subargp, VarRef)) {
AstVar* const varp = createTemp(subargp);
// Do not remove VarRefs to this in V3Const
varp->noSubst(true);
}

View File

@ -737,7 +737,7 @@ class ConstraintExprVisitor final : public VNVisitor {
bool m_wantSingle = false; // Whether to merge constraint expressions with LOGAND
VMemberMap& m_memberMap; // Member names cached for fast lookup
bool m_structSel = false; // Marks when inside structSel
// (used to format "%@.%@" for struct arrays)
// (used to format "%s.%s" for struct arrays)
std::set<std::string>& m_writtenVars; // Track which variable paths have write_var generated
// (shared across all constraints)
std::set<AstVar*>* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints
@ -887,19 +887,19 @@ class ConstraintExprVisitor final : public VNVisitor {
switch (pos[0]) {
case '%': break;
case 'l':
pos[0] = '@';
pos[0] = 's';
UASSERT_OBJ(lhsp, nodep, "emitSMT() references undef node");
argsp = AstNode::addNext(argsp, lhsp);
lhsp = nullptr;
break;
case 'r':
pos[0] = '@';
pos[0] = 's';
UASSERT_OBJ(rhsp, nodep, "emitSMT() references undef node");
argsp = AstNode::addNext(argsp, rhsp);
rhsp = nullptr;
break;
case 't':
pos[0] = '@';
pos[0] = 's';
UASSERT_OBJ(thsp, nodep, "emitSMT() references undef node");
argsp = AstNode::addNext(argsp, thsp);
thsp = nullptr;
@ -912,8 +912,8 @@ class ConstraintExprVisitor final : public VNVisitor {
UASSERT_OBJ(!rhsp, nodep, "Missing emitSMT %r for " << rhsp);
UASSERT_OBJ(!thsp, nodep, "Missing emitSMT %t for " << thsp);
AstSFormatF* const newp = new AstSFormatF{nodep->fileline(), smtExpr, false, argsp};
if (m_structSel && newp->name() == "(select %@ %@)") {
newp->name("%@.%@");
if (m_structSel && newp->name() == "(select %s %s)") {
newp->name("%s.%s");
if (!VN_IS(nodep, AssocSel)) newp->exprsp()->nextp()->name("%x");
}
nodep->replaceWith(newp);
@ -943,7 +943,7 @@ class ConstraintExprVisitor final : public VNVisitor {
std::ostringstream fmt;
fmt << "(bvand";
for (AstNode* itemp = exprsp; itemp; itemp = itemp->nextp()) fmt << " %@";
for (AstNode* itemp = exprsp; itemp; itemp = itemp->nextp()) fmt << " %s";
fmt << ')';
return new AstSFormatF{fl, fmt.str(), false, exprsp};
}
@ -1683,8 +1683,8 @@ class ConstraintExprVisitor final : public VNVisitor {
iterateChildren(nodep);
FileLine* const fl = nodep->fileline();
AstSFormatF* newp = nullptr;
if (VN_AS(nodep->fromp(), SFormatF)->name() == "%@.%@") {
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
if (VN_AS(nodep->fromp(), SFormatF)->name() == "%s.%s") {
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
VN_AS(nodep->fromp(), SFormatF)->exprsp()->cloneTreePure(true)};
if (newp->exprsp()->nextp()->name().rfind("#x", 0) == 0)
newp->exprsp()->nextp()->name("%x"); // for #x%x to %x
@ -1702,7 +1702,7 @@ class ConstraintExprVisitor final : public VNVisitor {
// Adaptive formatting and type handling for associative array keys
if (VN_IS(nodep->bitp(), VarRef) && VN_AS(nodep->bitp(), VarRef)->isString()) {
VNRelinker handle;
AstNodeExpr* const idxp = new AstSFormatF{fl, (m_structSel ? "%32p" : "#x%32p"), false,
AstNodeExpr* const idxp = new AstSFormatF{fl, (m_structSel ? "%32x" : "#x%32x"), false,
nodep->bitp()->unlinkFrBack(&handle)};
handle.relink(idxp);
editSMT(nodep, nodep->fromp(), idxp);
@ -1808,8 +1808,8 @@ class ConstraintExprVisitor final : public VNVisitor {
FileLine* const fl = nodep->fileline();
AstSFormatF* newp = nullptr;
if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) {
if (fromp->name() == "%@.%@") {
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
if (fromp->name() == "%s.%s") {
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
fromp->exprsp()->cloneTreePure(true)};
} else {
newp = new AstSFormatF{fl, fromp->name() + "." + nodep->name(), false,
@ -1865,8 +1865,8 @@ class ConstraintExprVisitor final : public VNVisitor {
FileLine* const fl = nodep->fileline();
AstSFormatF* newp = nullptr;
if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) {
if (fromp->name() == "%@.%@") {
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
if (fromp->name() == "%s.%s") {
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
fromp->exprsp()->cloneTreePure(true)};
} else {
newp = new AstSFormatF{fl, fromp->name() + "." + nodep->name(), false,
@ -1941,7 +1941,7 @@ class ConstraintExprVisitor final : public VNVisitor {
cexprp->add(new AstBegin{
fl, "", new AstForeach{fl, nodep->headerp()->unlinkFrBack(), cstmtp}, true});
cexprp->add("return ret.empty() ? \"#b1\" : \"(bvand\" + ret + \")\";\n})()");
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
} else {
iterateAndNextNull(nodep->bodyp());
nodep->replaceWith(new AstBegin{fl, "",
@ -2169,9 +2169,9 @@ class ConstraintExprVisitor final : public VNVisitor {
AstNodeExpr* const argsp = AstNode::addNext(nodep->fromp()->unlinkFrBack(), pinp);
AstSFormatF* newp = nullptr;
if (m_structSel)
newp = new AstSFormatF{fl, "%@.%@", false, argsp};
newp = new AstSFormatF{fl, "%s.%s", false, argsp};
else
newp = new AstSFormatF{fl, "(select %@ %@)", false, argsp};
newp = new AstSFormatF{fl, "(select %s %s)", false, argsp};
nodep->replaceWith(newp);
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
@ -2200,7 +2200,7 @@ class ConstraintExprVisitor final : public VNVisitor {
cexprp->add("([&]{\nstd::string ret;\n");
cexprp->add(new AstBegin{fl, "", new AstForeach{fl, headerp, cstmtp}, true});
cexprp->add("return ret.empty() ? \"#b0\" : \"(bvor\" + ret + \")\";\n})()");
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;
}
@ -2415,7 +2415,7 @@ class ConstraintExprVisitor final : public VNVisitor {
cexprp->add("([&]{\nstd::string ret = \"" + identity + "\";\n");
cexprp->add(new AstBegin{fl, "", new AstForeach{fl, headerp, cstmtp}, true});
cexprp->add("return ret;\n})()");
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
} else {
// For without 'with' clause: use AstCExpr with conditional
AstCExpr* const cexprp = new AstCExpr{fl};
@ -2424,7 +2424,7 @@ class ConstraintExprVisitor final : public VNVisitor {
cexprp->add(new AstBegin{fl, "", new AstForeach{fl, headerp, cstmtp}, true});
cexprp->add("return ret.empty() ? \"" + identity + "\" : \"(" + smtOp
+ "\" + ret + \")\";\n})()");
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
}
VL_DO_DANGLING(nodep->deleteTree(), nodep);
return;

View File

@ -1267,65 +1267,71 @@ private:
// Ignore
}
void visit(AstSFormatArg* nodep) override {
checkNodeInfo(nodep);
iterateChildrenConst(nodep);
}
void visit(AstSFormatF* nodep) override {
if (jumpingOver()) return;
if (!optimizable()) return; // Accelerate
checkNodeInfo(nodep);
iterateChildrenConst(nodep);
if (m_params) {
AstNode* nextArgp = nodep->exprsp();
if (m_checkOnly) return;
if (!optimizable()) return; // Accelerate
string result;
const string format = nodep->text();
auto pos = format.cbegin();
bool inPct = false;
string width;
for (; pos != format.cend(); ++pos) {
if (!inPct && pos[0] == '%') {
inPct = true;
width = "";
} else if (!inPct) { // Normal text
result += *pos;
} else { // Format character
if (std::isdigit(pos[0])) {
width += pos[0];
continue;
AstNode* nextArgp = nodep->exprsp();
string result;
const string format = nodep->text();
auto pos = format.cbegin();
bool inPct = false;
string width;
for (; pos != format.cend(); ++pos) {
if (!inPct && pos[0] == '%') {
inPct = true;
width = "";
} else if (!inPct) { // Normal text
result += *pos;
} else { // Format character
if (std::isdigit(pos[0])) {
width += pos[0];
continue;
}
inPct = false;
if (V3Number::displayedFmtHasArg(std::tolower(pos[0]), false)) {
AstNode* const argp = nextArgp;
nextArgp = nextArgp->nextp();
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg);
AstNode* const subargp = fargp ? fargp->exprp() : argp;
AstConst* const constp = fetchConstNull(subargp);
const VFormatAttr formatAttr
= argp ? AstSFormatArg::formatAttrDefauled(fargp, subargp->dtypep())
: VFormatAttr{};
if (!constp) {
clearOptimizable(nodep,
"Argument for $display-like statement is not constant");
break;
}
inPct = false;
if (V3Number::displayedFmtLegal(std::tolower(pos[0]), false)) {
AstNode* const argp = nextArgp;
nextArgp = nextArgp->nextp();
AstConst* const constp = fetchConstNull(argp);
if (!constp) {
clearOptimizable(
nodep, "Argument for $display like statement is not constant");
break;
}
const string pformat = "%"s + width + pos[0];
result += constp->num().displayed(nodep, pformat);
} else {
switch (std::tolower(pos[0])) {
case '%': result += "%"; break;
case 'm':
// This happens prior to AstScope so we don't
// know the scope name. Leave the %m in place.
result += "%m";
break;
default:
clearOptimizable(nodep, "Unknown $display-like format code.");
break;
}
const string pformat = "%"s + width + pos[0];
result += constp->num().displayed(nodep, pformat, formatAttr);
} else {
switch (std::tolower(pos[0])) {
case '%': result += "%"; break;
case 'm':
// This happens prior to AstScope so we don't
// know the scope name. Leave the %m in place.
result += "%m";
break;
default: clearOptimizable(nodep, "Unknown $display-like format code."); break;
}
}
}
AstConst* const resultConstp
= new AstConst{nodep->fileline(), AstConst::String{}, result};
setValue(nodep, resultConstp);
m_reclaimValuesp.push_back(resultConstp);
}
AstConst* const resultConstp = new AstConst{nodep->fileline(), AstConst::String{}, result};
setValue(nodep, resultConstp);
m_reclaimValuesp.push_back(resultConstp);
}
void visit(AstDisplay* nodep) override {
@ -1354,6 +1360,7 @@ private:
checkNodeInfo(nodep);
iterateChildrenConst(nodep);
if (!optimizable()) return;
if (m_checkOnly) return;
std::string result = toStringRecurse(nodep->lhsp());
if (!optimizable()) return;
AstConst* const resultConstp = new AstConst{nodep->fileline(), AstConst::String{}, result};

View File

@ -6034,14 +6034,31 @@ class WidthVisitor final : public VNVisitor {
checkForceReleaseLhs(nodep, nodep->lhsp());
}
void formatNoStringArg(AstNode* argp, char ch) {
if (argp && argp->isString()) {
static bool isFormatNonNumericArg(const AstNodeDType* dtypep) {
dtypep = dtypep->skipRefp();
return dtypep->isString() //
|| VN_IS(dtypep, AssocArrayDType) //
|| VN_IS(dtypep, WildcardArrayDType) //
|| VN_IS(dtypep, ClassRefDType) //
|| VN_IS(dtypep, DynArrayDType) //
|| VN_IS(dtypep, UnpackArrayDType) //
|| VN_IS(dtypep, QueueDType) //
|| (VN_IS(dtypep, NodeUOrStructDType)
&& !VN_AS(dtypep, NodeUOrStructDType)->packed());
}
void checkFormatNumericArg(AstNode* argp, char ch) {
// IEEE 1800-2023 21.2.1.1 suggests %d of a class is illegal.
// Howver UVM tests require %0d print unique identifier of the class.
if (std::tolower(ch) == 'd') return;
// In contrast, %x of a class reference, causes errors on several simulators.
if (isFormatNonNumericArg(argp->dtypep()))
argp->v3error(
"$display-like format of '%"s + ch + "' illegal with non-numeric argument\n"
<< argp->warnMore() << "... Suggest use '%s'");
}
}
void visit(AstSFormatArg* nodep) override {} // Done by AstSFormatF
void visit(AstSFormat* nodep) override {
assertAtStatement(nodep);
userIterateAndNext(nodep->fmtp(), WidthVP{SELF, BOTH}.p());
@ -6055,159 +6072,48 @@ class WidthVisitor final : public VNVisitor {
void visit(AstSFormatF* nodep) override {
// Excludes NodeDisplay, see below
if (m_vup && !m_vup->prelim()) return; // Can be called as statement or function
// Just let all arguments seek their natural sizes
userIterateChildren(nodep, WidthVP{SELF, BOTH}.p());
if (nodep->didWidthAndSet()) return;
//
UINFO(9, " Display in " << nodep->text());
string newFormat;
bool inPct = false;
AstNodeExpr* argp = nodep->exprsp();
const string txt = nodep->text();
string fmt;
for (char ch : txt) {
if (!inPct && ch == '%') {
inPct = true;
fmt = ch;
} else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) {
fmt += ch;
} else if (inPct) {
inPct = false;
bool added = false;
const AstNodeDType* const dtypep = argp ? argp->dtypep()->skipRefp() : nullptr;
const AstBasicDType* const basicp = dtypep ? dtypep->basicp() : nullptr;
ch = std::tolower(ch);
if (ch == '?') { // Unspecified by user, guess
if (argp && argp->isDouble()) {
ch = 'g';
} else if (argp && argp->isString()) {
ch = '@';
} else if (argp && nodep->missingArgChar() == 'd' && argp->isSigned()) {
ch = '~';
} else if (basicp) {
ch = nodep->missingArgChar();
} else {
ch = 'p';
}
}
switch (ch) {
case '%': break; // %% - just output a %
case 'm': break; // %m - auto insert "name"
case 'l': break; // %m - auto insert "library"
case 'c': {
if (argp->widthMin() > 8) {
// Technically legal, but surely not what the user intended.
argp->v3warn(WIDTHTRUNC,
"$display-like format of %c format of > 8 bit value");
}
if (argp) argp = VN_AS(argp->nextp(), NodeExpr);
break;
}
case 'd': { // Convert decimal to either 'd' or '#'
if (argp) {
AstNodeExpr* const nextp = VN_AS(argp->nextp(), NodeExpr);
formatNoStringArg(argp, ch);
if (argp->isDouble()) {
spliceCvtS(argp, true, 64);
ch = '~';
} else if (argp->isSigned()) { // Convert it
ch = '~';
}
argp = nextp;
}
break;
}
case 'b': // FALLTHRU
case 'o': // FALLTHRU
case 'x': {
if (argp) {
AstNodeExpr* const nextp = VN_AS(argp->nextp(), NodeExpr);
formatNoStringArg(argp, ch);
if (argp->isDouble()) spliceCvtS(argp, true, 64);
argp = nextp;
}
break;
}
case 'p': { // Pattern
if (basicp && basicp->isString()) {
added = true;
newFormat += "\"%@\"";
} else if (basicp && basicp->isDouble()) {
added = true;
newFormat += "%g";
} else if (VN_IS(dtypep, AssocArrayDType) //
|| VN_IS(dtypep, WildcardArrayDType) //
|| VN_IS(dtypep, ClassRefDType) //
|| VN_IS(dtypep, DynArrayDType) //
|| VN_IS(dtypep, UnpackArrayDType) //
|| VN_IS(dtypep, QueueDType)
|| (VN_IS(dtypep, NodeUOrStructDType)
&& !VN_AS(dtypep, NodeUOrStructDType)->packed())) {
added = true;
newFormat += "%@";
VNRelinker handle;
argp->unlinkFrBack(&handle);
FileLine* const flp = nodep->fileline();
AstNodeExpr* const newp = new AstToStringN{flp, argp};
handle.relink(newp);
// Set argp to what we replaced it with, as we will keep processing the
// next argument.
argp = newp;
} else {
added = true;
if (fmt == "%0") {
newFormat += "'h%0h"; // IEEE our choice
} else {
newFormat += "%0d"; // UVM tests require %0d
}
}
if (argp) argp = VN_AS(argp->nextp(), NodeExpr);
break;
}
case 's': { // Convert string to pack string
if (argp && argp->dtypep()->basicp()->isString()) { // Convert it
ch = '@';
}
if (argp) argp = VN_AS(argp->nextp(), NodeExpr);
break;
}
case 't': { // Convert decimal time to realtime
if (argp) {
AstNodeExpr* const nextp = VN_AS(argp->nextp(), NodeExpr);
formatNoStringArg(argp, ch);
if (argp->isDouble()) ch = '^'; // Convert it
UASSERT_OBJ(!nodep->timeunit().isNone(), nodep,
"display %t has no time units");
argp = nextp;
}
break;
}
case 'e': // FALLTHRU
case 'f': // FALLTHRU
case 'g': {
if (argp) {
AstNodeExpr* const nextp = VN_AS(argp->nextp(), NodeExpr);
formatNoStringArg(argp, ch);
if (!argp->isDouble()) {
iterateCheckReal(nodep, "Display argument", argp, BOTH);
}
argp = nextp;
}
break;
}
default: { // Most operators, just move to next argument
if (argp) argp = VN_AS(argp->nextp(), NodeExpr);
break;
}
} // switch
if (!added) {
fmt += ch;
newFormat += fmt;
}
// Just let all arguments seek their natural sizes
userIterateChildren(nodep, WidthVP{SELF, BOTH}.p());
if (!nodep->exprFormat()) { // Non-constant format, can't check format string types
visit_sformatf_format(nodep);
}
AstNodeExpr* oldExprsp = nodep->exprsp();
if (nodep->exprFormat() && oldExprsp) {
VL_DO_DANGLING(iterateCheckString(nodep, "format", nodep->exprsp(), BOTH), oldExprsp);
oldExprsp = VN_AS(nodep->exprsp()->nextp(), NodeExpr);
}
if (oldExprsp) oldExprsp->unlinkFrBackWithNext();
while (AstNodeExpr* argp = oldExprsp) {
oldExprsp = VN_AS(oldExprsp->nextp(), NodeExpr);
if (oldExprsp) oldExprsp->unlinkFrBackWithNext();
// Need to record formatAttr's at elaboration time, as later optimizations
// may change an argument's data type. Plus need them for runtime formats
VFormatAttr formatAttr = VFormatAttr::UNSIGNED;
const AstNodeDType* const dtypep = argp ? argp->dtypep()->skipRefp() : nullptr;
if (dtypep->isDouble()) {
formatAttr = VFormatAttr::DOUBLE;
} else if (dtypep->isString()) {
formatAttr = VFormatAttr::STRING;
} else if (isFormatNonNumericArg(dtypep)) {
AstNodeExpr* const newp = new AstToStringN{argp->fileline(), argp};
formatAttr = VFormatAttr::COMPLEX;
argp = newp;
} else if (dtypep->isSigned()) {
formatAttr = VFormatAttr::SIGNED;
}
if (VN_IS(argp, SFormatArg) // Already done
|| formatAttr.isUnsigned()) { // Save Ast space and imply the AstSFormatArg
nodep->addExprsp(argp);
} else {
newFormat += ch;
nodep->addExprsp(new AstSFormatArg{argp->fileline(), formatAttr, argp});
}
}
nodep->text(newFormat);
UINFO(9, " Display out " << nodep->text());
}
void visit(AstCReset* nodep) override {
@ -8028,6 +7934,92 @@ class WidthVisitor final : public VNVisitor {
}
}
void visit_sformatf_format(AstSFormatF* nodep) {
// For sformatf's with constant format, iterate/check arguments
UASSERT_OBJ(!nodep->exprFormat(), nodep, "Assumes constant format");
bool inPct = false;
AstNodeExpr* argp = nodep->exprsp();
string newFormat;
for (char ch : nodep->text()) {
if (!inPct && ch == '%') {
inPct = true;
newFormat += ch;
} else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) {
newFormat += ch;
} else if (!inPct) { // Normal text
newFormat += ch;
} else {
inPct = false;
AstNodeExpr* const nextp = argp ? VN_AS(argp->nextp(), NodeExpr) : nullptr;
AstSFormatArg* const fargp = VN_CAST(argp, SFormatArg); // May not exist yet
AstNodeExpr* const subargp = fargp ? fargp->exprp() : argp;
const AstNodeDType* const dtypep
= subargp ? subargp->dtypep()->skipRefp() : nullptr;
ch = std::tolower(ch);
switch (ch) {
case '?': // Unspecified by user, guess
if (dtypep->isDouble()) {
ch = 'g';
} else if (dtypep->isString()) {
ch = 's';
} else if (VN_IS(dtypep, BasicDType)) {
ch = nodep->missingArgChar();
} else {
ch = 'p';
}
argp = nextp;
break;
case '%': break; // %% - just output a %
case 'm': break; // %m - auto insert "name"
case 'l': break; // %m - auto insert "library"
case 'd':
if (subargp) {
checkFormatNumericArg(subargp, ch);
if (subargp->isDouble()) spliceCvtS(subargp, true, 64);
argp = nextp;
}
break;
case 'b': // FALLTHRU
case 'o': // FALLTHRU
case 'x':
if (subargp) {
// V3Randomize may make a %x on a string, or may
// have a runtime format which gets past this error
checkFormatNumericArg(subargp, ch);
argp = nextp;
}
break;
case 't': // Decimal/float time
if (subargp) {
checkFormatNumericArg(subargp, ch);
UASSERT_OBJ(!nodep->timeunit().isNone(), nodep,
"display %t has no time units");
argp = nextp;
}
break;
case 'e': // FALLTHRU
case 'f': // FALLTHRU
case 'g':
if (subargp) {
checkFormatNumericArg(subargp, ch);
if (!subargp->isDouble()) {
iterateCheckReal(nodep, "Display argument", subargp, BOTH);
}
argp = nextp;
}
break;
case 'p': // FALLTHRU
case 's': // FALLTHRU
default: // Most operators, just move to next argument
argp = nextp;
break;
} // switch
newFormat += ch;
}
}
nodep->text(newFormat);
}
//----------------------------------------------------------------------
// LOWER LEVEL WIDTH METHODS (none iterate)

View File

@ -4232,11 +4232,11 @@ system_t_stmt_call<nodeStmtp>: // IEEE: part of system_tf_call (as task returni
| yD_STOP parenE { $$ = new AstStop{$1, false}; }
| yD_STOP '(' expr ')' { $$ = new AstStop{$1, false}; DEL($3); }
//
| yD_SFORMAT '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5}; }
| yD_SWRITE '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5}; }
| yD_SWRITEB '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5, 'b'}; }
| yD_SWRITEH '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5, 'h'}; }
| yD_SWRITEO '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5, 'o'}; }
| yD_SFORMAT '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, false, $3, $5}; }
| yD_SWRITE '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'd'}; }
| yD_SWRITEB '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'b'}; }
| yD_SWRITEH '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'h'}; }
| yD_SWRITEO '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'o'}; }
//
| yD_DISPLAY parenE { $$ = new AstDisplay{$1, VDisplayType::DT_DISPLAY, nullptr, nullptr}; }
| yD_DISPLAY '(' commasE exprDispList ')' { $$ = new AstDisplay{$1, VDisplayType::DT_DISPLAY, nullptr, $4}; }

View File

@ -51,7 +51,7 @@ module Vt_debug_emitv_t;
$write("");
end
if ($value$plusargs("TEST=%d", i1)) begin
$display("value was %~", i1);
$display("value was %d", i1);
end
else begin
$display("+TEST= not found");
@ -134,7 +134,7 @@ module Vt_debug_emitv_t;
while ((i < 'sh3)) begin
begin
other = f(i);
$display("stmt %~ %~",
$display("stmt %d %d",
i, other);
t();
end
@ -182,7 +182,7 @@ module Vt_debug_emitv_t;
fo = cyc;
sub.inc(fosum);
sum = sub.f(sum);
$display("[%0t] sum = %~", $time, sum);
$display("[%0t] sum = %d", $time, sum);
$display("a?= %d", ($c('sh1) ? $c('sh14)
: $c('sh1e)));
$c(;);
@ -196,7 +196,7 @@ module Vt_debug_emitv_t;
$fflush(fd);
$fscanf(fd, "%d", sum);
;
$fdisplay(32'h69203d20, "%~", sum);
$fdisplay(32'h69203d20, "%d", sum);
$fwrite(fd, "hello");
$readmemh(string'(fd), array);
$readmemh(string'(fd), array, 'sh0);
@ -274,13 +274,13 @@ module Vt_debug_emitv_t;
else begin
$display("0");
end
$display("%~%~", $past(cyc), $past(cyc,
$display("%d%d", $past(cyc), $past(cyc,
'sh1));
str = $sformatf("cyc=%~", cyc);
str = $sformatf("cyc=%d", cyc);
;
$display("str = %@", str);
$display("struct = %@", ps);
$display("%% [%t] [%^] to=%o td=%d", $time,
$display("str = %s", str);
$display("struct = %p", ps);
$display("%% [%t] [%t] to=%o td=%d", $time,
$realtime, $time, $time);
$sscanf(40'h666f6f3d35, "foo=%d", i);
;

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2026 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('simulator')
test.compile()
test.execute()
test.passes()

View File

@ -0,0 +1,60 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2026 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t;
class uvm_object;
endclass
class uvm_typeid_base;
endclass
class uvm_typeid #(
type T = uvm_object
) extends uvm_typeid_base;
static uvm_typeid #(T) m_b_inst;
static function uvm_typeid#(T) get();
if (m_b_inst == null) begin
m_b_inst = new;
end
return m_b_inst;
endfunction
endclass
class base_cmp extends uvm_object;
endclass
class base_oth extends uvm_object;
endclass
initial begin
string sc1, sc2, so;
// IEEE 1800-2023 21.2.1.1 suggests %d of a class is illegal.
// Howver UVM tests require %0d print unique identifier of the class.
// In contrast, %x of a class reference, causes errors on several simulators.
$display("BASE_CMP: %%p=%p", uvm_typeid#(base_cmp)::get());
$display("BASE_CMP: %%0d=%0d", uvm_typeid#(base_cmp)::get());
$display("BASE_OTH: %%p=%p", uvm_typeid#(base_oth)::get());
$display("BASE_OTH: %%0d=%0d", uvm_typeid#(base_oth)::get());
sc1 = $sformatf(": %0d", uvm_typeid#(base_cmp)::get());
sc2 = $sformatf(": %0D", uvm_typeid#(base_cmp)::get());
so = $sformatf(": %0d", uvm_typeid#(base_oth)::get());
if (sc1 != sc2) begin
$display("Expected class-to-%%d strings to be ==\n sc1=%s\n sc2=%s", sc1, sc2);
$stop;
end
// TODO make a unique identifier
// Complication is runtime of %p vs %d; need to pass both string and object?
// if (sc1 == so) begin
// $display("Expected class-to-%%d strings to be !=\n sc1=%s\n so=%s", sc1, so);
// $stop;
// end
$finish;
end
endmodule

View File

@ -1,7 +1,6 @@
%Warning-WIDTHTRUNC: t/t_display_cwide_bad.v:10:20: $display-like format of %c format of > 8 bit value
: ... note: In instance 't'
%Warning-WIDTHTRUNC: t/t_display_cwide_bad.v:10:5: $display-like format of %c format of > 8 bit value
10 | $display("%c", 32'h1234);
| ^~~~~~~~
| ^~~~~~~~
... For warning description see https://verilator.org/warn/WIDTHTRUNC?v=latest
... Use "/* verilator lint_off WIDTHTRUNC */" and lint_on around source to disable this message.
%Error: Exiting due to

View File

@ -0,0 +1,3 @@
got=42 rest b=%b c=%c d=%d e=%e f=%f g=%g h=%h o=%o p=%p s=%s t=%t u=%u v=%v z=%z
got=42 rest B=%b C=%c D=%d E=%e F=%f G=%g H=%h O=%o P=%p S=%s T=%t U=%u V=%v Z=%z
*-* All Finished *-*

View File

@ -0,0 +1,18 @@
#!/usr/bin/env python3
# DESCRIPTION: Verilator: Verilog Test driver/expect definition
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of either the GNU Lesser General Public License Version 3
# or the Perl Artistic License Version 2.0.
# SPDX-FileCopyrightText: 2024 Wilson Snyder
# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0
import vltest_bootstrap
test.scenarios('vlt')
test.compile()
test.execute(expect_filename=test.golden_filename)
test.passes()

View File

@ -0,0 +1,25 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// This file ONLY is placed under the Creative Commons Public Domain.
// SPDX-FileCopyrightText: 2003 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t (
input string empty_no_opt
);
initial begin
automatic string format;
automatic string s;
format = "got=%0d rest b=%b c=%c d=%d e=%e f=%f g=%g h=%h o=%o p=%p s=%s t=%t u=%u v=%v z=%z";
$sformat(s, {format, empty_no_opt}, 42);
$display("%s", s);
format = "got=%0D rest B=%B C=%C D=%D E=%E F=%F G=%G H=%H O=%O P=%P S=%S T=%T U=%U V=%V Z=%Z";
$sformat(s, {format, empty_no_opt}, 42);
$display("%s", s);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -1,7 +1,7 @@
[0] lp %x=0bbccc %x=0bbccc %o=2736314 %b=010111011110011001100 %0d=769228 %d= 769228 %p=769228 %0p='hbbccc
[0] ln %x=1bbccc %x=1bbccc %o=6736314 %b=110111011110011001100 %0d=-279348 %d= -279348 %p=1817804 %0p='h1bbccc
[0] ln %x=1bbccc %x=1bbccc %o=6736314 %b=110111011110011001100 %0d=-279348 %d= -279348 %p=-279348 %0p='h1bbccc
[0] qp %x=001bbbbcccc %x=001bbbbcccc %o=00067356746314 %b=00000000110111011101110111100110011001100 %0d=7444614348 %d= 7444614348 %p=7444614348 %0p='h1bbbbcccc
[0] qn %x=101bbbbcccc %x=101bbbbcccc %o=20067356746314 %b=10000000110111011101110111100110011001100 %0d=-1092067013428 %d=-1092067013428 %p=1106956242124 %0p='h101bbbbcccc
[0] qn %x=101bbbbcccc %x=101bbbbcccc %o=20067356746314 %b=10000000110111011101110111100110011001100 %0d=-1092067013428 %d=-1092067013428 %p=-1092067013428 %0p='h101bbbbcccc
[0] wp %x=000bc1234567812345678 %x=000bc1234567812345678 %o=000570110642547402215053170 %b=000000000101111000001001000110100010101100111100000010010001101000101011001111000 %p=3469299654322568844920 %0p='hbc1234567812345678
[0] wn %x=000bc1234577812345678 %x=000bc1234577812345678 %o=000570110642567402215053170 %b=000000000101111000001001000110100010101110111100000010010001101000101011001111000 %p=3469299655422080472696 %0p='hbc1234577812345678

View File

@ -1,14 +1,9 @@
%Error: t/t_display_type_bad.v:11:29: $display-like format of '%d' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
11 | $display("%d %x %f %t", s, s, s, s);
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_display_type_bad.v:11:32: $display-like format of '%x' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
11 | $display("%d %x %f %t", s, s, s, s);
| ^
... See the manual at https://verilator.org/verilator_doc.html?v=latest for more assistance.
%Error: t/t_display_type_bad.v:11:35: $display-like format of '%f' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
@ -19,4 +14,49 @@
: ... Suggest use '%s'
11 | $display("%d %x %f %t", s, s, s, s);
| ^
%Error: t/t_display_type_bad.v:12:32: $display-like format of '%x' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
12 | $display("%D %X %F %T", s, s, s, s);
| ^
%Error: t/t_display_type_bad.v:12:35: $display-like format of '%f' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
12 | $display("%D %X %F %T", s, s, s, s);
| ^
%Error: t/t_display_type_bad.v:12:38: $display-like format of '%t' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
12 | $display("%D %X %F %T", s, s, s, s);
| ^
%Error: t/t_display_type_bad.v:13:34: $display-like format of '%x' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
13 | $display("%d %x %f %t", vec, vec, vec, vec);
| ^~~
%Error: t/t_display_type_bad.v:13:39: $display-like format of '%f' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
13 | $display("%d %x %f %t", vec, vec, vec, vec);
| ^~~
%Error: t/t_display_type_bad.v:13:44: $display-like format of '%t' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
13 | $display("%d %x %f %t", vec, vec, vec, vec);
| ^~~
%Error: t/t_display_type_bad.v:14:34: $display-like format of '%x' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
14 | $display("%D %X %F %T", vec, vec, vec, vec);
| ^~~
%Error: t/t_display_type_bad.v:14:39: $display-like format of '%f' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
14 | $display("%D %X %F %T", vec, vec, vec, vec);
| ^~~
%Error: t/t_display_type_bad.v:14:44: $display-like format of '%t' illegal with non-numeric argument
: ... note: In instance 't'
: ... Suggest use '%s'
14 | $display("%D %X %F %T", vec, vec, vec, vec);
| ^~~
%Error: Exiting due to

View File

@ -9,7 +9,9 @@ module t;
int vec[2];
initial begin
$display("%d %x %f %t", s, s, s, s);
$display("%D %X %F %T", s, s, s, s);
$display("%d %x %f %t", vec, vec, vec, vec);
$display("%D %X %F %T", vec, vec, vec, vec);
$write("*-* All Finished *-*\n");
$finish;
end

View File

@ -25,23 +25,26 @@
{"type":"VAR","name":"m","addr":"(AB)","loc":"d,35:32,35:33","dtypep":"(BB)","origName":"m","verilogName":"m","direction":"INPUT","isFuncLocal":true,"lifetime":"VAUTOMI","varType":"PORT","dtypeName":"string","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []},
{"type":"DISPLAY","name":"","addr":"(CB)","loc":"d,36:5,36:13",
"fmtp": [
{"type":"SFORMATF","name":"%@","addr":"(DB)","loc":"d,36:5,36:13","dtypep":"(BB)",
{"type":"SFORMATF","name":"%s","addr":"(DB)","loc":"d,36:5,36:13","dtypep":"(BB)",
"exprsp": [
{"type":"VARREF","name":"m","addr":"(EB)","loc":"d,36:20,36:21","dtypep":"(BB)","access":"RD","varp":"(AB)","varScopep":"UNLINKED","classOrPackagep":"UNLINKED"}
{"type":"SFORMATARG","name":"","addr":"(EB)","loc":"d,36:20,36:21","dtypep":"(BB)","formatAttr":"S",
"exprp": [
{"type":"VARREF","name":"m","addr":"(FB)","loc":"d,36:20,36:21","dtypep":"(BB)","access":"RD","varp":"(AB)","varScopep":"UNLINKED","classOrPackagep":"UNLINKED"}
]}
],"scopeNamep": []}
],"filep": []}
],"scopeNamep": []},
{"type":"INITIAL","name":"","addr":"(FB)","loc":"d,39:3,39:10",
{"type":"INITIAL","name":"","addr":"(GB)","loc":"d,39:3,39:10",
"stmtsp": [
{"type":"BEGIN","name":"","addr":"(GB)","loc":"d,39:11,39:16","unnamed":true,"declsp": [],
{"type":"BEGIN","name":"","addr":"(HB)","loc":"d,39:11,39:16","unnamed":true,"declsp": [],
"stmtsp": [
{"type":"STMTEXPR","name":"","addr":"(HB)","loc":"d,41:5,41:6",
{"type":"STMTEXPR","name":"","addr":"(IB)","loc":"d,41:5,41:6",
"exprp": [
{"type":"TASKREF","name":"f","addr":"(IB)","loc":"d,41:5,41:6","dtypep":"(JB)","dotted":"","taskp":"(Z)","classOrPackagep":"UNLINKED",
{"type":"TASKREF","name":"f","addr":"(JB)","loc":"d,41:5,41:6","dtypep":"(KB)","dotted":"","taskp":"(Z)","classOrPackagep":"UNLINKED",
"argsp": [
{"type":"ARG","name":"","addr":"(KB)","loc":"d,42:9,42:736",
{"type":"ARG","name":"","addr":"(LB)","loc":"d,42:9,42:736",
"exprp": [
{"type":"CONST","name":"\\\"\\001\\002\\003\\004\\005\\006\\007\\010\\t\\n\\013\\014\\r\\016\\017\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037 !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\177\\200\\201\\202\\203\\204\\205\\206\\207\\210\\211\\212\\213\\214\\215\\216\\217\\220\\221\\222\\223\\224\\225\\226\\227\\230\\231\\232\\233\\234\\235\\236\\237\\240\\241\\242\\243\\244\\245\\246\\247\\250\\251\\252\\253\\254\\255\\256\\257\\260\\261\\262\\263\\264\\265\\266\\267\\270\\271\\272\\273\\274\\275\\276\\277\\300\\301\\302\\303\\304\\305\\306\\307\\310\\311\\312\\313\\314\\315\\316\\317\\320\\321\\322\\323\\324\\325\\326\\327\\330\\331\\332\\333\\334\\335\\336\\337\\340\\341\\342\\343\\344\\345\\346\\347\\350\\351\\352\\353\\354\\355\\356\\357\\360\\361\\362\\363\\364\\365\\366\\367\\370\\371\\372\\373\\374\\375\\376\\377\\\"","addr":"(LB)","loc":"d,42:9,42:736","dtypep":"(BB)"}
{"type":"CONST","name":"\\\"\\001\\002\\003\\004\\005\\006\\007\\010\\t\\n\\013\\014\\r\\016\\017\\020\\021\\022\\023\\024\\025\\026\\027\\030\\031\\032\\033\\034\\035\\036\\037 !\\\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\177\\200\\201\\202\\203\\204\\205\\206\\207\\210\\211\\212\\213\\214\\215\\216\\217\\220\\221\\222\\223\\224\\225\\226\\227\\230\\231\\232\\233\\234\\235\\236\\237\\240\\241\\242\\243\\244\\245\\246\\247\\250\\251\\252\\253\\254\\255\\256\\257\\260\\261\\262\\263\\264\\265\\266\\267\\270\\271\\272\\273\\274\\275\\276\\277\\300\\301\\302\\303\\304\\305\\306\\307\\310\\311\\312\\313\\314\\315\\316\\317\\320\\321\\322\\323\\324\\325\\326\\327\\330\\331\\332\\333\\334\\335\\336\\337\\340\\341\\342\\343\\344\\345\\346\\347\\350\\351\\352\\353\\354\\355\\356\\357\\360\\361\\362\\363\\364\\365\\366\\367\\370\\371\\372\\373\\374\\375\\376\\377\\\"","addr":"(MB)","loc":"d,42:9,42:736","dtypep":"(BB)"}
]}
],"withp": [],"scopeNamep": []}
]}
@ -51,49 +54,49 @@
{"type":"IFACE","name":"ifc","addr":"(M)","loc":"d,7:11,7:14","origName":"ifc","verilogName":"ifc","level":2,"inLibrary":true,"timeunit":"1ps","inlinesp": [],
"stmtsp": [
{"type":"VAR","name":"value","addr":"(X)","loc":"d,8:11,8:16","dtypep":"(W)","origName":"value","verilogName":"value","direction":"NONE","lifetime":"VSTATICI","varType":"VAR","dtypeName":"integer","sensIfacep":"UNLINKED","childDTypep": [],"delayp": [],"valuep": [],"attrsp": []},
{"type":"MODPORT","name":"out_modport","addr":"(MB)","loc":"d,9:11,9:22",
{"type":"MODPORT","name":"out_modport","addr":"(NB)","loc":"d,9:11,9:22",
"varsp": [
{"type":"MODPORTVARREF","name":"value","addr":"(NB)","loc":"d,9:30,9:35","direction":"OUTPUT","varp":"(X)","exprp": []}
{"type":"MODPORTVARREF","name":"value","addr":"(OB)","loc":"d,9:30,9:35","direction":"OUTPUT","varp":"(X)","exprp": []}
]}
]}
],"filesp": [],
"miscsp": [
{"type":"TYPETABLE","name":"","addr":"(C)","loc":"a,0:0,0:0","constraintRefp":"UNLINKED","emptyQueuep":"UNLINKED","queueIndexp":"UNLINKED","streamp":"UNLINKED","voidp":"(JB)",
{"type":"TYPETABLE","name":"","addr":"(C)","loc":"a,0:0,0:0","constraintRefp":"UNLINKED","emptyQueuep":"UNLINKED","queueIndexp":"UNLINKED","streamp":"UNLINKED","voidp":"(KB)",
"typesp": [
{"type":"VOIDDTYPE","name":"","addr":"(JB)","loc":"d,41:5,41:6","dtypep":"(JB)"},
{"type":"VOIDDTYPE","name":"","addr":"(KB)","loc":"d,41:5,41:6","dtypep":"(KB)"},
{"type":"BASICDTYPE","name":"integer","addr":"(W)","loc":"d,8:3,8:10","dtypep":"(W)","keyword":"integer","range":"31:0","generic":true,"signed":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(G)","loc":"d,13:11,13:17","dtypep":"(G)","keyword":"logic","generic":true,"rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(OB)","loc":"d,21:5,21:10","dtypep":"(OB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(PB)","loc":"d,22:5,22:10","dtypep":"(PB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(QB)","loc":"d,23:5,23:10","dtypep":"(QB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(RB)","loc":"d,24:5,24:10","dtypep":"(RB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(PB)","loc":"d,21:5,21:10","dtypep":"(PB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(QB)","loc":"d,22:5,22:10","dtypep":"(QB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(RB)","loc":"d,23:5,23:10","dtypep":"(RB)","keyword":"logic","rangep": []},
{"type":"BASICDTYPE","name":"logic","addr":"(SB)","loc":"d,24:5,24:10","dtypep":"(SB)","keyword":"logic","rangep": []},
{"type":"STRUCTDTYPE","name":"m.my_struct","addr":"(K)","loc":"d,20:11,20:17","dtypep":"(K)","packed":true,"isFourstate":true,"classOrPackagep":"UNLINKED",
"membersp": [
{"type":"MEMBERDTYPE","name":"clk","addr":"(SB)","loc":"d,21:11,21:14","dtypep":"(OB)","name":"clk","tag":"this is clk","refDTypep":"(OB)","childDTypep": [],"valuep": []},
{"type":"MEMBERDTYPE","name":"k","addr":"(TB)","loc":"d,22:11,22:12","dtypep":"(PB)","name":"k","tag":"","refDTypep":"(PB)","childDTypep": [],"valuep": []},
{"type":"MEMBERDTYPE","name":"enable","addr":"(UB)","loc":"d,23:11,23:17","dtypep":"(QB)","name":"enable","tag":"enable","refDTypep":"(QB)","childDTypep": [],"valuep": []},
{"type":"MEMBERDTYPE","name":"data","addr":"(VB)","loc":"d,24:11,24:15","dtypep":"(RB)","name":"data","tag":"data","refDTypep":"(RB)","childDTypep": [],"valuep": []}
{"type":"MEMBERDTYPE","name":"clk","addr":"(TB)","loc":"d,21:11,21:14","dtypep":"(PB)","name":"clk","tag":"this is clk","refDTypep":"(PB)","childDTypep": [],"valuep": []},
{"type":"MEMBERDTYPE","name":"k","addr":"(UB)","loc":"d,22:11,22:12","dtypep":"(QB)","name":"k","tag":"","refDTypep":"(QB)","childDTypep": [],"valuep": []},
{"type":"MEMBERDTYPE","name":"enable","addr":"(VB)","loc":"d,23:11,23:17","dtypep":"(RB)","name":"enable","tag":"enable","refDTypep":"(RB)","childDTypep": [],"valuep": []},
{"type":"MEMBERDTYPE","name":"data","addr":"(WB)","loc":"d,24:11,24:15","dtypep":"(SB)","name":"data","tag":"data","refDTypep":"(SB)","childDTypep": [],"valuep": []}
]},
{"type":"IFACEREFDTYPE","name":"","addr":"(O)","loc":"d,29:7,29:11","dtypep":"(O)","cellName":"itop","ifaceName":"ifc","modportName":"","ifacep":"UNLINKED","cellp":"(L)","modportp":"UNLINKED","paramsp": []},
{"type":"BASICDTYPE","name":"logic","addr":"(S)","loc":"d,31:25,31:26","dtypep":"(S)","keyword":"logic","range":"31:0","generic":true,"rangep": []},
{"type":"REFDTYPE","name":"my_struct","addr":"(WB)","loc":"d,31:3,31:12","dtypep":"(K)","typedefp":"UNLINKED","refDTypep":"(K)","classOrPackagep":"UNLINKED","typeofp": [],"classOrPackageOpp": [],"paramsp": []},
{"type":"UNPACKARRAYDTYPE","name":"","addr":"(Q)","loc":"d,31:24,31:25","dtypep":"(Q)","declRange":"[0:1]","refDTypep":"(WB)","childDTypep": [],
{"type":"REFDTYPE","name":"my_struct","addr":"(XB)","loc":"d,31:3,31:12","dtypep":"(K)","typedefp":"UNLINKED","refDTypep":"(K)","classOrPackagep":"UNLINKED","typeofp": [],"classOrPackageOpp": [],"paramsp": []},
{"type":"UNPACKARRAYDTYPE","name":"","addr":"(Q)","loc":"d,31:24,31:25","dtypep":"(Q)","declRange":"[0:1]","refDTypep":"(XB)","childDTypep": [],
"rangep": [
{"type":"RANGE","name":"","addr":"(XB)","loc":"d,31:24,31:25","ascending":true,"fromBracket":true,
{"type":"RANGE","name":"","addr":"(YB)","loc":"d,31:24,31:25","ascending":true,"fromBracket":true,
"leftp": [
{"type":"CONST","name":"32'h0","addr":"(YB)","loc":"d,31:25,31:26","dtypep":"(S)"}
{"type":"CONST","name":"32'h0","addr":"(ZB)","loc":"d,31:25,31:26","dtypep":"(S)"}
],
"rightp": [
{"type":"CONST","name":"32'h1","addr":"(ZB)","loc":"d,31:25,31:26","dtypep":"(S)"}
{"type":"CONST","name":"32'h1","addr":"(AC)","loc":"d,31:25,31:26","dtypep":"(S)"}
]}
]},
{"type":"BASICDTYPE","name":"string","addr":"(BB)","loc":"d,35:25,35:31","dtypep":"(BB)","keyword":"string","generic":true,"rangep": []}
]},
{"type":"CONSTPOOL","name":"","addr":"(D)","loc":"a,0:0,0:0",
"modulep": [
{"type":"MODULE","name":"@CONST-POOL@","addr":"(AC)","loc":"a,0:0,0:0","origName":"@CONST-POOL@","verilogName":"@CONST-POOL@","level":0,"timeunit":"NONE","inlinesp": [],
{"type":"MODULE","name":"@CONST-POOL@","addr":"(BC)","loc":"a,0:0,0:0","origName":"@CONST-POOL@","verilogName":"@CONST-POOL@","level":0,"timeunit":"NONE","inlinesp": [],
"stmtsp": [
{"type":"SCOPE","name":"@CONST-POOL@","addr":"(BC)","loc":"a,0:0,0:0","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(AC)","varsp": [],"blocksp": [],"inlinesp": []}
{"type":"SCOPE","name":"@CONST-POOL@","addr":"(CC)","loc":"a,0:0,0:0","aboveScopep":"UNLINKED","aboveCellp":"UNLINKED","modp":"(BC)","varsp": [],"blocksp": [],"inlinesp": []}
]}
]}
]}

View File

@ -4,7 +4,19 @@
// SPDX-FileCopyrightText: 2008 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
module t;
// verilog_format: off
`define stop $stop
`define checks(gotv,expv) do if ((gotv) != (expv)) begin $write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__,`__LINE__, (gotv), (expv)); `stop; end while(0);
`ifdef verilator
`define no_optimize(v) $c(v)
`else
`define no_optimize(v) (v)
`endif
// verilog_format: on
module t (
input string empty_no_opt
);
// Note $sscanf already tested elsewhere
@ -21,28 +33,29 @@ module t;
real r;
function string cvt2string(input int width, input int v);
string fmt;
$sformat(fmt, "%0d'h%%%0dh", width, (width - 1) / 4 + 1);
$sformat(cvt2string, {"FMT=", fmt, empty_no_opt}, v);
$display("Format '%s' -> '%s'", fmt, cvt2string);
endfunction
int mem[2] = '{1, 2};
string s;
initial begin
n = 4'b1100;
q = 64'h1234_5678_abcd_0123;
wide = "hello-there12345";
$sformat(str, "n=%b q=%d w=%s", n, q, wide);
`ifdef TEST_VERBOSE
$display("str=%0s", str);
`endif
if (str !== "n=1100 q= 1311768467750060323 w=hello-there12345") $stop;
`checks(str, "n=1100 q= 1311768467750060323 w=hello-there12345");
q = {q[62:0], 1'b1};
$swrite(str2, "n=%b q=%d w=%s", n, q, wide);
`ifdef TEST_VERBOSE
$display("str2=%0s", str2);
`endif
if (str2 !== "n=1100 q= 2623536935500120647 w=hello-there12345") $stop;
`checks(str2, "n=1100 q= 2623536935500120647 w=hello-there12345");
str3 = $sformatf("n=%b q=%d w=%s", n, q, wide);
`ifdef TEST_VERBOSE
$display("str3=%0s", str3);
`endif
if (str3 !== "n=1100 q= 2623536935500120647 w=hello-there12345") $stop;
`checks(str3, "n=1100 q= 2623536935500120647 w=hello-there12345");
$swrite(str2, "e=%e", r);
$swrite(str2, "e=%f", r);
@ -50,35 +63,20 @@ module t;
str3 = "hello";
$swrite(str2, {str3, str3});
`ifdef TEST_VERBOSE
$display("str2=%0s", str2);
`endif
if (str2 !== "hellohello") $stop;
`checks(str2, "hellohello");
r = 0.01;
$swrite(str2, "e=%e f=%f g=%g", r, r, r);
`ifdef TEST_VERBOSE
$display("str2=%0s", str2);
`endif
if (str2 !== "e=1.000000e-02 f=0.010000 g=0.01") $stop;
`checks(str2, "e=1.000000e-02 f=0.010000 g=0.01");
$swrite(str2, "mod=%m");
`ifdef TEST_VERBOSE
$display("str2=%0s", str2);
`endif
if (str2 !== "mod=top.t") $stop;
`checks(str2, "mod=top.t");
$swrite(str2, "lib=%l");
`ifdef TEST_VERBOSE
$display("chkl %0s", str2);
`endif
if (str2 !== "lib=work.t") $stop;
`checks(str2, "lib=work.t");
str3 = $sformatf("u=%u", {"a", "b", "c", "d"}); // Value selected so is printable
`ifdef TEST_VERBOSE
$display("chku %s", str3);
`endif
if (str3 !== "u=dcba") $stop;
`checks(str3, "u=dcba");
str3 = $sformatf("v=%v", 4'b01xz); // Value selected so is printable
`ifdef TEST_VERBOSE
@ -91,29 +89,48 @@ module t;
`endif
$sformat(ochar, "%s", "c");
if (ochar != "c") $stop;
`checks(ochar, "c");
$swrite(str2, 4'd12);
if (str2 != "12") $stop;
`checks(str2, "12");
$swriteb(str2, 4'd12);
if (str2 != "1100") $stop;
`checks(str2, "1100");
$swriteh(str2, 4'd12);
if (str2 != "c") $stop;
`checks(str2, "c");
$swriteo(str2, 4'd12);
if (str2 != "14") $stop;
`checks(str2, "14");
str3 = "foo";
$sformat(str3, "%s", str3); // $sformat twice so verilator does not
$sformat(str3, "%s", str3); // optimize the call to $sformat(str3, "%s", "foo")
`ifdef TEST_VERBOSE
$display("str3=%0s", str3);
`endif
if (str3 != "foo") $stop;
`checks(str3, "foo");
$sformat(instruction_str[0], "%s", "Hello");
$sformat(instruction_str[1], "%s", "World");
if (instruction_str[0] != "Hello") $stop;
if (instruction_str[1] != "World") $stop;
`checks(instruction_str[0], "Hello");
`checks(instruction_str[1], "World");
s = cvt2string(`no_optimize(2), `no_optimize(2 * 16));
`checks(s, "FMT=2'h20");
s = cvt2string(`no_optimize(14), `no_optimize(14 * 16));
`checks(s, "FMT=14'h00e0");
s = "hello";
s = $sformatf({"%x", "-const"}, s);
`checks(s, "68656c6c6f-const");
s = "hello";
s = $sformatf({"%x", empty_no_opt}, s);
`checks(s, "68656c6c6f"); // Simulators vary in output; hex required by V3Randomize
// Also is more consistent with when have $sformat("%x", "string-as-verilog-number")
s = $sformatf("%s", "constified");
s = $sformatf("%s", s);
s = $sformatf("%s", s);
`checks(s, "constified");
$display(mem); // Implied %p
$write("*-* All Finished *-*\n");
$finish;