From 0b6a22066405a28a20b0ed6f6ef87d66d9f46f2e Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 28 Mar 2024 06:30:16 +0000 Subject: [PATCH 01/10] =?UTF-8?q?fmt:=20`FmtPart::{STRING=E2=86=92LITERAL}?= =?UTF-8?q?,{CHARACTER=E2=86=92STRING}`.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this commit, the `STRING` variant inserted a literal string; the `CHARACTER` variant inserted a string. This commit renames them to `LITERAL` and `STRING` respectively. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 14 ++++---- frontends/ast/genrtlil.cc | 2 +- frontends/ast/simplify.cc | 2 +- kernel/fmt.cc | 44 ++++++++++++------------- kernel/fmt.h | 12 +++---- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index b834cd12019..5d5c54d1ac8 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1010,19 +1010,19 @@ struct observer { // Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. struct fmt_part { enum { - STRING = 0, + LITERAL = 0, INTEGER = 1, - CHARACTER = 2, + STRING = 2, VLOG_TIME = 3, } type; - // STRING type + // LITERAL type std::string str; - // INTEGER/CHARACTER types + // INTEGER/STRING types // + value val; - // INTEGER/CHARACTER/VLOG_TIME types + // INTEGER/STRING/VLOG_TIME types enum { RIGHT = 0, LEFT = 1, @@ -1050,10 +1050,10 @@ struct fmt_part { // chunk access if it turns out to be slow enough to matter. std::string buf; switch (type) { - case STRING: + case LITERAL: return str; - case CHARACTER: { + case STRING: { buf.reserve(Bits/8); for (int i = 0; i < Bits; i += 8) { char ch = 0; diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index fe67f00c692..bc7f1ddd1d6 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -790,7 +790,7 @@ struct AST_INTERNAL::ProcessGenerator Fmt fmt; fmt.parse_verilog(args, /*sformat_like=*/false, default_base, /*task_name=*/ast->str, current_module->name); if (ast->str.substr(0, 8) == "$display") - fmt.append_string("\n"); + fmt.append_literal("\n"); fmt.emit_rtlil(cell); } else if (!ast->str.empty()) { log_file_error(ast->filename, ast->location.first_line, "Found unsupported invocation of system task `%s'!\n", ast->str.c_str()); diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 43a4e03a29d..3d8478ef160 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -1079,7 +1079,7 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin // when $display()/$write() functions are used in an initial block, print them during synthesis Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true); if (str.substr(0, 8) == "$display") - fmt.append_string("\n"); + fmt.append_literal("\n"); log("%s", fmt.render().c_str()); } diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 18eb7cb7193..336e3e2ab75 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -22,9 +22,9 @@ USING_YOSYS_NAMESPACE -void Fmt::append_string(const std::string &str) { +void Fmt::append_literal(const std::string &str) { FmtPart part = {}; - part.type = FmtPart::STRING; + part.type = FmtPart::LITERAL; part.str = str; parts.push_back(part); } @@ -46,7 +46,7 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { log_assert(false && "Unexpected '}' in format string"); else if (fmt[i] == '{') { if (!part.str.empty()) { - part.type = FmtPart::STRING; + part.type = FmtPart::LITERAL; parts.push_back(part); part = {}; } @@ -108,7 +108,7 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { part.type = FmtPart::INTEGER; part.base = 16; } else if (fmt[i] == 'c') { - part.type = FmtPart::CHARACTER; + part.type = FmtPart::STRING; } else if (fmt[i] == 't') { part.type = FmtPart::VLOG_TIME; } else if (fmt[i] == 'r') { @@ -150,7 +150,7 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { } } if (!part.str.empty()) { - part.type = FmtPart::STRING; + part.type = FmtPart::LITERAL; parts.push_back(part); } } @@ -161,7 +161,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { for (auto &part : parts) { switch (part.type) { - case FmtPart::STRING: + case FmtPart::LITERAL: for (char c : part.str) { if (c == '{') fmt += "{{"; @@ -175,7 +175,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { case FmtPart::VLOG_TIME: log_assert(part.sig.size() == 0); YS_FALLTHROUGH - case FmtPart::CHARACTER: + case FmtPart::STRING: log_assert(part.sig.size() % 8 == 0); YS_FALLTHROUGH case FmtPart::INTEGER: @@ -203,7 +203,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { if (part.plus) fmt += '+'; fmt += part.signed_ ? 's' : 'u'; - } else if (part.type == FmtPart::CHARACTER) { + } else if (part.type == FmtPart::STRING) { fmt += 'c'; } else if (part.type == FmtPart::VLOG_TIME) { if (part.realtime) @@ -299,12 +299,12 @@ void Fmt::apply_verilog_automatic_sizing_and_add(FmtPart &part) part.width = places; if (part.justify == FmtPart::RIGHT) { - append_string(gap); + append_literal(gap); parts.push_back(part); } else { part.justify = FmtPart::RIGHT; parts.push_back(part); - append_string(gap); + append_literal(gap); } } } @@ -355,7 +355,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik part.str += module_name.str(); } else { if (!part.str.empty()) { - part.type = FmtPart::STRING; + part.type = FmtPart::LITERAL; parts.push_back(part); part = {}; } @@ -408,11 +408,11 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik part.type = FmtPart::INTEGER; part.base = 16; } else if (fmt[i] == 'c' || fmt[i] == 'C') { - part.type = FmtPart::CHARACTER; + part.type = FmtPart::STRING; part.sig.extend_u0(8); // %10c and %010c not fully defined in IEEE 1800-2017 and do different things in iverilog } else if (fmt[i] == 's' || fmt[i] == 'S') { - part.type = FmtPart::CHARACTER; + part.type = FmtPart::STRING; if ((part.sig.size() % 8) != 0) part.sig.extend_u0((part.sig.size() + 7) / 8 * 8); // %10s and %010s not fully defined in IEEE 1800-2017 and do the same thing in iverilog @@ -449,12 +449,12 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik } } if (!part.str.empty()) { - part.type = FmtPart::STRING; + part.type = FmtPart::LITERAL; parts.push_back(part); } } else { FmtPart part = {}; - part.type = FmtPart::STRING; + part.type = FmtPart::LITERAL; part.str = arg->str; parts.push_back(part); } @@ -474,7 +474,7 @@ std::vector Fmt::emit_verilog() const for (auto &part : parts) { switch (part.type) { - case FmtPart::STRING: + case FmtPart::LITERAL: for (char c : part.str) { if (c == '%') fmt.str += "%%"; @@ -513,7 +513,7 @@ std::vector Fmt::emit_verilog() const break; } - case FmtPart::CHARACTER: { + case FmtPart::STRING: { VerilogFmtArg arg; arg.type = VerilogFmtArg::INTEGER; arg.sig = part.sig; @@ -599,9 +599,9 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function parts; - void append_string(const std::string &str); + void append_literal(const std::string &str); void parse_rtlil(const RTLIL::Cell *cell); void emit_rtlil(RTLIL::Cell *cell) const; From 27060ab9f7668b2b9e6287615a921756412545db Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 28 Mar 2024 06:13:35 +0000 Subject: [PATCH 02/10] fmt,cxxrtl: add support for uppercase hex format. This is necessary for translating Python format strings in Amaranth. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 3 ++- kernel/fmt.cc | 11 ++++++++--- kernel/fmt.h | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 5d5c54d1ac8..d6378fdd625 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1034,6 +1034,7 @@ struct fmt_part { unsigned base; // = 10; bool signed_; // = false; bool plus; // = false; + bool hex_upper; // = false; // VLOG_TIME type bool realtime; // = false; @@ -1085,7 +1086,7 @@ struct fmt_part { uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); if (step == 4) value |= val.bit(index + 3) << 3; - buf += "0123456789abcdef"[value]; + buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value]; } std::reverse(buf.begin(), buf.end()); } else if (base == 10) { diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 336e3e2ab75..6fdc18ad0a9 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -107,6 +107,10 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { } else if (fmt[i] == 'h') { part.type = FmtPart::INTEGER; part.base = 16; + } else if (fmt[i] == 'H') { + part.type = FmtPart::INTEGER; + part.base = 16; + part.hex_upper = true; } else if (fmt[i] == 'c') { part.type = FmtPart::STRING; } else if (fmt[i] == 't') { @@ -197,7 +201,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { case 2: fmt += 'b'; break; case 8: fmt += 'o'; break; case 10: fmt += 'd'; break; - case 16: fmt += 'h'; break; + case 16: fmt += part.hex_upper ? 'H' : 'h'; break; default: log_abort(); } if (part.plus) @@ -507,7 +511,7 @@ std::vector Fmt::emit_verilog() const case 2: fmt.str += 'b'; break; case 8: fmt.str += 'o'; break; case 10: fmt.str += 'd'; break; - case 16: fmt.str += 'h'; break; + case 16: fmt.str += 'h'; break; // treat uppercase hex as lowercase default: log_abort(); } break; @@ -617,6 +621,7 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function Date: Thu, 28 Mar 2024 06:59:23 +0000 Subject: [PATCH 03/10] fmt,cxxrtl: support `{,PLUS_,SPACE_}MINUS` integer formats. The first two were already supported with the `plus` boolean flag. The third one is a new specifier, which is allocated the ` ` character. In addition, `MINUS` is now allocated the `-` character, but old format where there is no `+`, `-`, or `-` in the respective position is also accepted for compatibility. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 13 +++++-- kernel/fmt.cc | 47 ++++++++++++++++++------- kernel/fmt.h | 6 +++- 3 files changed, 50 insertions(+), 16 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index d6378fdd625..84d51cce79d 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1033,7 +1033,11 @@ struct fmt_part { // INTEGER type unsigned base; // = 10; bool signed_; // = false; - bool plus; // = false; + enum { + MINUS = 0, + PLUS_MINUS = 1, + SPACE_MINUS = 2, + } sign; // = MINUS; bool hex_upper; // = false; // VLOG_TIME type @@ -1105,8 +1109,11 @@ struct fmt_part { buf += '0' + remainder.template trunc<4>().template get(); xval = quotient; } - if (negative || plus) - buf += negative ? '-' : '+'; + switch (sign) { + case MINUS: buf += negative ? "-" : ""; break; + case PLUS_MINUS: buf += negative ? "-" : "+"; break; + case SPACE_MINUS: buf += negative ? "-" : " "; break; + } std::reverse(buf.begin(), buf.end()); } else assert(false && "Unsupported base for fmt_part"); break; diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 6fdc18ad0a9..342b508d915 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -128,10 +128,20 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { log_assert(false && "Unexpected end in format substitution"); if (part.type == FmtPart::INTEGER) { - if (fmt[i] == '+') { - part.plus = true; + if (fmt[i] == '-') { + part.sign = FmtPart::MINUS; if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); + } else if (fmt[i] == '+') { + part.sign = FmtPart::PLUS_MINUS; + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + } else if (fmt[i] == ' ') { + part.sign = FmtPart::SPACE_MINUS; + if (++i == fmt.size()) + log_assert(false && "Unexpected end in format substitution"); + } else { + // also accept no sign character and treat like MINUS for compatibility } if (fmt[i] == 'u') @@ -204,8 +214,11 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { case 16: fmt += part.hex_upper ? 'H' : 'h'; break; default: log_abort(); } - if (part.plus) - fmt += '+'; + switch (part.sign) { + case FmtPart::MINUS: fmt += '-'; break; + case FmtPart::PLUS_MINUS: fmt += '+'; break; + case FmtPart::SPACE_MINUS: fmt += ' '; break; + } fmt += part.signed_ ? 's' : 'u'; } else if (part.type == FmtPart::STRING) { fmt += 'c'; @@ -379,7 +392,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik part.justify = FmtPart::LEFT; } else if (fmt[i] == '+') { // always show sign; not in IEEE 1800-2017 or verilator but iverilog has it - part.plus = true; + part.sign = FmtPart::PLUS_MINUS; } else break; } if (i == fmt.size()) { @@ -442,7 +455,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik if (part.padding == '\0') part.padding = (has_leading_zero && part.justify == FmtPart::RIGHT) ? '0' : ' '; - if (part.type == FmtPart::INTEGER && part.base != 10 && part.plus) + if (part.type == FmtPart::INTEGER && part.base != 10 && part.sign != FmtPart::MINUS) log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with invalid format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); if (part.type == FmtPart::INTEGER && !has_leading_zero) @@ -495,8 +508,8 @@ std::vector Fmt::emit_verilog() const args.push_back(arg); fmt.str += '%'; - if (part.plus) - fmt.str += '+'; + if (part.sign == FmtPart::PLUS_MINUS || part.sign == FmtPart::SPACE_MINUS) + fmt.str += '+'; // treat space/minus as plus/minus if (part.justify == FmtPart::LEFT) fmt.str += '-'; if (part.width == 0) { @@ -553,7 +566,8 @@ std::vector Fmt::emit_verilog() const args.push_back(arg); fmt.str += '%'; - if (part.plus) + log_assert(part.sign == FmtPart::MINUS || part.sign == FmtPart::PLUS_MINUS); + if (part.sign == FmtPart::PLUS_MINUS) fmt.str += '+'; if (part.justify == FmtPart::LEFT) fmt.str += '-'; @@ -620,7 +634,13 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function Date: Thu, 28 Mar 2024 07:23:09 +0000 Subject: [PATCH 04/10] fmt,cxxrtl: add support for `NUMERIC` justification. Before this commit, the existing alignments were `LEFT` and `RIGHT`, which added the `padding` character to the right and left just before finishing formatting. However, if `padding == '0'` and the alignment is to the right, then the padding character (digit zero) was added after the sign, if one is present. After this commit, the special case for `padding == '0'` is removed, and the new justification `NUMERIC` adds the padding character like the justification `RIGHT`, except after the sign, if one is present. (Space, for the `SPACE_MINUS` sign mode, counts as the sign.) --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 5 +++-- kernel/fmt.cc | 23 +++++++++++++++++------ kernel/fmt.h | 1 + 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 84d51cce79d..f697491d99a 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1026,6 +1026,7 @@ struct fmt_part { enum { RIGHT = 0, LEFT = 1, + NUMERIC = 2, } justify; // = RIGHT; char padding; // = '\0'; size_t width; // = 0; @@ -1131,9 +1132,9 @@ struct fmt_part { std::string str; assert(width == 0 || padding != '\0'); - if (justify == RIGHT && buf.size() < width) { + if (justify != LEFT && buf.size() < width) { size_t pad_width = width - buf.size(); - if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) { + if (justify == NUMERIC && (buf.front() == '+' || buf.front() == '-' || buf.front() == ' ')) { str += buf.front(); buf.erase(0, 1); } diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 342b508d915..43f60b1d867 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -78,6 +78,8 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { part.justify = FmtPart::RIGHT; else if (fmt[i] == '<') part.justify = FmtPart::LEFT; + else if (fmt[i] == '=') + part.justify = FmtPart::NUMERIC; else log_assert(false && "Unexpected justification in format substitution"); if (++i == fmt.size()) @@ -201,6 +203,8 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { fmt += '>'; else if (part.justify == FmtPart::LEFT) fmt += '<'; + else if (part.justify == FmtPart::NUMERIC) + fmt += '='; else log_abort(); log_assert(part.width == 0 || part.padding != '\0'); fmt += part.padding != '\0' ? part.padding : ' '; @@ -452,8 +456,14 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with incomplete format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); } - if (part.padding == '\0') - part.padding = (has_leading_zero && part.justify == FmtPart::RIGHT) ? '0' : ' '; + if (part.padding == '\0') { + if (has_leading_zero && part.justify == FmtPart::RIGHT) { + part.padding = '0'; + part.justify = FmtPart::NUMERIC; + } else { + part.padding = ' '; + } + } if (part.type == FmtPart::INTEGER && part.base != 10 && part.sign != FmtPart::MINUS) log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with invalid format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); @@ -626,8 +636,9 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function Date: Thu, 28 Mar 2024 07:55:46 +0000 Subject: [PATCH 05/10] fmt,cxxrtl: add `UNICHAR` format type. This format type is used to print an Unicode character (code point) as its UTF-8 serialization. To this end, two UTF-8 decoders (one for fmt, one for cxxrtl) are added for rendering. When converted to a Verilog format specifier, `UNICHAR` degrades to `%c` with the low 7 bits of the code point, which has equivalent behavior for inputs not exceeding ASCII. (SystemVerilog leaves source and display encodings completely undefined.) --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 24 +++++++++++-- kernel/fmt.cc | 47 +++++++++++++++++++++++-- kernel/fmt.h | 5 +-- 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index f697491d99a..857e6449091 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1013,13 +1013,14 @@ struct fmt_part { LITERAL = 0, INTEGER = 1, STRING = 2, - VLOG_TIME = 3, + UNICHAR = 3, + VLOG_TIME = 4, } type; // LITERAL type std::string str; - // INTEGER/STRING types + // INTEGER/STRING/UNICHAR types // + value val; // INTEGER/STRING/VLOG_TIME types @@ -1073,6 +1074,25 @@ struct fmt_part { break; } + case UNICHAR: { + uint32_t codepoint = val.template get(); + if (codepoint >= 0x10000) + buf += (char)(0xf0 | (codepoint >> 18)); + else if (codepoint >= 0x800) + buf += (char)(0xe0 | (codepoint >> 12)); + else if (codepoint >= 0x80) + buf += (char)(0xc0 | (codepoint >> 6)); + else + buf += (char)codepoint; + if (codepoint >= 0x10000) + buf += (char)(0x80 | ((codepoint >> 12) & 0x3f)); + if (codepoint >= 0x800) + buf += (char)(0x80 | ((codepoint >> 6) & 0x3f)); + if (codepoint >= 0x80) + buf += (char)(0x80 | ((codepoint >> 0) & 0x3f)); + break; + } + case INTEGER: { size_t width = Bits; if (base != 10) { diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 43f60b1d867..5bd4fd9c8e5 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -42,9 +42,9 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { } else if (fmt.substr(i, 2) == "{{") { part.str += '{'; ++i; - } else if (fmt[i] == '}') + } else if (fmt[i] == '}') { log_assert(false && "Unexpected '}' in format string"); - else if (fmt[i] == '{') { + } else if (fmt[i] == '{') { if (!part.str.empty()) { part.type = FmtPart::LITERAL; parts.push_back(part); @@ -74,6 +74,12 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { part.sig = args.extract(0, arg_size); args.remove(0, arg_size); + if (fmt[i] == 'U') { + part.type = FmtPart::UNICHAR; + ++i; + goto success; + } + if (fmt[i] == '>') part.justify = FmtPart::RIGHT; else if (fmt[i] == '<') @@ -156,6 +162,7 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { log_assert(false && "Unexpected end in format substitution"); } + success: if (fmt[i] != '}') log_assert(false && "Expected '}' after format substitution"); @@ -188,6 +195,11 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { } break; + case FmtPart::UNICHAR: + log_assert(part.sig.size() <= 32); + fmt += "{U}"; + break; + case FmtPart::VLOG_TIME: log_assert(part.sig.size() == 0); YS_FALLTHROUGH @@ -568,6 +580,16 @@ std::vector Fmt::emit_verilog() const break; } + case FmtPart::UNICHAR: { + VerilogFmtArg arg; + arg.type = VerilogFmtArg::INTEGER; + arg.sig = part.sig.extract(0, 7); // only ASCII + args.push_back(arg); + + fmt.str += "%c"; + break; + } + case FmtPart::VLOG_TIME: { VerilogFmtArg arg; arg.type = VerilogFmtArg::TIME; @@ -630,6 +652,7 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function= 0x10000) + str += (char)(0xf0 | (codepoint >> 18)); + else if (codepoint >= 0x800) + str += (char)(0xe0 | (codepoint >> 12)); + else if (codepoint >= 0x80) + str += (char)(0xc0 | (codepoint >> 6)); + else + str += (char)codepoint; + if (codepoint >= 0x10000) + str += (char)(0x80 | ((codepoint >> 12) & 0x3f)); + if (codepoint >= 0x800) + str += (char)(0x80 | ((codepoint >> 6) & 0x3f)); + if (codepoint >= 0x80) + str += (char)(0x80 | ((codepoint >> 0) & 0x3f)); + break; + } + case FmtPart::INTEGER: case FmtPart::STRING: case FmtPart::VLOG_TIME: { diff --git a/kernel/fmt.h b/kernel/fmt.h index 9dc6341ba57..7fe6fd55e25 100644 --- a/kernel/fmt.h +++ b/kernel/fmt.h @@ -56,13 +56,14 @@ struct FmtPart { LITERAL = 0, INTEGER = 1, STRING = 2, - VLOG_TIME = 3, + UNICHAR = 3, + VLOG_TIME = 4, } type; // LITERAL type std::string str; - // INTEGER/STRING types + // INTEGER/STRING/UNICHAR types RTLIL::SigSpec sig; // INTEGER/STRING/VLOG_TIME types From 04a28f12d480ac8834e7d2a8cb875e7ab744c3da Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 28 Mar 2024 08:33:29 +0000 Subject: [PATCH 06/10] fmt,cxxrtl: add option to print numeric base (`0x`, etc). The option is serialized to RTLIL as `#` (to match Python's and Rust's option with the same symbol), and sets the `show_base` flag. Because the flag is called `show_base` and not e.g. `alternate_format` (which is what Python and Rust call it), in addition to the prefixes `0x`, `0X`, `0o`, `0b`, the RTLIL option also prints the `0d` prefix. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 7 +++++++ kernel/fmt.cc | 13 +++++++++++++ kernel/fmt.h | 1 + 3 files changed, 21 insertions(+) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 857e6449091..4e21e60fae8 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1041,6 +1041,7 @@ struct fmt_part { SPACE_MINUS = 2, } sign; // = MINUS; bool hex_upper; // = false; + bool show_base; // = false; // VLOG_TIME type bool realtime; // = false; @@ -1103,6 +1104,8 @@ struct fmt_part { } if (base == 2) { + if (show_base) + buf += "0b"; for (size_t i = width; i > 0; i--) buf += (val.bit(i - 1) ? '1' : '0'); } else if (base == 8 || base == 16) { @@ -1113,6 +1116,8 @@ struct fmt_part { value |= val.bit(index + 3) << 3; buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value]; } + if (show_base) + buf += (base == 16) ? (hex_upper ? "X0" : "x0") : "o0"; std::reverse(buf.begin(), buf.end()); } else if (base == 10) { bool negative = signed_ && val.is_neg(); @@ -1130,6 +1135,8 @@ struct fmt_part { buf += '0' + remainder.template trunc<4>().template get(); xval = quotient; } + if (show_base) + buf += "d0"; switch (sign) { case MINUS: buf += negative ? "-" : ""; break; case PLUS_MINUS: buf += negative ? "-" : "+"; break; diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 5bd4fd9c8e5..8f4e61722e1 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -152,6 +152,11 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { // also accept no sign character and treat like MINUS for compatibility } + if (fmt[i] == '#') { + part.show_base = true; + ++i; + } + if (fmt[i] == 'u') part.signed_ = false; else if (fmt[i] == 's') @@ -235,6 +240,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { case FmtPart::PLUS_MINUS: fmt += '+'; break; case FmtPart::SPACE_MINUS: fmt += ' '; break; } + fmt += part.show_base ? "#" : ""; fmt += part.signed_ ? 's' : 'u'; } else if (part.type == FmtPart::STRING) { fmt += 'c'; @@ -676,6 +682,7 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function Date: Thu, 28 Mar 2024 08:55:26 +0000 Subject: [PATCH 07/10] fmt,cxxrtl: add option to group digits in numbers. The option is serialized to RTLIL as `_` (to match Python's option with the same symbol), and sets the `group` flag. This flag inserts an `_` symbol between each group of 3 digits (for decimal) or four digits (for binary, hex, and octal). --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 17 ++++++++++++--- kernel/fmt.cc | 29 +++++++++++++++++++++++-- kernel/fmt.h | 1 + 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 4e21e60fae8..9d9753871dc 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1042,6 +1042,7 @@ struct fmt_part { } sign; // = MINUS; bool hex_upper; // = false; bool show_base; // = false; + bool group; // = false; // VLOG_TIME type bool realtime; // = false; @@ -1104,13 +1105,19 @@ struct fmt_part { } if (base == 2) { + for (size_t index = 0; index < width; index++) { + if (group && index > 0 && index % 4 == 0) + buf += '_'; + buf += (val.bit(index) ? '1' : '0'); + } if (show_base) - buf += "0b"; - for (size_t i = width; i > 0; i--) - buf += (val.bit(i - 1) ? '1' : '0'); + buf += "b0"; + std::reverse(buf.begin(), buf.end()); } else if (base == 8 || base == 16) { size_t step = (base == 16) ? 4 : 3; for (size_t index = 0; index < width; index += step) { + if (group && index > 0 && index % (4 * step) == 0) + buf += '_'; uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); if (step == 4) value |= val.bit(index + 3) << 3; @@ -1126,7 +1133,10 @@ struct fmt_part { if (val.is_zero()) buf += '0'; value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); + size_t index = 0; while (!xval.is_zero()) { + if (group && index > 0 && index % 3 == 0) + buf += '_'; value<(Bits > 4 ? Bits : 4)> quotient, remainder; if (Bits >= 4) std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u}); @@ -1134,6 +1144,7 @@ struct fmt_part { std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval); buf += '0' + remainder.template trunc<4>().template get(); xval = quotient; + index++; } if (show_base) buf += "d0"; diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 8f4e61722e1..f5793d796f2 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -156,6 +156,10 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { part.show_base = true; ++i; } + if (fmt[i] == '_') { + part.group = true; + ++i; + } if (fmt[i] == 'u') part.signed_ = false; @@ -241,6 +245,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { case FmtPart::SPACE_MINUS: fmt += ' '; break; } fmt += part.show_base ? "#" : ""; + fmt += part.group ? "_" : ""; fmt += part.signed_ ? 's' : 'u'; } else if (part.type == FmtPart::STRING) { fmt += 'c'; @@ -683,6 +688,7 @@ void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function 0 && index % 4 == 0) + buf += '_'; + RTLIL::State bit = value[index]; + if (bit == State::Sx) + buf += 'x'; + else if (bit == State::Sz) + buf += 'z'; + else if (bit == State::S1) + buf += '1'; + else /* if (bit == State::S0) */ + buf += '0'; + } if (part.show_base) - buf += "0b"; - buf = value.as_string(); + buf += "b0"; + std::reverse(buf.begin(), buf.end()); } else if (part.base == 8 || part.base == 16) { size_t step = (part.base == 16) ? 4 : 3; for (size_t index = 0; index < (size_t)value.size(); index += step) { + if (part.group && index > 0 && index % (4 * step) == 0) + buf += '_'; RTLIL::Const subvalue = value.extract(index, min(step, value.size() - index)); bool has_x = false, all_x = true, has_z = false, all_z = true; for (State bit : subvalue) { @@ -799,9 +820,13 @@ std::string Fmt::render() const log_assert(absvalue.is_fully_def()); if (absvalue.is_fully_zero()) buf += '0'; + size_t index = 0; while (!absvalue.is_fully_zero()) { + if (part.group && index > 0 && index % 3 == 0) + buf += '_'; buf += '0' + RTLIL::const_mod(absvalue, 10, false, false, 4).as_int(); absvalue = RTLIL::const_div(absvalue, 10, false, false, absvalue.size()); + index++; } if (part.show_base) buf += "d0"; diff --git a/kernel/fmt.h b/kernel/fmt.h index 10b1711f19a..2d4b24979b2 100644 --- a/kernel/fmt.h +++ b/kernel/fmt.h @@ -85,6 +85,7 @@ struct FmtPart { } sign = MINUS; bool hex_upper = false; bool show_base = false; + bool group = false; // VLOG_TIME type bool realtime = false; From b36b4bb0298a91bd710d1de22434dca6f7a26490 Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 28 Mar 2024 09:45:10 +0000 Subject: [PATCH 08/10] fmt,cxxrtl: fix printing of non-decimal signed numbers. Also fix interaction of `NUMERIC` justification with `show_base`. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 67 ++++++++++------ kernel/fmt.cc | 101 ++++++++++++++---------- 2 files changed, 100 insertions(+), 68 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 9d9753871dc..f628051105e 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1058,6 +1058,7 @@ struct fmt_part { // We might want to replace some of these bit() calls with direct // chunk access if it turns out to be slow enough to matter. std::string buf; + std::string prefix; switch (type) { case LITERAL: return str; @@ -1096,24 +1097,38 @@ struct fmt_part { } case INTEGER: { + bool negative = signed_ && val.is_neg(); + if (negative) { + prefix = "-"; + val = val.neg(); + } else { + switch (sign) { + case MINUS: break; + case PLUS_MINUS: prefix = "+"; break; + case SPACE_MINUS: prefix = " "; break; + } + } + size_t width = Bits; if (base != 10) { - width = 0; + width = 1; for (size_t index = 0; index < Bits; index++) if (val.bit(index)) width = index + 1; } if (base == 2) { + if (show_base) + prefix += "0b"; for (size_t index = 0; index < width; index++) { if (group && index > 0 && index % 4 == 0) buf += '_'; buf += (val.bit(index) ? '1' : '0'); } - if (show_base) - buf += "b0"; std::reverse(buf.begin(), buf.end()); } else if (base == 8 || base == 16) { + if (show_base) + prefix += (base == 16) ? (hex_upper ? "0X" : "0x") : "0o"; size_t step = (base == 16) ? 4 : 3; for (size_t index = 0; index < width; index += step) { if (group && index > 0 && index % (4 * step) == 0) @@ -1123,13 +1138,10 @@ struct fmt_part { value |= val.bit(index + 3) << 3; buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value]; } - if (show_base) - buf += (base == 16) ? (hex_upper ? "X0" : "x0") : "o0"; std::reverse(buf.begin(), buf.end()); } else if (base == 10) { - bool negative = signed_ && val.is_neg(); - if (negative) - val = val.neg(); + if (show_base) + prefix += "0d"; if (val.is_zero()) buf += '0'; value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); @@ -1146,13 +1158,6 @@ struct fmt_part { xval = quotient; index++; } - if (show_base) - buf += "d0"; - switch (sign) { - case MINUS: buf += negative ? "-" : ""; break; - case PLUS_MINUS: buf += negative ? "-" : "+"; break; - case SPACE_MINUS: buf += negative ? "-" : " "; break; - } std::reverse(buf.begin(), buf.end()); } else assert(false && "Unsupported base for fmt_part"); break; @@ -1170,17 +1175,29 @@ struct fmt_part { std::string str; assert(width == 0 || padding != '\0'); - if (justify != LEFT && buf.size() < width) { - size_t pad_width = width - buf.size(); - if (justify == NUMERIC && (buf.front() == '+' || buf.front() == '-' || buf.front() == ' ')) { - str += buf.front(); - buf.erase(0, 1); - } - str += std::string(pad_width, padding); + if (prefix.size() + buf.size() < width) { + size_t pad_width = width - prefix.size() - buf.size(); + switch (justify) { + case LEFT: + str += prefix; + str += buf; + str += std::string(pad_width, padding); + break; + case RIGHT: + str += std::string(pad_width, padding); + str += prefix; + str += buf; + break; + case NUMERIC: + str += prefix; + str += std::string(pad_width, padding); + str += buf; + break; + } + } else { + str += prefix; + str += buf; } - str += buf; - if (justify == LEFT && buf.size() < width) - str += std::string(width - buf.size(), padding); return str; } }; diff --git a/kernel/fmt.cc b/kernel/fmt.cc index f5793d796f2..f70f6d3902c 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -491,6 +491,8 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik if (part.type == FmtPart::INTEGER && part.base != 10 && part.sign != FmtPart::MINUS) log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with invalid format specifier in argument %zu.\n", task_name.c_str(), fmtarg - args.begin() + 1); + if (part.base != 10) + part.signed_ = false; if (part.type == FmtPart::INTEGER && !has_leading_zero) apply_verilog_automatic_sizing_and_add(part); else @@ -731,11 +733,34 @@ std::string Fmt::render() const case FmtPart::STRING: case FmtPart::VLOG_TIME: { std::string buf; + std::string prefix; if (part.type == FmtPart::INTEGER) { RTLIL::Const value = part.sig.as_const(); + bool has_x = false, all_x = true, has_z = false, all_z = true; + for (State bit : value) { + if (bit == State::Sx) + has_x = true; + else + all_x = false; + if (bit == State::Sz) + has_z = true; + else + all_z = false; + } + + if (!has_z && !has_x && part.signed_ && value[value.size() - 1]) { + prefix = "-"; + value = RTLIL::const_neg(value, {}, part.signed_, {}, value.size() + 1); + } else { + switch (part.sign) { + case FmtPart::MINUS: break; + case FmtPart::PLUS_MINUS: prefix = "+"; break; + case FmtPart::SPACE_MINUS: prefix = " "; break; + } + } if (part.base != 10) { - size_t minimum_size = 0; + size_t minimum_size = 1; for (size_t index = 0; index < (size_t)value.size(); index++) if (value[index] != State::S0) minimum_size = index + 1; @@ -743,6 +768,8 @@ std::string Fmt::render() const } if (part.base == 2) { + if (part.show_base) + prefix += "0b"; for (size_t index = 0; index < (size_t)value.size(); index++) { if (part.group && index > 0 && index % 4 == 0) buf += '_'; @@ -756,10 +783,10 @@ std::string Fmt::render() const else /* if (bit == State::S0) */ buf += '0'; } - if (part.show_base) - buf += "b0"; std::reverse(buf.begin(), buf.end()); } else if (part.base == 8 || part.base == 16) { + if (part.show_base) + prefix += (part.base == 16) ? (part.hex_upper ? "0X" : "0x") : "0o"; size_t step = (part.base == 16) ? 4 : 3; for (size_t index = 0; index < (size_t)value.size(); index += step) { if (part.group && index > 0 && index % (4 * step) == 0) @@ -787,21 +814,10 @@ std::string Fmt::render() const else buf += (part.hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[subvalue.as_int()]; } - if (part.show_base) - buf += (part.base == 16) ? (part.hex_upper ? "X0" : "x0") : "o0"; std::reverse(buf.begin(), buf.end()); } else if (part.base == 10) { - bool has_x = false, all_x = true, has_z = false, all_z = true; - for (State bit : value) { - if (bit == State::Sx) - has_x = true; - else - all_x = false; - if (bit == State::Sz) - has_z = true; - else - all_z = false; - } + if (part.show_base) + prefix += "0d"; if (all_x) buf += 'x'; else if (all_z) @@ -811,30 +827,17 @@ std::string Fmt::render() const else if (has_z) buf += 'Z'; else { - bool negative = part.signed_ && value[value.size() - 1]; - RTLIL::Const absvalue; - if (negative) - absvalue = RTLIL::const_neg(value, {}, part.signed_, {}, value.size() + 1); - else - absvalue = value; - log_assert(absvalue.is_fully_def()); - if (absvalue.is_fully_zero()) + log_assert(value.is_fully_def()); + if (value.is_fully_zero()) buf += '0'; size_t index = 0; - while (!absvalue.is_fully_zero()) { + while (!value.is_fully_zero()) { if (part.group && index > 0 && index % 3 == 0) buf += '_'; - buf += '0' + RTLIL::const_mod(absvalue, 10, false, false, 4).as_int(); - absvalue = RTLIL::const_div(absvalue, 10, false, false, absvalue.size()); + buf += '0' + RTLIL::const_mod(value, 10, false, false, 4).as_int(); + value = RTLIL::const_div(value, 10, false, false, value.size()); index++; } - if (part.show_base) - buf += "d0"; - switch (part.sign) { - case FmtPart::MINUS: buf += negative ? "-" : ""; break; - case FmtPart::PLUS_MINUS: buf += negative ? "-" : "+"; break; - case FmtPart::SPACE_MINUS: buf += negative ? "-" : " "; break; - } std::reverse(buf.begin(), buf.end()); } } else log_abort(); @@ -846,17 +849,29 @@ std::string Fmt::render() const } log_assert(part.width == 0 || part.padding != '\0'); - if (part.justify != FmtPart::LEFT && buf.size() < part.width) { - size_t pad_width = part.width - buf.size(); - if (part.justify == FmtPart::NUMERIC && (!buf.empty() && (buf.front() == '+' || buf.front() == '-' || buf.front() == ' '))) { - str += buf.front(); - buf.erase(0, 1); + if (prefix.size() + buf.size() < part.width) { + size_t pad_width = part.width - prefix.size() - buf.size(); + switch (part.justify) { + case FmtPart::LEFT: + str += prefix; + str += buf; + str += std::string(pad_width, part.padding); + break; + case FmtPart::RIGHT: + str += std::string(pad_width, part.padding); + str += prefix; + str += buf; + break; + case FmtPart::NUMERIC: + str += prefix; + str += std::string(pad_width, part.padding); + str += buf; + break; } - str += std::string(pad_width, part.padding); + } else { + str += prefix; + str += buf; } - str += buf; - if (part.justify == FmtPart::LEFT && buf.size() < part.width) - str += std::string(part.width - buf.size(), part.padding); break; } } From 610bd72cfd0ca4febf9171b18a34e436c06eafab Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 28 Mar 2024 09:52:59 +0000 Subject: [PATCH 09/10] fmt: allow padding characters other than `'0'` and `' '`. When converted to Verilog, padding characters are replaced with one of these two. Otherwise padding is performed with exactly that character. --- kernel/fmt.cc | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/kernel/fmt.cc b/kernel/fmt.cc index f70f6d3902c..43d8feddfa4 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -91,10 +91,7 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); - if (fmt[i] == '0' || fmt[i] == ' ') - part.padding = fmt[i]; - else - log_assert(false && "Unexpected padding in format substitution"); + part.padding = fmt[i]; if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); @@ -550,7 +547,6 @@ std::vector Fmt::emit_verilog() const if (part.width == 0) { fmt.str += '0'; } else if (part.width > 0) { - log_assert(part.padding == ' ' || part.padding == '0'); if (part.base != 10 || part.padding == '0') fmt.str += '0'; fmt.str += std::to_string(part.width); @@ -576,7 +572,6 @@ std::vector Fmt::emit_verilog() const fmt.str += '-'; if (part.sig.size() == 8) { if (part.width > 0) { - log_assert(part.padding == '0' || part.padding == ' '); if (part.padding == '0') fmt.str += part.padding; fmt.str += std::to_string(part.width); @@ -585,7 +580,6 @@ std::vector Fmt::emit_verilog() const } else { log_assert(part.sig.size() % 8 == 0); if (part.width > 0) { - log_assert(part.padding == ' '); // no zero padding fmt.str += std::to_string(part.width); } fmt.str += 's'; @@ -616,7 +610,6 @@ std::vector Fmt::emit_verilog() const fmt.str += '+'; if (part.justify == FmtPart::LEFT) fmt.str += '-'; - log_assert(part.padding == ' ' || part.padding == '0'); if (part.padding == '0' && part.width > 0) fmt.str += '0'; fmt.str += std::to_string(part.width); From 577381b3c90a2b7593ed80cb85be1e34016faf2f Mon Sep 17 00:00:00 2001 From: Catherine Date: Thu, 28 Mar 2024 10:06:18 +0000 Subject: [PATCH 10/10] fmt: if enabled, group padding zeroes. Before this commit, the combination of `_` and `0` format characters would produce a result like `000000001010_1010`. After this commit, it would be `0000_0000_1010_1010`. This has a slight quirk where a format like `{:020_b}` results in the output `0_0000_0000_1010_1010`, which is one character longer than requested. Python has the same behavior, and it's not clear what would be strictly speaking correct, so Python behavior is implemented. --- backends/cxxrtl/runtime/cxxrtl/cxxrtl.h | 22 ++++++++++++++-------- kernel/fmt.cc | 12 +++++++++--- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index f628051105e..bc26f2b2e03 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -1109,28 +1109,27 @@ struct fmt_part { } } - size_t width = Bits; + size_t val_width = Bits; if (base != 10) { - width = 1; + val_width = 1; for (size_t index = 0; index < Bits; index++) if (val.bit(index)) - width = index + 1; + val_width = index + 1; } if (base == 2) { if (show_base) prefix += "0b"; - for (size_t index = 0; index < width; index++) { + for (size_t index = 0; index < val_width; index++) { if (group && index > 0 && index % 4 == 0) buf += '_'; buf += (val.bit(index) ? '1' : '0'); } - std::reverse(buf.begin(), buf.end()); } else if (base == 8 || base == 16) { if (show_base) prefix += (base == 16) ? (hex_upper ? "0X" : "0x") : "0o"; size_t step = (base == 16) ? 4 : 3; - for (size_t index = 0; index < width; index += step) { + for (size_t index = 0; index < val_width; index += step) { if (group && index > 0 && index % (4 * step) == 0) buf += '_'; uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); @@ -1138,7 +1137,6 @@ struct fmt_part { value |= val.bit(index + 3) << 3; buf += (hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[value]; } - std::reverse(buf.begin(), buf.end()); } else if (base == 10) { if (show_base) prefix += "0d"; @@ -1158,8 +1156,16 @@ struct fmt_part { xval = quotient; index++; } - std::reverse(buf.begin(), buf.end()); } else assert(false && "Unsupported base for fmt_part"); + if (justify == NUMERIC && group && padding == '0') { + int group_size = base == 10 ? 3 : 4; + while (prefix.size() + buf.size() < width) { + if (buf.size() % (group_size + 1) == group_size) + buf += '_'; + buf += '0'; + } + } + std::reverse(buf.begin(), buf.end()); break; } diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 43d8feddfa4..cbf2d12e9ac 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -776,7 +776,6 @@ std::string Fmt::render() const else /* if (bit == State::S0) */ buf += '0'; } - std::reverse(buf.begin(), buf.end()); } else if (part.base == 8 || part.base == 16) { if (part.show_base) prefix += (part.base == 16) ? (part.hex_upper ? "0X" : "0x") : "0o"; @@ -807,7 +806,6 @@ std::string Fmt::render() const else buf += (part.hex_upper ? "0123456789ABCDEF" : "0123456789abcdef")[subvalue.as_int()]; } - std::reverse(buf.begin(), buf.end()); } else if (part.base == 10) { if (part.show_base) prefix += "0d"; @@ -831,9 +829,17 @@ std::string Fmt::render() const value = RTLIL::const_div(value, 10, false, false, value.size()); index++; } - std::reverse(buf.begin(), buf.end()); } } else log_abort(); + if (part.justify == FmtPart::NUMERIC && part.group && part.padding == '0') { + int group_size = part.base == 10 ? 3 : 4; + while (prefix.size() + buf.size() < part.width) { + if (buf.size() % (group_size + 1) == group_size) + buf += '_'; + buf += '0'; + } + } + std::reverse(buf.begin(), buf.end()); } else if (part.type == FmtPart::STRING) { buf = part.sig.as_const().decode_string(); } else if (part.type == FmtPart::VLOG_TIME) {