3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-12-23 04:33:46 +00:00
yosys/passes/techmap/bufnorm.cc
Robert O'Callahan 46cb05c471 Pass IdString by value instead of by const reference.
When IdString refcounting was expensive, it made sense to pass it by const reference
instead of by value, to avoid refcount churn. Now that IdString is not refcounted,
it's slightly more efficient to pass it by value.
2025-12-22 01:52:59 +00:00

524 lines
15 KiB
C++

/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "kernel/yosys.h"
#include "kernel/sigtools.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct BufnormPass : public Pass {
BufnormPass() : Pass("bufnorm", "(experimental) convert design into buffered-normalized form") {
experimental();
}
void help() override
{
log("\n");
log(" bufnorm [options] [selection]\n");
log("\n");
log("Insert buffer cells into the design as needed, to make sure that each wire\n");
log("has exactly one driving cell port, and aliasing wires are buffered using\n");
log("buffer cells, than can be chained in a canonical order.\n");
log("\n");
log("Running 'bufnorm' on the whole design enters 'buffered-normalized mode'.\n");
log("\n");
log(" -buf\n");
log(" Create $buf cells for all buffers. The default is to use $_BUF_ cells\n");
log(" for sigle-bit buffers and $buf cells only for multi-bit buffers.\n");
log("\n");
log(" -chain\n");
log(" Chain all alias wires. By default only wires with positive-valued\n");
log(" 'chain' or 'keep' attribute on them are chained.\n");
log("\n");
log(" -output\n");
log(" Enable chaining of ouput ports wires.\n");
log("\n");
log(" -public\n");
log(" Enable chaining of wires wth public names.\n");
log("\n");
log(" -nochain\n");
log(" Disable chaining of wires with 'chain' attribute.\n");
log("\n");
log(" -nokeep\n");
log(" Disable chaining of wires with 'keep' attribute.\n");
log("\n");
log(" -flat\n");
log(" Alias for -nokeep and -nochain.\n");
log("\n");
log(" -nosticky\n");
log(" Disable 'sticky' behavior of output ports already driving whole\n");
log(" wires, and always enforce canonical sort order instead.\n");
log("\n");
log(" -alphasort\n");
log(" Strictly use alphanumeric sort for chain-order. (Default is\n");
log(" to chain 'keep' wires first, then ports in declaration order,\n");
log(" and then the other wires in alphanumeric sort order.)\n");
log("\n");
// log(" -noinit\n");
// log(" Do not move 'init' attributes to the wires on FF output ports.\n");
// log("\n");
log("Run 'bufnorm' with -pos, -bits, or -conn on the whole design to remove all\n");
log("$buf buffer cells and exit 'buffered-normalized mode' again.\n");
log("\n");
log(" -pos\n");
log(" Create (multi- and single-bit) $pos cells instead $buf and $_BUF_.\n");
log("\n");
log(" -bits\n");
log(" Create arrays of $_BUF_ cells instead of multi-bit $buf cells.\n");
log("\n");
log(" -conn\n");
log(" Create 'direct connections' instead of buffer cells.\n");
log("\n");
log(" -nomode\n");
log(" Do not automatically enter or leave 'buffered-normalized mode'.\n");
log("\n");
log("The 'bufnorm' command can also be used to just switch in and out of\n");
log("'buffered-normalized mode' and run the low-level re-normalizer.\n");
log("\n");
log(" -update\n");
log(" Enter 'buffered-normalized mode' and (re-)normalize.\n");
log("\n");
log(" -reset\n");
log(" Leave 'buffered-normalized mode' without changing the netlist.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
bool buf_mode = false;
bool chain_mode = false;
bool output_mode = false;
bool public_mode = false;
bool nochain_mode = false;
bool nokeep_mode = false;
bool nosticky_mode = false;
bool alphasort_mode = false;
// bool noinit_mode = false; // FIXME: Actually move init attributes
bool nomode_mode = false;
bool pos_mode = false;
bool bits_mode = false;
bool conn_mode = false;
bool update_mode = false;
bool reset_mode = false;
bool got_non_update_reset_opt = false;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
std::string arg = args[argidx];
if (arg == "-buf") {
buf_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-chain") {
chain_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-output") {
output_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-public") {
public_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-nochain") {
nochain_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-nokeep") {
nokeep_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-flat") {
nochain_mode = true;
nokeep_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-nosticky") {
nosticky_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-alphasort") {
alphasort_mode = true;
got_non_update_reset_opt = true;
continue;
}
// if (arg == "-noinit") {
// noinit_mode = true;
// got_non_update_reset_opt = true;
// continue;
// }
if (arg == "-pos") {
pos_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-bits") {
bits_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-conn") {
conn_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-nomode") {
nomode_mode = true;
got_non_update_reset_opt = true;
continue;
}
if (arg == "-update") {
update_mode = true;
continue;
}
if (arg == "-reset") {
reset_mode = true;
continue;
}
break;
}
extra_args(args, argidx, design);
if (buf_mode && pos_mode)
log_cmd_error("Options -buf and -pos are exclusive.\n");
if (buf_mode && conn_mode)
log_cmd_error("Options -buf and -conn are exclusive.\n");
if (pos_mode && conn_mode)
log_cmd_error("Options -pos and -conn are exclusive.\n");
if (update_mode && reset_mode)
log_cmd_error("Options -update and -reset are exclusive.\n");
if (update_mode && got_non_update_reset_opt)
log_cmd_error("Option -update can't be mixed with other options.\n");
if (reset_mode && got_non_update_reset_opt)
log_cmd_error("Option -reset can't be mixed with other options.\n");
if (update_mode) {
design->bufNormalize();
return;
}
if (reset_mode) {
design->bufNormalize(false);
return;
}
log_header(design, "Executing BUFNORM pass (convert to buffer-normalized form).\n");
int count_removed_buffers = 0;
int count_updated_buffers = 0;
int count_kept_buffers = 0;
int count_created_buffers = 0;
int count_updated_cellports = 0;
if (!nomode_mode && (pos_mode || bits_mode || conn_mode)) {
if (design->selection().full_selection)
design->bufNormalize(false);
}
for (auto module : design->selected_modules())
{
log("Buffer-normalizing module %s.\n", log_id(module));
SigMap sigmap(module);
module->new_connections({});
dict<pair<IdString, SigSpec>, Cell*> old_buffers;
{
vector<Cell*> old_dup_buffers;
for (auto cell : module->cells())
{
if (!cell->type.in(ID($buf), ID($_BUF_)))
continue;
SigSpec insig = cell->getPort(ID::A);
SigSpec outsig = cell->getPort(ID::Y);
for (int i = 0; i < GetSize(insig) && i < GetSize(outsig); i++)
sigmap.add(insig[i], outsig[i]);
pair<IdString,Wire*> key(cell->type, outsig.as_wire());
if (old_buffers.count(key))
old_dup_buffers.push_back(cell);
else
old_buffers[key] = cell;
}
for (auto cell : old_dup_buffers)
module->remove(cell);
count_removed_buffers += GetSize(old_dup_buffers);
}
dict<SigBit, pool<Wire*>> bit2wires;
dict<SigSpec, pool<Wire*>> whole_wires;
dict<SigBit, SigBit> mapped_bits;
pool<Wire*> unmapped_wires;
for (auto wire : module->wires())
{
SigSpec keysig = sigmap(wire);
whole_wires[keysig].insert(wire);
for (auto keybit : sigmap(wire))
bit2wires[keybit].insert(wire);
if (wire->port_input) {
log(" primary input: %s\n", log_id(wire));
for (auto bit : SigSpec(wire))
mapped_bits[sigmap(bit)] = bit;
} else {
unmapped_wires.insert(wire);
}
}
auto chain_this_wire_f = [&](Wire *wire)
{
if (chain_mode)
return true;
if (output_mode && wire->port_output)
return true;
if (public_mode && wire->name.isPublic())
return true;
if (!nokeep_mode && wire->get_bool_attribute(ID::keep))
return true;
if (!nochain_mode && wire->get_bool_attribute(ID::chain))
return true;
return false;
};
auto compare_wires_f = [&](Wire *a, Wire *b)
{
// Chaining wires first, then flat wires
bool chain_a = chain_this_wire_f(a);
bool chain_b = chain_this_wire_f(b);
if (chain_a != chain_b) return chain_a;
if (!alphasort_mode)
{
// Wires with 'chain' attribute first, high values before low values
if (!nochain_mode) {
int chain_a_val = a->attributes.at(ID::chain, Const(0)).as_int();
int chain_b_val = b->attributes.at(ID::chain, Const(0)).as_int();
if (chain_a_val != chain_b_val) return chain_a_val > chain_b_val;
}
// Then wires with 'keep' attribute
if (!nokeep_mode) {
bool keep_a = a->get_bool_attribute(ID::keep);
bool keep_b = b->get_bool_attribute(ID::keep);
if (keep_a != keep_b) return keep_a;
}
// Ports before non-ports
if ((a->port_id != 0) != (b->port_id != 0))
return a->port_id != 0;
// Ports in declaration order
if (a->port_id != b->port_id)
return a->port_id < b->port_id;
}
// Nets with public names first
if (a->name.isPublic() != b->name.isPublic())
return a->name.isPublic();
// Otherwise just sort by name alphanumerically
return a->name.str() < b->name.str();
};
for (auto cell : module->cells())
{
if (cell->type.in(ID($buf), ID($_BUF_)))
continue;
for (auto &conn : cell->connections())
{
if (!cell->output(conn.first))
continue;
Wire *w = nullptr;
if (!nosticky_mode && conn.second.is_wire())
w = conn.second.as_wire();
if (w == nullptr)
{
SigSpec keysig = sigmap(conn.second);
auto it = whole_wires.find(keysig);
if (it != whole_wires.end()) {
it->second.sort(compare_wires_f);
w = *(it->second.begin());
} else {
w = module->addWire(NEW_ID, GetSize(conn.second));
for (int i = 0; i < GetSize(w); i++)
sigmap.add(SigBit(w, i), keysig[i]);
}
}
if (w->name.isPublic())
log(" directly driven by cell %s port %s: %s\n",
log_id(cell), log_id(conn.first), log_id(w));
for (auto bit : SigSpec(w))
mapped_bits[sigmap(bit)] = bit;
unmapped_wires.erase(w);
cell->setPort(conn.first, w);
}
}
pool<Cell*> added_buffers;
const auto lookup_mapping = [&mapped_bits](const SigBit bit, bool default_sx = false)
{
if (!bit.is_wire())
return bit;
if (default_sx)
return mapped_bits.at(bit, State::Sx);
return mapped_bits.at(bit);
};
auto make_buffer_f = [&](IdString type, const SigSpec &src, const SigSpec &dst)
{
auto it = old_buffers.find(pair<IdString, SigSpec>(type, dst));
if (it != old_buffers.end())
{
Cell *cell = it->second;
old_buffers.erase(it);
added_buffers.insert(cell);
if (cell->getPort(ID::A) == src) {
count_kept_buffers++;
} else {
cell->setPort(ID::A, src);
count_updated_buffers++;
}
return;
}
Cell *cell = module->addCell(NEW_ID, type);
added_buffers.insert(cell);
cell->setPort(ID::A, src);
cell->setPort(ID::Y, dst);
cell->fixup_parameters();
count_created_buffers++;
};
unmapped_wires.sort(compare_wires_f);
for (auto wire : unmapped_wires)
{
bool chain_this_wire = chain_this_wire_f(wire);
SigSpec keysig = sigmap(wire), insig = wire, outsig = wire;
for (int i = 0; i < GetSize(insig); i++)
insig[i] = lookup_mapping(keysig[i], true);
if (chain_this_wire) {
for (int i = 0; i < GetSize(outsig); i++)
mapped_bits[keysig[i]] = outsig[i];
}
log(" %s %s for %s -> %s\n",
chain_this_wire ? "chaining" : "adding",
conn_mode ? "connection" : "buffer",
log_signal(insig), log_signal(outsig));
if (conn_mode) {
if (bits_mode) {
for (int i = 0; i < GetSize(insig) && i < GetSize(outsig); i++)
module->connect(outsig[i], insig[i]);
} else {
module->connect(outsig, insig);
}
} else {
if (bits_mode) {
IdString celltype = pos_mode ? ID($pos) : buf_mode ? ID($buf) : ID($_BUF_);
for (int i = 0; i < GetSize(insig) && i < GetSize(outsig); i++)
make_buffer_f(celltype, insig[i], outsig[i]);
} else {
IdString celltype = pos_mode ? ID($pos) : buf_mode ? ID($buf) :
GetSize(outsig) == 1 ? ID($_BUF_) : ID($buf);
make_buffer_f(celltype, insig, outsig);
}
}
}
for (auto &it : old_buffers)
module->remove(it.second);
count_removed_buffers += GetSize(old_buffers);
for (auto cell : module->cells())
{
if (added_buffers.count(cell))
continue;
for (auto &conn : cell->connections())
{
if (cell->output(conn.first))
continue;
SigSpec newsig = conn.second;
for (auto &bit : newsig)
bit = lookup_mapping(sigmap(bit));
if (conn.second != newsig) {
log(" fixing input signal on cell %s port %s: %s\n",
log_id(cell), log_id(conn.first), log_signal(newsig));
cell->setPort(conn.first, newsig);
count_updated_cellports++;
}
}
}
}
log("Summary: removed %d, updated %d, kept %d, and created %d buffers, and updated %d cell ports.\n",
count_removed_buffers, count_updated_buffers, count_kept_buffers,
count_created_buffers, count_updated_cellports);
if (!nomode_mode && !(pos_mode || bits_mode || conn_mode)) {
if (design->selection().full_selection)
design->bufNormalize(true);
}
}
} BufnormPass;
PRIVATE_NAMESPACE_END