From cbb776c62690ddf6fea4e79f48cdc60c11eb3c22 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Fri, 18 Oct 2024 16:25:15 +0200 Subject: [PATCH 1/7] opt_merge: avoid hashing strings --- passes/opt/opt_merge.cc | 173 +++++++++++++++++++++------------------- 1 file changed, 90 insertions(+), 83 deletions(-) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index eb3aa462e..d6ed74433 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -26,6 +26,8 @@ #include <stdlib.h> #include <stdio.h> #include <set> +#include <unordered_map> +#include <array> USING_YOSYS_NAMESPACE @@ -42,6 +44,22 @@ struct OptMergeWorker CellTypes ct; int total_count; + static vector<pair<SigBit, SigSpec>> sorted_pmux_in(const dict<RTLIL::IdString, RTLIL::SigSpec> &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<pair<SigBit, SigSpec>> sb_pairs; + for (int i = 0; i < s_width; i++) + sb_pairs.push_back(pair<SigBit, SigSpec>(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<RTLIL::IdString, RTLIL::SigSpec> &conn) { SigSpec sig_s = conn.at(ID::S); @@ -65,90 +83,74 @@ struct OptMergeWorker } } - std::string int_to_hash_string(unsigned int v) + Hasher hash_cell_inputs(const RTLIL::Cell *cell, Hasher h) { - 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<string> hash_conn_strings; - std::string hash_string = cell->type.str() + "\n"; - - const dict<RTLIL::IdString, RTLIL::SigSpec> *conn = &cell->connections(); - dict<RTLIL::IdString, RTLIL::SigSpec> 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<RTLIL::SigSpec, 2> inputs = { + assign_map(cell->getPort(ID::A)), + assign_map(cell->getPort(ID::B)) + }; + std::sort(inputs.begin(), inputs.end()); + h = hash_ops<std::array<RTLIL::SigSpec, 2>>::hash_acc(inputs, h); + h = assign_map(cell->getPort(ID::Y)).hash_acc(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_acc(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_acc(h); + } else if (cell->type == ID($pmux)) { + dict<RTLIL::IdString, RTLIL::SigSpec> 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_acc(h); + h = b_chunk.hash_acc(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_acc(h); + } else { + std::vector<std::pair<IdString, SigSpec>> 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_acc(h); + h = assign_map(sig).hash_acc(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_acc(h); + } + return h; + } - for (auto &it : cell->parameters) - hash_conn_strings.push_back("P " + it.first.str() + "=" + it.second.as_string() + "\n"); + static Hasher hash_cell_parameters(const RTLIL::Cell *cell, Hasher h) + { + using Paramvec = std::vector<std::pair<IdString, Const>>; + Paramvec params; + for (const auto& param : cell->parameters) { + params.push_back(param); + } + std::sort(params.begin(), params.end()); + return hash_ops<Paramvec>::hash_acc(params, h); + } - std::sort(hash_conn_strings.begin(), hash_conn_strings.end()); - - for (auto it : hash_conn_strings) - hash_string += it; - - return std::hash<std::string>{}(hash_string); + Hasher hash_cell_function(const RTLIL::Cell *cell, Hasher h) + { + 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) @@ -255,18 +257,23 @@ struct OptMergeWorker while (did_something) { std::vector<RTLIL::Cell*> 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<uint64_t, RTLIL::Cell*> sharemap; + dict<Hasher::hash_t, RTLIL::Cell*> sharemap; for (auto cell : cells) { if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known()) @@ -275,7 +282,7 @@ struct OptMergeWorker if (cell->type == ID($scopeinfo)) continue; - uint64_t hash = hash_cell_parameters_and_connections(cell); + Hasher::hash_t hash = hash_cell_function(cell, Hasher()).yield(); auto r = sharemap.insert(std::make_pair(hash, cell)); if (!r.second) { if (compare_cell_parameters_and_connections(cell, r.first->second)) { From ffc057a89c76f922c4bd5c13383551fc6f9233a3 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Fri, 18 Oct 2024 20:39:52 +0200 Subject: [PATCH 2/7] opt_merge: fix the many collisions case --- passes/opt/opt_merge.cc | 45 ++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index d6ed74433..025a6c977 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -254,6 +254,11 @@ struct OptMergeWorker initvals.set(&assign_map, module); bool did_something = true; + bool warned_collisions = false; + // 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. + int threshold = 1000; // adjust to taste while (did_something) { std::vector<RTLIL::Cell*> cells; @@ -273,7 +278,10 @@ struct OptMergeWorker } did_something = false; - dict<Hasher::hash_t, RTLIL::Cell*> sharemap; + // INVARIANT: All cells associated with the same hash in sharemap + // are distinct as far as compare_cell_parameters_and_connections + // can tell. + std::unordered_multimap<Hasher::hash_t, RTLIL::Cell*> sharemap; for (auto cell : cells) { if ((!mode_share_all && !ct.cell_known(cell->type)) || !cell->known()) @@ -283,21 +291,30 @@ struct OptMergeWorker continue; Hasher::hash_t hash = hash_cell_function(cell, Hasher()).yield(); - auto r = sharemap.insert(std::make_pair(hash, cell)); - if (!r.second) { - if (compare_cell_parameters_and_connections(cell, r.first->second)) { + // Get all cells with matching hashes + auto matching = sharemap.equal_range(hash); + int collisions = 0; + bool found = false; + for (auto it = matching.first; it != matching.second && !found; ++it) { + if (collisions > threshold && !warned_collisions) { + log_warning("High hash collision counts. opt_merge runtime may be excessive.\n" \ + "Please report to maintainers.\n"); + warned_collisions = true; + } + auto other_cell = it->second; + if (compare_cell_parameters_and_connections(cell, other_cell)) { + found = true; if (cell->has_keep_attr()) { - if (r.first->second->has_keep_attr()) + if (other_cell->has_keep_attr()) continue; - std::swap(r.first->second, cell); + std::swap(other_cell, cell); } - did_something = true; - log_debug(" Cell `%s' is identical to cell `%s'.\n", cell->name.c_str(), r.first->second->name.c_str()); + 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 = r.first->second->getPort(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); @@ -311,8 +328,18 @@ struct OptMergeWorker 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++; + break; + } else { + collisions++; + log_debug(" False hash match: `%s' and `%s'.\n", cell->name.c_str(), other_cell->name.c_str()); } } + if (!did_something) { + // This cell isn't represented yet in the sharemap. + // Either it's the first of its hash, + // or falsely matches all cells in sharemap sharing its hash. + sharemap.insert(std::make_pair(hash, cell)); + } } } From 890374014714532726ceab36d7b4eedb78e119eb Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Tue, 12 Nov 2024 14:59:09 +0100 Subject: [PATCH 3/7] opt_merge: switch to unordered_set --- passes/opt/opt_merge.cc | 126 +++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 65 deletions(-) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index 025a6c977..1992aa871 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -83,7 +83,7 @@ struct OptMergeWorker } } - Hasher hash_cell_inputs(const RTLIL::Cell *cell, Hasher h) + Hasher hash_cell_inputs(const RTLIL::Cell *cell, Hasher h) const { // TODO: when implemented, use celltypes to match: // (builtin || stdcell) && (unary || binary) && symmetrical @@ -94,26 +94,26 @@ struct OptMergeWorker assign_map(cell->getPort(ID::B)) }; std::sort(inputs.begin(), inputs.end()); - h = hash_ops<std::array<RTLIL::SigSpec, 2>>::hash_acc(inputs, h); - h = assign_map(cell->getPort(ID::Y)).hash_acc(h); + h = hash_ops<std::array<RTLIL::SigSpec, 2>>::hash_into(inputs, h); + h = assign_map(cell->getPort(ID::Y)).hash_into(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_acc(h); + 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_acc(h); + h = a.hash_into(h); } else if (cell->type == ID($pmux)) { dict<RTLIL::IdString, RTLIL::SigSpec> 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_acc(h); - h = b_chunk.hash_acc(h); + h = s_bit.hash_into(h); + h = b_chunk.hash_into(h); } - h = assign_map(cell->getPort(ID::A)).hash_acc(h); + h = assign_map(cell->getPort(ID::A)).hash_into(h); } else { std::vector<std::pair<IdString, SigSpec>> conns; for (const auto& conn : cell->connections()) { @@ -122,13 +122,13 @@ struct OptMergeWorker std::sort(conns.begin(), conns.end()); for (const auto& [port, sig] : conns) { if (!cell->output(port)) { - h = port.hash_acc(h); - h = assign_map(sig).hash_acc(h); + h = port.hash_into(h); + h = assign_map(sig).hash_into(h); } } if (RTLIL::builtin_ff_cell_types().count(cell->type)) - h = initvals(cell->getPort(ID::Q)).hash_acc(h); + h = initvals(cell->getPort(ID::Q)).hash_into(h); } return h; @@ -142,10 +142,10 @@ struct OptMergeWorker params.push_back(param); } std::sort(params.begin(), params.end()); - return hash_ops<Paramvec>::hash_acc(params, h); + return hash_ops<Paramvec>::hash_into(params, h); } - Hasher hash_cell_function(const RTLIL::Cell *cell, Hasher h) + Hasher hash_cell_function(const RTLIL::Cell *cell, Hasher h) const { h.eat(cell->type); h = hash_cell_inputs(cell, h); @@ -153,7 +153,7 @@ struct OptMergeWorker return h; } - bool compare_cell_parameters_and_connections(const RTLIL::Cell *cell1, const RTLIL::Cell *cell2) + bool compare_cell_parameters_and_connections(const RTLIL::Cell *cell1, const RTLIL::Cell *cell2) const { log_assert(cell1 != cell2); if (cell1->type != cell2->type) return false; @@ -254,11 +254,9 @@ struct OptMergeWorker initvals.set(&assign_map, module); bool did_something = true; - bool warned_collisions = false; // 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. - int threshold = 1000; // adjust to taste while (did_something) { std::vector<RTLIL::Cell*> cells; @@ -278,10 +276,29 @@ struct OptMergeWorker } did_something = false; - // INVARIANT: All cells associated with the same hash in sharemap - // are distinct as far as compare_cell_parameters_and_connections - // can tell. - std::unordered_multimap<Hasher::hash_t, RTLIL::Cell*> 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()) @@ -290,55 +307,34 @@ struct OptMergeWorker if (cell->type == ID($scopeinfo)) continue; - Hasher::hash_t hash = hash_cell_function(cell, Hasher()).yield(); - // Get all cells with matching hashes - auto matching = sharemap.equal_range(hash); - int collisions = 0; - bool found = false; - for (auto it = matching.first; it != matching.second && !found; ++it) { - if (collisions > threshold && !warned_collisions) { - log_warning("High hash collision counts. opt_merge runtime may be excessive.\n" \ - "Please report to maintainers.\n"); - warned_collisions = true; + 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; + std::swap(other_cell, cell); } - auto other_cell = it->second; - if (compare_cell_parameters_and_connections(cell, other_cell)) { - found = true; - if (cell->has_keep_attr()) { - if (other_cell->has_keep_attr()) - continue; - 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); - } + 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++; - break; - } else { - collisions++; - log_debug(" False hash match: `%s' and `%s'.\n", cell->name.c_str(), other_cell->name.c_str()); } - } - if (!did_something) { - // This cell isn't represented yet in the sharemap. - // Either it's the first of its hash, - // or falsely matches all cells in sharemap sharing its hash. - sharemap.insert(std::make_pair(hash, cell)); + 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++; } } } From 176faae7c94b0dea13052b17d4f210cb19b2d7ba Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Mon, 10 Mar 2025 11:43:13 +0100 Subject: [PATCH 4/7] opt_merge: fix trivial binary regression --- passes/opt/opt_merge.cc | 1 - tests/opt/opt_merge_basic.ys | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 tests/opt/opt_merge_basic.ys diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index 1992aa871..745b27e87 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -95,7 +95,6 @@ struct OptMergeWorker }; std::sort(inputs.begin(), inputs.end()); h = hash_ops<std::array<RTLIL::SigSpec, 2>>::hash_into(inputs, h); - h = assign_map(cell->getPort(ID::Y)).hash_into(h); } else if (cell->type.in(ID($reduce_xor), ID($reduce_xnor))) { SigSpec a = assign_map(cell->getPort(ID::A)); a.sort(); diff --git a/tests/opt/opt_merge_basic.ys b/tests/opt/opt_merge_basic.ys new file mode 100644 index 000000000..e6336f648 --- /dev/null +++ b/tests/opt/opt_merge_basic.ys @@ -0,0 +1,13 @@ +read_verilog -icells <<EOT +module top(A, B, X, Y); +input [8:0] A, B; +output [8:0] X, Y; +assign X = A + B; +assign Y = A + B; +endmodule +EOT + +select -assert-count 2 t:$add +equiv_opt -assert opt_merge +design -load postopt +select -assert-count 1 t:$add From ae7a97cc2d8bb2e92c96b08f83de4fc57e12fe74 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Mon, 10 Mar 2025 12:30:00 +0100 Subject: [PATCH 5/7] opt_merge: test some unary cells --- tests/opt/opt_merge_basic.ys | 69 +++++++++++++++++++++++++++++++++--- 1 file changed, 65 insertions(+), 4 deletions(-) diff --git a/tests/opt/opt_merge_basic.ys b/tests/opt/opt_merge_basic.ys index e6336f648..082fdb0b8 100644 --- a/tests/opt/opt_merge_basic.ys +++ b/tests/opt/opt_merge_basic.ys @@ -1,13 +1,74 @@ -read_verilog -icells <<EOT +read_verilog <<EOT module top(A, B, X, Y); -input [8:0] A, B; -output [8:0] X, Y; +input [7:0] A, B; +output [7:0] X, Y; assign X = A + B; assign Y = A + B; endmodule EOT - +# Most basic case +# Binary select -assert-count 2 t:$add equiv_opt -assert opt_merge design -load postopt select -assert-count 1 t:$add + +design -reset +read_verilog <<EOT +module top(A, B, C, X, Y); +input [7:0] A, B, C; +output [7:0] X, Y; +assign X = A + B; +assign Y = A + C; +endmodule +EOT +# Reject on a different input +select -assert-count 2 t:$add +opt_merge +select -assert-count 2 t:$add + +design -reset +read_verilog <<EOT +module top(A, X, Y); +input [7:0] A; +output X, Y; +assign X = ^A; +assign Y = ^A; +endmodule +EOT +# Unary +select -assert-count 2 t:$reduce_xor +dump +opt_merge +select -assert-count 1 t:$reduce_xor + +design -reset +read_verilog -icells <<EOT +module top(A, B, X, Y); +input [7:0] A; +input [7:0] B; +output X, Y; + \$reduce_or #( + .A_SIGNED(32'd0), + .A_WIDTH(32'd16), + .Y_WIDTH(32'd1), + ) one ( + .A({A, B}), // <- look here + .Y(X) + ); + \$reduce_or #( + .A_SIGNED(32'd0), + .A_WIDTH(32'd16), + .Y_WIDTH(32'd1), + ) other ( + .A({B, A}), // <- look here + .Y(Y) + ); +endmodule +EOT +# Unary +opt_expr +select -assert-count 2 t:$reduce_or +equiv_opt -assert opt_merge +design -load postopt +select -assert-count 1 t:$reduce_or From 33bfc9d19cc34ee25df127eab1d2cdad057a1dad Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Mon, 10 Mar 2025 12:56:21 +0100 Subject: [PATCH 6/7] opt_merge: test more kinds of cells --- tests/opt/opt_merge_basic.ys | 104 +++++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/tests/opt/opt_merge_basic.ys b/tests/opt/opt_merge_basic.ys index 082fdb0b8..9de320aaa 100644 --- a/tests/opt/opt_merge_basic.ys +++ b/tests/opt/opt_merge_basic.ys @@ -19,7 +19,7 @@ module top(A, B, C, X, Y); input [7:0] A, B, C; output [7:0] X, Y; assign X = A + B; -assign Y = A + C; +assign Y = A + C; // <- look here endmodule EOT # Reject on a different input @@ -45,10 +45,9 @@ select -assert-count 1 t:$reduce_xor design -reset read_verilog -icells <<EOT module top(A, B, X, Y); -input [7:0] A; -input [7:0] B; +input [7:0] A, B; output X, Y; - \$reduce_or #( + \$reduce_xor #( .A_SIGNED(32'd0), .A_WIDTH(32'd16), .Y_WIDTH(32'd1), @@ -56,7 +55,7 @@ output X, Y; .A({A, B}), // <- look here .Y(X) ); - \$reduce_or #( + \$reduce_xor #( .A_SIGNED(32'd0), .A_WIDTH(32'd16), .Y_WIDTH(32'd1), @@ -66,9 +65,102 @@ output X, Y; ); endmodule EOT -# Unary +# Unary is sorted +opt_expr +select -assert-count 2 t:$reduce_xor +equiv_opt -assert opt_merge +design -load postopt +select -assert-count 1 t:$reduce_xor + +design -reset +read_verilog -icells <<EOT +module top(A, B, X, Y); +input [7:0] A, B; +output X, Y; + \$reduce_or #( + .A_SIGNED(32'd0), + .A_WIDTH(32'd24), + .Y_WIDTH(32'd1), + ) one ( + .A({A, B, B}), // <- look here + .Y(X) + ); + \$reduce_or #( + .A_SIGNED(32'd0), + .A_WIDTH(32'd24), + .Y_WIDTH(32'd1), + ) other ( + .A({A, A, B}), // <- look here + .Y(Y) + ); +endmodule +EOT +# Unary is unified when valid opt_expr select -assert-count 2 t:$reduce_or equiv_opt -assert opt_merge design -load postopt select -assert-count 1 t:$reduce_or + +design -reset +read_verilog -icells <<EOT +module top(A, B, X, Y); +input [7:0] A, B; +output X, Y; + \$reduce_xor #( + .A_SIGNED(32'd0), + .A_WIDTH(32'd24), + .Y_WIDTH(32'd1), + ) one ( + .A({A, B, B}), // <- look here + .Y(X) + ); + \$reduce_xor #( + .A_SIGNED(32'd0), + .A_WIDTH(32'd24), + .Y_WIDTH(32'd1), + ) other ( + .A({A, A, B}), // <- look here + .Y(Y) + ); +endmodule +EOT +# Unary isn't unified when that would be invalid +opt_expr +select -assert-count 2 t:$reduce_xor +equiv_opt -assert opt_merge +design -load postopt +select -assert-count 2 t:$reduce_xor + +# TODO pmux + +design -reset +read_verilog <<EOT +module top(A, B, X, Y); +input [7:0] A, B; +output X, Y; +assign X = A > 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 <<EOT +module top(A, B, C, X, Y); +input [7:0] A, B, C; +output X, Y; +assign X = A > 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 From 1d773b50a4bc44cd9b431387adf053d1998a4590 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" <emil@tywoniak.eu> Date: Mon, 10 Mar 2025 13:02:49 +0100 Subject: [PATCH 7/7] opt_merge: fix dangling pointers in known_cells when keep attribute is used --- passes/opt/opt_merge.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/passes/opt/opt_merge.cc b/passes/opt/opt_merge.cc index 745b27e87..b519d33d6 100644 --- a/passes/opt/opt_merge.cc +++ b/passes/opt/opt_merge.cc @@ -154,7 +154,7 @@ struct OptMergeWorker bool compare_cell_parameters_and_connections(const RTLIL::Cell *cell1, const RTLIL::Cell *cell2) const { - log_assert(cell1 != cell2); + if (cell1 == cell2) return true; if (cell1->type != cell2->type) return false; if (cell1->parameters != cell2->parameters) @@ -313,6 +313,8 @@ struct OptMergeWorker 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); }