3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-04-28 03:15:50 +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

@ -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;