mirror of
https://github.com/YosysHQ/yosys.git
synced 2026-10-06 01:53:53 +02:00
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:
+118
-9
@@ -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
|
||||
|
||||
@@ -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:*
|
||||
Reference in New Issue
Block a user