Skip to content

opt_merge: hashing performance and correctness #4677

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Mar 24, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 141 additions & 110 deletions passes/opt/opt_merge.cc
Original file line number Diff line number Diff line change
@@ -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,95 +83,78 @@ struct OptMergeWorker
}
}

std::string int_to_hash_string(unsigned int v)
{
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)
Hasher hash_cell_inputs(const RTLIL::Cell *cell, Hasher h) const
{
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_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<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_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);
}
else
continue;
h = assign_map(cell->getPort(ID::A)).hash_into(h);
} else {
std::vector<std::pair<IdString, SigSpec>> conns;
for (const auto& conn : cell->connections()) {
conns.push_back(conn);
}
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();
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);
}
}
hash_conn_strings.push_back(s + "\n");
}

for (auto &it : cell->parameters)
hash_conn_strings.push_back("P " + it.first.str() + "=" + it.second.as_string() + "\n");
if (RTLIL::builtin_ff_cell_types().count(cell->type))
h = initvals(cell->getPort(ID::Q)).hash_into(h);

std::sort(hash_conn_strings.begin(), hash_conn_strings.end());
}
return h;
}

for (auto it : hash_conn_strings)
hash_string += it;
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_into(params, h);
}

return std::hash<std::string>{}(hash_string);
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)
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)
@@ -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<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 (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 (mode_keepdc && has_dont_care_initval(it.second))
}
if (mode_keepdc && has_dont_care_initval(cell))
continue;
if (ct.cell_known(it.second->type) || (mode_share_all && it.second->known()))
cells.push_back(it.second);
if (ct.cell_known(cell->type) || (mode_share_all && cell->known()))
cells.push_back(cell);
}

did_something = false;
dict<uint64_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())
@@ -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);
}

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(), 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);
}
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this initvals dance but I see it was pre-existing

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this might be to avoid FfInitVals or SigMap getting notified with their monitor about the connection, but they don't use Monitors so it remains a mystery

}
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++;
}
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++;
}
}
}
Loading

Unchanged files with check annotations Beta

last_begin = first_end + 1;
last_end = slice.c_str() + slice.length();
}
first = parse_index(first_begin, first_end, slice);

Check warning on line 43 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-10)

‘value’ may be used uninitialized in this function [-Wmaybe-uninitialized]

Check warning on line 43 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-10)

‘value’ may be used uninitialized in this function [-Wmaybe-uninitialized]

Check warning on line 43 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-13)

‘value’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 43 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-13)

‘value’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 43 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-13)

‘value’ may be used uninitialized [-Wmaybe-uninitialized]
last = parse_index(last_begin, last_end, slice);

Check warning on line 44 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-10)

‘value’ may be used uninitialized in this function [-Wmaybe-uninitialized]

Check warning on line 44 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-10)

‘value’ may be used uninitialized in this function [-Wmaybe-uninitialized]

Check warning on line 44 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-13)

‘value’ may be used uninitialized [-Wmaybe-uninitialized]

Check warning on line 44 in passes/cmds/abstract.cc

GitHub Actions / test-compile (ubuntu-latest, gcc-13)

‘value’ may be used uninitialized [-Wmaybe-uninitialized]
}
static int parse_index(const char *begin, const char *end, const std::string &slice) {