cxxrtl: first pass of $print impl

This commit is contained in:
Charlotte 2023-06-28 11:51:17 +10:00 committed by Marcelina Kościelnicka
parent 202c3776e2
commit 095b093f4a
4 changed files with 353 additions and 3 deletions

View File

@ -518,6 +518,14 @@ struct value : public expr_base<value<Bits>> {
return count;
}
size_t chunks_used() const {
for (size_t n = chunks; n > 0; n--) {
if (data[n - 1] != 0)
return n;
}
return 0;
}
template<bool Invert, bool CarryIn>
std::pair<value<Bits>, bool /*CarryOut*/> alu(const value<Bits> &other) const {
value<Bits> result;
@ -575,6 +583,140 @@ struct value : public expr_base<value<Bits>> {
result.data[result.chunks - 1] &= result.msb_mask;
return result;
}
// parallel to BigUnsigned::divideWithRemainder; quotient is stored in q,
// *this is left with the remainder. See that function for commentary describing
// how/why this works.
void divideWithRemainder(const value<Bits> &b, value<Bits> &q) {
assert(this != &q);
if (this == &b || &q == &b) {
value<Bits> tmpB(b);
divideWithRemainder(tmpB, q);
return;
}
q = value<Bits> {0u};
size_t blen = b.chunks_used();
if (blen == 0) {
return;
}
size_t len = chunks_used();
if (len < blen) {
return;
}
size_t i, j, k;
size_t i2;
chunk_t temp;
bool borrowIn, borrowOut;
size_t origLen = len;
len++;
chunk::type blk[len];
std::copy(data, data + origLen, blk);
blk[origLen] = 0;
chunk::type subtractBuf[len];
std::fill(subtractBuf, subtractBuf + len, 0);
size_t qlen = origLen - blen + 1;
i = qlen;
while (i > 0) {
i--;
i2 = chunk::bits;
while (i2 > 0) {
i2--;
for (j = 0, k = i, borrowIn = false; j <= blen; j++, k++) {
temp = blk[k] - getShiftedBlock(b, j, i2);
borrowOut = (temp > blk[k]);
if (borrowIn) {
borrowOut |= (temp == 0);
temp--;
}
subtractBuf[k] = temp;
borrowIn = borrowOut;
}
for (; k < origLen && borrowIn; k++) {
borrowIn = (blk[k] == 0);
subtractBuf[k] = blk[k] - 1;
}
if (!borrowIn) {
q.data[i] |= (chunk::type(1) << i2);
while (k > i) {
k--;
blk[k] = subtractBuf[k];
}
}
}
}
std::copy(blk, blk + origLen, data);
std::fill(blk + origLen, blk + chunks, 0);
}
static chunk::type getShiftedBlock(const value<Bits> &num, size_t x, size_t y) {
chunk::type part1 = (x == 0 || y == 0) ? 0 : (num.data[x - 1] >> (chunk::bits - y));
chunk::type part2 = (x == num.chunks) ? 0 : (num.data[x] << y);
return part1 | part2;
}
// parallel to BigInteger::divideWithRemainder; quotient is stored in q,
// *this is left with the remainder. See that function for commentary describing
// how/why this works.
void signedDivideWithRemainder(const value<Bits> &b, value<Bits> &q) {
assert(this != &q);
if (this == &b || &q == &b) {
value<Bits> tmpB(b);
signedDivideWithRemainder(tmpB, q);
return;
}
if (b.is_zero()) {
q = value<Bits>{0u};
return;
}
if (is_zero()) {
q = value<Bits>{0u};
return;
}
// BigInteger has a separate 'mag' to its sign. We don't, so the lazy
// approach is to improvise said.
auto mag = *this;
bool neg = mag.is_neg();
if (neg)
mag = mag.neg();
auto bmag = b;
bool bneg = bmag.is_neg();
if (bneg)
bmag = bmag.neg();
bool qneg = false;
if (neg != b.is_neg()) {
qneg = true;
mag = mag.sub(value<Bits>{1u});
}
mag.divideWithRemainder(bmag, q);
if (neg != bneg) {
q = q.add(value<Bits>{1u});
mag = bmag.sub(mag);
mag = mag.sub(value<Bits>{1u});
}
neg = bneg;
*this = neg ? mag.neg() : mag;
if (qneg)
q = q.neg();
}
};
// Expression template for a slice, usable as lvalue or rvalue, and composable with other expression templates here.
@ -707,6 +849,101 @@ 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 lzero;
bool plus;
value_formatted(const value<Bits> &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool lzero, bool plus) :
val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), lzero(lzero), plus(plus) {}
value_formatted(const value_formatted<Bits> &) = delete;
value_formatted<Bits> &operator=(const value_formatted<Bits> &rhs) = delete;
};
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.lzero && 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';
// TODO: rigorously check our signed behaviour here
while (!val.is_zero()) {
value<Bits> quotient;
val.signedDivideWithRemainder(value<Bits>{10u}, quotient);
buf += '0' + val.template slice<3, 0>().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;
}
template<size_t Bits>
struct wire {
static constexpr size_t bits = Bits;

View File

@ -24,6 +24,7 @@
#include "kernel/celltypes.h"
#include "kernel/mem.h"
#include "kernel/log.h"
#include "kernel/fmt.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@ -217,7 +218,7 @@ bool is_internal_cell(RTLIL::IdString type)
bool is_effectful_cell(RTLIL::IdString type)
{
return type.isPublic();
return type.isPublic() || type == ID($print);
}
bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell)
@ -1036,6 +1037,17 @@ struct CxxrtlWorker {
f << ".val()";
}
void dump_print(const RTLIL::Cell *cell)
{
Fmt fmt = {};
fmt.parse_rtlil(cell);
// TODO: we may want to configure the output stream
f << indent << "std::cout";
fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
f << ";\n";
}
void dump_inlined_cells(const std::vector<const RTLIL::Cell*> &cells)
{
if (cells.empty()) {
@ -1202,6 +1214,34 @@ struct CxxrtlWorker {
f << " = ";
dump_cell_expr(cell, for_debug);
f << ";\n";
// $print cell
} else if (cell->type == ID($print)) {
log_assert(!for_debug);
f << indent << "if (";
if (cell->getParam(ID::TRG_ENABLE).as_bool()) {
f << '(';
for (size_t i = 0; i < (size_t)cell->getParam(ID::TRG_WIDTH).as_int(); i++) {
RTLIL::SigBit trg_bit = cell->getPort(ID::TRG)[i];
trg_bit = sigmaps[trg_bit.wire->module](trg_bit);
log_assert(trg_bit.wire);
if (i != 0)
f << " || ";
if (cell->getParam(ID::TRG_POLARITY)[i] == State::S1)
f << "posedge_";
else
f << "negedge_";
f << mangle(trg_bit);
}
f << ") && ";
}
dump_sigspec_rhs(cell->getPort(ID::EN));
f << " == value<1>{1u}) {\n";
inc_indent();
dump_print(cell);
dec_indent();
f << indent << "}\n";
// Flip-flops
} else if (is_ff_cell(cell->type)) {
log_assert(!for_debug);
@ -2601,6 +2641,16 @@ struct CxxrtlWorker {
register_edge_signal(sigmap, cell->getPort(ID::CLK),
cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn);
}
// $print cells may be triggered on posedge/negedge events.
if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool()) {
for (size_t i = 0; i < (size_t)cell->getParam(ID::TRG_WIDTH).as_int(); i++) {
RTLIL::SigBit trg = cell->getPort(ID::TRG).extract(i, 1);
if (is_valid_clock(trg))
register_edge_signal(sigmap, trg,
cell->parameters[ID::TRG_POLARITY][i] == RTLIL::S1 ? RTLIL::STp : RTLIL::STn);
}
}
}
for (auto &mem : memories) {

View File

@ -29,7 +29,7 @@ void Fmt::append_string(const std::string &str) {
parts.push_back(part);
}
void Fmt::parse_rtlil(RTLIL::Cell *cell) {
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();
@ -465,6 +465,67 @@ std::vector<VerilogFmtArg> Fmt::emit_verilog() const
return args;
}
void Fmt::emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> 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.lzero;
f << ", " << part.plus;
f << ')';
break;
}
}
}
}
std::string Fmt::render() const
{
std::string str;

View File

@ -76,12 +76,14 @@ struct Fmt {
void append_string(const std::string &str);
void parse_rtlil(RTLIL::Cell *cell);
void parse_rtlil(const RTLIL::Cell *cell);
void emit_rtlil(RTLIL::Cell *cell) const;
void parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name);
std::vector<VerilogFmtArg> emit_verilog() const;
void emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> emit_sig) const;
std::string render() const;
};