From c8d8c4f408ceea3fbd422f1ccc77f297760c3274 Mon Sep 17 00:00:00 2001 From: Akash Levy Date: Wed, 1 Oct 2025 19:23:45 -0700 Subject: [PATCH] Add fanoutbuf pass --- passes/silimate/Makefile.inc | 1 + passes/silimate/fanoutbuf.cc | 197 +++++++++++++++++ tests/silimate/fanoutbuf.ys | 402 +++++++++++++++++++++++++++++++++++ 3 files changed, 600 insertions(+) create mode 100644 passes/silimate/fanoutbuf.cc create mode 100644 tests/silimate/fanoutbuf.ys diff --git a/passes/silimate/Makefile.inc b/passes/silimate/Makefile.inc index 4622893db..074ca16d2 100644 --- a/passes/silimate/Makefile.inc +++ b/passes/silimate/Makefile.inc @@ -5,6 +5,7 @@ OBJS += passes/silimate/annotate_logic_depth.o OBJS += passes/silimate/breakreduce.o OBJS += passes/silimate/breaksop.o OBJS += passes/silimate/bus_rebuild.o +OBJS += passes/silimate/fanoutbuf.o OBJS += passes/silimate/lut2bmux.o OBJS += passes/silimate/obs_clean.o OBJS += passes/silimate/opt_balance_tree.o diff --git a/passes/silimate/fanoutbuf.cc b/passes/silimate/fanoutbuf.cc new file mode 100644 index 000000000..0ca9ae7e3 --- /dev/null +++ b/passes/silimate/fanoutbuf.cc @@ -0,0 +1,197 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf + * 2025 Akash Levy + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "kernel/utils.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct FanoutbufPass : public Pass { + FanoutbufPass() : Pass("fanoutbuf", "insert $buf cells to limit fanout") { } + void help() override + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" fanoutbuf [options] [selection]\n"); + log("\n"); + log("This command inserts $buf cells to limit fanout while minimizing the number of"); + log("buffers inserted.\n"); + log("\n"); + log(" -limit n\n"); + log(" max fanout to allow (default: 4).\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) override + { + int limit = 4; + log_header(design, "Executing FANOUTBUF pass (inserting buffers to limit fanout).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + if (args[argidx] == "-limit" && argidx+1 < args.size()) { + limit = std::stoi(args[++argidx]); + continue; + } + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + { + // Add $pos cells on input ports and output ports + vector ports; + vector iobufs; + for (auto port : module->ports) { + auto wire = module->wire(port); + if (wire->port_input) { + auto new_in = module->addWire(module->uniquify(wire->name.str() + "_new"), wire); + auto iobuf = module->addPos(module->uniquify(wire->name.str() + "_in"), new_in, wire); + iobufs.push_back(iobuf); + module->swap_names(wire, new_in); + wire->port_input = false; + } + if (wire->port_output) { + auto new_out = module->addWire(module->uniquify(wire->name.str() + "_new"), wire); + auto iobuf = module->addPos(module->uniquify(wire->name.str() + "_out"), wire, new_out); + iobufs.push_back(iobuf); + module->swap_names(wire, new_out); + wire->port_output = false; + } + } + module->fixup_ports(); + + // Data structures for splitting fanout + SigMap sigmap(module); + dict>> bit_users_db; + int bufcount = 0; + + // Build bit_users_db + log_debug("Building bit_users_db...\n"); + for (auto cell : module->cells()) { + for (auto conn : cell->connections()) { + if (!cell->input(conn.first)) continue; + for (int i = 0; i < GetSize(conn.second); i++) { + SigBit bit(sigmap(conn.second[i])); + bit_users_db[bit].insert(tuple(cell->name, conn.first, i)); + } + } + } + + // Queue up all cells + std::set> work_queue_cells; + for (auto cell : module->selected_cells()) + work_queue_cells.insert(cell); + + // Split fanout with BFS + while (!work_queue_cells.empty()) { + auto cell = *work_queue_cells.begin(); + work_queue_cells.erase(work_queue_cells.begin()); + for (auto conn : cell->connections()) { + if (!cell->output(conn.first)) continue; + for (auto bit : sigmap(conn.second)) { + // Collect targets + auto targets = bit_users_db[bit]; + log("Cell, targets: %s, %d\n", log_id(cell), GetSize(targets)); + bit_users_db[bit].clear(); + + // If there are less than limit targets, no need to split + if (GetSize(targets) <= limit) + continue; + + // Set up buffers + int nbufs = std::min(limit, GetSize(targets) / limit); + vector bufouts(nbufs, nullptr); + for (int i = 0; i < nbufs; i++) { + // New buffer name should be unique and short + IdString buf_name; + if (cell->type == ID::$_BUF_) + buf_name = NEW_ID2; + else + buf_name = NEW_ID2_SUFFIX("fbuf"); + // Create buffer, connect input to bit and output to new wire + Wire *bufout = module->addWire(buf_name.str() + "_out"); + Cell *buf = module->addBuf(buf_name, bit, bufout, false, cell->get_src_attribute()); + sigmap.add(bufout); + work_queue_cells.insert(buf); + bufouts[i] = bufout; + bufcount++; + } + + // Target limit is initialized to limit + // Gets multiplied by limit each time the current set of buffers is saturated + int target_limit = limit; + int target_i = 0; + int cur_buf_i = 0; + vector current_fanout(nbufs, 0); + for (auto target : targets) { + // If there are less buffers than the limit, keep a few targets on the original cell + bool skip_target = false; + if (target_i < limit - nbufs) { + target_i++; + skip_target = true; + } + if (skip_target) + continue; + // Search for a buffer with fanout less than target_limit + while (current_fanout[cur_buf_i] >= target_limit) { + cur_buf_i++; + // If we've reached the end of the buffers, reset to the start + // and increase target limit + if (cur_buf_i >= nbufs) { + cur_buf_i = 0; + target_limit *= limit; + } + } + // Replace bit in target cell with buffered output + auto target_cell = module->cell(std::get<0>(target)); + auto target_port = std::get<1>(target); + auto target_bit_idx = std::get<2>(target); + auto new_cell_in = target_cell->getPort(target_port); + new_cell_in[target_bit_idx] = bufouts[cur_buf_i]; + target_cell->setPort(target_port, new_cell_in); + bit_users_db[bufouts[cur_buf_i]].insert( + tuple(target_cell->name, target_port, target_bit_idx) + ); + sigmap.add(bufouts[cur_buf_i], new_cell_in[target_bit_idx]); + // Increment fanout for current buffer + current_fanout[cur_buf_i]++; + } + } + } + } + + // Remove $pos cells + for (auto cell : iobufs) { + if (cell->type == ID::$pos) { + module->connect(cell->getPort(ID::Y), cell->getPort(ID::A)); + module->remove(cell); + } + } + + // Log number of buffers inserted + log("Inserted %d buffers in module '%s'.\n", bufcount, log_id(module)); + } + } +} FanoutbufPass; + +PRIVATE_NAMESPACE_END diff --git a/tests/silimate/fanoutbuf.ys b/tests/silimate/fanoutbuf.ys new file mode 100644 index 000000000..a3df4e848 --- /dev/null +++ b/tests/silimate/fanoutbuf.ys @@ -0,0 +1,402 @@ +# Test 1 +log -header "Simple input to output buffer > limit 4" +log -push +design -reset +read_verilog < limit 4) + assign w2 = a; + assign w3 = a; + assign w4 = a; + assign w5 = a; + assign x1 = b; // b has fanout 2 (< limit 4) + assign x2 = b; +endmodule +EOF +check -assert + +# Check equivalence after fanoutbuf - should insert 1 buffer for signal a only +equiv_opt -assert fanoutbuf +design -load postopt +select -assert-count 1 t:$buf + +design -reset +log -pop + + + +# Test 7 +log -header "Logic gates with high fanout" +log -push +design -reset +read_verilog <