diff --git a/include/verilated.cpp b/include/verilated.cpp index f1565a506..ece3347ff 100644 --- a/include/verilated.cpp +++ b/include/verilated.cpp @@ -1282,7 +1282,7 @@ void _vl_vsformat(std::string& output, const std::string& format, int argc, output += t_tmp; } else if (formatAttr == VL_VFORMATATTR_STRING) { const std::string* const strp = static_cast(thingp); - output += '"' + *strp + '"'; + output += VL_TO_STRING(*strp); } else if (formatAttr == VL_VFORMATATTR_COMPLEX) { const std::string* const strp = static_cast(thingp); output += *strp; @@ -2488,6 +2488,30 @@ std::string VL_TO_STRING(QData lhs) { std::string VL_TO_STRING(double lhs) { return VL_SFORMATF_N_NX("%g", 1, VL_VFORMATATTR_DOUBLE, lhs); } +std::string VL_TO_STRING(const std::string& obj) VL_PURE { + std::string out{"\""}; + out.reserve(obj.size() + 2); + for (const unsigned char ch : obj) { + switch (ch) { + case '\n': out += "\\n"; break; + case '\r': out += "\\r"; break; + case '\t': out += "\\t"; break; + case '"': out += "\\\""; break; + case '\\': out += "\\\\"; break; + default: + if (std::isprint(ch)) { + out += static_cast(ch); + } else { + out += '\\'; + out += static_cast('0' + ((ch >> 6) & 3)); + out += static_cast('0' + ((ch >> 3) & 7)); + out += static_cast('0' + (ch & 7)); + } + break; + } + } + return out + '"'; +} std::string VL_TO_STRING_W(int words, const WDataInP obj) { return VL_SFORMATF_N_NX("'h%0x", 1, VL_VFORMATATTR_UNSIGNED, words * VL_EDATASIZE, obj); } diff --git a/include/verilated_types.h b/include/verilated_types.h index 76a24d01b..dd2d25d99 100644 --- a/include/verilated_types.h +++ b/include/verilated_types.h @@ -231,7 +231,7 @@ extern std::string VL_TO_STRING(SData lhs); extern std::string VL_TO_STRING(IData lhs); extern std::string VL_TO_STRING(QData lhs); extern std::string VL_TO_STRING(double lhs); -inline std::string VL_TO_STRING(const std::string& obj) { return "\"" + obj + "\""; } +extern std::string VL_TO_STRING(const std::string& obj) VL_PURE; template inline std::string VL_TO_STRING(const VlWide& obj) { return VL_TO_STRING_W(N_Words, obj); diff --git a/src/V3Number.cpp b/src/V3Number.cpp index 17eaac079..62d0a60c4 100644 --- a/src/V3Number.cpp +++ b/src/V3Number.cpp @@ -754,7 +754,8 @@ string V3Number::displayed(FileLine* fl, const string& vformat, (void)VL_SNPRINTF(tmp, MAX_SPRINTF_DOUBLE_SIZE, "%g", n); return tmp; } - if (formatAttr.isString()) return '"' + toString() + '"'; + if (formatAttr.isString()) + return '"' + V3OutFormatter::quoteNameControls(toString()) + '"'; if (formatAttr.isComplex()) return toString(); return "%p"; } diff --git a/test_regress/t/t_display_pattern_format.py b/test_regress/t/t_display_pattern_format.py new file mode 100755 index 000000000..a16833e52 --- /dev/null +++ b/test_regress/t/t_display_pattern_format.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python3 +# DESCRIPTION: Verilator: Verilog Test driver/expect definition +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of either the GNU Lesser General Public License Version 3 +# or the Perl Artistic License Version 2.0. +# SPDX-FileCopyrightText: 2026 Wilson Snyder +# SPDX-License-Identifier: LGPL-3.0-only OR Artistic-2.0 + +import vltest_bootstrap + +test.scenarios('simulator_st') + +test.compile(verilator_flags2=['--binary']) + +test.execute() + +test.passes() diff --git a/test_regress/t/t_display_pattern_format.v b/test_regress/t/t_display_pattern_format.v new file mode 100644 index 000000000..fd6a03b06 --- /dev/null +++ b/test_regress/t/t_display_pattern_format.v @@ -0,0 +1,59 @@ +// 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 + +// 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; + bit clk = 0; + always #5 clk = ~clk; + int cyc = 0; + typedef string text_t; + + localparam text_t TEXT_PARAM = "quote=\" slash=\\ bell=\a form=\f vert=\v ctrl=\001"; + localparam string ESCAPED_PARAM_STRING = $sformatf("%p", TEXT_PARAM); + + initial begin +`ifdef QUESTA + // Questa 2025.2 does not escape strings as required by IEEE 1800-2012 21.2.1.7. + `checks(ESCAPED_PARAM_STRING, {"\"", TEXT_PARAM, "\""}); +`else + `checks(ESCAPED_PARAM_STRING, + "\"quote=\\\" slash=\\\\ bell=\\007 form=\\014 vert=\\013 ctrl=\\001\""); +`endif + end + + always @(posedge clk) begin + text_t plain; + string escaped; + string escaped_expected; + string fmt; + + plain = $sformatf("round %0d", cyc); + escaped = {"quote=\" slash=\\ line=\n cr=\r tab=\t bell=\a form=\f vert=\v ctrl=\001 ", plain}; +`ifdef QUESTA + escaped_expected = {"\"", escaped, "\""}; +`else + escaped_expected = {"\"quote=\\\" slash=\\\\ line=\\n cr=\\r tab=\\t bell=\\007 ", + "form=\\014 vert=\\013 ctrl=\\001 ", plain, "\""}; +`endif + `checks($sformatf("%p", plain), {"\"", plain, "\""}); + `checks($sformatf("%p", escaped), escaped_expected); + `checks($sformatf("%s", escaped), escaped); + fmt = cyc[0] ? "%p" : "%P"; + `checks($sformatf(fmt, escaped), escaped_expected); + plain = ""; + `checks($sformatf("%p", plain), "\"\""); + + cyc <= cyc + 1; + if (cyc == 3) begin + $write("*-* All Finished *-*\n"); + $finish; + end + end +endmodule