From 9dfc050fb508f350ebaa9db055ab7c33df73ec82 Mon Sep 17 00:00:00 2001 From: Wilson Snyder Date: Mon, 13 Oct 2025 19:47:08 -0400 Subject: [PATCH] Fix '' with multiple format strings --- src/V3LinkResolve.cpp | 153 ++++++++++++++--------------------- test_regress/t/t_display.out | 5 ++ test_regress/t/t_display.v | 15 ++++ 3 files changed, 81 insertions(+), 92 deletions(-) diff --git a/src/V3LinkResolve.cpp b/src/V3LinkResolve.cpp index f2b97b849..83377eb1f 100644 --- a/src/V3LinkResolve.cpp +++ b/src/V3LinkResolve.cpp @@ -308,106 +308,75 @@ class LinkResolveVisitor final : public VNVisitor { bool inPct = false; bool inIgnore = false; string fmt; - for (const char ch : format) { - if (!inPct && ch == '%') { - inPct = true; - inIgnore = false; - fmt = ch; - } else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) { - fmt += ch; - } else if (inPct) { - inPct = false; - fmt += ch; - switch (std::tolower(ch)) { - case '%': // %% - just output a % - break; - case '*': + string parseFormat = format; + while (!parseFormat.empty() || argp) { + for (const char ch : parseFormat) { + if (!inPct && ch == '%') { inPct = true; - inIgnore = true; - break; - case 'm': // %m - auto insert "name" - if (isScan) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: %m in $fscanf"); - fmt = ""; - } - break; - case 'l': // %l - auto insert "library" - if (isScan) { - nodep->v3warn(E_UNSUPPORTED, "Unsupported: %l in $fscanf"); - fmt = ""; - } - if (m_modp) - fmt = AstNode::prettyName(m_modp->libname()) + "." + m_modp->prettyName(); - break; - default: // Most operators, just move to next argument - if (!V3Number::displayedFmtLegal(ch, isScan)) { - nodep->v3error("Unknown $display-like format code: '%" << ch << "'"); - } else if (!inIgnore) { - if (!argp) { - nodep->v3error("Missing arguments for $display-like format"); - } else { - argp = argp->nextp(); + inIgnore = false; + fmt = ch; + } else if (inPct && (std::isdigit(ch) || ch == '.' || ch == '-')) { + fmt += ch; + } else if (inPct) { + inPct = false; + fmt += ch; + switch (std::tolower(ch)) { + case '%': // %% - just output a % + break; + case '*': + inPct = true; + inIgnore = true; + break; + case 'm': // %m - auto insert "name" + if (isScan) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: %m in $fscanf"); + fmt = ""; } - } - break; - } // switch - newFormat += fmt; - } else { - newFormat += ch; - } - } - - if (argp && !isScan) { - int skipCount = 0; // number of args consume by any additional format strings - while (argp) { - if (skipCount) { - argp = argp->nextp(); - --skipCount; - continue; - } - const AstConst* const constp = VN_CAST(argp, Const); - const bool isFromString = (constp) ? constp->num().isFromString() : false; - if (isFromString) { - const int numchars = argp->dtypep()->width() / 8; - if (!constp->num().toString().empty()) { - string str(numchars, ' '); - // now scan for % operators - bool inpercent = false; - for (int i = 0; i < numchars; i++) { - const int ii = numchars - i - 1; - const char c = constp->num().dataByte(ii); - str[i] = c; - if (!inpercent && c == '%') { - inpercent = true; - } else if (inpercent) { - inpercent = false; - switch (c) { - 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 '.': inpercent = true; break; - case '%': break; - default: - if (V3Number::displayedFmtLegal(c, isScan)) ++skipCount; - } + break; + case 'l': // %l - auto insert "library" + if (isScan) { + nodep->v3warn(E_UNSUPPORTED, "Unsupported: %l in $fscanf"); + fmt = ""; + } + if (m_modp) + fmt = AstNode::prettyName(m_modp->libname()) + "." + + m_modp->prettyName(); + break; + default: // Most operators, just move to next argument + if (!V3Number::displayedFmtLegal(ch, isScan)) { + nodep->v3error("Unknown $display-like format code: '%" << ch << "'"); + } else if (!inIgnore) { + if (!argp) { + nodep->v3error("Missing arguments for $display-like format"); + } else { + argp = argp->nextp(); } } - newFormat.append(str); - } + break; + } // switch + newFormat += fmt; + } else { + newFormat += ch; + } + } + + // Find additional arguments (without format) or additional format strings + parseFormat = ""; + + if (isScan) break; + while (argp) { + const AstConst* const constp = VN_CAST(argp, Const); + const bool isFromString = (constp) ? constp->num().isFromString() : false; + if (!isFromString) { + newFormat.append("%?"); // V3Width to figure it out + argp = argp->nextp(); + } else { // New format string + parseFormat += constp->num().toString(); AstNode* const nextp = argp->nextp(); argp->unlinkFrBack(); VL_DO_DANGLING(pushDeletep(argp), argp); argp = nextp; - } else { - newFormat.append("%?"); // V3Width to figure it out - argp = argp->nextp(); + break; // And continue at top of parsing the new parseFormat } } } diff --git a/test_regress/t/t_display.out b/test_regress/t/t_display.out index 79cf4f30a..c9e9c744f 100644 --- a/test_regress/t/t_display.out +++ b/test_regress/t/t_display.out @@ -101,4 +101,9 @@ ZzX ZzXx XXXx 10 +[50] FIFTY 50 +[0] FIFTY 50 +[0] not-fmt %-d 60 +[0] fmt-as-string-not-%0x 70 +s=[0] fmt-string-not-%s *-* All Finished *-* diff --git a/test_regress/t/t_display.v b/test_regress/t/t_display.v index b3c39aaa1..cf6b9b90f 100644 --- a/test_regress/t/t_display.v +++ b/test_regress/t/t_display.v @@ -21,6 +21,9 @@ module t; reg [5:0] assoc_c[int]; + string s; + string not_fmt; + sub sub (); sub2 sub2 (); sub3 sub3 (); @@ -215,6 +218,18 @@ multiline", $time); $display(,, 10); // Strange but legal + // $sformat allows only single format while $display/write allow multiple + $display("[50] FIFTY 50"); + $display("[%0t]", $time, " FIFTY %-d", 50); + // This prints as %s, the %-d is not a format, as not_fmt is not literal + not_fmt = " not-fmt %-d"; + $display("[%0t]", $time, not_fmt, 60); + // This prints as %s as forces the literal to a string + $display("[%0t] %s", $time, " fmt-as-string-not-%0x", 70); + // Sformat takes only single format per IEEE + s = $sformatf("[%0t] %s", $time, " fmt-string-not-%s"); + $display("s=%s", s); + $write("*-* All Finished *-*\n"); $finish; end