Fixes #7212.
This commit is contained in:
parent
42cf5d3be2
commit
602ee384de
|
|
@ -926,20 +926,52 @@ std::string _vl_vsformat_time(char* tmp, T ld, int timeunit, bool left, size_t w
|
|||
// Do a va_arg returning a quad, assuming input argument is anything less than wide
|
||||
#define VL_VA_ARG_Q_(ap, bits) (((bits) <= VL_IDATASIZE) ? va_arg(ap, IData) : va_arg(ap, QData))
|
||||
|
||||
void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL_MT_SAFE {
|
||||
void _vl_vsformat(std::string& output, const std::string& format, int argc,
|
||||
va_list ap) VL_MT_SAFE {
|
||||
// Format a Verilog $write style format into the output list
|
||||
// The format must be pre-processed (and lower cased) by Verilator
|
||||
// Arguments are in "width, arg-value (or WDataIn* if wide)" form
|
||||
// The format must be pre-processed (and lower cased) by Verilator.
|
||||
// Arguments are each {"VFormatAttr character, int width, arg-value (or WDataIn* if wide)"}
|
||||
//
|
||||
// Note uses a single buffer internally; presumes only one usage per printf
|
||||
// Note also assumes variables < 64 are not wide, this assumption is
|
||||
// Uses a single buffer internally; presumes only one usage per printf.
|
||||
// Also assumes variables < 64 are not wide, this assumption is
|
||||
// sometimes not true in low-level routines written here in verilated.cpp
|
||||
|
||||
// Look ahead at args to capture any %m/%t baseline information
|
||||
char formatAttr = '\0'; // Fetched format for _next_ argument
|
||||
bool formatAttrValid = false;
|
||||
const char* modulep = nullptr;
|
||||
const char* scopep = nullptr;
|
||||
int timeunit = 0;
|
||||
int argn = 0;
|
||||
while (argn < argc) {
|
||||
formatAttr = va_arg(ap, int); // Char promoted to int
|
||||
switch (formatAttr) {
|
||||
case VL_VFORMATATTR_TIMEUNIT:
|
||||
++argn;
|
||||
timeunit = va_arg(ap, int);
|
||||
continue;
|
||||
case VL_VFORMATATTR_SCOPE:
|
||||
// No width
|
||||
++argn;
|
||||
modulep = va_arg(ap, const char*);
|
||||
scopep = va_arg(ap, const char*);
|
||||
continue;
|
||||
default: // Normal arg; will consume formatAttr later
|
||||
formatAttrValid = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse format
|
||||
static thread_local char t_tmp[VL_VALUE_STRING_MAX_WIDTH];
|
||||
std::string::const_iterator pctit = format.end(); // Most recent %##.##g format
|
||||
bool inPct = false;
|
||||
bool widthSet = false;
|
||||
bool left = false;
|
||||
size_t width = 0;
|
||||
output = "";
|
||||
output.reserve(format.length());
|
||||
for (std::string::const_iterator pos = format.cbegin(); pos != format.cend(); ++pos) {
|
||||
if (!inPct && pos[0] == '%') {
|
||||
pctit = pos;
|
||||
|
|
@ -956,7 +988,7 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
|
|||
}
|
||||
} else { // Format character
|
||||
inPct = false;
|
||||
const char fmt = pos[0];
|
||||
char fmt = std::tolower(pos[0]);
|
||||
switch (fmt) {
|
||||
case '0': // FALLTHRU
|
||||
case '1': // FALLTHRU
|
||||
|
|
@ -971,96 +1003,135 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
|
|||
inPct = true; // Get more digits
|
||||
widthSet = true;
|
||||
width = width * 10 + (fmt - '0');
|
||||
break;
|
||||
continue;
|
||||
case '-':
|
||||
left = true;
|
||||
inPct = true; // Get more digits
|
||||
break;
|
||||
continue;
|
||||
case '.':
|
||||
inPct = true; // Get more digits
|
||||
break;
|
||||
continue;
|
||||
case '%': //
|
||||
output += '%';
|
||||
continue;
|
||||
case 'l':
|
||||
output += "----"; // Library - compile-time only
|
||||
continue;
|
||||
case 'm':
|
||||
if (modulep) output += modulep;
|
||||
if (modulep && modulep[0] && scopep && scopep[0]) output += '.';
|
||||
if (scopep) output += scopep;
|
||||
continue;
|
||||
//--------
|
||||
// Standard format handling -- all take arguments
|
||||
case 'b': // FALLTHRU
|
||||
case 'c': // FALLTHRU
|
||||
case 'd': // FALLTHRU
|
||||
case 'e': // FALLTHRU
|
||||
case 'f': // FALLTHRU
|
||||
case 'g': // FALLTHRU
|
||||
case 'h': // FALLTHRU
|
||||
case 'o': // FALLTHRU
|
||||
case 'p': // FALLTHRU
|
||||
case 's': // FALLTHRU
|
||||
case 't': // FALLTHRU
|
||||
case 'u': // FALLTHRU
|
||||
case 'v': // FALLTHRU
|
||||
case 'x': // FALLTHRU
|
||||
case 'z': // FALLTHRU
|
||||
break;
|
||||
case 'N': { // "C" string with name of module, add . if needed
|
||||
const char* const cstrp = va_arg(ap, const char*);
|
||||
if (VL_LIKELY(*cstrp)) {
|
||||
output += cstrp;
|
||||
output += '.';
|
||||
}
|
||||
break;
|
||||
//--------
|
||||
default: // Bad escape, just print %letter so user sees it
|
||||
output += '%';
|
||||
output += fmt;
|
||||
continue;
|
||||
} // switch
|
||||
|
||||
// At this point only have escapes that expect arguments
|
||||
if (++argn > argc) {
|
||||
output += '%';
|
||||
output += fmt;
|
||||
continue; // Out of arguments
|
||||
}
|
||||
case 'S': { // "C" string
|
||||
const char* const cstrp = va_arg(ap, const char*);
|
||||
output += cstrp;
|
||||
break;
|
||||
}
|
||||
case '@': { // Verilog/C++ string
|
||||
va_arg(ap, int); // # bits is ignored
|
||||
const std::string* const cstrp = va_arg(ap, const std::string*);
|
||||
std::string padding;
|
||||
if (width > cstrp->size()) padding.append(width - cstrp->size(), ' ');
|
||||
output += left ? (*cstrp + padding) : (padding + *cstrp);
|
||||
break;
|
||||
}
|
||||
case 'e':
|
||||
case 'f':
|
||||
case 'g':
|
||||
case '^': { // Realtime
|
||||
const int lbits = va_arg(ap, int);
|
||||
const double d = va_arg(ap, double);
|
||||
(void)lbits; // UNUSED - always 64
|
||||
if (fmt == '^') { // Realtime
|
||||
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
|
||||
const int timeunit = va_arg(ap, int);
|
||||
output += _vl_vsformat_time(t_tmp, d, timeunit, left, width);
|
||||
} else {
|
||||
const std::string fmts{pctit, pos + 1};
|
||||
VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, fmts.c_str(), d);
|
||||
output += t_tmp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'p': { // 'x' but parameter is string
|
||||
const int lbits = va_arg(ap, int);
|
||||
(void)lbits;
|
||||
const std::string* const cstr = va_arg(ap, const std::string*);
|
||||
std::ostringstream oss;
|
||||
for (unsigned char c : *cstr) oss << std::hex << static_cast<int>(c);
|
||||
std::string hex_str = oss.str();
|
||||
if (width > 0 && widthSet) {
|
||||
hex_str = hex_str.size() > width
|
||||
? hex_str.substr(0, width)
|
||||
: std::string(width - hex_str.size(), '0') + hex_str;
|
||||
output += hex_str;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Deal with all read-and-print somethings
|
||||
const int lbits = va_arg(ap, int);
|
||||
QData ld = 0;
|
||||
VlWide<VL_WQ_WORDS_E> qlwp;
|
||||
WDataInP lwp = nullptr;
|
||||
if (!formatAttrValid) formatAttr = va_arg(ap, int); // char promoted to int
|
||||
formatAttrValid = false;
|
||||
|
||||
// Process an argument
|
||||
// Similar code flow in V3Number::displayed
|
||||
int lbits = 0;
|
||||
void* thingp = nullptr;
|
||||
QData ld = 0;
|
||||
VlWide<VL_VALUE_STRING_MAX_WIDTH / 4 + 2> strwide;
|
||||
WDataInP lwp = nullptr;
|
||||
int lsb = 0;
|
||||
double real = 0.0;
|
||||
if (formatAttr == VL_VFORMATATTR_COMPLEX) { // printed as string
|
||||
thingp = va_arg(ap, std::string*);
|
||||
if (fmt != 'p') fmt = 's'; // Override
|
||||
} else if (formatAttr == VL_VFORMATATTR_DOUBLE) {
|
||||
real = va_arg(ap, double);
|
||||
ld = VL_RTOIROUND_Q_D(real);
|
||||
VL_SET_WQ(strwide, ld);
|
||||
lwp = strwide;
|
||||
lbits = 64;
|
||||
// Not changint fmt == 'p' to fmt = 'g', as need fmts correct
|
||||
} else if (formatAttr == VL_VFORMATATTR_STRING) {
|
||||
thingp = va_arg(ap, std::string*);
|
||||
if (fmt != 'p' && fmt != 'x') fmt = 's'; // Override
|
||||
} else { // Numeric
|
||||
lbits = va_arg(ap, int);
|
||||
if (lbits <= VL_QUADSIZE) {
|
||||
ld = VL_VA_ARG_Q_(ap, lbits);
|
||||
VL_SET_WQ(qlwp, ld);
|
||||
lwp = qlwp;
|
||||
VL_SET_WQ(strwide, ld);
|
||||
lwp = strwide;
|
||||
} else {
|
||||
lwp = va_arg(ap, WDataInP);
|
||||
ld = lwp[0];
|
||||
}
|
||||
int lsb = lbits - 1;
|
||||
if (fmt == 'p') {
|
||||
if (widthSet && width == 0) { // For %0p, IEEE our choice, use 'h%0h
|
||||
output += "'h";
|
||||
fmt = 'h';
|
||||
} else { // UVM tests require %0d
|
||||
widthSet = true;
|
||||
width = 0;
|
||||
fmt = 'd';
|
||||
}
|
||||
}
|
||||
lsb = lbits - 1;
|
||||
if (widthSet && width == 0) {
|
||||
while (lsb && !VL_BITISSET_W(lwp, lsb)) --lsb;
|
||||
}
|
||||
switch (fmt) {
|
||||
case 'c': {
|
||||
const IData charval = ld & 0xff;
|
||||
output += static_cast<char>(charval);
|
||||
break;
|
||||
}
|
||||
|
||||
// fmt may have been overridden above based on formatAttr datatype passed
|
||||
switch (fmt) {
|
||||
case 'c': {
|
||||
const IData charval = ld & 0xff;
|
||||
output += static_cast<char>(charval);
|
||||
break;
|
||||
}
|
||||
case 'e': // FALLTHRU
|
||||
case 'f': // FALLTHRU
|
||||
case 'g': {
|
||||
if (formatAttr == VL_VFORMATATTR_SIGNED) {
|
||||
real = VL_ISTOR_D_W(lbits, lwp);
|
||||
} else if (formatAttr == VL_VFORMATATTR_UNSIGNED) {
|
||||
real = VL_ITOR_D_W(lbits, lwp);
|
||||
}
|
||||
case 's': {
|
||||
const std::string fmts{pctit, pos + 1};
|
||||
VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, fmts.c_str(), real);
|
||||
output += t_tmp;
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
if (thingp) { // VNumber::STRING Verilog 'string'
|
||||
const std::string* const strp = static_cast<const std::string*>(thingp);
|
||||
std::string padding;
|
||||
if (width > strp->size()) padding.append(width - strp->size(), ' ');
|
||||
output += left ? (*strp + padding) : (padding + *strp);
|
||||
break;
|
||||
} else { // Number-based string
|
||||
std::string field;
|
||||
for (; lsb >= 0; --lsb) {
|
||||
lsb = (lsb / 8) * 8; // Next digit
|
||||
|
|
@ -1070,11 +1141,28 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
|
|||
std::string padding;
|
||||
if (width > field.size()) padding.append(width - field.size(), ' ');
|
||||
output += left ? (field + padding) : (padding + field);
|
||||
break;
|
||||
}
|
||||
case 'd': { // Signed decimal
|
||||
int digits = 0;
|
||||
std::string append;
|
||||
break;
|
||||
}
|
||||
case 'p': { // Pattern
|
||||
// 'p' with NUMBER was earlier converted to 'd'
|
||||
if (formatAttr
|
||||
== VL_VFORMATATTR_DOUBLE) { // Can't just change to 'g' as need fixed format
|
||||
VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, "%g", real);
|
||||
output += t_tmp;
|
||||
} else if (formatAttr == VL_VFORMATATTR_STRING) {
|
||||
const std::string* const strp = static_cast<const std::string*>(thingp);
|
||||
output += '"' + *strp + '"';
|
||||
} else if (formatAttr == VL_VFORMATATTR_COMPLEX) {
|
||||
const std::string* const strp = static_cast<const std::string*>(thingp);
|
||||
output += *strp;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'd': { // Signed/unsigned decimal
|
||||
int digits = 0;
|
||||
std::string append;
|
||||
if (formatAttr == VL_VFORMATATTR_SIGNED) {
|
||||
if (lbits <= VL_QUADSIZE) {
|
||||
digits
|
||||
= VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, "%" PRId64,
|
||||
|
|
@ -1090,28 +1178,7 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
|
|||
}
|
||||
digits = static_cast<int>(append.length());
|
||||
}
|
||||
const int needmore = static_cast<int>(width) - digits;
|
||||
if (needmore > 0) {
|
||||
std::string padding;
|
||||
if (left) {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
output += append + padding;
|
||||
} else {
|
||||
if (pctit != format.end() && pctit[0] && pctit[1] == '0') { // %0
|
||||
padding.append(needmore, '0'); // Pre-pad zero
|
||||
} else {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
}
|
||||
output += padding + append;
|
||||
}
|
||||
} else {
|
||||
output += append;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '#': { // Unsigned decimal
|
||||
int digits = 0;
|
||||
std::string append;
|
||||
} else { // Unsigned decimal
|
||||
if (lbits <= VL_QUADSIZE) {
|
||||
digits = VL_SNPRINTF(t_tmp, VL_VALUE_STRING_MAX_WIDTH, "%" PRIu64, ld);
|
||||
append = t_tmp;
|
||||
|
|
@ -1119,119 +1186,146 @@ void _vl_vsformat(std::string& output, const std::string& format, va_list ap) VL
|
|||
append = VL_DECIMAL_NW(lbits, lwp);
|
||||
digits = static_cast<int>(append.length());
|
||||
}
|
||||
const int needmore = static_cast<int>(width) - digits;
|
||||
if (needmore > 0) {
|
||||
std::string padding;
|
||||
if (left) {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
output += append + padding;
|
||||
} else {
|
||||
if (pctit != format.end() && pctit[0] && pctit[1] == '0') { // %0
|
||||
padding.append(needmore, '0'); // Pre-pad zero
|
||||
} else {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
}
|
||||
output += padding + append;
|
||||
}
|
||||
}
|
||||
if (!widthSet) {
|
||||
const double mantissabits
|
||||
= lbits - ((formatAttr == VL_VFORMATATTR_SIGNED) ? 1 : 0);
|
||||
// This is log10(2**mantissabits) as log2(2**mantissabits)/log2(10),
|
||||
// + 1.0 rounding bias.
|
||||
double dchars = mantissabits / 3.321928094887362 + 1.0;
|
||||
if (formatAttr == VL_VFORMATATTR_SIGNED) ++dchars; // space for sign
|
||||
width = int(dchars);
|
||||
}
|
||||
const int needmore = static_cast<int>(width) - digits;
|
||||
if (needmore > 0) {
|
||||
std::string padding;
|
||||
if (left) {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
output += append + padding;
|
||||
} else {
|
||||
output += append;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 't': { // Time
|
||||
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
|
||||
const int timeunit = va_arg(ap, int);
|
||||
output += _vl_vsformat_time(t_tmp, ld, timeunit, left, width);
|
||||
break;
|
||||
}
|
||||
case 'b': // FALLTHRU
|
||||
case 'o': // FALLTHRU
|
||||
case 'x': {
|
||||
if (widthSet || left) {
|
||||
lsb = VL_MOSTSETBITP1_W(VL_WORDS_I(lbits), lwp);
|
||||
lsb = (lsb < 1) ? 0 : (lsb - 1);
|
||||
}
|
||||
|
||||
std::string append;
|
||||
int digits;
|
||||
switch (fmt) {
|
||||
case 'b': {
|
||||
digits = lsb + 1;
|
||||
for (; lsb >= 0; --lsb) append += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0';
|
||||
break;
|
||||
}
|
||||
case 'o': {
|
||||
digits = (lsb + 1 + 2) / 3;
|
||||
for (; lsb >= 0; --lsb) {
|
||||
lsb = (lsb / 3) * 3; // Next digit
|
||||
// Octal numbers may span more than one wide word,
|
||||
// so we need to grab each bit separately and check for overrun
|
||||
// Octal is rare, so we'll do it a slow simple way
|
||||
append += static_cast<char>(
|
||||
'0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0)
|
||||
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0)
|
||||
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: { // 'x'
|
||||
digits = (lsb + 1 + 3) / 4;
|
||||
for (; lsb >= 0; --lsb) {
|
||||
lsb = (lsb / 4) * 4; // Next digit
|
||||
const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf;
|
||||
append += "0123456789abcdef"[charval];
|
||||
}
|
||||
break;
|
||||
}
|
||||
} // switch
|
||||
|
||||
const int needmore = static_cast<int>(width) - digits;
|
||||
if (needmore > 0) {
|
||||
std::string padding;
|
||||
if (left) {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
output += append + padding;
|
||||
} else {
|
||||
if (pctit != format.end() && pctit[0] && pctit[1] == '0') { // %0
|
||||
padding.append(needmore, '0'); // Pre-pad zero
|
||||
output += padding + append;
|
||||
} else {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
}
|
||||
} else {
|
||||
output += append;
|
||||
output += padding + append;
|
||||
}
|
||||
} else {
|
||||
output += append;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 't': { // Time
|
||||
// Timeunit was read earlier from up-front arguments
|
||||
if (formatAttr == VL_VFORMATATTR_DOUBLE) { // Realtime
|
||||
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
|
||||
output += _vl_vsformat_time(t_tmp, real, timeunit, left, width);
|
||||
} else {
|
||||
if (!widthSet) width = Verilated::threadContextp()->impp()->timeFormatWidth();
|
||||
output += _vl_vsformat_time(t_tmp, ld, timeunit, left, width);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'b': // FALLTHRU
|
||||
case 'h': // FALLTHRU
|
||||
case 'o': // FALLTHRU
|
||||
case 'x': {
|
||||
if (formatAttr == VL_VFORMATATTR_STRING) {
|
||||
// V3Width errors on const %x of string, but V3Randomize may make a %x on a
|
||||
// string, or may have a runtime format
|
||||
const std::string* const strp = static_cast<const std::string*>(thingp);
|
||||
int chars = std::min(static_cast<int>(strp->size()),
|
||||
static_cast<int>(VL_VALUE_STRING_MAX_WIDTH / 2));
|
||||
int truncFront = widthSet ? (chars - (static_cast<int>(width) / 2)) : 0;
|
||||
if (truncFront < 0) truncFront = 0;
|
||||
lbits = chars * 8;
|
||||
lwp = strwide;
|
||||
lsb = lbits - 1;
|
||||
VL_NTOI_W(lbits, strwide, *strp, truncFront);
|
||||
}
|
||||
|
||||
if (widthSet || left) {
|
||||
lsb = VL_MOSTSETBITP1_W(VL_WORDS_I(lbits), lwp);
|
||||
lsb = (lsb < 1) ? 0 : (lsb - 1);
|
||||
}
|
||||
|
||||
std::string append;
|
||||
int digits;
|
||||
switch (fmt) {
|
||||
case 'b': {
|
||||
digits = lsb + 1;
|
||||
for (; lsb >= 0; --lsb) append += (VL_BITRSHIFT_W(lwp, lsb) & 1) + '0';
|
||||
break;
|
||||
} // b / o / x
|
||||
case 'u':
|
||||
case 'z': { // Packed 4-state
|
||||
const bool is_4_state = (fmt == 'z');
|
||||
output.reserve(output.size() + ((is_4_state ? 2 : 1) * VL_WORDS_I(lbits)));
|
||||
int bytes_to_go = VL_BYTES_I(lbits);
|
||||
int bit = 0;
|
||||
while (bytes_to_go > 0) {
|
||||
const int wr_bytes = std::min(4, bytes_to_go);
|
||||
for (int byte = 0; byte < wr_bytes; byte++, bit += 8)
|
||||
output += static_cast<char>(VL_BITRSHIFT_W(lwp, bit) & 0xff);
|
||||
output.append(4 - wr_bytes, static_cast<char>(0));
|
||||
if (is_4_state) output.append(4, static_cast<char>(0));
|
||||
bytes_to_go -= wr_bytes;
|
||||
}
|
||||
case 'o': {
|
||||
digits = (lsb + 1 + 2) / 3;
|
||||
for (; lsb >= 0; --lsb) {
|
||||
lsb = (lsb / 3) * 3; // Next digit
|
||||
// Octal numbers may span more than one wide word,
|
||||
// so we need to grab each bit separately and check for overrun
|
||||
// Octal is rare, so we'll do it a slow simple way
|
||||
append += static_cast<char>(
|
||||
'0' + ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 0)) ? 1 : 0)
|
||||
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 1)) ? 2 : 0)
|
||||
+ ((VL_BITISSETLIMIT_W(lwp, lbits, lsb + 2)) ? 4 : 0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'v': // Strength; assume always strong
|
||||
for (lsb = lbits - 1; lsb >= 0; --lsb) {
|
||||
if (VL_BITRSHIFT_W(lwp, lsb) & 1) {
|
||||
output += "St1 ";
|
||||
} else {
|
||||
output += "St0 ";
|
||||
}
|
||||
default: { // 'x'
|
||||
digits = (lsb + 1 + 3) / 4;
|
||||
for (; lsb >= 0; --lsb) {
|
||||
lsb = (lsb / 4) * 4; // Next digit
|
||||
const IData charval = VL_BITRSHIFT_W(lwp, lsb) & 0xf;
|
||||
append += "0123456789abcdef"[charval];
|
||||
}
|
||||
break;
|
||||
default: { // LCOV_EXCL_START
|
||||
const std::string msg = "Unknown _vl_vsformat code: "s + pos[0];
|
||||
VL_FATAL_MT(__FILE__, __LINE__, "", msg.c_str());
|
||||
break;
|
||||
} // LCOV_EXCL_STOP
|
||||
}
|
||||
} // switch
|
||||
|
||||
const int needmore = static_cast<int>(width) - digits;
|
||||
if (needmore > 0) {
|
||||
std::string padding;
|
||||
if (left) {
|
||||
padding.append(needmore, ' '); // Pre-pad spaces
|
||||
output += append + padding;
|
||||
} else {
|
||||
padding.append(needmore, '0'); // Pre-pad zero
|
||||
output += padding + append;
|
||||
}
|
||||
} else {
|
||||
output += append;
|
||||
}
|
||||
break;
|
||||
} // b / o / x
|
||||
case 'u':
|
||||
case 'z': { // Packed 4-state
|
||||
const bool is_4_state = (fmt == 'z');
|
||||
output.reserve(output.size() + ((is_4_state ? 2 : 1) * VL_WORDS_I(lbits)));
|
||||
int bytes_to_go = VL_BYTES_I(lbits);
|
||||
int bit = 0;
|
||||
while (bytes_to_go > 0) {
|
||||
const int wr_bytes = std::min(4, bytes_to_go);
|
||||
for (int byte = 0; byte < wr_bytes; byte++, bit += 8)
|
||||
output += static_cast<char>(VL_BITRSHIFT_W(lwp, bit) & 0xff);
|
||||
output.append(4 - wr_bytes, static_cast<char>(0));
|
||||
if (is_4_state) output.append(4, static_cast<char>(0));
|
||||
bytes_to_go -= wr_bytes;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'v': // Strength; assume always strong
|
||||
for (lsb = lbits - 1; lsb >= 0; --lsb) {
|
||||
if (VL_BITRSHIFT_W(lwp, lsb) & 1) {
|
||||
output += "St1 ";
|
||||
} else {
|
||||
output += "St0 ";
|
||||
}
|
||||
}
|
||||
break;
|
||||
default: { // LCOV_EXCL_START
|
||||
VL_DEBUG_IFDEF(assert(0);); // Missing case between this case, and one above
|
||||
break;
|
||||
} // LCOV_EXCL_STOP
|
||||
} // switch
|
||||
}
|
||||
}
|
||||
|
|
@ -1317,7 +1411,7 @@ void _vl_vsss_based(WDataOutP owp, int obits, int baseLog2, const char* strp, si
|
|||
for (int i = 0, pos = static_cast<int>(posend) - 1;
|
||||
i < obits && pos >= static_cast<int>(posstart); --pos) {
|
||||
// clang-format off
|
||||
switch (tolower (strp[pos])) {
|
||||
switch (std::tolower (strp[pos])) {
|
||||
case 'x': case 'z': case '?': // FALLTHRU
|
||||
case '0': lsb += baseLog2; break;
|
||||
case '1': _vl_vsss_setbit(owp, obits, lsb, baseLog2, 1); lsb += baseLog2; break;
|
||||
|
|
@ -1344,7 +1438,7 @@ void _vl_vsss_based(WDataOutP owp, int obits, int baseLog2, const char* strp, si
|
|||
IData _vl_vsscanf(FILE* fp, // If a fscanf
|
||||
int fbits, const WDataInP fromp, // Else if a sscanf
|
||||
const std::string& fstr, // if a sscanf to string
|
||||
const std::string& format, va_list ap) VL_MT_SAFE {
|
||||
const std::string& format, int argc, va_list ap) VL_MT_SAFE {
|
||||
// Read a Verilog $sscanf/$fscanf style format into the output list
|
||||
// The format must be pre-processed (and lower cased) by Verilator
|
||||
// Arguments are in "width, arg-value (or WDataIn* if wide)" form
|
||||
|
|
@ -1353,6 +1447,25 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
IData got = 0;
|
||||
bool inPct = false;
|
||||
bool inIgnore = false;
|
||||
int argn = 0;
|
||||
|
||||
char formatAttr = '\0'; // Fetched format for _next_ argument
|
||||
bool formatAttrValid = false;
|
||||
int timeunit = 0;
|
||||
while (argn < argc) {
|
||||
formatAttr = va_arg(ap, int); // Char promoted to int
|
||||
switch (formatAttr) {
|
||||
case VL_VFORMATATTR_TIMEUNIT:
|
||||
++argn;
|
||||
timeunit = va_arg(ap, int);
|
||||
continue;
|
||||
default: // Normal arg; will consume formatAttr later
|
||||
formatAttrValid = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
std::string::const_iterator pos = format.cbegin();
|
||||
for (; pos != format.cend(); ++pos) {
|
||||
// VL_DBG_MSGF("_vlscan fmt='%c' floc=%d file='%c'\n", pos[0], floc,
|
||||
|
|
@ -1371,7 +1484,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
} else { // Format character
|
||||
// Skip loading spaces
|
||||
inPct = false;
|
||||
const char fmt = pos[0];
|
||||
const char fmt = std::tolower(pos[0]);
|
||||
switch (fmt) {
|
||||
case '%': {
|
||||
const int c = _vl_vsss_peek(fp, floc, fromp, fstr);
|
||||
|
|
@ -1399,20 +1512,22 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
default: {
|
||||
// Deal with all read-and-scan somethings
|
||||
// Note LSBs are preserved if there's an overflow
|
||||
int obits = inIgnore ? 0 : va_arg(ap, int);
|
||||
if (!inIgnore && (++argn > argc)) inIgnore = true; // Overflowed arguments
|
||||
if (!inIgnore) {
|
||||
if (!formatAttrValid) formatAttr = va_arg(ap, int); // char promoted to int
|
||||
formatAttrValid = false;
|
||||
}
|
||||
int obits = (!inIgnore
|
||||
&& (formatAttr == VL_VFORMATATTR_UNSIGNED
|
||||
|| formatAttr == VL_VFORMATATTR_SIGNED))
|
||||
? va_arg(ap, int)
|
||||
: 0;
|
||||
void* const thingp = inIgnore ? nullptr : va_arg(ap, void*);
|
||||
double real = 0;
|
||||
|
||||
VlWide<VL_WQ_WORDS_E> qowp;
|
||||
VL_SET_WQ(qowp, 0ULL);
|
||||
WDataOutP owp = qowp;
|
||||
if (obits == -1) { // string
|
||||
owp = nullptr;
|
||||
if (VL_UNCOVERABLE(fmt != 's')) {
|
||||
VL_FATAL_MT(
|
||||
__FILE__, __LINE__, "",
|
||||
"Internal: format other than %s is passed to string"); // LCOV_EXCL_LINE
|
||||
}
|
||||
} else if (obits > VL_QUADSIZE) {
|
||||
owp = va_arg(ap, WDataOutP);
|
||||
}
|
||||
WDataOutP owp = (obits <= 64) ? qowp : static_cast<WDataOutP>(thingp);
|
||||
|
||||
for (int i = 0; i < VL_WORDS_I(obits); ++i) owp[i] = 0;
|
||||
switch (fmt) {
|
||||
|
|
@ -1427,23 +1542,27 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
_vl_vsss_skipspace(fp, floc, fromp, fstr);
|
||||
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, nullptr);
|
||||
if (!t_tmp[0]) goto done;
|
||||
if (owp) {
|
||||
int lpos = (static_cast<int>(std::strlen(t_tmp))) - 1;
|
||||
int lsb = 0;
|
||||
for (int i = 0; i < obits && lpos >= 0; --lpos) {
|
||||
_vl_vsss_setbit(owp, obits, lsb, 8, t_tmp[lpos]);
|
||||
lsb += 8;
|
||||
}
|
||||
int lpos = (static_cast<int>(std::strlen(t_tmp))) - 1;
|
||||
int lsb = 0;
|
||||
for (int i = 0; i < obits && lpos >= 0; --lpos) {
|
||||
_vl_vsss_setbit(owp, obits, lsb, 8, t_tmp[lpos]);
|
||||
lsb += 8;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'd': { // Signed decimal
|
||||
case 'd': { // Signed/unsigned decimal
|
||||
_vl_vsss_skipspace(fp, floc, fromp, fstr);
|
||||
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "0123456789+-xXzZ?_");
|
||||
if (!t_tmp[0]) goto done;
|
||||
int64_t ld = 0;
|
||||
std::sscanf(t_tmp, "%30" PRId64, &ld);
|
||||
VL_SET_WQ(owp, ld);
|
||||
if (formatAttr == VL_VFORMATATTR_SIGNED) {
|
||||
QData ld = 0;
|
||||
std::sscanf(t_tmp, "%30" PRIu64, &ld);
|
||||
VL_SET_WQ(owp, ld);
|
||||
} else if (formatAttr == VL_VFORMATATTR_UNSIGNED) {
|
||||
int64_t ld = 0;
|
||||
std::sscanf(t_tmp, "%30" PRId64, &ld);
|
||||
VL_SET_WQ(owp, ld);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'f':
|
||||
|
|
@ -1456,7 +1575,8 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
double r;
|
||||
int64_t ld;
|
||||
} u;
|
||||
u.r = std::strtod(t_tmp, nullptr);
|
||||
real = std::strtod(t_tmp, nullptr);
|
||||
u.r = real;
|
||||
VL_SET_WQ(owp, u.ld);
|
||||
break;
|
||||
}
|
||||
|
|
@ -1468,25 +1588,12 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
double r;
|
||||
int64_t ld;
|
||||
} u;
|
||||
// Get pointer argument first, as proceeds the timeunit value
|
||||
if (obits != 64) goto done;
|
||||
QData* const realp = va_arg(ap, QData*);
|
||||
const int timeunit = va_arg(ap, int);
|
||||
const int userUnits
|
||||
= Verilated::threadContextp()->impp()->timeFormatUnits(); // 0..-15
|
||||
// Timeunit was read earlier from up-front arguments
|
||||
const int userUnits = Verilated::threadContextp()->impp()->timeFormatUnits();
|
||||
// 0..-15
|
||||
const int shift = -userUnits + timeunit; // 0..-15
|
||||
u.r = std::strtod(t_tmp, nullptr) * vl_time_multiplier(-shift);
|
||||
*realp = VL_CLEAN_QQ(obits, obits, u.ld);
|
||||
obits = 0; // Already loaded the value, don't read arg
|
||||
break;
|
||||
}
|
||||
case '#': { // Unsigned decimal
|
||||
_vl_vsss_skipspace(fp, floc, fromp, fstr);
|
||||
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp, "0123456789+-xXzZ?_");
|
||||
if (!t_tmp[0]) goto done;
|
||||
QData ld = 0;
|
||||
std::sscanf(t_tmp, "%30" PRIu64, &ld);
|
||||
VL_SET_WQ(owp, ld);
|
||||
real = std::strtod(t_tmp, nullptr) * vl_time_multiplier(-shift);
|
||||
VL_SET_WQ(owp, static_cast<uint64_t>(real));
|
||||
break;
|
||||
}
|
||||
case 'b': {
|
||||
|
|
@ -1503,6 +1610,7 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
_vl_vsss_based(owp, obits, 3, t_tmp, 0, std::strlen(t_tmp));
|
||||
break;
|
||||
}
|
||||
case 'h': // FALLTHRU
|
||||
case 'x': {
|
||||
_vl_vsss_skipspace(fp, floc, fromp, fstr);
|
||||
_vl_vsss_read_str(fp, floc, fromp, fstr, t_tmp,
|
||||
|
|
@ -1548,21 +1656,24 @@ IData _vl_vsscanf(FILE* fp, // If a fscanf
|
|||
|
||||
if (!inIgnore) ++got;
|
||||
// Reload data if non-wide (if wide, we put it in the right place directly)
|
||||
if (obits == 0) { // Due to inIgnore
|
||||
} else if (obits == -1) { // string
|
||||
std::string* const p = va_arg(ap, std::string*);
|
||||
if (inIgnore) {
|
||||
} else if (formatAttr == VL_VFORMATATTR_DOUBLE) {
|
||||
double* const p = static_cast<double*>(thingp);
|
||||
*p = real;
|
||||
} else if (formatAttr == VL_VFORMATATTR_STRING) {
|
||||
std::string* const p = static_cast<std::string*>(thingp);
|
||||
*p = t_tmp;
|
||||
} else if (obits <= VL_BYTESIZE) {
|
||||
CData* const p = va_arg(ap, CData*);
|
||||
CData* const p = static_cast<CData*>(thingp);
|
||||
*p = VL_CLEAN_II(obits, obits, owp[0]);
|
||||
} else if (obits <= VL_SHORTSIZE) {
|
||||
SData* const p = va_arg(ap, SData*);
|
||||
SData* const p = static_cast<SData*>(thingp);
|
||||
*p = VL_CLEAN_II(obits, obits, owp[0]);
|
||||
} else if (obits <= VL_IDATASIZE) {
|
||||
IData* const p = va_arg(ap, IData*);
|
||||
IData* const p = static_cast<IData*>(thingp);
|
||||
*p = VL_CLEAN_II(obits, obits, owp[0]);
|
||||
} else if (obits <= VL_QUADSIZE) {
|
||||
QData* const p = va_arg(ap, QData*);
|
||||
QData* const p = static_cast<QData*>(thingp);
|
||||
*p = VL_CLEAN_QQ(obits, obits, VL_SET_QW(owp));
|
||||
} else {
|
||||
_vl_clean_inplace_w(obits, owp);
|
||||
|
|
@ -1689,7 +1800,7 @@ void VL_SFORMAT_NX(int obits, CData& destr, const std::string& format, int argc,
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
|
||||
|
|
@ -1700,7 +1811,7 @@ void VL_SFORMAT_NX(int obits, SData& destr, const std::string& format, int argc,
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
|
||||
|
|
@ -1711,7 +1822,7 @@ void VL_SFORMAT_NX(int obits, IData& destr, const std::string& format, int argc,
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
|
||||
|
|
@ -1722,7 +1833,7 @@ void VL_SFORMAT_NX(int obits, QData& destr, const std::string& format, int argc,
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
_vl_string_to_vint(obits, &destr, t_output.length(), t_output.c_str());
|
||||
|
|
@ -1733,7 +1844,7 @@ void VL_SFORMAT_NX(int obits, void* destp, const std::string& format, int argc,
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
_vl_string_to_vint(obits, destp, t_output.length(), t_output.c_str());
|
||||
|
|
@ -1745,7 +1856,7 @@ void VL_SFORMAT_NX(int obits_ignored, std::string& output, const std::string& fo
|
|||
std::string temp_output;
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(temp_output, format, ap);
|
||||
_vl_vsformat(temp_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
output = temp_output;
|
||||
}
|
||||
|
|
@ -1755,7 +1866,7 @@ std::string VL_SFORMATF_N_NX(const std::string& format, int argc, ...) VL_MT_SAF
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
return t_output;
|
||||
|
|
@ -1766,7 +1877,7 @@ void VL_WRITEF_NX(const std::string& format, int argc, ...) VL_MT_SAFE {
|
|||
t_output = "";
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
VL_PRINTF_MT("%s", t_output.c_str());
|
||||
|
|
@ -1779,7 +1890,7 @@ void VL_FWRITEF_NX(IData fpi, const std::string& format, int argc, ...) VL_MT_SA
|
|||
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
_vl_vsformat(t_output, format, ap);
|
||||
_vl_vsformat(t_output, format, argc, ap);
|
||||
va_end(ap);
|
||||
|
||||
Verilated::threadContextp()->impp()->fdWrite(fpi, t_output);
|
||||
|
|
@ -1792,7 +1903,7 @@ IData VL_FSCANF_INX(IData fpi, const std::string& format, int argc, ...) VL_MT_S
|
|||
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
const IData got = _vl_vsscanf(fp, 0, nullptr, "", format, ap);
|
||||
const IData got = _vl_vsscanf(fp, 0, nullptr, "", format, argc, ap);
|
||||
va_end(ap);
|
||||
return got;
|
||||
}
|
||||
|
|
@ -1803,7 +1914,7 @@ IData VL_SSCANF_IINX(int lbits, IData ld, const std::string& format, int argc, .
|
|||
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, ap);
|
||||
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, argc, ap);
|
||||
va_end(ap);
|
||||
return got;
|
||||
}
|
||||
|
|
@ -1813,7 +1924,7 @@ IData VL_SSCANF_IQNX(int lbits, QData ld, const std::string& format, int argc, .
|
|||
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, ap);
|
||||
const IData got = _vl_vsscanf(nullptr, lbits, fnw, "", format, argc, ap);
|
||||
va_end(ap);
|
||||
return got;
|
||||
}
|
||||
|
|
@ -1821,7 +1932,7 @@ IData VL_SSCANF_IWNX(int lbits, const WDataInP lwp, const std::string& format, i
|
|||
...) VL_MT_SAFE {
|
||||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
const IData got = _vl_vsscanf(nullptr, lbits, lwp, "", format, ap);
|
||||
const IData got = _vl_vsscanf(nullptr, lbits, lwp, "", format, argc, ap);
|
||||
va_end(ap);
|
||||
return got;
|
||||
}
|
||||
|
|
@ -1830,7 +1941,7 @@ IData VL_SSCANF_INNX(int, const std::string& ld, const std::string& format, int
|
|||
va_list ap;
|
||||
va_start(ap, argc);
|
||||
const IData got
|
||||
= _vl_vsscanf(nullptr, static_cast<int>(ld.length() * 8), nullptr, ld, format, ap);
|
||||
= _vl_vsscanf(nullptr, static_cast<int>(ld.length() * 8), nullptr, ld, format, argc, ap);
|
||||
va_end(ap);
|
||||
return got;
|
||||
}
|
||||
|
|
@ -2133,13 +2244,25 @@ const char* vl_mc_scan_plusargs(const char* prefixp) VL_MT_SAFE {
|
|||
//===========================================================================
|
||||
// Heavy string functions
|
||||
|
||||
std::string VL_TO_STRING(CData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 8, lhs); }
|
||||
std::string VL_TO_STRING(SData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 16, lhs); }
|
||||
std::string VL_TO_STRING(IData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 32, lhs); }
|
||||
std::string VL_TO_STRING(QData lhs) { return VL_SFORMATF_N_NX("'h%0x", 0, 64, lhs); }
|
||||
std::string VL_TO_STRING(double lhs) { return VL_SFORMATF_N_NX("%g", 0, 64, lhs); }
|
||||
// TODO these could be accelerated with a dedicated to-Hex formatter
|
||||
// instead of using VL_SFORMATF_N_NX
|
||||
std::string VL_TO_STRING(CData lhs) {
|
||||
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 8, lhs);
|
||||
}
|
||||
std::string VL_TO_STRING(SData lhs) {
|
||||
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 16, lhs);
|
||||
}
|
||||
std::string VL_TO_STRING(IData lhs) {
|
||||
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 32, lhs);
|
||||
}
|
||||
std::string VL_TO_STRING(QData lhs) {
|
||||
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, 64, lhs);
|
||||
}
|
||||
std::string VL_TO_STRING(double lhs) {
|
||||
return VL_SFORMATF_N_NX("%g", 1, VL_VFORMATATTR_DOUBLE, lhs);
|
||||
}
|
||||
std::string VL_TO_STRING_W(int words, const WDataInP obj) {
|
||||
return VL_SFORMATF_N_NX("'h%0x", 0, words * VL_EDATASIZE, obj);
|
||||
return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, words * VL_EDATASIZE, obj);
|
||||
}
|
||||
|
||||
std::string VL_TOLOWER_NN(const std::string& ld) VL_PURE {
|
||||
|
|
@ -2221,11 +2344,12 @@ QData VL_NTOI_Q(int obits, const std::string& str) VL_PURE {
|
|||
}
|
||||
return out & VL_MASK_Q(obits);
|
||||
}
|
||||
void VL_NTOI_W(int obits, WDataOutP owp, const std::string& str) VL_PURE {
|
||||
void VL_NTOI_W(int obits, WDataOutP owp, const std::string& str, int truncFront) VL_PURE {
|
||||
// Could also be called VL_CVT_PACK_STR_WN; converts string to wide
|
||||
const int words = VL_WORDS_I(obits);
|
||||
for (int i = 0; i < words; ++i) owp[i] = 0;
|
||||
const char* const datap = str.data();
|
||||
int pos = static_cast<int>(str.length()) - 1;
|
||||
int pos = static_cast<int>(str.length()) - 1 - truncFront;
|
||||
int bit = 0;
|
||||
while (bit < obits && pos >= 0) {
|
||||
owp[VL_BITWORD_I(bit)] |= static_cast<EData>(datap[pos]) << VL_BITBIT_I(bit);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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;"
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -121,28 +121,6 @@ class EmitCFunc VL_NOT_FINAL : public EmitCConstInit {
|
|||
std::unordered_map<AstJumpBlock*, size_t> m_labelNumbers; // Label numbers for AstJumpBlocks
|
||||
bool m_createdScopeHash = false; // Already created a scope hash
|
||||
|
||||
// State associated with processing $display style string formatting
|
||||
struct EmitDispState final {
|
||||
string m_format; // "%s" and text from user
|
||||
std::vector<char> m_argsChar; // Format of each argument to be printed
|
||||
std::vector<AstNode*> m_argsp; // Each argument to be printed
|
||||
std::vector<string> m_argsFunc; // Function before each argument to be printed
|
||||
EmitDispState() { clear(); }
|
||||
void clear() {
|
||||
m_format = "";
|
||||
m_argsChar.clear();
|
||||
m_argsp.clear();
|
||||
m_argsFunc.clear();
|
||||
}
|
||||
void pushFormat(const string& fmt) { m_format += fmt; }
|
||||
void pushFormat(char fmt) { m_format += fmt; }
|
||||
void pushArg(char fmtChar, AstNode* nodep, const string& func) {
|
||||
m_argsChar.push_back(fmtChar);
|
||||
m_argsp.push_back(nodep);
|
||||
m_argsFunc.push_back(func);
|
||||
}
|
||||
} m_emitDispState;
|
||||
|
||||
protected:
|
||||
VL_DEFINE_DEBUG_FUNCTIONS;
|
||||
|
||||
|
|
@ -172,11 +150,9 @@ protected:
|
|||
|
||||
public:
|
||||
// METHODS
|
||||
void displayNode(AstNode* nodep, AstScopeName* scopenamep, const string& vformat,
|
||||
AstNode* exprsp, bool isScan);
|
||||
void displayEmit(AstNode* nodep, bool isScan);
|
||||
void displayArg(AstNode* dispp, AstNode** elistp, bool isScan, const string& vfmt, bool ignore,
|
||||
char fmtLetter);
|
||||
bool displayEmitHeader(AstNode* nodep, bool isScan);
|
||||
void displayNode(AstNode* nodep, AstSFormatF* fmtp, const string& vformat, AstNode* exprsp,
|
||||
bool isScan);
|
||||
|
||||
bool emitSimpleOk(AstNodeExpr* nodep);
|
||||
void emitIQW(const AstNode* nodep) {
|
||||
|
|
@ -895,7 +871,7 @@ public:
|
|||
void visit(AstDisplay* nodep) override {
|
||||
string text = nodep->fmtp()->text();
|
||||
if (nodep->addNewline()) text += "\n";
|
||||
displayNode(nodep, nodep->fmtp()->scopeNamep(), text, nodep->fmtp()->exprsp(), false);
|
||||
displayNode(nodep, nodep->fmtp(), text, nodep->fmtp()->exprsp(), false);
|
||||
}
|
||||
void visit(AstDumpCtl* nodep) override {
|
||||
switch (nodep->ctlType()) {
|
||||
|
|
@ -946,11 +922,10 @@ public:
|
|||
}
|
||||
}
|
||||
void visit(AstSFormat* nodep) override {
|
||||
displayNode(nodep, nodep->fmtp()->scopeNamep(), nodep->fmtp()->text(),
|
||||
nodep->fmtp()->exprsp(), false);
|
||||
displayNode(nodep, nodep->fmtp(), nodep->fmtp()->text(), nodep->fmtp()->exprsp(), false);
|
||||
}
|
||||
void visit(AstSFormatF* nodep) override {
|
||||
displayNode(nodep, nodep->scopeNamep(), nodep->text(), nodep->exprsp(), false);
|
||||
displayNode(nodep, nodep, nodep->text(), nodep->exprsp(), false);
|
||||
}
|
||||
void visit(AstFScanF* nodep) override {
|
||||
displayNode(nodep, nullptr, nodep->text(), nodep->exprsp(), true);
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
164
src/V3Number.cpp
164
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<int>(toString().length()) * 8, 0};
|
||||
strwide.opNToI(*this);
|
||||
return strwide.displayed(fl, vformat, VFormatAttr::UNSIGNED);
|
||||
}
|
||||
if (fmt != 'p') fmt = 's'; // override
|
||||
} else if (formatAttr.isSigned() || formatAttr.isUnsigned()) {
|
||||
if (fmt == 'p') {
|
||||
if (fmtsize == "0") { // For %0p, IEEE our choice, use 'h%0h
|
||||
str += "'h";
|
||||
fmt = 'h';
|
||||
} else { // UVM tests require %0d
|
||||
fmtsize = "0";
|
||||
fmt = 'd';
|
||||
}
|
||||
}
|
||||
} else if (formatAttr.isDouble()) {
|
||||
// 'p' not converted to 'g' here as vformat string can't be easily changed
|
||||
if (!(fmt == 'e' || fmt == 'f' || fmt == 'g' || fmt == 'p'
|
||||
|| VL_UNCOVERABLE(fmt == 't'))) {
|
||||
// A non-float format with a float, must convert to integer then format
|
||||
V3Number nonfloat{fl, 64, 0};
|
||||
nonfloat.opRToIRoundS(*this);
|
||||
return nonfloat.displayed(fl, vformat, VFormatAttr::SIGNED);
|
||||
}
|
||||
}
|
||||
|
||||
switch (fmt) {
|
||||
case 'c': {
|
||||
if (width() > 8)
|
||||
fl->v3warn(WIDTHTRUNC, "$display-like format of %c format of > 8 bit value");
|
||||
const unsigned int v = bitsValue(0, 8);
|
||||
str.push_back(static_cast<char>(v));
|
||||
return str;
|
||||
}
|
||||
case 'e': // FALLTHRU
|
||||
case 'f': // FALLTHRU
|
||||
case 'g': {
|
||||
const double n = formatAttr.isDouble() ? toDouble()
|
||||
: formatAttr.isSigned() ? toSQuad()
|
||||
: toUQuad();
|
||||
char tmp[MAX_SPRINTF_DOUBLE_SIZE];
|
||||
VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, vformat.c_str(), n);
|
||||
return tmp;
|
||||
}
|
||||
case 's': {
|
||||
if (formatAttr.isString() || formatAttr.isComplex()) {
|
||||
str = toString();
|
||||
} else {
|
||||
// Spec says always drop leading zeros, this isn't quite right, we space pad.
|
||||
int bit = width() - 1;
|
||||
bool start = true;
|
||||
while ((bit % 8) != 7) ++bit;
|
||||
for (; bit >= 0; bit -= 8) {
|
||||
const int v = bitsValue(bit - 7, 8);
|
||||
if (!start || v) {
|
||||
str += static_cast<char>((v == 0) ? ' ' : v);
|
||||
start = false; // Drop leading 0s
|
||||
} else {
|
||||
if (fmtsize != "0") str += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
const size_t fmtsizen = static_cast<size_t>(std::atoi(fmtsize.c_str()));
|
||||
str = displayPad(fmtsizen, ' ', left, str);
|
||||
return str;
|
||||
}
|
||||
case 'p': { // Pattern
|
||||
// 'p' with NUMBER was earlier converted to 'd'
|
||||
if (formatAttr.isDouble()) {
|
||||
const double n = formatAttr.isDouble() ? toDouble()
|
||||
: formatAttr.isSigned() ? toSQuad()
|
||||
: toUQuad();
|
||||
char tmp[MAX_SPRINTF_DOUBLE_SIZE];
|
||||
VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, "%g", n);
|
||||
return tmp;
|
||||
}
|
||||
if (formatAttr.isString()) return '"' + toString() + '"';
|
||||
if (formatAttr.isComplex()) return toString();
|
||||
return "%p";
|
||||
}
|
||||
case 'b': // FALLTHRU
|
||||
case 'o': // FALLTHRU
|
||||
case 'h': // FALLTHRU
|
||||
|
|
@ -671,7 +766,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
|
|||
if (left || !fmtsize.empty()) {
|
||||
while (bit && bitIs0(bit)) --bit;
|
||||
}
|
||||
switch (code) {
|
||||
switch (fmt) {
|
||||
case 'b': {
|
||||
for (; bit >= 0; --bit) {
|
||||
if (bitIs0(bit)) {
|
||||
|
|
@ -747,34 +842,9 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
|
|||
str = displayPad(fmtsizen, (left ? ' ' : '0'), left, str);
|
||||
return str;
|
||||
} // case b/d/x/o
|
||||
case 'c': {
|
||||
// V3Width has warning if > 8 bits
|
||||
const unsigned int v = bitsValue(0, 8);
|
||||
str.push_back(static_cast<char>(v));
|
||||
return str;
|
||||
}
|
||||
case 's': {
|
||||
// Spec says always drop leading zeros, this isn't quite right, we space pad.
|
||||
int bit = width() - 1;
|
||||
bool start = true;
|
||||
while ((bit % 8) != 7) ++bit;
|
||||
for (; bit >= 0; bit -= 8) {
|
||||
const int v = bitsValue(bit - 7, 8);
|
||||
if (!start || v) {
|
||||
str += static_cast<char>((v == 0) ? ' ' : v);
|
||||
start = false; // Drop leading 0s
|
||||
} else {
|
||||
if (fmtsize != "0") str += ' ';
|
||||
}
|
||||
}
|
||||
const size_t fmtsizen = static_cast<size_t>(std::atoi(fmtsize.c_str()));
|
||||
str = displayPad(fmtsizen, ' ', left, str);
|
||||
return str;
|
||||
}
|
||||
case '~': // Signed decimal
|
||||
case 't': // Time
|
||||
case 'd': { // Unsigned decimal
|
||||
const bool issigned = (code == '~');
|
||||
const bool issigned = formatAttr.isSigned();
|
||||
if (fmtsize == "" && !left) {
|
||||
const double mantissabits = width() - (issigned ? 1 : 0);
|
||||
// To get the number of digits required, we want to compute
|
||||
|
|
@ -820,16 +890,7 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
|
|||
str = displayPad(fmtsizen, (zeropad ? '0' : ' '), left, str);
|
||||
return str;
|
||||
}
|
||||
case 'e':
|
||||
case 'f':
|
||||
case 'g':
|
||||
case '^': { // Realtime
|
||||
char tmp[MAX_SPRINTF_DOUBLE_SIZE];
|
||||
VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, vformat.c_str(), toDouble());
|
||||
return tmp;
|
||||
}
|
||||
// 'l' // Library - converted to text by V3LinkResolve
|
||||
// 'p' // Packed - converted to another code by V3Width
|
||||
case 'u': { // Packed 2-state
|
||||
for (int i = 0; i < words(); ++i) {
|
||||
const uint32_t v = m_data.num()[i].m_value;
|
||||
|
|
@ -870,11 +931,6 @@ string V3Number::displayed(FileLine* fl, const string& vformat) const VL_MT_STAB
|
|||
}
|
||||
return str;
|
||||
}
|
||||
case '@': { // Packed string
|
||||
const size_t fmtsizen = static_cast<size_t>(std::atoi(fmtsize.c_str()));
|
||||
str = displayPad(fmtsizen, ' ', left, toString());
|
||||
return str;
|
||||
}
|
||||
default: fl->v3fatalSrc("Unknown $display-like format code for number: %" << pos[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,53 @@
|
|||
#include <limits>
|
||||
#include <vector>
|
||||
|
||||
//============================================================================
|
||||
|
||||
class AstNode;
|
||||
class AstNodeDType;
|
||||
class AstSFormatArg;
|
||||
class FileLine;
|
||||
|
||||
//============================================================================
|
||||
|
||||
class VFormatAttr final {
|
||||
public:
|
||||
enum en : char {
|
||||
// // AstSFormatArg is typically skipped for UNSIGNED, as is the default
|
||||
UNSIGNED = VL_VFORMATATTR_UNSIGNED,
|
||||
SIGNED = VL_VFORMATATTR_SIGNED,
|
||||
//
|
||||
COMPLEX = VL_VFORMATATTR_COMPLEX,
|
||||
DOUBLE = VL_VFORMATATTR_DOUBLE,
|
||||
SCOPE = VL_VFORMATATTR_SCOPE,
|
||||
STRING = VL_VFORMATATTR_STRING,
|
||||
TIMEUNIT = VL_VFORMATATTR_TIMEUNIT
|
||||
};
|
||||
enum en m_e;
|
||||
VFormatAttr()
|
||||
: m_e{UNSIGNED} {}
|
||||
// cppcheck-suppress noExplicitConstructor
|
||||
constexpr VFormatAttr(en _e)
|
||||
: m_e{_e} {}
|
||||
explicit VFormatAttr(int _e)
|
||||
: m_e(static_cast<en>(_e)) {} // Need () or GCC 4.8 false warning
|
||||
constexpr operator en() const { return m_e; }
|
||||
char ascii() const { return m_e; }
|
||||
bool isComplex() const { return m_e == COMPLEX; }
|
||||
bool isDouble() const { return m_e == DOUBLE; }
|
||||
bool isSigned() const { return m_e == SIGNED; }
|
||||
bool isString() const { return m_e == STRING; }
|
||||
bool isUnsigned() const { return m_e == UNSIGNED; }
|
||||
};
|
||||
constexpr bool operator==(const VFormatAttr& lhs, const VFormatAttr& rhs) {
|
||||
return lhs.m_e == rhs.m_e;
|
||||
}
|
||||
constexpr bool operator==(const VFormatAttr& lhs, VFormatAttr::en rhs) { return lhs.m_e == rhs; }
|
||||
constexpr bool operator==(VFormatAttr::en lhs, const VFormatAttr& rhs) { return lhs == rhs.m_e; }
|
||||
inline std::ostream& operator<<(std::ostream& os, const VFormatAttr& rhs) {
|
||||
return os << rhs.ascii();
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
|
||||
class V3NumberData final {
|
||||
public:
|
||||
// TYPES
|
||||
|
|
@ -300,6 +341,8 @@ private:
|
|||
new (&m_string) std::string(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
string formatDecimal();
|
||||
|
||||
void destroyDynamicNumber() { m_dynamicNumber.~vector(); }
|
||||
void destroyString() { m_string.~string(); }
|
||||
void destroyStoredValue() {
|
||||
|
|
@ -595,9 +638,11 @@ public:
|
|||
|
||||
// ACCESSORS
|
||||
string ascii(bool prefixed = true, bool cleanVerilog = false) const VL_MT_STABLE;
|
||||
string displayed(const AstNode* nodep, const string& vformat) const VL_MT_STABLE;
|
||||
string displayed(FileLine* fl, const string& vformat) const VL_MT_STABLE;
|
||||
static bool displayedFmtLegal(char format, bool isScan); // Is this a valid format letter?
|
||||
string displayed(const AstNode* nodep, const string& vformat,
|
||||
const VFormatAttr& formatAttr = VFormatAttr::UNSIGNED) const VL_MT_STABLE;
|
||||
string displayed(FileLine* fl, const string& vformat,
|
||||
const VFormatAttr& formatAttr = VFormatAttr::UNSIGNED) const VL_MT_STABLE;
|
||||
static bool displayedFmtHasArg(char format, bool isScan);
|
||||
string emitC() const VL_MT_STABLE;
|
||||
int width() const VL_MT_SAFE { return m_data.width(); }
|
||||
int widthToFit() const; // Minimum width that can represent this number (~== log2(num)+1)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -737,7 +737,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
bool m_wantSingle = false; // Whether to merge constraint expressions with LOGAND
|
||||
VMemberMap& m_memberMap; // Member names cached for fast lookup
|
||||
bool m_structSel = false; // Marks when inside structSel
|
||||
// (used to format "%@.%@" for struct arrays)
|
||||
// (used to format "%s.%s" for struct arrays)
|
||||
std::set<std::string>& m_writtenVars; // Track which variable paths have write_var generated
|
||||
// (shared across all constraints)
|
||||
std::set<AstVar*>* m_sizeConstrainedArraysp = nullptr; // Arrays with size+element constraints
|
||||
|
|
@ -887,19 +887,19 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
switch (pos[0]) {
|
||||
case '%': break;
|
||||
case 'l':
|
||||
pos[0] = '@';
|
||||
pos[0] = 's';
|
||||
UASSERT_OBJ(lhsp, nodep, "emitSMT() references undef node");
|
||||
argsp = AstNode::addNext(argsp, lhsp);
|
||||
lhsp = nullptr;
|
||||
break;
|
||||
case 'r':
|
||||
pos[0] = '@';
|
||||
pos[0] = 's';
|
||||
UASSERT_OBJ(rhsp, nodep, "emitSMT() references undef node");
|
||||
argsp = AstNode::addNext(argsp, rhsp);
|
||||
rhsp = nullptr;
|
||||
break;
|
||||
case 't':
|
||||
pos[0] = '@';
|
||||
pos[0] = 's';
|
||||
UASSERT_OBJ(thsp, nodep, "emitSMT() references undef node");
|
||||
argsp = AstNode::addNext(argsp, thsp);
|
||||
thsp = nullptr;
|
||||
|
|
@ -912,8 +912,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
UASSERT_OBJ(!rhsp, nodep, "Missing emitSMT %r for " << rhsp);
|
||||
UASSERT_OBJ(!thsp, nodep, "Missing emitSMT %t for " << thsp);
|
||||
AstSFormatF* const newp = new AstSFormatF{nodep->fileline(), smtExpr, false, argsp};
|
||||
if (m_structSel && newp->name() == "(select %@ %@)") {
|
||||
newp->name("%@.%@");
|
||||
if (m_structSel && newp->name() == "(select %s %s)") {
|
||||
newp->name("%s.%s");
|
||||
if (!VN_IS(nodep, AssocSel)) newp->exprsp()->nextp()->name("%x");
|
||||
}
|
||||
nodep->replaceWith(newp);
|
||||
|
|
@ -943,7 +943,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
|
||||
std::ostringstream fmt;
|
||||
fmt << "(bvand";
|
||||
for (AstNode* itemp = exprsp; itemp; itemp = itemp->nextp()) fmt << " %@";
|
||||
for (AstNode* itemp = exprsp; itemp; itemp = itemp->nextp()) fmt << " %s";
|
||||
fmt << ')';
|
||||
return new AstSFormatF{fl, fmt.str(), false, exprsp};
|
||||
}
|
||||
|
|
@ -1683,8 +1683,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
iterateChildren(nodep);
|
||||
FileLine* const fl = nodep->fileline();
|
||||
AstSFormatF* newp = nullptr;
|
||||
if (VN_AS(nodep->fromp(), SFormatF)->name() == "%@.%@") {
|
||||
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
|
||||
if (VN_AS(nodep->fromp(), SFormatF)->name() == "%s.%s") {
|
||||
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
|
||||
VN_AS(nodep->fromp(), SFormatF)->exprsp()->cloneTreePure(true)};
|
||||
if (newp->exprsp()->nextp()->name().rfind("#x", 0) == 0)
|
||||
newp->exprsp()->nextp()->name("%x"); // for #x%x to %x
|
||||
|
|
@ -1702,7 +1702,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
// Adaptive formatting and type handling for associative array keys
|
||||
if (VN_IS(nodep->bitp(), VarRef) && VN_AS(nodep->bitp(), VarRef)->isString()) {
|
||||
VNRelinker handle;
|
||||
AstNodeExpr* const idxp = new AstSFormatF{fl, (m_structSel ? "%32p" : "#x%32p"), false,
|
||||
AstNodeExpr* const idxp = new AstSFormatF{fl, (m_structSel ? "%32x" : "#x%32x"), false,
|
||||
nodep->bitp()->unlinkFrBack(&handle)};
|
||||
handle.relink(idxp);
|
||||
editSMT(nodep, nodep->fromp(), idxp);
|
||||
|
|
@ -1808,8 +1808,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
FileLine* const fl = nodep->fileline();
|
||||
AstSFormatF* newp = nullptr;
|
||||
if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) {
|
||||
if (fromp->name() == "%@.%@") {
|
||||
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
|
||||
if (fromp->name() == "%s.%s") {
|
||||
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
|
||||
fromp->exprsp()->cloneTreePure(true)};
|
||||
} else {
|
||||
newp = new AstSFormatF{fl, fromp->name() + "." + nodep->name(), false,
|
||||
|
|
@ -1865,8 +1865,8 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
FileLine* const fl = nodep->fileline();
|
||||
AstSFormatF* newp = nullptr;
|
||||
if (AstSFormatF* const fromp = VN_CAST(nodep->fromp(), SFormatF)) {
|
||||
if (fromp->name() == "%@.%@") {
|
||||
newp = new AstSFormatF{fl, "%@.%@." + nodep->name(), false,
|
||||
if (fromp->name() == "%s.%s") {
|
||||
newp = new AstSFormatF{fl, "%s.%s." + nodep->name(), false,
|
||||
fromp->exprsp()->cloneTreePure(true)};
|
||||
} else {
|
||||
newp = new AstSFormatF{fl, fromp->name() + "." + nodep->name(), false,
|
||||
|
|
@ -1941,7 +1941,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
cexprp->add(new AstBegin{
|
||||
fl, "", new AstForeach{fl, nodep->headerp()->unlinkFrBack(), cstmtp}, true});
|
||||
cexprp->add("return ret.empty() ? \"#b1\" : \"(bvand\" + ret + \")\";\n})()");
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
|
||||
} else {
|
||||
iterateAndNextNull(nodep->bodyp());
|
||||
nodep->replaceWith(new AstBegin{fl, "",
|
||||
|
|
@ -2169,9 +2169,9 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
AstNodeExpr* const argsp = AstNode::addNext(nodep->fromp()->unlinkFrBack(), pinp);
|
||||
AstSFormatF* newp = nullptr;
|
||||
if (m_structSel)
|
||||
newp = new AstSFormatF{fl, "%@.%@", false, argsp};
|
||||
newp = new AstSFormatF{fl, "%s.%s", false, argsp};
|
||||
else
|
||||
newp = new AstSFormatF{fl, "(select %@ %@)", false, argsp};
|
||||
newp = new AstSFormatF{fl, "(select %s %s)", false, argsp};
|
||||
nodep->replaceWith(newp);
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
return;
|
||||
|
|
@ -2200,7 +2200,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
cexprp->add("([&]{\nstd::string ret;\n");
|
||||
cexprp->add(new AstBegin{fl, "", new AstForeach{fl, headerp, cstmtp}, true});
|
||||
cexprp->add("return ret.empty() ? \"#b0\" : \"(bvor\" + ret + \")\";\n})()");
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2415,7 +2415,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
cexprp->add("([&]{\nstd::string ret = \"" + identity + "\";\n");
|
||||
cexprp->add(new AstBegin{fl, "", new AstForeach{fl, headerp, cstmtp}, true});
|
||||
cexprp->add("return ret;\n})()");
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
|
||||
} else {
|
||||
// For without 'with' clause: use AstCExpr with conditional
|
||||
AstCExpr* const cexprp = new AstCExpr{fl};
|
||||
|
|
@ -2424,7 +2424,7 @@ class ConstraintExprVisitor final : public VNVisitor {
|
|||
cexprp->add(new AstBegin{fl, "", new AstForeach{fl, headerp, cstmtp}, true});
|
||||
cexprp->add("return ret.empty() ? \"" + identity + "\" : \"(" + smtOp
|
||||
+ "\" + ret + \")\";\n})()");
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%@", false, cexprp});
|
||||
nodep->replaceWith(new AstSFormatF{fl, "%s", false, cexprp});
|
||||
}
|
||||
VL_DO_DANGLING(nodep->deleteTree(), nodep);
|
||||
return;
|
||||
|
|
|
|||
103
src/V3Simulate.h
103
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};
|
||||
|
|
|
|||
294
src/V3Width.cpp
294
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -4232,11 +4232,11 @@ system_t_stmt_call<nodeStmtp>: // IEEE: part of system_tf_call (as task returni
|
|||
| yD_STOP parenE { $$ = new AstStop{$1, false}; }
|
||||
| yD_STOP '(' expr ')' { $$ = new AstStop{$1, false}; DEL($3); }
|
||||
//
|
||||
| yD_SFORMAT '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5}; }
|
||||
| yD_SWRITE '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5}; }
|
||||
| yD_SWRITEB '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5, 'b'}; }
|
||||
| yD_SWRITEH '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5, 'h'}; }
|
||||
| yD_SWRITEO '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, $3, $5, 'o'}; }
|
||||
| yD_SFORMAT '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, false, $3, $5}; }
|
||||
| yD_SWRITE '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'd'}; }
|
||||
| yD_SWRITEB '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'b'}; }
|
||||
| yD_SWRITEH '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'h'}; }
|
||||
| yD_SWRITEO '(' expr ',' exprDispList ')' { $$ = new AstSFormat{$1, true, $3, $5, 'o'}; }
|
||||
//
|
||||
| yD_DISPLAY parenE { $$ = new AstDisplay{$1, VDisplayType::DT_DISPLAY, nullptr, nullptr}; }
|
||||
| yD_DISPLAY '(' commasE exprDispList ')' { $$ = new AstDisplay{$1, VDisplayType::DT_DISPLAY, nullptr, $4}; }
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 *-*
|
||||
|
|
@ -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()
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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": []}
|
||||
]}
|
||||
]}
|
||||
]}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue