This commit is contained in:
Nick Brereton 2026-04-06 08:57:15 -07:00 committed by GitHub
commit f270402871
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 193 additions and 2 deletions

View File

@ -8111,13 +8111,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;
@ -8125,7 +8128,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);
@ -8182,7 +8185,50 @@ class WidthVisitor final : public VNVisitor {
}
break;
case 'p': // FALLTHRU
case 's': // FALLTHRU
case 's':
// Keep enum `%p`/`%s` behavior aligned with enum.name():
// valid enum values print the mnemonic; invalid values print numeric fallback.
if (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);
}
}
if (enumDtp) {
string fallbackFormat = "%0d";
if (ch == 'p') {
bool widthSet = false;
size_t width = 0;
for (const char mod : fmtMods) {
if (std::isdigit(mod)) {
widthSet = true;
width = width * 10 + (mod - '0');
}
}
if (widthSet && width == 0) fallbackFormat = "'h%0h";
}
AstNodeExpr* const newp = new AstCond{
subargp->fileline(),
enumTestValid(subargp->cloneTreePure(false), enumDtp),
enumSelect(subargp->cloneTreePure(false), enumDtp,
VAttrType::ENUM_NAME),
new AstSFormatF{subargp->fileline(), fallbackFormat, true,
subargp->cloneTreePure(false)}};
if (fargp) {
fargp->formatAttr(VFormatAttr::COMPLEX);
subargp->replaceWith(newp);
} else {
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;

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,127 @@
// 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;
typedef enum logic [1:0] {
E0 = 0,
E1 = 1,
E2 = 2
} my_e;
my_e e;
task check(input string got, input string exp);
if (got != exp) begin
$write("%%Error: %s:%0d: got='%s' exp='%s'\n", `__FILE__, `__LINE__, got, exp);
$stop;
end
endtask
initial begin
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");
// 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);
$write("*-* All Finished *-*\n");
$finish;
end
endmodule