Merge pull request #6150 from YosysHQ/nella/rm-unused-ports

rmports: remove output ports unused by all instances [sc-799]
This commit is contained in:
nella
2026-09-22 13:01:17 +00:00
committed by GitHub
2 changed files with 277 additions and 9 deletions
+118 -9
View File
@@ -30,33 +30,127 @@ struct RmportsPassPass : public Pass {
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" rmports [selection]\n");
log(" rmports [options] [selection]\n");
log("\n");
log("This pass identifies ports in the selected modules which are not used or\n");
log("driven and removes them.\n");
log("\n");
log("An output which is driven inside the module is also removed, if no\n");
log("instance of the module uses it. The top module, a module which nothing\n");
log("instantiates, and a port with the keep attribute stay. This needs a module\n");
log("with the top attribute (see hierarchy -top). Without one, a parent design\n");
log("which is not loaded can still use these ports.\n");
log("\n");
log(" -purge\n");
log(" remove driven outputs also when no module has the top attribute\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing RMPORTS pass (remove ports with no connections).\n");
size_t argidx = 1;
bool purge_mode = false;
size_t argidx;
for(argidx = 1; argidx < args.size(); argidx++)
{
if(args[argidx] == "-purge")
{
purge_mode = true;
continue;
}
break;
}
extra_args(args, argidx, design);
// The set of ports we removed
dict<IdString, pool<IdString>> removed_ports;
// Find the ports which are used by some instance in the design
dict<IdString, pool<IdString>> used_instance_ports;
pool<IdString> instantiated;
pool<IdString> positional;
CollectInstantiated(design, instantiated);
bool scan_outputs = purge_mode || HasTopModule(design);
if(scan_outputs)
ScanInstances(design, used_instance_ports, positional);
else
log("The design has no module with the top attribute. Outputs which are driven inside their module stay.\n");
// Find all of the unused ports, and remove them from that module
auto modules = design->selected_modules();
for(auto mod : modules)
ScanModule(mod, removed_ports);
for(auto mod : design->selected_modules())
{
bool keep_outputs = !scan_outputs || !instantiated.count(mod->name) ||
positional.count(mod->name) || mod->get_bool_attribute(ID::top);
ScanModule(mod, removed_ports, used_instance_ports[mod->name], keep_outputs);
}
// Remove the unused ports from all instances of those modules
for(auto mod : modules)
for(auto mod : design->modules())
CleanupModule(mod, removed_ports);
}
// The cell types which some cell in the design instantiates
void CollectInstantiated(Design *design, pool<IdString> &instantiated)
{
for(auto mod : design->modules())
for(auto cell : mod->cells())
instantiated.insert(cell->type);
}
bool HasTopModule(Design *design)
{
for(auto mod : design->modules())
if(mod->get_bool_attribute(ID::top))
return true;
return false;
}
// A port is used by an instance if some bit of its connection goes anywhere
// else in the parent module: a public, port or kept wire, a second reference
// to the same bit, or any wire at all if the parent still contains processes
void ScanInstances(Design *design, dict<IdString, pool<IdString>> &used_instance_ports, pool<IdString> &positional)
{
// Count how often each wire bit is referenced anywhere in the design
dict<SigBit, int> bit_refs;
for(auto mod : design->modules())
{
for(auto &conn : mod->connections())
for(auto bit : SigSpec{conn.first, conn.second})
if(bit.wire != NULL)
bit_refs[bit]++;
for(auto cell : mod->cells())
for(auto &conn : cell->connections())
for(auto bit : conn.second)
if(bit.wire != NULL)
bit_refs[bit]++;
}
for(auto mod : design->modules())
{
bool has_procs = !mod->processes.empty();
for(auto cell : mod->cells())
for(auto &conn : cell->connections())
{
if(!conn.first.isPublic())
positional.insert(cell->type);
for(auto bit : conn.second)
{
if(bit.wire == NULL)
continue;
if(has_procs || bit.wire->name.isPublic() || bit.wire->port_input || bit.wire->port_output ||
bit.wire->get_bool_attribute(ID::keep) || bit_refs.at(bit) > 1)
{
used_instance_ports[cell->type].insert(conn.first);
break;
}
}
}
}
}
void CleanupModule(Module *module, dict<IdString, pool<IdString>> &removed_ports)
{
log("Removing now-unused cell ports in module %s\n", module->name);
@@ -81,7 +175,8 @@ struct RmportsPassPass : public Pass {
}
}
void ScanModule(Module* module, dict<IdString, pool<IdString>> &removed_ports)
void ScanModule(Module* module, dict<IdString, pool<IdString>> &removed_ports,
const pool<IdString> &used_instance_ports, bool keep_outputs)
{
log("Finding unconnected ports in module %s\n", module->name);
@@ -142,9 +237,23 @@ struct RmportsPassPass : public Pass {
pool<IdString> unused_ports;
for(auto port : module->ports)
{
if(used_ports.find(port) != used_ports.end())
if(used_ports.find(port) == used_ports.end())
{
unused_ports.insert(port);
continue;
unused_ports.insert(port);
}
// An output which no instance of this module uses can be removed
// even if it is driven internally
auto wire = module->wire(port);
if(
wire->port_output &&
!wire->port_input &&
!keep_outputs &&
used_instance_ports.find(port) == used_instance_ports.end() &&
!wire->get_bool_attribute(ID::keep)
)
unused_ports.insert(port);
}
// Print the ports out as we go through them
+159
View File
@@ -0,0 +1,159 @@
# rmports: remove output ports which are driven internally but unused by all instances
# https://github.com/YosysHQ/yosys/issues/1793
read_verilog <<EOT
module sub(input a, input z, output x, output y);
assign x = a;
assign y = ~a;
endmodule
module top(input a, output o);
sub s(.a(a), .z(), .x(o), .y());
endmodule
EOT
hierarchy -top top
design -save simple
select -assert-count 2 sub/o:*
select -assert-count 2 sub/i:*
rmports
hierarchy -check
select -assert-count 0 sub/o:y
select -assert-count 1 sub/w:y
select -assert-count 1 sub/o:*
select -assert-count 1 sub/i:a
select -assert-count 1 sub/i:*
select -assert-count 1 top/o:*
select -assert-count 1 top/i:*
# a parent outside the selection is cleaned up as well
design -load simple
rmports sub
select -assert-count 1 sub/o:*
hierarchy -check
# an output is kept when any instance uses it
design -reset
read_verilog <<EOT
module sub(input a, output x, output y);
assign x = a;
assign y = ~a;
endmodule
module top(input a, output o, output p);
sub s1(.a(a), .x(o), .y());
sub s2(.a(a), .x(), .y(p));
endmodule
EOT
hierarchy -top top
rmports
hierarchy -check
select -assert-count 2 sub/o:*
# a cell which is not derived yet names its ports $1, $2, etc
design -reset
read_verilog <<EOT
module sub(input as, output xs, input bs, output ys, input cs);
assign xs = as & bs;
assign ys = as ^ cs;
endmodule
(* top *)
module top(input a, input b, output o);
wire t;
sub s(a, t, b);
assign o = t ^ a;
endmodule
EOT
rmports
hierarchy -check -top top
select -assert-count 2 sub/o:*
# a box parent is never selected, but should also be cleaned up
design -reset
read_verilog <<EOT
module sub(input a, output x, output y);
assign x = a;
assign y = ~a;
endmodule
module mid(input a, output x);
wire dead;
sub s(.a(a), .x(x), .y(dead));
endmodule
module top(input a, output o);
mid m(.a(a), .x(o));
endmodule
EOT
hierarchy -top top
# a public wire counts as a use
rename -hide mid/w:dead
setattr -mod -set whitebox 1 mid
select -clear
rmports
select -assert-count 1 sub/o:*
hierarchy -check
# no module has the top attribute, so driven outputs stay
design -reset
read_verilog <<EOT
module sub(input a, output x, output y);
assign x = a;
assign y = ~a;
endmodule
module use1(input a, output o);
sub s(.a(a), .x(o), .y());
endmodule
module use2(input a, output o);
sub s(.a(a), .x(o), .y());
endmodule
EOT
design -save roots
rmports
select -assert-count 2 sub/o:*
rmports -purge
hierarchy -check
select -assert-count 1 sub/o:x
design -load roots
delete use2
rmports
select -assert-count 2 sub/o:*
hierarchy -top use1
rmports
hierarchy -check
select -assert-count 1 sub/o:x
# repro
design -reset
read_verilog <<EOT
module TOP(a, b, y);
input [3:0] a, b;
output [2:0] y;
reg [2:0] tmp;
wire NC;
sub p(a, b, {NC, tmp});
assign y = tmp;
endmodule
module sub(as, bs, ys);
input [3:0] as, bs;
output [3:0] ys;
assign ys = as & bs;
endmodule
EOT
hierarchy -top TOP
delete w:NC
splitnets -ports
rmports
hierarchy -check
clean -purge
select -assert-count 3 sub/o:*
select -assert-count 8 sub/i:*
select -assert-count 3 TOP/o:*
select -assert-count 8 TOP/i:*