From dd51234178cc8f76a8b9e08aa63c016bc0088fef Mon Sep 17 00:00:00 2001 From: nella Date: Tue, 25 Aug 2026 17:20:24 +0200 Subject: [PATCH 1/3] Remove unused ports. --- passes/opt/rmports.cc | 76 ++++++++++++++++++++++++++++++++++++++++--- tests/opt/bug1793.ys | 56 +++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 5 deletions(-) create mode 100644 tests/opt/bug1793.ys diff --git a/passes/opt/rmports.cc b/passes/opt/rmports.cc index 12171f007..5152e7cfd 100644 --- a/passes/opt/rmports.cc +++ b/passes/opt/rmports.cc @@ -33,7 +33,10 @@ struct RmportsPassPass : public Pass { log(" rmports [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("driven and removes them. Output ports which are driven inside the module\n"); + log("are also removed if their connections on all instances of the module lead\n"); + log("nowhere. This does not apply to the top module and to modules which are\n"); + log("never instantiated.\n"); log("\n"); } @@ -47,16 +50,63 @@ struct RmportsPassPass : public Pass { // The set of ports we removed dict> removed_ports; + // Find the ports which are used by some instance in the design + dict> used_instance_ports; + pool instantiated; + ScanInstances(design, used_instance_ports, instantiated); + // 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); + ScanModule(mod, removed_ports, used_instance_ports[mod->name], instantiated.count(mod->name)); // Remove the unused ports from all instances of those modules for(auto mod : modules) CleanupModule(mod, removed_ports); } + // 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> &used_instance_ports, pool &instantiated) + { + // Count how often each wire bit is referenced anywhere in the design + dict 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()) + { + instantiated.insert(cell->type); + 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()) + 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> &removed_ports) { log("Removing now-unused cell ports in module %s\n", module->name); @@ -81,7 +131,8 @@ struct RmportsPassPass : public Pass { } } - void ScanModule(Module* module, dict> &removed_ports) + void ScanModule(Module* module, dict> &removed_ports, + const pool &used_instance_ports, bool is_instantiated) { log("Finding unconnected ports in module %s\n", module->name); @@ -139,12 +190,27 @@ struct RmportsPassPass : public Pass { } // Now that we know what IS used, get rid of anything that isn't in that list + bool keep_outputs = !is_instantiated || module->get_bool_attribute(ID::top); pool 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 diff --git a/tests/opt/bug1793.ys b/tests/opt/bug1793.ys new file mode 100644 index 000000000..8634d2624 --- /dev/null +++ b/tests/opt/bug1793.ys @@ -0,0 +1,56 @@ +# rmports: remove output ports which are driven internally but unused by all instances + +read_verilog < Date: Tue, 1 Sep 2026 14:46:04 +0200 Subject: [PATCH 2/3] Only remove if clear top module exists. --- passes/opt/rmports.cc | 89 +++++++++++++++++++++++------ tests/opt/bug1793.ys | 128 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 186 insertions(+), 31 deletions(-) diff --git a/passes/opt/rmports.cc b/passes/opt/rmports.cc index 5152e7cfd..8b3284f28 100644 --- a/passes/opt/rmports.cc +++ b/passes/opt/rmports.cc @@ -30,13 +30,20 @@ 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. Output ports which are driven inside the module\n"); - log("are also removed if their connections on all instances of the module lead\n"); - log("nowhere. This does not apply to the top module and to modules which are\n"); - log("never instantiated.\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 clear\n"); + log("top module: one with the top attribute, or the only module which nothing\n"); + log("instantiates. Without one, a parent design which is not loaded can still\n"); + log("use these ports.\n"); + log("\n"); + log(" -purge\n"); + log(" remove driven outputs also when there is no clear top module\n"); log("\n"); } @@ -44,7 +51,18 @@ struct RmportsPassPass : public Pass { { 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 @@ -53,22 +71,58 @@ struct RmportsPassPass : public Pass { // Find the ports which are used by some instance in the design dict> used_instance_ports; pool instantiated; - ScanInstances(design, used_instance_ports, instantiated); + pool positional; + CollectInstantiated(design, instantiated); + bool scan_outputs = purge_mode || HasTopModule(design) || HasUniqueRoot(design, instantiated); + if(scan_outputs) + ScanInstances(design, used_instance_ports, positional); + else + log("The design has no clear top module. 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, used_instance_ports[mod->name], instantiated.count(mod->name)); + 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 &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 design where only one module is not instantiated has a clear top even + // when no module has the attribute + bool HasUniqueRoot(Design *design, const pool &instantiated) + { + int roots = 0; + for(auto mod : design->modules()) + if(!instantiated.count(mod->name)) + roots++; + return roots == 1; + } + // 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> &used_instance_ports, pool &instantiated) + void ScanInstances(Design *design, dict> &used_instance_ports, pool &positional) { // Count how often each wire bit is referenced anywhere in the design dict bit_refs; @@ -79,13 +133,10 @@ struct RmportsPassPass : public Pass { if(bit.wire != NULL) bit_refs[bit]++; for(auto cell : mod->cells()) - { - instantiated.insert(cell->type); for(auto &conn : cell->connections()) for(auto bit : conn.second) if(bit.wire != NULL) bit_refs[bit]++; - } } for(auto mod : design->modules()) @@ -93,6 +144,10 @@ struct RmportsPassPass : public Pass { 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) @@ -104,6 +159,7 @@ struct RmportsPassPass : public Pass { break; } } + } } } @@ -132,7 +188,7 @@ struct RmportsPassPass : public Pass { } void ScanModule(Module* module, dict> &removed_ports, - const pool &used_instance_ports, bool is_instantiated) + const pool &used_instance_ports, bool keep_outputs) { log("Finding unconnected ports in module %s\n", module->name); @@ -190,7 +246,6 @@ struct RmportsPassPass : public Pass { } // Now that we know what IS used, get rid of anything that isn't in that list - bool keep_outputs = !is_instantiated || module->get_bool_attribute(ID::top); pool unused_ports; for(auto port : module->ports) { diff --git a/tests/opt/bug1793.ys b/tests/opt/bug1793.ys index 8634d2624..35043a399 100644 --- a/tests/opt/bug1793.ys +++ b/tests/opt/bug1793.ys @@ -1,27 +1,131 @@ # rmports: remove output ports which are driven internally but unused by all instances +# https://github.com/YosysHQ/yosys/issues/1793 read_verilog < Date: Mon, 14 Sep 2026 10:21:17 +0200 Subject: [PATCH 3/3] rmports: only rely on top attr. --- passes/opt/rmports.cc | 24 ++++++------------------ tests/opt/bug1793.ys | 7 +++++-- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/passes/opt/rmports.cc b/passes/opt/rmports.cc index 8b3284f28..6d88b2666 100644 --- a/passes/opt/rmports.cc +++ b/passes/opt/rmports.cc @@ -37,13 +37,12 @@ struct RmportsPassPass : public Pass { 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 clear\n"); - log("top module: one with the top attribute, or the only module which nothing\n"); - log("instantiates. Without one, a parent design which is not loaded can still\n"); - log("use these ports.\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 there is no clear top module\n"); + log(" remove driven outputs also when no module has the top attribute\n"); log("\n"); } @@ -73,11 +72,11 @@ struct RmportsPassPass : public Pass { pool instantiated; pool positional; CollectInstantiated(design, instantiated); - bool scan_outputs = purge_mode || HasTopModule(design) || HasUniqueRoot(design, instantiated); + bool scan_outputs = purge_mode || HasTopModule(design); if(scan_outputs) ScanInstances(design, used_instance_ports, positional); else - log("The design has no clear top module. Outputs which are driven inside their module stay.\n"); + 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 for(auto mod : design->selected_modules()) @@ -108,17 +107,6 @@ struct RmportsPassPass : public Pass { return false; } - // A design where only one module is not instantiated has a clear top even - // when no module has the attribute - bool HasUniqueRoot(Design *design, const pool &instantiated) - { - int roots = 0; - for(auto mod : design->modules()) - if(!instantiated.count(mod->name)) - roots++; - return roots == 1; - } - // 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 diff --git a/tests/opt/bug1793.ys b/tests/opt/bug1793.ys index 35043a399..1b38d4450 100644 --- a/tests/opt/bug1793.ys +++ b/tests/opt/bug1793.ys @@ -95,7 +95,7 @@ rmports select -assert-count 1 sub/o:* hierarchy -check -# two modules are a root here, so there is no clear top +# no module has the top attribute, so driven outputs stay design -reset read_verilog <