From f6e16e7f4c65440783047d3f2b03563f36f6dae8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 27 May 2020 00:21:15 +0000 Subject: [PATCH] cxxrtl: generate debug information for non-localized public wires. Debug information describes values, wires, and memories with a simple C-compatible layout. It can be emitted on demand into a map, which has no runtime cost when it is unused, and allows late bound designs. The `hdlname` attribute is used as the lookup key such that original names, as emitted by the frontend, can be used for debugging and introspection. --- backends/cxxrtl/cxxrtl.cc | 87 ++++++++++++++++++++++++++++++++++++++- backends/cxxrtl/cxxrtl.h | 46 ++++++++++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/backends/cxxrtl/cxxrtl.cc b/backends/cxxrtl/cxxrtl.cc index 0cceecbba..edd606c1a 100644 --- a/backends/cxxrtl/cxxrtl.cc +++ b/backends/cxxrtl/cxxrtl.cc @@ -502,6 +502,15 @@ std::string escape_cxx_string(const std::string &input) return output; } +template +std::string get_hdl_name(T *object) +{ + if (object->has_attribute(ID::hdlname)) + return object->get_string_attribute(ID::hdlname); + else + return object->name.str(); +} + struct CxxrtlWorker { bool split_intf = false; std::string intf_filename; @@ -516,6 +525,8 @@ struct CxxrtlWorker { bool run_proc_flatten = false; bool max_opt_level = false; + bool debug_info = false; + std::ostringstream f; std::string indent; int temporary = 0; @@ -1593,6 +1604,34 @@ struct CxxrtlWorker { dec_indent(); } + void dump_debug_info_method(RTLIL::Module *module) + { + inc_indent(); + f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n"; + for (auto wire : module->wires()) { + if (wire->name[0] != '\\') + continue; + if (localized_wires.count(wire)) + continue; + f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire)); + f << ", debug_item(" << mangle(wire) << "));\n"; + } + 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 << ", debug_item(" << mangle(memory_it.second) << "));\n"; + } + for (auto cell : module->cells()) { + if (is_internal_cell(cell->type)) + continue; + const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; + f << indent << mangle(cell) << access << "debug_info(items, "; + f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n"; + } + dec_indent(); + } + void dump_metadata_map(const dict &metadata_map) { if (metadata_map.empty()) { @@ -1641,6 +1680,12 @@ struct CxxrtlWorker { dump_commit_method(module); f << indent << "}\n"; f << "\n"; + if (debug_info) { + f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; + dump_debug_info_method(module); + f << indent << "}\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"; @@ -1689,7 +1734,7 @@ struct CxxrtlWorker { if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> "; f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell); - f << "::create(" << escape_cxx_string(cell->name.str()) << ", "; + f << "::create(" << escape_cxx_string(get_hdl_name(cell)) << ", "; dump_metadata_map(cell->parameters); f << ", "; dump_metadata_map(cell->attributes); @@ -1703,6 +1748,8 @@ struct CxxrtlWorker { f << "\n"; f << indent << "bool eval() override;\n"; f << indent << "bool commit() override;\n"; + if (debug_info) + f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n"; dec_indent(); f << indent << "}; // struct " << mangle(module) << "\n"; f << "\n"; @@ -1721,6 +1768,12 @@ struct CxxrtlWorker { dump_commit_method(module); f << indent << "}\n"; f << "\n"; + if (debug_info) { + f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; + dump_debug_info_method(module); + f << indent << "}\n"; + f << "\n"; + } } void dump_design(RTLIL::Design *design) @@ -2120,6 +2173,7 @@ struct CxxrtlWorker { struct CxxrtlBackend : public Backend { static const int DEFAULT_OPT_LEVEL = 5; + static const int DEFAULT_DEBUG_LEVEL = 1; CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } void help() YS_OVERRIDE @@ -2313,10 +2367,22 @@ struct CxxrtlBackend : public Backend { log(" -O5\n"); log(" like -O4, and run `proc; flatten` first.\n"); log("\n"); + log(" -g \n"); + log(" set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL); + log(" more visibility and generate more code, but do not pessimize evaluation.\n"); + log("\n"); + log(" -g0\n"); + log(" no debug information.\n"); + log("\n"); + log(" -g1\n"); + log(" debug information for non-localized public wires.\n"); + log("\n"); } + void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) YS_OVERRIDE { int opt_level = DEFAULT_OPT_LEVEL; + int debug_level = DEFAULT_DEBUG_LEVEL; CxxrtlWorker worker; log_header(design, "Executing CXXRTL backend.\n"); @@ -2332,6 +2398,14 @@ struct CxxrtlBackend : public Backend { opt_level = std::stoi(args[argidx].substr(2)); continue; } + if (args[argidx] == "-g" && argidx+1 < args.size()) { + debug_level = std::stoi(args[++argidx]); + continue; + } + if (args[argidx].substr(0, 2) == "-g" && args[argidx].size() == 3 && isdigit(args[argidx][2])) { + debug_level = std::stoi(args[argidx].substr(2)); + continue; + } if (args[argidx] == "-header") { worker.split_intf = true; continue; @@ -2368,6 +2442,17 @@ struct CxxrtlBackend : public Backend { log_cmd_error("Invalid optimization level %d.\n", opt_level); } + switch (debug_level) { + // the highest level here must match DEFAULT_DEBUG_LEVEL + case 1: + worker.debug_info = true; + YS_FALLTHROUGH + case 0: + break; + default: + log_cmd_error("Invalid optimization level %d.\n", opt_level); + } + std::ofstream intf_f; if (worker.split_intf) { if (filename == "") diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 7b91742e0..14613afb0 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -49,6 +49,8 @@ namespace cxxrtl { // invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway. // Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be // clobbered results in simpler generated code. +typedef uint32_t chunk_t; + template struct chunk_traits { static_assert(std::is_integral::value && std::is_unsigned::value, @@ -65,7 +67,7 @@ template struct value : public expr_base> { static constexpr size_t bits = Bits; - using chunk = chunk_traits; + using chunk = chunk_traits; static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask : chunk::mask >> (chunk::bits - (Bits % chunk::bits)); @@ -712,6 +714,46 @@ struct metadata { typedef std::map metadata_map; +// This structure is intended for consumption via foreign function interfaces, like Python's ctypes. +// Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++. +struct debug_item { + enum : uint32_t { + VALUE = 0, + WIRE = 1, + MEMORY = 2, + } type; + + size_t width; // in bits + size_t depth; // 1 if `type != MEMORY` + chunk_t *curr; + chunk_t *next; // nullptr if `type == VALUE || type == MEMORY` + + template + debug_item(value &item) : type(VALUE), width(Bits), depth(1), + curr(item.data), next(nullptr) { + static_assert(sizeof(item) == value::chunks * sizeof(chunk_t), + "value is not compatible with C layout"); + } + + template + debug_item(wire &item) : type(WIRE), width(Bits), depth(1), + curr(item.curr.data), next(item.next.data) { + static_assert(sizeof(item.curr) == value::chunks * sizeof(chunk_t) && + sizeof(item.next) == value::chunks * sizeof(chunk_t), + "wire is not compatible with C layout"); + } + + template + debug_item(memory &item) : type(MEMORY), width(Width), depth(item.data.size()), + curr(item.data.empty() ? nullptr : item.data[0].data), next(nullptr) { + static_assert(sizeof(item.data[0]) == value::chunks * sizeof(chunk_t), + "memory is not compatible with C layout"); + } +}; +static_assert(std::is_standard_layout::value, "debug_item is not compatible with C layout"); + +typedef std::map debug_items; + struct module { module() {} virtual ~module() {} @@ -731,6 +773,8 @@ struct module { } while (commit() && !converged); return deltas; } + + virtual void debug_info(debug_items &items, std::string path = "") {} }; } // namespace cxxrtl -- 2.30.2