3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-06-10 16:13:26 +00:00

Merge pull request #4134 from whitequark/cxxrtl-capture-print

CXXRTL: Allow capturing `$print` cell output
This commit is contained in:
Catherine 2024-01-17 13:13:15 +00:00 committed by GitHub
commit c4c55cb565
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 243 additions and 216 deletions

View file

@ -1072,9 +1072,23 @@ struct CxxrtlWorker {
dump_sigspec_rhs(cell->getPort(ID::EN)); dump_sigspec_rhs(cell->getPort(ID::EN));
f << " == value<1>{1u}) {\n"; f << " == value<1>{1u}) {\n";
inc_indent(); inc_indent();
f << indent << print_output; f << indent << "auto formatter = [&](int64_t itime, double ftime) {\n";
fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); inc_indent();
f << ";\n"; fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
dec_indent();
f << indent << "};\n";
f << indent << "if (performer) {\n";
inc_indent();
f << indent << "static const metadata_map attributes = ";
dump_metadata_map(cell->attributes);
f << ";\n";
f << indent << "performer->on_print(formatter(performer->time(), performer->realtime()), attributes);\n";
dec_indent();
f << indent << "} else {\n";
inc_indent();
f << indent << print_output << " << formatter(0, 0.0);\n";
dec_indent();
f << indent << "}\n";
dec_indent(); dec_indent();
f << indent << "}\n"; f << indent << "}\n";
} }
@ -1494,11 +1508,11 @@ struct CxxrtlWorker {
}; };
if (buffered_inputs) { if (buffered_inputs) {
// If we have any buffered inputs, there's no chance of converging immediately. // If we have any buffered inputs, there's no chance of converging immediately.
f << indent << mangle(cell) << access << "eval();\n"; f << indent << mangle(cell) << access << "eval(performer);\n";
f << indent << "converged = false;\n"; f << indent << "converged = false;\n";
assign_from_outputs(/*cell_converged=*/false); assign_from_outputs(/*cell_converged=*/false);
} else { } else {
f << indent << "if (" << mangle(cell) << access << "eval()) {\n"; f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n";
inc_indent(); inc_indent();
assign_from_outputs(/*cell_converged=*/true); assign_from_outputs(/*cell_converged=*/true);
dec_indent(); dec_indent();
@ -2381,7 +2395,8 @@ struct CxxrtlWorker {
dump_reset_method(module); dump_reset_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool eval() override {\n"; // No default argument, to prevent unintentional `return bb_foo::eval();` calls that drop performer.
f << indent << "bool eval(performer *performer) override {\n";
dump_eval_method(module); dump_eval_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
@ -2391,7 +2406,7 @@ struct CxxrtlWorker {
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool commit() override {\n"; f << indent << "bool commit() override {\n";
f << indent << indent << "null_observer observer;\n"; f << indent << indent << "observer observer;\n";
f << indent << indent << "return commit<>(observer);\n"; f << indent << indent << "return commit<>(observer);\n";
f << indent << "}\n"; f << indent << "}\n";
if (debug_info) { if (debug_info) {
@ -2478,7 +2493,7 @@ struct CxxrtlWorker {
f << "\n"; f << "\n";
f << indent << "void reset() override;\n"; f << indent << "void reset() override;\n";
f << "\n"; f << "\n";
f << indent << "bool eval() override;\n"; f << indent << "bool eval(performer *performer = nullptr) override;\n";
f << "\n"; f << "\n";
f << indent << "template<class ObserverT>\n"; f << indent << "template<class ObserverT>\n";
f << indent << "bool commit(ObserverT &observer) {\n"; f << indent << "bool commit(ObserverT &observer) {\n";
@ -2486,7 +2501,7 @@ struct CxxrtlWorker {
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool commit() override {\n"; f << indent << "bool commit() override {\n";
f << indent << indent << "null_observer observer;\n"; f << indent << indent << "observer observer;\n";
f << indent << indent << "return commit<>(observer);\n"; f << indent << indent << "return commit<>(observer);\n";
f << indent << "}\n"; f << indent << "}\n";
if (debug_info) { if (debug_info) {
@ -2517,7 +2532,7 @@ struct CxxrtlWorker {
dump_reset_method(module); dump_reset_method(module);
f << indent << "}\n"; f << indent << "}\n";
f << "\n"; f << "\n";
f << indent << "bool " << mangle(module) << "::eval() {\n"; f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n";
dump_eval_method(module); dump_eval_method(module);
f << indent << "}\n"; f << indent << "}\n";
if (debug_info) { if (debug_info) {
@ -2541,7 +2556,6 @@ struct CxxrtlWorker {
RTLIL::Module *top_module = nullptr; RTLIL::Module *top_module = nullptr;
std::vector<RTLIL::Module*> modules; std::vector<RTLIL::Module*> modules;
TopoSort<RTLIL::Module*> topo_design; TopoSort<RTLIL::Module*> topo_design;
bool has_prints = false;
for (auto module : design->modules()) { for (auto module : design->modules()) {
if (!design->selected_module(module)) if (!design->selected_module(module))
continue; continue;
@ -2554,8 +2568,6 @@ struct CxxrtlWorker {
topo_design.node(module); topo_design.node(module);
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
if (cell->type == ID($print))
has_prints = true;
if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell)) if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell))
continue; continue;
RTLIL::Module *cell_module = design->module(cell->type); RTLIL::Module *cell_module = design->module(cell->type);
@ -2614,8 +2626,6 @@ struct CxxrtlWorker {
f << "#include \"" << basename(intf_filename) << "\"\n"; f << "#include \"" << basename(intf_filename) << "\"\n";
else else
f << "#include <cxxrtl/cxxrtl.h>\n"; f << "#include <cxxrtl/cxxrtl.h>\n";
if (has_prints)
f << "#include <iostream>\n";
f << "\n"; f << "\n";
f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n"; f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n";
f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n";
@ -3293,7 +3303,7 @@ struct CxxrtlBackend : public Backend {
log(" value<8> p_i_data;\n"); log(" value<8> p_i_data;\n");
log(" wire<8> p_o_data;\n"); log(" wire<8> p_o_data;\n");
log("\n"); log("\n");
log(" bool eval() override;\n"); log(" bool eval(performer *performer) override;\n");
log(" template<class ObserverT>\n"); log(" template<class ObserverT>\n");
log(" bool commit(ObserverT &observer);\n"); log(" bool commit(ObserverT &observer);\n");
log(" bool commit() override;\n"); log(" bool commit() override;\n");
@ -3308,11 +3318,11 @@ struct CxxrtlBackend : public Backend {
log(" namespace cxxrtl_design {\n"); log(" namespace cxxrtl_design {\n");
log("\n"); log("\n");
log(" struct stderr_debug : public bb_p_debug {\n"); log(" struct stderr_debug : public bb_p_debug {\n");
log(" bool eval() override {\n"); log(" bool eval(performer *performer) override {\n");
log(" if (posedge_p_clk() && p_en)\n"); log(" if (posedge_p_clk() && p_en)\n");
log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n"); log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n");
log(" p_o_data.next = p_i_data;\n"); log(" p_o_data.next = p_i_data;\n");
log(" return bb_p_debug::eval();\n"); log(" return bb_p_debug::eval(performer);\n");
log(" }\n"); log(" }\n");
log(" };\n"); log(" };\n");
log("\n"); log("\n");
@ -3413,7 +3423,7 @@ struct CxxrtlBackend : public Backend {
log(" -print-output <stream>\n"); log(" -print-output <stream>\n");
log(" $print cells in the generated code direct their output to <stream>.\n"); log(" $print cells in the generated code direct their output to <stream>.\n");
log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n"); log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n");
log(" \"std::cout\" is used.\n"); log(" \"std::cout\" is used. explicitly provided performer overrides this.\n");
log("\n"); log("\n");
log(" -nohierarchy\n"); log(" -nohierarchy\n");
log(" use design hierarchy as-is. in most designs, a top module should be\n"); log(" use design hierarchy as-is. in most designs, a top module should be\n");

View file

@ -39,6 +39,7 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <sstream> #include <sstream>
#include <iostream>
// `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements. // `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements.
#include <cxxrtl/capi/cxxrtl_capi.h> #include <cxxrtl/capi/cxxrtl_capi.h>
@ -565,7 +566,7 @@ struct value : public expr_base<value<Bits>> {
} }
value<Bits> neg() const { value<Bits> neg() const {
return value<Bits> { 0u }.sub(*this); return value<Bits>().sub(*this);
} }
bool ucmp(const value<Bits> &other) const { bool ucmp(const value<Bits> &other) const {
@ -763,121 +764,132 @@ std::ostream &operator<<(std::ostream &os, const value<Bits> &val) {
return os; return os;
} }
template<size_t Bits> // Must be kept in sync with `struct FmtPart` in kernel/fmt.h!
struct value_formatted { // Default member initializers would make this a non-aggregate-type in C++11, so they are commented out.
const value<Bits> &val; struct fmt_part {
bool character; enum {
bool justify_left; STRING = 0,
char padding; INTEGER = 1,
int width; CHARACTER = 2,
int base; TIME = 3,
bool signed_; } type;
bool plus;
value_formatted(const value<Bits> &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool plus) : // STRING type
val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), plus(plus) {} std::string str;
value_formatted(const value_formatted<Bits> &) = delete;
value_formatted<Bits> &operator=(const value_formatted<Bits> &rhs) = delete;
};
template<size_t Bits> // INTEGER/CHARACTER types
std::ostream &operator<<(std::ostream &os, const value_formatted<Bits> &vf) // + value<Bits> val;
{
value<Bits> val = vf.val;
std::string buf; // INTEGER/CHARACTER/TIME types
enum {
RIGHT = 0,
LEFT = 1,
} justify; // = RIGHT;
char padding; // = '\0';
size_t width; // = 0;
// We might want to replace some of these bit() calls with direct // INTEGER type
// chunk access if it turns out to be slow enough to matter. unsigned base; // = 10;
bool signed_; // = false;
bool plus; // = false;
if (!vf.character) { // TIME type
size_t width = Bits; bool realtime; // = false;
if (vf.base != 10) { // + int64_t itime;
width = 0; // + double ftime;
for (size_t index = 0; index < Bits; index++)
if (val.bit(index))
width = index + 1;
}
if (vf.base == 2) { // Format the part as a string.
for (size_t i = width; i > 0; i--) //
buf += (val.bit(i - 1) ? '1' : '0'); // The values of `itime` and `ftime` are used for `$time` and `$realtime`, correspondingly.
} else if (vf.base == 8 || vf.base == 16) { template<size_t Bits>
size_t step = (vf.base == 16) ? 4 : 3; std::string render(value<Bits> val, int64_t itime, double ftime)
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); // We might want to replace some of these bit() calls with direct
if (step == 4) // chunk access if it turns out to be slow enough to matter.
value |= val.bit(index + 3) << 3; std::string buf;
buf += "0123456789abcdef"[value]; 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;
} }
std::reverse(buf.begin(), buf.end());
} else if (vf.base == 10) { case INTEGER: {
bool negative = vf.signed_ && val.is_neg(); size_t width = Bits;
if (negative) if (base != 10) {
val = val.neg(); width = 0;
if (val.is_zero()) for (size_t index = 0; index < Bits; index++)
buf += '0'; if (val.bit(index))
while (!val.is_zero()) { width = index + 1;
value<Bits> quotient, remainder; }
if (Bits >= 4)
std::tie(quotient, remainder) = val.udivmod(value<Bits>{10u}); if (base == 2) {
else for (size_t i = width; i > 0; i--)
std::tie(quotient, remainder) = std::make_pair(value<Bits>{0u}, val); buf += (val.bit(i - 1) ? '1' : '0');
buf += '0' + remainder.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get<uint8_t>(); } else if (base == 8 || base == 16) {
val = quotient; 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;
} }
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'); std::string str;
if (!vf.justify_left && buf.size() < vf.width) { assert(width == 0 || padding != '\0');
size_t pad_width = vf.width - buf.size(); if (justify == RIGHT && buf.size() < width) {
if (vf.padding == '0' && (buf.front() == '+' || buf.front() == '-')) { size_t pad_width = width - buf.size();
os << buf.front(); if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) {
buf.erase(0, 1); str += buf.front();
buf.erase(0, 1);
}
str += std::string(pad_width, padding);
} }
os << std::string(pad_width, vf.padding); str += buf;
if (justify == LEFT && buf.size() < width)
str += std::string(width - buf.size(), padding);
return str;
} }
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 {
// Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks
// at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and
// `base` points to the first chunk.
virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) = 0;
// Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]`
// with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to
// the memory element chunk count and `base` points to the first chunk of the first element of the memory.
virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0;
};
// The `null_observer` class has the same interface as `observer`, but has no invocation overhead, since its methods
// are final and have no implementation. This allows the observer feature to be zero-cost when not in use.
struct null_observer final: observer {
void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override {}
void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {}
}; };
template<size_t Bits> template<size_t Bits>
@ -916,12 +928,11 @@ struct wire {
// This method intentionally takes a mandatory argument (to make it more difficult to misuse in // This method intentionally takes a mandatory argument (to make it more difficult to misuse in
// black box implementations, leading to missed observer events). It is generic over its argument // black box implementations, leading to missed observer events). It is generic over its argument
// to make sure the `on_commit` call is devirtualized. This is somewhat awkward but lets us keep // to allow the `on_update` method to be non-virtual.
// a single implementation for both this method and the one in `memory`.
template<class ObserverT> template<class ObserverT>
bool commit(ObserverT &observer) { bool commit(ObserverT &observer) {
if (curr != next) { if (curr != next) {
observer.on_commit(curr.chunks, curr.data, next.data); observer.on_update(curr.chunks, curr.data, next.data);
curr = next; curr = next;
return true; return true;
} }
@ -1003,7 +1014,7 @@ struct memory {
value<Width> elem = data[entry.index]; value<Width> elem = data[entry.index];
elem = elem.update(entry.val, entry.mask); elem = elem.update(entry.val, entry.mask);
if (data[entry.index] != elem) { if (data[entry.index] != elem) {
observer.on_commit(value<Width>::chunks, data[0].data, elem.data, entry.index); observer.on_update(value<Width>::chunks, data[0].data, elem.data, entry.index);
changed |= true; changed |= true;
} }
data[entry.index] = elem; data[entry.index] = elem;
@ -1062,6 +1073,33 @@ struct metadata {
typedef std::map<std::string, metadata> metadata_map; typedef std::map<std::string, metadata> metadata_map;
// An object that can be passed to a `eval()` method in order to act on side effects.
struct performer {
// Called to evaluate a Verilog `$time` expression.
virtual int64_t time() const { return 0; }
// Called to evaluate a Verilog `$realtime` expression.
virtual double realtime() const { return time(); }
// Called when a `$print` cell is triggered.
virtual void on_print(const std::string &output, const metadata_map &attributes) { std::cout << output; }
};
// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in
// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and
// a comparatively heavyweight template-based solution is justified.
struct observer {
// Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks
// at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and
// `base` points to the first chunk.
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {}
// Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]`
// with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to
// the memory element chunk count and `base` points to the first chunk of the first element of the memory.
void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {}
};
// Tag class to disambiguate values/wires and their aliases. // Tag class to disambiguate values/wires and their aliases.
struct debug_alias {}; struct debug_alias {};
@ -1304,17 +1342,14 @@ struct module {
virtual void reset() = 0; virtual void reset() = 0;
virtual bool eval() = 0; virtual bool eval(performer *performer = nullptr) = 0;
virtual bool commit() = 0; virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls
unsigned int steps = 0; size_t step(performer *performer = nullptr) {
size_t step() {
++steps;
size_t deltas = 0; size_t deltas = 0;
bool converged = false; bool converged = false;
do { do {
converged = eval(); converged = eval(performer);
deltas++; deltas++;
} while (commit() && !converged); } while (commit() && !converged);
return deltas; return deltas;

View file

@ -556,22 +556,20 @@ public:
bool record_incremental(ModuleT &module) { bool record_incremental(ModuleT &module) {
assert(streaming); assert(streaming);
struct : public observer { struct {
std::unordered_map<const chunk_t*, spool::ident_t> *ident_lookup; std::unordered_map<const chunk_t*, spool::ident_t> *ident_lookup;
spool::writer *writer; spool::writer *writer;
CXXRTL_ALWAYS_INLINE CXXRTL_ALWAYS_INLINE
void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override { void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {
writer->write_change(ident_lookup->at(base), chunks, value); writer->write_change(ident_lookup->at(base), chunks, value);
} }
CXXRTL_ALWAYS_INLINE CXXRTL_ALWAYS_INLINE
void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override { void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {
writer->write_change(ident_lookup->at(base), chunks, value, index); writer->write_change(ident_lookup->at(base), chunks, value, index);
} }
} record_observer; } record_observer = { &ident_lookup, &writer };
record_observer.ident_lookup = &ident_lookup;
record_observer.writer = &writer;
writer.write_sample(/*incremental=*/true, pointer++, timestamp); writer.write_sample(/*incremental=*/true, pointer++, timestamp);
for (auto input_index : inputs) { for (auto input_index : inputs) {

View file

@ -569,82 +569,60 @@ std::vector<VerilogFmtArg> Fmt::emit_verilog() const
return args; return args;
} }
void Fmt::emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> emit_sig) const std::string escape_cxx_string(const std::string &input)
{ {
for (auto &part : parts) { std::string output = "\"";
switch (part.type) { for (auto c : input) {
case FmtPart::STRING: if (::isprint(c)) {
f << " << \""; if (c == '\\')
for (char c : part.str) { output.push_back('\\');
switch (c) { output.push_back(c);
case '\\': } else {
YS_FALLTHROUGH char l = c & 0xf, h = (c >> 4) & 0xf;
case '"': output.append("\\x");
f << '\\' << c; output.push_back((h < 10 ? '0' + h : 'a' + h - 10));
break; output.push_back((l < 10 ? '0' + l : 'a' + l - 10));
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();
} }
} }
output.push_back('"');
if (output.find('\0') != std::string::npos) {
output.insert(0, "std::string {");
output.append(stringf(", %zu}", input.size()));
}
return output;
}
void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function<void(const RTLIL::SigSpec &)> emit_sig) const
{
os << indent << "std::string buf;\n";
for (auto &part : parts) {
os << indent << "buf += fmt_part { ";
os << "fmt_part::";
switch (part.type) {
case FmtPart::STRING: os << "STRING"; break;
case FmtPart::INTEGER: os << "INTEGER"; break;
case FmtPart::CHARACTER: os << "CHARACTER"; break;
case FmtPart::TIME: os << "TIME"; break;
}
os << ", ";
os << escape_cxx_string(part.str) << ", ";
os << "fmt_part::";
switch (part.justify) {
case FmtPart::LEFT: os << "LEFT"; break;
case FmtPart::RIGHT: os << "RIGHT"; break;
}
os << ", ";
os << "(char)" << (int)part.padding << ", ";
os << part.width << ", ";
os << part.base << ", ";
os << part.signed_ << ", ";
os << part.plus << ", ";
os << part.realtime;
os << " }.render(";
emit_sig(part.sig);
os << ", itime, ftime);\n";
}
os << indent << "return buf;\n";
} }
std::string Fmt::render() const std::string Fmt::render() const

View file

@ -50,6 +50,7 @@ struct VerilogFmtArg {
// RTLIL format part, such as the substitutions in: // RTLIL format part, such as the substitutions in:
// "foo {4:> 4du} bar {2:<01hs}" // "foo {4:> 4du} bar {2:<01hs}"
// Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h!
struct FmtPart { struct FmtPart {
enum { enum {
STRING = 0, STRING = 0,
@ -71,7 +72,7 @@ struct FmtPart {
} justify = RIGHT; } justify = RIGHT;
char padding = '\0'; char padding = '\0';
size_t width = 0; size_t width = 0;
// INTEGER type // INTEGER type
unsigned base = 10; unsigned base = 10;
bool signed_ = false; bool signed_ = false;
@ -93,7 +94,7 @@ public:
void parse_verilog(const std::vector<VerilogFmtArg> &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name); 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; std::vector<VerilogFmtArg> emit_verilog() const;
void emit_cxxrtl(std::ostream &f, std::function<void(const RTLIL::SigSpec &)> emit_sig) const; void emit_cxxrtl(std::ostream &os, std::string indent, std::function<void(const RTLIL::SigSpec &)> emit_sig) const;
std::string render() const; std::string render() const;

View file

@ -2,8 +2,13 @@
int main() int main()
{ {
struct : public performer {
int64_t time() const override { return 1; }
void on_print(const std::string &output, const cxxrtl::metadata_map &) override { std::cerr << output; }
} performer;
cxxrtl_design::p_always__full uut; cxxrtl_design::p_always__full uut;
uut.p_clk.set(!uut.p_clk); uut.p_clk.set(!uut.p_clk);
uut.step(); uut.step(&performer);
return 0; return 0;
} }