This commit is contained in:
AdvaySingh1 2026-04-10 12:54:43 -07:00
parent 485cc675e5
commit 54bcc49987
3 changed files with 521 additions and 0 deletions

View File

@ -11,6 +11,7 @@ OBJS += passes/silimate/obs_clean.o
OBJS += passes/silimate/segv.o
OBJS += passes/silimate/reg_rename.o
OBJS += passes/silimate/infer_ce.o
OBJS += passes/silimate/report_fanout.o
OBJS += passes/silimate/splitfanout.o
OBJS += passes/silimate/splitlarge.o
OBJS += passes/silimate/splitnetlist.o

View File

@ -0,0 +1,268 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2025 Silimate Inc.
*
* 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"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
// Worker that counts fanout for each driver (input ports and cell output ports)
// and reports any that exceed a user-specified limit.
struct ReportFanoutWorker
{
Module *module;
SigMap sigmap;
// Maps each driven SigBit to its fanout count
dict<SigBit, int> bit_fanout;
// Maps each driven SigBit back to its driver:
// - For input ports: driver_port[bit] = (wire_name, IdString())
// - For cell output ports: driver_port[bit] = (cell_name, port_name)
dict<SigBit, pair<IdString, IdString>> bit_driver;
// SigBits that are clock or reset signals (driven into CLK/RST/ARST ports)
pool<SigBit> clk_rst_bits;
ReportFanoutWorker(Module *module, bool skip_clk_rst) : module(module), sigmap(module)
{
// Record drivers from input ports
// Every bit of an input port wire is a driver into the module
for (auto wire : module->wires()) {
if (!wire->port_input) continue;
SigSpec sig = sigmap(wire);
for (int i = 0; i < GetSize(sig); i++) {
SigBit bit = sig[i];
bit_driver[bit] = {wire->name, IdString()};
}
}
// Record drivers from cell output ports
for (auto cell : module->cells()) {
for (auto &conn : cell->connections()) {
if (!cell->output(conn.first)) continue;
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit = sigmap(conn.second[i]);
bit_driver[bit] = {cell->name, conn.first};
}
}
}
// Identify clock and reset signals by looking at FF cell ports,
// then trace backwards through driver cells to mark the original
// source bits (handles intermediate cells like $reduce_or inserted
// by proc_dff between input ports and FF ARST/CLK).
if (skip_clk_rst) {
for (auto cell : module->cells()) {
for (auto port_id : {ID(CLK), ID(C)}) {
if (cell->hasPort(port_id)) {
SigSpec sig = sigmap(cell->getPort(port_id));
for (auto bit : sig)
clk_rst_bits.insert(bit);
}
}
for (auto port_id : {ID(RST), ID(ARST), ID(SRST), ID(R), ID(S)}) {
if (cell->hasPort(port_id)) {
SigSpec sig = sigmap(cell->getPort(port_id));
for (auto bit : sig)
clk_rst_bits.insert(bit);
}
}
}
// Build a map from each driven bit to the cell that drives it
dict<SigBit, Cell*> bit_to_cell;
for (auto cell : module->cells()) {
for (auto &conn : cell->connections()) {
if (!cell->output(conn.first)) continue;
for (auto bit : sigmap(conn.second))
bit_to_cell[bit] = cell;
}
}
// Iteratively trace backwards: if a clk/rst bit is driven by a
// cell, mark all of that cell's input bits as clk/rst too.
bool changed = true;
while (changed) {
changed = false;
pool<SigBit> new_bits;
for (auto bit : clk_rst_bits) {
auto it = bit_to_cell.find(bit);
if (it == bit_to_cell.end()) continue;
Cell *drv_cell = it->second;
for (auto &conn : drv_cell->connections()) {
if (!drv_cell->input(conn.first)) continue;
for (auto inp_bit : sigmap(conn.second)) {
if (!clk_rst_bits.count(inp_bit))
new_bits.insert(inp_bit);
}
}
}
if (!new_bits.empty()) {
changed = true;
for (auto bit : new_bits)
clk_rst_bits.insert(bit);
}
}
}
// Count fanout: each cell input pin connection is one fanout
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]);
if (bit_driver.count(bit))
bit_fanout[bit]++;
}
}
}
// Count fanout: each output port connection is one fanout
for (auto wire : module->wires()) {
if (!wire->port_output) continue;
SigSpec sig = sigmap(wire);
for (int i = 0; i < GetSize(sig); i++) {
SigBit bit = sig[i];
if (bit_driver.count(bit))
bit_fanout[bit]++;
}
}
}
// Report all drivers whose total fanout exceeds the limit.
// Aggregates per-bit fanout to the (driver, port) level.
// If f is non-null, also writes the same lines to that file.
void report(int limit, FILE *f = nullptr)
{
// Aggregate fanout per driver (identified by driver_name + port_name),
// skipping bits identified as clock or reset signals.
dict<pair<IdString, IdString>, int> driver_fanout;
for (auto &entry : bit_fanout) {
SigBit bit = entry.first;
if (clk_rst_bits.count(bit))
continue;
int count = entry.second;
auto &drv = bit_driver.at(bit);
driver_fanout[drv] += count;
}
// Report any driver exceeding the limit
for (auto &entry : driver_fanout) {
int fanout = entry.second;
if (fanout <= limit) continue;
IdString driver_name = entry.first.first;
IdString port_name = entry.first.second;
if (port_name == IdString()) {
// Driver is an input port
log_warning("(%s, %s) = %d\n",
log_id(module), log_id(driver_name), fanout);
if (f)
fprintf(f, "(%s, %s) = %d\n",
log_id(module), log_id(driver_name), fanout);
} else {
// Driver is a cell output port
log_warning("(%s, %s, %s) = %d\n",
log_id(module), log_id(driver_name), log_id(port_name), fanout);
if (f)
fprintf(f, "(%s, %s, %s) = %d\n",
log_id(module), log_id(driver_name), log_id(port_name), fanout);
}
}
}
};
struct ReportFanoutPass : public Pass {
ReportFanoutPass() : Pass("report_fanout", "report nets with fanout exceeding a limit") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" report_fanout -limit <n> [-file <filename>] [-include_clk_rst] [selection]\n");
log("\n");
log("This command reports all input ports and cell output ports whose fanout\n");
log("exceeds the specified limit. Results are printed as log_warning messages.\n");
log("\n");
log("By default, nets driving clock or reset ports (CLK, C, RST, ARST, SRST,\n");
log("R, S) on flip-flops are excluded from the report.\n");
log("\n");
log(" -limit n\n");
log(" fanout threshold. Nets with fanout > n are reported.\n");
log("\n");
log(" -file filename\n");
log(" also write the report to the specified file.\n");
log("\n");
log(" -include_clk_rst\n");
log(" include clock and reset nets in the report.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
int limit = -1;
std::string filename;
bool include_clk_rst = false;
log_header(design, "Executing REPORT_FANOUT pass (reporting nets with high 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;
}
if (args[argidx] == "-file" && argidx + 1 < args.size()) {
filename = args[++argidx];
continue;
}
if (args[argidx] == "-include_clk_rst") {
include_clk_rst = true;
continue;
}
break;
}
extra_args(args, argidx, design);
if (limit < 0)
log_cmd_error("Missing required -limit option.\n");
// Open output file if requested
FILE *f = nullptr;
if (!filename.empty()) {
f = fopen(filename.c_str(), "w");
if (!f)
log_cmd_error("Cannot open file '%s' for writing.\n", filename.c_str());
}
bool skip_clk_rst = !include_clk_rst;
for (auto module : design->selected_modules())
{
log("Analyzing fanout for module %s...\n", log_id(module));
ReportFanoutWorker worker(module, skip_clk_rst);
worker.report(limit, f);
}
if (f)
fclose(f);
}
} ReportFanoutPass;
PRIVATE_NAMESPACE_END

View File

@ -0,0 +1,252 @@
# Test 1: Input port with high fanout is reported
# Input 'a' fans out to 5 output ports, limit is 3 => should warn for 'a'
# Input 'b' fans out to 2 output ports (<= 3) => no warn for 'b'
log -header "Input port fanout exceeds limit"
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;
assign w2 = a;
assign w3 = a;
assign w4 = a;
assign w5 = a;
assign x1 = b;
assign x2 = b;
endmodule
EOF
proc -noopt
opt_clean -purge
# 'a' has fanout 5 (> 3), 'b' has fanout 2 (<= 3)
# Expect exactly one warning for input port 'a'
logger -expect warning "\(top, a\) = 5" 1
report_fanout -limit 3
design -reset
log -pop
# Test 2: Cell output port with high fanout is reported
# AND gate output fans out to 5 output ports, limit is 2 => should warn
log -header "Cell output port fanout exceeds limit"
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
);
wire and_out = a & b;
assign w1 = and_out;
assign w2 = and_out;
assign w3 = and_out;
assign w4 = and_out;
assign w5 = and_out;
endmodule
EOF
proc -noopt
opt_clean -purge
# The $and cell's Y port fans out to 5 (> 2)
# Input ports a and b each fan out to 1 cell input (<= 2), no warn for them
logger -expect warning "\(top, .*, Y\) = 5" 1
report_fanout -limit 2
design -reset
log -pop
# Test 3: No warnings when all fanouts are within the limit
log -header "No fanout exceeds limit"
log -push
design -reset
read_verilog <<EOF
module top (
input wire a,
input wire b,
output wire w1,
output wire w2
);
wire and_out = a & b;
assign w1 = and_out;
assign w2 = and_out;
endmodule
EOF
proc -noopt
opt_clean -purge
# All fanouts <= 10, so no warnings expected
logger -expect-no-warnings
report_fanout -limit 10
design -reset
log -pop
# Test 4: Multiple cell output ports reported in the same module
# Two cells each have fanout of 3, limit is 1 => two warnings
log -header "Multiple cells with high fanout"
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 x1,
output wire x2,
output wire x3
);
wire and_out = a & b;
wire or_out = a | c;
assign w1 = and_out;
assign w2 = and_out;
assign w3 = and_out;
assign x1 = or_out;
assign x2 = or_out;
assign x3 = or_out;
endmodule
EOF
proc -noopt
opt_clean -purge
# Both cells' Y ports fan out to 3 (> 1). Input 'a' fans out to 2 cell inputs (> 1).
# Expect 3 warnings total.
logger -expect warning "\(top, .*, Y\) = 3" 2
logger -expect warning "\(top, a\) = 2" 1
report_fanout -limit 1
design -reset
log -pop
# Test 5: Mixed - input port AND cell output both exceed limit
log -header "Input port and cell output both exceed limit"
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 y1,
output wire y2,
output wire y3
);
wire not_out = !a;
assign w1 = a;
assign w2 = a;
assign w3 = a;
assign y1 = not_out;
assign y2 = not_out;
assign y3 = not_out;
endmodule
EOF
proc -noopt
opt_clean -purge
# 'a' fans out to 3 output ports + 1 cell input = 4 total (> 2)
# $logic_not Y fans out to 3 (> 2)
logger -expect warning "\(top, a\) = 4" 1
logger -expect warning "\(top, .*, Y\) = 3" 1
report_fanout -limit 2
design -reset
log -pop
# Test 6: Clock and reset ports are excluded by default
log -header "Clock and reset exclusion"
log -push
design -reset
read_verilog <<EOF
module top (
input wire clk,
input wire rst,
input wire [3:0] d,
output reg [3:0] q
);
// Four separate always blocks to create four independent $adff cells
always @(posedge clk or posedge rst) begin
if (rst) q[0] <= 1'b0; else q[0] <= d[0];
end
always @(posedge clk or posedge rst) begin
if (rst) q[1] <= 1'b0; else q[1] <= d[1];
end
always @(posedge clk or posedge rst) begin
if (rst) q[2] <= 1'b0; else q[2] <= d[2];
end
always @(posedge clk or posedge rst) begin
if (rst) q[3] <= 1'b0; else q[3] <= d[3];
end
endmodule
EOF
proc -noopt
opt_clean -purge
# 'clk' and 'rst' each fan out to 4 FF cells but are excluded by default
# 'd' is 4 bits wide, each bit fans out to 1 FF (aggregated = 4), not excluded
logger -expect warning "\(top, d\) = 4" 1
report_fanout -limit 3
design -reset
log -pop
# Test 7: -include_clk_rst makes clock/reset nets reportable
log -header "Clock and reset included with -include_clk_rst"
log -push
design -reset
read_verilog <<EOF
module top (
input wire clk,
input wire rst,
input wire [3:0] d,
output reg [3:0] q
);
always @(posedge clk or posedge rst) begin
if (rst) q[0] <= 1'b0; else q[0] <= d[0];
end
always @(posedge clk or posedge rst) begin
if (rst) q[1] <= 1'b0; else q[1] <= d[1];
end
always @(posedge clk or posedge rst) begin
if (rst) q[2] <= 1'b0; else q[2] <= d[2];
end
always @(posedge clk or posedge rst) begin
if (rst) q[3] <= 1'b0; else q[3] <= d[3];
end
endmodule
EOF
proc -noopt
opt_clean -purge
# With -include_clk_rst, clk and rst (fanout 4 each) should be reported
logger -expect warning "\(top, d\) = 4" 1
logger -expect warning "\(top, clk\) = 4" 1
logger -expect warning "\(top, rst\) = 4" 1
report_fanout -limit 3 -include_clk_rst
design -reset
log -pop