From d25171806b1ec8ca272c022f6b0d15d5f4a5e577 Mon Sep 17 00:00:00 2001 From: Natalia Date: Tue, 13 Jan 2026 22:41:13 -0800 Subject: [PATCH] wreduce: queue traversal and add regression test Implement iterative queue-based traversal in wreduce pass to propagate width reductions across dependent cells and wires. Previously, wreduce would process all cells once, then all wires once. This meant that reductions couldn't propagate through chains of operations. The new algorithm maintains work queues for both cells and wires, processing them iteratively until no more reductions are possible. When a cell or wire is reduced, dependent cells and wires are added back to the queues for reprocessing. Add regression test to verify that width reductions propagate through a chain of operations: (a + b)[3:0] + c, ensuring the first addition is reduced from 9 bits to 4 bits. --- passes/opt/wreduce.cc | 97 ++++++++++++++++++++++------------ tests/opt/wreduce_traversal.ys | 34 ++++++++++++ 2 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 tests/opt/wreduce_traversal.ys diff --git a/passes/opt/wreduce.cc b/passes/opt/wreduce.cc index 359c76d42..3b059e0e5 100644 --- a/passes/opt/wreduce.cc +++ b/passes/opt/wreduce.cc @@ -55,6 +55,7 @@ struct WreduceWorker ModIndex mi; std::set> work_queue_cells; + std::set> work_queue_wires; std::set work_queue_bits; pool keep_bits; FfInitVals initvals; @@ -78,6 +79,8 @@ struct WreduceWorker for (int i = GetSize(sig_y)-1; i >= 0; i--) { auto info = mi.query(sig_y[i]); + if (info == nullptr) + return; if (!info->is_output && GetSize(info->ports) <= 1 && !keep_bits.count(mi.sigmap(sig_y[i]))) { bits_removed.push_back(State::Sx); continue; @@ -393,6 +396,8 @@ struct WreduceWorker break; auto info = mi.query(bit); + if (info == nullptr) + return; if (info->is_output || GetSize(info->ports) > 1) break; @@ -457,63 +462,87 @@ struct WreduceWorker return count; } + void run_wire(Wire *w, pool complete_wires) + { + int unused_top_bits = 0; + + if (w->port_id > 0 || count_nontrivial_wire_attrs(w) > 0) + return; + + for (int i = GetSize(w)-1; i >= 0; i--) { + SigBit bit(w, i); + auto info = mi.query(bit); + if (info == nullptr) + return; + if (info && (info->is_input || info->is_output || GetSize(info->ports) > 0)) + break; + unused_top_bits++; + } + + if (unused_top_bits == 0 || unused_top_bits == GetSize(w)) + return; + + if (complete_wires[mi.sigmap(w).extract(0, GetSize(w) - unused_top_bits)]) + return; + + log("Removed top %d bits (of %d) from wire %s.%s.\n", unused_top_bits, GetSize(w), log_id(module), log_id(w)); + Wire *nw = module->addWire(module->uniquify(IdString(w->name.str() + "_wreduce")), GetSize(w) - unused_top_bits); + module->connect(nw, SigSpec(w).extract(0, GetSize(nw))); + module->swap_names(w, nw); + } + void run() { - // create a copy as mi.sigmap will be updated as we process the module + // Create a copy as mi.sigmap will be updated as we process the module SigMap init_attr_sigmap = mi.sigmap; initvals.set(&init_attr_sigmap, module); + // Initialize cell work queue + for (auto c : module->selected_cells()) + work_queue_cells.insert(c); + + // Initialize wire work queue + for (auto w : module->selected_wires()) + work_queue_wires.insert(w); + + // Initialize keep bits for (auto w : module->wires()) { if (w->get_bool_attribute(ID::keep)) for (auto bit : mi.sigmap(w)) keep_bits.insert(bit); } - for (auto c : module->selected_cells()) - work_queue_cells.insert(c); - - while (!work_queue_cells.empty()) + while (!work_queue_cells.empty() || !work_queue_wires.empty()) { + // Initialize complete wires + pool complete_wires; + for (auto w : module->wires()) + complete_wires.insert(mi.sigmap(w)); + + // Run cells work_queue_bits.clear(); for (auto c : work_queue_cells) run_cell(c); + // Run wires + for (auto w : work_queue_wires) + run_wire(w, complete_wires); + + // Get next batch of cells to process work_queue_cells.clear(); for (auto bit : work_queue_bits) for (auto port : mi.query_ports(bit)) if (module->selected(port.cell)) work_queue_cells.insert(port.cell); - } - pool complete_wires; - for (auto w : module->wires()) - complete_wires.insert(mi.sigmap(w)); + // Get next batch of wires to process + work_queue_wires.clear(); + for (auto bit : work_queue_bits) + if (bit.wire != NULL && module->selected(bit.wire)) + work_queue_wires.insert(bit.wire); - for (auto w : module->selected_wires()) - { - int unused_top_bits = 0; - - if (w->port_id > 0 || count_nontrivial_wire_attrs(w) > 0) - continue; - - for (int i = GetSize(w)-1; i >= 0; i--) { - SigBit bit(w, i); - auto info = mi.query(bit); - if (info && (info->is_input || info->is_output || GetSize(info->ports) > 0)) - break; - unused_top_bits++; - } - - if (unused_top_bits == 0 || unused_top_bits == GetSize(w)) - continue; - - if (complete_wires[mi.sigmap(w).extract(0, GetSize(w) - unused_top_bits)]) - continue; - - log("Removed top %d bits (of %d) from wire %s.%s.\n", unused_top_bits, GetSize(w), log_id(module), log_id(w)); - Wire *nw = module->addWire(NEW_ID, GetSize(w) - unused_top_bits); - module->connect(nw, SigSpec(w).extract(0, GetSize(nw))); - module->swap_names(w, nw); + // Reload module + mi.reload_module(); } } }; diff --git a/tests/opt/wreduce_traversal.ys b/tests/opt/wreduce_traversal.ys new file mode 100644 index 000000000..ab4bd2b44 --- /dev/null +++ b/tests/opt/wreduce_traversal.ys @@ -0,0 +1,34 @@ +# Ensure wreduce propagates width reductions across dependent cells. +read_verilog <