diff --git a/kernel/unstable/patch.cc b/kernel/unstable/patch.cc index edc3081c5..35075bc39 100644 --- a/kernel/unstable/patch.cc +++ b/kernel/unstable/patch.cc @@ -97,6 +97,8 @@ void Patch::gc(Cell* old_cell, bool track) { auto dir = old_cell->port_dir(port_name); // Unknown port direction (e.g. user module instance whose interface // isn't registered): can't decide input vs output, so don't gc it. + // TODO: should be log_assert once PD_UNKNOWN is fixed at the source + // (see claude-notes.md). if (dir == PD_UNKNOWN) return; // TODO only running GC through whole connections? diff --git a/kernel/utils.h b/kernel/utils.h index e71fb4911..f6db48030 100644 --- a/kernel/utils.h +++ b/kernel/utils.h @@ -22,6 +22,7 @@ #include "kernel/yosys.h" #include +#include #ifndef UTILS_H #define UTILS_H @@ -125,6 +126,91 @@ public: }; +// --------------------------------------------------- +// BitGrouper — partition output bits by a per-bit key +// --------------------------------------------------- +// +// Many passes that split a multi-bit cell or word-level FF into smaller +// pieces share the same shape: +// +// 1. iterate bits of the output +// 2. for each bit, compute a key describing where it should go (or +// decide it stays where it is) +// 3. emit one piece per distinct key, covering exactly that group's bits +// 4. either consume the un-grouped "remaining" bits as a smaller original, +// or report that everything was grouped. +// +// BitGrouper handles (1)+(2)+(3)'s bookkeeping; callers do (3)'s +// per-group emission and (4)'s remainder handling. +// +// Construction is one-shot: pass a width and a `key_fn(int) -> optional`. +// `key_fn` returning std::nullopt leaves the bit in `remaining()`. +// +// Group iteration order matches first appearance, so emission is +// deterministic across runs. + +template +class BitGrouper +{ +public: + struct Group { + Key key; + std::vector indices; + + // True when `indices` is exactly [front, front+size). Callers can + // use this to pick the cheap contiguous slice op when available. + bool is_contiguous() const { + if (indices.empty()) + return true; + int start = indices.front(); + for (size_t i = 0; i < indices.size(); i++) + if (indices[i] != start + (int)i) + return false; + return true; + } + }; + + template + BitGrouper(int width, KeyFn key_fn) { + std::map idx; + for (int i = 0; i < width; i++) { + std::optional k = key_fn(i); + if (!k) { + remaining_.push_back(i); + continue; + } + auto it = idx.find(*k); + if (it == idx.end()) { + idx.emplace(*k, groups_.size()); + groups_.push_back({*k, {i}}); + } else { + groups_[it->second].indices.push_back(i); + } + } + } + + const std::vector &groups() const { return groups_; } + const std::vector &remaining() const { return remaining_; } + bool fully_grouped() const { return remaining_.empty(); } + bool nothing_grouped() const { return groups_.empty(); } + + // Pick the bits of `sig` at the group's indices. Uses contiguous-range + // slicing when possible. + static RTLIL::SigSpec extract(const RTLIL::SigSpec &sig, const Group &g) { + if (g.is_contiguous() && !g.indices.empty()) + return sig.extract(g.indices.front(), (int)g.indices.size()); + RTLIL::SigSpec out; + for (int i : g.indices) + out.append(sig[i]); + return out; + } + +private: + std::vector groups_; + std::vector remaining_; +}; + + // --------------------------------------------------- // Best-effort topological sorting with loop detection // --------------------------------------------------- diff --git a/passes/opt/opt_dff.cc b/passes/opt/opt_dff.cc index 94785c843..b2b7e3445 100644 --- a/passes/opt/opt_dff.cc +++ b/passes/opt/opt_dff.cc @@ -23,6 +23,7 @@ #include "kernel/rtlil.h" #include "kernel/qcsat.h" #include "kernel/modtools.h" +#include "kernel/utils.h" #include "kernel/sigtools.h" #include "kernel/ffinit.h" #include "kernel/ff.h" @@ -557,11 +558,9 @@ struct OptDffWorker bool try_merge_srst(FfDataSigMapped &ff, Cell *cell, bool &changed) { - std::map> groups; - std::vector remaining_indices; Const::Builder val_srst_builder(ff.width); - for (int i = 0; i < ff.width; i++) { + BitGrouper grouper(ff.width, [&](int i) -> std::optional { ctrls_t resets; State reset_val = ff.has_srst ? ff.val_srst[i] : State::Sx; @@ -593,29 +592,26 @@ struct OptDffWorker } } - if (!resets.empty()) { - if (ff.has_srst) - resets.insert(ctrl_t(ff.sig_srst, ff.pol_srst)); - - groups[resets].push_back(i); - } else { - remaining_indices.push_back(i); - } - val_srst_builder.push_back(reset_val); - } + + if (resets.empty()) + return std::nullopt; + if (ff.has_srst) + resets.insert(ctrl_t(ff.sig_srst, ff.pol_srst)); + return resets; + }); Const val_srst = val_srst_builder.build(); - for (auto &it : groups) { - FfDataSigMapped new_ff = ff.slice(it.second); + for (auto &g : grouper.groups()) { + FfDataSigMapped new_ff = ff.slice(g.indices); Const::Builder new_val_srst_builder(new_ff.width); for (int i = 0; i < new_ff.width; i++) - new_val_srst_builder.push_back(val_srst[it.second[i]]); + new_val_srst_builder.push_back(val_srst[g.indices[i]]); new_ff.val_srst = new_val_srst_builder.build(); - ctrl_t srst = combine_resets(it.first, ff.is_fine); + ctrl_t srst = combine_resets(g.key, ff.is_fine); new_ff.has_srst = true; new_ff.sig_srst = srst.first; new_ff.pol_srst = srst.second; @@ -631,13 +627,13 @@ struct OptDffWorker log_signal(new_ff.sig_d), log_signal(new_ff.sig_q), log_signal(new_ff.val_srst)); } - if (remaining_indices.empty()) { + if (grouper.fully_grouped()) { module->remove(cell); return true; } - if (GetSize(remaining_indices) != ff.width) { - ff = ff.slice(remaining_indices); + if ((int)grouper.remaining().size() != ff.width) { + ff = ff.slice(grouper.remaining()); ff.cell = cell; changed = true; } @@ -647,10 +643,8 @@ struct OptDffWorker bool try_merge_ce(FfDataSigMapped &ff, Cell *cell, bool &changed) { - std::map, std::vector> groups; - std::vector remaining_indices; - - for (int i = 0; i < ff.width; i++) { + using CeKey = std::pair; + BitGrouper grouper(ff.width, [&](int i) -> std::optional { ctrls_t enables; while (bit2mux.count(ff.sig_d[i]) && bitusers[ff.sig_d[i]] == 1) { @@ -677,19 +671,17 @@ struct OptDffWorker if (!opt.simple_dffe) patterns = find_muxtree_feedback_patterns(ff.sig_d[i], ff.sig_q[i], pattern_t()); - if (!patterns.empty() || !enables.empty()) { - if (ff.has_ce) - enables.insert(ctrl_t(ff.sig_ce, ff.pol_ce)); - simplify_patterns(patterns); - groups[std::make_pair(patterns, enables)].push_back(i); - } else { - remaining_indices.push_back(i); - } - } + if (patterns.empty() && enables.empty()) + return std::nullopt; + if (ff.has_ce) + enables.insert(ctrl_t(ff.sig_ce, ff.pol_ce)); + simplify_patterns(patterns); + return std::make_pair(patterns, enables); + }); - for (auto &it : groups) { - FfDataSigMapped new_ff = ff.slice(it.second); - ctrl_t en = make_patterns_logic(it.first.first, it.first.second, ff.is_fine); + for (auto &g : grouper.groups()) { + FfDataSigMapped new_ff = ff.slice(g.indices); + ctrl_t en = make_patterns_logic(g.key.first, g.key.second, ff.is_fine); new_ff.has_ce = true; new_ff.sig_ce = en.first; @@ -705,13 +697,13 @@ struct OptDffWorker log_signal(new_ff.sig_d), log_signal(new_ff.sig_q)); } - if (remaining_indices.empty()) { + if (grouper.fully_grouped()) { module->remove(cell); return true; } - if (GetSize(remaining_indices) != ff.width) { - ff = ff.slice(remaining_indices); + if ((int)grouper.remaining().size() != ff.width) { + ff = ff.slice(grouper.remaining()); ff.cell = cell; changed = true; } diff --git a/passes/opt/opt_expr.cc b/passes/opt/opt_expr.cc index 200fbf06e..30627a46e 100644 --- a/passes/opt/opt_expr.cc +++ b/passes/opt/opt_expr.cc @@ -170,10 +170,11 @@ bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutativ std::vector bits_a = sig_a, bits_b = sig_b, bits_y = sig_y; enum { GRP_DYN, GRP_CONST_A, GRP_CONST_B, GRP_CONST_AB, GRP_N }; - std::map, std::set> grouped_bits[GRP_N]; - - for (int i = 0; i < GetSize(bits_y); i++) - { + // Two-level partition: outer key is the rewrite kind (GRP_*), inner key + // is the (a, b) pair that selects this bit's slot inside the new cell. + // Two original bits sharing (kind, a, b) collapse to the same output bit. + using Key = std::tuple; + BitGrouper grouper(GetSize(bits_y), [&](int i) -> std::optional { int group_idx = GRP_DYN; RTLIL::SigBit bit_a = bits_a[i], bit_b = bits_b[i]; @@ -208,11 +209,16 @@ bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutativ group_idx = GRP_CONST_B; } - grouped_bits[group_idx][std::pair(bit_a, bit_b)].insert(bits_y[i]); - } + return std::make_tuple(group_idx, bit_a, bit_b); + }); - for (int i = 0; i < GRP_N; i++) - if (GetSize(grouped_bits[i]) == GetSize(bits_y)) + // If every original bit ended up with its own unique (a, b) slot within + // some single kind, splitting would just rename without shrinking. + int slots_per_kind[GRP_N] = {}; + for (auto &g : grouper.groups()) + slots_per_kind[std::get<0>(g.key)]++; + for (int kind = 0; kind < GRP_N; kind++) + if (slots_per_kind[kind] == GetSize(bits_y)) return false; log_debug("Replacing %s cell `%s' in module `%s' with cells using grouped bits:\n", @@ -223,23 +229,33 @@ bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutativ // Replacement bit for each original sig_y bit. dict bit_map; - for (int i = 0; i < GRP_N; i++) + // Pre-collect groups per kind, preserving BitGrouper's insertion order + // within each kind. + std::vector::Group*> per_kind[GRP_N]; + for (auto &g : grouper.groups()) + per_kind[std::get<0>(g.key)].push_back(&g); + + for (int kind = 0; kind < GRP_N; kind++) { - if (grouped_bits[i].empty()) + if (per_kind[kind].empty()) continue; - int group_size = GetSize(grouped_bits[i]); + int group_size = GetSize(per_kind[kind]); RTLIL::SigSpec new_y = patcher.addWire(NEW_ID, group_size); RTLIL::SigSpec new_a, new_b; - for (auto &it : grouped_bits[i]) { - for (auto &bit : it.second) - bit_map[bit] = new_y[new_a.size()]; - new_a.append(it.first.first); - new_b.append(it.first.second); + int slot = 0; + for (auto *g : per_kind[kind]) { + SigBit bit_a = std::get<1>(g->key); + SigBit bit_b = std::get<2>(g->key); + new_a.append(bit_a); + new_b.append(bit_b); + for (int i : g->indices) + bit_map[bits_y[i]] = new_y[slot]; + slot++; } - if (cell->type.in(ID($and), ID($or)) && i == GRP_CONST_A) { + if (cell->type.in(ID($and), ID($or)) && kind == GRP_CONST_A) { if (!keepdc) { if (cell->type == ID($and)) new_a.replace(dict{{State::Sx, State::S0}, {State::Sz, State::S0}}, &new_b); @@ -259,34 +275,34 @@ bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutativ continue; } - if (cell->type.in(ID($xor), ID($xnor)) && i == GRP_CONST_A) { + if (cell->type.in(ID($xor), ID($xnor)) && kind == GRP_CONST_A) { SigSpec undef_a, undef_y, undef_b; SigSpec def_y, def_a, def_b; - for (int i = 0; i < GetSize(new_y); i++) { - bool undef = new_a[i] == State::Sx || new_a[i] == State::Sz; - if (!keepdc && (undef || new_a[i] == new_b[i])) { - undef_a.append(new_a[i]); + for (int j = 0; j < GetSize(new_y); j++) { + bool undef = new_a[j] == State::Sx || new_a[j] == State::Sz; + if (!keepdc && (undef || new_a[j] == new_b[j])) { + undef_a.append(new_a[j]); if (cell->type == ID($xor)) undef_b.append(State::S0); // For consistency since simplemap does $xnor -> $_XOR_ + $_NOT_ else if (cell->type == ID($xnor)) undef_b.append(State::S1); else log_abort(); - undef_y.append(new_y[i]); + undef_y.append(new_y[j]); } - else if (new_a[i] == State::S0 || new_a[i] == State::S1) { - undef_a.append(new_a[i]); + else if (new_a[j] == State::S0 || new_a[j] == State::S1) { + undef_a.append(new_a[j]); if (cell->type == ID($xor)) - undef_b.append(new_a[i] == State::S1 ? patcher.Not(NEW_ID, new_b[i]).as_bit() : new_b[i]); + undef_b.append(new_a[j] == State::S1 ? patcher.Not(NEW_ID, new_b[j]).as_bit() : new_b[j]); else if (cell->type == ID($xnor)) - undef_b.append(new_a[i] == State::S1 ? new_b[i] : patcher.Not(NEW_ID, new_b[i]).as_bit()); + undef_b.append(new_a[j] == State::S1 ? new_b[j] : patcher.Not(NEW_ID, new_b[j]).as_bit()); else log_abort(); - undef_y.append(new_y[i]); + undef_y.append(new_y[j]); } else { - def_a.append(new_a[i]); - def_b.append(new_b[i]); - def_y.append(new_y[i]); + def_a.append(new_a[j]); + def_b.append(new_b[j]); + def_y.append(new_y[j]); } } if (!undef_y.empty()) {