Merge pull request #2479 from zachjs/const-arg-hint
[yosys.git] / backends / cxxrtl / cxxrtl_backend.cc
index a48ea5b23a8115d682b4c33d0f830d33fa47c3c7..916303bfe49d37f0bd491e490fb27ad70e460060 100644 (file)
@@ -195,7 +195,7 @@ bool is_extending_cell(RTLIL::IdString type)
                ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool));
 }
 
-bool is_elidable_cell(RTLIL::IdString type)
+bool is_inlinable_cell(RTLIL::IdString type)
 {
        return is_unary_cell(type) || is_binary_cell(type) || type.in(
                ID($mux), ID($concat), ID($slice), ID($pmux));
@@ -211,7 +211,7 @@ bool is_ff_cell(RTLIL::IdString type)
 
 bool is_internal_cell(RTLIL::IdString type)
 {
-       return type[0] == '$' && !type.begins_with("$paramod");
+       return !type.isPublic() && !type.begins_with("$paramod");
 }
 
 bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell)
@@ -273,7 +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<const RTLIL::Wire*, bool> wire_def_inlinable, wire_use_inlinable;
        dict<RTLIL::SigBit, bool> bit_has_state;
 
        ~FlowGraph()
@@ -282,7 +282,7 @@ struct FlowGraph {
                        delete node;
        }
 
-       void add_defs(Node *node, const RTLIL::SigSpec &sig, bool is_ff, bool elidable)
+       void add_defs(Node *node, const RTLIL::SigSpec &sig, bool is_ff, bool inlinable)
        {
                for (auto chunk : sig.chunks())
                        if (chunk.wire) {
@@ -298,9 +298,9 @@ struct FlowGraph {
                        }
                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.
+               // Only comb defs of an entire wire in the right order can be inlined.
                if (!is_ff && sig.is_wire())
-                       wire_def_elidable[sig.as_wire()] = elidable;
+                       wire_def_inlinable[sig.as_wire()] = inlinable;
        }
 
        void add_uses(Node *node, const RTLIL::SigSpec &sig)
@@ -308,26 +308,26 @@ struct FlowGraph {
                for (auto chunk : sig.chunks())
                        if (chunk.wire) {
                                wire_uses[chunk.wire].insert(node);
-                               // Only a single use of an entire wire in the right order can be elided.
+                               // Only a single use of an entire wire in the right order can be inlined.
                                // (But the use can include other chunks.)
-                               if (!wire_use_elidable.count(chunk.wire))
-                                       wire_use_elidable[chunk.wire] = true;
+                               if (!wire_use_inlinable.count(chunk.wire))
+                                       wire_use_inlinable[chunk.wire] = true;
                                else
-                                       wire_use_elidable[chunk.wire] = false;
+                                       wire_use_inlinable[chunk.wire] = false;
                        }
        }
 
-       bool is_elidable(const RTLIL::Wire *wire) const
+       bool is_inlinable(const RTLIL::Wire *wire) const
        {
-               if (wire_def_elidable.count(wire) && wire_use_elidable.count(wire))
-                       return wire_def_elidable.at(wire) && wire_use_elidable.at(wire);
+               if (wire_def_inlinable.count(wire) && wire_use_inlinable.count(wire))
+                       return wire_def_inlinable.at(wire) && wire_use_inlinable.at(wire);
                return false;
        }
 
        // Connections
        void add_connect_defs_uses(Node *node, const RTLIL::SigSig &conn)
        {
-               add_defs(node, conn.first, /*is_ff=*/false, /*elidable=*/true);
+               add_defs(node, conn.first, /*is_ff=*/false, /*inlinable=*/true);
                add_uses(node, conn.second);
        }
 
@@ -373,8 +373,8 @@ struct FlowGraph {
                for (auto conn : cell->connections())
                        if (cell->output(conn.first))
                                if (is_cxxrtl_sync_port(cell, conn.first)) {
-                                       // See note regarding elidability below.
-                                       add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/false);
+                                       // See note regarding inlinability below.
+                                       add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false);
                                }
        }
 
@@ -382,19 +382,19 @@ struct FlowGraph {
        {
                for (auto conn : cell->connections()) {
                        if (cell->output(conn.first)) {
-                               if (is_elidable_cell(cell->type))
-                                       add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/true);
+                               if (is_inlinable_cell(cell->type))
+                                       add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/true);
                                else if (is_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool()))
-                                       add_defs(node, conn.second, /*is_ff=*/true,  /*elidable=*/false);
+                                       add_defs(node, conn.second, /*is_ff=*/true,  /*inlinable=*/false);
                                else if (is_internal_cell(cell->type))
-                                       add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/false);
+                                       add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false);
                                else if (!is_cxxrtl_sync_port(cell, conn.first)) {
-                                       // Although at first it looks like outputs of user-defined cells may always be elided, the reality is
-                                       // more complex. Fully sync outputs produce no defs and so don't participate in elision. Fully comb
+                                       // Although at first it looks like outputs of user-defined cells may always be inlined, the reality is
+                                       // more complex. Fully sync outputs produce no defs and so don't participate in inlining. Fully comb
                                        // outputs are assigned in a different way depending on whether the cell's eval() immediately converged.
-                                       // Unknown/mixed outputs could be elided, but should be rare in practical designs and don't justify
-                                       // the infrastructure required to elide outputs of cells with many of them.
-                                       add_defs(node, conn.second, /*is_ff=*/false, /*elidable=*/false);
+                                       // Unknown/mixed outputs could be inlined, but should be rare in practical designs and don't justify
+                                       // the infrastructure required to inline outputs of cells with many of them.
+                                       add_defs(node, conn.second, /*is_ff=*/false, /*inlinable=*/false);
                                }
                        }
                        if (cell->input(conn.first))
@@ -432,7 +432,7 @@ struct FlowGraph {
        void add_case_defs_uses(Node *node, const RTLIL::CaseRule *case_)
        {
                for (auto &action : case_->actions) {
-                       add_defs(node, action.first, /*is_ff=*/false, /*elidable=*/false);
+                       add_defs(node, action.first, /*is_ff=*/false, /*inlinable=*/false);
                        add_uses(node, action.second);
                }
                for (auto sub_switch : case_->switches) {
@@ -451,9 +451,9 @@ struct FlowGraph {
                for (auto sync : process->syncs)
                        for (auto action : sync->actions) {
                                if (sync->type == RTLIL::STp || sync->type == RTLIL::STn || sync->type == RTLIL::STe)
-                                 add_defs(node, action.first, /*is_ff=*/true,  /*elidable=*/false);
+                                 add_defs(node, action.first, /*is_ff=*/true,  /*inlinable=*/false);
                                else
-                                       add_defs(node, action.first, /*is_ff=*/false, /*elidable=*/false);
+                                       add_defs(node, action.first, /*is_ff=*/false, /*inlinable=*/false);
                                add_uses(node, action.second);
                        }
        }
@@ -535,10 +535,12 @@ struct CxxrtlWorker {
        bool unbuffer_public = false;
        bool localize_internal = false;
        bool localize_public = false;
-       bool elide_internal = false;
-       bool elide_public = false;
+       bool inline_internal = false;
+       bool inline_public = false;
 
        bool debug_info = false;
+       bool debug_alias = false;
+       bool debug_eval = false;
 
        std::ostringstream f;
        std::string indent;
@@ -549,12 +551,13 @@ struct CxxrtlWorker {
        dict<RTLIL::SigBit, RTLIL::SyncType> edge_types;
        pool<const RTLIL::Memory*> writable_memories;
        dict<const RTLIL::Cell*, pool<const RTLIL::Cell*>> transparent_for;
-       dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires;
        dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule;
        pool<const RTLIL::Wire*> unbuffered_wires;
        pool<const RTLIL::Wire*> localized_wires;
-       dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires;
+       dict<const RTLIL::Wire*, FlowGraph::Node> inlined_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 +789,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 (elided_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 = elided_wires[chunk.wire];
+                               const FlowGraph::Node &node = inlined_wires[chunk.wire];
                                switch (node.type) {
                                        case FlowGraph::Node::Type::CONNECT:
-                                               dump_connect_elided(node.connect);
+                                               dump_connect_expr(node.connect, for_debug);
                                                break;
                                        case FlowGraph::Node::Type::CELL_EVAL:
-                                               log_assert(is_elidable_cell(node.cell->type));
-                                               dump_cell_elided(node.cell);
+                                               log_assert(is_inlinable_cell(node.cell->type));
+                                               dump_cell_expr(node.cell, for_debug);
                                                break;
                                        default:
                                                log_assert(false);
@@ -821,36 +824,51 @@ 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);
-                       for (auto it = sig.chunks().rbegin() + 1; it != sig.chunks().rend(); ++it) {
-                               f << ".concat(";
-                               dump_sigchunk(*it, is_lhs);
-                               f << ")";
+                       bool first = true;
+                       auto chunks = sig.chunks();
+                       for (auto it = chunks.rbegin(); it != chunks.rend(); it++) {
+                               if (!first)
+                                       f << ".concat(";
+                               bool is_complex = dump_sigchunk(*it, is_lhs, for_debug);
+                               if (!is_lhs && it->width == 1) {
+                                       size_t repeat = 1;
+                                       while ((it + repeat) != chunks.rend() && *(it + repeat) == *it)
+                                               repeat++;
+                                       if (repeat > 1) {
+                                               if (is_complex)
+                                                       f << ".val()";
+                                               f << ".repeat<" << repeat << ">()";
+                                       }
+                                       it += repeat - 1;
+                               }
+                               if (!first)
+                                       f << ")";
+                               first = false;
                        }
                        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()";
        }
@@ -858,10 +876,10 @@ struct CxxrtlWorker {
        void collect_sigspec_rhs(const RTLIL::SigSpec &sig, std::vector<RTLIL::IdString> &cells)
        {
                for (auto chunk : sig.chunks()) {
-                       if (!chunk.wire || !elided_wires.count(chunk.wire))
+                       if (!chunk.wire || !inlined_wires.count(chunk.wire))
                                continue;
 
-                       const FlowGraph::Node &node = elided_wires[chunk.wire];
+                       const FlowGraph::Node &node = inlined_wires[chunk.wire];
                        switch (node.type) {
                                case FlowGraph::Node::Type::CONNECT:
                                        collect_connect(node.connect, cells);
@@ -875,34 +893,53 @@ struct CxxrtlWorker {
                }
        }
 
-       void dump_connect_elided(const RTLIL::SigSig &conn)
+       void dump_connect_expr(const RTLIL::SigSig &conn, bool for_debug = false)
+       {
+               dump_sigspec_rhs(conn.second, for_debug);
+       }
+
+       bool is_connect_inlined(const RTLIL::SigSig &conn)
        {
-               dump_sigspec_rhs(conn.second);
+               return conn.first.is_wire() && inlined_wires.count(conn.first.as_wire());
        }
 
-       bool is_connect_elided(const RTLIL::SigSig &conn)
+       bool is_connect_outlined(const RTLIL::SigSig &conn)
        {
-               return conn.first.is_wire() && elided_wires.count(conn.first.as_wire());
+               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_elided(conn))
+               if (!is_connect_inlined(conn))
                        return;
 
                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_elided(conn))
+               if (!for_debug && is_connect_inlined(conn))
+                       return;
+               if (for_debug && !is_connect_outlined(conn))
                        return;
 
-               f << indent << "// connection\n";
+               std::vector<RTLIL::IdString> inlined_cells;
+               collect_sigspec_rhs(conn.second, inlined_cells);
+               if (for_debug || inlined_cells.empty()) {
+                       f << indent << "// connection\n";
+               } else {
+                       f << indent << "// cells";
+                       for (auto inlined_cell : inlined_cells)
+                               f << " " << inlined_cell.str();
+                       f << "\n";
+               }
                f << indent;
-               dump_sigspec_lhs(conn.first);
+               dump_sigspec_lhs(conn.first, for_debug);
                f << " = ";
-               dump_connect_elided(conn);
+               dump_connect_expr(conn, for_debug);
                f << ";\n";
        }
 
@@ -919,7 +956,7 @@ struct CxxrtlWorker {
                                }
        }
 
-       void dump_cell_elided(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 +964,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 +973,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 +992,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 << ",";
@@ -983,15 +1020,26 @@ struct CxxrtlWorker {
                }
        }
 
-       bool is_cell_elided(const RTLIL::Cell *cell)
+       bool is_cell_inlined(const RTLIL::Cell *cell)
        {
-               return is_elidable_cell(cell->type) && cell->hasPort(ID::Y) && cell->getPort(ID::Y).is_wire() &&
-                       elided_wires.count(cell->getPort(ID::Y).as_wire());
+               return is_inlinable_cell(cell->type) && cell->hasPort(ID::Y) && cell->getPort(ID::Y).is_wire() &&
+                       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_elided(cell))
+               if (!is_cell_inlined(cell))
                        return;
 
                cells.push_back(cell->name);
@@ -1000,35 +1048,37 @@ 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_elided(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.
 
-               std::vector<RTLIL::IdString> elided_cells;
-               if (is_elidable_cell(cell->type)) {
+               std::vector<RTLIL::IdString> inlined_cells;
+               if (is_inlinable_cell(cell->type)) {
                        for (auto port : cell->connections())
                                if (port.first != ID::Y)
-                                       collect_sigspec_rhs(port.second, elided_cells);
+                                       collect_sigspec_rhs(port.second, inlined_cells);
                }
-               if (elided_cells.empty()) {
+               if (inlined_cells.empty()) {
                        dump_attrs(cell);
                        f << indent << "// cell " << cell->name.str() << "\n";
                } else {
                        f << indent << "// cells";
-                       for (auto elided_cell : elided_cells)
-                               f << " " << elided_cell.str();
+                       for (auto inlined_cell : inlined_cells)
+                               f << " " << inlined_cell.str();
                        f << "\n";
                }
 
                // Elidable cells
-               if (is_elidable_cell(cell->type)) {
+               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_elided(cell);
+                       dump_cell_expr(cell, for_debug);
                        f << ";\n";
                // Flip-flops
                } else if (is_ff_cell(cell->type)) {
@@ -1036,8 +1086,12 @@ struct CxxrtlWorker {
                                // Edge-sensitive logic
                                RTLIL::SigBit clk_bit = cell->getPort(ID::CLK)[0];
                                clk_bit = sigmaps[clk_bit.wire->module](clk_bit);
-                               f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
-                                           << mangle(clk_bit) << ") {\n";
+                               if (clk_bit.wire) {
+                                       f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
+                                                   << mangle(clk_bit) << ") {\n";
+                               } else {
+                                       f << indent << "if (false) {\n";
+                               }
                                inc_indent();
                                        if (cell->hasPort(ID::EN)) {
                                                f << indent << "if (";
@@ -1130,8 +1184,12 @@ struct CxxrtlWorker {
                        if (cell->getParam(ID::CLK_ENABLE).as_bool()) {
                                RTLIL::SigBit clk_bit = cell->getPort(ID::CLK)[0];
                                clk_bit = sigmaps[clk_bit.wire->module](clk_bit);
-                               f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
-                                           << mangle(clk_bit) << ") {\n";
+                               if (clk_bit.wire) {
+                                       f << indent << "if (" << (cell->getParam(ID::CLK_POLARITY).as_bool() ? "posedge_" : "negedge_")
+                                                   << mangle(clk_bit) << ") {\n";
+                               } else {
+                                       f << indent << "if (false) {\n";
+                               }
                                inc_indent();
                        }
                        RTLIL::Memory *memory = cell->module->memories[cell->getParam(ID::MEMID).decode_string()];
@@ -1149,12 +1207,12 @@ struct CxxrtlWorker {
                                }
                                // The generated code has two bounds checks; one in an assertion, and another that guards the read.
                                // This is done so that the code does not invoke undefined behavior under any conditions, but nevertheless
-                               // loudly crashes if an illegal condition is encountered. The assert may be turned off with -DNDEBUG not
-                               // just for release builds, but also to make sure the simulator (which is presumably embedded in some
+                               // loudly crashes if an illegal condition is encountered. The assert may be turned off with -DCXXRTL_NDEBUG
+                               // not only for release builds, but also to make sure the simulator (which is presumably embedded in some
                                // larger program) will never crash the code that calls into it.
                                //
                                // If assertions are disabled, out of bounds reads are defined to return zero.
-                               f << indent << "assert(" << valid_index_temp << ".valid && \"out of bounds read\");\n";
+                               f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds read\");\n";
                                f << indent << "if(" << valid_index_temp << ".valid) {\n";
                                inc_indent();
                                        if (writable_memories[memory]) {
@@ -1211,7 +1269,7 @@ struct CxxrtlWorker {
                                // See above for rationale of having both the assert and the condition.
                                //
                                // If assertions are disabled, out of bounds writes are defined to do nothing.
-                               f << indent << "assert(" << valid_index_temp << ".valid && \"out of bounds write\");\n";
+                               f << indent << "CXXRTL_ASSERT(" << valid_index_temp << ".valid && \"out of bounds write\");\n";
                                f << indent << "if (" << valid_index_temp << ".valid) {\n";
                                inc_indent();
                                        f << indent << mangle(memory) << ".update(" << valid_index_temp << ".index, ";
@@ -1232,10 +1290,19 @@ struct CxxrtlWorker {
                // User cells
                } else {
                        log_assert(cell->known());
+                       bool buffered_inputs = false;
                        const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
                        for (auto conn : cell->connections())
-                               if (cell->input(conn.first) && !cell->output(conn.first)) {
-                                       f << indent << mangle(cell) << access << mangle_wire_name(conn.first) << " = ";
+                               if (cell->input(conn.first)) {
+                                       RTLIL::Module *cell_module = cell->module->design->module(cell->type);
+                                       log_assert(cell_module != nullptr && cell_module->wire(conn.first) && conn.second.is_wire());
+                                       RTLIL::Wire *cell_module_wire = cell_module->wire(conn.first);
+                                       f << indent << mangle(cell) << access << mangle_wire_name(conn.first);
+                                       if (!is_cxxrtl_blackbox_cell(cell) && !unbuffered_wires[cell_module_wire]) {
+                                               buffered_inputs = true;
+                                               f << ".next";
+                                       }
+                                       f << " = ";
                                        dump_sigspec_rhs(conn.second);
                                        f << ";\n";
                                        if (getenv("CXXRTL_VOID_MY_WARRANTY")) {
@@ -1247,19 +1314,11 @@ struct CxxrtlWorker {
                                                // with:
                                                //   top.prev_p_clk = value<1>{0u}; top.p_clk = value<1>{1u}; top.step();
                                                // Don't rely on this; it will be removed without warning.
-                                               RTLIL::Module *cell_module = cell->module->design->module(cell->type);
-                                               if (cell_module != nullptr && cell_module->wire(conn.first) && conn.second.is_wire()) {
-                                                       RTLIL::Wire *cell_module_wire = cell_module->wire(conn.first);
-                                                       if (edge_wires[conn.second.as_wire()] && edge_wires[cell_module_wire]) {
-                                                               f << indent << mangle(cell) << access << "prev_" << mangle(cell_module_wire) << " = ";
-                                                               f << "prev_" << mangle(conn.second.as_wire()) << ";\n";
-                                                       }
+                                               if (edge_wires[conn.second.as_wire()] && edge_wires[cell_module_wire]) {
+                                                       f << indent << mangle(cell) << access << "prev_" << mangle(cell_module_wire) << " = ";
+                                                       f << "prev_" << mangle(conn.second.as_wire()) << ";\n";
                                                }
                                        }
-                               } else if (cell->input(conn.first)) {
-                                       f << indent << mangle(cell) << access << mangle_wire_name(conn.first) << ".next = ";
-                                       dump_sigspec_rhs(conn.second);
-                                       f << ";\n";
                                }
                        auto assign_from_outputs = [&](bool cell_converged) {
                                for (auto conn : cell->connections()) {
@@ -1277,9 +1336,9 @@ struct CxxrtlWorker {
                                                // have any buffered wires if they were not output ports. Imagine inlining the cell's eval() function,
                                                // and consider the fate of the localized wires that used to be output ports.)
                                                //
-                                               // Unlike cell inputs (which are never buffered), it is not possible to know apriori whether the cell
-                                               // (which may be late bound) will converge immediately. Because of this, the choice between using .curr
-                                               // (appropriate for buffered outputs) and .next (appropriate for unbuffered outputs) is made at runtime.
+                                               // It is not possible to know apriori whether the cell (which may be late bound) will converge immediately.
+                                               // Because of this, the choice between using .curr (appropriate for buffered outputs) and .next (appropriate
+                                               // for unbuffered outputs) is made at runtime.
                                                if (cell_converged && is_cxxrtl_comb_port(cell, conn.first))
                                                        f << ".next;\n";
                                                else
@@ -1287,16 +1346,23 @@ struct CxxrtlWorker {
                                        }
                                }
                        };
-                       f << indent << "if (" << mangle(cell) << access << "eval()) {\n";
-                       inc_indent();
-                               assign_from_outputs(/*cell_converged=*/true);
-                       dec_indent();
-                       f << indent << "} else {\n";
-                       inc_indent();
+                       if (buffered_inputs) {
+                               // If we have any buffered inputs, there's no chance of converging immediately.
+                               f << indent << mangle(cell) << access << "eval();\n";
                                f << indent << "converged = false;\n";
                                assign_from_outputs(/*cell_converged=*/false);
-                       dec_indent();
-                       f << indent << "}\n";
+                       } else {
+                               f << indent << "if (" << mangle(cell) << access << "eval()) {\n";
+                               inc_indent();
+                                       assign_from_outputs(/*cell_converged=*/true);
+                               dec_indent();
+                               f << indent << "} else {\n";
+                               inc_indent();
+                                       f << indent << "converged = false;\n";
+                                       assign_from_outputs(/*cell_converged=*/false);
+                               dec_indent();
+                               f << indent << "}\n";
+                       }
                }
        }
 
@@ -1442,16 +1508,13 @@ struct CxxrtlWorker {
                }
        }
 
-       void dump_wire(const RTLIL::Wire *wire, bool is_local_context)
+       void dump_wire(const RTLIL::Wire *wire, bool is_local)
        {
-               if (elided_wires.count(wire))
-                       return;
-
-               if (localized_wires[wire] && is_local_context) {
+               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_context) {
+               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));
@@ -1514,6 +1577,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;
@@ -1581,7 +1661,7 @@ struct CxxrtlWorker {
                                        }
                                }
                                for (auto wire : module->wires())
-                                       dump_wire(wire, /*is_local_context=*/true);
+                                       dump_wire(wire, /*is_local=*/true);
                                for (auto node : schedule[module]) {
                                        switch (node.type) {
                                                case FlowGraph::Node::Type::CONNECT:
@@ -1603,12 +1683,33 @@ 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();
                        f << indent << "bool changed = false;\n";
                        for (auto wire : module->wires()) {
-                               if (elided_wires.count(wire))
+                               if (inlined_wires.count(wire))
                                        continue;
                                if (unbuffered_wires[wire]) {
                                        if (edge_wires[wire])
@@ -1616,19 +1717,19 @@ struct CxxrtlWorker {
                                        continue;
                                }
                                if (!module->get_bool_attribute(ID(cxxrtl_blackbox)) || wire->port_id != 0)
-                                       f << indent << "changed |= " << mangle(wire) << ".commit();\n";
+                                       f << indent << "if (" << mangle(wire) << ".commit()) changed = true;\n";
                        }
                        if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
                                for (auto memory : module->memories) {
                                        if (!writable_memories[memory.second])
                                                continue;
-                                       f << indent << "changed |= " << mangle(memory.second) << ".commit();\n";
+                                       f << indent << "if (" << mangle(memory.second) << ".commit()) changed = true;\n";
                                }
                                for (auto cell : module->cells()) {
                                        if (is_internal_cell(cell->type))
                                                continue;
                                        const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
-                                       f << indent << "changed |= " << mangle(cell) << access << "commit();\n";
+                                       f << indent << "if (" << mangle(cell) << access << "commit()) changed = true;\n";
                                }
                        }
                        f << indent << "return changed;\n";
@@ -1640,6 +1741,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;
@@ -1649,7 +1751,7 @@ struct CxxrtlWorker {
                inc_indent();
                        f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
                        for (auto wire : module->wires()) {
-                               if (wire->name[0] != '\\')
+                               if (!wire->name.isPublic())
                                        continue;
                                if (module->get_bool_attribute(ID(cxxrtl_blackbox)) && (wire->port_id == 0))
                                        continue;
@@ -1669,6 +1771,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;
@@ -1722,12 +1830,13 @@ struct CxxrtlWorker {
                                        f << "));\n";
                                        count_member_wires++;
                                } else {
+                                       // Localized or inlined wire with no debug information
                                        count_skipped_wires++;
                                }
                        }
                        if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
                                for (auto &memory_it : module->memories) {
-                                       if (memory_it.first[0] != '\\')
+                                       if (!memory_it.first.isPublic())
                                                continue;
                                        f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(memory_it.second));
                                        f << ", debug_item(" << mangle(memory_it.second) << ", ";
@@ -1745,14 +1854,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)
@@ -1792,7 +1903,7 @@ struct CxxrtlWorker {
                        inc_indent();
                                for (auto wire : module->wires()) {
                                        if (wire->port_id != 0)
-                                               dump_wire(wire, /*is_local_context=*/false);
+                                               dump_wire(wire, /*is_local=*/false);
                                }
                                f << "\n";
                                f << indent << "bool eval() override {\n";
@@ -1838,8 +1949,9 @@ struct CxxrtlWorker {
                        f << indent << "struct " << mangle(module) << " : public module {\n";
                        inc_indent();
                                for (auto wire : module->wires())
-                                       dump_wire(wire, /*is_local_context=*/false);
-                               f << "\n";
+                                       dump_wire(wire, /*is_local=*/false);
+                               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);
@@ -1869,10 +1981,62 @@ struct CxxrtlWorker {
                                }
                                if (has_cells)
                                        f << "\n";
+                               f << indent << mangle(module) << "() {}\n";
+                               if (has_cells) {
+                                       f << indent << mangle(module) << "(adopt, " << mangle(module) << " other) :\n";
+                                       bool first = true;
+                                       for (auto cell : module->cells()) {
+                                               if (is_internal_cell(cell->type))
+                                                       continue;
+                                               if (first) {
+                                                       first = false;
+                                               } else {
+                                                       f << ",\n";
+                                               }
+                                               RTLIL::Module *cell_module = module->design->module(cell->type);
+                                               if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) {
+                                                       f << indent << "  " << mangle(cell) << "(std::move(other." << mangle(cell) << "))";
+                                               } else {
+                                                       f << indent << "  " << mangle(cell) << "(adopt {}, std::move(other." << mangle(cell) << "))";
+                                               }
+                                       }
+                                       f << " {\n";
+                                       inc_indent();
+                                               for (auto cell : module->cells()) {
+                                                       if (is_internal_cell(cell->type))
+                                                               continue;
+                                                       RTLIL::Module *cell_module = module->design->module(cell->type);
+                                                       if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox)))
+                                                               f << indent << mangle(cell) << "->reset();\n";
+                                               }
+                                       dec_indent();
+                                       f << indent << "}\n";
+                               } else {
+                                       f << indent << mangle(module) << "(adopt, " << mangle(module) << " other) {}\n";
+                               }
+                               f << "\n";
+                               f << indent << "void reset() override {\n";
+                               inc_indent();
+                                       f << indent << "*this = " << mangle(module) << "(adopt {}, std::move(*this));\n";
+                               dec_indent();
+                               f << indent << "}\n";
+                               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";
@@ -1892,6 +2056,13 @@ 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 << "CXXRTL_EXTREMELY_COLD\n";
                        f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
                        dump_debug_info_method(module);
                        f << indent << "}\n";
@@ -2105,14 +2276,14 @@ struct CxxrtlWorker {
 
                                // Various DFF cells are treated like posedge/negedge processes, see above for details.
                                if (cell->type.in(ID($dff), ID($dffe), ID($adff), ID($adffe), ID($dffsr), ID($dffsre), ID($sdff), ID($sdffe), ID($sdffce))) {
-                                       if (cell->getPort(ID::CLK).is_wire())
+                                       if (sigmap(cell->getPort(ID::CLK)).is_wire())
                                                register_edge_signal(sigmap, cell->getPort(ID::CLK),
                                                        cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn);
                                }
                                // Similar for memory port cells.
                                if (cell->type.in(ID($memrd), ID($memwr))) {
                                        if (cell->getParam(ID::CLK_ENABLE).as_bool()) {
-                                               if (cell->getPort(ID::CLK).is_wire())
+                                               if (sigmap(cell->getPort(ID::CLK)).is_wire())
                                                        register_edge_signal(sigmap, cell->getPort(ID::CLK),
                                                                cell->parameters[ID::CLK_POLARITY].as_bool() ? RTLIL::STp : RTLIL::STn);
                                        }
@@ -2178,16 +2349,16 @@ struct CxxrtlWorker {
                        }
 
                        for (auto wire : module->wires()) {
-                               if (!flow.is_elidable(wire)) continue;
+                               if (!flow.is_inlinable(wire)) continue;
                                if (wire->port_id != 0) continue;
                                if (wire->get_bool_attribute(ID::keep)) continue;
-                               if (wire->name.begins_with("$") && !elide_internal) continue;
-                               if (wire->name.begins_with("\\") && !elide_public) continue;
+                               if (wire->name.begins_with("$") && !inline_internal) continue;
+                               if (wire->name.begins_with("\\") && !inline_public) continue;
                                if (edge_wires[wire]) continue;
                                if (flow.wire_comb_defs[wire].size() > 1)
                                        log_cmd_error("Wire %s.%s has multiple drivers.\n", log_id(module), log_id(wire));
                                log_assert(flow.wire_comb_defs[wire].size() == 1);
-                               elided_wires[wire] = **flow.wire_comb_defs[wire].begin();
+                               inlined_wires[wire] = **flow.wire_comb_defs[wire].begin();
                        }
 
                        dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_defs;
@@ -2195,6 +2366,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)
@@ -2224,9 +2400,9 @@ struct CxxrtlWorker {
                                        for (auto succ_node : flow.wire_uses[wire])
                                                if (evaluated[succ_node]) {
                                                        feedback_wires.insert(wire);
-                                                       // Feedback wires may never be elided because feedback requires state, but the point of elision
-                                                       // (and localization) is to eliminate state.
-                                                       elided_wires.erase(wire);
+                                                       // Feedback wires may never be inlined because feedback requires state, but the point of
+                                                       // inlining (and localization) is to eliminate state.
+                                                       inlined_wires.erase(wire);
                                                }
                        }
 
@@ -2275,41 +2451,75 @@ struct CxxrtlWorker {
                        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.
-                               //
-                               // Note that the information collected here can't be used for optimizing the netlist: debug information queries
-                               // are pure and run on a design in a stable state, which allows assumptions that do not otherwise hold.
+                       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[0] != '\\')
+                                       if (!wire->name.isPublic())
                                                continue;
-                                       if (!unbuffered_wires[wire])
+                                       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 (debug_info && debug_alias) {
+                               // Find wires that alias other wires or are tied to a constant. Both of these cases are
+                               // directly expressible in the debug information, improving coverage at zero cost.
+                               for (auto wire : module->wires()) {
+                                       if (!wire->name.isPublic())
                                                continue;
-                                       const RTLIL::Wire *wire_it = wire;
+                                       const RTLIL::Wire *cursor = wire;
+                                       RTLIL::SigSpec alias_of;
                                        while (1) {
-                                               if (!(flow.wire_def_elidable.count(wire_it) && flow.wire_def_elidable[wire_it]))
+                                               if (!(flow.wire_def_inlinable.count(cursor) && flow.wire_def_inlinable[cursor]))
                                                        break; // not an alias: complex def
-                                               log_assert(flow.wire_comb_defs[wire_it].size() == 1);
-                                               FlowGraph::Node *node = *flow.wire_comb_defs[wire_it].begin();
+                                               log_assert(flow.wire_comb_defs[cursor].size() == 1);
+                                               FlowGraph::Node *node = *flow.wire_comb_defs[cursor].begin();
                                                if (node->type != FlowGraph::Node::Type::CONNECT)
                                                        break; // not an alias: def by cell
                                                RTLIL::SigSpec rhs_sig = node->connect.second;
-                                               if (rhs_sig.is_wire()) {
-                                                       RTLIL::Wire *rhs_wire = rhs_sig.as_wire();
-                                                       if (unbuffered_wires[rhs_wire]) {
-                                                               wire_it = rhs_wire; // maybe an alias
-                                                       } else {
-                                                               debug_alias_wires[wire] = rhs_wire; // is an alias
+                                               if (rhs_sig.is_fully_const()) {
+                                                       alias_of = rhs_sig; // alias of const
+                                                       break;
+                                               } else if (rhs_sig.is_wire()) {
+                                                       RTLIL::Wire *rhs_wire = rhs_sig.as_wire(); // possible alias of wire
+                                                       if (rhs_wire->port_input && !rhs_wire->port_output) {
+                                                               alias_of = rhs_wire; // alias of input
                                                                break;
+                                                       } else if (!localized_wires.count(rhs_wire) && !inlined_wires.count(rhs_wire)) {
+                                                               alias_of = rhs_wire; // alias of member
+                                                               break;
+                                                       } else {
+                                                               if (rhs_wire->name.isPublic() && debug_outlined_wires.count(rhs_wire))
+                                                                       alias_of = rhs_wire; // alias of either outline or another alias
+                                                               cursor = rhs_wire; // keep looking
                                                        }
-                                               } else if (rhs_sig.is_fully_const()) {
-                                                       debug_const_wires[wire] = rhs_sig.as_const(); // is a const
-                                                       break;
                                                } else {
                                                        break; // not an alias: complex rhs
                                                }
                                        }
+                                       if (alias_of.empty()) {
+                                               continue;
+                                       } else if (alias_of.is_fully_const()) {
+                                               debug_const_wires[wire] = alias_of.as_const();
+                                       } else if (alias_of.is_wire()) {
+                                               debug_alias_wires[wire] = alias_of.as_wire();
+                                       } else log_abort();
+                                       if (inlined_wires.count(wire))
+                                               debug_outlined_wires.erase(wire);
                                }
                        }
                }
@@ -2401,8 +2611,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 = 3;
 
        CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { }
        void help() override
@@ -2604,7 +2813,7 @@ struct CxxrtlBackend : public Backend {
                log("        like -O1, and unbuffer internal wires if possible.\n");
                log("\n");
                log("    -O3\n");
-               log("        like -O2, and elide internal wires if possible.\n");
+               log("        like -O2, and inline internal wires if possible.\n");
                log("\n");
                log("    -O4\n");
                log("        like -O3, and unbuffer public wires not marked (*keep*) if possible.\n");
@@ -2613,22 +2822,26 @@ struct CxxrtlBackend : public Backend {
                log("        like -O4, and localize public wires not marked (*keep*) if possible.\n");
                log("\n");
                log("    -O6\n");
-               log("        like -O5, and elide 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("        like -O5, and inline public wires not marked (*keep*) if possible.\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("        no debug information. the C API is unavailable.\n");
                log("\n");
                log("    -g1\n");
-               log("        debug information for non-optimized public wires. this also makes it\n");
-               log("        possible to use the C API.\n");
+               log("        debug information for member public wires only. this is the bare minimum\n");
+               log("        necessary to access all design state. enables the C API.\n");
+               log("\n");
+               log("    -g2\n");
+               log("        like -g1, and include debug information for public wires that are tied\n");
+               log("        to a constant or another public wire.\n");
+               log("\n");
+               log("    -g3\n");
+               log("        like -g2, and compute debug information on demand for all public wires\n");
+               log("        that were optimized out.\n");
                log("\n");
        }
 
@@ -2659,12 +2872,14 @@ struct CxxrtlBackend : public Backend {
                                continue;
                        }
                        if (args[argidx] == "-Og") {
-                               opt_level = OPT_LEVEL_DEBUG;
+                               log_warning("The `-Og` option has been removed. Use `-g3` 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 `-g3` instead for complete "
+                                           "design coverage regardless of optimization level.\n");
                                continue;
                        }
                        if (args[argidx] == "-O" && argidx+1 < args.size()) {
@@ -2701,7 +2916,7 @@ struct CxxrtlBackend : public Backend {
                switch (opt_level) {
                        // the highest level here must match DEFAULT_OPT_LEVEL
                        case 6:
-                               worker.elide_public = true;
+                               worker.inline_public = true;
                                YS_FALLTHROUGH
                        case 5:
                                worker.localize_public = true;
@@ -2710,7 +2925,7 @@ struct CxxrtlBackend : public Backend {
                                worker.unbuffer_public = true;
                                YS_FALLTHROUGH
                        case 3:
-                               worker.elide_internal = true;
+                               worker.inline_internal = true;
                                YS_FALLTHROUGH
                        case 2:
                                worker.localize_internal = true;
@@ -2725,6 +2940,12 @@ struct CxxrtlBackend : public Backend {
                }
                switch (debug_level) {
                        // the highest level here must match DEFAULT_DEBUG_LEVEL
+                       case 3:
+                               worker.debug_eval = true;
+                               YS_FALLTHROUGH
+                       case 2:
+                               worker.debug_alias = true;
+                               YS_FALLTHROUGH
                        case 1:
                                worker.debug_info = true;
                                YS_FALLTHROUGH