From 8d712b1095c85cd34f8f4d33798d6a7f1f6c5a2d Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2020 13:31:16 +0000 Subject: [PATCH] cxxrtl: handle multipart signals. This avoids losing design visibility when using the `splitnets` pass. --- backends/cxxrtl/cxxrtl.h | 33 ++++++++++++++++++++++++++++++- backends/cxxrtl/cxxrtl_backend.cc | 8 ++++---- backends/cxxrtl/cxxrtl_capi.cc | 17 +++++++++------- backends/cxxrtl/cxxrtl_capi.h | 30 +++++++++++++++++++++++----- backends/cxxrtl/cxxrtl_vcd.h | 33 +++++++++++++++++++++---------- 5 files changed, 94 insertions(+), 27 deletions(-) diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index b8acd02df..43546bd3c 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -815,7 +815,38 @@ struct debug_item : ::cxxrtl_object { }; static_assert(std::is_standard_layout::value, "debug_item is not compatible with C layout"); -typedef std::map debug_items; +struct debug_items { + std::map> table; + + void add(const std::string &name, debug_item &&item) { + std::vector &parts = table[name]; + parts.emplace_back(item); + std::sort(parts.begin(), parts.end(), + [](const debug_item &a, const debug_item &b) { + return a.lsb_at < b.lsb_at; + }); + } + + size_t count(const std::string &name) const { + if (table.count(name) == 0) + return 0; + return table.at(name).size(); + } + + const std::vector &parts_at(const std::string &name) const { + return table.at(name); + } + + const debug_item &at(const std::string &name) const { + const std::vector &parts = table.at(name); + assert(parts.size() == 1); + return parts.at(0); + } + + const debug_item &operator [](const std::string &name) const { + return at(name); + } +}; struct module { module() {} diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index dd2029dc5..94e823075 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1640,19 +1640,19 @@ struct CxxrtlWorker { f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = "; dump_const(debug_const_wires[wire]); f << ";\n"; - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); f << ", debug_item(const_" << mangle(wire) << ", "; f << wire->start_offset << "));\n"; count_const_wires++; } else if (debug_alias_wires.count(wire)) { // Alias of a member wire - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", "; f << wire->start_offset << "));\n"; count_alias_wires++; } else if (!localized_wires.count(wire)) { // Member wire - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire)); f << ", debug_item(" << mangle(wire) << ", "; f << wire->start_offset << "));\n"; count_member_wires++; @@ -1663,7 +1663,7 @@ struct CxxrtlWorker { for (auto &memory_it : module->memories) { if (memory_it.first[0] != '\\') continue; - f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); + f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(memory_it.second)); f << ", debug_item(" << mangle(memory_it.second) << ", "; f << memory_it.second->start_offset << "));\n"; } diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc index 489d72da5..e0566e152 100644 --- a/backends/cxxrtl/cxxrtl_capi.cc +++ b/backends/cxxrtl/cxxrtl_capi.cc @@ -47,14 +47,17 @@ size_t cxxrtl_step(cxxrtl_handle handle) { return handle->module->step(); } -cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { - if (handle->objects.count(name) > 0) - return static_cast(&handle->objects.at(name)); - return nullptr; +struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts) { + auto it = handle->objects.table.find(name); + if (it == handle->objects.table.end()) + return nullptr; + *parts = it->second.size(); + return static_cast(&it->second[0]); } void cxxrtl_enum(cxxrtl_handle handle, void *data, - void (*callback)(void *data, const char *name, cxxrtl_object *object)) { - for (auto &it : handle->objects) - callback(data, it.first.c_str(), static_cast(&it.second)); + void (*callback)(void *data, const char *name, + cxxrtl_object *object, size_t parts)) { + for (auto &it : handle->objects.table) + callback(data, it.first.c_str(), static_cast(&it.second[0]), it.second.size()); } diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h index cdddf63f3..599284898 100644 --- a/backends/cxxrtl/cxxrtl_capi.h +++ b/backends/cxxrtl/cxxrtl_capi.h @@ -26,6 +26,7 @@ #include #include +#include #ifdef __cplusplus extern "C" { @@ -146,17 +147,36 @@ struct cxxrtl_object { // the top-level module instantiates a module `foo`, which in turn contains a wire `bar`, the full // hierarchical name is `\foo \bar`. // -// Returns the object if it was found, NULL otherwise. The returned value is valid until the design -// is destroyed. -struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name); +// The storage of a single abstract object may be split (usually with the `splitnets` pass) into +// many physical parts, all of which correspond to the same hierarchical name. To handle such cases, +// this function returns an array and writes its length to `parts`. The array is sorted by `lsb_at`. +// +// Returns the object parts if it was found, NULL otherwise. The returned parts are valid until +// the design is destroyed. +struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts); + +// Retrieve description of a single part simulated object. +// +// This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that, +// if the object exists, it consists of a single part. If assertions are disabled, it returns NULL +// for multi-part objects. +inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) { + size_t parts = 0; + struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts); + assert(object == NULL || parts == 1); + if (object == NULL || parts == 1) + return object; + return NULL; +} // Enumerate simulated objects. // // For every object in the simulation, `callback` is called with the provided `data`, the full -// hierarchical name of the object (see `cxxrtl_get` for details), and the object description. +// hierarchical name of the object (see `cxxrtl_get` for details), and the object parts. // The provided `name` and `object` values are valid until the design is destroyed. void cxxrtl_enum(cxxrtl_handle handle, void *data, - void (*callback)(void *data, const char *name, struct cxxrtl_object *object)); + void (*callback)(void *data, const char *name, + struct cxxrtl_object *object, size_t parts)); #ifdef __cplusplus } diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h index 4c2021e92..dbeabbaf2 100644 --- a/backends/cxxrtl/cxxrtl_vcd.h +++ b/backends/cxxrtl/cxxrtl_vcd.h @@ -66,11 +66,19 @@ class vcd_writer { } while (ident != 0); } - void emit_var(const variable &var, const std::string &type, const std::string &name) { + void emit_var(const variable &var, const std::string &type, const std::string &name, + size_t lsb_at, bool multipart) { assert(!streaming); buffer += "$var " + type + " " + std::to_string(var.width) + " "; emit_ident(var.ident); - buffer += " " + name + " $end\n"; + buffer += " " + name; + if (multipart || name.back() == ']' || lsb_at != 0) { + if (var.width == 1) + buffer += " [" + std::to_string(lsb_at) + "]"; + else + buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]"; + } + buffer += " $end\n"; } void emit_enddefinitions() { @@ -155,7 +163,7 @@ public: emit_timescale(number, unit); } - void add(const std::string &hier_name, const debug_item &item) { + void add(const std::string &hier_name, const debug_item &item, bool multipart = false) { std::vector scope = split_hierarchy(hier_name); std::string name = scope.back(); scope.pop_back(); @@ -164,17 +172,20 @@ public: switch (item.type) { // Not the best naming but oh well... case debug_item::VALUE: - emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), "wire", name); + emit_var(register_variable(item.width, item.curr, /*constant=*/item.next == nullptr), + "wire", name, item.lsb_at, multipart); break; case debug_item::WIRE: - emit_var(register_variable(item.width, item.curr), "reg", name); + emit_var(register_variable(item.width, item.curr), + "reg", name, item.lsb_at, multipart); break; case debug_item::MEMORY: { const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8); for (size_t index = 0; index < item.depth; index++) { chunk_t *nth_curr = &item.curr[stride * index]; std::string nth_name = name + '[' + std::to_string(index) + ']'; - emit_var(register_variable(item.width, nth_curr), "reg", nth_name); + emit_var(register_variable(item.width, nth_curr), + "reg", nth_name, item.lsb_at, multipart); } break; } @@ -183,7 +194,8 @@ public: // can actually change, and must be tracked. In most cases the VCD identifier will be // unified with the aliased reg, but we should handle the case where only the alias is // added to the VCD writer, too. - emit_var(register_variable(item.width, item.curr), "wire", name); + emit_var(register_variable(item.width, item.curr), + "wire", name, item.lsb_at, multipart); break; } } @@ -192,9 +204,10 @@ public: void add(const debug_items &items, const Filter &filter) { // `debug_items` is a map, so the items are already sorted in an order optimal for emitting // VCD scope sections. - for (auto &it : items) - if (filter(it.first, it.second)) - add(it.first, it.second); + for (auto &it : items.table) + for (auto &part : it.second) + if (filter(it.first, part)) + add(it.first, part, it.second.size() > 1); } void add(const debug_items &items) { -- 2.30.2