3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-06-06 22:23:23 +00:00

opt_expr: refactor to worker class

This commit is contained in:
Emil J. Tywoniak 2025-03-20 15:44:22 +01:00
parent 0a15a23e8f
commit 39006c9145

View file

@ -29,11 +29,12 @@
USING_YOSYS_NAMESPACE USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN PRIVATE_NAMESPACE_BEGIN
bool did_something; // bool did_something;
int sort_fails = 0;
void replace_undriven(RTLIL::Module *module, const CellTypes &ct) [[nodiscard]]
bool replace_undriven(RTLIL::Module *module, const CellTypes &ct)
{ {
bool did_something = false;
SigMap sigmap(module); SigMap sigmap(module);
SigPool driven_signals; SigPool driven_signals;
SigPool used_signals; SigPool used_signals;
@ -115,10 +116,135 @@ void replace_undriven(RTLIL::Module *module, const CellTypes &ct)
} }
} }
} }
return did_something;
} }
void replace_cell(SigMap &assign_map, RTLIL::Module *module, RTLIL::Cell *cell, void handle_polarity_inv(Cell *cell, IdString port, IdString param, const SigMap &assign_map, const dict<RTLIL::SigSpec, RTLIL::SigSpec> &invert_map)
const std::string &info, IdString out_port, RTLIL::SigSpec out_val) {
SigSpec sig = assign_map(cell->getPort(port));
if (invert_map.count(sig)) {
log_debug("Inverting %s of %s cell `%s' in module `%s': %s -> %s\n",
log_id(port), log_id(cell->type), log_id(cell), log_id(cell->module),
log_signal(sig), log_signal(invert_map.at(sig)));
cell->setPort(port, (invert_map.at(sig)));
cell->setParam(param, !cell->getParam(param).as_bool());
}
}
void handle_clkpol_celltype_swap(Cell *cell, string type1, string type2, IdString port, const SigMap &assign_map, const dict<RTLIL::SigSpec, RTLIL::SigSpec> &invert_map)
{
log_assert(GetSize(type1) == GetSize(type2));
string cell_type = cell->type.str();
if (GetSize(type1) != GetSize(cell_type))
return;
for (int i = 0; i < GetSize(type1); i++) {
log_assert((type1[i] == '?') == (type2[i] == '?'));
if (type1[i] == '?') {
if (cell_type[i] != '0' && cell_type[i] != '1' && cell_type[i] != 'N' && cell_type[i] != 'P')
return;
type1[i] = cell_type[i];
type2[i] = cell_type[i];
}
}
if (cell->type.in(type1, type2)) {
SigSpec sig = assign_map(cell->getPort(port));
if (invert_map.count(sig)) {
log_debug("Inverting %s of %s cell `%s' in module `%s': %s -> %s\n",
log_id(port), log_id(cell->type), log_id(cell), log_id(cell->module),
log_signal(sig), log_signal(invert_map.at(sig)));
cell->setPort(port, (invert_map.at(sig)));
cell->type = cell->type == type1 ? type2 : type1;
}
}
}
bool is_one_or_minus_one(const Const &value, bool is_signed, bool &is_negative)
{
bool all_bits_one = true;
bool last_bit_one = true;
if (GetSize(value) < 1)
return false;
if (GetSize(value) == 1) {
if (value[0] != State::S1)
return false;
if (is_signed)
is_negative = true;
return true;
}
for (int i = 0; i < GetSize(value); i++) {
if (value[i] != State::S1)
all_bits_one = false;
if (value[i] != (i ? State::S0 : State::S1))
last_bit_one = false;
}
if (all_bits_one && is_signed) {
is_negative = true;
return true;
}
return last_bit_one;
}
int get_highest_hot_index(RTLIL::SigSpec signal)
{
for (int i = GetSize(signal) - 1; i >= 0; i--)
{
if (signal[i] == RTLIL::State::S0)
continue;
if (signal[i] == RTLIL::State::S1)
return i;
break;
}
return -1;
}
struct OptExprOptions
{
bool mux_undef;
bool mux_bool;
bool do_fine;
bool keepdc;
bool noclkinv;
bool undriven;
int effort;
};
struct OptExprWorker
{
RTLIL::Design *design;
RTLIL::Module *module;
const OptExprOptions &options;
CellTypes ct_memcells;
SigMap assign_map;
dict<RTLIL::SigSpec, RTLIL::SigSpec> invert_map;
int sort_fails = 0;
bool did_something = false;
OptExprWorker(Design *d, Module *mod, const OptExprOptions &os) : design(d), module(mod), options(os) {
ct_memcells.setup_stdcells_mem();
for (auto cell : module->cells()) {
if (design->selected(module, cell) && cell->type[0] == '$') {
if (cell->type.in(ID($_NOT_), ID($not), ID($logic_not)) &&
GetSize(cell->getPort(ID::A)) == 1 && GetSize(cell->getPort(ID::Y)) == 1)
invert_map[assign_map(cell->getPort(ID::Y))] = assign_map(cell->getPort(ID::A));
if (cell->type.in(ID($mux), ID($_MUX_)) &&
cell->getPort(ID::A) == SigSpec(State::S1) && cell->getPort(ID::B) == SigSpec(State::S0))
invert_map[assign_map(cell->getPort(ID::Y))] = assign_map(cell->getPort(ID::S));
}
}
}
private:
void replace_cell(RTLIL::Cell *cell, const std::string &info, IdString out_port, RTLIL::SigSpec out_val)
{ {
RTLIL::SigSpec Y = cell->getPort(out_port); RTLIL::SigSpec Y = cell->getPort(out_port);
out_val.extend_u0(Y.size(), false); out_val.extend_u0(Y.size(), false);
@ -131,7 +257,6 @@ void replace_cell(SigMap &assign_map, RTLIL::Module *module, RTLIL::Cell *cell,
module->remove(cell); module->remove(cell);
did_something = true; did_something = true;
} }
bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutative, SigMap &sigmap, bool keepdc) bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutative, SigMap &sigmap, bool keepdc)
{ {
IdString b_name = cell->hasPort(ID::B) ? ID::B : ID::A; IdString b_name = cell->hasPort(ID::B) ? ID::B : ID::A;
@ -303,116 +428,11 @@ bool group_cell_inputs(RTLIL::Module *module, RTLIL::Cell *cell, bool commutativ
did_something = true; did_something = true;
return true; return true;
} }
public:
void handle_polarity_inv(Cell *cell, IdString port, IdString param, const SigMap &assign_map, const dict<RTLIL::SigSpec, RTLIL::SigSpec> &invert_map) bool run(bool consume_x) {
{ did_something = false;
SigSpec sig = assign_map(cell->getPort(port)); assign_map = SigMap(module);
if (invert_map.count(sig)) { if (!options.noclkinv)
log_debug("Inverting %s of %s cell `%s' in module `%s': %s -> %s\n",
log_id(port), log_id(cell->type), log_id(cell), log_id(cell->module),
log_signal(sig), log_signal(invert_map.at(sig)));
cell->setPort(port, (invert_map.at(sig)));
cell->setParam(param, !cell->getParam(param).as_bool());
}
}
void handle_clkpol_celltype_swap(Cell *cell, string type1, string type2, IdString port, const SigMap &assign_map, const dict<RTLIL::SigSpec, RTLIL::SigSpec> &invert_map)
{
log_assert(GetSize(type1) == GetSize(type2));
string cell_type = cell->type.str();
if (GetSize(type1) != GetSize(cell_type))
return;
for (int i = 0; i < GetSize(type1); i++) {
log_assert((type1[i] == '?') == (type2[i] == '?'));
if (type1[i] == '?') {
if (cell_type[i] != '0' && cell_type[i] != '1' && cell_type[i] != 'N' && cell_type[i] != 'P')
return;
type1[i] = cell_type[i];
type2[i] = cell_type[i];
}
}
if (cell->type.in(type1, type2)) {
SigSpec sig = assign_map(cell->getPort(port));
if (invert_map.count(sig)) {
log_debug("Inverting %s of %s cell `%s' in module `%s': %s -> %s\n",
log_id(port), log_id(cell->type), log_id(cell), log_id(cell->module),
log_signal(sig), log_signal(invert_map.at(sig)));
cell->setPort(port, (invert_map.at(sig)));
cell->type = cell->type == type1 ? type2 : type1;
}
}
}
bool is_one_or_minus_one(const Const &value, bool is_signed, bool &is_negative)
{
bool all_bits_one = true;
bool last_bit_one = true;
if (GetSize(value) < 1)
return false;
if (GetSize(value) == 1) {
if (value[0] != State::S1)
return false;
if (is_signed)
is_negative = true;
return true;
}
for (int i = 0; i < GetSize(value); i++) {
if (value[i] != State::S1)
all_bits_one = false;
if (value[i] != (i ? State::S0 : State::S1))
last_bit_one = false;
}
if (all_bits_one && is_signed) {
is_negative = true;
return true;
}
return last_bit_one;
}
int get_highest_hot_index(RTLIL::SigSpec signal)
{
for (int i = GetSize(signal) - 1; i >= 0; i--)
{
if (signal[i] == RTLIL::State::S0)
continue;
if (signal[i] == RTLIL::State::S1)
return i;
break;
}
return -1;
}
void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool consume_x, bool mux_undef, bool mux_bool, bool do_fine, bool keepdc, bool noclkinv, int effort)
{
SigMap assign_map(module);
dict<RTLIL::SigSpec, RTLIL::SigSpec> invert_map;
for (auto cell : module->cells()) {
if (design->selected(module, cell) && cell->type[0] == '$') {
if (cell->type.in(ID($_NOT_), ID($not), ID($logic_not)) &&
GetSize(cell->getPort(ID::A)) == 1 && GetSize(cell->getPort(ID::Y)) == 1)
invert_map[assign_map(cell->getPort(ID::Y))] = assign_map(cell->getPort(ID::A));
if (cell->type.in(ID($mux), ID($_MUX_)) &&
cell->getPort(ID::A) == SigSpec(State::S1) && cell->getPort(ID::B) == SigSpec(State::S0))
invert_map[assign_map(cell->getPort(ID::Y))] = assign_map(cell->getPort(ID::S));
}
}
CellTypes ct_memcells;
ct_memcells.setup_stdcells_mem();
if (!noclkinv)
for (auto cell : module->cells()) for (auto cell : module->cells())
if (design->selected(module, cell)) { if (design->selected(module, cell)) {
if (cell->type.in(ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($fsm), ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2))) if (cell->type.in(ID($dff), ID($dffe), ID($dffsr), ID($dffsre), ID($adff), ID($adffe), ID($aldff), ID($aldffe), ID($sdff), ID($sdffe), ID($sdffce), ID($fsm), ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2)))
@ -492,7 +512,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
std::vector<Cell*> module_cells = module->cells(); std::vector<Cell*> module_cells = module->cells();
auto visitor = [&](auto&& do_action) { auto visitor = [&](auto&& do_action) {
if (sort_fails >= effort) { if (sort_fails >= options.effort) {
for (auto cell : module_cells) for (auto cell : module_cells)
if (design->selected(module, cell) && yosys_celltypes.cell_evaluable(cell->type)) if (design->selected(module, cell) && yosys_celltypes.cell_evaluable(cell->type))
do_action(cell); do_action(cell);
@ -524,9 +544,9 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
// ...unless this is a coarse-grained cell loop, but not a bit loop, in which case it won't, and all is good. // ...unless this is a coarse-grained cell loop, but not a bit loop, in which case it won't, and all is good.
log("Couldn't topologically sort cells, optimizing module %s may take a longer time.\n", log_id(module)); log("Couldn't topologically sort cells, optimizing module %s may take a longer time.\n", log_id(module));
sort_fails++; sort_fails++;
if (sort_fails >= effort) if (sort_fails >= options.effort)
log("Effort of %d exceeded, no longer attempting toposort on module %s.\n", log("Effort of %d exceeded, no longer attempting toposort on module %s.\n",
effort, log_id(module)); options.effort, log_id(module));
} }
for (auto cell : cells.sorted) { for (auto cell : cells.sorted) {
do_action(cell); do_action(cell);
@ -535,7 +555,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
}; };
visitor([&](auto& cell) visitor([&](auto& cell)
{ {
#define ACTION_DO(_p_, _s_) do { cover("opt.opt_expr.action_" S__LINE__); replace_cell(assign_map, module, cell, input.as_string(), _p_, _s_); goto next_cell; } while (0) #define ACTION_DO(_p_, _s_) do { cover("opt.opt_expr.action_" S__LINE__); replace_cell(cell, input.as_string(), _p_, _s_); goto next_cell; } while (0)
#define ACTION_DO_Y(_v_) ACTION_DO(ID::Y, RTLIL::SigSpec(RTLIL::State::S ## _v_)) #define ACTION_DO_Y(_v_) ACTION_DO(ID::Y, RTLIL::SigSpec(RTLIL::State::S ## _v_))
bool detect_const_and = false; bool detect_const_and = false;
@ -583,19 +603,19 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
if (detect_const_and && (found_zero || found_inv || (found_undef && consume_x))) { if (detect_const_and && (found_zero || found_inv || (found_undef && consume_x))) {
cover("opt.opt_expr.const_and"); cover("opt.opt_expr.const_and");
replace_cell(assign_map, module, cell, "const_and", ID::Y, RTLIL::State::S0); replace_cell(cell, "const_and", ID::Y, RTLIL::State::S0);
goto next_cell; goto next_cell;
} }
if (detect_const_or && (found_one || found_inv || (found_undef && consume_x))) { if (detect_const_or && (found_one || found_inv || (found_undef && consume_x))) {
cover("opt.opt_expr.const_or"); cover("opt.opt_expr.const_or");
replace_cell(assign_map, module, cell, "const_or", ID::Y, RTLIL::State::S1); replace_cell(cell, "const_or", ID::Y, RTLIL::State::S1);
goto next_cell; goto next_cell;
} }
if (non_const_input != State::Sm && !found_undef) { if (non_const_input != State::Sm && !found_undef) {
cover("opt.opt_expr.and_or_buffer"); cover("opt.opt_expr.and_or_buffer");
replace_cell(assign_map, module, cell, "and_or_buffer", ID::Y, non_const_input); replace_cell(cell, "and_or_buffer", ID::Y, non_const_input);
goto next_cell; goto next_cell;
} }
} }
@ -604,17 +624,17 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
{ {
SigBit sig_a = assign_map(cell->getPort(ID::A)); SigBit sig_a = assign_map(cell->getPort(ID::A));
SigBit sig_b = assign_map(cell->getPort(ID::B)); SigBit sig_b = assign_map(cell->getPort(ID::B));
if (!keepdc && (sig_a == sig_b || sig_a == State::Sx || sig_a == State::Sz || sig_b == State::Sx || sig_b == State::Sz)) { if (!options.keepdc && (sig_a == sig_b || sig_a == State::Sx || sig_a == State::Sz || sig_b == State::Sx || sig_b == State::Sz)) {
if (cell->type.in(ID($xor), ID($_XOR_))) { if (cell->type.in(ID($xor), ID($_XOR_))) {
cover("opt.opt_expr.const_xor"); cover("opt.opt_expr.const_xor");
replace_cell(assign_map, module, cell, "const_xor", ID::Y, RTLIL::State::S0); replace_cell(cell, "const_xor", ID::Y, RTLIL::State::S0);
goto next_cell; goto next_cell;
} }
if (cell->type.in(ID($xnor), ID($_XNOR_))) { if (cell->type.in(ID($xnor), ID($_XNOR_))) {
cover("opt.opt_expr.const_xnor"); cover("opt.opt_expr.const_xnor");
// For consistency since simplemap does $xnor -> $_XOR_ + $_NOT_ // For consistency since simplemap does $xnor -> $_XOR_ + $_NOT_
int width = GetSize(cell->getPort(ID::Y)); int width = GetSize(cell->getPort(ID::Y));
replace_cell(assign_map, module, cell, "const_xnor", ID::Y, SigSpec(RTLIL::State::S1, width)); replace_cell(cell, "const_xnor", ID::Y, SigSpec(RTLIL::State::S1, width));
goto next_cell; goto next_cell;
} }
log_abort(); log_abort();
@ -631,7 +651,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
else if (cell->type == ID($_XOR_)) else if (cell->type == ID($_XOR_))
sig_y = (sig_b == State::S1 ? module->NotGate(NEW_ID, sig_a) : sig_a); sig_y = (sig_b == State::S1 ? module->NotGate(NEW_ID, sig_a) : sig_a);
else log_abort(); else log_abort();
replace_cell(assign_map, module, cell, "xor_buffer", ID::Y, sig_y); replace_cell(cell, "xor_buffer", ID::Y, sig_y);
goto next_cell; goto next_cell;
} }
if (cell->type.in(ID($xnor), ID($_XNOR_))) { if (cell->type.in(ID($xnor), ID($_XNOR_))) {
@ -645,7 +665,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
else if (cell->type == ID($_XNOR_)) else if (cell->type == ID($_XNOR_))
sig_y = (sig_b == State::S1 ? sig_a : module->NotGate(NEW_ID, sig_a)); sig_y = (sig_b == State::S1 ? sig_a : module->NotGate(NEW_ID, sig_a));
else log_abort(); else log_abort();
replace_cell(assign_map, module, cell, "xnor_buffer", ID::Y, sig_y); replace_cell(cell, "xnor_buffer", ID::Y, sig_y);
goto next_cell; goto next_cell;
} }
log_abort(); log_abort();
@ -663,7 +683,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
did_something = true; did_something = true;
} else { } else {
cover("opt.opt_expr.unary_buffer"); cover("opt.opt_expr.unary_buffer");
replace_cell(assign_map, module, cell, "unary_buffer", ID::Y, cell->getPort(ID::A)); replace_cell(cell, "unary_buffer", ID::Y, cell->getPort(ID::A));
} }
goto next_cell; goto next_cell;
} }
@ -673,8 +693,8 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec sig_a = assign_map(cell->getPort(ID::A));
RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B)); RTLIL::SigSpec sig_b = assign_map(cell->getPort(ID::B));
bool a_fully_const = (sig_a.is_fully_const() && (!keepdc || !sig_a.is_fully_undef())); bool a_fully_const = (sig_a.is_fully_const() && (!options.keepdc || !sig_a.is_fully_undef()));
bool b_fully_const = (sig_b.is_fully_const() && (!keepdc || !sig_b.is_fully_undef())); bool b_fully_const = (sig_b.is_fully_const() && (!options.keepdc || !sig_b.is_fully_undef()));
if (a_fully_const != b_fully_const) if (a_fully_const != b_fully_const)
{ {
@ -712,7 +732,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
if (!y_group_0.empty()) y_new_0 = Const(State::S0, GetSize(y_group_0)); if (!y_group_0.empty()) y_new_0 = Const(State::S0, GetSize(y_group_0));
if (!y_group_1.empty()) y_new_1 = b_group_1; if (!y_group_1.empty()) y_new_1 = b_group_1;
if (!y_group_x.empty()) { if (!y_group_x.empty()) {
if (keepdc) if (options.keepdc)
y_new_x = module->And(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); y_new_x = module->And(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x);
else else
y_new_x = Const(State::S0, GetSize(y_group_x)); y_new_x = Const(State::S0, GetSize(y_group_x));
@ -721,7 +741,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
if (!y_group_0.empty()) y_new_0 = b_group_0; if (!y_group_0.empty()) y_new_0 = b_group_0;
if (!y_group_1.empty()) y_new_1 = Const(State::S1, GetSize(y_group_1)); if (!y_group_1.empty()) y_new_1 = Const(State::S1, GetSize(y_group_1));
if (!y_group_x.empty()) { if (!y_group_x.empty()) {
if (keepdc) if (options.keepdc)
y_new_x = module->Or(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); y_new_x = module->Or(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x);
else else
y_new_x = Const(State::S1, GetSize(y_group_x)); y_new_x = Const(State::S1, GetSize(y_group_x));
@ -730,7 +750,7 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
if (!y_group_0.empty()) y_new_0 = b_group_0; if (!y_group_0.empty()) y_new_0 = b_group_0;
if (!y_group_1.empty()) y_new_1 = module->Not(NEW_ID, b_group_1); if (!y_group_1.empty()) y_new_1 = module->Not(NEW_ID, b_group_1);
if (!y_group_x.empty()) { if (!y_group_x.empty()) {
if (keepdc) if (options.keepdc)
y_new_x = module->Xor(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x); y_new_x = module->Xor(NEW_ID, Const(State::Sx, GetSize(y_group_x)), b_group_x);
else // This should be fine even with keepdc, but opt_expr_xor.ys wants to keep the xor else // This should be fine even with keepdc, but opt_expr_xor.ys wants to keep the xor
y_new_x = Const(State::Sx, GetSize(y_group_x)); y_new_x = Const(State::Sx, GetSize(y_group_x));
@ -810,10 +830,10 @@ void replace_const_cells(RTLIL::Design *design, RTLIL::Module *module, bool cons
} }
} }
if (do_fine) if (options.do_fine)
{ {
if (cell->type.in(ID($not), ID($pos), ID($and), ID($or), ID($xor), ID($xnor))) if (cell->type.in(ID($not), ID($pos), ID($and), ID($or), ID($xor), ID($xnor)))
if (group_cell_inputs(module, cell, true, assign_map, keepdc)) if (group_cell_inputs(module, cell, true, assign_map, options.keepdc))
goto next_cell; goto next_cell;
if (cell->type.in(ID($logic_not), ID($logic_and), ID($logic_or), ID($reduce_or), ID($reduce_and), ID($reduce_bool))) if (cell->type.in(ID($logic_not), ID($logic_and), ID($logic_or), ID($reduce_or), ID($reduce_and), ID($reduce_bool)))
@ -1065,9 +1085,9 @@ skip_fine_alu:
cover_list("opt.opt_expr.xbit", "$reduce_xor", "$reduce_xnor", "$shl", "$shr", "$sshl", "$sshr", "$shift", "$shiftx", cover_list("opt.opt_expr.xbit", "$reduce_xor", "$reduce_xnor", "$shl", "$shr", "$sshl", "$sshr", "$shift", "$shiftx",
"$lt", "$le", "$ge", "$gt", "$neg", "$add", "$sub", "$mul", "$div", "$mod", "$divfloor", "$modfloor", "$pow", cell->type.str()); "$lt", "$le", "$ge", "$gt", "$neg", "$add", "$sub", "$mul", "$div", "$mod", "$divfloor", "$modfloor", "$pow", cell->type.str());
if (cell->type.in(ID($reduce_xor), ID($reduce_xnor), ID($lt), ID($le), ID($ge), ID($gt))) if (cell->type.in(ID($reduce_xor), ID($reduce_xnor), ID($lt), ID($le), ID($ge), ID($gt)))
replace_cell(assign_map, module, cell, "x-bit in input", ID::Y, RTLIL::State::Sx); replace_cell(cell, "x-bit in input", ID::Y, RTLIL::State::Sx);
else else
replace_cell(assign_map, module, cell, "x-bit in input", ID::Y, RTLIL::SigSpec(RTLIL::State::Sx, GetSize(cell->getPort(ID::Y)))); replace_cell(cell, "x-bit in input", ID::Y, RTLIL::SigSpec(RTLIL::State::Sx, GetSize(cell->getPort(ID::Y))));
goto next_cell; goto next_cell;
} }
} }
@ -1075,7 +1095,7 @@ skip_fine_alu:
if (cell->type.in(ID($shiftx), ID($shift)) && (cell->type == ID($shiftx) || !cell->getParam(ID::A_SIGNED).as_bool())) { if (cell->type.in(ID($shiftx), ID($shift)) && (cell->type == ID($shiftx) || !cell->getParam(ID::A_SIGNED).as_bool())) {
SigSpec sig_a = assign_map(cell->getPort(ID::A)); SigSpec sig_a = assign_map(cell->getPort(ID::A));
int width; int width;
bool trim_x = cell->type == ID($shiftx) || !keepdc; bool trim_x = cell->type == ID($shiftx) || !options.keepdc;
bool trim_0 = cell->type == ID($shift); bool trim_0 = cell->type == ID($shift);
for (width = GetSize(sig_a); width > 1; width--) { for (width = GetSize(sig_a); width > 1; width--) {
if ((trim_x && sig_a[width-1] == State::Sx) || if ((trim_x && sig_a[width-1] == State::Sx) ||
@ -1097,7 +1117,7 @@ skip_fine_alu:
if (cell->type.in(ID($_NOT_), ID($not), ID($logic_not)) && GetSize(cell->getPort(ID::Y)) == 1 && if (cell->type.in(ID($_NOT_), ID($not), ID($logic_not)) && GetSize(cell->getPort(ID::Y)) == 1 &&
invert_map.count(assign_map(cell->getPort(ID::A))) != 0) { invert_map.count(assign_map(cell->getPort(ID::A))) != 0) {
cover_list("opt.opt_expr.invert.double", "$_NOT_", "$not", "$logic_not", cell->type.str()); cover_list("opt.opt_expr.invert.double", "$_NOT_", "$not", "$logic_not", cell->type.str());
replace_cell(assign_map, module, cell, "double_invert", ID::Y, invert_map.at(assign_map(cell->getPort(ID::A)))); replace_cell(cell, "double_invert", ID::Y, invert_map.at(assign_map(cell->getPort(ID::A))));
goto next_cell; goto next_cell;
} }
@ -1197,7 +1217,7 @@ skip_fine_alu:
if (input.match("** ")) ACTION_DO_Y(x); if (input.match("** ")) ACTION_DO_Y(x);
if (input.match("01*")) ACTION_DO_Y(x); if (input.match("01*")) ACTION_DO_Y(x);
if (input.match("10*")) ACTION_DO_Y(x); if (input.match("10*")) ACTION_DO_Y(x);
if (mux_undef) { if (options.mux_undef) {
if (input.match("* ")) ACTION_DO(ID::Y, input.extract(1, 1)); if (input.match("* ")) ACTION_DO(ID::Y, input.extract(1, 1));
if (input.match(" * ")) ACTION_DO(ID::Y, input.extract(2, 1)); if (input.match(" * ")) ACTION_DO(ID::Y, input.extract(2, 1));
if (input.match(" *")) ACTION_DO(ID::Y, input.extract(2, 1)); if (input.match(" *")) ACTION_DO(ID::Y, input.extract(2, 1));
@ -1240,7 +1260,7 @@ skip_fine_alu:
cover_list("opt.opt_expr.eqneq.isneq", "$eq", "$ne", "$eqx", "$nex", cell->type.str()); cover_list("opt.opt_expr.eqneq.isneq", "$eq", "$ne", "$eqx", "$nex", cell->type.str());
RTLIL::SigSpec new_y = RTLIL::SigSpec(cell->type.in(ID($eq), ID($eqx)) ? RTLIL::State::S0 : RTLIL::State::S1); RTLIL::SigSpec new_y = RTLIL::SigSpec(cell->type.in(ID($eq), ID($eqx)) ? RTLIL::State::S0 : RTLIL::State::S1);
new_y.extend_u0(cell->parameters[ID::Y_WIDTH].as_int(), false); new_y.extend_u0(cell->parameters[ID::Y_WIDTH].as_int(), false);
replace_cell(assign_map, module, cell, "isneq", ID::Y, new_y); replace_cell(cell, "isneq", ID::Y, new_y);
goto next_cell; goto next_cell;
} }
if (a[i] == b[i]) if (a[i] == b[i])
@ -1253,7 +1273,7 @@ skip_fine_alu:
cover_list("opt.opt_expr.eqneq.empty", "$eq", "$ne", "$eqx", "$nex", cell->type.str()); cover_list("opt.opt_expr.eqneq.empty", "$eq", "$ne", "$eqx", "$nex", cell->type.str());
RTLIL::SigSpec new_y = RTLIL::SigSpec(cell->type.in(ID($eq), ID($eqx)) ? RTLIL::State::S1 : RTLIL::State::S0); RTLIL::SigSpec new_y = RTLIL::SigSpec(cell->type.in(ID($eq), ID($eqx)) ? RTLIL::State::S1 : RTLIL::State::S0);
new_y.extend_u0(cell->parameters[ID::Y_WIDTH].as_int(), false); new_y.extend_u0(cell->parameters[ID::Y_WIDTH].as_int(), false);
replace_cell(assign_map, module, cell, "empty", ID::Y, new_y); replace_cell(cell, "empty", ID::Y, new_y);
goto next_cell; goto next_cell;
} }
@ -1319,7 +1339,7 @@ skip_fine_alu:
goto next_cell; goto next_cell;
} }
if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx)) && (keepdc ? assign_map(cell->getPort(ID::B)).is_fully_def() : assign_map(cell->getPort(ID::B)).is_fully_const())) if (cell->type.in(ID($shl), ID($shr), ID($sshl), ID($sshr), ID($shift), ID($shiftx)) && (options.keepdc ? assign_map(cell->getPort(ID::B)).is_fully_def() : assign_map(cell->getPort(ID::B)).is_fully_const()))
{ {
bool sign_ext = cell->type == ID($sshr) && cell->getParam(ID::A_SIGNED).as_bool(); bool sign_ext = cell->type == ID($sshr) && cell->getParam(ID::A_SIGNED).as_bool();
int shift_bits = assign_map(cell->getPort(ID::B)).as_int(cell->type.in(ID($shift), ID($shiftx)) && cell->getParam(ID::B_SIGNED).as_bool()); int shift_bits = assign_map(cell->getPort(ID::B)).as_int(cell->type.in(ID($shift), ID($shiftx)) && cell->getParam(ID::B_SIGNED).as_bool());
@ -1466,14 +1486,14 @@ skip_fine_alu:
} }
skip_identity: skip_identity:
if (mux_bool && cell->type.in(ID($mux), ID($_MUX_)) && if (options.mux_bool && cell->type.in(ID($mux), ID($_MUX_)) &&
cell->getPort(ID::A) == State::S0 && cell->getPort(ID::B) == State::S1) { cell->getPort(ID::A) == State::S0 && cell->getPort(ID::B) == State::S1) {
cover_list("opt.opt_expr.mux_bool", "$mux", "$_MUX_", cell->type.str()); cover_list("opt.opt_expr.mux_bool", "$mux", "$_MUX_", cell->type.str());
replace_cell(assign_map, module, cell, "mux_bool", ID::Y, cell->getPort(ID::S)); replace_cell(cell, "mux_bool", ID::Y, cell->getPort(ID::S));
goto next_cell; goto next_cell;
} }
if (mux_bool && cell->type.in(ID($mux), ID($_MUX_)) && if (options.mux_bool && cell->type.in(ID($mux), ID($_MUX_)) &&
cell->getPort(ID::A) == State::S1 && cell->getPort(ID::B) == State::S0) { cell->getPort(ID::A) == State::S1 && cell->getPort(ID::B) == State::S0) {
cover_list("opt.opt_expr.mux_invert", "$mux", "$_MUX_", cell->type.str()); cover_list("opt.opt_expr.mux_invert", "$mux", "$_MUX_", cell->type.str());
log_debug("Replacing %s cell `%s' in module `%s' with inverter.\n", log_id(cell->type), log_id(cell), log_id(module)); log_debug("Replacing %s cell `%s' in module `%s' with inverter.\n", log_id(cell->type), log_id(cell), log_id(module));
@ -1493,7 +1513,7 @@ skip_identity:
goto next_cell; goto next_cell;
} }
if (consume_x && mux_bool && cell->type.in(ID($mux), ID($_MUX_)) && cell->getPort(ID::A) == State::S0) { if (consume_x && options.mux_bool && cell->type.in(ID($mux), ID($_MUX_)) && cell->getPort(ID::A) == State::S0) {
cover_list("opt.opt_expr.mux_and", "$mux", "$_MUX_", cell->type.str()); cover_list("opt.opt_expr.mux_and", "$mux", "$_MUX_", cell->type.str());
log_debug("Replacing %s cell `%s' in module `%s' with and-gate.\n", log_id(cell->type), log_id(cell), log_id(module)); log_debug("Replacing %s cell `%s' in module `%s' with and-gate.\n", log_id(cell->type), log_id(cell), log_id(module));
cell->setPort(ID::A, cell->getPort(ID::S)); cell->setPort(ID::A, cell->getPort(ID::S));
@ -1513,7 +1533,7 @@ skip_identity:
goto next_cell; goto next_cell;
} }
if (consume_x && mux_bool && cell->type.in(ID($mux), ID($_MUX_)) && cell->getPort(ID::B) == State::S1) { if (consume_x && options.mux_bool && cell->type.in(ID($mux), ID($_MUX_)) && cell->getPort(ID::B) == State::S1) {
cover_list("opt.opt_expr.mux_or", "$mux", "$_MUX_", cell->type.str()); cover_list("opt.opt_expr.mux_or", "$mux", "$_MUX_", cell->type.str());
log_debug("Replacing %s cell `%s' in module `%s' with or-gate.\n", log_id(cell->type), log_id(cell), log_id(module)); log_debug("Replacing %s cell `%s' in module `%s' with or-gate.\n", log_id(cell->type), log_id(cell), log_id(module));
cell->setPort(ID::B, cell->getPort(ID::S)); cell->setPort(ID::B, cell->getPort(ID::S));
@ -1533,13 +1553,13 @@ skip_identity:
goto next_cell; goto next_cell;
} }
if (mux_undef && cell->type.in(ID($mux), ID($pmux))) { if (options.mux_undef && cell->type.in(ID($mux), ID($pmux))) {
RTLIL::SigSpec new_a, new_b, new_s; RTLIL::SigSpec new_a, new_b, new_s;
int width = GetSize(cell->getPort(ID::A)); int width = GetSize(cell->getPort(ID::A));
if ((cell->getPort(ID::A).is_fully_undef() && cell->getPort(ID::B).is_fully_undef()) || if ((cell->getPort(ID::A).is_fully_undef() && cell->getPort(ID::B).is_fully_undef()) ||
cell->getPort(ID::S).is_fully_undef()) { cell->getPort(ID::S).is_fully_undef()) {
cover_list("opt.opt_expr.mux_undef", "$mux", "$pmux", cell->type.str()); cover_list("opt.opt_expr.mux_undef", "$mux", "$pmux", cell->type.str());
replace_cell(assign_map, module, cell, "mux_undef", ID::Y, cell->getPort(ID::A)); replace_cell(cell, "mux_undef", ID::Y, cell->getPort(ID::A));
goto next_cell; goto next_cell;
} }
for (int i = 0; i < cell->getPort(ID::S).size(); i++) { for (int i = 0; i < cell->getPort(ID::S).size(); i++) {
@ -1558,12 +1578,12 @@ skip_identity:
} }
if (new_s.size() == 0) { if (new_s.size() == 0) {
cover_list("opt.opt_expr.mux_empty", "$mux", "$pmux", cell->type.str()); cover_list("opt.opt_expr.mux_empty", "$mux", "$pmux", cell->type.str());
replace_cell(assign_map, module, cell, "mux_empty", ID::Y, new_a); replace_cell(cell, "mux_empty", ID::Y, new_a);
goto next_cell; goto next_cell;
} }
if (new_a == RTLIL::SigSpec(RTLIL::State::S0) && new_b == RTLIL::SigSpec(RTLIL::State::S1)) { if (new_a == RTLIL::SigSpec(RTLIL::State::S0) && new_b == RTLIL::SigSpec(RTLIL::State::S1)) {
cover_list("opt.opt_expr.mux_sel01", "$mux", "$pmux", cell->type.str()); cover_list("opt.opt_expr.mux_sel01", "$mux", "$pmux", cell->type.str());
replace_cell(assign_map, module, cell, "mux_sel01", ID::Y, new_s); replace_cell(cell, "mux_sel01", ID::Y, new_s);
goto next_cell; goto next_cell;
} }
if (cell->getPort(ID::S).size() != new_s.size()) { if (cell->getPort(ID::S).size() != new_s.size()) {
@ -1594,7 +1614,7 @@ skip_identity:
cell->parameters[ID::A_SIGNED].as_bool(), false, \ cell->parameters[ID::A_SIGNED].as_bool(), false, \
cell->parameters[ID::Y_WIDTH].as_int())); \ cell->parameters[ID::Y_WIDTH].as_int())); \
cover("opt.opt_expr.const.$" #_t); \ cover("opt.opt_expr.const.$" #_t); \
replace_cell(assign_map, module, cell, stringf("%s", log_signal(a)), ID::Y, y); \ replace_cell(cell, stringf("%s", log_signal(a)), ID::Y, y); \
goto next_cell; \ goto next_cell; \
} \ } \
} }
@ -1609,7 +1629,7 @@ skip_identity:
cell->parameters[ID::B_SIGNED].as_bool(), \ cell->parameters[ID::B_SIGNED].as_bool(), \
cell->parameters[ID::Y_WIDTH].as_int())); \ cell->parameters[ID::Y_WIDTH].as_int())); \
cover("opt.opt_expr.const.$" #_t); \ cover("opt.opt_expr.const.$" #_t); \
replace_cell(assign_map, module, cell, stringf("%s, %s", log_signal(a), log_signal(b)), ID::Y, y); \ replace_cell(cell, stringf("%s, %s", log_signal(a), log_signal(b)), ID::Y, y); \
goto next_cell; \ goto next_cell; \
} \ } \
} }
@ -1621,7 +1641,7 @@ skip_identity:
if (a.is_fully_const() && b.is_fully_const()) { \ if (a.is_fully_const() && b.is_fully_const()) { \
RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const())); \ RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const())); \
cover("opt.opt_expr.const.$" #_t); \ cover("opt.opt_expr.const.$" #_t); \
replace_cell(assign_map, module, cell, stringf("%s, %s", log_signal(a), log_signal(b)), ID::Y, y); \ replace_cell(cell, stringf("%s, %s", log_signal(a), log_signal(b)), ID::Y, y); \
goto next_cell; \ goto next_cell; \
} \ } \
} }
@ -1634,7 +1654,7 @@ skip_identity:
if (a.is_fully_const() && b.is_fully_const() && s.is_fully_const()) { \ if (a.is_fully_const() && b.is_fully_const() && s.is_fully_const()) { \
RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const(), s.as_const())); \ RTLIL::SigSpec y(RTLIL::const_ ## _t(a.as_const(), b.as_const(), s.as_const())); \
cover("opt.opt_expr.const.$" #_t); \ cover("opt.opt_expr.const.$" #_t); \
replace_cell(assign_map, module, cell, stringf("%s, %s, %s", log_signal(a), log_signal(b), log_signal(s)), ID::Y, y); \ replace_cell(cell, stringf("%s, %s, %s", log_signal(a), log_signal(b), log_signal(s)), ID::Y, y); \
goto next_cell; \ goto next_cell; \
} \ } \
} }
@ -1696,13 +1716,13 @@ skip_identity:
RTLIL::SigSpec input = assign_map(cell->getPort(ID::S)); RTLIL::SigSpec input = assign_map(cell->getPort(ID::S));
RTLIL::SigSpec inA = assign_map(cell->getPort(ID::A)); RTLIL::SigSpec inA = assign_map(cell->getPort(ID::A));
RTLIL::SigSpec inB = assign_map(cell->getPort(ID::B)); RTLIL::SigSpec inB = assign_map(cell->getPort(ID::B));
if (input.is_fully_const() && (!keepdc || input.is_fully_def())) if (input.is_fully_const() && (!options.keepdc || input.is_fully_def()))
ACTION_DO(ID::Y, input.as_bool() ? cell->getPort(ID::B) : cell->getPort(ID::A)); ACTION_DO(ID::Y, input.as_bool() ? cell->getPort(ID::B) : cell->getPort(ID::A));
else if (inA == inB) else if (inA == inB)
ACTION_DO(ID::Y, cell->getPort(ID::A)); ACTION_DO(ID::Y, cell->getPort(ID::A));
} }
if (!keepdc && cell->type == ID($mul)) if (!options.keepdc && cell->type == ID($mul))
{ {
bool a_signed = cell->parameters[ID::A_SIGNED].as_bool(); bool a_signed = cell->parameters[ID::A_SIGNED].as_bool();
bool b_signed = cell->parameters[ID::B_SIGNED].as_bool(); bool b_signed = cell->parameters[ID::B_SIGNED].as_bool();
@ -1828,7 +1848,7 @@ skip_identity:
} }
int exp; int exp;
if (!keepdc && sig_b.is_onehot(&exp) && !(b_signed && exp == GetSize(sig_b) - 1)) if (!options.keepdc && sig_b.is_onehot(&exp) && !(b_signed && exp == GetSize(sig_b) - 1))
{ {
if (cell->type.in(ID($div), ID($divfloor))) if (cell->type.in(ID($div), ID($divfloor)))
{ {
@ -1904,7 +1924,7 @@ skip_identity:
} }
// Find places in $alu cell where the carry is constant, and split it at these points. // Find places in $alu cell where the carry is constant, and split it at these points.
if (do_fine && !keepdc && cell->type == ID($alu)) if (options.do_fine && !options.keepdc && cell->type == ID($alu))
{ {
bool a_signed = cell->parameters[ID::A_SIGNED].as_bool(); bool a_signed = cell->parameters[ID::A_SIGNED].as_bool();
bool b_signed = cell->parameters[ID::B_SIGNED].as_bool(); bool b_signed = cell->parameters[ID::B_SIGNED].as_bool();
@ -1998,7 +2018,7 @@ skip_alu_split:
// remove redundant pairs of bits in ==, ===, !=, and !== // remove redundant pairs of bits in ==, ===, !=, and !==
// replace cell with const driver if inputs can't be equal // replace cell with const driver if inputs can't be equal
if (do_fine && cell->type.in(ID($eq), ID($ne), ID($eqx), ID($nex))) if (options.do_fine && cell->type.in(ID($eq), ID($ne), ID($eqx), ID($nex)))
{ {
pool<pair<SigBit, SigBit>> redundant_cache; pool<pair<SigBit, SigBit>> redundant_cache;
mfp<SigBit> contradiction_cache; mfp<SigBit> contradiction_cache;
@ -2072,7 +2092,7 @@ skip_alu_split:
} }
// simplify comparisons // simplify comparisons
if (do_fine && cell->type.in(ID($lt), ID($ge), ID($gt), ID($le))) if (options.do_fine && cell->type.in(ID($lt), ID($ge), ID($gt), ID($le)))
{ {
IdString cmp_type = cell->type; IdString cmp_type = cell->type;
SigSpec var_sig = cell->getPort(ID::A); SigSpec var_sig = cell->getPort(ID::A);
@ -2214,10 +2234,13 @@ skip_alu_split:
#undef FOLD_1ARG_CELL #undef FOLD_1ARG_CELL
#undef FOLD_2ARG_CELL #undef FOLD_2ARG_CELL
}); });
return did_something;
} }
};
void replace_const_connections(RTLIL::Module *module) { bool replace_const_connections(RTLIL::Module *module) {
SigMap assign_map(module); SigMap assign_map(module);
bool did_something = false;
for (auto cell : module->selected_cells()) for (auto cell : module->selected_cells())
{ {
std::vector<std::pair<RTLIL::IdString, SigSpec>> changes; std::vector<std::pair<RTLIL::IdString, SigSpec>> changes;
@ -2231,6 +2254,7 @@ void replace_const_connections(RTLIL::Module *module) {
for (auto &it : changes) for (auto &it : changes)
cell->setPort(it.first, it.second); cell->setPort(it.first, it.second);
} }
return did_something;
} }
struct OptExprPass : public Pass { struct OptExprPass : public Pass {
@ -2275,51 +2299,53 @@ struct OptExprPass : public Pass {
} }
void execute(std::vector<std::string> args, RTLIL::Design *design) override void execute(std::vector<std::string> args, RTLIL::Design *design) override
{ {
bool mux_undef = false; OptExprOptions options = {
bool mux_bool = false; false,
bool undriven = false; false,
bool noclkinv = false; false,
bool do_fine = false; false,
bool keepdc = false; false,
int effort = 5; false,
5
};
log_header(design, "Executing OPT_EXPR pass (perform const folding).\n"); log_header(design, "Executing OPT_EXPR pass (perform const folding).\n");
log_push(); log_push();
size_t argidx; size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) { for (argidx = 1; argidx < args.size(); argidx++) {
if (args[argidx] == "-mux_undef") { if (args[argidx] == "-mux_undef") {
mux_undef = true; options.mux_undef = true;
continue; continue;
} }
if (args[argidx] == "-mux_bool") { if (args[argidx] == "-mux_bool") {
mux_bool = true; options.mux_bool = true;
continue; continue;
} }
if (args[argidx] == "-undriven") { if (args[argidx] == "-undriven") {
undriven = true; options.undriven = true;
continue; continue;
} }
if (args[argidx] == "-noclkinv") { if (args[argidx] == "-noclkinv") {
noclkinv = true; options.noclkinv = true;
continue; continue;
} }
if (args[argidx] == "-fine") { if (args[argidx] == "-fine") {
do_fine = true; options.do_fine = true;
continue; continue;
} }
if (args[argidx] == "-full") { if (args[argidx] == "-full") {
mux_undef = true; options.mux_undef = true;
mux_bool = true; options.mux_bool = true;
undriven = true; options.undriven = true;
do_fine = true; options.do_fine = true;
continue; continue;
} }
if (args[argidx] == "-keepdc") { if (args[argidx] == "-keepdc") {
keepdc = true; options.keepdc = true;
continue; continue;
} }
if (args[argidx] == "-effort" && argidx + 1 < args.size()) { if (args[argidx] == "-effort" && argidx + 1 < args.size()) {
effort = atoi(args[++argidx].c_str()); options.effort = atoi(args[++argidx].c_str());
continue; continue;
} }
break; break;
@ -2330,30 +2356,28 @@ struct OptExprPass : public Pass {
for (auto module : design->selected_modules()) for (auto module : design->selected_modules())
{ {
log("Optimizing module %s.\n", log_id(module)); log("Optimizing module %s.\n", log_id(module));
bool did_something = false;
if (undriven) { if (options.undriven) {
did_something = false; did_something = replace_undriven(module, ct);
replace_undriven(module, ct);
if (did_something) if (did_something)
design->scratchpad_set_bool("opt.did_something", true); design->scratchpad_set_bool("opt.did_something", true);
} }
sort_fails = 0; auto worker = OptExprWorker(design, module, options);
do { do {
do { do {
did_something = false; did_something = worker.run(false);
replace_const_cells(design, module, false /* consume_x */, mux_undef, mux_bool, do_fine, keepdc, noclkinv, effort);
if (did_something) if (did_something)
design->scratchpad_set_bool("opt.did_something", true); design->scratchpad_set_bool("opt.did_something", true);
} while (did_something); } while (did_something);
if (!keepdc) if (!options.keepdc)
replace_const_cells(design, module, true /* consume_x */, mux_undef, mux_bool, do_fine, keepdc, noclkinv, effort); did_something = worker.run(true);
if (did_something) if (did_something)
design->scratchpad_set_bool("opt.did_something", true); design->scratchpad_set_bool("opt.did_something", true);
} while (did_something); } while (did_something);
did_something = false; did_something = replace_const_connections(module);
replace_const_connections(module);
if (did_something) if (did_something)
design->scratchpad_set_bool("opt.did_something", true); design->scratchpad_set_bool("opt.did_something", true);