mirror of https://github.com/YosysHQ/yosys.git
Merge pull request #4134 from whitequark/cxxrtl-capture-print
CXXRTL: Allow capturing `$print` cell output
This commit is contained in:
commit
c4c55cb565
|
@ -1072,9 +1072,23 @@ 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 << "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();
|
||||
f << indent << "}\n";
|
||||
}
|
||||
|
@ -1494,11 +1508,11 @@ struct CxxrtlWorker {
|
|||
};
|
||||
if (buffered_inputs) {
|
||||
// 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";
|
||||
assign_from_outputs(/*cell_converged=*/false);
|
||||
} else {
|
||||
f << indent << "if (" << mangle(cell) << access << "eval()) {\n";
|
||||
f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n";
|
||||
inc_indent();
|
||||
assign_from_outputs(/*cell_converged=*/true);
|
||||
dec_indent();
|
||||
|
@ -2381,7 +2395,8 @@ struct CxxrtlWorker {
|
|||
dump_reset_method(module);
|
||||
f << indent << "}\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);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
|
@ -2391,7 +2406,7 @@ struct CxxrtlWorker {
|
|||
f << indent << "}\n";
|
||||
f << "\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 << "}\n";
|
||||
if (debug_info) {
|
||||
|
@ -2478,7 +2493,7 @@ struct CxxrtlWorker {
|
|||
f << "\n";
|
||||
f << indent << "void reset() override;\n";
|
||||
f << "\n";
|
||||
f << indent << "bool eval() override;\n";
|
||||
f << indent << "bool eval(performer *performer = nullptr) override;\n";
|
||||
f << "\n";
|
||||
f << indent << "template<class ObserverT>\n";
|
||||
f << indent << "bool commit(ObserverT &observer) {\n";
|
||||
|
@ -2486,7 +2501,7 @@ struct CxxrtlWorker {
|
|||
f << indent << "}\n";
|
||||
f << "\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 << "}\n";
|
||||
if (debug_info) {
|
||||
|
@ -2517,7 +2532,7 @@ struct CxxrtlWorker {
|
|||
dump_reset_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
f << indent << "bool " << mangle(module) << "::eval() {\n";
|
||||
f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n";
|
||||
dump_eval_method(module);
|
||||
f << indent << "}\n";
|
||||
if (debug_info) {
|
||||
|
@ -2541,7 +2556,6 @@ struct CxxrtlWorker {
|
|||
RTLIL::Module *top_module = nullptr;
|
||||
std::vector<RTLIL::Module*> modules;
|
||||
TopoSort<RTLIL::Module*> topo_design;
|
||||
bool has_prints = false;
|
||||
for (auto module : design->modules()) {
|
||||
if (!design->selected_module(module))
|
||||
continue;
|
||||
|
@ -2554,8 +2568,6 @@ struct CxxrtlWorker {
|
|||
|
||||
topo_design.node(module);
|
||||
for (auto cell : module->cells()) {
|
||||
if (cell->type == ID($print))
|
||||
has_prints = true;
|
||||
if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell))
|
||||
continue;
|
||||
RTLIL::Module *cell_module = design->module(cell->type);
|
||||
|
@ -2614,8 +2626,6 @@ struct CxxrtlWorker {
|
|||
f << "#include \"" << basename(intf_filename) << "\"\n";
|
||||
else
|
||||
f << "#include <cxxrtl/cxxrtl.h>\n";
|
||||
if (has_prints)
|
||||
f << "#include <iostream>\n";
|
||||
f << "\n";
|
||||
f << "#if defined(CXXRTL_INCLUDE_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(" wire<8> p_o_data;\n");
|
||||
log("\n");
|
||||
log(" bool eval() override;\n");
|
||||
log(" bool eval(performer *performer) override;\n");
|
||||
log(" template<class ObserverT>\n");
|
||||
log(" bool commit(ObserverT &observer);\n");
|
||||
log(" bool commit() override;\n");
|
||||
|
@ -3308,11 +3318,11 @@ struct CxxrtlBackend : public Backend {
|
|||
log(" namespace cxxrtl_design {\n");
|
||||
log("\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(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\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");
|
||||
|
@ -3413,7 +3423,7 @@ struct CxxrtlBackend : public Backend {
|
|||
log(" -print-output <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(" \"std::cout\" is used.\n");
|
||||
log(" \"std::cout\" is used. explicitly provided performer overrides this.\n");
|
||||
log("\n");
|
||||
log(" -nohierarchy\n");
|
||||
log(" use design hierarchy as-is. in most designs, a top module should be\n");
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
#include <memory>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
// `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements.
|
||||
#include <cxxrtl/capi/cxxrtl_capi.h>
|
||||
|
@ -565,7 +566,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,121 +764,132 @@ 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;
|
||||
|
||||
template<size_t Bits>
|
||||
std::ostream &operator<<(std::ostream &os, const value_formatted<Bits> &vf)
|
||||
{
|
||||
value<Bits> val = vf.val;
|
||||
// INTEGER/CHARACTER types
|
||||
// + value<Bits> 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
|
||||
// chunk access if it turns out to be slow enough to matter.
|
||||
// INTEGER type
|
||||
unsigned base; // = 10;
|
||||
bool signed_; // = false;
|
||||
bool plus; // = false;
|
||||
|
||||
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;
|
||||
}
|
||||
// TIME type
|
||||
bool realtime; // = false;
|
||||
// + int64_t itime;
|
||||
// + double ftime;
|
||||
|
||||
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];
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
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>
|
||||
|
@ -916,12 +928,11 @@ struct wire {
|
|||
|
||||
// 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
|
||||
// to make sure the `on_commit` call is devirtualized. This is somewhat awkward but lets us keep
|
||||
// a single implementation for both this method and the one in `memory`.
|
||||
// to allow the `on_update` method to be non-virtual.
|
||||
template<class ObserverT>
|
||||
bool commit(ObserverT &observer) {
|
||||
if (curr != next) {
|
||||
observer.on_commit(curr.chunks, curr.data, next.data);
|
||||
observer.on_update(curr.chunks, curr.data, next.data);
|
||||
curr = next;
|
||||
return true;
|
||||
}
|
||||
|
@ -1003,7 +1014,7 @@ struct memory {
|
|||
value<Width> elem = data[entry.index];
|
||||
elem = elem.update(entry.val, entry.mask);
|
||||
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;
|
||||
}
|
||||
data[entry.index] = elem;
|
||||
|
@ -1062,6 +1073,33 @@ struct metadata {
|
|||
|
||||
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.
|
||||
struct debug_alias {};
|
||||
|
||||
|
@ -1304,17 +1342,14 @@ struct module {
|
|||
|
||||
virtual void reset() = 0;
|
||||
|
||||
virtual bool eval() = 0;
|
||||
virtual bool commit() = 0;
|
||||
virtual bool eval(performer *performer = nullptr) = 0;
|
||||
virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls
|
||||
|
||||
unsigned int steps = 0;
|
||||
|
||||
size_t step() {
|
||||
++steps;
|
||||
size_t step(performer *performer = nullptr) {
|
||||
size_t deltas = 0;
|
||||
bool converged = false;
|
||||
do {
|
||||
converged = eval();
|
||||
converged = eval(performer);
|
||||
deltas++;
|
||||
} while (commit() && !converged);
|
||||
return deltas;
|
||||
|
|
|
@ -556,22 +556,20 @@ public:
|
|||
bool record_incremental(ModuleT &module) {
|
||||
assert(streaming);
|
||||
|
||||
struct : public observer {
|
||||
struct {
|
||||
std::unordered_map<const chunk_t*, spool::ident_t> *ident_lookup;
|
||||
spool::writer *writer;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
} record_observer;
|
||||
record_observer.ident_lookup = &ident_lookup;
|
||||
record_observer.writer = &writer;
|
||||
} record_observer = { &ident_lookup, &writer };
|
||||
|
||||
writer.write_sample(/*incremental=*/true, pointer++, timestamp);
|
||||
for (auto input_index : inputs) {
|
||||
|
|
124
kernel/fmt.cc
124
kernel/fmt.cc
|
@ -569,82 +569,60 @@ 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
|
||||
std::string escape_cxx_string(const std::string &input)
|
||||
{
|
||||
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 output = "\"";
|
||||
for (auto c : input) {
|
||||
if (::isprint(c)) {
|
||||
if (c == '\\')
|
||||
output.push_back('\\');
|
||||
output.push_back(c);
|
||||
} else {
|
||||
char l = c & 0xf, h = (c >> 4) & 0xf;
|
||||
output.append("\\x");
|
||||
output.push_back((h < 10 ? '0' + h : 'a' + h - 10));
|
||||
output.push_back((l < 10 ? '0' + l : 'a' + l - 10));
|
||||
}
|
||||
}
|
||||
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
|
||||
|
|
|
@ -50,6 +50,7 @@ struct VerilogFmtArg {
|
|||
|
||||
// RTLIL format part, such as the substitutions in:
|
||||
// "foo {4:> 4du} bar {2:<01hs}"
|
||||
// Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h!
|
||||
struct FmtPart {
|
||||
enum {
|
||||
STRING = 0,
|
||||
|
@ -71,7 +72,7 @@ struct FmtPart {
|
|||
} justify = RIGHT;
|
||||
char padding = '\0';
|
||||
size_t width = 0;
|
||||
|
||||
|
||||
// INTEGER type
|
||||
unsigned base = 10;
|
||||
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);
|
||||
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;
|
||||
|
||||
|
|
|
@ -2,8 +2,13 @@
|
|||
|
||||
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;
|
||||
uut.p_clk.set(!uut.p_clk);
|
||||
uut.step();
|
||||
uut.step(&performer);
|
||||
return 0;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue