Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cost: add keep_hierarchy pass with min_cost argument #4344

Merged
merged 1 commit into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,7 @@ $(eval $(call add_include_file,backends/rtlil/rtlil_backend.h))

OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
OBJS += kernel/binding.o
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o
OBJS += kernel/cellaigs.o kernel/celledges.o kernel/cost.o kernel/satgen.o kernel/scopeinfo.o kernel/qcsat.o kernel/mem.o kernel/ffmerge.o kernel/ff.o kernel/yw.o kernel/json.o kernel/fmt.o
ifeq ($(ENABLE_ZLIB),1)
OBJS += kernel/fstdata.o
endif
Expand Down
195 changes: 195 additions & 0 deletions kernel/cost.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
#include "kernel/cost.h"

USING_YOSYS_NAMESPACE

unsigned int CellCosts::get(RTLIL::Module *mod)
{
if (mod_cost_cache_.count(mod->name))
return mod_cost_cache_.at(mod->name);

unsigned int module_cost = 1;
for (auto c : mod->cells()) {
unsigned int new_cost = module_cost + get(c);
module_cost = new_cost >= module_cost ? new_cost : INT_MAX;
}

mod_cost_cache_[mod->name] = module_cost;
return module_cost;
}

static unsigned int y_coef(RTLIL::IdString type)
{
if (
// equality
type.in(ID($bweqx), ID($nex), ID($eqx)) ||
// basic logic
type.in(ID($and), ID($or), ID($xor), ID($xnor), ID($not)) ||
// mux
type.in(ID($bwmux), ID($mux)) ||
// others
type == ID($tribuf)) {
return 1;
} else if (type == ID($neg)) {
return 4;
} else if (type == ID($demux)) {
return 2;
} else if (type == ID($fa)) {
return 5;
} else if (type.in(ID($add), ID($sub), ID($alu))) {
// multi-bit adders
return 8;
} else if (type.in(ID($shl), ID($sshl))) {
// left shift
return 10;
}
return 0;
}

static unsigned int max_inp_coef(RTLIL::IdString type)
{
if (
// binop reduce
type.in(ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool)) ||
// others
type.in(ID($logic_not), ID($pmux), ID($bmux))) {
return 1;
} else if (
// equality
type.in(ID($eq), ID($ne)) ||
// logic
type.in(ID($logic_and), ID($logic_or))) {
return 2;
} else if (type == ID($lcu)) {
return 5;
} else if (type.in(ID($lt), ID($le), ID($ge), ID($gt))) {
// comparison
return 7;
}
return 0;
}

static unsigned int sum_coef(RTLIL::IdString type)
{
if (type.in(ID($shr), ID($sshr))) {
// right shift
return 4;
} else if (type.in(ID($shift), ID($shiftx))) {
// shift
return 8;
}
return 0;
}

static unsigned int is_div_mod(RTLIL::IdString type)
{
return (type == ID($div) || type == ID($divfloor) || type == ID($mod) || type == ID($modfloor));
}

static bool is_free(RTLIL::IdString type)
{
return (
// tags
type.in(ID($overwrite_tag), ID($set_tag), ID($original_tag), ID($get_tag)) ||
// formal
type.in(ID($check), ID($equiv), ID($initstate), ID($assert), ID($assume), ID($live), ID($cover), ID($fair)) ||
type.in(ID($allseq), ID($allconst), ID($anyseq), ID($anyconst), ID($anyinit)) ||
// utilities
type.in(ID($scopeinfo), ID($print)) ||
// real but free
type.in(ID($concat), ID($slice), ID($pos)) ||
// specify
type.in(ID($specrule), ID($specify2), ID($specify3)));
}

unsigned int max_inp_width(RTLIL::Cell *cell)
{
unsigned int max = 0;
RTLIL::IdString input_width_params[] = {
ID::WIDTH,
ID::A_WIDTH,
ID::B_WIDTH,
ID::S_WIDTH,
};

if (cell->type == ID($bmux))
return cell->getParam(ID::WIDTH).as_int() << cell->getParam(ID::S_WIDTH).as_int();

for (RTLIL::IdString param : input_width_params)
if (cell->hasParam(param))
max = std::max(max, (unsigned int)cell->getParam(param).as_int());
return max;
}

unsigned int port_width_sum(RTLIL::Cell *cell)
{
unsigned int sum = 0;
RTLIL::IdString port_width_params[] = {
ID::WIDTH, ID::A_WIDTH, ID::B_WIDTH, ID::S_WIDTH, ID::Y_WIDTH,
};

for (auto param : port_width_params)
if (cell->hasParam(param))
sum += cell->getParam(param).as_int();

return sum;
}

unsigned int CellCosts::get(RTLIL::Cell *cell)
{

// simple 1-bit cells
if (cmos_gate_cost().count(cell->type))
return 1;

if (design_ && design_->module(cell->type) && cell->parameters.empty()) {
log_debug("%s is a module, recurse\n", cell->name.c_str());
return get(design_->module(cell->type));
} else if (RTLIL::builtin_ff_cell_types().count(cell->type)) {
log_assert(cell->hasPort(ID::Q) && "Weird flip flop");
log_debug("%s is ff\n", cell->name.c_str());
return cell->getParam(ID::WIDTH).as_int();
} else if (y_coef(cell->type)) {
// linear with Y_WIDTH or WIDTH
log_assert((cell->hasParam(ID::Y_WIDTH) || cell->hasParam(ID::WIDTH)) && "Unknown width");
auto param = cell->hasParam(ID::Y_WIDTH) ? ID::Y_WIDTH : ID::WIDTH;
int width = cell->getParam(param).as_int();
if (cell->type == ID($demux))
width <<= cell->getParam(ID::S_WIDTH).as_int();
log_debug("%s Y*coef %d * %d\n", cell->name.c_str(), width, y_coef(cell->type));
return width * y_coef(cell->type);
} else if (sum_coef(cell->type)) {
// linear with sum of port widths
unsigned int sum = port_width_sum(cell);
log_debug("%s sum*coef %d * %d\n", cell->name.c_str(), sum, sum_coef(cell->type));
return sum * sum_coef(cell->type);
} else if (max_inp_coef(cell->type)) {
// linear with largest input width
unsigned int max = max_inp_width(cell);
log_debug("%s max*coef %d * %d\n", cell->name.c_str(), max, max_inp_coef(cell->type));
return max * max_inp_coef(cell->type);
} else if (is_div_mod(cell->type) || cell->type == ID($mul)) {
// quadratic with sum of port widths
unsigned int sum = port_width_sum(cell);
unsigned int coef = cell->type == ID($mul) ? 3 : 5;
log_debug("%s coef*(sum**2) %d * %d\n", cell->name.c_str(), coef, sum * sum);
return coef * sum * sum;
} else if (cell->type == ID($lut)) {
int width = cell->getParam(ID::WIDTH).as_int();
unsigned int cost = 1U << (unsigned int)width;
log_debug("%s is 2**%d\n", cell->name.c_str(), width);
return cost;
} else if (cell->type == ID($sop)) {
int width = cell->getParam(ID::WIDTH).as_int();
int depth = cell->getParam(ID::DEPTH).as_int();
log_debug("%s is (2*%d + 1)*%d\n", cell->name.c_str(), width, depth);
return (2 * width + 1) * depth;
} else if (is_free(cell->type)) {
log_debug("%s is free\n", cell->name.c_str());
return 0;
}
// TODO: $fsm $mem.* $macc
// ignored: $pow

log_warning("Can't determine cost of %s cell (%d parameters).\n", log_id(cell->type), GetSize(cell->parameters));
return 1;
}
65 changes: 24 additions & 41 deletions kernel/cost.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,17 @@ YOSYS_NAMESPACE_BEGIN

struct CellCosts
{

private:
dict<RTLIL::IdString, int> mod_cost_cache_;
Design *design_ = nullptr;

public:
CellCosts(RTLIL::Design *design) : design_(design) { }

static const dict<RTLIL::IdString, int>& default_gate_cost() {
// Default size heuristics for several common PDK standard cells
// used by abc and stat
static const dict<RTLIL::IdString, int> db = {
{ ID($_BUF_), 1 },
{ ID($_NOT_), 2 },
Expand All @@ -43,12 +53,14 @@ struct CellCosts
{ ID($_AOI4_), 7 },
{ ID($_OAI4_), 7 },
{ ID($_MUX_), 4 },
{ ID($_NMUX_), 4 }
{ ID($_NMUX_), 4 },
};
return db;
}

static const dict<RTLIL::IdString, int>& cmos_gate_cost() {
// Estimated CMOS transistor counts for several common PDK standard cells
// used by stat and optionally by abc
static const dict<RTLIL::IdString, int> db = {
{ ID($_BUF_), 1 },
{ ID($_NOT_), 2 },
Expand All @@ -65,50 +77,21 @@ struct CellCosts
{ ID($_AOI4_), 8 },
{ ID($_OAI4_), 8 },
{ ID($_MUX_), 12 },
{ ID($_NMUX_), 10 }
{ ID($_NMUX_), 10 },
{ ID($_DFF_P_), 16 },
{ ID($_DFF_N_), 16 },
};
return db;
}

dict<RTLIL::IdString, int> mod_cost_cache;
const dict<RTLIL::IdString, int> *gate_cost = nullptr;
Design *design = nullptr;

int get(RTLIL::IdString type) const
{
if (gate_cost && gate_cost->count(type))
return gate_cost->at(type);

log_warning("Can't determine cost of %s cell.\n", log_id(type));
return 1;
}

int get(RTLIL::Cell *cell)
{
if (gate_cost && gate_cost->count(cell->type))
return gate_cost->at(cell->type);

if (design && design->module(cell->type) && cell->parameters.empty())
{
RTLIL::Module *mod = design->module(cell->type);

if (mod->attributes.count(ID(cost)))
return mod->attributes.at(ID(cost)).as_int();

if (mod_cost_cache.count(mod->name))
return mod_cost_cache.at(mod->name);

int module_cost = 1;
for (auto c : mod->cells())
module_cost += get(c);

mod_cost_cache[mod->name] = module_cost;
return module_cost;
}

log_warning("Can't determine cost of %s cell (%d parameters).\n", log_id(cell->type), GetSize(cell->parameters));
return 1;
}
// Get the cell cost for a cell based on its parameters.
// This cost is an *approximate* upper bound for the number of gates that
// the cell will get mapped to with "opt -fast; techmap"
// The intended usage is for flattening heuristics and similar situations
unsigned int get(RTLIL::Cell *cell);
// Sum up the cell costs of all cells in the module
// and all its submodules recursively
unsigned int get(RTLIL::Module *mod);
Comment on lines +87 to +94
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have expected CellCosts to become a base class with the get functions virtual (pure, or maybe the costs for default_gate_cost()?), and the heuristic a derived class with a name like NumInternalGatesEstimate that'll make it immediately obvious at the point of use what's happening. Then the cmos and default costs are just other variants of cost models, rather than static functions that are for some reason defined in a class that now does something unrelated.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My get method operate on cells, the previously implemented estimation dicts operate on types. They don't share an interface. The dicts are provided over static functions, only using CellCosts as a namespace. I can move them out to make this distinction more explicit. I have previously done something like what you describe but moved away from it

};

YOSYS_NAMESPACE_END
Expand Down
2 changes: 0 additions & 2 deletions passes/cmds/stat.cc
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,6 @@ struct statdata_t

if (gate_costs.count(ctype))
tran_cnt += cnum * gate_costs.at(ctype);
else if (ctype.in(ID($_DFF_P_), ID($_DFF_N_)))
tran_cnt += cnum * 16;
else
*tran_cnt_exact = false;
}
Expand Down
1 change: 1 addition & 0 deletions passes/hierarchy/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
OBJS += passes/hierarchy/hierarchy.o
OBJS += passes/hierarchy/uniquify.o
OBJS += passes/hierarchy/submod.o
OBJS += passes/hierarchy/keep_hierarchy.o

74 changes: 74 additions & 0 deletions passes/hierarchy/keep_hierarchy.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <[email protected]>
*
* 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/cost.h"

USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN

struct KeepHierarchyPass : public Pass {
KeepHierarchyPass() : Pass("keep_hierarchy", "add the keep_hierarchy attribute") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" keep_hierarchy [options]\n");
log("\n");
log("Add the keep_hierarchy attribute.\n");
log("\n");
log(" -min_cost <min_cost>\n");
log(" only add the attribute to modules estimated to have more\n");
log(" than <min_cost> gates after simple techmapping. Intended\n");
log(" for tuning trade-offs between quality and yosys runtime.\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
unsigned int min_cost = 0;

log_header(design, "Executing KEEP_HIERARCHY pass.\n");

size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-min_cost" && argidx+1 < args.size()) {
min_cost = std::stoi(args[++argidx].c_str());
continue;
}
break;
}
extra_args(args, argidx, design);

CellCosts costs(design);

for (auto module : design->selected_modules()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's two options to consider for operations on modules, selected_modules() vs selected_whole_modules_warn(). I think in this case it makes sense to operate on partial modules (can be useful e.g. if you want to set keep_hierarchy on any module that contains a particular type of cell) but wanted to get everyone else's opinion too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The best thing to do would be to warn on partially selected modules, but still add them, which selected_whole_modules_warn doesn't do, so I'll leave it as-is

if (min_cost) {
unsigned int cost = costs.get(module);
if (cost > min_cost) {
log("Marking %s (module too big: %d > %d).\n", log_id(module), cost, min_cost);
module->set_bool_attribute(ID::keep_hierarchy);
}
} else {
log("Marking %s.\n", log_id(module));
module->set_bool_attribute(ID::keep_hierarchy);
}
}
}
} KeepHierarchyPass;

PRIVATE_NAMESPACE_END
Loading
Loading