mirror of https://github.com/YosysHQ/yosys.git
commit
20aec224de
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* 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 <memory>
|
||||
#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()) {
|
||||
try {
|
||||
limit = std::stoi(args[++argidx]);
|
||||
} catch (...) {
|
||||
log_cmd_error("Invalid value for -limit: '%s'. Expected an integer.\n", args[argidx].c_str());
|
||||
}
|
||||
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 (must be a non-negative integer).\n");
|
||||
|
||||
// Open output file if requested
|
||||
std::unique_ptr<FILE, decltype(&fclose)> f(nullptr, &fclose);
|
||||
if (!filename.empty()) {
|
||||
f.reset(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.get());
|
||||
}
|
||||
}
|
||||
} ReportFanoutPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue