cxxrtl: generate debug information for non-localized public wires.
authorwhitequark <whitequark@whitequark.org>
Wed, 27 May 2020 00:21:15 +0000 (00:21 +0000)
committerwhitequark <whitequark@whitequark.org>
Sat, 6 Jun 2020 21:12:55 +0000 (21:12 +0000)
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
backends/cxxrtl/cxxrtl.h

index 0cceecbba21f51884cb14ac3cb711eeaf05e4029..edd606c1a6623868026469a499b6cdf0e82e3add 100644 (file)
@@ -502,6 +502,15 @@ std::string escape_cxx_string(const std::string &input)
        return output;
 }
 
+template<class T>
+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<RTLIL::IdString, RTLIL::Const> &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 <level>\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<std::string> 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 == "<stdout>")
index 7b91742e01af9d3a6715aab7b078b7cf5891c103..14613afb0b59943639185b34ba14b51ce59865c5 100644 (file)
@@ -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<typename T>
 struct chunk_traits {
        static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value,
@@ -65,7 +67,7 @@ template<size_t Bits>
 struct value : public expr_base<value<Bits>> {
        static constexpr size_t bits = Bits;
 
-       using chunk = chunk_traits<uint32_t>;
+       using chunk = chunk_traits<chunk_t>;
        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<std::string, metadata> 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<size_t Bits>
+       debug_item(value<Bits> &item) : type(VALUE), width(Bits), depth(1),
+               curr(item.data), next(nullptr) {
+                       static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
+                                     "value<Bits> is not compatible with C layout");
+               }
+
+       template<size_t Bits>
+       debug_item(wire<Bits> &item) : type(WIRE), width(Bits), depth(1),
+               curr(item.curr.data), next(item.next.data) {
+                       static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
+                                     sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
+                                     "wire<Bits> is not compatible with C layout");
+               }
+
+       template<size_t Width>
+       debug_item(memory<Width> &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<Width>::chunks * sizeof(chunk_t),
+                                     "memory<Width> is not compatible with C layout");
+               }
+};
+static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout");
+
+typedef std::map<std::string, debug_item> 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