/*
  *  yosys -- Yosys Open SYnthesis Suite
  *
- *  Copyright (C) 2019  whitequark <whitequark@whitequark.org>
+ *  Copyright (C) 2019-2020  whitequark <whitequark@whitequark.org>
  *
  *  Permission to use, copy, modify, and/or distribute this software for any
  *  purpose with or without fee is hereby granted, provided that the above
 #include "kernel/rtlil.h"
 #include "kernel/register.h"
 #include "kernel/sigtools.h"
+#include "kernel/utils.h"
 #include "kernel/celltypes.h"
 #include "kernel/log.h"
 
                ID($dff), ID($dffe), ID($adff), ID($dffsr));
 }
 
+static bool is_internal_cell(RTLIL::IdString type)
+{
+       return type[0] == '$' && !type.begins_with("$paramod\\");
+}
+
 struct FlowGraph {
        struct Node {
                enum class Type {
                                        /* non-combinatorial outputs do not introduce defs */;
                                else if (is_elidable_cell(cell->type))
                                        add_defs(node, conn.second, /*elidable=*/true);
-                               else
+                               else if (is_internal_cell(cell->type))
                                        add_defs(node, conn.second, /*elidable=*/false);
+                               else {
+                                       // Unlike outputs of internal cells (which generate code that depends on the ability to set the output
+                                       // wire bits), outputs of user cells are normal wires, and the wires connected to them can be elided.
+                                       add_defs(node, conn.second, /*elidable=*/true);
+                               }
                        }
                        if (cell->input(conn.first))
                                add_uses(node, conn.second);
        dict<RTLIL::SigBit, RTLIL::SyncType> sync_types;
        pool<const RTLIL::Memory*> writable_memories;
        dict<const RTLIL::Cell*, pool<const RTLIL::Cell*>> transparent_for;
+       dict<const RTLIL::Cell*, dict<RTLIL::Wire*, RTLIL::IdString>> cell_wire_defs;
        dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires;
        dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule;
        pool<const RTLIL::Wire*> localized_wires;
                return "memory_" + mangle_name(name);
        }
 
+       std::string mangle_cell_name(const RTLIL::IdString &name)
+       {
+               // Class member namespace.
+               return "cell_" + mangle_name(name);
+       }
+
        std::string mangle_wire_name(const RTLIL::IdString &name)
        {
                // Class member namespace.
                return mangle_memory_name(memory->name);
        }
 
+       std::string mangle(const RTLIL::Cell *cell)
+       {
+               return mangle_cell_name(cell->name);
+       }
+
        std::string mangle(const RTLIL::Wire *wire)
        {
                return mangle_wire_name(wire->name);
                                                dump_connect_elided(node.connect);
                                                break;
                                        case FlowGraph::Node::Type::CELL:
-                                               dump_cell_elided(node.cell);
+                                               if (is_elidable_cell(node.cell->type)) {
+                                                       dump_cell_elided(node.cell);
+                                               } else {
+                                                       f << mangle(node.cell) << "." << mangle_wire_name(cell_wire_defs[node.cell][chunk.wire]) << ".curr";
+                                               }
                                                break;
                                        default:
                                                log_assert(false);
 
        bool is_cell_elided(const RTLIL::Cell *cell)
        {
-               return cell->hasPort(ID(Y)) && cell->getPort(ID(Y)).is_wire() && elided_wires.count(cell->getPort(ID(Y)).as_wire());
+               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());
        }
 
        void collect_cell(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells)
                                dec_indent();
                                f << indent << "}\n";
                        }
-               // Memory initializers
-               } else if (cell->type[0] == '$') {
+               // Internal cells
+               } else if (is_internal_cell(cell->type)) {
                        log_cmd_error("Unsupported internal cell `%s'.\n", cell->type.c_str());
+               // User cells
                } else {
-                       log_assert(false);
+                       log_assert(cell->known());
+                       for (auto conn : cell->connections())
+                               if (cell->input(conn.first)) {
+                                       f << indent << mangle(cell) << "." << mangle_wire_name(conn.first) << ".next = ";
+                                       dump_sigspec_rhs(conn.second);
+                                       f << ";\n";
+                               }
+                       f << indent << mangle(cell) << ".eval();\n";
+                       for (auto conn : cell->connections()) {
+                               if (conn.second.is_wire()) {
+                                       RTLIL::Wire *wire = conn.second.as_wire();
+                                       if (elided_wires.count(wire) && cell_wire_defs[cell].count(wire))
+                                               continue;
+                               }
+                               if (cell->output(conn.first)) {
+                                       f << indent;
+                                       dump_sigspec_lhs(conn.second);
+                                       f << " = " << mangle(cell) << "." << mangle_wire_name(conn.first) << ".curr;\n";
+                               }
+                       }
                }
        }
 
                        for (auto wire : module->wires())
                                dump_wire(wire, /*is_local=*/false);
                        f << "\n";
-                       for (auto memory : module->memories)
+                       bool has_memories = false;
+                       for (auto memory : module->memories) {
                                dump_memory(module, memory.second);
-                       if (!module->memories.empty())
+                               has_memories = true;
+                       }
+                       if (has_memories)
+                               f << "\n";
+                       bool has_cells = false;
+                       for (auto cell : module->cells()) {
+                               if (is_internal_cell(cell->type))
+                                       continue;
+                               f << indent << mangle_module_name(cell->type) << " " << mangle(cell) << ";\n";
+                               has_cells = true;
+                       }
+                       if (has_cells)
                                f << "\n";
                        f << indent << "void eval() override;\n";
                        f << indent << "bool commit() override;\n";
                        }
                dec_indent();
                f << "}\n";
-
                f << "\n";
+
                f << "bool " << mangle(module) << "::commit() {\n";
                inc_indent();
                        f << indent << "bool changed = false;\n";
                                        f << indent << "changed |= " << mangle(memory.second) << "[i].commit();\n";
                                dec_indent();
                        }
+                       for (auto cell : module->cells()) {
+                               if (is_internal_cell(cell->type))
+                                       continue;
+                               f << indent << "changed |= " << mangle(cell) << ".commit();\n";
+                       }
                        f << indent << "return changed;\n";
                dec_indent();
                f << "}\n";
+               f << "\n";
        }
 
        void dump_design(RTLIL::Design *design)
        {
+               TopoSort<RTLIL::Module*> topo_design;
+               for (auto module : design->modules()) {
+                       if (module->get_blackbox_attribute() || !design->selected_module(module))
+                               continue;
+                       topo_design.node(module);
+
+                       for (auto cell : module->cells()) {
+                               if (is_internal_cell(cell->type))
+                                       continue;
+                               log_assert(design->has(cell->type));
+                               topo_design.edge(design->module(cell->type), module);
+                       }
+               }
+               log_assert(topo_design.sort());
+
                f << "#include <cxxrtl.h>\n";
                f << "\n";
                f << "using namespace cxxrtl_yosys;\n";
                f << "\n";
                f << "namespace cxxrtl_design {\n";
-               for (auto module : design->modules()) {
-                       if (module->get_blackbox_attribute())
-                               continue;
-
+               f << "\n";
+               for (auto module : topo_design.sorted) {
                        if (!design->selected_module(module))
                                continue;
-
-                       f << "\n";
                        dump_module(module);
                }
-               f << "\n";
                f << "} // namespace cxxrtl_design\n";
        }
 
                                elided_wires[wire] = **flow.wire_defs[wire].begin();
                        }
 
+                       // Elided wires that are outputs of internal cells are always connected to a well known port (Y).
+                       // For user cells, there could be multiple of them, and we need a way to look up the port name
+                       // knowing only the wire.
+                       for (auto cell : module->cells())
+                               for (auto conn : cell->connections())
+                                       if (conn.second.is_wire() && elided_wires.count(conn.second.as_wire()))
+                                               cell_wire_defs[cell][conn.second.as_wire()] = conn.first;
+
                        dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_defs;
                        for (auto wire_def : flow.wire_defs)
                                for (auto node : wire_def.second)
        {
                bool has_sync_init, has_packed_mem;
                check_design(design, has_sync_init, has_packed_mem);
-               if (has_sync_init)
+               if (has_sync_init) {
+                       // We're only interested in proc_init, but it depends on proc_prune and proc_clean, so call those
+                       // in case they weren't already. (This allows `yosys foo.v -o foo.cc` to work.)
+                       Pass::call(design, "proc_prune");
+                       Pass::call(design, "proc_clean");
                        Pass::call(design, "proc_init");
+               }
                if (has_packed_mem)
                        Pass::call(design, "memory_unpack");
                // Recheck the design if it was modified.