diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index eb3aa462e..b519d33d6 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -26,6 +26,8 @@ #include #include #include +#include +#include USING_YOSYS_NAMESPACE @@ -42,6 +44,22 @@ struct OptMergeWorker CellTypes ct; int total_count; + static vector> sorted_pmux_in(const dict &conn) + { + SigSpec sig_s = conn.at(ID::S); + SigSpec sig_b = conn.at(ID::B); + + int s_width = GetSize(sig_s); + int width = GetSize(sig_b) / s_width; + + vector> sb_pairs; + for (int i = 0; i < s_width; i++) + sb_pairs.push_back(pair(sig_s[i], sig_b.extract(i*width, width))); + + std::sort(sb_pairs.begin(), sb_pairs.end()); + return sb_pairs; + } + static void sort_pmux_conn(dict &conn) { SigSpec sig_s = conn.at(ID::S); @@ -65,95 +83,78 @@ struct OptMergeWorker } } - std::string int_to_hash_string(unsigned int v) + Hasher hash_cell_inputs(const RTLIL::Cell *cell, Hasher h) const { - if (v == 0) - return "0"; - std::string str = ""; - while (v > 0) { - str += 'a' + (v & 15); - v = v >> 4; - } - return str; - } - - uint64_t hash_cell_parameters_and_connections(const RTLIL::Cell *cell) - { - vector hash_conn_strings; - std::string hash_string = cell->type.str() + "\n"; - - const dict *conn = &cell->connections(); - dict alt_conn; - + // TODO: when implemented, use celltypes to match: + // (builtin || stdcell) && (unary || binary) && symmetrical if (cell->type.in(ID($and), ID($or), ID($xor), ID($xnor), ID($add), ID($mul), ID($logic_and), ID($logic_or), ID($_AND_), ID($_OR_), ID($_XOR_))) { - alt_conn = *conn; - if (assign_map(alt_conn.at(ID::A)) < assign_map(alt_conn.at(ID::B))) { - alt_conn[ID::A] = conn->at(ID::B); - alt_conn[ID::B] = conn->at(ID::A); + std::array inputs = { + assign_map(cell->getPort(ID::A)), + assign_map(cell->getPort(ID::B)) + }; + std::sort(inputs.begin(), inputs.end()); + h = hash_ops>::hash_into(inputs, h); + } else if (cell->type.in(ID($reduce_xor), ID($reduce_xnor))) { + SigSpec a = assign_map(cell->getPort(ID::A)); + a.sort(); + h = a.hash_into(h); + } else if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool))) { + SigSpec a = assign_map(cell->getPort(ID::A)); + a.sort_and_unify(); + h = a.hash_into(h); + } else if (cell->type == ID($pmux)) { + dict conn = cell->connections(); + assign_map.apply(conn.at(ID::A)); + assign_map.apply(conn.at(ID::B)); + assign_map.apply(conn.at(ID::S)); + for (const auto& [s_bit, b_chunk] : sorted_pmux_in(conn)) { + h = s_bit.hash_into(h); + h = b_chunk.hash_into(h); } - conn = &alt_conn; - } else - if (cell->type.in(ID($reduce_xor), ID($reduce_xnor))) { - alt_conn = *conn; - assign_map.apply(alt_conn.at(ID::A)); - alt_conn.at(ID::A).sort(); - conn = &alt_conn; - } else - if (cell->type.in(ID($reduce_and), ID($reduce_or), ID($reduce_bool))) { - alt_conn = *conn; - assign_map.apply(alt_conn.at(ID::A)); - alt_conn.at(ID::A).sort_and_unify(); - conn = &alt_conn; - } else - if (cell->type == ID($pmux)) { - alt_conn = *conn; - assign_map.apply(alt_conn.at(ID::A)); - assign_map.apply(alt_conn.at(ID::B)); - assign_map.apply(alt_conn.at(ID::S)); - sort_pmux_conn(alt_conn); - conn = &alt_conn; - } - - for (auto &it : *conn) { - RTLIL::SigSpec sig; - if (cell->output(it.first)) { - if (it.first == ID::Q && RTLIL::builtin_ff_cell_types().count(cell->type)) { - // For the 'Q' output of state elements, - // use its (* init *) attribute value - sig = initvals(it.second); + h = assign_map(cell->getPort(ID::A)).hash_into(h); + } else { + std::vector> conns; + for (const auto& conn : cell->connections()) { + conns.push_back(conn); + } + std::sort(conns.begin(), conns.end()); + for (const auto& [port, sig] : conns) { + if (!cell->output(port)) { + h = port.hash_into(h); + h = assign_map(sig).hash_into(h); } - else - continue; } - else - sig = assign_map(it.second); - string s = "C " + it.first.str() + "="; - for (auto &chunk : sig.chunks()) { - if (chunk.wire) - s += "{" + chunk.wire->name.str() + " " + - int_to_hash_string(chunk.offset) + " " + - int_to_hash_string(chunk.width) + "}"; - else - s += RTLIL::Const(chunk.data).as_string(); - } - hash_conn_strings.push_back(s + "\n"); + + if (RTLIL::builtin_ff_cell_types().count(cell->type)) + h = initvals(cell->getPort(ID::Q)).hash_into(h); + } - - for (auto &it : cell->parameters) - hash_conn_strings.push_back("P " + it.first.str() + "=" + it.second.as_string() + "\n"); - - std::sort(hash_conn_strings.begin(), hash_conn_strings.end()); - - for (auto it : hash_conn_strings) - hash_string += it; - - return std::hash{}(hash_string); + return h; } - bool compare_cell_parameters_and_connections(const RTLIL::Cell *cell1, const RTLIL::Cell *cell2) + static Hasher hash_cell_parameters(const RTLIL::Cell *cell, Hasher h) { - log_assert(cell1 != cell2); + using Paramvec = std::vector>; + Paramvec params; + for (const auto& param : cell->parameters) { + params.push_back(param); + } + std::sort(params.begin(), params.end()); + return hash_ops::hash_into(params, h); + } + + Hasher hash_cell_function(const RTLIL::Cell *cell, Hasher h) const + { + h.eat(cell->type); + h = hash_cell_inputs(cell, h); + h = hash_cell_parameters(cell, h); + return h; + } + + bool compare_cell_parameters_and_connections(const RTLIL::Cell *cell1, const RTLIL::Cell *cell2) const + { + if (cell1 == cell2) return true; if (cell1->type != cell2->type) return false; if (cell1->parameters != cell2->parameters) @@ -252,21 +253,51 @@ struct OptMergeWorker initvals.set(&assign_map, module); bool did_something = true; + // A cell may have to go through a lot of collisions if the hash + // function is performing poorly, but it's a symptom of something bad + // beyond the user's control. while (did_something) { std::vector cells; - cells.reserve(module->cells_.size()); - for (auto &it : module->cells_) { - if (!design->selected(module, it.second)) + cells.reserve(module->cells().size()); + for (auto cell : module->cells()) { + if (!design->selected(module, cell)) continue; - if (mode_keepdc && has_dont_care_initval(it.second)) + if (cell->type.in(ID($meminit), ID($meminit_v2), ID($mem), ID($mem_v2))) { + // Ignore those for performance: meminit can have an excessively large port, + // mem can have an excessively large parameter holding the init data continue; - if (ct.cell_known(it.second->type) || (mode_share_all && it.second->known())) - cells.push_back(it.second); + } + if (mode_keepdc && has_dont_care_initval(cell)) + continue; + if (ct.cell_known(cell->type) || (mode_share_all && cell->known())) + cells.push_back(cell); } did_something = false; - dict sharemap; + + // We keep a set of known cells. They're hashed with our hash_cell_function + // and compared with our compare_cell_parameters_and_connections. + // Both need to capture OptMergeWorker to access initvals + struct CellPtrHash { + const OptMergeWorker& worker; + CellPtrHash(const OptMergeWorker& w) : worker(w) {} + std::size_t operator()(const Cell* c) const { + return (std::size_t)worker.hash_cell_function(c, Hasher()).yield(); + } + }; + struct CellPtrEqual { + const OptMergeWorker& worker; + CellPtrEqual(const OptMergeWorker& w) : worker(w) {} + bool operator()(const Cell* lhs, const Cell* rhs) const { + return worker.compare_cell_parameters_and_connections(lhs, rhs); + } + }; + std::unordered_set< + RTLIL::Cell*, + CellPtrHash, + CellPtrEqual> known_cells (0, CellPtrHash(*this), CellPtrEqual(*this)); + for (auto cell : cells) { if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known()) @@ -275,36 +306,36 @@ struct OptMergeWorker if (cell->type == ID($scopeinfo)) continue; - uint64_t hash = hash_cell_parameters_and_connections(cell); - auto r = sharemap.insert(std::make_pair(hash, cell)); - if (!r.second) { - if (compare_cell_parameters_and_connections(cell, r.first->second)) { - if (cell->has_keep_attr()) { - if (r.first->second->has_keep_attr()) - continue; - std::swap(r.first->second, cell); - } - - - did_something = true; - log_debug(" Cell `%s' is identical to cell `%s'.\n", cell->name.c_str(), r.first->second->name.c_str()); - for (auto &it : cell->connections()) { - if (cell->output(it.first)) { - RTLIL::SigSpec other_sig = r.first->second->getPort(it.first); - log_debug(" Redirecting output %s: %s = %s\n", it.first.c_str(), - log_signal(it.second), log_signal(other_sig)); - Const init = initvals(other_sig); - initvals.remove_init(it.second); - initvals.remove_init(other_sig); - module->connect(RTLIL::SigSig(it.second, other_sig)); - assign_map.add(it.second, other_sig); - initvals.set_init(other_sig, init); - } - } - log_debug(" Removing %s cell `%s' from module `%s'.\n", cell->type.c_str(), cell->name.c_str(), module->name.c_str()); - module->remove(cell); - total_count++; + auto [cell_in_map, inserted] = known_cells.insert(cell); + if (!inserted) { + // We've failed to insert since we already have an equivalent cell + Cell* other_cell = *cell_in_map; + if (cell->has_keep_attr()) { + if (other_cell->has_keep_attr()) + continue; + known_cells.erase(other_cell); + known_cells.insert(cell); + std::swap(other_cell, cell); } + + did_something = true; + log_debug(" Cell `%s' is identical to cell `%s'.\n", cell->name.c_str(), other_cell->name.c_str()); + for (auto &it : cell->connections()) { + if (cell->output(it.first)) { + RTLIL::SigSpec other_sig = other_cell->getPort(it.first); + log_debug(" Redirecting output %s: %s = %s\n", it.first.c_str(), + log_signal(it.second), log_signal(other_sig)); + Const init = initvals(other_sig); + initvals.remove_init(it.second); + initvals.remove_init(other_sig); + module->connect(RTLIL::SigSig(it.second, other_sig)); + assign_map.add(it.second, other_sig); + initvals.set_init(other_sig, init); + } + } + log_debug(" Removing %s cell `%s' from module `%s'.\n", cell->type.c_str(), cell->name.c_str(), module->name.c_str()); + module->remove(cell); + total_count++; } } } diff --git a/tests/opt/opt_merge_basic.ys b/tests/opt/opt_merge_basic.ys new file mode 100644 index 000000000..9de320aaa --- /dev/null +++ b/tests/opt/opt_merge_basic.ys @@ -0,0 +1,166 @@ +read_verilog < B; +assign Y = A > B; +endmodule +EOT +# Exercise the general case in hash_cell_inputs - accept +opt_expr +select -assert-count 2 t:$gt +equiv_opt -assert opt_merge +design -load postopt +select -assert-count 1 t:$gt + +design -reset +read_verilog < B; +assign Y = A > C; // <- look here +endmodule +EOT +# Exercise the general case in hash_cell_inputs - reject +opt_expr +select -assert-count 2 t:$gt +opt_merge +select -assert-count 2 t:$gt