3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-07-25 00:22:34 +00:00

Merge pull request #6050 from YosysHQ/emil/autoname-rewrite

autoname: rewrite
This commit is contained in:
Emil J 2026-07-19 22:10:02 +00:00 committed by GitHub
commit 5f29546d80
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 259 additions and 104 deletions

View file

@ -17,95 +17,246 @@
*
*/
#include "kernel/yosys.h"
#include <functional>
#include <queue>
#include <ranges>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
typedef struct name_proposal {
string name;
// An "object" is a cell or a wire.
// An object is "private" if its name starts with a '$'.
//
// The "autoname" pass renames private objects based on public object names,
// with suffixes added to show the relationship to the public objects.
// The pass chooses the "best" names based on minimizing their "cost".
// The cost of a new name for a private object is the cost along a path
// to a publicly named object.
struct cost {
// score is a property of one "edge", not of the entire path.
// 0 if the neighbour drives the object, and the wire's fanout otherwise
unsigned int score = UINT_MAX;
// New name length
size_t length = SIZE_MAX;
// Earlier discovered connection wins
int edge_pos = 0;
auto operator<=>(const cost &) const = default;
};
// Endpoints of an "edge" are always a cell index and a wire index. Goofy, I know.
// SigSig connections (module->connections()) don't count
// as neighbors. This means that the equivalent of `assign $w1 = \w2;` won't lead
// to $w1 being renamed.
struct Edge {
// Cell port name at which the wire connects to the cell
IdString port;
int cell;
int wire;
bool cell_is_output;
unsigned int score;
name_proposal() : name(""), score(-1) { }
name_proposal(string name, unsigned int score) : name(name), score(score) { }
bool operator<(const name_proposal &other) const {
if (score != other.score)
return score < other.score;
else
return name.length() < other.name.length();
}
} name_proposal;
// Edge's position in the edge list
int pos_in_cell;
int pos_in_wire;
};
int autoname_worker(Module *module, const dict<Wire*, unsigned int>& wire_score)
// decide() finds shortest names for every object reachable from a public name,
// cheapest-first. It doesn't apply the renames to the objects yet.
//
// commit() then renames in decide() order, which is topological, so a source
// neighbour always already carries its final uniquify()d name.
// A selected cell or wire
struct node {
// Exactly one of cell/wire is set
Cell *cell = nullptr;
Wire *wire = nullptr;
vector<Edge> edges;
// Ignores module->connections() just as the rest of the code
unsigned int fanout = 0;
bool is_public = false;
bool renameable = false;
bool selected = false;
size_t name_length = 0;
// Node index from which we want to construct the rename
int from_node = -1;
// Cost for the edge from that node
cost c;
// Suffix to append to the name of that node
string suffix;
// Is this name final?
bool decided = false;
const IdString& name() const { return cell ? cell->name : wire->name; }
};
// Decides the order of exploring neighbors
struct queue_item {
unsigned int score;
size_t length;
int index;
auto operator<=>(const queue_item &) const = default;
};
struct ModuleAutonamer
{
dict<Cell*, name_proposal> proposed_cell_names;
dict<Wire*, name_proposal> proposed_wire_names;
name_proposal best_name;
Module *module;
for (auto cell : module->selected_cells()) {
if (cell->name[0] == '$') {
// Cells in module order, then wires in the order that they're seen from cells.
// The index doubles as the tie-break between equally good
// proposals for different nodes
vector<node> nodes;
vector<int> decided;
std::priority_queue<queue_item, std::vector<queue_item>, std::greater<>> queue;
int renamed = 0;
ModuleAutonamer(Module *module) : module(module) { build_adjacency(); }
void build_adjacency()
{
vector<Cell*> cells(module->cells().begin(), module->cells().end());
// Arbitrary. Kinda just for passing tests without modifying them
for (auto cell : cells | std::views::reverse)
nodes.emplace_back().cell = cell;
int ncells = GetSize(nodes);
idict<Wire*> wire_ids;
for (int ci = 0; ci < ncells; ci++) {
Cell *cell = nodes[ci].cell;
for (auto &conn : cell->connections()) {
string suffix;
for (auto bit : conn.second)
if (bit.wire != nullptr && bit.wire->name[0] != '$') {
if (suffix.empty())
suffix = stringf("_%s_%s", cell->type.unescape(), conn.first.unescape());
name_proposal proposed_name(
bit.wire->name.str() + suffix,
cell->output(conn.first) ? 0 : wire_score.at(bit.wire)
);
if (!proposed_cell_names.count(cell) || proposed_name < proposed_cell_names.at(cell)) {
if (proposed_name < best_name)
best_name = proposed_name;
proposed_cell_names[cell] = proposed_name;
}
}
}
} else {
for (auto &conn : cell->connections()) {
string suffix;
for (auto bit : conn.second)
if (bit.wire != nullptr && bit.wire->name[0] == '$' && !bit.wire->port_id) {
if (suffix.empty())
suffix = stringf("_%s", conn.first.unescape());
name_proposal proposed_name(
cell->name.str() + suffix,
cell->output(conn.first) ? 0 : wire_score.at(bit.wire)
);
if (!proposed_wire_names.count(bit.wire) || proposed_name < proposed_wire_names.at(bit.wire)) {
if (proposed_name < best_name)
best_name = proposed_name;
proposed_wire_names[bit.wire] = proposed_name;
}
}
bool cell_is_output = cell->output(conn.first);
pool<Wire*> seen_in_this_port;
for (auto bit : conn.second) {
if (bit.wire == nullptr)
continue;
int wi = ncells + wire_ids(bit.wire);
if (wi == GetSize(nodes))
nodes.emplace_back().wire = bit.wire;
nodes[wi].fanout++;
if (!seen_in_this_port.insert(bit.wire).second)
continue;
Edge edge{
.port = conn.first,
.cell = ci,
.wire = wi,
.cell_is_output = cell_is_output,
.score = 0, // no fanouts are final yet
.pos_in_cell = GetSize(nodes[ci].edges),
.pos_in_wire = GetSize(nodes[wi].edges),
};
nodes[ci].edges.push_back(edge);
nodes[wi].edges.push_back(edge);
}
}
}
// Resolve selection before renaming
for (auto &nd : nodes) {
IdString name = nd.name();
nd.selected = nd.cell ? module->selected(nd.cell) : module->selected(nd.wire);
nd.is_public = (name[0] != '$');
nd.renameable = !nd.is_public && (nd.cell || nd.wire->port_id == 0);
if (nd.is_public)
nd.name_length = name.str().size();
}
// Only possible once every fanout is known
for (auto &nd : nodes)
for (auto &edge : nd.edges)
edge.score = edge.cell_is_output ? 0 : nodes[edge.wire].fanout;
}
void offer(int from, int to, const Edge &edge, int edge_pos)
{
node &nd = nodes[to];
if (!nd.renameable || nd.decided)
return;
string suffix = nd.cell
? stringf("_%s_%s", nd.cell->type.unescape(), edge.port.unescape())
: stringf("_%s", edge.port.unescape());
cost c{edge.score, nodes[from].name_length + suffix.length(), edge_pos};
if (c >= nd.c)
return;
nd.c = c;
nd.from_node = from;
nd.name_length = c.length;
nd.suffix = std::move(suffix);
queue.push(queue_item{c.score, c.length, to});
}
// Expand a public (or newly decided) node. The name it lends
// is already settled and the neighbour can keep what offer() built
void expand(int n)
{
const node &nd = nodes[n];
for (auto &edge : nd.edges)
if (nd.cell)
offer(n, edge.wire, edge, edge.pos_in_wire);
else
offer(n, edge.cell, edge, edge.pos_in_cell);
}
void decide()
{
for (int n = 0; n < GetSize(nodes); n++)
if (nodes[n].is_public)
expand(n);
while (!queue.empty()) {
int n = queue.top().index;
queue.pop();
if (nodes[n].decided)
continue;
nodes[n].decided = true;
decided.push_back(n);
expand(n);
}
}
int count = 0;
// compare against double best score for following comparisons so we don't
// pre-empt a future iteration
best_name.score *= 2;
for (auto &it : proposed_cell_names) {
if (best_name < it.second)
continue;
IdString n = module->uniquify(IdString(it.second.name));
log_debug("Rename cell %s in %s to %s.\n", it.first, module, n.unescape());
module->rename(it.first, n);
count++;
void append_name(int n, string &out)
{
const node &nd = nodes[n];
if (nd.is_public || nd.selected)
return nd.name().append_to(&out);
append_name(nd.from_node, out);
out += nd.suffix;
}
for (auto &it : proposed_wire_names) {
if (best_name < it.second)
continue;
IdString n = module->uniquify(IdString(it.second.name));
log_debug("Rename wire %s in %s to %s.\n", it.first, module, n.unescape());
module->rename(it.first, n);
count++;
void commit(int n)
{
node &nd = nodes[n];
if (!nd.selected)
return;
string full;
full.reserve(nd.name_length);
append_name(nd.from_node, full);
full += nd.suffix;
IdString name = module->uniquify(IdString(full));
if (nd.cell) {
log_debug("Rename cell %s in %s to %s.\n", nd.cell, module, name.unescape());
module->rename(nd.cell, name);
} else {
log_debug("Rename wire %s in %s to %s.\n", nd.wire, module, name.unescape());
module->rename(nd.wire, name);
}
renamed++;
}
return count;
}
void run()
{
decide();
for (int n : decided)
commit(n);
if (renamed > 0)
log("Renamed %d objects in module %s.\n", renamed, module);
}
};
struct AutonamePass : public Pass {
AutonamePass() : Pass("autoname", "automatically assign names to objects") { }
@ -135,24 +286,7 @@ struct AutonamePass : public Pass {
log_header(design, "Executing AUTONAME pass.\n");
for (auto module : design->selected_modules())
{
dict<Wire*, unsigned int> wire_score;
for (auto cell : module->selected_cells())
for (auto &conn : cell->connections())
for (auto bit : conn.second)
if (bit.wire != nullptr)
wire_score[bit.wire]++;
int count = 0, iter = 0;
while (1) {
iter++;
int n = autoname_worker(module, wire_score);
if (!n) break;
count += n;
}
if (count > 0)
log("Renamed %d objects in module %s (%d iterations).\n", count, module, iter);
}
ModuleAutonamer(module).run();
}
} AutonamePass;

View file

@ -78,12 +78,21 @@ module \top
end
end
EOT
design -save fanout_test
logger -expect log "Rename cell .name in top to bcd_.and_B" 1
logger -expect log "Rename cell .name2 in top to c_has_a_long_name_.or_B" 1
logger -expect log "Renamed 2 objects" 1
debug autoname
logger -check-expected
# a selection only limits what gets renamed, never the name that gets picked:
# \a's fanout still counts $name2, even though it isn't selected
design -load fanout_test
logger -expect log "Rename cell .name in top to bcd_.and_B" 1
logger -expect log "Renamed 1 objects" 1
debug autoname c:$name
logger -check-expected
# names are unique
design -reset
read_rtlil <<EOT
@ -177,29 +186,41 @@ end
EOT
design -save order_test
# don't rename prematurely (some objects should be named after $name2)
# wires are named for being cell outputs
logger -expect log "Rename wire .d in top to or_Y" 1
logger -expect log "Rename cell .name2 in top to or_Y_.or_B" 1
logger -expect log "Renamed 2 objects" 1
debug autoname t:$or
logger -check-expected
logger -expect log "Rename wire .e in top to or_Y_.or_B_Y" 1
# $name gets shortest name (otherwise bcd_$__unknown_B)
logger -expect log "Rename cell .name in top to a_.__unknown_A" 1
# another output wire
logger -expect log "Rename wire .e in top to or_Y_.or_B_Y" 1
logger -expect log "Renamed 4 objects" 1
debug autoname
logger -check-expected
# don't rename prematurely (some objects should be named after $name2)
design -load order_test
# $name3 named for lowest fanout wire (otherwise a_$__unknown_A_Y_$and_A)
logger -expect log "Rename cell .name3 in top to or_Y_.or_B_Y_.and_B" 1
# $c gets shortest name, since the cell driving it doesn't have known port
# directions (otherwise a_$__unknown_A_Y)
logger -expect log "Rename wire .c in top to or_Y_.or_B_A" 1
# $name3 named for lowest fanout wire (otherwise a_$__unknown_A_Y_$and_A)
logger -expect log "Rename cell .name3 in top to or_Y_.or_B_Y_.and_B" 1
logger -expect log "Renamed 6 objects" 1
debug autoname
logger -check-expected
# Only selected objects are renamed, but each one gets exactly the name the
# unrestricted run above gave it, whatever the selection.
design -load order_test
logger -expect log "Rename cell .name2 in top to or_Y_.or_B" 1
logger -expect log "Renamed 1 objects" 1
debug autoname t:$or
logger -check-expected
design -load order_test
logger -expect log "Rename wire .d in top to or_Y" 1
logger -expect log "Rename wire .e in top to or_Y_.or_B_Y" 1
logger -expect log "Rename wire .c in top to or_Y_.or_B_A" 1
logger -expect log "Renamed 3 objects" 1
debug autoname w:*
logger -check-expected
# $name3 is named after a chain of objects that all keep their $-names
design -load order_test
logger -expect log "Rename cell .name3 in top to or_Y_.or_B_Y_.and_B" 1
logger -expect log "Renamed 1 objects" 1
debug autoname c:$name3
logger -check-expected