cxxrtl: implement debug information outlining.
authorwhitequark <whitequark@whitequark.org>
Sun, 13 Dec 2020 07:03:16 +0000 (07:03 +0000)
committerwhitequark <whitequark@whitequark.org>
Mon, 14 Dec 2020 01:27:27 +0000 (01:27 +0000)
Aggressive wire localization and inlining is necessary for CXXRTL to
achieve high performance. However, that comes with a cost: reduced
debug information coverage. Previously, as a workaround, the `-Og`
option could have been used to guarantee complete coverage, at a cost
of a significant performance penalty.

This commit introduces debug information outlining. The main eval()
function is compiled with the user-specified optimization settings.
In tandem, an auxiliary debug_eval() function, compiled from the same
netlist, can be used to reconstruct the values of localized/inlined
signals on demand. To the extent that it is possible, debug_eval()
reuses the results of computations performed by eval(), only filling
in the missing values.

Benchmarking a representative design (Minerva SoC SRAM) shows that:
  * Switching from `-O4`/`-Og` to `-O6` reduces runtime by ~40%.
  * Switching from `-g1` to `-g2`, both used with `-O6`, increases
    compile time by ~25%.
  * Although `-g2` increases the resident size of generated modules,
    this has no effect on runtime.

Because the impact of `-g2` is minimal and the benefits of having
unconditional 100% debug information coverage (and the performance
improvement as well) are major, this commit removes `-Og` and changes
the defaults to `-O6 -g2`.

We'll have our cake and eat it too!

backends/cxxrtl/cxxrtl.h
backends/cxxrtl/cxxrtl_backend.cc
backends/cxxrtl/cxxrtl_capi.cc
backends/cxxrtl/cxxrtl_capi.h
backends/cxxrtl/cxxrtl_vcd.h

index d850fdba40b0dbe8202868d702d82c065f8bfa68..59393e415cf125a76eb4e0e860aa95339b39709e 100644 (file)
@@ -36,6 +36,7 @@
 #include <map>
 #include <algorithm>
 #include <memory>
+#include <functional>
 #include <sstream>
 
 #include <backends/cxxrtl/cxxrtl_capi.h>
@@ -843,6 +844,9 @@ typedef std::map<std::string, metadata> metadata_map;
 // Tag class to disambiguate values/wires and their aliases.
 struct debug_alias {};
 
+// Tag declaration to disambiguate values and debug outlines.
+using debug_outline = ::_cxxrtl_outline;
+
 // 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++.
 //
@@ -851,10 +855,11 @@ struct debug_alias {};
 struct debug_item : ::cxxrtl_object {
        // Object types.
        enum : uint32_t {
-               VALUE  = CXXRTL_VALUE,
-               WIRE   = CXXRTL_WIRE,
-               MEMORY = CXXRTL_MEMORY,
-               ALIAS  = CXXRTL_ALIAS,
+               VALUE   = CXXRTL_VALUE,
+               WIRE    = CXXRTL_WIRE,
+               MEMORY  = CXXRTL_MEMORY,
+               ALIAS   = CXXRTL_ALIAS,
+               OUTLINE = CXXRTL_OUTLINE,
        };
 
        // Object flags.
@@ -881,6 +886,7 @@ struct debug_item : ::cxxrtl_object {
                zero_at = 0;
                curr    = item.data;
                next    = item.data;
+               outline = nullptr;
        }
 
        template<size_t Bits>
@@ -895,6 +901,7 @@ struct debug_item : ::cxxrtl_object {
                zero_at = 0;
                curr    = const_cast<chunk_t*>(item.data);
                next    = nullptr;
+               outline = nullptr;
        }
 
        template<size_t Bits>
@@ -910,6 +917,7 @@ struct debug_item : ::cxxrtl_object {
                zero_at = 0;
                curr    = item.curr.data;
                next    = item.next.data;
+               outline = nullptr;
        }
 
        template<size_t Width>
@@ -924,6 +932,7 @@ struct debug_item : ::cxxrtl_object {
                zero_at = zero_offset;
                curr    = item.data.empty() ? nullptr : item.data[0].data;
                next    = nullptr;
+               outline = nullptr;
        }
 
        template<size_t Bits>
@@ -938,6 +947,7 @@ struct debug_item : ::cxxrtl_object {
                zero_at = 0;
                curr    = const_cast<chunk_t*>(item.data);
                next    = nullptr;
+               outline = nullptr;
        }
 
        template<size_t Bits>
@@ -953,6 +963,22 @@ struct debug_item : ::cxxrtl_object {
                zero_at = 0;
                curr    = const_cast<chunk_t*>(item.curr.data);
                next    = nullptr;
+               outline = nullptr;
+       }
+
+       template<size_t Bits>
+       debug_item(debug_outline &group, const value<Bits> &item, size_t lsb_offset = 0) {
+               static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
+                             "value<Bits> is not compatible with C layout");
+               type    = OUTLINE;
+               flags   = DRIVEN_COMB;
+               width   = Bits;
+               lsb_at  = lsb_offset;
+               depth   = 1;
+               zero_at = 0;
+               curr    = const_cast<chunk_t*>(item.data);
+               next    = nullptr;
+               outline = &group;
        }
 };
 static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout");
@@ -1029,11 +1055,16 @@ struct module {
 
 } // namespace cxxrtl
 
-// Internal structure used to communicate with the implementation of the C interface.
+// Internal structures used to communicate with the implementation of the C interface.
+
 typedef struct _cxxrtl_toplevel {
        std::unique_ptr<cxxrtl::module> module;
 } *cxxrtl_toplevel;
 
+typedef struct _cxxrtl_outline {
+       std::function<void()> eval;
+} *cxxrtl_outline;
+
 // Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic
 // and indepenent of Yosys implementation details.
 //
index ca7f3a3cc811916db157af81b5ff8b7bdfb2d492..5e2f4f31a5cc2c1e7a2b153b27b7d74090630cfb 100644 (file)
@@ -539,6 +539,7 @@ struct CxxrtlWorker {
        bool inline_public = false;
 
        bool debug_info = false;
+       bool debug_eval = false;
 
        std::ostringstream f;
        std::string indent;
@@ -553,8 +554,9 @@ struct CxxrtlWorker {
        pool<const RTLIL::Wire*> unbuffered_wires;
        pool<const RTLIL::Wire*> localized_wires;
        dict<const RTLIL::Wire*, FlowGraph::Node> inlined_wires;
-       dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires;
        dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires;
+       dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires;
+       pool<const RTLIL::Wire*> debug_outlined_wires;
        dict<RTLIL::SigBit, bool> bit_has_state;
        dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations;
        dict<const RTLIL::Module*, bool> eval_converges;
@@ -786,22 +788,22 @@ struct CxxrtlWorker {
                dump_const(data, data.size());
        }
 
-       bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs)
+       bool dump_sigchunk(const RTLIL::SigChunk &chunk, bool is_lhs, bool for_debug = false)
        {
                if (chunk.wire == NULL) {
                        dump_const(chunk.data, chunk.width, chunk.offset);
                        return false;
                } else {
-                       if (inlined_wires.count(chunk.wire)) {
+                       if (inlined_wires.count(chunk.wire) && (!for_debug || !debug_outlined_wires[chunk.wire])) {
                                log_assert(!is_lhs);
                                const FlowGraph::Node &node = inlined_wires[chunk.wire];
                                switch (node.type) {
                                        case FlowGraph::Node::Type::CONNECT:
-                                               dump_connect_expr(node.connect);
+                                               dump_connect_expr(node.connect, for_debug);
                                                break;
                                        case FlowGraph::Node::Type::CELL_EVAL:
                                                log_assert(is_inlinable_cell(node.cell->type));
-                                               dump_cell_expr(node.cell);
+                                               dump_cell_expr(node.cell, for_debug);
                                                break;
                                        default:
                                                log_assert(false);
@@ -821,36 +823,36 @@ struct CxxrtlWorker {
                }
        }
 
-       bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs)
+       bool dump_sigspec(const RTLIL::SigSpec &sig, bool is_lhs, bool for_debug = false)
        {
                if (sig.empty()) {
                        f << "value<0>()";
                        return false;
                } else if (sig.is_chunk()) {
-                       return dump_sigchunk(sig.as_chunk(), is_lhs);
+                       return dump_sigchunk(sig.as_chunk(), is_lhs, for_debug);
                } else {
-                       dump_sigchunk(*sig.chunks().rbegin(), is_lhs);
+                       dump_sigchunk(*sig.chunks().rbegin(), is_lhs, for_debug);
                        for (auto it = sig.chunks().rbegin() + 1; it != sig.chunks().rend(); ++it) {
                                f << ".concat(";
-                               dump_sigchunk(*it, is_lhs);
+                               dump_sigchunk(*it, is_lhs, for_debug);
                                f << ")";
                        }
                        return true;
                }
        }
 
-       void dump_sigspec_lhs(const RTLIL::SigSpec &sig)
+       void dump_sigspec_lhs(const RTLIL::SigSpec &sig, bool for_debug = false)
        {
-               dump_sigspec(sig, /*is_lhs=*/true);
+               dump_sigspec(sig, /*is_lhs=*/true, for_debug);
        }
 
-       void dump_sigspec_rhs(const RTLIL::SigSpec &sig)
+       void dump_sigspec_rhs(const RTLIL::SigSpec &sig, bool for_debug = false)
        {
                // In the contexts where we want template argument deduction to occur for `template<size_t Bits> ... value<Bits>`,
                // it is necessary to have the argument to already be a `value<N>`, since template argument deduction and implicit
                // type conversion are mutually exclusive. In these contexts, we use dump_sigspec_rhs() to emit an explicit
                // type conversion, but only if the expression needs it.
-               bool is_complex = dump_sigspec(sig, /*is_lhs=*/false);
+               bool is_complex = dump_sigspec(sig, /*is_lhs=*/false, for_debug);
                if (is_complex)
                        f << ".val()";
        }
@@ -875,9 +877,9 @@ struct CxxrtlWorker {
                }
        }
 
-       void dump_connect_expr(const RTLIL::SigSig &conn)
+       void dump_connect_expr(const RTLIL::SigSig &conn, bool for_debug = false)
        {
-               dump_sigspec_rhs(conn.second);
+               dump_sigspec_rhs(conn.second, for_debug);
        }
 
        bool is_connect_inlined(const RTLIL::SigSig &conn)
@@ -885,6 +887,14 @@ struct CxxrtlWorker {
                return conn.first.is_wire() && inlined_wires.count(conn.first.as_wire());
        }
 
+       bool is_connect_outlined(const RTLIL::SigSig &conn)
+       {
+               for (auto chunk : conn.first.chunks())
+                       if (debug_outlined_wires.count(chunk.wire))
+                               return true;
+               return false;
+       }
+
        void collect_connect(const RTLIL::SigSig &conn, std::vector<RTLIL::IdString> &cells)
        {
                if (!is_connect_inlined(conn))
@@ -893,16 +903,18 @@ struct CxxrtlWorker {
                collect_sigspec_rhs(conn.second, cells);
        }
 
-       void dump_connect(const RTLIL::SigSig &conn)
+       void dump_connect(const RTLIL::SigSig &conn, bool for_debug = false)
        {
-               if (is_connect_inlined(conn))
+               if (!for_debug && is_connect_inlined(conn))
+                       return;
+               if (for_debug && !is_connect_outlined(conn))
                        return;
 
                f << indent << "// connection\n";
                f << indent;
-               dump_sigspec_lhs(conn.first);
+               dump_sigspec_lhs(conn.first, for_debug);
                f << " = ";
-               dump_connect_expr(conn);
+               dump_connect_expr(conn, for_debug);
                f << ";\n";
        }
 
@@ -919,7 +931,7 @@ struct CxxrtlWorker {
                                }
        }
 
-       void dump_cell_expr(const RTLIL::Cell *cell)
+       void dump_cell_expr(const RTLIL::Cell *cell, bool for_debug = false)
        {
                // Unary cells
                if (is_unary_cell(cell->type)) {
@@ -927,7 +939,7 @@ struct CxxrtlWorker {
                        if (is_extending_cell(cell->type))
                                f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u');
                        f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
-                       dump_sigspec_rhs(cell->getPort(ID::A));
+                       dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
                        f << ")";
                // Binary cells
                } else if (is_binary_cell(cell->type)) {
@@ -936,18 +948,18 @@ struct CxxrtlWorker {
                                f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') <<
                                            (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u');
                        f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
-                       dump_sigspec_rhs(cell->getPort(ID::A));
+                       dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
                        f << ", ";
-                       dump_sigspec_rhs(cell->getPort(ID::B));
+                       dump_sigspec_rhs(cell->getPort(ID::B), for_debug);
                        f << ")";
                // Muxes
                } else if (cell->type == ID($mux)) {
                        f << "(";
-                       dump_sigspec_rhs(cell->getPort(ID::S));
+                       dump_sigspec_rhs(cell->getPort(ID::S), for_debug);
                        f << " ? ";
-                       dump_sigspec_rhs(cell->getPort(ID::B));
+                       dump_sigspec_rhs(cell->getPort(ID::B), for_debug);
                        f << " : ";
-                       dump_sigspec_rhs(cell->getPort(ID::A));
+                       dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
                        f << ")";
                // Parallel (one-hot) muxes
                } else if (cell->type == ID($pmux)) {
@@ -955,24 +967,24 @@ struct CxxrtlWorker {
                        int s_width = cell->getParam(ID::S_WIDTH).as_int();
                        for (int part = 0; part < s_width; part++) {
                                f << "(";
-                               dump_sigspec_rhs(cell->getPort(ID::S).extract(part));
+                               dump_sigspec_rhs(cell->getPort(ID::S).extract(part), for_debug);
                                f << " ? ";
-                               dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width));
+                               dump_sigspec_rhs(cell->getPort(ID::B).extract(part * width, width), for_debug);
                                f << " : ";
                        }
-                       dump_sigspec_rhs(cell->getPort(ID::A));
+                       dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
                        for (int part = 0; part < s_width; part++) {
                                f << ")";
                        }
                // Concats
                } else if (cell->type == ID($concat)) {
-                       dump_sigspec_rhs(cell->getPort(ID::B));
+                       dump_sigspec_rhs(cell->getPort(ID::B), for_debug);
                        f << ".concat(";
-                       dump_sigspec_rhs(cell->getPort(ID::A));
+                       dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
                        f << ").val()";
                // Slices
                } else if (cell->type == ID($slice)) {
-                       dump_sigspec_rhs(cell->getPort(ID::A));
+                       dump_sigspec_rhs(cell->getPort(ID::A), for_debug);
                        f << ".slice<";
                        f << cell->getParam(ID::OFFSET).as_int() + cell->getParam(ID::Y_WIDTH).as_int() - 1;
                        f << ",";
@@ -989,6 +1001,17 @@ struct CxxrtlWorker {
                        inlined_wires.count(cell->getPort(ID::Y).as_wire());
        }
 
+       bool is_cell_outlined(const RTLIL::Cell *cell)
+       {
+               if (is_internal_cell(cell->type))
+                       for (auto conn : cell->connections())
+                               if (cell->output(conn.first))
+                                       for (auto chunk : conn.second.chunks())
+                                               if (debug_outlined_wires.count(chunk.wire))
+                                                       return true;
+               return false;
+       }
+
        void collect_cell_eval(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells)
        {
                if (!is_cell_inlined(cell))
@@ -1000,9 +1023,11 @@ struct CxxrtlWorker {
                                collect_sigspec_rhs(port.second, cells);
        }
 
-       void dump_cell_eval(const RTLIL::Cell *cell)
+       void dump_cell_eval(const RTLIL::Cell *cell, bool for_debug = false)
        {
-               if (is_cell_inlined(cell))
+               if (!for_debug && is_cell_inlined(cell))
+                       return;
+               if (for_debug && !is_cell_outlined(cell))
                        return;
                if (cell->type == ID($meminit))
                        return; // Handled elsewhere.
@@ -1026,9 +1051,9 @@ struct CxxrtlWorker {
                // Elidable cells
                if (is_inlinable_cell(cell->type)) {
                        f << indent;
-                       dump_sigspec_lhs(cell->getPort(ID::Y));
+                       dump_sigspec_lhs(cell->getPort(ID::Y), for_debug);
                        f << " = ";
-                       dump_cell_expr(cell);
+                       dump_cell_expr(cell, for_debug);
                        f << ";\n";
                // Flip-flops
                } else if (is_ff_cell(cell->type)) {
@@ -1460,14 +1485,11 @@ struct CxxrtlWorker {
 
        void dump_wire(const RTLIL::Wire *wire, bool is_local)
        {
-               if (inlined_wires.count(wire))
-                       return;
-
-               if (localized_wires[wire] && is_local) {
+               if (is_local && localized_wires[wire] && !inlined_wires.count(wire)) {
                        dump_attrs(wire);
                        f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n";
                }
-               if (!localized_wires[wire] && !is_local) {
+               if (!is_local && !localized_wires[wire]) {
                        std::string width;
                        if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) {
                                width = wire->get_string_attribute(ID(cxxrtl_width));
@@ -1530,6 +1552,23 @@ struct CxxrtlWorker {
                }
        }
 
+       void dump_debug_wire(const RTLIL::Wire *wire, bool is_local)
+       {
+               if (!debug_outlined_wires[wire])
+                       return;
+
+               bool is_outlined_member = wire->name.isPublic() &&
+                       !(debug_const_wires.count(wire) || debug_alias_wires.count(wire));
+               if (is_local && !is_outlined_member) {
+                       dump_attrs(wire);
+                       f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n";
+               }
+               if (!is_local && is_outlined_member) {
+                       dump_attrs(wire);
+                       f << indent << "/*outline*/ value<" << wire->width << "> " << mangle(wire) << ";\n";
+               }
+       }
+
        void dump_memory(RTLIL::Module *module, const RTLIL::Memory *memory)
        {
                vector<const RTLIL::Cell*> init_cells;
@@ -1619,6 +1658,27 @@ struct CxxrtlWorker {
                dec_indent();
        }
 
+       void dump_debug_eval_method(RTLIL::Module *module)
+       {
+               inc_indent();
+                       for (auto wire : module->wires())
+                               dump_debug_wire(wire, /*is_local=*/true);
+                       for (auto node : schedule[module]) {
+                               switch (node.type) {
+                                       case FlowGraph::Node::Type::CONNECT:
+                                               dump_connect(node.connect, /*for_debug=*/true);
+                                               break;
+                                       case FlowGraph::Node::Type::CELL_EVAL:
+                                               dump_cell_eval(node.cell, /*for_debug=*/true);
+                                               break;
+                                       case FlowGraph::Node::Type::CELL_SYNC:
+                                       case FlowGraph::Node::Type::PROCESS:
+                                               break;
+                               }
+                       }
+               dec_indent();
+       }
+
        void dump_commit_method(RTLIL::Module *module)
        {
                inc_indent();
@@ -1656,6 +1716,7 @@ struct CxxrtlWorker {
                size_t count_public_wires = 0;
                size_t count_const_wires = 0;
                size_t count_alias_wires = 0;
+               size_t count_inline_wires = 0;
                size_t count_member_wires = 0;
                size_t count_skipped_wires = 0;
                size_t count_driven_sync = 0;
@@ -1685,6 +1746,12 @@ struct CxxrtlWorker {
                                        f << ", debug_item(debug_alias(), " << mangle(debug_alias_wires[wire]) << ", ";
                                        f << wire->start_offset << "));\n";
                                        count_alias_wires++;
+                               } else if (debug_outlined_wires.count(wire)) {
+                                       // Inlined but rematerializable wire
+                                       f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
+                                       f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", ";
+                                       f << wire->start_offset << "));\n";
+                                       count_inline_wires++;
                                } else if (!localized_wires.count(wire)) {
                                        // Member wire
                                        std::vector<std::string> flags;
@@ -1738,6 +1805,7 @@ struct CxxrtlWorker {
                                        f << "));\n";
                                        count_member_wires++;
                                } else {
+                                       // Localized or inlined wire with no debug information
                                        count_skipped_wires++;
                                }
                        }
@@ -1761,14 +1829,16 @@ struct CxxrtlWorker {
 
                log_debug("Debug information statistics for module `%s':\n", log_id(module));
                log_debug("  Public wires: %zu, of which:\n", count_public_wires);
-               log_debug("    Const wires:  %zu\n", count_const_wires);
-               log_debug("    Alias wires:  %zu\n", count_alias_wires);
                log_debug("    Member wires: %zu, of which:\n", count_member_wires);
                log_debug("      Driven sync:  %zu\n", count_driven_sync);
                log_debug("      Driven comb:  %zu\n", count_driven_comb);
-               log_debug("      Undriven:     %zu\n", count_undriven);
                log_debug("      Mixed driver: %zu\n", count_mixed_driver);
-               log_debug("    Other wires:  %zu (no debug information)\n", count_skipped_wires);
+               log_debug("      Undriven:     %zu\n", count_undriven);
+               log_debug("    Inline wires:   %zu\n", count_inline_wires);
+               log_debug("    Alias wires:    %zu\n", count_alias_wires);
+               log_debug("    Const wires:    %zu\n", count_const_wires);
+               log_debug("    Other wires:    %zu%s\n", count_skipped_wires,
+                         count_skipped_wires > 0 ? " (debug information unavailable)" : "");
        }
 
        void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map)
@@ -1855,7 +1925,8 @@ struct CxxrtlWorker {
                        inc_indent();
                                for (auto wire : module->wires())
                                        dump_wire(wire, /*is_local=*/false);
-                               f << "\n";
+                               for (auto wire : module->wires())
+                                       dump_debug_wire(wire, /*is_local=*/false);
                                bool has_memories = false;
                                for (auto memory : module->memories) {
                                        dump_memory(module, memory.second);
@@ -1927,8 +1998,20 @@ struct CxxrtlWorker {
                                f << "\n";
                                f << indent << "bool eval() override;\n";
                                f << indent << "bool commit() override;\n";
-                               if (debug_info)
+                               if (debug_info) {
+                                       if (debug_eval) {
+                                               f << "\n";
+                                               f << indent << "void debug_eval();\n";
+                                               for (auto wire : module->wires())
+                                                       if (debug_outlined_wires.count(wire)) {
+                                                               f << indent << "debug_outline debug_eval_outline { std::bind(&"
+                                                                           << mangle(module) << "::debug_eval, this) };\n";
+                                                               break;
+                                                       }
+                                       }
+                                       f << "\n";
                                        f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n";
+                               }
                        dec_indent();
                        f << indent << "}; // struct " << mangle(module) << "\n";
                        f << "\n";
@@ -1948,6 +2031,12 @@ struct CxxrtlWorker {
                f << indent << "}\n";
                f << "\n";
                if (debug_info) {
+                       if (debug_eval) {
+                               f << indent << "void " << mangle(module) << "::debug_eval() {\n";
+                               dump_debug_eval_method(module);
+                               f << indent << "}\n";
+                               f << "\n";
+                       }
                        f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
                        dump_debug_info_method(module);
                        f << indent << "}\n";
@@ -2251,6 +2340,11 @@ struct CxxrtlWorker {
                                for (auto node : wire_comb_def.second)
                                        node_defs[node].insert(wire_comb_def.first);
 
+                       dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_uses;
+                       for (auto wire_use : flow.wire_uses)
+                               for (auto node : wire_use.second)
+                                       node_uses[node].insert(wire_use.first);
+
                        Scheduler<FlowGraph::Node> scheduler;
                        dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map;
                        for (auto node : flow.nodes)
@@ -2368,6 +2462,30 @@ struct CxxrtlWorker {
                                        }
                                }
                        }
+                       if (debug_info && debug_eval) {
+                               // Find wires that can be be outlined, i.e. whose values can be always recovered from
+                               // the values of other wires. (This is the inverse of inlining--any wire that can be
+                               // inlined can also be outlined.) Although this may seem strictly less efficient, since
+                               // such values are computed at least twice, second-order effects make outlining useful.
+                               pool<const RTLIL::Wire*> worklist, visited;
+                               for (auto wire : module->wires()) {
+                                       if (!wire->name.isPublic())
+                                               continue; // only outline public wires
+                                       worklist.insert(wire);
+                               }
+                               while (!worklist.empty()) {
+                                       const RTLIL::Wire *wire = worklist.pop();
+                                       visited.insert(wire);
+                                       if (!localized_wires.count(wire) && !inlined_wires.count(wire))
+                                               continue; // member wire, doesn't need outlining
+                                       if (wire->name.isPublic() || !inlined_wires.count(wire))
+                                               debug_outlined_wires.insert(wire); // allow outlining of internal wires only
+                                       for (auto node : flow.wire_comb_defs[wire])
+                                               for (auto node_use : node_uses[node])
+                                                       if (!visited.count(node_use))
+                                                               worklist.insert(node_use);
+                               }
+                       }
                }
                if (has_feedback_arcs || has_buffered_comb_wires) {
                        // Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated
@@ -2457,8 +2575,7 @@ struct CxxrtlWorker {
 
 struct CxxrtlBackend : public Backend {
        static const int DEFAULT_OPT_LEVEL = 6;
-       static const int OPT_LEVEL_DEBUG = 4;
-       static const int DEFAULT_DEBUG_LEVEL = 1;
+       static const int DEFAULT_DEBUG_LEVEL = 2;
 
        CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { }
        void help() override
@@ -2671,10 +2788,6 @@ struct CxxrtlBackend : public Backend {
                log("    -O6\n");
                log("        like -O5, and inline public wires not marked (*keep*) if possible.\n");
                log("\n");
-               log("    -Og\n");
-               log("        highest optimization level that provides debug information for all\n");
-               log("        public wires. currently, alias for -O%d.\n", OPT_LEVEL_DEBUG);
-               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");
@@ -2686,6 +2799,10 @@ struct CxxrtlBackend : public Backend {
                log("        debug information for non-optimized public wires. this also makes it\n");
                log("        possible to use the C API.\n");
                log("\n");
+               log("    -g2\n");
+               log("        like -g1, and compute debug information on demand for all public wires\n");
+               log("        that were optimized out.\n");
+               log("\n");
        }
 
        void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
@@ -2715,12 +2832,14 @@ struct CxxrtlBackend : public Backend {
                                continue;
                        }
                        if (args[argidx] == "-Og") {
-                               opt_level = OPT_LEVEL_DEBUG;
+                               log_warning("The `-Og` option has been removed. Use `-g2` instead for complete "
+                                           "design coverage regardless of optimization level.\n");
                                continue;
                        }
                        if (args[argidx] == "-O" && argidx+1 < args.size() && args[argidx+1] == "g") {
                                argidx++;
-                               opt_level = OPT_LEVEL_DEBUG;
+                               log_warning("The `-Og` option has been removed. Use `-g2` instead for complete "
+                                           "design coverage regardless of optimization level.\n");
                                continue;
                        }
                        if (args[argidx] == "-O" && argidx+1 < args.size()) {
@@ -2781,6 +2900,9 @@ struct CxxrtlBackend : public Backend {
                }
                switch (debug_level) {
                        // the highest level here must match DEFAULT_DEBUG_LEVEL
+                       case 2:
+                               worker.debug_eval = true;
+                               YS_FALLTHROUGH
                        case 1:
                                worker.debug_info = true;
                                YS_FALLTHROUGH
index f92709b4696b81d1e632e13c15c10ce8f4c0a861..227173ba87f94de34b393a4eed57378c7c9ae706 100644 (file)
@@ -86,3 +86,7 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,
        for (auto &it : handle->objects.table)
                callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size());
 }
+
+void cxxrtl_outline_eval(cxxrtl_outline outline) {
+       outline->eval();
+}
index d67c58f94d1f64087bb72a7050179a706c2cf14a..7d9c60ac5907365a706c1b38ba77c13c6203e328 100644 (file)
@@ -128,6 +128,18 @@ enum cxxrtl_type {
        // pointer is always NULL.
        CXXRTL_ALIAS = 3,
 
+       // Outlines correspond to netlist nodes that were optimized in a way that makes them inaccessible
+       // outside of a module's `eval()` function. At the highest debug information level, every inlined
+       // node has a corresponding outline object.
+       //
+       // Outlines can be inspected via the `curr` pointer and can never be modified; the `next` pointer
+       // is always NULL. Unlike all other objects, the bits of an outline object are meaningful only
+       // after a call to `cxxrtl_outline_eval` and until any subsequent modification to the netlist.
+       // Observing this requirement is the responsibility of the caller; it is not enforced.
+       //
+       // Outlines always correspond to combinatorial netlist nodes that are not ports.
+       CXXRTL_OUTLINE = 4,
+
        // More object types may be added in the future, but the existing ones will never change.
 };
 
@@ -171,8 +183,8 @@ enum cxxrtl_flag {
 
        // Node has bits that are driven by a combinatorial cell or another node.
        //
-       // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
-       // with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags.
+       // This flag can be set on objects of type `CXXRTL_VALUE`, `CXXRTL_WIRE`, and `CXXRTL_OUTLINE`.
+       // It may be combined with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags.
        //
        // This flag is set on objects that have bits connected to the output of a combinatorial cell,
        // or directly to another node. For designs without combinatorial loops, writing to such bits
@@ -193,8 +205,8 @@ enum cxxrtl_flag {
 
 // Description of a simulated object.
 //
-// The `data` array can be accessed directly to inspect and, if applicable, modify the bits
-// stored in the object.
+// The `curr` and `next` arrays can be accessed directly to inspect and, if applicable, modify
+// the bits stored in the object.
 struct cxxrtl_object {
        // Type of the object.
        //
@@ -231,6 +243,12 @@ struct cxxrtl_object {
        uint32_t *curr;
        uint32_t *next;
 
+       // Opaque reference to an outline. Only meaningful for outline objects.
+       //
+       // See the documentation of `cxxrtl_outline` for details. When creating a `cxxrtl_object`, set
+       // this field to NULL.
+       struct _cxxrtl_outline *outline;
+
        // More description fields may be added in the future, but the existing ones will never change.
 };
 
@@ -272,6 +290,20 @@ void cxxrtl_enum(cxxrtl_handle handle, void *data,
                  void (*callback)(void *data, const char *name,
                                   struct cxxrtl_object *object, size_t parts));
 
+// Opaque reference to an outline.
+//
+// An outline is a group of outline objects that are evaluated simultaneously. The identity of
+// an outline can be compared to determine whether any two objects belong to the same outline.
+typedef struct _cxxrtl_outline *cxxrtl_outline;
+
+// Evaluate an outline.
+//
+// After evaluating an outline, the bits of every outline object contained in it are consistent
+// with the current state of the netlist. In general, any further modification to the netlist
+// causes every outline object to become stale, after which the corresponding outline must be
+// re-evaluated, otherwise the bits read from that object are meaningless.
+void cxxrtl_outline_eval(cxxrtl_outline outline);
+
 #ifdef __cplusplus
 }
 #endif
index dbeabbaf2d8548862f77e1a69e4fb40af2e02b54..6ee98b4284db16080b45054f50b400be70e677bc 100644 (file)
@@ -28,10 +28,13 @@ class vcd_writer {
                size_t ident;
                size_t width;
                chunk_t *curr;
-               size_t prev_off;
+               size_t cache_offset;
+               debug_outline *outline;
+               bool *outline_warm;
        };
 
        std::vector<std::string> current_scope;
+       std::map<debug_outline*, bool> outlines;
        std::vector<variable> variables;
        std::vector<chunk_t> cache;
        std::map<chunk_t*, size_t> aliases;
@@ -112,16 +115,22 @@ class vcd_writer {
                buffer += '\n';
        }
 
-       const variable &register_variable(size_t width, chunk_t *curr, bool constant = false) {
+       void reset_outlines() {
+               for (auto &outline_it : outlines)
+                       outline_it.second = /*warm=*/(outline_it.first == nullptr);
+       }
+
+       variable &register_variable(size_t width, chunk_t *curr, bool constant = false, debug_outline *outline = nullptr) {
                if (aliases.count(curr)) {
                        return variables[aliases[curr]];
                } else {
+                       auto outline_it = outlines.emplace(outline, /*warm=*/(outline == nullptr)).first;
                        const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
                        aliases[curr] = variables.size();
                        if (constant) {
-                               variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1 });
+                               variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1, outline_it->first, &outline_it->second });
                        } else {
-                               variables.emplace_back(variable { variables.size(), width, curr, cache.size() });
+                               variables.emplace_back(variable { variables.size(), width, curr, cache.size(), outline_it->first, &outline_it->second });
                                cache.insert(cache.end(), &curr[0], &curr[chunks]);
                        }
                        return variables.back();
@@ -129,13 +138,17 @@ class vcd_writer {
        }
 
        bool test_variable(const variable &var) {
-               if (var.prev_off == (size_t)-1)
+               if (var.cache_offset == (size_t)-1)
                        return false; // constant
+               if (!*var.outline_warm) {
+                       var.outline->eval();
+                       *var.outline_warm = true;
+               }
                const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
-               if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) {
+               if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset])) {
                        return false;
                } else {
-                       std::copy(&var.curr[0], &var.curr[chunks], &cache[var.prev_off]);
+                       std::copy(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset]);
                        return true;
                }
        }
@@ -197,6 +210,10 @@ public:
                                emit_var(register_variable(item.width, item.curr),
                                         "wire", name, item.lsb_at, multipart);
                                break;
+                       case debug_item::OUTLINE:
+                               emit_var(register_variable(item.width, item.curr, /*constant=*/false, item.outline),
+                                        "wire", name, item.lsb_at, multipart);
+                               break;
                }
        }
 
@@ -228,6 +245,7 @@ public:
                        emit_scope({});
                        emit_enddefinitions();
                }
+               reset_outlines();
                emit_time(timestamp);
                for (auto var : variables)
                        if (test_variable(var) || first_sample) {