diff --git a/include/verilated.cpp b/include/verilated.cpp index 3c966a811..61f0c97e1 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -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(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 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 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(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(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(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(thingp); + output += '"' + *strp + '"'; + } else if (formatAttr == VL_VFORMATATTR_COMPLEX) { + const std::string* const strp = static_cast(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(append.length()); } - const int needmore = static_cast(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(append.length()); } - const int needmore = static_cast(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(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( - '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(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(thingp); + int chars = std::min(static_cast(strp->size()), + static_cast(VL_VALUE_STRING_MAX_WIDTH / 2)); + int truncFront = widthSet ? (chars - (static_cast(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(VL_BITRSHIFT_W(lwp, bit) & 0xff); - output.append(4 - wr_bytes, static_cast(0)); - if (is_4_state) output.append(4, static_cast(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( + '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(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(VL_BITRSHIFT_W(lwp, bit) & 0xff); + output.append(4 - wr_bytes, static_cast(0)); + if (is_4_state) output.append(4, static_cast(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(posend) - 1; i < obits && pos >= static_cast(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 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(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(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(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(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(thingp); + *p = real; + } else if (formatAttr == VL_VFORMATATTR_STRING) { + std::string* const p = static_cast(thingp); *p = t_tmp; } else if (obits <= VL_BYTESIZE) { - CData* const p = va_arg(ap, CData*); + CData* const p = static_cast(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(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(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(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(ld.length() * 8), nullptr, ld, format, ap); + = _vl_vsscanf(nullptr, static_cast(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(str.length()) - 1; + int pos = static_cast(str.length()) - 1 - truncFront; int bit = 0; while (bit < obits && pos >= 0) { owp[VL_BITWORD_I(bit)] |= static_cast(datap[pos]) << VL_BITBIT_I(bit); diff --git a/include/verilated_funcs.h b/include/verilated_funcs.h index d5fde472b..f6404392c 100644 --- a/include/verilated_funcs.h +++ b/include/verilated_funcs.h @@ -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; diff --git a/include/verilatedos.h b/include/verilatedos.h index b93eaae56..3e2e93154 100644 --- a/include/verilatedos.h +++ b/include/verilatedos.h @@ -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 diff --git a/src/V3Assert.cpp b/src/V3Assert.cpp index c844ebaaf..4922bd0e3 100644 --- a/src/V3Assert.cpp +++ b/src/V3Assert.cpp @@ -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;" diff --git a/src/V3AstNodeExpr.h b/src/V3AstNodeExpr.h index 6ca80cc14..fba213671 100644 --- a/src/V3AstNodeExpr.h +++ b/src/V3AstNodeExpr.h @@ -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; } diff --git a/src/V3AstNodeStmt.h b/src/V3AstNodeStmt.h index f1198b94b..7c3a013e8 100644 --- a/src/V3AstNodeStmt.h +++ b/src/V3AstNodeStmt.h @@ -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; diff --git a/src/V3AstNodes.cpp b/src/V3AstNodes.cpp index 23706a51e..c5cce1b69 100644 --- a/src/V3AstNodes.cpp +++ b/src/V3AstNodes.cpp @@ -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); diff --git a/src/V3Const.cpp b/src/V3Const.cpp index ec10df6ae..7ebf4c155 100644 --- a/src/V3Const.cpp +++ b/src/V3Const.cpp @@ -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 { diff --git a/src/V3Depth.cpp b/src/V3Depth.cpp index c0c96c594..b0912d3f1 100644 --- a/src/V3Depth.cpp +++ b/src/V3Depth.cpp @@ -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 { diff --git a/src/V3EmitCFunc.cpp b/src/V3EmitCFunc.cpp index cd94a1534..495dbcbaf 100644 --- a/src/V3EmitCFunc.cpp +++ b/src/V3EmitCFunc.cpp @@ -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, diff --git a/src/V3EmitCFunc.h b/src/V3EmitCFunc.h index 383e58376..7c27f1f3b 100644 --- a/src/V3EmitCFunc.h +++ b/src/V3EmitCFunc.h @@ -121,28 +121,6 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit { std::unordered_map 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 m_argsChar; // Format of each argument to be printed - std::vector m_argsp; // Each argument to be printed - std::vector 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); diff --git a/src/V3EmitV.cpp b/src/V3EmitV.cpp index b2d1f3e7a..f720cf776 100644 --- a/src/V3EmitV.cpp +++ b/src/V3EmitV.cpp @@ -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()); } diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index ed88f792a..3690fdb39 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -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 { diff --git a/src/V3Number.cpp b/src/V3Number.cpp index d98d055f9..6a5c415be 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -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(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(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((v == 0) ? ' ' : v); + start = false; // Drop leading 0s + } else { + if (fmtsize != "0") str += ' '; + } + } + } + const size_t fmtsizen = static_cast(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(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((v == 0) ? ' ' : v); - start = false; // Drop leading 0s - } else { - if (fmtsize != "0") str += ' '; - } - } - const size_t fmtsizen = static_cast(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(std::atoi(fmtsize.c_str())); - str = displayPad(fmtsizen, ' ', left, toString()); - return str; - } default: fl->v3fatalSrc("Unknown $display-like format code for number: %" << pos[0]); } } diff --git a/src/V3Number.h b/src/V3Number.h index 8c84252d1..a088219ef 100644 --- a/src/V3Number.h +++ b/src/V3Number.h @@ -30,12 +30,53 @@ #include #include -//============================================================================ - 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(_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)...); } + 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) diff --git a/src/V3Premit.cpp b/src/V3Premit.cpp index b64995044..e58531403 100644 --- a/src/V3Premit.cpp +++ b/src/V3Premit.cpp @@ -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); } diff --git a/src/V3Randomize.cpp b/src/V3Randomize.cpp index 04fe08943..1fa1f4919 100644 --- a/src/V3Randomize.cpp +++ b/src/V3Randomize.cpp @@ -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& m_writtenVars; // Track which variable paths have write_var generated // (shared across all constraints) std::set* 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; diff --git a/src/V3Simulate.h b/src/V3Simulate.h index d945ba8da..7946e661c 100644 --- a/src/V3Simulate.h +++ b/src/V3Simulate.h @@ -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}; diff --git a/src/V3Width.cpp b/src/V3Width.cpp index dfd481ceb..ef032d984 100644 --- a/src/V3Width.cpp +++ b/src/V3Width.cpp @@ -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) diff --git a/src/verilog.y b/src/verilog.y index 82db03c53..421e301a9 100644 --- a/src/verilog.y +++ b/src/verilog.y @@ -4232,11 +4232,11 @@ system_t_stmt_call: // 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}; } diff --git a/test_regress/t/t_debug_emitv.out b/test_regress/t/t_debug_emitv.out index aecca1cf7..b7bea1967 100644 --- a/test_regress/t/t_debug_emitv.out +++ b/test_regress/t/t_debug_emitv.out @@ -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); ; diff --git a/test_regress/t/t_display_class.py b/test_regress/t/t_display_class.py new file mode 100755 index 000000000..8a938befd --- /dev/null +++ b/test_regress/t/t_display_class.py @@ -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() diff --git a/test_regress/t/t_display_class.v b/test_regress/t/t_display_class.v new file mode 100644 index 000000000..535aa7c1f --- /dev/null +++ b/test_regress/t/t_display_class.v @@ -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 diff --git a/test_regress/t/t_display_cwide_bad.out b/test_regress/t/t_display_cwide_bad.out index 18a1bfb05..900f43601 100644 --- a/test_regress/t/t_display_cwide_bad.out +++ b/test_regress/t/t_display_cwide_bad.out @@ -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 diff --git a/test_regress/t/t_display_exprformat_args_bad.out b/test_regress/t/t_display_exprformat_args_bad.out new file mode 100644 index 000000000..9e8859f93 --- /dev/null +++ b/test_regress/t/t_display_exprformat_args_bad.out @@ -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 *-* diff --git a/test_regress/t/t_display_exprformat_args_bad.py b/test_regress/t/t_display_exprformat_args_bad.py new file mode 100755 index 000000000..c03eaf086 --- /dev/null +++ b/test_regress/t/t_display_exprformat_args_bad.py @@ -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() diff --git a/test_regress/t/t_display_exprformat_args_bad.v b/test_regress/t/t_display_exprformat_args_bad.v new file mode 100644 index 000000000..5297c61b0 --- /dev/null +++ b/test_regress/t/t_display_exprformat_args_bad.v @@ -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 diff --git a/test_regress/t/t_display_signed.out b/test_regress/t/t_display_signed.out index ea3be3e34..d79e193b6 100644 --- a/test_regress/t/t_display_signed.out +++ b/test_regress/t/t_display_signed.out @@ -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 diff --git a/test_regress/t/t_display_type_bad.out b/test_regress/t/t_display_type_bad.out index 50603e15e..7ec3b49a3 100644 --- a/test_regress/t/t_display_type_bad.out +++ b/test_regress/t/t_display_type_bad.out @@ -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 diff --git a/test_regress/t/t_display_type_bad.v b/test_regress/t/t_display_type_bad.v index b0422faf6..c419f4370 100644 --- a/test_regress/t/t_display_type_bad.v +++ b/test_regress/t/t_display_type_bad.v @@ -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 diff --git a/test_regress/t/t_json_only_tag.out b/test_regress/t/t_json_only_tag.out index 7b2964a10..29efb7f28 100644 --- a/test_regress/t/t_json_only_tag.out +++ b/test_regress/t/t_json_only_tag.out @@ -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": []} ]} ]} ]} diff --git a/test_regress/t/t_sys_sformat.v b/test_regress/t/t_sys_sformat.v index cc2cadb17..c283ac9b6 100644 --- a/test_regress/t/t_sys_sformat.v +++ b/test_regress/t/t_sys_sformat.v @@ -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;