cxxrtl: expose driver kind in debug information.
authorwhitequark <whitequark@whitequark.org>
Wed, 2 Sep 2020 17:16:10 +0000 (17:16 +0000)
committerwhitequark <whitequark@whitequark.org>
Wed, 2 Sep 2020 18:00:12 +0000 (18:00 +0000)
This can be useful to determine whether the wire should be a part of
a design checkpoint, whether it can be used to override design state,
and whether driving it may cause a conflict.

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

index df42f5807ced2b29f8d529d35808b74dbc658515..41089a153d5fb32d50a5fa3683b1e8dd508a221f 100644 (file)
@@ -837,6 +837,9 @@ struct debug_item : ::cxxrtl_object {
                INPUT  = CXXRTL_INPUT,
                OUTPUT = CXXRTL_OUTPUT,
                INOUT  = CXXRTL_INOUT,
+               DRIVEN_SYNC = CXXRTL_DRIVEN_SYNC,
+               DRIVEN_COMB = CXXRTL_DRIVEN_COMB,
+               UNDRIVEN    = CXXRTL_UNDRIVEN,
        };
 
        debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {}
@@ -856,11 +859,11 @@ struct debug_item : ::cxxrtl_object {
        }
 
        template<size_t Bits>
-       debug_item(const value<Bits> &item, size_t lsb_offset = 0, uint32_t flags_ = 0) {
+       debug_item(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    = VALUE;
-               flags   = flags_;
+               flags   = DRIVEN_COMB;
                width   = Bits;
                lsb_at  = lsb_offset;
                depth   = 1;
@@ -903,7 +906,7 @@ struct debug_item : ::cxxrtl_object {
                static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
                              "value<Bits> is not compatible with C layout");
                type    = ALIAS;
-               flags   = 0;
+               flags   = DRIVEN_COMB;
                width   = Bits;
                lsb_at  = lsb_offset;
                depth   = 1;
@@ -918,7 +921,7 @@ struct debug_item : ::cxxrtl_object {
                              sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
                              "wire<Bits> is not compatible with C layout");
                type    = ALIAS;
-               flags   = 0;
+               flags   = DRIVEN_COMB;
                width   = Bits;
                lsb_at  = lsb_offset;
                depth   = 1;
index 00b2ed315551b31b56c5760a06af2ad2ea4fe751..dfea04409dfceca98989416dd0cbd0c0555f2aaf 100644 (file)
@@ -273,6 +273,7 @@ struct FlowGraph {
        std::vector<Node*> nodes;
        dict<const RTLIL::Wire*, pool<Node*, hash_ptr_ops>> wire_comb_defs, wire_sync_defs, wire_uses;
        dict<const RTLIL::Wire*, bool> wire_def_elidable, wire_use_elidable;
+       dict<RTLIL::SigBit, bool> bit_has_state;
 
        ~FlowGraph()
        {
@@ -294,6 +295,8 @@ struct FlowGraph {
                                        wire_comb_defs[chunk.wire].insert(node);
                                }
                        }
+               for (auto bit : sig.bits())
+                       bit_has_state[bit] |= is_ff;
                // Only comb defs of an entire wire in the right order can be elided.
                if (!is_ff && sig.is_wire())
                        wire_def_elidable[sig.as_wire()] = elidable;
@@ -550,6 +553,7 @@ struct CxxrtlWorker {
        pool<const RTLIL::Wire*> localized_wires;
        dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires;
        dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires;
+       dict<RTLIL::SigBit, bool> bit_has_state;
        dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations;
        dict<const RTLIL::Module*, bool> eval_converges;
 
@@ -1636,6 +1640,10 @@ struct CxxrtlWorker {
                size_t count_alias_wires = 0;
                size_t count_member_wires = 0;
                size_t count_skipped_wires = 0;
+               size_t count_driven_sync = 0;
+               size_t count_driven_comb = 0;
+               size_t count_undriven = 0;
+               size_t count_mixed_driver = 0;
                inc_indent();
                        f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
                        for (auto wire : module->wires()) {
@@ -1661,15 +1669,54 @@ struct CxxrtlWorker {
                                        count_alias_wires++;
                                } else if (!localized_wires.count(wire)) {
                                        // Member wire
-                                       f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
-                                       f << ", debug_item(" << mangle(wire) << ", ";
-                                       f << wire->start_offset;
+                                       std::vector<std::string> flags;
+
                                        if (wire->port_input && wire->port_output)
-                                               f << ", debug_item::INOUT";
+                                               flags.push_back("INOUT");
                                        else if (wire->port_input)
-                                               f << ", debug_item::INPUT";
+                                               flags.push_back("INPUT");
                                        else if (wire->port_output)
-                                               f << ", debug_item::OUTPUT";
+                                               flags.push_back("OUTPUT");
+
+                                       bool has_driven_sync = false;
+                                       bool has_driven_comb = false;
+                                       bool has_undriven = false;
+                                       SigSpec sig(wire);
+                                       for (auto bit : sig.bits())
+                                               if (!bit_has_state.count(bit))
+                                                       has_undriven = true;
+                                               else if (bit_has_state[bit])
+                                                       has_driven_sync = true;
+                                               else
+                                                       has_driven_comb = true;
+                                       if (has_driven_sync)
+                                               flags.push_back("DRIVEN_SYNC");
+                                       if (has_driven_sync && !has_driven_comb && !has_undriven)
+                                               count_driven_sync++;
+                                       if (has_driven_comb)
+                                               flags.push_back("DRIVEN_COMB");
+                                       if (!has_driven_sync && has_driven_comb && !has_undriven)
+                                               count_driven_comb++;
+                                       if (has_undriven)
+                                               flags.push_back("UNDRIVEN");
+                                       if (!has_driven_sync && !has_driven_comb && has_undriven)
+                                               count_undriven++;
+                                       if (has_driven_sync + has_driven_comb + has_undriven > 1)
+                                               count_mixed_driver++;
+
+                                       f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
+                                       f << ", debug_item(" << mangle(wire) << ", ";
+                                       f << wire->start_offset;
+                                       bool first = true;
+                                       for (auto flag : flags) {
+                                               if (first) {
+                                                       first = false;
+                                                       f << ", ";
+                                               } else {
+                                                       f << "|";
+                                               }
+                                               f << "debug_item::" << flag;
+                                       }
                                        f << "));\n";
                                        count_member_wires++;
                                } else {
@@ -1698,7 +1745,11 @@ struct CxxrtlWorker {
                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\n", count_member_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);
        }
 
@@ -2217,6 +2268,9 @@ struct CxxrtlWorker {
 
                        eval_converges[module] = feedback_wires.empty() && buffered_comb_wires.empty();
 
+                       for (auto item : flow.bit_has_state)
+                               bit_has_state.insert(item);
+
                        if (debug_info) {
                                // Find wires that alias other wires or are tied to a constant; debug information can be enriched with these
                                // at essentially zero additional cost.
index d2e9787dd4d1adeda79122519728f121a387b004..385d6dcf386080c80a878ac34a9979807984bb8d 100644 (file)
@@ -73,6 +73,10 @@ int cxxrtl_commit(cxxrtl_handle handle);
 size_t cxxrtl_step(cxxrtl_handle handle);
 
 // Type of a simulated object.
+//
+// The type of a simulated object indicates the way it is stored and the operations that are legal
+// to perform on it (i.e. won't crash the simulation). It says very little about object semantics,
+// which is specified through flags.
 enum cxxrtl_type {
        // Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by
        // combinatorial cells, or toplevel input nodes.
@@ -86,7 +90,8 @@ enum cxxrtl_type {
        CXXRTL_VALUE = 0,
 
        // Wires correspond to doubly buffered netlist nodes, i.e. nodes driven, at least in part, by
-       // storage cells, or by combinatorial cells that are a part of a feedback path.
+       // storage cells, or by combinatorial cells that are a part of a feedback path. They are also
+       // present in non-optimized builds.
        //
        // Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are
        // distinct for wires). Note that changes to the bits driven by combinatorial cells will be
@@ -113,6 +118,12 @@ enum cxxrtl_type {
 };
 
 // Flags of a simulated object.
+//
+// The flags of a simulated object indicate its role in the netlist:
+//  * The flags `CXXRTL_INPUT` and `CXXRTL_OUTPUT` designate module ports.
+//  * The flags `CXXRTL_DRIVEN_SYNC`, `CXXRTL_DRIVEN_COMB`, and `CXXRTL_UNDRIVEN` specify
+//    the semantics of node state. An object with several of these flags set has different bits
+//    follow different semantics.
 enum cxxrtl_flag {
        // Node is a module input port.
        //
@@ -131,6 +142,38 @@ enum cxxrtl_flag {
        // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with other flags.
        CXXRTL_INOUT = (CXXRTL_INPUT|CXXRTL_OUTPUT),
 
+       // Node has bits that are driven by a storage cell.
+       //
+       // This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with
+       // `CXXRTL_DRIVEN_COMB` and `CXXRTL_UNDRIVEN`, as well as other flags.
+       //
+       // This flag is set on wires that have bits connected directly to the output of a flip-flop or
+       // a latch, and hold its state. Many `CXXRTL_WIRE` objects may not have the `CXXRTL_DRIVEN_SYNC`
+       // flag set; for example, output ports and feedback wires generally won't. Writing to the `next`
+       // pointer of these wires updates stored state, and for designs without combinatorial loops,
+       // capturing the value from every of these wires through the `curr` pointer creates a complete
+       // snapshot of the design state.
+       CXXRTL_DRIVEN_SYNC = 1 << 2,
+
+       // 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 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
+       // through the `next` pointer (if it is not NULL) has no effect.
+       CXXRTL_DRIVEN_COMB = 1 << 3,
+
+       // Node has bits that are not driven.
+       //
+       // This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
+       // with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_DRIVEN_COMB`, as well as other flags.
+       //
+       // This flag is set on objects that have bits not driven by an output of any cell or by another
+       // node, such as inputs and dangling wires.
+       CXXRTL_UNDRIVEN = 1 << 4,
+
        // More object flags may be added in the future, but the existing ones will never change.
 };