diff --git a/passes/cmds/autoname.cc b/passes/cmds/autoname.cc index 6994fdefd..134d36ee6 100644 --- a/passes/cmds/autoname.cc +++ b/passes/cmds/autoname.cc @@ -17,95 +17,246 @@ * */ + #include "kernel/yosys.h" +#include +#include +#include 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_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 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 proposed_cell_names; - dict 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 nodes; + vector decided; + std::priority_queue, std::greater<>> queue; + int renamed = 0; + + ModuleAutonamer(Module *module) : module(module) { build_adjacency(); } + + void build_adjacency() + { + vector 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_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 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_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; diff --git a/tests/various/autoname.ys b/tests/various/autoname.ys index 7df1571b2..22b532f2c 100644 --- a/tests/various/autoname.ys +++ b/tests/various/autoname.ys @@ -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 <