3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-04-05 09:04:08 +00:00

cxxrtl: introduce performer, a context object for eval(). (breaking change)

At the moment the only thing it allows is redirecting `$print` cell
output in a context-dependent manner. In the future, it will allow
customizing handling of `$check` cells (where the default is to abort),
of out-of-range memory accesses, and other runtime conditions with
effects.

This context object also allows a suitably written testbench to add
Verilog-compliant `$time`/`$realtime` handling, albeit it involves
the ceremony of defining a `performer` subclass. Most people will
want something like this to customize `$time`:

    int64_t time = 0;
    struct : public performer {
      int64_t *time_ptr;
      int64_t time() const override { return *time_ptr; }
    } performer = { &time };

    p_top.step(&performer);
This commit is contained in:
Catherine 2024-01-16 11:12:32 +00:00
parent 02e3d508fa
commit 905f07c03f
2 changed files with 38 additions and 20 deletions

View file

@ -1077,7 +1077,15 @@ struct CxxrtlWorker {
fmt.emit_cxxrtl(f, indent, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); });
dec_indent();
f << indent << "};\n";
f << indent << print_output << " << formatter(0, 0.0);\n";
f << indent << "if (performer) {\n";
inc_indent();
f << indent << "performer->on_print(formatter(performer->time(), performer->realtime()));\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";
}
@ -1497,11 +1505,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();
@ -2384,7 +2392,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";
@ -2481,7 +2490,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";
@ -2520,7 +2529,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) {
@ -2544,7 +2553,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;
@ -2557,8 +2565,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);
@ -2617,8 +2623,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";
@ -3296,7 +3300,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");
@ -3311,11 +3315,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");
@ -3416,7 +3420,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");

View file

@ -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>
@ -891,8 +892,21 @@ struct fmt_part {
}
};
// 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) { 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.
// 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
@ -1328,14 +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
size_t step() {
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;