mirror of
https://github.com/YosysHQ/yosys
synced 2026-07-25 00:22:34 +00:00
autoname: rewrite
This commit is contained in:
parent
9914b6fa25
commit
fb0bb160ad
2 changed files with 216 additions and 94 deletions
|
|
@ -17,95 +17,233 @@
|
|||
*
|
||||
*/
|
||||
|
||||
|
||||
#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;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
// 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()
|
||||
{
|
||||
auto selected = module->selected_cells();
|
||||
// Arbitrary. Kinda just for passing tests without modifying them.
|
||||
for (auto cell : selected | 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &nd : nodes) {
|
||||
IdString name = nd.cell ? nd.cell->name : nd.wire->name;
|
||||
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 n, const Edge &edge, int edge_pos)
|
||||
{
|
||||
node &nd = nodes[n];
|
||||
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, n});
|
||||
}
|
||||
|
||||
// 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++;
|
||||
// The source neighbour is decided before this node, so commit() reaches it
|
||||
// first and its name is already final -- uniquify() suffix and all.
|
||||
void commit(int n)
|
||||
{
|
||||
node &nd = nodes[n];
|
||||
const node &src = nodes[nd.from_node];
|
||||
string full;
|
||||
full.reserve(nd.name_length);
|
||||
(src.cell ? src.cell->name : src.wire->name).append_to(&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++;
|
||||
}
|
||||
|
||||
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 run()
|
||||
{
|
||||
decide();
|
||||
for (int n : decided)
|
||||
commit(n);
|
||||
if (renamed > 0)
|
||||
log("Renamed %d objects in module %s.\n", renamed, module);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
struct AutonamePass : public Pass {
|
||||
AutonamePass() : Pass("autoname", "automatically assign names to objects") { }
|
||||
|
|
@ -135,24 +273,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -180,15 +180,16 @@ design -save order_test
|
|||
# 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
|
||||
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 4 objects" 1
|
||||
debug autoname t:$or
|
||||
logger -check-expected
|
||||
|
||||
# $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
|
||||
logger -expect log "Rename cell .name3 in top to or_Y_.or_B_Y_.and_B" 1
|
||||
logger -expect log "Renamed 2 objects" 1
|
||||
debug autoname
|
||||
logger -check-expected
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue