3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-07-24 21:27:00 +00:00
This commit is contained in:
Akash Levy 2025-02-14 08:48:27 -08:00
parent f76fd9280b
commit fd811ddaee
23 changed files with 39 additions and 126 deletions

View file

@ -0,0 +1,11 @@
OBJS += passes/silimate/activity.o
OBJS += passes/silimate/annotate_cell_fanout.o
OBJS += passes/silimate/annotate_logic_depth.o
OBJS += passes/silimate/breaksop.o
OBJS += passes/silimate/bus_rebuild.o
OBJS += passes/silimate/longloop_select.o
OBJS += passes/silimate/opt_balance_tree.o
OBJS += passes/silimate/selectconst.o
OBJS += passes/silimate/splitfanout.o
OBJS += passes/silimate/splitnetlist.o

230
passes/silimate/activity.cc Normal file
View file

@ -0,0 +1,230 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* 2024 Alain Dargelas <alain@silimate.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 ActivityProp {
Module *module;
SigMap sigmap;
uint32_t nbBitsWithActivity = 0;
uint32_t wireCount = 0;
// Split a string based on separator, returns a vector of tokens as reference argument
// If skipEmpty is true, return "" for string " ", when separator is " "
void tokenize(std::string_view str, std::string_view separator, std::vector<std::string> &result, bool skipEmpty)
{
std::string::size_type pos{0};
const auto sepSize = separator.size();
const auto stringSize = str.size();
std::string tmp;
std::string::size_type n = str.find(separator, pos);
while (n != std::string::npos) {
tmp = str.substr(pos, n - pos);
if (!(tmp.empty() && skipEmpty))
result.push_back(tmp);
pos = n + sepSize;
n = str.find(separator, pos);
}
if (pos < stringSize) { // put last part
tmp = str.substr(pos, stringSize - pos);
if (!(tmp.empty() && skipEmpty))
result.push_back(tmp);
}
}
// Split a string based on separator, returns a vector of tokens
// If skipEmpty is true, return "" for string " ", when separator is " "
std::vector<std::string> tokenize(std::string_view str, std::string_view separator, bool skipEmpty)
{
std::vector<std::string> result;
tokenize(str, separator, result, skipEmpty);
return result;
}
ActivityProp(Module *module) : module(module), sigmap(module)
{
std::map<SigBit, std::string> ActivityMap;
std::map<SigBit, std::string> DutyMap;
// Build {signal bit - activity} map from the wire activities calculated in the sim pass
bool debug = false;
if (std::getenv("DEBUG_ACTIVITY_PROP")) {
debug = true;
}
int localActivity = 0;
for (Wire *wire : module->wires()) {
wireCount++;
SigSpec sig(sigmap(wire));
// Retrieve the activity/dutycycle attributes created in the sim pass, attached to wires, in the form:
// $ACKT: 0.1 0.2 .... (Each bit in a bus has its own activity, index 0 of the bus is left most)
std::string act = wire->get_string_attribute("$ACKT");
std::string duty = wire->get_string_attribute("$DUTY");
//std::cout << "WIRE: " << wire->name.c_str() << " act: " << act << std::endl;
// Split the activity lists
std::vector<std::string> activities = tokenize(act, " ", true);
std::vector<std::string> duties = tokenize(duty, " ", true);
// Assign them to each SigBit (1 signal bit)
for (uint32_t i = 0; i < (uint32_t)GetSize(sig); i++) {
SigBit bit(sig[i]);
if (i < activities.size()) {
ActivityMap.emplace(bit, activities[i]);
DutyMap.emplace(bit, duties[i]);
nbBitsWithActivity++;
localActivity++;
if (debug)
log("Found activity for module: %s, wire: %s, wire_size: %d, activity: %s\n", module->name.c_str(), wire->name.c_str(), GetSize(sig), act.c_str());
} else {
if (debug)
log_warning("Zeroing out activity for module: %s, wire: %s, wire_size: %d, activ_size: %ld\n",
module->name.c_str(), wire->name.c_str(), GetSize(sig), activities.size());
ActivityMap.emplace(bit, "0.0");
DutyMap.emplace(bit, "0.0");
}
}
}
if (localActivity)
log("Collected %d bits with activity in module %s out of %d wires\n", localActivity, module->name.c_str(), wireCount);
// Attach port activity to cell using sigmap
for (auto cell : module->cells()) {
std::string cell_ports_activity;
std::string cell_ports_duty;
for (auto conn : cell->connections()) {
// Recombine individual bit activities for all cell ports into a list attached to the cell
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit(sigmap(conn.second[i]));
std::string port_name = std::string(conn.first.c_str()) + "[" + std::to_string(i) + "]";
{
std::map<SigBit, std::string>::iterator itr = ActivityMap.find(bit);
if (itr != ActivityMap.end()) {
cell_ports_activity += port_name + "=" + (*itr).second + " ";
} else {
RTLIL::SigSpec sigspec(bit);
if (!sigspec.is_fully_const()) {
log_warning("No activity found for : %s/%s/%s\n", module->name.c_str(), cell->name.c_str(), port_name.c_str());
}
// constants have no activity
cell_ports_activity += port_name + "=" + "0.0 ";
}
}
{
std::map<SigBit, std::string>::iterator itr = DutyMap.find(bit);
if (itr != DutyMap.end()) {
cell_ports_duty += port_name + "=" + (*itr).second + " ";
} else {
RTLIL::SigSpec sigspec(bit);
if (!sigspec.is_fully_const()) {
log_warning("No dutycycle found for : %s/%s/%s\n", module->name.c_str(), cell->name.c_str(), port_name.c_str());
}
// constant 1 has duty cycle 1, constant 0 has duty cycle 0
cell_ports_duty += port_name + "=" + (sigspec.as_bool() ? "1.0" : "0.0") + " ";
}
}
}
}
// Annotate on cells the complete list of ports activities and dutycycles in the form:
// $ACKT: \P1=0.1 \P2=0.2 ....
cell->set_string_attribute("$ACKT:", cell_ports_activity);
cell->set_string_attribute("$DUTY:", cell_ports_duty);
}
}
uint32_t getNbBitsWithActivity() { return nbBitsWithActivity; }
uint32_t getWireCount() { return wireCount; }
};
struct ActivityClear {
Module *module;
ActivityClear(Module *module) : module(module)
{
for (Wire *wire : module->wires()) {
wire->set_string_attribute("$ACKT", "");
wire->set_string_attribute("$DUTY", "");
}
for (auto cell : module->cells()) {
cell->set_string_attribute("$ACKT:", "");
cell->set_string_attribute("$DUTY:", "");
}
}
};
struct ActivityPropPass : public Pass {
ActivityPropPass() : Pass("activity_prop", "Attaches wire activity to cell ports") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" activity_prop\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing Activity propagation pass\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
// No options currently. When adding in the future make sure to update docstring with [options]
break;
}
extra_args(args, argidx, design);
uint32_t totalNbBitsWithActivity = 0;
uint32_t wireCount = 0;
for (auto module : design->selected_modules()) {
ActivityProp worker(module);
totalNbBitsWithActivity += worker.getNbBitsWithActivity();
wireCount += worker.getWireCount();
}
log("Collected %d bits in total with activity out of %d wires\n", totalNbBitsWithActivity, wireCount);
log_flush();
}
} ActivityPropPass;
struct ActivityClearPass : public Pass {
ActivityClearPass() : Pass("activity_clear", "Clears activity attached to cells and wires") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" activity_clear\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing Activity clearing pass\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
// No options currently. When adding in the future make sure to update docstring with [options]
break;
}
extra_args(args, argidx, design);
for (auto module : design->selected_modules()) {
ActivityClear worker(module);
}
}
} ActivityClearPass;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,152 @@
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
#include <set>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
// Signal cell driver(s), precompute a cell output signal to a cell map
void sigCellDrivers(RTLIL::Design *design, dict<RTLIL::SigSpec, std::set<Cell *>> &sig2CellsInFanout,
dict<RTLIL::SigSpec, std::set<Cell *>> &sig2CellsInFanin)
{
for (auto module : design->selected_modules()) {
SigMap sigmap(module);
for (auto cell : module->selected_cells()) {
for (auto &conn : cell->connections()) {
IdString portName = conn.first;
RTLIL::SigSpec actual = conn.second;
if (cell->output(portName)) {
sig2CellsInFanin[actual].insert(cell);
for (int i = 0; i < actual.size(); i++) {
SigSpec bit_sig = actual.extract(i, 1);
sig2CellsInFanin[sigmap(bit_sig)].insert(cell);
}
} else {
sig2CellsInFanout[sigmap(actual)].insert(cell);
for (int i = 0; i < actual.size(); i++) {
SigSpec bit_sig = actual.extract(i, 1);
if (!bit_sig.is_fully_const()) {
sig2CellsInFanout[sigmap(bit_sig)].insert(cell);
}
}
}
}
}
}
}
// Assign statements fanin, fanout, traces the lhs2rhs and rhs2lhs sigspecs and precompute maps
void lhs2rhs_rhs2lhs(RTLIL::Design *design, dict<RTLIL::SigSpec, std::set<RTLIL::SigSpec>> &rhsSig2LhsSig,
dict<RTLIL::SigSpec, RTLIL::SigSpec> &lhsSig2rhsSig)
{
for (auto module : design->selected_modules()) {
SigMap sigmap(module);
for (auto it = module->connections().begin(); it != module->connections().end(); ++it) {
RTLIL::SigSpec lhs = it->first;
RTLIL::SigSpec rhs = it->second;
std::vector<SigSpec> lhsBits;
for (int i = 0; i < lhs.size(); i++) {
SigSpec bit_sig = lhs.extract(i, 1);
lhsBits.push_back(bit_sig);
}
std::vector<SigSpec> rhsBits;
for (int i = 0; i < rhs.size(); i++) {
SigSpec bit_sig = rhs.extract(i, 1);
if (bit_sig.is_fully_const()) {
continue;
}
rhsBits.push_back(bit_sig);
}
for (uint32_t i = 0; i < lhsBits.size(); i++) {
if (i < rhsBits.size()) {
rhsSig2LhsSig[sigmap(rhsBits[i])].insert(sigmap(lhsBits[i]));
lhsSig2rhsSig[sigmap(lhsBits[i])] = sigmap(rhsBits[i]);
}
}
}
}
}
struct AnnotateCellFanout : public ScriptPass {
AnnotateCellFanout() : ScriptPass("annotate_cell_fanout", "Annotate the cell fanout on the cell") {}
void script() override {}
void execute(std::vector<std::string>, RTLIL::Design *design) override
{
if (design == nullptr) {
log_error("No design object");
return;
}
log("Running annotate_cell_fanout pass\n");
log_flush();
// Precompute cell output sigspec to cell map
dict<RTLIL::SigSpec, std::set<Cell *>> sig2CellsInFanin;
dict<RTLIL::SigSpec, std::set<Cell *>> sig2CellsInFanout;
sigCellDrivers(design, sig2CellsInFanout, sig2CellsInFanin);
// Precompute lhs2rhs and rhs2lhs sigspec map
dict<RTLIL::SigSpec, RTLIL::SigSpec> lhsSig2RhsSig;
dict<RTLIL::SigSpec, std::set<RTLIL::SigSpec>> rhsSig2LhsSig;
lhs2rhs_rhs2lhs(design, rhsSig2LhsSig, lhsSig2RhsSig);
// Accumulate fanout from cell connections
dict<RTLIL::SigSpec, int> sigFanout;
for (auto itrSig : sig2CellsInFanout) {
SigSpec sigspec = itrSig.first;
std::set<Cell *> &cells = itrSig.second;
sigFanout[sigspec] = cells.size();
}
// Accumulate fanout from assign stmts connections
for (auto itrSig : rhsSig2LhsSig) {
SigSpec sigspec = itrSig.first;
std::set<RTLIL::SigSpec> &fanout = itrSig.second;
if (sigFanout.count(sigspec)) {
sigFanout[sigspec] += fanout.size();
} else {
sigFanout[sigspec] = fanout.size();
}
}
// Collect max fanout from all the output bits of a cell
dict<Cell *, int> cellFanout;
for (auto itrSig : sigFanout) {
SigSpec sigspec = itrSig.first;
int fanout = itrSig.second;
std::set<Cell *> &cells = sig2CellsInFanin[sigspec];
for (Cell *cell : cells) {
if (cellFanout.count(cell)) {
cellFanout[cell] = std::max(fanout, cellFanout[cell]);
} else {
cellFanout[cell] = fanout;
}
}
}
// Find cells with no fanout info (connected to output ports, or not connected)
std::set<Cell *> noFanoutInfo;
for (auto module : design->selected_modules()) {
for (auto cell : module->selected_cells()) {
if (!cellFanout.count(cell)) {
noFanoutInfo.insert(cell);
}
}
}
// Set those cells to fanout 1
for (auto cell : noFanoutInfo) {
cellFanout[cell] = 1;
}
// Add attribute with fanout info to every cell
for (auto itrCell : cellFanout) {
Cell *cell = itrCell.first;
int fanout = itrCell.second;
cell->set_string_attribute("$FANOUT", std::to_string(fanout));
}
log("End annotate_cell_fanout pass\n");
log_flush();
}
} SplitNetlist;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,130 @@
/*
* 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/celltypes.h"
#include "kernel/sigtools.h"
#include "kernel/utils.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
typedef RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell> cell_ptr_cmp;
struct AnnotateLogicDepth : public ScriptPass {
AnnotateLogicDepth() : ScriptPass("annotate_logic_depth", "Annotate logic depth per cells and modules") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" annotate_logic_depth [-module] [-cell]\n");
log("\n");
}
void script() override {}
// Adapted from the torder pass
void toposorting(std::vector<Cell *> &cells, SigMap &sigmap,
TopoSort<RTLIL::Cell *, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> &toposort)
{
dict<SigBit, pool<Cell *>> bit_drivers, bit_users;
for (Cell *cell : cells) {
for (auto conn : cell->connections()) {
bool noautostop = false;
if (!noautostop && yosys_celltypes.cell_known(cell->type)) {
if (conn.first.in(ID::Q, ID::CTRL_OUT, ID::RD_DATA))
continue;
if (cell->type.in(ID($memrd), ID($memrd_v2)) && conn.first == ID::DATA)
continue;
}
if (cell->input(conn.first))
for (auto bit : sigmap(conn.second))
bit_users[bit].insert(cell);
if (cell->output(conn.first))
for (auto bit : sigmap(conn.second))
bit_drivers[bit].insert(cell);
toposort.node(cell);
}
}
for (auto &it : bit_users)
if (bit_drivers.count(it.first))
for (auto driver_cell : bit_drivers.at(it.first))
for (auto user_cell : it.second)
toposort.edge(driver_cell, user_cell);
toposort.analyze_loops = false;
toposort.sort();
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
if (design == nullptr) {
log_error("No design object");
return;
}
size_t argidx;
bool annotate_modules = false;
bool annotate_cells = false;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-module") {
annotate_modules = true;
} else if (args[argidx] == "-cell") {
annotate_cells = true;
} else {
break;
}
}
extra_args(args, argidx, design);
int design_max_depth = 0;
for (auto module : design->selected_modules()) {
int module_max_depth = 0;
SigMap sigmap(module);
std::vector<Cell *> cells;
std::map<RTLIL::Cell *, int> celllevel;
for (auto cell : module->selected_cells()) {
cells.push_back(cell);
celllevel.emplace(cell, 0);
}
TopoSort<RTLIL::Cell *, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> toposort;
toposorting(cells, sigmap, toposort);
std::map<RTLIL::Cell *, std::set<RTLIL::Cell *, cell_ptr_cmp>, cell_ptr_cmp> topo_cell_drivers = toposort.get_database();
for (auto cell : toposort.sorted) {
int level = 0;
auto itrAdj = topo_cell_drivers.find(cell);
for (auto c : (*itrAdj).second) {
level = std::max(level, celllevel[c]);
}
level++;
celllevel[cell] = level;
module_max_depth = std::max(module_max_depth, level);
if (annotate_cells)
cell->set_string_attribute("$DEPTH", std::to_string(level));
}
design_max_depth = std::max(design_max_depth, module_max_depth);
if (annotate_modules)
module->set_string_attribute("$MAX_DEPTH", std::to_string(module_max_depth));
}
design->top_module()->set_string_attribute("$DESIGN_MAX_DEPTH", std::to_string(design_max_depth));
log("Max logic depth: %d\n", design_max_depth);
}
} AnnotateLogicDepth;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,96 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2017 Robert Ou <rqou@robertou.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 BreakSopPass : public Pass {
BreakSopPass() : Pass("breaksop", "break $sop cells into $reduce_and/$reduce_or cells") { }
void help() override
{
log("\n");
log(" breaksop [selection]\n");
log("\n");
log("Break $sop cells into $reduce_and/$reduce_or cells.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing BREAKSOP pass (break $sop cells into $reduce_and/$reduce_or cells).\n");
extra_args(args, 1, design);
for (auto module : design->selected_modules())
{
// Data structures
pool<Cell*> cells_to_remove;
SigMap sigmap(module);
// Process $sop cells
for (auto cell : module->selected_cells())
{
if (cell->type == ID($sop))
{
// Read the inputs/outputs/parameters of the $sop cell
auto sop_inputs = sigmap(cell->getPort(ID::A));
auto sop_output = sigmap(cell->getPort(ID::Y))[0];
auto sop_depth = cell->getParam(ID::DEPTH).as_int();
auto sop_width = cell->getParam(ID::WIDTH).as_int();
auto sop_table = cell->getParam(ID::TABLE);
// Get $sop output wire name
module->rename(cell->name, module->uniquify(sop_output.wire->name.str() + "_sop"));
// Construct $reduce_and cells
pool<SigBit> intermed_wires;
for (int i = 0; i < sop_depth; i++) {
// Wire for the output
auto and_out = module->addWire(NEW_ID2_SUFFIX("andterm_out"));
intermed_wires.insert(and_out);
// Signals for the inputs
pool<SigBit> and_in;
for (int j = 0; j < sop_width; j++)
if (sop_table[2 * (i * sop_width + j) + 0])
and_in.insert(module->Not(NEW_ID2_SUFFIX(stringf("sop_in_%d_comp", j)), sop_inputs[j], false, cell->get_src_attribute()));
else if (sop_table[2 * (i * sop_width + j) + 1])
and_in.insert(sop_inputs[j]);
// Construct the cell
module->addReduceAnd(NEW_ID2_SUFFIX("andterm"), and_in, and_out, false, cell->get_src_attribute());
}
// Construct the $reduce_or cell
module->addReduceOr(NEW_ID2_SUFFIX("orterm"), intermed_wires, sop_output, false, cell->get_src_attribute());
// Mark the $sop cell for removal
cells_to_remove.insert(cell);
}
}
// Perform removal of $sop cells
for (auto cell : cells_to_remove)
module->remove(cell);
}
}
} BreakSopPass;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,327 @@
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
#include <set>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct bus_rebuild : public ScriptPass {
bus_rebuild()
: ScriptPass("bus_rebuild", "Reconstruct busses from wires with the same prefix following the convention: <prefix>_<index>_")
{
}
void script() override {}
bool is_digits(const std::string &str) { return std::all_of(str.begin(), str.end(), ::isdigit); }
// Get the index <index> from the signal name "prefix_<index>_"
int getIndex(std::string name, std::map<std::string, RTLIL::Wire *> &wirenames_to_remove)
{
std::map<std::string, RTLIL::Wire *>::iterator itr_lhs = wirenames_to_remove.find(name);
if (itr_lhs != wirenames_to_remove.end()) {
std::string::iterator conn_lhs_end = name.end() - 1;
if ((*conn_lhs_end) == '_') {
name = name.substr(0, name.size() - 1);
if (name.find("_") != std::string::npos) {
std::string ch_index_str = name.substr(name.find_last_of('_') + 1);
if (!ch_index_str.empty() && is_digits(ch_index_str)) {
int ch_index = std::stoi(ch_index_str);
return ch_index;
}
}
}
}
return -1;
}
void execute(std::vector<std::string>, RTLIL::Design *design) override
{
if (design == nullptr) {
log_error("No design object");
return;
}
bool debug = false;
if (std::getenv("DEBUG_RECONSTRUCT_BUSSES")) {
debug = true;
}
log("Running bus_rebuild pass\n");
log_flush();
for (auto module : design->modules()) {
if (module->get_bool_attribute("\\blackbox"))
continue;
log("Creating bus groups for module %s\n", module->name.str().c_str());
log_flush();
// Collect all wires with a common prefix
dict<std::string, std::vector<RTLIL::Wire *>> wire_groups;
for (auto wire : module->wires()) {
if (wire->name[0] == '$') // Skip internal wires
continue;
if ((!wire->port_input) && (!wire->port_output)) {
continue;
}
std::string prefix = wire->name.str();
if (prefix.empty())
continue;
// We want to truncate the final _<index>_ part of the string
// Example: "add_Y_0_"
// Result: "add_Y"
std::string::iterator end = prefix.end() - 1;
if ((*end) == '_') {
// Last character is an _, check that it is a bit blasted index:
bool valid_index = false;
std::string ch_name = prefix.substr(0, prefix.size() - 1);
if (ch_name.find("_") != std::string::npos) {
std::string ch_index_str = ch_name.substr(ch_name.find_last_of('_') + 1);
if ((!ch_index_str.empty() && is_digits(ch_index_str))) {
valid_index = true;
}
}
if (!valid_index) {
log_warning("Invalid net name %s\n", prefix.c_str());
log_flush();
continue;
}
end--;
for (; end != prefix.begin(); end--) {
if ((*end) != '_') {
// Truncate until the next _
continue;
} else {
// Truncate the _
break;
}
}
if (end == prefix.begin()) {
// Last _ didn't mean there was another _
log_warning("Invalid net name %s\n", prefix.c_str());
log_flush();
continue;
}
std::string no_bitblast_prefix;
std::copy(prefix.begin(), end, std::back_inserter(no_bitblast_prefix));
wire_groups[no_bitblast_prefix].push_back(wire);
}
}
log("Found %ld groups\n", wire_groups.size());
if (wire_groups.size() == 0) {
log("No busses to reconstruct. Done.\n");
continue;
}
log("Creating busses\n");
log_flush();
std::map<std::string, RTLIL::Wire *> wirenames_to_remove;
pool<RTLIL::Wire *> wires_to_remove;
// Reconstruct vectors
for (auto &it : wire_groups) {
std::string prefix = it.first;
if (debug)
std::cout << "Wire group:" << prefix << std::endl;
std::vector<RTLIL::Wire *> &wires = it.second;
// Create a new vector wire
int width = wires.size();
RTLIL::Wire *new_wire = module->addWire(prefix, width);
for (RTLIL::Wire *w : wires) {
// All wires in the same wire_group are of the same type (input_port, output_port or none)
if (w->port_input)
new_wire->port_input = 1;
else if (w->port_output)
new_wire->port_output = 1;
break;
}
for (auto wire : wires) {
std::string wire_name = wire->name.c_str();
wirenames_to_remove.emplace(wire_name, new_wire);
wires_to_remove.insert(wire);
}
}
log("Reconnecting cells\n");
log_flush();
// Reconnect cells
for (auto cell : module->cells()) {
if (debug)
std::cout << "Cell:" << cell->name.c_str() << std::endl;
for (auto &conn : cell->connections_) {
RTLIL::SigSpec new_sig;
bool modified = false;
for (auto chunk : conn.second.chunks()) {
if (debug) {
std::cout << " Port:" << conn.first.c_str() << std::endl;
std::cout << " Conn:" << (chunk.wire ? chunk.wire->name.c_str() : "constant") << std::endl;
}
// Find the connections that match the wire group prefix
if (chunk.wire == nullptr) {
continue;
}
std::string lhs_name = chunk.wire ? chunk.wire->name.c_str() : "";
int lhsIndex = getIndex(lhs_name, wirenames_to_remove);
std::map<std::string, RTLIL::Wire *>::iterator itr_lhs = wirenames_to_remove.find(lhs_name);
if (itr_lhs != wirenames_to_remove.end()) {
if (lhsIndex >= 0) {
// Create a new connection sigspec that matches the previous
// bit index
if (lhsIndex < itr_lhs->second->width) {
RTLIL::SigSpec bit = RTLIL::SigSpec(itr_lhs->second, lhsIndex, 1);
new_sig.append(bit);
modified = true;
} else {
log_warning("Attempting to reconnect cell %s, port: %s of size %d with "
"out-of-bound index %d\n",
cell->name.c_str(), conn.first.c_str(), itr_lhs->second->width,
lhsIndex);
for (RTLIL::Wire *w : wires_to_remove) {
if (strcmp(w->name.c_str(), itr_lhs->second->name.c_str()) == 0) {
wires_to_remove.erase(w);
break;
}
}
}
} else {
new_sig.append(chunk);
modified = true;
}
}
}
// Replace the previous connection
if (modified)
conn.second = new_sig;
}
}
if (debug)
run_pass("write_rtlil post_reconnect_cells.rtlil");
log("Reconnecting top connections\n");
log_flush();
// Reconnect top connections before removing the old wires
std::vector<RTLIL::SigSig> newConnections;
for (auto &conn : module->connections()) {
// Keep all the connections that won't get rewired
newConnections.push_back(conn);
}
for (auto &conn : newConnections) {
RTLIL::SigSpec lhs = conn.first;
RTLIL::SigSpec rhs = conn.second;
auto lit = lhs.chunks().rbegin();
if (lit == lhs.chunks().rend())
continue;
auto rit = rhs.chunks().rbegin();
if (rit == rhs.chunks().rend())
continue;
RTLIL::SigChunk sub_rhs = *rit;
while (lit != lhs.chunks().rend()) {
RTLIL::SigChunk sub_lhs = *lit;
std::string conn_lhs_s = sub_lhs.wire ? sub_lhs.wire->name.c_str() : "";
std::string conn_rhs_s = sub_rhs.wire ? sub_rhs.wire->name.c_str() : "";
if (!conn_lhs_s.empty()) {
if (debug) {
std::cout << "Conn LHS: " << conn_lhs_s << std::endl;
std::cout << "Conn RHS: " << conn_rhs_s << std::endl;
}
int lhsIndex = getIndex(conn_lhs_s, wirenames_to_remove);
int rhsIndex = getIndex(conn_rhs_s, wirenames_to_remove);
std::map<std::string, RTLIL::Wire *>::iterator itr_lhs = wirenames_to_remove.find(conn_lhs_s);
std::map<std::string, RTLIL::Wire *>::iterator itr_rhs = wirenames_to_remove.find(conn_rhs_s);
if (itr_lhs != wirenames_to_remove.end() || itr_rhs != wirenames_to_remove.end()) {
if (lhsIndex >= 0) {
RTLIL::SigSpec lbit;
// Create the LHS sigspec of the desired bit
if (lhsIndex < itr_lhs->second->width) {
lbit = RTLIL::SigSpec(itr_lhs->second, lhsIndex, 1);
} else {
lbit = itr_lhs->second;
log_warning("Attempting to reconnect signal %s, of "
"size %d with out-of-bound index %d\n",
conn_lhs_s.c_str(),
itr_lhs->second->width, lhsIndex);
for (RTLIL::Wire *w : wires_to_remove) {
if (strcmp(w->name.c_str(),conn_lhs_s.c_str()) == 0) {
wires_to_remove.erase(w);
break;
}
}
}
if (sub_rhs.size() > 1) {
// If RHS has width > 1, replace with the bitblasted RHS
// corresponding to the connected bit
if (lhsIndex < sub_rhs.wire->width) {
RTLIL::SigSpec rhs_bit = RTLIL::SigSpec(sub_rhs.wire, lhsIndex, 1);
// And connect it
module->connect(lbit, rhs_bit);
} else {
log_warning("Attempting to reconnect signal %s, of "
"size %d with out-of-bound index %d\n",
conn_rhs_s.c_str(),
sub_rhs.wire->width, lhsIndex);
for (RTLIL::Wire *w : wires_to_remove) {
if (strcmp(w->name.c_str(), conn_rhs_s.c_str()) == 0) {
wires_to_remove.erase(w);
break;
}
}
}
} else {
// Else, directly connect
if (rhsIndex >= 0) {
if (rhsIndex < itr_rhs->second->width) {
RTLIL::SigSpec rbit =
RTLIL::SigSpec(itr_rhs->second, rhsIndex, 1);
module->connect(lbit, rbit);
} else {
log_warning("Attempting to reconnect signal %s, of "
"size %d with out-of-bound index %d\n",
conn_rhs_s.c_str(),
itr_rhs->second->width, rhsIndex);
for (RTLIL::Wire *w : wires_to_remove) {
if (strcmp(w->name.c_str(), conn_rhs_s.c_str()) == 0) {
wires_to_remove.erase(w);
break;
}
}
}
} else {
module->connect(lbit, sub_rhs);
}
}
} else {
// LHS is not a bus
if (itr_rhs->second->width > 1) {
RTLIL::SigSpec rhs_bit = RTLIL::SigSpec(itr_rhs->second, 0, 1);
module->connect(sub_lhs, rhs_bit);
} else {
module->connect(sub_lhs, sub_rhs);
}
}
}
}
lit++;
if (++rit != rhs.chunks().rend())
rit++;
}
}
if (debug)
run_pass("write_rtlil post_reconnect_top.rtlil");
// Remove old bit blasted wires
// Cleans the dangling connections too
log("Removing bit blasted wires\n");
log_flush();
if (debug) {
for (RTLIL::Wire *w : wires_to_remove) {
std::cout << " " << w->name.c_str() << std::endl;
}
}
module->remove(wires_to_remove);
// Update module port list
log("Re-creating ports\n");
log_flush();
module->fixup_ports();
}
if (debug)
run_pass("write_rtlil post_bus_rebuild.rtlil");
log("End bus_rebuild pass\n");
log_flush();
}
} bus_rebuild;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,257 @@
/*
* 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/celltypes.h"
#include "kernel/sigtools.h"
#include "kernel/utils.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
typedef RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell> cell_ptr_cmp;
struct LongLoopSelect : public ScriptPass {
LongLoopSelect()
: ScriptPass("longloop_select", "Selects long for-loops (Creating logic above a certain logic depth) for further optimizations")
{
}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" longloop_select [-depth <for-loop threshold depth>] [-abc_opt <ABC options>] [-abc_script <ABC script>]\n");
log(" If no ABC script/option is provided, this pass simply selects cells in for-loops\n");
log(" If an ABC script/option is provided, this pass selects cells in a per for-loop basis and runs ABC with the given script\n");
log("\n");
}
void script() override {}
// Adapted from the torder pass
void toposorting(std::vector<Cell *> &cells, SigMap &sigmap,
TopoSort<RTLIL::Cell *, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> &toposort, bool debug)
{
if (debug) {
log(" Collecting design data\n");
log_flush();
}
dict<SigBit, pool<Cell *>> bit_drivers, bit_users;
for (Cell *cell : cells) {
for (auto conn : cell->connections()) {
bool noautostop = false;
if (!noautostop && yosys_celltypes.cell_known(cell->type)) {
if (conn.first.in(ID::Q, ID::CTRL_OUT, ID::RD_DATA))
continue;
if (cell->type.in(ID($memrd), ID($memrd_v2)) && conn.first == ID::DATA)
continue;
}
if (cell->input(conn.first))
for (auto bit : sigmap(conn.second))
bit_users[bit].insert(cell);
if (cell->output(conn.first))
for (auto bit : sigmap(conn.second))
bit_drivers[bit].insert(cell);
toposort.node(cell);
}
}
if (debug) {
log(" Creating sorting data structure\n");
log_flush();
}
for (auto &it : bit_users)
if (bit_drivers.count(it.first))
for (auto driver_cell : bit_drivers.at(it.first))
for (auto user_cell : it.second)
toposort.edge(driver_cell, user_cell);
toposort.analyze_loops = false;
if (debug) {
log(" Sorting\n");
log_flush();
}
toposort.sort();
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
if (design == nullptr) {
log_error("No design object");
return;
}
uint32_t threshold_depth = 100;
bool debug = false;
size_t argidx;
std::string abc_script;
std::string abc_options;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-depth") {
argidx++;
threshold_depth = std::stoul(args[argidx], nullptr, 10);
} else if (args[argidx] == "-debug") {
debug = true;
} else if (args[argidx] == "-abc_script") {
argidx++;
abc_script = args[argidx];
} else if (args[argidx] == "-abc_opt") {
argidx++;
abc_options = args[argidx];
} else {
break;
}
}
extra_args(args, argidx, design);
if (std::getenv("DEBUG_LONGLOOPS")) {
debug = true;
}
log("Running longloop_select pass\n");
log_flush();
// Memorize the existing selection, so the loop over modules still works after "select -none"
const std::vector<Yosys::RTLIL::Module *> &modules = design->selected_modules();
// Start with an empty selection, the code below is going to add selection based on loop depth
Pass::call(design, "select -none");
for (auto module : modules) {
if (debug) {
log("Module %s\n", log_id(module));
log_flush();
}
if (debug) {
log(" Creating sigmap\n");
log_flush();
}
SigMap sigmap(module);
std::map<std::string, std::vector<Cell *>> loopIndexCellMap;
if (debug) {
log(" Creating sorting datastructures\n");
log_flush();
}
for (auto cell : module->cells()) {
std::string loopIndex = cell->get_string_attribute("\\in_for_loop");
if (!loopIndex.empty()) {
std::map<std::string, std::vector<Cell *>>::iterator itr = loopIndexCellMap.find(loopIndex);
if (itr == loopIndexCellMap.end()) {
std::vector<Cell *> cellSet;
cellSet.push_back(cell);
loopIndexCellMap.emplace(loopIndex, cellSet);
} else {
itr->second.push_back(cell);
}
}
}
if (!loopIndexCellMap.empty()) {
log(" Found %ld for-loop clusters in module %s\n", loopIndexCellMap.size(), module->name.c_str());
log_flush();
}
for (std::map<std::string, std::vector<Cell *>>::iterator itrCluster = loopIndexCellMap.begin();
itrCluster != loopIndexCellMap.end(); itrCluster++) {
std::string loopInd = itrCluster->first;
if (itrCluster->second.size() < threshold_depth) {
if (debug) {
log(" Skipping loop location %s as it contains only %ld cells\n", loopInd.c_str(),
itrCluster->second.size());
log_flush();
}
continue;
}
if (debug) {
log(" Analyzing loop location %s containing %ld cells\n", loopInd.c_str(), itrCluster->second.size());
log_flush();
}
// For a given for-loop cell group, perform topological sorting to get the logic depth of the ending cell in
// the group
std::map<RTLIL::Cell *, int> celllevel;
for (auto cell : itrCluster->second) {
celllevel.emplace(cell, 0);
}
TopoSort<RTLIL::Cell *, RTLIL::IdString::compare_ptr_by_name<RTLIL::Cell>> toposort;
toposorting(itrCluster->second, sigmap, toposort, debug);
std::vector<Cell *>::reverse_iterator itrLastCell = toposort.sorted.rbegin();
int logicdepth = 0;
std::map<RTLIL::Cell *, std::set<RTLIL::Cell *, cell_ptr_cmp>, cell_ptr_cmp> topo_cell_drivers =
toposort.get_database();
for (auto cell : toposort.sorted) {
int level = 0;
auto itrAdj = topo_cell_drivers.find(cell);
for (auto c : (*itrAdj).second) {
level = std::max(level, celllevel[c]);
}
level++;
celllevel[cell] = level;
logicdepth = std::max(logicdepth, level);
}
if (debug) {
log(" Logic depth: %d\n", logicdepth);
log_flush();
}
if (logicdepth > (int)threshold_depth) {
log(" Selecting %ld cells in for-loop location %s of depth %d ending with cell %s\n",
itrCluster->second.size(), loopInd.c_str(), logicdepth, log_id((*itrLastCell)));
log_flush();
std::string src_info = (*itrLastCell)->get_src_attribute();
if (!(*itrLastCell)->get_string_attribute("\\in_for_loop").empty()) {
src_info = (*itrLastCell)->get_string_attribute("\\in_for_loop");
}
// Select all cells in the loop cluster
if (!abc_script.empty()) {
// If an ABC script is provided, select on a per-loop basis
Pass::call(design, "select -none");
}
for (auto cell : itrCluster->second) {
if (debug) {
log(" Selecting: %s\n", cell->name.c_str());
log_flush();
}
design->select(module, cell);
if (cell->get_string_attribute("\\in_for_loop").empty()) {
cell->set_string_attribute("\\in_for_loop", src_info);
} else {
src_info = cell->get_string_attribute("\\in_for_loop");
}
}
if (!abc_script.empty()) {
std::string command = "abc -map_src " + src_info + " -script " + abc_script;
log(" Executing: %s\n", command.c_str());
log_flush();
Pass::call(design, command);
} else if (!abc_options.empty()) {
abc_options.erase(std::remove(abc_options.begin(), abc_options.end(), '"'), abc_options.end());
std::string command = "abc -map_src " + src_info + " " + abc_options;
log(" Executing: %s\n", command.c_str());
log_flush();
Pass::call(design, command);
}
}
}
}
log("End longloop_select pass\n");
log_flush();
}
} LongLoopSelect;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,486 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* 2019 Eddie Hung <eddie@fpgeh.com>
* 2024 Akash Levy <akash@silimate.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/sigtools.h"
#include "kernel/yosys.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct OptBalanceTreeWorker {
// Module and signal map
Design *design;
Module *module;
SigMap sigmap;
bool allow_off_chain;
int limit = -1;
// Counts of each cell type that are getting balanced
dict<IdString, int> cell_count;
// Driver data
dict<SigBit, tuple<IdString, IdString, int>> bit_drivers_db;
// Load data
dict<SigBit, pool<tuple<IdString, IdString, int>>> bit_users_db;
// Signal chain data structures
dict<SigSpec, Cell *> sig_chain_next;
dict<SigSpec, Cell *> sig_chain_prev;
pool<SigBit> sigbit_with_non_chain_users;
pool<Cell *> chain_start_cells;
pool<Cell *> candidate_cells;
// Ignore signals fanout while looking ahead which chains to split.
// Post splitfanout, take that into account.
void make_sig_chain_next_prev(IdString cell_type, bool ignore_split)
{
// Mark all wires with keep attribute as having non-chain users
for (auto wire : module->wires()) {
if (wire->get_bool_attribute(ID::keep)) {
for (auto bit : sigmap(wire))
sigbit_with_non_chain_users.insert(bit);
}
}
// Iterate over all cells in module
for (auto cell : module->cells()) {
// If cell matches and not marked as keep
if (cell->type == cell_type && !cell->get_bool_attribute(ID::keep)) {
// Get signals for cell ports
SigSpec a_sig = sigmap(cell->getPort(ID::A));
SigSpec b_sig = sigmap(cell->getPort(ID::B));
SigSpec y_sig = sigmap(cell->getPort(ID::Y));
// If a_sig already has a chain user, mark its bits as having non-chain users
if (sig_chain_next.count(a_sig)) {
if (!ignore_split)
for (auto a_bit : a_sig.bits())
sigbit_with_non_chain_users.insert(a_bit);
// Otherwise, mark cell as the next in the chain relative to a_sig
} else {
if (fanout_in_range(y_sig)) {
sig_chain_next[a_sig] = cell;
}
}
if (!b_sig.empty()) {
// If b_sig already has a chain user, mark its bits as having non-chain users
if (sig_chain_next.count(b_sig)) {
if (!ignore_split)
for (auto b_bit : b_sig.bits())
sigbit_with_non_chain_users.insert(b_bit);
// Otherwise, mark cell as the next in the chain relative to b_sig
} else {
if (fanout_in_range(y_sig)) {
sig_chain_next[b_sig] = cell;
}
}
}
if (fanout_in_range(y_sig)) {
// Add cell as candidate
candidate_cells.insert(cell);
// Mark cell as the previous in the chain relative to y_sig
sig_chain_prev[y_sig] = cell;
}
}
// If cell is not matching type, mark all cell input signals as being non-chain users
else {
for (auto conn : cell->connections())
if (cell->input(conn.first))
for (auto bit : sigmap(conn.second))
sigbit_with_non_chain_users.insert(bit);
}
}
}
void find_chain_start_cells()
{
for (auto cell : candidate_cells) {
// Log candidate cell
log_debug("Considering %s (%s)\n", log_id(cell), log_id(cell->type));
// Get signals for cell ports
SigSpec a_sig = sigmap(cell->getPort(ID::A));
SigSpec b_sig = sigmap(cell->getPort(ID::B));
SigSpec prev_sig = sig_chain_prev.count(a_sig) ? a_sig : b_sig;
// This is a start cell if there was no previous cell in the chain for a_sig or b_sig
if (sig_chain_prev.count(a_sig) + sig_chain_prev.count(b_sig) != 1) {
chain_start_cells.insert(cell);
continue;
}
// If any bits in previous cell signal have non-chain users, this is a start cell
for (auto bit : prev_sig.bits())
if (sigbit_with_non_chain_users.count(bit)) {
chain_start_cells.insert(cell);
continue;
}
}
}
vector<Cell *> create_chain(Cell *start_cell)
{
// Chain of cells
vector<Cell *> chain;
// Current cell
Cell *c = start_cell;
// Iterate over cells and add to chain
while (c != nullptr) {
chain.push_back(c);
SigSpec y_sig = sigmap(c->getPort(ID::Y));
if (sig_chain_next.count(y_sig) == 0)
break;
c = sig_chain_next.at(y_sig);
if (chain_start_cells.count(c) != 0)
break;
}
// Return chain of cells
return chain;
}
void wreduce(Cell *cell)
{
// If cell is arithmetic, remove leading zeros from inputs, then clean up outputs
if (cell->type.in(ID($add), ID($mul))) {
// Remove leading zeros from inputs
for (auto inport : {ID::A, ID::B}) {
// Record number of bits removed
int bits_removed = 0;
IdString inport_signed = (inport == ID::A) ? ID::A_SIGNED : ID::B_SIGNED;
IdString inport_width = (inport == ID::A) ? ID::A_WIDTH : ID::B_WIDTH;
SigSpec inport_sig = sigmap(cell->getPort(inport));
cell->unsetPort(inport);
if (cell->getParam((inport == ID::A) ? ID::A_SIGNED : ID::B_SIGNED).as_bool()) {
while (GetSize(inport_sig) > 1 && inport_sig[GetSize(inport_sig) - 1] == State::S0 &&
inport_sig[GetSize(inport_sig) - 2] == State::S0) {
inport_sig.remove(GetSize(inport_sig) - 1, 1);
bits_removed++;
}
} else {
while (GetSize(inport_sig) > 0 && inport_sig[GetSize(inport_sig) - 1] == State::S0) {
inport_sig.remove(GetSize(inport_sig) - 1, 1);
bits_removed++;
}
}
cell->setPort(inport, inport_sig);
cell->setParam(inport_width, GetSize(inport_sig));
log_debug("Width reduced %s/%s by %d bits\n", log_id(cell), log_id(inport), bits_removed);
}
// Record number of bits removed from output
int bits_removed = 0;
// Remove unnecessary bits from output
SigSpec y_sig = sigmap(cell->getPort(ID::Y));
cell->unsetPort(ID::Y);
int width;
if (cell->type == ID($add))
width = std::max(cell->getParam(ID::A_WIDTH).as_int(), cell->getParam(ID::B_WIDTH).as_int()) + 1;
else if (cell->type == ID($mul))
width = cell->getParam(ID::A_WIDTH).as_int() + cell->getParam(ID::B_WIDTH).as_int();
else
log_abort();
for (int i = GetSize(y_sig) - 1; i >= width; i--) {
module->connect(y_sig[i], State::S0);
y_sig.remove(i, 1);
bits_removed++;
}
cell->setPort(ID::Y, y_sig);
cell->setParam(ID::Y_WIDTH, GetSize(y_sig));
log_debug("Width reduced %s/Y by %d bits\n", log_id(cell), bits_removed);
}
cell->fixup_parameters();
}
bool process_chain(vector<Cell *> &chain)
{
// If chain size is less than 3, no balancing needed
if (GetSize(chain) < 3)
return false;
// Get mid, midnext (at index mid+1) and end of chain
Cell *mid_cell = chain[GetSize(chain) / 2];
Cell *cell = mid_cell; // SILIMATE: Set cell to mid_cell for better naming
Cell *midnext_cell = chain[GetSize(chain) / 2 + 1];
Cell *end_cell = chain.back();
log_debug("Balancing chain of %d cells: mid=%s, midnext=%s, endcell=%s\n", GetSize(chain), log_id(mid_cell), log_id(midnext_cell),
log_id(end_cell));
// Get mid signals
SigSpec mid_a_sig = sigmap(mid_cell->getPort(ID::A));
SigSpec mid_b_sig = sigmap(mid_cell->getPort(ID::B));
SigSpec mid_non_chain_sig = sig_chain_prev.count(mid_a_sig) ? mid_b_sig : mid_a_sig;
IdString mid_non_chain_port = sig_chain_prev.count(mid_a_sig) ? ID::B : ID::A;
// Get midnext signals
SigSpec midnext_a_sig = sigmap(midnext_cell->getPort(ID::A));
SigSpec midnext_b_sig = sigmap(midnext_cell->getPort(ID::B));
IdString midnext_chain_port = sig_chain_prev.count(midnext_a_sig) ? ID::A : ID::B;
// Get output signal
SigSpec end_y_sig = sigmap(end_cell->getPort(ID::Y));
// Create new mid wire
Wire *mid_wire = module->addWire(NEW_ID2_SUFFIX("mid"), GetSize(end_y_sig)); // SILIMATE: Improve the naming
// Perform rotation
mid_cell->setPort(mid_non_chain_port, mid_wire);
mid_cell->setPort(ID::Y, end_y_sig);
midnext_cell->setPort(midnext_chain_port, mid_non_chain_sig);
end_cell->setPort(ID::Y, mid_wire);
// Recreate sigmap
sigmap.set(module);
// Get subtrees
vector<Cell *> left_chain(chain.begin(), chain.begin() + GetSize(chain) / 2);
vector<Cell *> right_chain(chain.begin() + GetSize(chain) / 2 + 1, chain.end());
// Recurse on subtrees
process_chain(left_chain);
process_chain(right_chain);
// Width reduce left subtree
for (auto c : left_chain)
wreduce(c);
// Width reduce right subtree
for (auto c : right_chain)
wreduce(c);
// Recreate sigmap
sigmap.set(module);
// Width reduce mid cell
wreduce(mid_cell);
return true;
}
void cleanup()
{
// Fix ports
module->fixup_ports();
// Clear data structures
sig_chain_next.clear();
sig_chain_prev.clear();
sigbit_with_non_chain_users.clear();
chain_start_cells.clear();
candidate_cells.clear();
}
bool fanout_in_range(SigSpec outsig)
{
// Check if output signal is "bit-split", skip if so
// This is a lookahead for the splitfanout pass that has this limitation
auto bit_users = bit_users_db[outsig[0]];
for (int i = 0; i < GetSize(outsig); i++) {
if (bit_users_db[outsig[i]] != bit_users) {
return false;
}
}
// Skip if fanout is above limit
if (limit != -1 && GetSize(bit_users) > limit) {
return false;
}
return true;
}
OptBalanceTreeWorker(Design *design, Module *module, const vector<IdString> cell_types, bool allow_off_chain, int limit)
: design(design), module(module), sigmap(module), allow_off_chain(allow_off_chain), limit(limit)
{
if (allow_off_chain) {
// Build bit_drivers_db
log("Building bit_drivers_db...\n");
for (auto cell : module->cells()) {
for (auto conn : cell->connections()) {
if (!cell->output(conn.first))
continue;
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit(sigmap(conn.second[i]));
bit_drivers_db[bit] = tuple<IdString, IdString, int>(cell->name, conn.first, i);
}
}
}
// Build bit_users_db
log("Building bit_users_db...\n");
for (auto cell : module->cells()) {
for (auto conn : cell->connections()) {
if (!cell->input(conn.first))
continue;
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit(sigmap(conn.second[i]));
if (!bit_drivers_db.count(bit))
continue;
bit_users_db[bit].insert(
tuple<IdString, IdString, int>(cell->name, conn.first, i - std::get<2>(bit_drivers_db[bit])));
}
}
}
// Build bit_users_db for output ports
log("Building bit_users_db for output ports...\n");
for (auto wire : module->wires()) {
if (!wire->port_output)
continue;
SigSpec sig(sigmap(wire));
for (int i = 0; i < GetSize(sig); i++) {
SigBit bit(sig[i]);
if (!bit_drivers_db.count(bit))
continue;
bit_users_db[bit].insert(
tuple<IdString, IdString, int>(wire->name, IdString(), i - std::get<2>(bit_drivers_db[bit])));
}
}
// Deselect all cells
Pass::call(design, "select -none");
// Do for each cell type
bool has_cell_to_split = false;
for (auto cell_type : cell_types) {
// Find chains of ops
make_sig_chain_next_prev(cell_type, true);
find_chain_start_cells();
// For each chain, if len >= 3, select all the elements
for (auto c : chain_start_cells) {
vector<Cell *> chain = create_chain(c);
if (GetSize(chain) < 3)
continue;
for (auto cell : chain) {
has_cell_to_split = true;
design->select(module, cell);
}
}
// Clean up
cleanup();
}
// Splitfanout of selected cells
if (has_cell_to_split)
Pass::call(design, "splitfanout");
// Reset selection for other passes
Pass::call(design, "select -clear");
// Recreate sigmap
sigmap.set(module);
}
// Do for each cell type
for (auto cell_type : cell_types) {
// Find chains of ops
make_sig_chain_next_prev(cell_type, false);
find_chain_start_cells();
// For each chain, if len >= 3, convert to tree via "rotation" and recurse on subtrees
for (auto c : chain_start_cells) {
vector<Cell *> chain = create_chain(c);
if (process_chain(chain)) {
// Rename cells and wires for formal check to pass as cells signals have changed functionalities post rotation
for (Cell *cell : chain) {
module->rename(cell, NEW_ID2_SUFFIX("rot_cell"));
}
for (Cell *cell : chain) {
SigSpec y_sig = sigmap(cell->getPort(ID::Y));
if (y_sig.is_wire()) {
Wire *wire = y_sig.as_wire();
if (wire && !wire->port_input && !wire->port_output) {
module->rename(y_sig.as_wire(), NEW_ID2_SUFFIX("rot_wire"));
}
}
}
}
cell_count[cell_type] += GetSize(chain);
}
// Clean up
cleanup();
}
}
};
struct OptBalanceTreePass : public Pass {
OptBalanceTreePass() : Pass("opt_balance_tree", "$and/$or/$xor/$xnor/$add/$mul cascades to trees") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" opt_balance_tree [options] [selection]\n");
log("\n");
log("This pass converts cascaded chains of $and/$or/$xor/$xnor/$add/$mul cells into\n");
log("trees of cells to improve timing.\n");
log("\n");
log(" -allow-off-chain\n");
log(" Allows matching of cells that have loads outside the chain. These cells\n");
log(" will be replicated and balanced into a tree, but the original\n");
log(" cell will remain, driving its original loads.\n");
log(" -fanout_limit n\n");
log(" max fanout to split.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing OPT_BALANCE_TREE pass (cell cascades to trees).\n");
bool allow_off_chain = false;
size_t argidx;
int limit = -1;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-allow-off-chain") {
allow_off_chain = true;
continue;
}
if (args[argidx] == "-fanout_limit" && argidx + 1 < args.size()) {
limit = std::stoi(args[++argidx]);
continue;
}
break;
}
extra_args(args, argidx, design);
// Count of all cells that were packed
dict<IdString, int> cell_count;
const vector<IdString> cell_types = {ID($and), ID($or), ID($xor), ID($xnor), ID($add), ID($mul)};
for (auto module : design->selected_modules()) {
OptBalanceTreeWorker worker(design, module, cell_types, allow_off_chain, limit);
for (auto cell : worker.cell_count) {
cell_count[cell.first] += cell.second;
}
}
// Log stats
for (auto cell_type : cell_types)
log("Converted %d %s cells into trees.\n", cell_count[cell_type], log_id(cell_type));
}
} OptBalanceTreePass;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,85 @@
/*
* 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/celltypes.h"
#include "kernel/sigtools.h"
#include <string.h>
#include <errno.h>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct SelectConstPass : public Pass {
SelectConstPass() : Pass("selectconst", "select objects with (mostly) constant input bits") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" selectconst [ -frac <frac> ] {<selection>}\n");
log("\n");
log("Select cells with (mostly) constant input bits.\n");
log("\n");
log(" -frac\n");
log(" the fraction of constant input bits (default: 1.0).\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
float frac = 1;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
std::string arg = args[argidx];
if (arg == "-frac") {
frac = std::stof(args[++argidx]);
continue;
}
break;
}
extra_args(args, argidx, design);
for (auto module : design->selected_modules())
{
SigMap sigmap(module);
for (auto cell : module->selected_cells())
{
for (auto conn : cell->connections()) {
if (!cell->input(conn.first) || GetSize(conn.second) == 0)
continue;
SigPool sigpool;
for (int i = 0; i < GetSize(conn.second); i++) {
if (conn.second[i].wire == nullptr)
continue;
sigpool.add(sigmap(conn.second[i]));
}
if ((float) GetSize(sigpool) / GetSize(conn.second) >= frac) {
cell->set_bool_attribute("\\mostlyconst");
log("Marking %s as mostly constant (%d/%d bits constant).\n", log_id(cell), GetSize(sigpool), GetSize(conn.second));
}
}
}
}
}
} SelectConstPass;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,247 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
* 2024 Akash Levy <akash@silimate.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"
#include "kernel/utils.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
struct SplitfanoutWorker
{
Module *module;
SigMap sigmap;
dict<SigBit, tuple<IdString,IdString,int>> bit_drivers_db;
dict<SigBit, pool<tuple<IdString,IdString,int>>> bit_users_db;
TopoSort<IdString, RTLIL::sort_by_id_str> toposort;
SplitfanoutWorker(Module *module) : module(module), sigmap(module)
{
// Add nodes to topological sorter for all selected cells
log("Making toposort nodes for module %s...\n", log_id(module));
for (auto cell : module->selected_cells())
toposort.node(cell->name);
// Build bit_drivers_db
log("Building bit_drivers_db...\n");
for (auto cell : module->cells()) {
for (auto conn : cell->connections()) {
if (!cell->output(conn.first)) continue;
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit(sigmap(conn.second[i]));
bit_drivers_db[bit] = tuple<IdString,IdString,int>(cell->name, conn.first, i);
}
}
}
// Build bit_users_db and add edges to topological sorter
log("Building bit_users_db and adding edges to toposort...\n");
for (auto cell : module->cells()) {
for (auto conn : cell->connections()) {
if (!cell->input(conn.first)) continue;
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit(sigmap(conn.second[i]));
if (!bit_drivers_db.count(bit)) continue;
bit_users_db[bit].insert(tuple<IdString,IdString,int>(cell->name,
conn.first, i-std::get<2>(bit_drivers_db[bit])));
IdString driver_cell = std::get<0>(bit_drivers_db[bit]);
if (toposort.has_node(driver_cell) && toposort.has_node(cell->name))
// toposort.edge(driver_cell, cell->name);
toposort.edge(cell->name, driver_cell);
}
}
}
// Build bit_users_db for output ports
log("Building bit_users_db for output ports...\n");
for (auto wire : module->wires()) {
if (!wire->port_output) continue;
SigSpec sig(sigmap(wire));
for (int i = 0; i < GetSize(sig); i++) {
SigBit bit(sig[i]);
if (!bit_drivers_db.count(bit)) continue;
bit_users_db[bit].insert(tuple<IdString,IdString,int>(wire->name,
IdString(), i-std::get<2>(bit_drivers_db[bit])));
}
}
// Sort using the topological sorter
log("Sorting using toposort...\n");
toposort.analyze_loops = false;
toposort.sort();
}
int split(Cell *cell, int limit)
{
// Get output signal/port
SigSpec outsig;
IdString outport;
int output_count = 0;
for (auto conn : cell->connections())
if (cell->output(conn.first)) {
output_count++;
outport = conn.first;
outsig = conn.second;
}
if (output_count != 1) {
log("Skipping %s cell %s/%s with %d output ports.\n", log_id(cell->type), log_id(module), log_id(cell), output_count);
return 0;
}
// Check if output signal is "bit-split", skip if so
auto bit_users = bit_users_db[outsig[0]];
for (int i = 0; i < GetSize(outsig); i++) {
if (bit_users_db[outsig[i]] != bit_users) {
log("Skipping %s cell %s/%s with bit-split output.\n", log_id(cell->type), log_id(module), log_id(cell));
return 0;
}
}
// Skip if output signal has only one user
if (GetSize(bit_users) <= 1)
return 0;
// Skip if fanout is above limit
if (limit != -1 && GetSize(bit_users) > limit) {
log("Skipping %s cell %s/%s with high fanout %d.\n", log_id(cell->type), log_id(module), log_id(cell), GetSize(bit_users)-1);
return 0;
}
// Iterate over bit users and create a new cell for each one
log("Splitting %s cell %s/%s into %d copies based on fanout\n", log_id(cell->type), log_id(module), log_id(cell), GetSize(bit_users)-1);
int foi = 0;
cell->unsetPort(outport);
int num_new_cells = GetSize(bit_users)-1;
int bit_user_i = num_new_cells;
for (auto bit_user : bit_users)
{
// Configure the driver cell
IdString new_name;
Cell *new_cell;
if (bit_user_i != 0) { // create a new cell
new_name = module->uniquify(stringf("%s_splfo%d", cell->name.c_str(), bit_user_i));
new_cell = module->addCell(new_name, cell);
// Add new cell to the bit_users_db
for (auto conn : new_cell->connections()) {
if (!new_cell->input(conn.first)) continue;
for (int i = 0; i < GetSize(conn.second); i++) {
SigBit bit(sigmap(conn.second[i]));
if (!bit_drivers_db.count(bit)) continue;
bit_users_db[bit].insert(tuple<IdString,IdString,int>(new_cell->name,
conn.first, i-std::get<2>(bit_drivers_db[bit])));
}
}
} else { // if last cell, reuse the original cell
new_name = cell->name;
new_cell = cell;
}
// Connect the new cell to the user
if (std::get<1>(bit_user) == IdString()) { // is wire
Wire *old_wire = module->wire(std::get<0>(bit_user));
Wire *new_wire = module->addWire(NEW_ID2_SUFFIX(stringf("splfow%d", bit_user_i)), old_wire); // SILIMATE: Improve the naming
module->swap_names(old_wire, new_wire);
old_wire->port_input = false;
old_wire->port_output = false;
SigSpec sig(new_wire, std::get<2>(bit_user), GetSize(outsig));
new_cell->setPort(outport, sig);
}
else {
Wire *new_wire = module->addWire(NEW_ID2_SUFFIX(stringf("splfon%d", bit_user_i)), GetSize(outsig)); // SILIMATE: Improve the naming
Cell *target_cell = module->cell(std::get<0>(bit_user));
SigSpec sig = target_cell->getPort(std::get<1>(bit_user));
sig.replace(std::get<2>(bit_user), new_wire);
module->cell(std::get<0>(bit_user))->setPort(std::get<1>(bit_user), sig);
new_cell->setPort(outport, new_wire);
}
// Decrement bit user index
bit_user_i--;
// Log the new cell
log_debug(" slice %d: %s => %s\n", foi++, log_id(new_name), log_signal(new_cell->getPort(outport)));
}
// Fix up ports
module->fixup_ports();
// Return the number of new cells created
return num_new_cells;
}
};
struct SplitfanoutPass : public Pass {
SplitfanoutPass() : Pass("splitfanout", "split up cells with >1 fanout into copies") { }
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" splitfanout [selection]\n");
log("\n");
log("This command copies selected cells with >1 fanout into cells with fanout 1. It\n");
log("is effectively the opposite of the opt_merge pass.\n");
log("\n");
log("This command operates only on cells with 1 output and no 'bit split' on that\n");
log("output.\n");
log("\n");
log(" -limit n\n");
log(" max fanout to split.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
int limit = -1;
log_header(design, "Executing SPLITFANOUT pass (splitting up cells with >1 fanout into copies).\n");
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++)
{
// No options currently. When adding in the future make sure to update docstring with [options]
if (args[argidx] == "-limit" && argidx+1 < args.size()) {
limit = std::stoi(args[++argidx]);
continue;
}
break;
}
extra_args(args, argidx, design);
for (auto module : design->selected_modules())
{
int count_split_pre = 0;
int count_split_post = 0;
SplitfanoutWorker worker(module);
for (auto cell : worker.toposort.sorted) {
int n = worker.split(module->cell(cell), limit);
count_split_pre += (n != 0);
count_split_post += n;
}
if (count_split_pre)
log("Split %d cells in module '%s' into %d copies based on fanout.\n",
count_split_pre, log_id(module), count_split_post);
}
Pass::call(design, "clean *");
}
} SplitfanoutPass;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,272 @@
#include "kernel/sigtools.h"
#include "kernel/yosys.h"
#include <set>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
// Recursively traverses backward from a sig, record if a cell was traversed, and push onto the cell's inputs.
// Similarly with assign statements traverses lhs -> rhs
void recordTransFanin(RTLIL::SigSpec &sig, dict<RTLIL::SigSpec, std::set<Cell *> *> &sig2CellsInFanin,
dict<RTLIL::SigSpec, RTLIL::SigSpec> &lhsSig2RhsSig, std::set<Cell *> &visitedCells, std::set<RTLIL::SigSpec> &visitedSigSpec)
{
if (sig.is_fully_const()) {
return;
}
if (visitedSigSpec.count(sig)) {
return;
}
visitedSigSpec.insert(sig);
if (sig2CellsInFanin.count(sig)) {
std::set<Cell *> *sigFanin = sig2CellsInFanin[sig];
for (std::set<Cell *>::iterator it = sigFanin->begin(); it != sigFanin->end(); it++) {
Cell *cell = *it;
if (visitedCells.count(cell)) {
continue;
}
visitedCells.insert(cell);
for (auto &conn : cell->connections()) {
IdString portName = conn.first;
RTLIL::SigSpec actual = conn.second;
if (cell->input(portName)) {
if (!actual.is_chunk()) {
for (auto it = actual.chunks().rbegin(); it != actual.chunks().rend(); ++it) {
RTLIL::SigSpec sub_actual = *it;
recordTransFanin(sub_actual, sig2CellsInFanin, lhsSig2RhsSig, visitedCells, visitedSigSpec);
}
} else {
recordTransFanin(actual, sig2CellsInFanin, lhsSig2RhsSig, visitedCells, visitedSigSpec);
}
}
}
}
}
if (lhsSig2RhsSig.count(sig)) {
RTLIL::SigSpec rhs = lhsSig2RhsSig[sig];
recordTransFanin(rhs, sig2CellsInFanin, lhsSig2RhsSig, visitedCells, visitedSigSpec);
}
}
// Signal cell driver(s), precompute a cell output signal to a cell map
void sigCellDrivers(RTLIL::Design *design, dict<RTLIL::SigSpec, std::set<Cell *> *> &sig2CellsInFanin)
{
if (!design->top_module())
return;
if (design->top_module()->cells().size() == 0)
return;
for (auto cell : design->top_module()->cells()) {
for (auto &conn : cell->connections()) {
IdString portName = conn.first;
RTLIL::SigSpec actual = conn.second;
std::set<Cell *> *newSet;
if (cell->output(portName)) {
if (!actual.is_chunk()) {
for (auto it = actual.chunks().rbegin(); it != actual.chunks().rend(); ++it) {
RTLIL::SigSpec sub_actual = *it;
if (sig2CellsInFanin.count(sub_actual)) {
newSet = sig2CellsInFanin[sub_actual];
} else {
newSet = new std::set<Cell *>;
sig2CellsInFanin[sub_actual] = newSet;
}
newSet->insert(cell);
}
} else {
if (sig2CellsInFanin.count(actual)) {
newSet = sig2CellsInFanin[actual];
} else {
newSet = new std::set<Cell *>;
sig2CellsInFanin[actual] = newSet;
}
newSet->insert(cell);
for (int i = 0; i < actual.size(); i++) {
SigSpec bit_sig = actual.extract(i, 1);
if (sig2CellsInFanin.count(bit_sig)) {
newSet = sig2CellsInFanin[bit_sig];
} else {
newSet = new std::set<Cell *>;
sig2CellsInFanin[bit_sig] = newSet;
}
newSet->insert(cell);
}
}
}
}
}
}
// Assign statements fanin, traces the lhs to rhs sigspecs and precompute a map
void lhs2rhs(RTLIL::Design *design, dict<RTLIL::SigSpec, RTLIL::SigSpec> &lhsSig2rhsSig)
{
if (!design->top_module())
return;
if (design->top_module()->connections().size() == 0)
return;
for (auto it = design->top_module()->connections().begin(); it != design->top_module()->connections().end(); ++it) {
RTLIL::SigSpec lhs = it->first;
RTLIL::SigSpec rhs = it->second;
if (rhs.is_fully_const()) {
continue;
}
if (!lhs.is_chunk()) {
// If lhs is not a chunk (leaf) ie: assign {a,b} = ..., then bitblast both lhs and rhs
std::vector<SigSpec> lhsBits;
for (int i = 0; i < lhs.size(); i++) {
SigSpec bit_sig = lhs.extract(i, 1);
lhsBits.push_back(bit_sig);
}
std::vector<SigSpec> rhsBits;
for (int i = 0; i < rhs.size(); i++) {
SigSpec bit_sig = rhs.extract(i, 1);
rhsBits.push_back(bit_sig);
}
for (uint32_t i = 0; i < lhsBits.size(); i++) {
if (i < rhsBits.size())
lhsSig2rhsSig[lhsBits[i]] = rhsBits[i];
}
} else {
lhsSig2rhsSig[lhs] = rhs;
}
}
}
std::string_view rtrim_until(std::string_view str, char c)
{
auto pos = str.rfind(c);
if (pos != std::string_view::npos)
str = str.substr(0, pos);
return str;
}
struct SplitNetlist : public ScriptPass {
SplitNetlist()
: ScriptPass("splitnetlist", "Splits a netlist into multiple modules using transitive fanin grouping. \
The output names that belong in the same logical cluster have to have the same prefix: <prefix>_<name>")
{
}
void script() override {}
void execute(std::vector<std::string>, RTLIL::Design *design) override
{
if (design == nullptr) {
log_error("No design object");
return;
}
bool debug = false;
if (std::getenv("DEBUG_SPLITNETLIST")) {
debug = true;
}
log("Running splitnetlist pass\n");
log_flush();
if (debug)
run_pass("write_rtlil post_buf.rtlil");
log("Mapping signals to cells\n");
log_flush();
// Precompute cell output sigspec to cell map
dict<RTLIL::SigSpec, std::set<Cell *> *> sig2CellsInFanin;
sigCellDrivers(design, sig2CellsInFanin);
log("Mapping assignments\n");
log_flush();
// Precompute lhs to rhs sigspec map
dict<RTLIL::SigSpec, RTLIL::SigSpec> lhsSig2RhsSig;
lhs2rhs(design, lhsSig2RhsSig);
// Struct representing a cluster
typedef struct CellsAndSigs {
std::set<Cell *> visitedCells;
std::set<RTLIL::SigSpec> visitedSigSpec;
} CellsAndSigs;
// Cluster mapped by prefix
typedef std::map<std::string, CellsAndSigs> CellName_ObjectMap;
CellName_ObjectMap cellName_ObjectMap;
// Record logic cone by output sharing the same prefix
if (!design->top_module())
return;
if (design->top_module()->wires().size() == 0)
return;
log("Cells grouping\n");
log_flush();
for (auto wire : design->top_module()->wires()) {
if (!wire->port_output)
continue;
std::string output_port_name = wire ? wire->name.c_str() : "";
if (output_port_name.empty())
continue;
// We want to truncate the final _<index>_ part of the string
// Example: "add_Y_0_"
// Result: "add_Y"
std::string::iterator end = output_port_name.end() - 1;
if ((*end) == '_') {
// Last character is an _, it is a bit blasted index
end--;
for (; end != output_port_name.begin(); end--) {
if ((*end) != '_') {
// Truncate until the next _
continue;
} else {
// Truncate the _
break;
}
}
}
std::string no_bitblast_prefix;
std::copy(output_port_name.begin(), end, std::back_inserter(no_bitblast_prefix));
// We then truncate the port name, Result: "add"
std::string_view po_prefix = rtrim_until(std::string_view(no_bitblast_prefix), '_');
std::set<Cell *> visitedCells;
std::set<RTLIL::SigSpec> visitedSigSpec;
RTLIL::SigSpec actual = wire;
// Visit the output sigspec
recordTransFanin(actual, sig2CellsInFanin, lhsSig2RhsSig, visitedCells, visitedSigSpec);
// Visit the output sigspec bits
for (int i = 0; i < actual.size(); i++) {
SigSpec bit_sig = actual.extract(i, 1);
recordTransFanin(bit_sig, sig2CellsInFanin, lhsSig2RhsSig, visitedCells, visitedSigSpec);
}
// Record the visited objects in the corresponding cluster
CellName_ObjectMap::iterator itr = cellName_ObjectMap.find(std::string(po_prefix));
if (itr == cellName_ObjectMap.end()) {
CellsAndSigs components;
for (auto cell : visitedCells) {
components.visitedCells.insert(cell);
}
for (auto sig : visitedSigSpec) {
components.visitedSigSpec.insert(sig);
}
cellName_ObjectMap.emplace(std::string(po_prefix), components);
} else {
CellsAndSigs &components = itr->second;
for (auto cell : visitedCells) {
components.visitedCells.insert(cell);
}
for (auto sig : visitedSigSpec) {
components.visitedSigSpec.insert(sig);
}
}
}
// Create submod attributes for the submod command
log("Creating submods\n");
log_flush();
for (CellName_ObjectMap::iterator itr = cellName_ObjectMap.begin(); itr != cellName_ObjectMap.end(); itr++) {
if (debug)
std::cout << "Cluster name: " << itr->first << std::endl;
CellsAndSigs &components = itr->second;
for (auto cell : components.visitedCells) {
cell->set_string_attribute(RTLIL::escape_id("submod"), itr->first);
if (debug)
std::cout << " CELL: " << cell->name.c_str() << std::endl;
}
}
// Execute the submod command
Pass::call(design, "submod");
log("End splitnetlist pass\n");
log_flush();
}
} SplitNetlist;
PRIVATE_NAMESPACE_END