From: whitequark Date: Fri, 3 Apr 2020 16:07:43 +0000 (+0000) Subject: write_cxxrtl: add support for hierarchical designs. X-Git-Tag: working-ls180~670^2~5 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=fb0270b75258fa58cbf0594873721c88964f69a5;p=yosys.git write_cxxrtl: add support for hierarchical designs. Hierarchical design simulations are generally much slower, but this comes with a major increase in flexibility: 1. Since the `flatten` pass currently does not support flattening of designs with processes, this is the only way to simulate such designs with cxxrtl. 2. Support for hierarchy paves way for simulation black boxes, which are necessary for e.g. replacing PHYs with C++ code that integrates with the host system. --- diff --git a/backends/cxxrtl/cxxrtl.cc b/backends/cxxrtl/cxxrtl.cc index 8a9e8348b..710462e96 100644 --- a/backends/cxxrtl/cxxrtl.cc +++ b/backends/cxxrtl/cxxrtl.cc @@ -1,7 +1,7 @@ /* * yosys -- Yosys Open SYnthesis Suite * - * Copyright (C) 2019 whitequark + * Copyright (C) 2019-2020 whitequark * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -20,6 +20,7 @@ #include "kernel/rtlil.h" #include "kernel/register.h" #include "kernel/sigtools.h" +#include "kernel/utils.h" #include "kernel/celltypes.h" #include "kernel/log.h" @@ -197,6 +198,11 @@ static bool is_ff_cell(RTLIL::IdString type) 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 { @@ -279,8 +285,13 @@ struct FlowGraph { /* 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); @@ -354,6 +365,7 @@ struct CxxrtlWorker { dict sync_types; pool writable_memories; dict> transparent_for; + dict> cell_wire_defs; dict elided_wires; dict> schedule; pool localized_wires; @@ -418,6 +430,12 @@ struct CxxrtlWorker { 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. @@ -434,6 +452,11 @@ struct CxxrtlWorker { 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); @@ -512,7 +535,11 @@ struct CxxrtlWorker { 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); @@ -652,7 +679,8 @@ struct CxxrtlWorker { 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 &cells) @@ -885,11 +913,31 @@ struct CxxrtlWorker { 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"; + } + } } } @@ -1115,9 +1163,21 @@ struct CxxrtlWorker { 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"; @@ -1152,8 +1212,8 @@ struct CxxrtlWorker { } dec_indent(); f << "}\n"; - f << "\n"; + f << "bool " << mangle(module) << "::commit() {\n"; inc_indent(); f << indent << "bool changed = false;\n"; @@ -1202,29 +1262,45 @@ struct CxxrtlWorker { 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 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 \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"; } @@ -1357,6 +1433,14 @@ struct CxxrtlWorker { 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, hash_ptr_ops> node_defs; for (auto wire_def : flow.wire_defs) for (auto node : wire_def.second) @@ -1451,8 +1535,13 @@ struct CxxrtlWorker { { 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.