/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "libs/bigint/BigUnsigned.hh" #include "kernel/fmt.h" USING_YOSYS_NAMESPACE void Fmt::append_string(const std::string &str) { FmtPart part = {}; part.type = FmtPart::STRING; part.str = str; parts.push_back(part); } void Fmt::parse_rtlil(const RTLIL::Cell *cell) { std::string fmt = cell->getParam(ID(FORMAT)).decode_string(); RTLIL::SigSpec args = cell->getPort(ID(ARGS)); parts.clear(); FmtPart part; for (size_t i = 0; i < fmt.size(); i++) { if (fmt.substr(i, 2) == "}}") { part.str += '}'; ++i; } else if (fmt.substr(i, 2) == "{{") { part.str += '{'; ++i; } else if (fmt[i] == '}') log_assert(false && "Unexpected '}' in format string"); else if (fmt[i] == '{') { if (!part.str.empty()) { part.type = FmtPart::STRING; parts.push_back(part); part = {}; } if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); size_t arg_size = 0; for (; i < fmt.size(); i++) { if (fmt[i] >= '0' && fmt[i] <= '9') { arg_size *= 10; arg_size += fmt[i] - '0'; } else if (fmt[i] == ':') { ++i; break; } else { log_assert(false && "Unexpected character in format substitution"); } } if (i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); if ((size_t)args.size() < arg_size) log_assert(false && "Format part overruns arguments"); part.sig = args.extract(0, arg_size); args.remove(0, arg_size); if (fmt[i] == '>') part.justify = FmtPart::RIGHT; else if (fmt[i] == '<') part.justify = FmtPart::LEFT; else log_assert(false && "Unexpected justification in format substitution"); 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"); if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); for (; i < fmt.size(); i++) { if (fmt[i] >= '0' && fmt[i] <= '9') { part.width *= 10; part.width += fmt[i] - '0'; continue; } else if (fmt[i] == 'b') { part.type = FmtPart::INTEGER; part.base = 2; } else if (fmt[i] == 'o') { part.type = FmtPart::INTEGER; part.base = 8; } else if (fmt[i] == 'd') { part.type = FmtPart::INTEGER; part.base = 10; } else if (fmt[i] == 'h') { part.type = FmtPart::INTEGER; part.base = 16; } else if (fmt[i] == 'c') { part.type = FmtPart::CHARACTER; } else if (fmt[i] == 't') { part.type = FmtPart::TIME; } else if (fmt[i] == 'r') { part.type = FmtPart::TIME; part.realtime = true; } else { log_assert(false && "Unexpected character in format substitution"); } ++i; break; } if (i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); if (part.type == FmtPart::INTEGER) { if (fmt[i] == '+') { part.plus = true; if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); } if (fmt[i] == 'u') part.signed_ = false; else if (fmt[i] == 's') part.signed_ = true; else log_assert(false && "Unexpected character in format substitution"); if (++i == fmt.size()) log_assert(false && "Unexpected end in format substitution"); } if (fmt[i] != '}') log_assert(false && "Expected '}' after format substitution"); parts.push_back(part); part = {}; } else { part.str += fmt[i]; } } if (!part.str.empty()) { part.type = FmtPart::STRING; parts.push_back(part); } } void Fmt::emit_rtlil(RTLIL::Cell *cell) const { std::string fmt; RTLIL::SigSpec args; for (auto &part : parts) { switch (part.type) { case FmtPart::STRING: for (char c : part.str) { if (c == '{') fmt += "{{"; else if (c == '}') fmt += "}}"; else fmt += c; } break; case FmtPart::TIME: log_assert(part.sig.size() == 0); YS_FALLTHROUGH case FmtPart::CHARACTER: log_assert(part.sig.size() % 8 == 0); YS_FALLTHROUGH case FmtPart::INTEGER: args.append(part.sig); fmt += '{'; fmt += std::to_string(part.sig.size()); fmt += ':'; if (part.justify == FmtPart::RIGHT) fmt += '>'; else if (part.justify == FmtPart::LEFT) fmt += '<'; else log_abort(); log_assert(part.width == 0 || part.padding != '\0'); fmt += part.padding != '\0' ? part.padding : ' '; if (part.width > 0) fmt += std::to_string(part.width); if (part.type == FmtPart::INTEGER) { switch (part.base) { case 2: fmt += 'b'; break; case 8: fmt += 'o'; break; case 10: fmt += 'd'; break; case 16: fmt += 'h'; break; default: log_abort(); } if (part.plus) fmt += '+'; fmt += part.signed_ ? 's' : 'u'; } else if (part.type == FmtPart::CHARACTER) { fmt += 'c'; } else if (part.type == FmtPart::TIME) { if (part.realtime) fmt += 'r'; else fmt += 't'; } else log_abort(); fmt += '}'; break; default: log_abort(); } } cell->setParam(ID(FORMAT), fmt); cell->setParam(ID(ARGS_WIDTH), args.size()); cell->setPort(ID(ARGS), args); } static size_t compute_required_decimal_places(size_t size, bool signed_) { BigUnsigned max; if (!signed_) max.setBit(size, true); else max.setBit(size - 1, true); size_t places = 0; while (!max.isZero()) { places++; max /= 10; } if (signed_) places++; return places; } static size_t compute_required_nondecimal_places(size_t size, unsigned base) { log_assert(base != 10); BigUnsigned max; max.setBit(size - 1, true); size_t places = 0; while (!max.isZero()) { places++; max /= base; } return places; } // Only called for integers, either when: // // (a) passed without a format string (e.g. "$display(a);"), or // // (b) the corresponding format specifier has no leading zero, e.g. "%b", // "%20h", "%-10d". // // In these cases, for binary/octal/hex, we always zero-pad to the size of the // signal; i.e. whether "%h" or "%10h" or "%-20h" is used, if the corresponding // signal is 32'h1234, "00001234" will always be a substring of the output. // // For case (a), we have no specified width, so there is nothing more to do. // // For case (b), because we are only called with no leading zero on the // specifier, any specified width beyond the signal size is therefore space // padding, whatever the justification. // // For decimal, we do no zero-padding, instead space-padding to the size // required for the signal's largest value. This is per other Verilog // implementations, and intuitively makes sense as decimal representations lack // a discrete mapping of digits to bit groups. Decimals may also show sign and // must accommodate this, whereas other representations do not. void Fmt::apply_verilog_automatic_sizing_and_add(FmtPart &part) { if (part.base == 10) { size_t places = compute_required_decimal_places(part.sig.size(), part.signed_); part.padding = ' '; part.width = std::max(part.width, places); parts.push_back(part); return; } part.padding = '0'; size_t places = compute_required_nondecimal_places(part.sig.size(), part.base); if (part.width < places) { part.justify = FmtPart::RIGHT; part.width = places; parts.push_back(part); } else if (part.width == places) { parts.push_back(part); } else if (part.width > places) { auto gap = std::string(part.width - places, ' '); part.width = places; if (part.justify == FmtPart::RIGHT) { append_string(gap); parts.push_back(part); } else { part.justify = FmtPart::RIGHT; parts.push_back(part); append_string(gap); } } } void Fmt::parse_verilog(const std::vector &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name) { parts.clear(); auto arg = args.begin(); for (; arg != args.end(); ++arg) { switch (arg->type) { case VerilogFmtArg::INTEGER: { FmtPart part = {}; part.type = FmtPart::INTEGER; part.sig = arg->sig; part.base = default_base; part.signed_ = arg->signed_; apply_verilog_automatic_sizing_and_add(part); break; } case VerilogFmtArg::TIME: { FmtPart part = {}; part.type = FmtPart::TIME; part.realtime = arg->realtime; part.padding = ' '; part.width = 20; parts.push_back(part); break; } case VerilogFmtArg::STRING: { if (arg == args.begin() || !sformat_like) { const auto fmtarg = arg; const std::string &fmt = fmtarg->str; FmtPart part = {}; for (size_t i = 0; i < fmt.size(); i++) { if (fmt[i] != '%') { part.str += fmt[i]; } else if (fmt.substr(i, 2) == "%%") { i++; part.str += '%'; } else if (fmt.substr(i, 2) == "%l" || fmt.substr(i, 2) == "%L") { i++; part.str += module_name.str(); } else if (fmt.substr(i, 2) == "%m" || fmt.substr(i, 2) == "%M") { i++; part.str += module_name.str(); } else { if (!part.str.empty()) { part.type = FmtPart::STRING; parts.push_back(part); part = {}; } if (++i == fmt.size()) { 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 (++arg == args.end()) { log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with fewer arguments than the format specifiers in argument %zu require.\n", task_name.c_str(), fmtarg - args.begin() + 1); } part.sig = arg->sig; part.signed_ = arg->signed_; for (; i < fmt.size(); i++) { if (fmt[i] == '-') { // left justify; not in IEEE 1800-2017 or verilator but iverilog has it 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; } else break; } if (i == fmt.size()) { 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); } bool has_leading_zero = false, has_width = false; for (; i < fmt.size(); i++) { if (fmt[i] >= '0' && fmt[i] <= '9') { if (fmt[i] == '0' && !has_width) { has_leading_zero = true; } else { has_width = true; part.width *= 10; part.width += fmt[i] - '0'; } continue; } else if (fmt[i] == 'b' || fmt[i] == 'B') { part.type = FmtPart::INTEGER; part.base = 2; } else if (fmt[i] == 'o' || fmt[i] == 'O') { part.type = FmtPart::INTEGER; part.base = 8; } else if (fmt[i] == 'd' || fmt[i] == 'D') { part.type = FmtPart::INTEGER; part.base = 10; } else if (fmt[i] == 'h' || fmt[i] == 'H' || fmt[i] == 'x' || fmt[i] == 'X') { // hex digits always printed in lowercase for %h%x as well as %H%X part.type = FmtPart::INTEGER; part.base = 16; } else if (fmt[i] == 'c' || fmt[i] == 'C') { part.type = FmtPart::CHARACTER; 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; 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 part.padding = ' '; } else if (fmt[i] == 't' || fmt[i] == 'T') { if (arg->type == VerilogFmtArg::TIME) { part.type = FmtPart::TIME; part.realtime = arg->realtime; if (!has_width && !has_leading_zero) part.width = 20; } else { log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with format character `%c' in argument %zu, but the argument is not $time or $realtime.\n", task_name.c_str(), fmt[i], fmtarg - args.begin() + 1); } } else { log_file_error(fmtarg->filename, fmtarg->first_line, "System task `%s' called with unrecognized format character `%c' in argument %zu.\n", task_name.c_str(), fmt[i], fmtarg - args.begin() + 1); } break; } if (i == fmt.size()) { 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.type == FmtPart::INTEGER && part.base != 10 && part.plus) 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) apply_verilog_automatic_sizing_and_add(part); else parts.push_back(part); part = {}; } } if (!part.str.empty()) { part.type = FmtPart::STRING; parts.push_back(part); } } else { FmtPart part = {}; part.type = FmtPart::STRING; part.str = arg->str; parts.push_back(part); } break; } default: log_abort(); } } } std::vector Fmt::emit_verilog() const { std::vector args; VerilogFmtArg fmt = {}; fmt.type = VerilogFmtArg::STRING; for (auto &part : parts) { switch (part.type) { case FmtPart::STRING: for (char c : part.str) { if (c == '%') fmt.str += "%%"; else fmt.str += c; } break; case FmtPart::INTEGER: { VerilogFmtArg arg = {}; arg.type = VerilogFmtArg::INTEGER; arg.sig = part.sig; arg.signed_ = part.signed_; args.push_back(arg); fmt.str += '%'; if (part.plus) fmt.str += '+'; if (part.justify == FmtPart::LEFT) fmt.str += '-'; 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); } switch (part.base) { case 2: fmt.str += 'b'; break; case 8: fmt.str += 'o'; break; case 10: fmt.str += 'd'; break; case 16: fmt.str += 'h'; break; default: log_abort(); } break; } case FmtPart::CHARACTER: { VerilogFmtArg arg; arg.type = VerilogFmtArg::INTEGER; arg.sig = part.sig; args.push_back(arg); fmt.str += '%'; if (part.justify == FmtPart::LEFT) 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); } fmt.str += 'c'; } 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'; } break; } case FmtPart::TIME: { VerilogFmtArg arg; arg.type = VerilogFmtArg::TIME; if (part.realtime) arg.realtime = true; args.push_back(arg); fmt.str += '%'; if (part.plus) 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); fmt.str += 't'; break; } default: log_abort(); } } args.insert(args.begin(), fmt); return args; } void Fmt::emit_cxxrtl(std::ostream &f, std::function emit_sig) const { for (auto &part : parts) { switch (part.type) { case FmtPart::STRING: f << " << \""; for (char c : part.str) { switch (c) { case '\\': YS_FALLTHROUGH case '"': f << '\\' << c; break; case '\a': f << "\\a"; break; case '\b': f << "\\b"; break; case '\f': f << "\\f"; break; case '\n': f << "\\n"; break; case '\r': f << "\\r"; break; case '\t': f << "\\t"; break; case '\v': f << "\\v"; break; default: f << c; break; } } f << '"'; break; case FmtPart::INTEGER: case FmtPart::CHARACTER: { f << " << value_formatted<" << part.sig.size() << ">("; emit_sig(part.sig); f << ", " << (part.type == FmtPart::CHARACTER); f << ", " << (part.justify == FmtPart::LEFT); f << ", (char)" << (int)part.padding; f << ", " << part.width; f << ", " << part.base; f << ", " << part.signed_; f << ", " << part.plus; f << ')'; break; } case FmtPart::TIME: { // CXXRTL only records steps taken, so there's no difference between // the values taken by $time and $realtime. f << " << value_formatted<64>("; f << "value<64>{steps}"; f << ", " << (part.type == FmtPart::CHARACTER); f << ", " << (part.justify == FmtPart::LEFT); f << ", (char)" << (int)part.padding; f << ", " << part.width; f << ", " << part.base; f << ", " << part.signed_; f << ", " << part.plus; f << ')'; break; } default: log_abort(); } } } std::string Fmt::render() const { std::string str; for (auto &part : parts) { switch (part.type) { case FmtPart::STRING: str += part.str; break; case FmtPart::INTEGER: case FmtPart::TIME: case FmtPart::CHARACTER: { std::string buf; if (part.type == FmtPart::INTEGER) { RTLIL::Const value = part.sig.as_const(); if (part.base != 10) { size_t minimum_size = 0; for (size_t index = 0; index < (size_t)value.size(); index++) if (value[index] != State::S0) minimum_size = index + 1; value = value.extract(0, minimum_size); } if (part.base == 2) { buf = value.as_string(); } 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) { 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) { if (bit == State::Sx) has_x = true; else all_x = false; if (bit == State::Sz) has_z = true; else all_z = false; } if (all_x) buf += 'x'; else if (all_z) buf += 'z'; else if (has_x) buf += 'X'; else if (has_z) buf += 'Z'; else buf += "0123456789abcdef"[subvalue.as_int()]; } 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 (all_x) buf += 'x'; else if (all_z) buf += 'z'; else if (has_x) buf += 'X'; 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()) buf += '0'; while (!absvalue.is_fully_zero()) { buf += '0' + RTLIL::const_mod(absvalue, 10, false, false, 4).as_int(); absvalue = RTLIL::const_div(absvalue, 10, false, false, absvalue.size()); } if (negative || part.plus) buf += negative ? '-' : '+'; std::reverse(buf.begin(), buf.end()); } } else log_abort(); } else if (part.type == FmtPart::CHARACTER) { buf = part.sig.as_const().decode_string(); } else if (part.type == FmtPart::TIME) { // We only render() during initial, so time is always zero. buf = "0"; } log_assert(part.width == 0 || part.padding != '\0'); if (part.justify == FmtPart::RIGHT && buf.size() < part.width) { size_t pad_width = part.width - buf.size(); if (part.padding == '0' && (!buf.empty() && (buf.front() == '+' || buf.front() == '-'))) { str += buf.front(); buf.erase(0, 1); } str += std::string(pad_width, part.padding); } str += buf; if (part.justify == FmtPart::LEFT && buf.size() < part.width) str += std::string(part.width - buf.size(), part.padding); break; } } } return str; }