mirror of https://github.com/YosysHQ/yosys.git
Add fanoutbuf pass
This commit is contained in:
parent
17e3ed3258
commit
c8d8c4f408
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue