Skip to content

Commit

Permalink
cxxrtl: add a way to observe state changes during the commit step.
Browse files Browse the repository at this point in the history
The commit observer is a structure containing a callback that is invoked
whenever the `commit()` method changes a wire or a memory. This allows
code external to the compiled netlist to react to changes in the design
state in a very efficient way. One example of how this feature can be
used is an efficient implementation of record/replay.

Note that the VCD writer does not benefit from this feature because it
must be able to react to changes in any debug items and not just those
that contain design state.
  • Loading branch information
whitequark committed Jan 5, 2024
1 parent a94fafa commit 3e358d9
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 17 deletions.
42 changes: 28 additions & 14 deletions backends/cxxrtl/cxxrtl_backend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2115,19 +2115,19 @@ struct CxxrtlWorker {
if (wire_type.type == WireType::MEMBER && edge_wires[wire])
f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n";
if (wire_type.is_buffered())
f << indent << "if (" << mangle(wire) << ".commit()) changed = true;\n";
f << indent << "if (" << mangle(wire) << ".commit(observer)) changed = true;\n";
}
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto &mem : mod_memories[module]) {
if (!writable_memories.count({module, mem.memid}))
continue;
f << indent << "if (" << mangle(&mem) << ".commit()) changed = true;\n";
f << indent << "if (" << mangle(&mem) << ".commit(observer)) changed = true;\n";
}
for (auto cell : module->cells()) {
if (is_internal_cell(cell->type))
continue;
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
f << indent << "if (" << mangle(cell) << access << "commit()) changed = true;\n";
f << indent << "if (" << mangle(cell) << access << "commit(observer)) changed = true;\n";
}
}
f << indent << "return changed;\n";
Expand All @@ -2146,7 +2146,7 @@ struct CxxrtlWorker {
if (!metadata_item.first.isPublic())
continue;
if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) {
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */";
f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n";
continue;
}
f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", ";
Expand Down Expand Up @@ -2371,16 +2371,22 @@ struct CxxrtlWorker {
dump_eval_method(module);
f << indent << "}\n";
f << "\n";
f << indent << "bool commit() override {\n";
f << indent << "template<class ObserverT>\n";
f << indent << "bool commit(ObserverT &observer) {\n";
dump_commit_method(module);
f << indent << "}\n";
f << "\n";
f << indent << "bool commit() override {\n";
f << indent << indent << "null_observer observer;\n";
f << indent << indent << "return commit<>(observer);\n";
f << indent << "}\n";
if (debug_info) {
f << "\n";
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n";
dump_debug_info_method(module);
f << indent << "}\n";
f << "\n";
}
f << "\n";
f << indent << "static std::unique_ptr<" << mangle(module);
f << template_params(module, /*is_decl=*/false) << "> ";
f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n";
Expand Down Expand Up @@ -2457,8 +2463,18 @@ struct CxxrtlWorker {
f << indent << "};\n";
f << "\n";
f << indent << "void reset() override;\n";
f << "\n";
f << indent << "bool eval() override;\n";
f << indent << "bool commit() override;\n";
f << "\n";
f << indent << "template<class ObserverT>\n";
f << indent << "bool commit(ObserverT &observer) {\n";
dump_commit_method(module);
f << indent << "}\n";
f << "\n";
f << indent << "bool commit() override {\n";
f << indent << indent << "null_observer observer;\n";
f << indent << indent << "return commit<>(observer);\n";
f << indent << "}\n";
if (debug_info) {
if (debug_eval) {
f << "\n";
Expand Down Expand Up @@ -2490,24 +2506,20 @@ struct CxxrtlWorker {
f << indent << "bool " << mangle(module) << "::eval() {\n";
dump_eval_method(module);
f << indent << "}\n";
f << "\n";
f << indent << "bool " << mangle(module) << "::commit() {\n";
dump_commit_method(module);
f << indent << "}\n";
f << "\n";
if (debug_info) {
if (debug_eval) {
f << "\n";
f << indent << "void " << mangle(module) << "::debug_eval() {\n";
dump_debug_eval_method(module);
f << indent << "}\n";
f << "\n";
}
f << "\n";
f << indent << "CXXRTL_EXTREMELY_COLD\n";
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
dump_debug_info_method(module);
f << indent << "}\n";
f << "\n";
}
f << "\n";
}

void dump_design(RTLIL::Design *design)
Expand Down Expand Up @@ -3267,6 +3279,8 @@ struct CxxrtlBackend : public Backend {
log(" wire<8> p_o_data;\n");
log("\n");
log(" bool eval() override;\n");
log(" template<class ObserverT>\n");
log(" bool commit(ObserverT &observer);\n");
log(" bool commit() override;\n");
log("\n");
log(" static std::unique_ptr<bb_p_debug>\n");
Expand Down
40 changes: 37 additions & 3 deletions backends/cxxrtl/runtime/cxxrtl/cxxrtl.h
Original file line number Diff line number Diff line change
Expand Up @@ -841,6 +841,29 @@ std::ostream &operator<<(std::ostream &os, const value_formatted<Bits> &vf)
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 a `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 a `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` covers 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>
struct wire {
static constexpr size_t bits = Bits;
Expand Down Expand Up @@ -875,8 +898,14 @@ struct wire {
next.template set<IntegerT>(other);
}

bool commit() {
// 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`.
template<class ObserverT>
bool commit(ObserverT &observer) {
if (curr != next) {
observer.on_commit(curr.chunks, curr.data, next.data);
curr = next;
return true;
}
Expand Down Expand Up @@ -950,12 +979,17 @@ struct memory {
write { index, val, mask, priority });
}

bool commit() {
// See the note for `wire::commit()`.
template<class ObserverT>
bool commit(ObserverT &observer) {
bool changed = false;
for (const write &entry : write_queue) {
value<Width> elem = data[entry.index];
elem = elem.update(entry.val, entry.mask);
changed |= (data[entry.index] != elem);
if (data[entry.index] != elem) {
observer.on_commit(value<Width>::chunks, data[0].data, elem.data, entry.index);
changed |= true;
}
data[entry.index] = elem;
}
write_queue.clear();
Expand Down

0 comments on commit 3e358d9

Please sign in to comment.