Support printing enum names for %p and %s (#5523) (#7338 repair) (#7521) (#7527)

This commit is contained in:
Nick Brereton 2026-06-03 14:55:00 -04:00 committed by GitHub
parent 41811d436f
commit bbd8d927ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 381 additions and 22 deletions

View File

@ -1088,6 +1088,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
// Similar code flow in V3Number::displayed
int lbits = 0;
void* thingp = nullptr;
const std::string* enump = nullptr;
QData ld = 0;
std::vector<EData> strwide;
WDataInP lwp{nullptr};
@ -1108,6 +1109,34 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
} else if (formatAttr == VL_VFORMATATTR_STRING) {
thingp = va_arg(ap, std::string*);
if (fmt != 'p' && fmt != 'x') fmt = 's'; // Override
} else if (formatAttr == VL_VFORMATATTR_ENUM) {
// Always <= VL_QUADSIZE; emit uses non-ENUM format for wider enums
lbits = va_arg(ap, int);
ld = VL_VA_ARG_Q_(ap, lbits);
strwide.resize(2);
WDataOutP strwidep = WDataOutP::external(strwide.data());
VL_SET_WQ(strwidep, ld);
lwp = strwidep;
lsb = lbits - 1;
++argn; // Enum value is followed by the generated name string argument
static_cast<void>(va_arg(ap, int)); // VL_VFORMATATTR_STRING
enump = va_arg(ap, std::string*);
if (enump && !enump->empty()) {
formatAttr = (fmt == 'p') ? VL_VFORMATATTR_COMPLEX : VL_VFORMATATTR_STRING;
thingp = const_cast<std::string*>(enump);
} else if (fmt == 'p' && widthSet && width == 0) {
output += "'h";
fmt = 'h';
formatAttr = VL_VFORMATATTR_UNSIGNED;
} else {
if (fmt == 'p') width = 0;
widthSet = true;
fmt = 'd';
formatAttr = VL_VFORMATATTR_UNSIGNED;
}
if (widthSet && width == 0) {
while (lsb && !VL_BITISSET_W(lwp, lsb)) --lsb;
}
} else { // Numeric
lbits = va_arg(ap, int);
if (lbits <= VL_QUADSIZE) {
@ -1118,7 +1147,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc,
lwp = strwidep;
} else {
lwp = WDataInP::external(va_arg(ap, EData*));
ld = lwp[0];
ld = VL_SET_QW(lwp); // Low 64 bits, for %c/%t
}
if (fmt == 'p') {
if (widthSet && width == 0) { // For %0p, IEEE our choice, use 'h%0h

View File

@ -442,6 +442,7 @@ using ssize_t = uint32_t; ///< signed size_t; returned from read()
#define VL_VFORMATATTR_SIGNED '~' // (int widthMin, IData/VlWide/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_ENUM 'E' // (width, IData/QData, std::string* name); <= 64 bit enum with runtime %p/%s
#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

View File

@ -344,7 +344,7 @@ void EmitCFunc::displayNode(AstNode* nodep, AstSFormatF* fmtp, // fmtp is nullp
AstNode* const subargp = fargp ? fargp->exprp() : argp;
const VFormatAttr formatAttr = AstSFormatArg::formatAttrDefauled(fargp, subargp->dtypep());
puts(", '"s + formatAttr.ascii() + '\'');
if (formatAttr.isSigned() || formatAttr.isUnsigned())
if (formatAttr.isSigned() || formatAttr.isUnsigned() || formatAttr.isEnum())
puts("," + cvtToStr(subargp->widthMin()));
const bool addrof = isScan || formatAttr.isString() || formatAttr.isComplex();
puts(",");

View File

@ -46,6 +46,7 @@ public:
//
COMPLEX = VL_VFORMATATTR_COMPLEX,
DOUBLE = VL_VFORMATATTR_DOUBLE,
ENUM = VL_VFORMATATTR_ENUM,
SCOPE = VL_VFORMATATTR_SCOPE,
STRING = VL_VFORMATATTR_STRING,
TIMEUNIT = VL_VFORMATATTR_TIMEUNIT
@ -62,6 +63,7 @@ public:
char ascii() const { return m_e; }
bool isComplex() const { return m_e == COMPLEX; }
bool isDouble() const { return m_e == DOUBLE; }
bool isEnum() const { return m_e == ENUM; }
bool isSigned() const { return m_e == SIGNED; }
bool isString() const { return m_e == STRING; }
bool isUnsigned() const { return m_e == UNSIGNED; }

View File

@ -6505,7 +6505,17 @@ class WidthVisitor final : public VNVisitor {
AstNodeExpr* const newp = new AstToStringN{argp->fileline(), argp};
formatAttr = VFormatAttr::COMPLEX;
argp = newp;
} else if (dtypep->isSigned()) {
} else if (nodep->exprFormat()) {
if (AstEnumDType* const enumDtp = formatEnumDType(argp)) {
nodep->addExprsp(new AstSFormatArg{argp->fileline(), VFormatAttr::ENUM, argp});
AstNodeExpr* const namep
= enumSelect(argp->cloneTreePure(false), enumDtp, VAttrType::ENUM_NAME);
nodep->addExprsp(
new AstSFormatArg{namep->fileline(), VFormatAttr::STRING, namep});
continue;
}
}
if (formatAttr.isUnsigned() && dtypep->isSigned()) {
formatAttr = VFormatAttr::SIGNED;
}
if (VN_IS(argp, SFormatArg) // Already done
@ -8387,13 +8397,16 @@ class WidthVisitor final : public VNVisitor {
// For sformatf's with constant format, iterate/check arguments
UASSERT_OBJ(!nodep->exprFormat(), nodep, "Assumes constant format");
bool inPct = false;
string fmtMods;
AstNodeExpr* argp = nodep->exprsp();
string newFormat;
for (char ch : nodep->text()) {
if (!inPct && ch == '%') {
inPct = true;
fmtMods = "";
newFormat += ch;
} else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) {
fmtMods += ch;
newFormat += ch;
} else if (!inPct) { // Normal text
newFormat += ch;
@ -8401,7 +8414,7 @@ class WidthVisitor final : public VNVisitor {
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;
AstNodeExpr* subargp = fargp ? fargp->exprp() : argp;
const AstNodeDType* const dtypep
= subargp ? subargp->dtypep()->skipRefp() : nullptr;
ch = std::tolower(ch);
@ -8458,7 +8471,34 @@ class WidthVisitor final : public VNVisitor {
}
break;
case 'p': // FALLTHRU
case 's': // FALLTHRU
case 's':
// As with enum.name(): valid values print the mnemonic, else numeric
if (subargp) {
if (AstEnumDType* const enumDtp = formatEnumDType(subargp)) {
string fallbackFormat = "%0d";
if (ch == 'p') {
bool widthSet = false;
size_t width = 0;
for (const char mod : fmtMods) {
if (!std::isdigit(mod)) continue;
widthSet = true;
width = width * 10 + (mod - '0');
}
if (widthSet && width == 0) fallbackFormat = "'h%0h";
}
AstNodeExpr* const newp = new AstCond{
subargp->fileline(), enumTestValid(subargp, enumDtp),
enumSelect(subargp->cloneTreePure(false), enumDtp,
VAttrType::ENUM_NAME),
new AstSFormatF{subargp->fileline(), fallbackFormat, true,
subargp->cloneTreePure(false)}};
subargp->replaceWith(new AstSFormatArg{subargp->fileline(),
VFormatAttr::COMPLEX, newp});
VL_DO_DANGLING(pushDeletep(subargp), subargp);
}
}
argp = nextp;
break;
default: // Most operators, just move to next argument
argp = nextp;
break;
@ -8469,6 +8509,18 @@ class WidthVisitor final : public VNVisitor {
nodep->text(newFormat);
}
static AstEnumDType* formatEnumDType(AstNodeExpr* subargp) {
AstEnumDType* enumDtp = VN_CAST(subargp->dtypep()->skipRefToEnump(), EnumDType);
if (!enumDtp) {
if (const AstVarRef* const varrefp = VN_CAST(subargp, VarRef)) {
enumDtp = VN_CAST(varrefp->varp()->dtypep()->skipRefToEnump(), EnumDType);
}
}
// Enums > 64 bits have no name table (see enumMaxValue); format as plain numbers
if (enumDtp && enumDtp->width() > VL_QUADSIZE) return nullptr;
return enumDtp;
}
//----------------------------------------------------------------------
// LOWER LEVEL WIDTH METHODS (none iterate)

View File

@ -4,6 +4,11 @@
// SPDX-FileCopyrightText: 2020 Wilson Snyder
// SPDX-License-Identifier: CC0-1.0
// 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);
// verilog_format: on
module t;
class Cls;
@ -12,11 +17,32 @@ module t;
B = 20,
C = 30
} en_t;
en_t en;
endclass
class WideCls;
typedef enum logic [95:0] {
A = 96'h1,
B = 96'h2
} en_t;
en_t en;
endclass
initial begin
Cls c;
WideCls w;
string s;
if (c.A != 10) $stop;
c = new;
c.en = c.B;
if (c.en != 20) $stop;
s = $sformatf("%p", c);
`checks(s, "'{en:'h14}");
w = new;
w.en = w.B;
s = $sformatf("%p", w);
`checks(s, "'{en:'h2}");
$write("*-* All Finished *-*\n");
$finish;
end

View File

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

View File

@ -0,0 +1,199 @@
// DESCRIPTION: Verilator: Verilog Test module
//
// 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
module t (
input string empty_no_opt
);
typedef enum logic [1:0] {
E0 = 0,
E1 = 1,
E2 = 2
} my_e;
typedef enum logic [63:0] {
W64A = 64'h1,
W64B = 64'h0000_0001_0000_0001
} wide64_e;
// Enums > 64 bits are beyond enum.name() support, so %p/%s format numerically
typedef enum logic [95:0] {
W96A = 96'h1,
W96B = 96'hA_0000_0000_0000_0001
} wide96_e;
typedef logic signed [4095:0] uvm_bitstream_t;
my_e e;
wide64_e e64;
wide96_e e96;
logic [63:0] n64;
uvm_bitstream_t bitstream_value;
`define check(got, exp) do if ((got) != (exp)) begin \
$write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__, `__LINE__, got, exp); \
$stop; \
end while(0)
initial begin
string fmt;
begin
my_e it;
string names_p;
string names_s;
string vals_d;
names_p = "";
names_s = "";
vals_d = "";
for (it = it.first; ; it = it.next) begin
if (names_p != "") begin
names_p = {names_p, ","};
names_s = {names_s, ","};
vals_d = {vals_d, ","};
end
names_p = {names_p, $sformatf("%p", it)};
names_s = {names_s, $sformatf("%s", it)};
vals_d = {vals_d, $sformatf("%0d", it)};
if (it == it.last) break;
end
`check(names_p, "E0,E1,E2");
`check(names_s, "E0,E1,E2");
`check(vals_d, "0,1,2");
end
// Valid enum values print mnemonic for %p/%s.
e = E0;
`check($sformatf("%p", e), "E0");
`check($sformatf("%s", e), "E0");
e = E1;
`check($sformatf("%p", e), "E1");
`check($sformatf("%P", e), "E1");
`check($sformatf("%0p", e), "E1");
`check($sformatf("%s", e), "E1");
`check($sformatf("%S", e), "E1");
`check($sformatf("%d", e), "1");
`check($sformatf("%0d", e), "1");
`check($sformatf("%h", e), "1");
`check($sformatf("%0h", e), "1");
`check($sformatf("%b", e), "01");
`check($sformatf("%0b", e), "1");
`check($sformatf("%o", e), "1");
`check($sformatf("%0o", e), "1");
`check($sformatf("%x", e), "1");
`check($sformatf("%0x", e), "1");
e = E2;
`check($sformatf("%p", e), "E2");
`check($sformatf("%s", e), "E2");
`check($sformatf("%s|%p", e, e), "E2|E2");
`check($sformatf("%4p", e), "E2");
`check($sformatf("%-4p", e), "E2");
`check($sformatf("%d", e), "2");
`check($sformatf("%h", e), "2");
`check($sformatf("%b", e), "10");
`check($sformatf("%0b", e), "10");
`check($sformatf("%o", e), "2");
`check($sformatf("%x", e), "2");
`check($sformatf("%4d", e), " 2");
`check($sformatf("%04d", e), "0002");
`check($sformatf("%4h", e), "0002");
`check($sformatf("%-4s", e), "E2 ");
`check($sformatf("%4s", e), " E2");
// `%p`/`%s` in non-terminal positions with mixed formatters.
`check($sformatf("%0d:%s:%0d", 9, e, 7), "9:E2:7");
`check($sformatf("%s %h %p", e, 4'hA, e), "E2 a E2");
`check($sformatf("pre %% %s post", e), "pre % E2 post");
// Complex enum expressions (non-var-ref) in format args.
`check($sformatf("%s", (1'b1 ? E2 : E0)), "E2");
// 64-bit enums should preserve bits above 32 in both named and numeric cases.
e64 = W64B;
`check($sformatf("%p", e64), "W64B");
`check($sformatf("%s", e64), "W64B");
e64 = wide64_e'(64'h0000_0002_0000_0001);
`check($sformatf("%p", e64), "8589934593");
`check($sformatf("%s", e64), "8589934593");
n64 = 64'h0000_0000_0000_0001;
`check($sformatf("%0p", n64), "'h1");
// > 64-bit enums print numerically for %p (no name table support)
e96 = W96B; // 10 * 2**64 + 1
if (empty_no_opt != "") e96 = W96A; // Defeat constant folding
`check($sformatf("%p", e96), "184467440737095516161");
`check($sformatf("%0p", e96), "'ha0000000000000001");
`check($sformatf("%0d", e96), "184467440737095516161");
`check($sformatf("%0h", e96), "a0000000000000001");
// Exercise display/write-family formatting path in addition to $sformatf checks.
$display("display-valid:%s:%0d:%p", e, 7, e);
$write("write-valid:%s:%0d:%p\n", e, 8, e);
// Invalid enum values fall back to numeric formatting for %p/%s.
e = my_e'(3);
`check($sformatf("%p", e), "3");
`check($sformatf("%P", e), "3");
`check($sformatf("%0p", e), "'h3");
`check($sformatf("%s", e), "3");
`check($sformatf("%S", e), "3");
`check($sformatf("%4p", e), "3");
`check($sformatf("%4s", e), " 3");
`check($sformatf("%d", e), "3");
`check($sformatf("%0d", e), "3");
`check($sformatf("%h", e), "3");
`check($sformatf("%0h", e), "3");
`check($sformatf("%b", e), "11");
`check($sformatf("%0b", e), "11");
`check($sformatf("%o", e), "3");
`check($sformatf("%x", e), "3");
// Non-terminal invalid-value fallback with mixed formatters.
`check($sformatf("%0d:%p:%0d", 9, e, 7), "9:3:7");
`check($sformatf("%s %h %p", e, 4'hA, e), "3 a 3");
`check($sformatf("pre %% %s post", e), "pre % 3 post");
`check($sformatf("%s|%p", e, e), "3|3");
`check($sformatf("%s", (1'b1 ? my_e'(3) : E0)), "3");
`check($sformatf("%p", (1'b0 ? E0 : my_e'(3))), "3");
$display("display-invalid:%s:%0d:%p", e, 7, e);
$write("write-invalid:%s:%0d:%p\n", e, 8, e);
// Runtime-computed $sformatf formats should preserve enum mnemonic/fallback behavior.
e = E2;
fmt = {"%", "s", empty_no_opt};
`check($sformatf(fmt, e), "E2");
fmt = {"%", "p", empty_no_opt};
`check($sformatf(fmt, e), "E2");
fmt = {"%0d:%", "s", ":%0d", empty_no_opt};
`check($sformatf(fmt, 9, e, 7), "9:E2:7");
fmt = {"%", "s", " %h %", "p", empty_no_opt};
`check($sformatf(fmt, e, 4'hA, e), "E2 a E2");
e = my_e'(3);
fmt = {"%", "s", empty_no_opt};
`check($sformatf(fmt, e), "3");
fmt = {"%", "p", empty_no_opt};
`check($sformatf(fmt, e), "3");
fmt = {"%0", "p", empty_no_opt};
`check($sformatf(fmt, e), "'h3");
fmt = {"%0d:%", "s", ":%0d", empty_no_opt};
`check($sformatf(fmt, 9, e, 7), "9:3:7");
fmt = {"%", "s", " %h %", "p", empty_no_opt};
`check($sformatf(fmt, e, 4'hA, e), "3 a 3");
fmt = {"%", "p", empty_no_opt};
`check($sformatf(fmt, e64), "8589934593");
// > 64-bit enums use the non-ENUM format in runtime formats too
fmt = {"%", "p", empty_no_opt};
`check($sformatf(fmt, e96), "184467440737095516161");
fmt = {"%0d", empty_no_opt};
`check($sformatf(fmt, e96), "184467440737095516161");
bitstream_value = 30;
`check($sformatf("%0s%0t", "", bitstream_value), "30");
bitstream_value = '0;
bitstream_value[32] = 1'b1;
`check($sformatf("%0s%0t", "", bitstream_value), "4294967296");
bitstream_value = '0;
bitstream_value[63:0] = 64'h0000_0001_0000_0001;
`check($sformatf("%0s%0t", "", bitstream_value), "4294967297");
bitstream_value[7:0] = "A";
// verilator lint_off WIDTHTRUNC
`check($sformatf("%c", bitstream_value), "A");
// verilator lint_on WIDTHTRUNC
$write("*-* All Finished *-*\n");
$finish;
end
endmodule

View File

@ -22,7 +22,7 @@ module t (
integer cyc = 0;
my_t e;
string all;
string s;
int i_cast;
// Check runtime
@ -45,6 +45,10 @@ module t (
`checkh(e.prev, E01);
`checkh(e.next(0), ELARGE);
`checkh(e.prev(0), ELARGE);
s = $sformatf("%p", e);
`checks(s, "ELARGE");
s = $sformatf("%s", e); // Non-standard but majority
`checks(s, "ELARGE");
e <= E01;
end
//
@ -68,6 +72,8 @@ module t (
end
else if (cyc == 21) begin
`checks(e.name, ""); // Unknown
s = $sformatf("%p", e);
`checks(s, "17");
end
else if (cyc == 99) begin
$write("*-* All Finished *-*\n");

View File

@ -22,7 +22,7 @@ module t (
integer cyc = 0;
my_t e;
string all;
string s;
// Check runtime
always @(posedge clk) begin
@ -40,6 +40,8 @@ module t (
`checks(e.name, "ELARGE");
`checkh(e.next, E01);
`checkh(e.prev, E01);
s = $sformatf("%p", e);
`checks(s, "ELARGE");
e <= E01;
end
else if (cyc == 20) begin
@ -47,6 +49,8 @@ module t (
end
else if (cyc == 21) begin
`checks(e.name, ""); // Unknown
s = $sformatf("%p", e);
`checks(s, "17");
end
else if (cyc == 99) begin
$write("*-* All Finished *-*\n");

View File

@ -11,8 +11,8 @@
// verilog_format: on
module t (
input clk
);
input clk
);
typedef enum [3:0] {
E01 = 1,
@ -20,12 +20,18 @@ module t (
E04 = 4
} my_t;
integer cyc = 0;
integer cyc = 0;
my_t e;
int arrayfits [e.num]; // Check can use as constant
int arrayfits[e.num]; // Check can use as constant
string all;
typedef struct {
my_t m_a;
my_t m_b;
} mystr_t;
mystr_t mystr;
string s;
// Check constification
initial begin
@ -48,25 +54,38 @@ module t (
`checkh(e.num, 3);
`checks(e.name, "E03");
//
all = "";
s = "";
for (my_t e = e.first; e != e.last; e = e.next) begin
all = {all, e.name};
s = {s, e.name};
end
e = e.last;
all = {all, e.name};
`checks(all, "E01E03E04");
s = {s, e.name};
`checks(s, "E01E03E04");
//
e = E04;
s = $sformatf("%p", e);
`checks(s, "E04");
s = $sformatf("%p", E03);
`checks(s, "E03");
s = $sformatf("%s", E03); // Non-standard but majority
`checks(s, "E03");
//
mystr.m_a = E03;
mystr.m_b = E04;
s = $sformatf("%p", mystr);
`checks(s, "'{m_a:'h3, m_b:'h4}");
end
localparam THREE = 3;
// Check runtime
always @ (posedge clk) begin
always @(posedge clk) begin
cyc <= cyc + 1;
if (cyc==0) begin
if (cyc == 0) begin
// Setup
e <= E01;
end
else if (cyc==1) begin
else if (cyc == 1) begin
`checks(e.name, "E01");
`checkh(e.next, E03);
`checkh(e.next(1), E03);
@ -76,7 +95,7 @@ module t (
`checkh(e.prev(2), E03);
e <= E03;
end
else if (cyc==2) begin
else if (cyc == 2) begin
`checks(e.name, "E03");
`checkh(e.next, E04);
`checkh(e.next(1), E04);
@ -86,7 +105,7 @@ module t (
`checkh(e.prev(2), E04);
e <= E04;
end
else if (cyc==3) begin
else if (cyc == 3) begin
`checks(e.name, "E04");
`checkh(e.next, E01);
`checkh(e.next(1), E01);
@ -95,8 +114,11 @@ module t (
`checkh(e.prev(1), E03);
`checkh(e.prev(2), E01);
e <= E01;
//
s = $sformatf("%p", e);
`checks(s, "E04");
end
else if (cyc==99) begin
else if (cyc == 99) begin
$write("*-* All Finished *-*\n");
$finish;
end