opt_clean: move unused_bits analysis to kernel/unused_bits.h

This commit is contained in:
Emil J. Tywoniak 2026-08-11 17:23:57 +02:00
parent bf86dd7faf
commit 0253911ee2
12 changed files with 86 additions and 104 deletions

View File

@ -406,7 +406,6 @@ struct EdifBackend : public Backend {
auto count_nontrivial_attr = [](Wire *w) { auto count_nontrivial_attr = [](Wire *w) {
int count = w->attributes.size(); int count = w->attributes.size();
count -= w->attributes.count(ID::src); count -= w->attributes.count(ID::src);
count -= w->attributes.count(ID::unused_bits);
return count; return count;
}; };

View File

@ -108,9 +108,7 @@ FSM optimization
The `fsm_opt` pass performs basic optimizations on `$fsm` cells (not including The `fsm_opt` pass performs basic optimizations on `$fsm` cells (not including
state recoding). The following optimizations are performed (in this order): state recoding). The following optimizations are performed (in this order):
- Unused control outputs are removed from the `$fsm` cell. The attribute - Unused control outputs are removed from the `$fsm` cell.
``unused_bits`` (that is usually set by the `opt_clean` pass) is used to
determine which control outputs are unused.
- Control inputs that are connected to the same driver are merged. - Control inputs that are connected to the same driver are merged.

View File

@ -203,8 +203,6 @@ Removing unused cells and wires - `opt_clean` pass
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This pass identifies unused signals and cells and removes them from the design. This pass identifies unused signals and cells and removes them from the design.
It also creates an ``unused_bits`` attribute on wires with unused bits. This
attribute can be used for debugging or by other optimization passes.
When to use `opt` or `clean` When to use `opt` or `clean`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -80,6 +80,7 @@ yosys_core(kernel
threading.h threading.h
timinginfo.h timinginfo.h
topo_scc.h topo_scc.h
unused_bits.h
utils.h utils.h
version.cc version.cc
yosys.cc yosys.cc
@ -145,6 +146,7 @@ yosys_core(kernel
sigtools.h sigtools.h
threading.h threading.h
timinginfo.h timinginfo.h
unused_bits.h
utils.h utils.h
yosys.h yosys.h
yosys_common.h yosys_common.h

View File

@ -1029,7 +1029,6 @@ X(unsigned_a_i)
X(unsigned_b) X(unsigned_b)
X(unsigned_b_i) X(unsigned_b_i)
X(unsupported_sva) X(unsupported_sva)
X(unused_bits)
X(use_dsp) X(use_dsp)
X(value) X(value)
X(via_celltype) X(via_celltype)

54
kernel/unused_bits.h Normal file
View File

@ -0,0 +1,54 @@
#ifndef UNUSED_BITS_H
#define UNUSED_BITS_H
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
#include "kernel/newcelltypes.h"
YOSYS_NAMESPACE_BEGIN
struct UnusedBits
{
SigMap sigmap;
pool<RTLIL::SigBit> used;
UnusedBits(RTLIL::Module *module) : sigmap(module)
{
NewCellTypes ct(module->design);
collect_used(module, ct);
}
UnusedBits(RTLIL::Module *module, const NewCellTypes &ct) : sigmap(module)
{
collect_used(module, ct);
}
void collect_used(RTLIL::Module *module, const NewCellTypes &ct)
{
for (auto cell : module->cells())
for (auto &conn : cell->connections())
if (!ct.cell_output(cell->type, conn.first))
add_used(conn.second);
for (auto wire : module->wires())
if (wire->port_output)
add_used(wire);
}
void add_used(const RTLIL::SigSpec &sig)
{
for (auto bit : sigmap(sig))
if (bit.wire != nullptr)
used.insert(bit);
}
bool check(RTLIL::SigBit bit) const
{
RTLIL::SigBit mapped = sigmap(bit);
return mapped.wire != nullptr && !used.count(mapped);
}
};
YOSYS_NAMESPACE_END
#endif

View File

@ -19,6 +19,7 @@
#include "kernel/yosys.h" #include "kernel/yosys.h"
#include "kernel/celltypes.h" #include "kernel/celltypes.h"
#include "kernel/sigtools.h" #include "kernel/sigtools.h"
#include "kernel/unused_bits.h"
#include "backends/rtlil/rtlil_backend.h" #include "backends/rtlil/rtlil_backend.h"
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
@ -190,19 +191,10 @@ struct WrapcellPass : Pass {
bool tracking_unused = has_fmt_field(name_fmt, "%unused"); bool tracking_unused = has_fmt_field(name_fmt, "%unused");
for (auto module : d->selected_modules()) { NewCellTypes ct_all(d);
SigPool unused;
for (auto wire : module->wires()) for (auto module : d->selected_modules()) {
if (wire->has_attribute(ID::unused_bits)) { UnusedBits unused(module, ct_all);
std::string str = wire->get_string_attribute(ID::unused_bits);
for (auto it = str.begin(); it != str.end();) {
auto sep = it;
for (; sep != str.end() && *sep != ' '; sep++);
unused.add(SigBit(wire, std::stoi(std::string(it, sep))));
for (it = sep; it != str.end() && *it == ' '; it++);
}
}
for (auto cell : module->selected_cells()) { for (auto cell : module->selected_cells()) {
Module *subm; Module *subm;

View File

@ -22,6 +22,7 @@
#include "kernel/sigtools.h" #include "kernel/sigtools.h"
#include "kernel/consteval.h" #include "kernel/consteval.h"
#include "kernel/celltypes.h" #include "kernel/celltypes.h"
#include "kernel/unused_bits.h"
#include "fsmdata.h" #include "fsmdata.h"
#include <string.h> #include <string.h>
@ -32,6 +33,7 @@ struct FsmExpand
{ {
RTLIL::Module *module; RTLIL::Module *module;
RTLIL::Cell *fsm_cell; RTLIL::Cell *fsm_cell;
UnusedBits& unused_bits;
bool full_mode; bool full_mode;
SigMap assign_map; SigMap assign_map;
@ -127,7 +129,7 @@ struct FsmExpand
if (trans_num > limit_transitions) if (trans_num > limit_transitions)
{ {
log(" grown transition table to %d entries -> optimize.\n", trans_num); log(" grown transition table to %d entries -> optimize.\n", trans_num);
FsmData::optimize_fsm(fsm_cell, module); FsmData::optimize_fsm(fsm_cell, module, unused_bits);
already_optimized = true; already_optimized = true;
trans_num = fsm_cell->parameters[ID::TRANS_NUM].as_int(); trans_num = fsm_cell->parameters[ID::TRANS_NUM].as_int();
@ -218,7 +220,7 @@ struct FsmExpand
fsm_data.copy_to_cell(fsm_cell); fsm_data.copy_to_cell(fsm_cell);
} }
FsmExpand(RTLIL::Cell *cell, RTLIL::Design *design, RTLIL::Module *mod, bool full) FsmExpand(RTLIL::Cell *cell, RTLIL::Design *design, RTLIL::Module *mod, UnusedBits& unused_bits, bool full) : unused_bits(unused_bits)
{ {
module = mod; module = mod;
fsm_cell = cell; fsm_cell = cell;
@ -257,7 +259,7 @@ struct FsmExpand
module->remove(c); module->remove(c);
if (merged_set.size() > 0 && !already_optimized) if (merged_set.size() > 0 && !already_optimized)
FsmData::optimize_fsm(fsm_cell, module); FsmData::optimize_fsm(fsm_cell, module, unused_bits);
log(" merged %d cells into FSM.\n", GetSize(merged_set)); log(" merged %d cells into FSM.\n", GetSize(merged_set));
} }
@ -296,12 +298,13 @@ struct FsmExpandPass : public Pass {
extra_args(args, argidx, design); extra_args(args, argidx, design);
for (auto mod : design->selected_modules()) { for (auto mod : design->selected_modules()) {
UnusedBits unused_bits(mod);
std::vector<RTLIL::Cell*> fsm_cells; std::vector<RTLIL::Cell*> fsm_cells;
for (auto cell : mod->selected_cells()) for (auto cell : mod->selected_cells())
if (cell->type == ID($fsm)) if (cell->type == ID($fsm))
fsm_cells.push_back(cell); fsm_cells.push_back(cell);
for (auto c : fsm_cells) { for (auto c : fsm_cells) {
FsmExpand fsm_expand(c, design, mod, full_mode); FsmExpand fsm_expand(c, design, mod, unused_bits, full_mode);
fsm_expand.execute(); fsm_expand.execute();
} }
} }

View File

@ -22,8 +22,8 @@
#include "kernel/sigtools.h" #include "kernel/sigtools.h"
#include "kernel/consteval.h" #include "kernel/consteval.h"
#include "kernel/celltypes.h" #include "kernel/celltypes.h"
#include "kernel/unused_bits.h"
#include "fsmdata.h" #include "fsmdata.h"
#include <string.h>
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN PRIVATE_NAMESPACE_BEGIN
@ -33,6 +33,7 @@ struct FsmOpt
FsmData fsm_data; FsmData fsm_data;
RTLIL::Cell *cell; RTLIL::Cell *cell;
RTLIL::Module *module; RTLIL::Module *module;
UnusedBits& unused_bits;
void opt_unreachable_states() void opt_unreachable_states()
{ {
@ -79,21 +80,7 @@ struct FsmOpt
bool signal_is_unused(RTLIL::SigSpec sig) bool signal_is_unused(RTLIL::SigSpec sig)
{ {
RTLIL::SigBit bit = sig.as_bit(); return unused_bits.check(sig.as_bit());
if (bit.wire == NULL || bit.wire->attributes.count(ID::unused_bits) == 0)
return false;
char *str = strdup(bit.wire->attributes[ID::unused_bits].decode_string().c_str());
for (char *tok = strtok(str, " "); tok != NULL; tok = strtok(NULL, " ")) {
if (tok[0] && bit.offset == atoi(tok)) {
free(str);
return true;
}
}
free(str);
return false;
} }
void opt_const_and_unused_inputs() void opt_const_and_unused_inputs()
@ -294,7 +281,7 @@ struct FsmOpt
} }
} }
FsmOpt(RTLIL::Cell *cell, RTLIL::Module *module) FsmOpt(RTLIL::Cell *cell, RTLIL::Module *module, UnusedBits& unused_bits) : unused_bits(unused_bits)
{ {
log("Optimizing FSM `%s' from module `%s'.\n", cell->name, module->name); log("Optimizing FSM `%s' from module `%s'.\n", cell->name, module->name);
@ -318,9 +305,9 @@ struct FsmOpt
PRIVATE_NAMESPACE_END PRIVATE_NAMESPACE_END
void YOSYS_NAMESPACE_PREFIX FsmData::optimize_fsm(RTLIL::Cell *cell, RTLIL::Module *module) void YOSYS_NAMESPACE_PREFIX FsmData::optimize_fsm(RTLIL::Cell *cell, RTLIL::Module *module, UnusedBits& unused_bits)
{ {
FsmOpt fsmopt(cell, module); FsmOpt fsmopt(cell, module, unused_bits);
} }
PRIVATE_NAMESPACE_BEGIN PRIVATE_NAMESPACE_BEGIN
@ -343,10 +330,12 @@ struct FsmOptPass : public Pass {
log_header(design, "Executing FSM_OPT pass (simple optimizations of FSMs).\n"); log_header(design, "Executing FSM_OPT pass (simple optimizations of FSMs).\n");
extra_args(args, 1, design); extra_args(args, 1, design);
for (auto mod : design->selected_modules()) for (auto mod : design->selected_modules()) {
UnusedBits unused_bits(mod);
for (auto cell : mod->selected_cells()) for (auto cell : mod->selected_cells())
if (cell->type == ID($fsm)) if (cell->type == ID($fsm))
FsmData::optimize_fsm(cell, mod); FsmData::optimize_fsm(cell, mod, unused_bits);
}
} }
} FsmOptPass; } FsmOptPass;

View File

@ -21,6 +21,7 @@
#define FSMDATA_H #define FSMDATA_H
#include "kernel/yosys.h" #include "kernel/yosys.h"
#include "kernel/unused_bits.h"
YOSYS_NAMESPACE_BEGIN YOSYS_NAMESPACE_BEGIN
@ -155,7 +156,7 @@ struct FsmData
} }
// implemented in fsm_opt.cc // implemented in fsm_opt.cc
static void optimize_fsm(RTLIL::Cell *cell, RTLIL::Module *module); static void optimize_fsm(RTLIL::Cell *cell, RTLIL::Module *module, UnusedBits& unused_bits);
}; };
YOSYS_NAMESPACE_END YOSYS_NAMESPACE_END

View File

@ -86,7 +86,6 @@ int count_nontrivial_wire_attrs(RTLIL::Wire *w)
count -= w->attributes.count(ID::src); count -= w->attributes.count(ID::src);
count -= w->attributes.count(ID::hdlname); count -= w->attributes.count(ID::hdlname);
count -= w->attributes.count(ID::scopename); count -= w->attributes.count(ID::scopename);
count -= w->attributes.count(ID::unused_bits);
return count; return count;
} }
@ -168,13 +167,6 @@ bool check_any(const ShardedSigPool &sigs, const RTLIL::SigSpec &spec) {
return false; return false;
} }
bool check_all(const ShardedSigPool &sigs, const RTLIL::SigSpec &spec) {
for (SigBit b : spec)
if (sigs.find({b, b.hash_top().yield()}) == nullptr)
return false;
return true;
}
struct UpdateConnection { struct UpdateConnection {
RTLIL::Cell *cell; RTLIL::Cell *cell;
RTLIL::IdString port; RTLIL::IdString port;
@ -328,33 +320,28 @@ struct DeferredUpdates {
}; };
struct UsedSignals { struct UsedSignals {
// here, "connected" means "driven or driving something" // here, "connected" means "driven or driving something"
// meanwhile, "used" means "driving something"
// sigmapped // sigmapped
ShardedSigPool connected; ShardedSigPool connected;
// pre-sigmapped // pre-sigmapped
ShardedSigPool raw_connected; ShardedSigPool raw_connected;
// sigmapped
ShardedSigPool used;
void clear(ParallelDispatchThreadPool::Subpool &subpool) { void clear(ParallelDispatchThreadPool::Subpool &subpool) {
subpool.run([this](const ParallelDispatchThreadPool::RunCtx &ctx) { subpool.run([this](const ParallelDispatchThreadPool::RunCtx &ctx) {
connected.clear(ctx); connected.clear(ctx);
raw_connected.clear(ctx); raw_connected.clear(ctx);
used.clear(ctx);
}); });
} }
}; };
DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analysis, const AnalysisContext& actx, CleanRunContext &clean_ctx) { DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analysis, const AnalysisContext& actx) {
DeferredUpdates deferred(actx.subpool); DeferredUpdates deferred(actx.subpool);
ShardedSigPool::Builder conn_builder(actx.subpool); ShardedSigPool::Builder conn_builder(actx.subpool);
ShardedSigPool::Builder raw_conn_builder(actx.subpool); ShardedSigPool::Builder raw_conn_builder(actx.subpool);
ShardedSigPool::Builder used_builder(actx.subpool);
// gather the usage information for cells and update cell connections with the altered sigmap // gather the usage information for cells and update cell connections with the altered sigmap
// also gather the usage information for ports, wires with `keep` // also gather the usage information for ports, wires with `keep`
// also gather init bits // also gather init bits
actx.subpool.run([&deferred, &conn_builder, &raw_conn_builder, &used_builder, &sig_analysis, &actx, &clean_ctx](const ParallelDispatchThreadPool::RunCtx &ctx) { actx.subpool.run([&deferred, &conn_builder, &raw_conn_builder, &sig_analysis, &actx](const ParallelDispatchThreadPool::RunCtx &ctx) {
// Parallel destruction of these sharded structures // Parallel destruction of these sharded structures
sig_analysis.clear(ctx); sig_analysis.clear(ctx);
@ -366,8 +353,6 @@ DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analys
deferred.update_connections.insert(ctx, {cell, port, spec}); deferred.update_connections.insert(ctx, {cell, port, spec});
add_spec(raw_conn_builder, ctx, spec); add_spec(raw_conn_builder, ctx, spec);
add_spec(conn_builder, ctx, spec); add_spec(conn_builder, ctx, spec);
if (!clean_ctx.ct_all.cell_output(cell->type, port))
add_spec(used_builder, ctx, spec);
} }
} }
for (int i : ctx.item_range(actx.mod->wires_size())) { for (int i : ctx.item_range(actx.mod->wires_size())) {
@ -377,8 +362,6 @@ DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analys
add_spec(raw_conn_builder, ctx, sig); add_spec(raw_conn_builder, ctx, sig);
actx.assign_map.apply(sig); actx.assign_map.apply(sig);
add_spec(conn_builder, ctx, sig); add_spec(conn_builder, ctx, sig);
if (!wire->port_input)
add_spec(used_builder, ctx, sig);
} }
if (wire->get_bool_attribute(ID::keep)) { if (wire->get_bool_attribute(ID::keep)) {
RTLIL::SigSpec sig = RTLIL::SigSpec(wire); RTLIL::SigSpec sig = RTLIL::SigSpec(wire);
@ -390,12 +373,11 @@ DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analys
deferred.initialized_wires.insert(ctx, wire); deferred.initialized_wires.insert(ctx, wire);
} }
}); });
actx.subpool.run([&conn_builder, &raw_conn_builder, &used_builder](const ParallelDispatchThreadPool::RunCtx &ctx) { actx.subpool.run([&conn_builder, &raw_conn_builder](const ParallelDispatchThreadPool::RunCtx &ctx) {
conn_builder.process(ctx); conn_builder.process(ctx);
raw_conn_builder.process(ctx); raw_conn_builder.process(ctx);
used_builder.process(ctx);
}); });
used = {conn_builder, raw_conn_builder, used_builder}; used = {conn_builder, raw_conn_builder};
return deferred; return deferred;
} }
@ -404,14 +386,10 @@ struct WireDeleter {
ShardedVector<RTLIL::Wire*> remove_init; ShardedVector<RTLIL::Wire*> remove_init;
ShardedVector<std::pair<RTLIL::Wire*, RTLIL::Const>> set_init; ShardedVector<std::pair<RTLIL::Wire*, RTLIL::Const>> set_init;
ShardedVector<RTLIL::SigSig> new_connections; ShardedVector<RTLIL::SigSig> new_connections;
ShardedVector<RTLIL::Wire*> remove_unused_bits;
ShardedVector<std::pair<RTLIL::Wire*, RTLIL::Const>> set_unused_bits;
WireDeleter(UsedSignals& used_sig_analysis, bool purge_mode, const AnalysisContext& actx) : WireDeleter(UsedSignals& used_sig_analysis, bool purge_mode, const AnalysisContext& actx) :
remove_init(actx.subpool), remove_init(actx.subpool),
set_init(actx.subpool), set_init(actx.subpool),
new_connections(actx.subpool), new_connections(actx.subpool) {
remove_unused_bits(actx.subpool),
set_unused_bits(actx.subpool) {
ShardedVector<RTLIL::Wire*> del_wires(actx.subpool); ShardedVector<RTLIL::Wire*> del_wires(actx.subpool);
actx.subpool.run([&actx, purge_mode, &del_wires, &used_sig_analysis, this](const ParallelDispatchThreadPool::RunCtx &ctx) { actx.subpool.run([&actx, purge_mode, &del_wires, &used_sig_analysis, this](const ParallelDispatchThreadPool::RunCtx &ctx) {
for (int i : ctx.item_range(actx.mod->wires_size())) { for (int i : ctx.item_range(actx.mod->wires_size())) {
@ -471,32 +449,6 @@ struct WireDeleter {
} else } else
if (init_changed) if (init_changed)
set_init.insert(ctx, {wire, std::move(initval)}); set_init.insert(ctx, {wire, std::move(initval)});
std::string unused_bits;
if (!check_all(used_sig_analysis.used, s2)) {
for (int i = 0; i < GetSize(s2); i++) {
if (s2[i].wire == NULL)
continue;
SigBit b = s2[i];
if (used_sig_analysis.used.find({b, b.hash_top().yield()}) == nullptr) {
if (!unused_bits.empty())
unused_bits += " ";
unused_bits += stringf("%d", i);
}
}
}
if (unused_bits.empty() || wire->port_id != 0) {
if (wire->attributes.count(ID::unused_bits))
remove_unused_bits.insert(ctx, wire);
} else {
RTLIL::Const unused_bits_const(std::move(unused_bits));
if (wire->attributes.count(ID::unused_bits)) {
RTLIL::Const &unused_bits_attr = wire->attributes.at(ID::unused_bits);
if (unused_bits_attr != unused_bits_const)
set_unused_bits.insert(ctx, {wire, std::move(unused_bits_const)});
} else
set_unused_bits.insert(ctx, {wire, std::move(unused_bits_const)});
}
} }
} }
}); });
@ -511,10 +463,6 @@ struct WireDeleter {
p.first->attributes[ID::init] = std::move(p.second); p.first->attributes[ID::init] = std::move(p.second);
for (auto &conn : new_connections) for (auto &conn : new_connections)
mod->connect(std::move(conn)); mod->connect(std::move(conn));
for (RTLIL::Wire *wire : remove_unused_bits)
wire->attributes.erase(ID::unused_bits);
for (auto &p : set_unused_bits)
p.first->attributes[ID::unused_bits] = std::move(p.second);
} }
int delete_wires(RTLIL::Module* mod, bool verbose) { int delete_wires(RTLIL::Module* mod, bool verbose) {
int deleted_and_unreported = 0; int deleted_and_unreported = 0;
@ -557,7 +505,7 @@ bool rmunused_module_signals(RTLIL::Module *module, ParallelDispatchThreadPool::
module->connections_.clear(); module->connections_.clear();
UsedSignals used; UsedSignals used;
DeferredUpdates deferred = analyse_connectivity(used, conn_kinds, actx, clean_ctx); DeferredUpdates deferred = analyse_connectivity(used, conn_kinds, actx);
fixup_cell_ports(deferred.update_connections); fixup_cell_ports(deferred.update_connections);
// Rip up and re-apply init attributes onto representative wires with x-bits // Rip up and re-apply init attributes onto representative wires with x-bits
// in place of unset init bits // in place of unset init bits

View File

@ -453,7 +453,6 @@ struct WreduceWorker
{ {
int count = w->attributes.size(); int count = w->attributes.size();
count -= w->attributes.count(ID::src); count -= w->attributes.count(ID::src);
count -= w->attributes.count(ID::unused_bits);
return count; return count;
} }