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) {
int count = w->attributes.size();
count -= w->attributes.count(ID::src);
count -= w->attributes.count(ID::unused_bits);
return count;
};

View File

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

View File

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

View File

@ -1029,7 +1029,6 @@ X(unsigned_a_i)
X(unsigned_b)
X(unsigned_b_i)
X(unsupported_sva)
X(unused_bits)
X(use_dsp)
X(value)
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/celltypes.h"
#include "kernel/sigtools.h"
#include "kernel/unused_bits.h"
#include "backends/rtlil/rtlil_backend.h"
USING_YOSYS_NAMESPACE
@ -190,19 +191,10 @@ struct WrapcellPass : Pass {
bool tracking_unused = has_fmt_field(name_fmt, "%unused");
for (auto module : d->selected_modules()) {
SigPool unused;
NewCellTypes ct_all(d);
for (auto wire : module->wires())
if (wire->has_attribute(ID::unused_bits)) {
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 module : d->selected_modules()) {
UnusedBits unused(module, ct_all);
for (auto cell : module->selected_cells()) {
Module *subm;

View File

@ -22,6 +22,7 @@
#include "kernel/sigtools.h"
#include "kernel/consteval.h"
#include "kernel/celltypes.h"
#include "kernel/unused_bits.h"
#include "fsmdata.h"
#include <string.h>
@ -32,6 +33,7 @@ struct FsmExpand
{
RTLIL::Module *module;
RTLIL::Cell *fsm_cell;
UnusedBits& unused_bits;
bool full_mode;
SigMap assign_map;
@ -127,7 +129,7 @@ struct FsmExpand
if (trans_num > limit_transitions)
{
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;
trans_num = fsm_cell->parameters[ID::TRANS_NUM].as_int();
@ -218,7 +220,7 @@ struct FsmExpand
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;
fsm_cell = cell;
@ -257,7 +259,7 @@ struct FsmExpand
module->remove(c);
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));
}
@ -296,12 +298,13 @@ struct FsmExpandPass : public Pass {
extra_args(args, argidx, design);
for (auto mod : design->selected_modules()) {
UnusedBits unused_bits(mod);
std::vector<RTLIL::Cell*> fsm_cells;
for (auto cell : mod->selected_cells())
if (cell->type == ID($fsm))
fsm_cells.push_back(cell);
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();
}
}

View File

@ -22,8 +22,8 @@
#include "kernel/sigtools.h"
#include "kernel/consteval.h"
#include "kernel/celltypes.h"
#include "kernel/unused_bits.h"
#include "fsmdata.h"
#include <string.h>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@ -33,6 +33,7 @@ struct FsmOpt
FsmData fsm_data;
RTLIL::Cell *cell;
RTLIL::Module *module;
UnusedBits& unused_bits;
void opt_unreachable_states()
{
@ -79,21 +80,7 @@ struct FsmOpt
bool signal_is_unused(RTLIL::SigSpec sig)
{
RTLIL::SigBit bit = 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;
return unused_bits.check(sig.as_bit());
}
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);
@ -318,9 +305,9 @@ struct FsmOpt
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
@ -343,10 +330,12 @@ struct FsmOptPass : public Pass {
log_header(design, "Executing FSM_OPT pass (simple optimizations of FSMs).\n");
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())
if (cell->type == ID($fsm))
FsmData::optimize_fsm(cell, mod);
FsmData::optimize_fsm(cell, mod, unused_bits);
}
}
} FsmOptPass;

View File

@ -21,6 +21,7 @@
#define FSMDATA_H
#include "kernel/yosys.h"
#include "kernel/unused_bits.h"
YOSYS_NAMESPACE_BEGIN
@ -155,7 +156,7 @@ struct FsmData
}
// 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

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::hdlname);
count -= w->attributes.count(ID::scopename);
count -= w->attributes.count(ID::unused_bits);
return count;
}
@ -168,13 +167,6 @@ bool check_any(const ShardedSigPool &sigs, const RTLIL::SigSpec &spec) {
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 {
RTLIL::Cell *cell;
RTLIL::IdString port;
@ -328,33 +320,28 @@ struct DeferredUpdates {
};
struct UsedSignals {
// here, "connected" means "driven or driving something"
// meanwhile, "used" means "driving something"
// sigmapped
ShardedSigPool connected;
// pre-sigmapped
ShardedSigPool raw_connected;
// sigmapped
ShardedSigPool used;
void clear(ParallelDispatchThreadPool::Subpool &subpool) {
subpool.run([this](const ParallelDispatchThreadPool::RunCtx &ctx) {
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);
ShardedSigPool::Builder 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
// also gather the usage information for ports, wires with `keep`
// 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
sig_analysis.clear(ctx);
@ -366,8 +353,6 @@ DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analys
deferred.update_connections.insert(ctx, {cell, port, spec});
add_spec(raw_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())) {
@ -377,8 +362,6 @@ DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analys
add_spec(raw_conn_builder, ctx, sig);
actx.assign_map.apply(sig);
add_spec(conn_builder, ctx, sig);
if (!wire->port_input)
add_spec(used_builder, ctx, sig);
}
if (wire->get_bool_attribute(ID::keep)) {
RTLIL::SigSpec sig = RTLIL::SigSpec(wire);
@ -390,12 +373,11 @@ DeferredUpdates analyse_connectivity(UsedSignals& used, SigConnKinds& sig_analys
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);
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;
}
@ -404,14 +386,10 @@ struct WireDeleter {
ShardedVector<RTLIL::Wire*> remove_init;
ShardedVector<std::pair<RTLIL::Wire*, RTLIL::Const>> set_init;
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) :
remove_init(actx.subpool),
set_init(actx.subpool),
new_connections(actx.subpool),
remove_unused_bits(actx.subpool),
set_unused_bits(actx.subpool) {
new_connections(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) {
for (int i : ctx.item_range(actx.mod->wires_size())) {
@ -471,32 +449,6 @@ struct WireDeleter {
} else
if (init_changed)
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);
for (auto &conn : new_connections)
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 deleted_and_unreported = 0;
@ -557,7 +505,7 @@ bool rmunused_module_signals(RTLIL::Module *module, ParallelDispatchThreadPool::
module->connections_.clear();
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);
// Rip up and re-apply init attributes onto representative wires with x-bits
// in place of unset init bits

View File

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