3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-01-18 16:28:57 +00:00

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.
This commit is contained in:
Natalia 2026-01-13 22:41:13 -08:00
parent 71feb2a2a1
commit d25171806b
2 changed files with 97 additions and 34 deletions

View file

@ -55,6 +55,7 @@ struct WreduceWorker
ModIndex mi;
std::set<Cell*, IdString::compare_ptr_by_name<Cell>> work_queue_cells;
std::set<Wire*, IdString::compare_ptr_by_name<Wire>> work_queue_wires;
std::set<SigBit> work_queue_bits;
pool<SigBit> 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<SigSpec> 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<SigSpec> 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<SigSpec> 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();
}
}
};

View file

@ -0,0 +1,34 @@
# Ensure wreduce propagates width reductions across dependent cells.
read_verilog <<EOT
module top(input [7:0] a, input [7:0] b, input [3:0] c, output [3:0] y);
wire [8:0] sum_full;
wire [3:0] sum_trunc;
assign sum_full = a + b;
assign sum_trunc = sum_full[3:0];
assign y = sum_trunc + c;
endmodule
EOT
hierarchy -auto-top
proc
opt_expr
opt_clean
design -save gold
wreduce
opt_clean
# After wreduce, the first add should be reduced from 9 bits to 4 bits
select -assert-count 2 t:$add
select -assert-count 0 t:$add r:Y_WIDTH=9 %i
select -assert-count 2 t:$add r:Y_WIDTH=4 %i
design -stash reduced
design -import gold -as gold
design -import reduced -as reduced
miter -equiv -flatten -make_assert -make_outputs gold reduced miter
sat -verify -prove-asserts -show-ports miter