opt_clean: refactor

This commit is contained in:
Emil J. Tywoniak 2026-02-27 14:17:58 +01:00
parent 818aad7542
commit d2d97433b6
5 changed files with 80 additions and 73 deletions

View File

@ -53,10 +53,10 @@ ShardedVector<std::pair<SigBit, State>> build_inits(AnalysisContext& actx) {
return results; return results;
} }
dict<SigBit, State> qbits_from_inits(ShardedVector<std::pair<SigBit, State>>& inits, AnalysisContext& actx) { dict<SigBit, State> qbits_from_inits(ShardedVector<std::pair<SigBit, State>>& inits, SigMap& assign_map) {
dict<SigBit, State> qbits; dict<SigBit, State> qbits;
for (std::pair<SigBit, State> &p : inits) { for (std::pair<SigBit, State> &p : inits) {
actx.assign_map.add(p.first); assign_map.add(p.first);
qbits[p.first] = p.second; qbits[p.first] = p.second;
} }
return qbits; return qbits;
@ -126,7 +126,7 @@ bool rmunused_module_init(RTLIL::Module *module, ParallelDispatchThreadPool::Sub
AnalysisContext actx(module, subpool); AnalysisContext actx(module, subpool);
ShardedVector<std::pair<SigBit, State>> inits = build_inits(actx); ShardedVector<std::pair<SigBit, State>> inits = build_inits(actx);
dict<SigBit, State> qbits = qbits_from_inits(inits, actx); dict<SigBit, State> qbits = qbits_from_inits(inits, actx.assign_map);
ShardedVector<RTLIL::Wire*> inits_to_transfer = deferred_init_transfer(qbits, actx); ShardedVector<RTLIL::Wire*> inits_to_transfer = deferred_init_transfer(qbits, actx);
bool did_something = remove_redundant_inits(inits_to_transfer, verbose); bool did_something = remove_redundant_inits(inits_to_transfer, verbose);

View File

@ -28,12 +28,12 @@
YOSYS_NAMESPACE_BEGIN YOSYS_NAMESPACE_BEGIN
struct keep_cache_t struct KeepCache
{ {
dict<Module*, bool> keep_modules; dict<Module*, bool> keep_modules;
bool purge_mode; bool purge_mode;
keep_cache_t(bool purge_mode, ParallelDispatchThreadPool &thread_pool, const std::vector<RTLIL::Module *> &selected_modules) KeepCache(bool purge_mode, ParallelDispatchThreadPool &thread_pool, const std::vector<RTLIL::Module *> &selected_modules)
: purge_mode(purge_mode) { : purge_mode(purge_mode) {
std::vector<RTLIL::Module *> scan_modules_worklist; std::vector<RTLIL::Module *> scan_modules_worklist;

View File

@ -84,7 +84,7 @@ struct OptCleanPass : public Pass {
for (RTLIL::Module *m : selected_modules) for (RTLIL::Module *m : selected_modules)
thread_pool_size = std::max(thread_pool_size, ThreadPool::work_pool_size(0, m->cells_size(), 1000)); thread_pool_size = std::max(thread_pool_size, ThreadPool::work_pool_size(0, m->cells_size(), 1000));
ParallelDispatchThreadPool thread_pool(thread_pool_size); ParallelDispatchThreadPool thread_pool(thread_pool_size);
keep_cache_t keep_cache(purge_mode, thread_pool, selected_modules); KeepCache keep_cache(purge_mode, thread_pool, selected_modules);
{ {
CleanRunContext clean_ctx(design, {purge_mode, true}); CleanRunContext clean_ctx(design, {purge_mode, true});

View File

@ -54,7 +54,7 @@ struct CleanRunContext {
RmStats stats; RmStats stats;
ParallelDispatchThreadPool thread_pool; ParallelDispatchThreadPool thread_pool;
std::vector<RTLIL::Module*> selected_modules; std::vector<RTLIL::Module*> selected_modules;
keep_cache_t keep_cache; KeepCache keep_cache;
Flags flags; Flags flags;
private: private:

View File

@ -50,24 +50,24 @@ struct ShardedSigSpecEquality {
}; };
using ShardedSigSpecPool = ShardedHashtable<ShardedSigSpec, ShardedSigSpecEquality>; using ShardedSigSpecPool = ShardedHashtable<ShardedSigSpec, ShardedSigSpecEquality>;
struct DirectWires { struct ExactCellWires {
const ShardedSigSpecPool &direct_sigs; const ShardedSigSpecPool &exact_cells;
const SigMap &assign_map; const SigMap &assign_map;
dict<RTLIL::Wire *, bool> cache; dict<RTLIL::Wire *, bool> cache;
DirectWires(const ShardedSigSpecPool &direct_sigs, const SigMap &assign_map) : direct_sigs(direct_sigs), assign_map(assign_map) {} ExactCellWires(const ShardedSigSpecPool &exact_cells, const SigMap &assign_map) : exact_cells(exact_cells), assign_map(assign_map) {}
void cache_result_for_bit(const SigBit &bit) { void cache_result_for_bit(const SigBit &bit) {
if (bit.wire != nullptr) if (bit.wire != nullptr)
(void)is_direct(bit.wire); (void)is_exactly_cell_driven(bit.wire);
} }
bool is_direct(RTLIL::Wire *wire) { bool is_exactly_cell_driven(RTLIL::Wire *wire) {
if (wire->port_input) if (wire->port_input)
return true; return true;
auto it = cache.find(wire); auto it = cache.find(wire);
if (it != cache.end()) if (it != cache.end())
return it->second; return it->second;
SigSpec direct_sig = assign_map(wire); SigSpec sig = assign_map(wire);
bool direct = direct_sigs.find({direct_sig, direct_sig.hash_into(Hasher()).yield()}) != nullptr; bool direct = exact_cells.find({sig, sig.hash_into(Hasher()).yield()}) != nullptr;
cache.insert({wire, direct}); cache.insert({wire, direct});
return direct; return direct;
} }
@ -91,7 +91,7 @@ int count_nontrivial_wire_attrs(RTLIL::Wire *w)
} }
// Should we pick `s2` over `s1` to represent a signal? // Should we pick `s2` over `s1` to represent a signal?
bool compare_signals(const RTLIL::SigBit &s1, const RTLIL::SigBit &s2, const ShardedSigPool &regs, const ShardedSigPool &conns, DirectWires &direct_wires) bool compare_signals(const RTLIL::SigBit &s1, const RTLIL::SigBit &s2, const ShardedSigPool &regs, const ShardedSigPool &conns, ExactCellWires &cell_wires)
{ {
if (s1 == s2) if (s1 == s2)
return false; return false;
@ -115,10 +115,10 @@ bool compare_signals(const RTLIL::SigBit &s1, const RTLIL::SigBit &s2, const Sha
bool regs2 = regs.find(s2_val) != nullptr; bool regs2 = regs.find(s2_val) != nullptr;
if (regs1 != regs2) if (regs1 != regs2)
return regs2; return regs2;
bool w1_direct = direct_wires.is_direct(w1); bool w1_exact = cell_wires.is_exactly_cell_driven(w1);
bool w2_direct = direct_wires.is_direct(w2); bool w2_exact = cell_wires.is_exactly_cell_driven(w2);
if (w1_direct != w2_direct) if (w1_exact != w2_exact)
return w2_direct; return w2_exact;
bool conns1 = conns.find(s1_val) != nullptr; bool conns1 = conns.find(s1_val) != nullptr;
bool conns2 = conns.find(s2_val) != nullptr; bool conns2 = conns.find(s2_val) != nullptr;
if (conns1 != conns2) if (conns1 != conns2)
@ -180,7 +180,7 @@ struct UpdateConnection {
RTLIL::IdString port; RTLIL::IdString port;
RTLIL::SigSpec spec; RTLIL::SigSpec spec;
}; };
void fixup_update_ports(ShardedVector<UpdateConnection> &update_connections) void fixup_cell_ports(ShardedVector<UpdateConnection> &update_connections)
{ {
for (UpdateConnection &update : update_connections) for (UpdateConnection &update : update_connections)
update.cell->connections_.at(update.port) = std::move(update.spec); update.cell->connections_.at(update.port) = std::move(update.spec);
@ -229,18 +229,23 @@ static InitBits consume_inits(ShardedVector<RTLIL::Wire*> &initialized_wires, co
* What kinds of things are signals connected to? * What kinds of things are signals connected to?
* Helps pick representatives out of groups of connected signals */ * Helps pick representatives out of groups of connected signals */
struct SigConnKinds { struct SigConnKinds {
// Wire bits driven by registers (with clk2fflogic exception) // Wire bits directly driven by registers (with clk2fflogic exception)
ShardedSigPool registers; ShardedSigPool raw_registers;
// Wire bits connected to any cell port // Wire bits directly connected to any cell port
ShardedSigPool cells; ShardedSigPool raw_cell_connected;
// construct a pool of wires which are directly driven by a known celltype,
// this will influence our choice of representatives // Signals exactly driven by a known cell output,
ShardedSigSpecPool direct; // this will influence only our choice of representatives.
// A signal is exactly driven by a cell output iff all its bits are driven by this output
// and all bits of this output drive a bit of this signal.
// Additionally, all signals that sigmap to this signal are exactly driven by the port, too
ShardedSigSpecPool exact_cells;
SigConnKinds(bool purge_mode, const AnalysisContext& actx, CleanRunContext& clean_ctx) { SigConnKinds(bool purge_mode, const AnalysisContext& actx, CleanRunContext& clean_ctx) {
ShardedSigPool::Builder register_signals_builder(actx.subpool); ShardedSigPool::Builder raw_register_builder(actx.subpool);
ShardedSigPool::Builder connected_signals_builder(actx.subpool); ShardedSigPool::Builder raw_cell_connected_builder(actx.subpool);
ShardedSigSpecPool::Builder direct_sigs_builder(actx.subpool); ShardedSigSpecPool::Builder exact_cell_output_builder(actx.subpool);
actx.subpool.run([&direct_sigs_builder, &register_signals_builder, &connected_signals_builder, purge_mode, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) { actx.subpool.run([&exact_cell_output_builder, &raw_register_builder, &raw_cell_connected_builder, purge_mode, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) {
for (int i : ctx.item_range(actx.mod->cells_size())) { for (int i : ctx.item_range(actx.mod->cells_size())) {
RTLIL::Cell *cell = actx.mod->cell_at(i); RTLIL::Cell *cell = actx.mod->cell_at(i);
@ -251,65 +256,65 @@ struct SigConnKinds {
bool clk2fflogic = cell->get_bool_attribute(ID::clk2fflogic); bool clk2fflogic = cell->get_bool_attribute(ID::clk2fflogic);
for (auto &[port, sig] : cell->connections()) for (auto &[port, sig] : cell->connections())
if (clk2fflogic ? port == ID::D : clean_ctx.ct_reg.cell_output(cell->type, port)) if (clk2fflogic ? port == ID::D : clean_ctx.ct_reg.cell_output(cell->type, port))
add_spec(register_signals_builder, ctx, sig); add_spec(raw_register_builder, ctx, sig);
} }
// TODO optimize for direct wire connections?
for (auto &[_, sig] : cell->connections()) for (auto &[_, sig] : cell->connections())
add_spec(connected_signals_builder, ctx, sig); add_spec(raw_cell_connected_builder, ctx, sig);
} }
if (clean_ctx.ct_all.cell_known(cell->type)) if (clean_ctx.ct_all.cell_known(cell->type))
for (auto &[port, sig] : cell->connections()) for (auto &[port, sig] : cell->connections())
if (clean_ctx.ct_all.cell_output(cell->type, port)) { if (clean_ctx.ct_all.cell_output(cell->type, port)) {
RTLIL::SigSpec spec = actx.assign_map(sig); RTLIL::SigSpec spec = actx.assign_map(sig);
unsigned int hash = spec.hash_into(Hasher()).yield(); unsigned int hash = spec.hash_into(Hasher()).yield();
direct_sigs_builder.insert(ctx, {std::move(spec), hash}); exact_cell_output_builder.insert(ctx, {std::move(spec), hash});
} }
} }
}); });
actx.subpool.run([&register_signals_builder, &connected_signals_builder, &direct_sigs_builder](const ParallelDispatchThreadPool::RunCtx &ctx) { actx.subpool.run([&raw_register_builder, &raw_cell_connected_builder, &exact_cell_output_builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
register_signals_builder.process(ctx); raw_register_builder.process(ctx);
connected_signals_builder.process(ctx); raw_cell_connected_builder.process(ctx);
direct_sigs_builder.process(ctx); exact_cell_output_builder.process(ctx);
}); });
registers = register_signals_builder; raw_registers = raw_register_builder;
cells = connected_signals_builder; raw_cell_connected = raw_cell_connected_builder;
direct = direct_sigs_builder; exact_cells = exact_cell_output_builder;
} }
void clear(const ParallelDispatchThreadPool::RunCtx &ctx) { void clear(const ParallelDispatchThreadPool::RunCtx &ctx) {
registers.clear(ctx); raw_registers.clear(ctx);
cells.clear(ctx); raw_cell_connected.clear(ctx);
direct.clear(ctx); exact_cells.clear(ctx);
} }
}; };
ShardedVector<RTLIL::SigBit> build_candidates(DirectWires& direct_wires, const SigConnKinds& sig_analysis, const AnalysisContext& actx) { ShardedVector<RTLIL::SigBit> build_candidates(ExactCellWires& cell_wires, const SigConnKinds& sig_analysis, const AnalysisContext& actx) {
ShardedVector<RTLIL::SigBit> sigmap_canonical_candidates(actx.subpool); ShardedVector<RTLIL::SigBit> candidates(actx.subpool);
actx.subpool.run([&actx, &sig_analysis, &sigmap_canonical_candidates, &direct_wires](const ParallelDispatchThreadPool::RunCtx &ctx) { actx.subpool.run([&actx, &sig_analysis, &candidates, &cell_wires](const ParallelDispatchThreadPool::RunCtx &ctx) {
std::optional<DirectWires> local_direct_wires; std::optional<ExactCellWires> local_cell_wires;
DirectWires *this_thread_direct_wires = &direct_wires; ExactCellWires *this_thread_cell_wires = &cell_wires;
if (ctx.thread_num > 0) { if (ctx.thread_num > 0) {
// Rebuild a thread-local direct_wires from scratch local_cell_wires.emplace(sig_analysis.exact_cells, actx.assign_map);
// but from the same inputs this_thread_cell_wires = &local_cell_wires.value();
local_direct_wires.emplace(sig_analysis.direct, actx.assign_map);
this_thread_direct_wires = &local_direct_wires.value();
} }
for (int i : ctx.item_range(actx.mod->wires_size())) { for (int i : ctx.item_range(actx.mod->wires_size())) {
RTLIL::Wire *wire = actx.mod->wire_at(i); RTLIL::Wire *wire = actx.mod->wire_at(i);
for (int j = 0; j < wire->width; ++j) { for (int j = 0; j < wire->width; ++j) {
RTLIL::SigBit s1(wire, j); RTLIL::SigBit s1(wire, j);
RTLIL::SigBit s2 = actx.assign_map(s1); RTLIL::SigBit s2 = actx.assign_map(s1);
if (compare_signals(s2, s1, sig_analysis.registers, sig_analysis.cells, *this_thread_direct_wires)) if (compare_signals(s2, s1, sig_analysis.raw_registers, sig_analysis.raw_cell_connected, *this_thread_cell_wires))
sigmap_canonical_candidates.insert(ctx, s1); candidates.insert(ctx, s1);
} }
} }
}); });
return sigmap_canonical_candidates; return candidates;
} }
void update_assign_map(ShardedVector<RTLIL::SigBit>& sigmap_canonical_candidates, DirectWires& direct_wires, const SigConnKinds& sig_analysis, SigMap& assign_map) { void update_assign_map(SigMap& assign_map, ShardedVector<RTLIL::SigBit>& sigmap_canonical_candidates, ExactCellWires& cell_wires, const SigConnKinds& sig_analysis) {
for (RTLIL::SigBit candidate : sigmap_canonical_candidates) { for (RTLIL::SigBit candidate : sigmap_canonical_candidates) {
RTLIL::SigBit current_canonical = assign_map(candidate); RTLIL::SigBit current_canonical = assign_map(candidate);
if (compare_signals(current_canonical, candidate, sig_analysis.registers, sig_analysis.cells, direct_wires)) // Resolves if two threads in build_candidates found different candidates
// for the same set
// TODO adds effort for single-threaded?
if (compare_signals(current_canonical, candidate, sig_analysis.raw_registers, sig_analysis.raw_cell_connected, cell_wires))
assign_map.add(candidate); assign_map.add(candidate);
} }
} }
@ -340,7 +345,7 @@ struct UsedSignals {
} }
}; };
std::tuple<DeferredUpdates, UsedSignals> analyse_connectivity(SigConnKinds& sig_analysis, const AnalysisContext& actx, CleanRunContext &clean_ctx) { DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analysis, const AnalysisContext& actx, CleanRunContext &clean_ctx) {
DeferredUpdates deferred(actx.subpool); DeferredUpdates deferred(actx.subpool);
ShardedSigPool::Builder conn_builder(actx.subpool); ShardedSigPool::Builder conn_builder(actx.subpool);
ShardedSigPool::Builder raw_conn_builder(actx.subpool); ShardedSigPool::Builder raw_conn_builder(actx.subpool);
@ -390,8 +395,8 @@ std::tuple<DeferredUpdates, UsedSignals> analyse_connectivity(SigConnKinds& sig_
raw_conn_builder.process(ctx); raw_conn_builder.process(ctx);
used_builder.process(ctx); used_builder.process(ctx);
}); });
UsedSignals used {conn_builder, raw_conn_builder, used_builder}; used = {conn_builder, raw_conn_builder, used_builder};
return {std::move(deferred), std::move(used)}; return deferred;
} }
struct WireDeleter { struct WireDeleter {
@ -536,24 +541,26 @@ bool rmunused_module_signals(RTLIL::Module *module, ParallelDispatchThreadPool::
AnalysisContext actx(module, subpool); AnalysisContext actx(module, subpool);
SigConnKinds conn_kinds(clean_ctx.flags.purge, actx, clean_ctx); SigConnKinds conn_kinds(clean_ctx.flags.purge, actx, clean_ctx);
// Main thread's cached direct wires are retained and used later: ExactCellWires cell_wires(conn_kinds.exact_cells, actx.assign_map);
DirectWires direct_wires(conn_kinds.direct, actx.assign_map); // Collect sigmap representative candidates as built in parallel
// Other threads' caches get discarded when threads finish in build_candidates // With parallel runs, this creates redundant candidates that have to resolve in update_assign_map
// but the per-thread results are collected into sigmap_canonical_candidates ShardedVector<RTLIL::SigBit> new_sigmap_rep_candidates = build_candidates(cell_wires, conn_kinds, actx);
ShardedVector<RTLIL::SigBit> sigmap_canonical_candidates = build_candidates(direct_wires, conn_kinds, actx);
// Cache all the direct_wires results that we might possible need. This avoids the results // Cache all the cell_wires results that we might possible need. This avoids the results
// changing when we update `assign_map` below. // changing when we update `assign_map` below.
direct_wires.cache_all(sigmap_canonical_candidates); cell_wires.cache_all(new_sigmap_rep_candidates);
// Modify assign_map to reflect the connectivity we want, not the one we have // Modify assign_map to reflect the connectivity we want, not the one we have
update_assign_map(sigmap_canonical_candidates, direct_wires, conn_kinds, actx.assign_map); // this changes representative selection in assign_map
update_assign_map(actx.assign_map, new_sigmap_rep_candidates, cell_wires, conn_kinds);
// Remove all wire-wire connections // Remove all wire-wire connections
module->connections_.clear(); module->connections_.clear();
// UsedSigConnKinds used_sig_analysis(sig_analysis, actx); UsedSignals used;
auto [deferred, used] = analyse_connectivity(conn_kinds, actx, clean_ctx); DeferredUpdates deferred = analyse_connectivity(used, conn_kinds, actx, clean_ctx);
fixup_update_ports(deferred.update_connections); fixup_cell_ports(deferred.update_connections);
// Rip up and re-apply init attributes onto representative wires with x-bits
// in place of unset init bits
consume_inits(deferred.initialized_wires, actx.assign_map).apply_normalised_inits(); consume_inits(deferred.initialized_wires, actx.assign_map).apply_normalised_inits();
WireDeleter deleter(used, clean_ctx.flags.purge, actx); WireDeleter deleter(used, clean_ctx.flags.purge, actx);