Add fanoutbuf pass

This commit is contained in:
Akash Levy 2025-10-01 19:23:45 -07:00
parent 17e3ed3258
commit c8d8c4f408
3 changed files with 600 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,197 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* 2025 Akash Levy <akash@silimate.com>
*
* 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<std::string> 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<Wire *> ports;
vector<Cell *> 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<SigBit, pool<tuple<IdString,IdString,int>>> 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<IdString,IdString,int>(cell->name, conn.first, i));
}
}
}
// Queue up all cells
std::set<Cell*, IdString::compare_ptr_by_name<Cell>> 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<Wire *> 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<int> 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<IdString,IdString,int>(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

402
tests/silimate/fanoutbuf.ys Normal file
View File

@ -0,0 +1,402 @@
# Test 1
log -header "Simple input to output buffer > limit 4"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5
);
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign w4 = a;
assign w5 = a;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 1 t:$buf
design -reset
log -pop
# Test 2
log -header "Simple input to output buffer = limit 4, should do nothing"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
output wire w1,
output wire w2,
output wire w3,
output wire w4
);
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign w4 = a;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-none t:$buf
design -reset
log -pop
# Test 3
log -header "Simple input to output buffer = limit 4*4 + 1 = 17, should do two layers"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5,
output wire w6,
output wire w7,
output wire w8,
output wire w9,
output wire w10,
output wire w11,
output wire w12,
output wire w13,
output wire w14,
output wire w15,
output wire w16,
output wire w17
);
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign w4 = a;
assign w5 = a;
assign w6 = a;
assign w7 = a;
assign w8 = a;
assign w9 = a;
assign w10 = a;
assign w11 = a;
assign w12 = a;
assign w13 = a;
assign w14 = a;
assign w15 = a;
assign w16 = a;
assign w17 = a;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 5 t:$buf
design -reset
log -pop
# Test 4
log -header "Multi-bit signal fanout buffering"
log -push
design -reset
read_verilog <<EOF
module top (
input wire [3:0] a,
output wire [3:0] w1,
output wire [3:0] w2,
output wire [3:0] w3,
output wire [3:0] w4,
output wire [3:0] w5
);
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign w4 = a;
assign w5 = a;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf - should have 4 buffers (one per bit)
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 4 t:$buf
design -reset
log -pop
# Test 5
log -header "Custom fanout limit (limit 3)"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
output wire w1,
output wire w2,
output wire w3,
output wire w4
);
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign w4 = a;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf with limit 3 - should insert 1 buffer
equiv_opt -assert fanoutbuf -limit 3
design -load postopt
select -assert-count 1 t:$buf
design -reset
log -pop
# Test 6
log -header "Mixed fanout counts on different signals"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
input wire b,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5,
output wire x1,
output wire x2
);
assign w1 = a; // a has fanout 5 (> 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 <<EOF
module top (
input wire a,
input wire b,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5,
output wire w6
);
wire and_out;
assign and_out = a & b;
assign w1 = and_out;
assign w2 = and_out;
assign w3 = and_out;
assign w4 = and_out;
assign w5 = and_out;
assign w6 = and_out;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf - should insert buffers for and_out
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 1 t:$buf
design -reset
log -pop
# Test 8
log -header "Edge case: exactly limit+1 fanout"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5
);
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign w4 = a;
assign w5 = a;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf - should insert exactly 1 buffer
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 1 t:$buf
design -reset
log -pop
# Test 9
log -header "Complex expressions with intermediate signals"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
input wire b,
input wire c,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5,
output wire x1,
output wire x2,
output wire x3,
output wire x4,
output wire x5
);
wire intermediate1, intermediate2;
assign intermediate1 = a & b;
assign intermediate2 = intermediate1 | c;
assign w1 = intermediate1;
assign w2 = intermediate1;
assign w3 = intermediate1;
assign w4 = intermediate1;
assign w5 = intermediate1;
assign x1 = intermediate2;
assign x2 = intermediate2;
assign x3 = intermediate2;
assign x4 = intermediate2;
assign x5 = intermediate2;
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf - should insert buffers for both intermediates
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 2 t:$buf
design -reset
log -pop
# Test 10
log -header "Hierarchical design with fanout buffering"
log -push
design -reset
read_verilog <<EOF
module sub (
input wire in,
output wire out1,
output wire out2,
output wire out3,
output wire out4,
output wire out5
);
assign out1 = in;
assign out2 = in;
assign out3 = in;
assign out4 = in;
assign out5 = in;
endmodule
module top (
input wire a,
output wire w1,
output wire w2,
output wire w3,
output wire w4,
output wire w5
);
sub u1 (.in(a), .out1(w1), .out2(w2), .out3(w3), .out4(w4), .out5(w5));
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf - should insert buffers in sub module
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-count 1 t:$buf
design -reset
log -pop
# Test 11
log -header "Very high fanout requiring multiple buffer layers"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
output wire [31:0] w
);
assign w = {32{a}}; // 32-bit fanout from single input
endmodule
EOF
check -assert
# Check equivalence after fanoutbuf - should insert multiple layers of buffers
equiv_opt -assert fanoutbuf
design -load postopt
select -assert-min 8 t:$buf
design -reset
log -pop