3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-04-24 01:25:33 +00:00

cxxrtl: refactor the formatter and use a closure.

This commit achieves three roughly equally important goals:
1. To bring the rendering code in kernel/fmt.cc and in cxxrtl.h as close
   together as possible, with an ideal of only having the bigint library
   as the difference between the render functions.
2. To make the treatment of `$time` and `$realtime` in CXXRTL closer to
   the Verilog semantics, at least in the formatting code.
3. To change the code generator so that all of the `$print`-to-`string`
   conversion code is contained inside of a closure.

There are two reasons to aim for goal (3):
a. Because output redirection through definition of a global ostream
   object is neither convenient nor useful for environments where
   the output is consumed by other code rather than being printed on
   a terminal.
b. Because it may be desirable to, in some cases, ignore the `$print`
   cells that are present in the netlist based on a runtime decision.
   This is doubly true for an upcoming `$check` cell implementing
   assertions, since failing a `$check` would by default cause a crash.
This commit is contained in:
Catherine 2024-01-16 09:08:07 +00:00
parent bf1a99da09
commit a33acb7cd9
5 changed files with 192 additions and 173 deletions

View file

@ -1072,9 +1072,12 @@ struct CxxrtlWorker {
dump_sigspec_rhs(cell->getPort(ID::EN));
f << " == value<1>{1u}) {\n";
inc_indent();
f << indent << print_output;
fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
f << ";\n";
f << indent << "auto formatter = [&](int64_t itime, double ftime) {\n";
inc_indent();
fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
dec_indent();
f << indent << "};\n";
f << indent << print_output << " << formatter(steps, steps);\n";
dec_indent();
f << indent << "}\n";
}

View file

@ -565,7 +565,7 @@ struct value : public expr_base<value<Bits>> {
}
value<Bits> neg() const {
return value<Bits> { 0u }.sub(*this);
return value<Bits>().sub(*this);
}
bool ucmp(const value<Bits> &other) const {
@ -763,102 +763,134 @@ std::ostream &operator<<(std::ostream &os, const value<Bits> &val) {
return os;
}
template<size_t Bits>
struct value_formatted {
const value<Bits> &val;
bool character;
bool justify_left;
char padding;
int width;
int base;
bool signed_;
bool plus;
// Must be kept in sync with `struct FmtPart` in kernel/fmt.h!
// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out.
struct fmt_part {
enum {
STRING = 0,
INTEGER = 1,
CHARACTER = 2,
TIME = 3,
} type;
value_formatted(const value<Bits> &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool plus) :
val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), plus(plus) {}
value_formatted(const value_formatted<Bits> &) = delete;
value_formatted<Bits> &operator=(const value_formatted<Bits> &rhs) = delete;
// STRING type
std::string str;
// INTEGER/CHARACTER types
// + value<Bits> val;
// INTEGER/CHARACTER/TIME types
enum {
RIGHT = 0,
LEFT = 1,
} justify; // = RIGHT;
char padding; // = '\0';
size_t width; // = 0;
// INTEGER type
unsigned base; // = 10;
bool signed_; // = false;
bool plus; // = false;
// TIME type
bool realtime; // = false;
// + int64_t itime;
// + double ftime;
// Format the part as a string.
//
// The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly.
template<size_t Bits>
std::string render(value<Bits> val, int64_t itime, double ftime)
{
// 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;
switch (type) {
case STRING:
return str;
case CHARACTER: {
buf.reserve(Bits/8);
for (int i = 0; i < Bits; i += 8) {
char ch = 0;
for (int j = 0; j < 8 && i + j < int(Bits); j++)
if (val.bit(i + j))
ch |= 1 << j;
if (ch != 0)
buf.append({ch});
}
std::reverse(buf.begin(), buf.end());
break;
}
case INTEGER: {
size_t width = Bits;
if (base != 10) {
width = 0;
for (size_t index = 0; index < Bits; index++)
if (val.bit(index))
width = index + 1;
}
if (base == 2) {
for (size_t i = width; i > 0; i--)
buf += (val.bit(i - 1) ? '1' : '0');
} else if (base == 8 || base == 16) {
size_t step = (base == 16) ? 4 : 3;
for (size_t index = 0; index < width; index += step) {
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];
}
std::reverse(buf.begin(), buf.end());
} else if (base == 10) {
bool negative = signed_ && val.is_neg();
if (negative)
val = val.neg();
if (val.is_zero())
buf += '0';
value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>();
while (!xval.is_zero()) {
value<(Bits > 4 ? Bits : 4)> quotient, remainder;
if (Bits >= 4)
std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u});
else
std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval);
buf += '0' + remainder.template trunc<4>().template get<uint8_t>();
xval = quotient;
}
if (negative || plus)
buf += negative ? '-' : '+';
std::reverse(buf.begin(), buf.end());
} else assert(false && "Unsupported base for fmt_part");
break;
}
case TIME: {
buf = realtime ? std::to_string(ftime) : std::to_string(itime);
break;
}
}
std::string str;
assert(width == 0 || padding != '\0');
if (justify == RIGHT && buf.size() < width) {
size_t pad_width = width - buf.size();
if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
str += buf.front();
buf.erase(0, 1);
}
str += std::string(pad_width, padding);
}
str += buf;
if (justify == LEFT && buf.size() < width)
str += std::string(width - buf.size(), padding);
return str;
}
};
template<size_t Bits>
std::ostream &operator<<(std::ostream &os, const value_formatted<Bits> &vf)
{
value<Bits> val = vf.val;
std::string buf;
// We might want to replace some of these bit() calls with direct
// chunk access if it turns out to be slow enough to matter.
if (!vf.character) {
size_t width = Bits;
if (vf.base != 10) {
width = 0;
for (size_t index = 0; index < Bits; index++)
if (val.bit(index))
width = index + 1;
}
if (vf.base == 2) {
for (size_t i = width; i > 0; i--)
buf += (val.bit(i - 1) ? '1' : '0');
} else if (vf.base == 8 || vf.base == 16) {
size_t step = (vf.base == 16) ? 4 : 3;
for (size_t index = 0; index < width; index += step) {
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];
}
std::reverse(buf.begin(), buf.end());
} else if (vf.base == 10) {
bool negative = vf.signed_ && val.is_neg();
if (negative)
val = val.neg();
if (val.is_zero())
buf += '0';
while (!val.is_zero()) {
value<Bits> quotient, remainder;
if (Bits >= 4)
std::tie(quotient, remainder) = val.udivmod(value<Bits>{10u});
else
std::tie(quotient, remainder) = std::make_pair(value<Bits>{0u}, val);
buf += '0' + remainder.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get<uint8_t>();
val = quotient;
}
if (negative || vf.plus)
buf += negative ? '-' : '+';
std::reverse(buf.begin(), buf.end());
} else assert(false);
} else {
buf.reserve(Bits/8);
for (int i = 0; i < Bits; i += 8) {
char ch = 0;
for (int j = 0; j < 8 && i + j < int(Bits); j++)
if (val.bit(i + j))
ch |= 1 << j;
if (ch != 0)
buf.append({ch});
}
std::reverse(buf.begin(), buf.end());
}
assert(vf.width == 0 || vf.padding != '\0');
if (!vf.justify_left && buf.size() < vf.width) {
size_t pad_width = vf.width - buf.size();
if (vf.padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
os << buf.front();
buf.erase(0, 1);
}
os << std::string(pad_width, vf.padding);
}
os << buf;
if (vf.justify_left && buf.size() < vf.width)
os << std::string(vf.width - buf.size(), vf.padding);
return os;
}
// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in
// the simulation.
struct observer {