mirror of
https://github.com/YosysHQ/yosys.git
synced 2026-10-06 01:53:53 +02:00
Merge pull request #6202 from hfthra/main
multiple constant multiplication (mcm) pass
This commit is contained in:
@@ -32,6 +32,9 @@ yosys_pass(maccmap
|
||||
yosys_pass(booth
|
||||
booth.cc
|
||||
)
|
||||
yosys_pass(mcm
|
||||
mcm.cc
|
||||
)
|
||||
yosys_pass(libcache
|
||||
libcache.cc
|
||||
REQUIRES
|
||||
|
||||
@@ -0,0 +1,777 @@
|
||||
/**
|
||||
* mcm -- Multiple Constant Multiplication.
|
||||
*
|
||||
* Replaces $mul cells that have a constant operand with a shared adder graph
|
||||
* of shifts, additions and subtractions. Multipliers driven by the same
|
||||
* variable operand are solved together, so common subterms are computed once.
|
||||
*
|
||||
* The graph search is the classic Hcub-style greedy over A-operations
|
||||
*
|
||||
* w = | u<<su +/- v<<sv | u,v already computable
|
||||
* Constant multiplies that are a plain power of two, zero, or one are left to
|
||||
* the existing constant folding.
|
||||
*/
|
||||
|
||||
#include "kernel/yosys.h"
|
||||
#include "kernel/sigtools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
USING_YOSYS_NAMESPACE
|
||||
PRIVATE_NAMESPACE_BEGIN
|
||||
|
||||
// One A-operation: res == |(u << su) +/- (v << sv)| >> norm
|
||||
struct AOp {
|
||||
int res, u, su, v, sv, norm;
|
||||
bool sub, swapped;
|
||||
};
|
||||
|
||||
struct AdderGraphConfig {
|
||||
int max_depth = 3;
|
||||
int max_shift = 12;
|
||||
int max_nodes = 64;
|
||||
long long work_budget = 2000000;
|
||||
};
|
||||
|
||||
struct McmConfig {
|
||||
AdderGraphConfig search;
|
||||
int min_const = 3;
|
||||
int min_gain = 0;
|
||||
bool force = false;
|
||||
};
|
||||
|
||||
struct AdderGraphCandidate {
|
||||
int value = -1;
|
||||
int depth = 0;
|
||||
int score = -1;
|
||||
AOp op;
|
||||
};
|
||||
|
||||
using CandidateMap = dict<int, AdderGraphCandidate>;
|
||||
|
||||
struct AdderGraph {
|
||||
dict<int, int> depth;
|
||||
std::vector<AOp> ops;
|
||||
};
|
||||
|
||||
enum class AdderGraphStatus {
|
||||
success,
|
||||
no_graph,
|
||||
node_limit,
|
||||
budget_exhausted,
|
||||
};
|
||||
|
||||
static int oddify(int n)
|
||||
{
|
||||
if (n < 0)
|
||||
n = -n;
|
||||
while (n && (n % 2) == 0)
|
||||
n /= 2;
|
||||
return n;
|
||||
}
|
||||
|
||||
static int shift_of(int n)
|
||||
{
|
||||
int k = 0;
|
||||
if (n < 0)
|
||||
n = -n;
|
||||
while (n && (n % 2) == 0) { n /= 2; k++; }
|
||||
return k;
|
||||
}
|
||||
|
||||
// Number of non-zero digits in the non-adjacent signed-digit form.
|
||||
static int naf_weight(int n)
|
||||
{
|
||||
uint64_t v = n < 0 ? uint64_t(-(int64_t)n) : uint64_t(n);
|
||||
int weight = 0;
|
||||
while (v) {
|
||||
if (v & 1) {
|
||||
weight++;
|
||||
if ((v & 3) == 3)
|
||||
v++;
|
||||
else
|
||||
v--;
|
||||
}
|
||||
v >>= 1;
|
||||
}
|
||||
return weight;
|
||||
}
|
||||
|
||||
struct SearchResult {
|
||||
AdderGraph graph;
|
||||
AdderGraphStatus status = AdderGraphStatus::no_graph;
|
||||
};
|
||||
|
||||
struct SearchParams {
|
||||
const AdderGraphConfig &config;
|
||||
const std::vector<int> &targets;
|
||||
SearchResult &result;
|
||||
};
|
||||
|
||||
struct SearchState;
|
||||
|
||||
class Scorer {
|
||||
public:
|
||||
virtual bool better(const AdderGraphCandidate &candidate, const AdderGraphCandidate &selected,
|
||||
bool direct_target) const = 0;
|
||||
virtual bool score_intermediate(SearchState &state, const AdderGraphCandidate &candidate,
|
||||
const pool<int> &remaining, int &score) const = 0;
|
||||
virtual ~Scorer() = default;
|
||||
};
|
||||
|
||||
class HcubScorer : public Scorer {
|
||||
public:
|
||||
bool better(const AdderGraphCandidate &candidate, const AdderGraphCandidate &selected,
|
||||
bool direct_target) const override
|
||||
{
|
||||
if (selected.value < 0)
|
||||
return true;
|
||||
if (!direct_target && candidate.score != selected.score)
|
||||
return candidate.score > selected.score;
|
||||
if (candidate.depth != selected.depth)
|
||||
return candidate.depth < selected.depth;
|
||||
return candidate.value < selected.value;
|
||||
}
|
||||
|
||||
bool score_intermediate(SearchState &state, const AdderGraphCandidate &candidate,
|
||||
const pool<int> &remaining, int &score) const override;
|
||||
|
||||
};
|
||||
|
||||
struct SearchState {
|
||||
AdderGraphConfig config;
|
||||
int limit;
|
||||
long long search_work;
|
||||
bool budget_exhausted;
|
||||
AdderGraph graph;
|
||||
dict<std::pair<int, int>, std::vector<AOp>> op_cache;
|
||||
|
||||
explicit SearchState(const AdderGraphConfig &config)
|
||||
: config(config), limit(1), search_work(0), budget_exhausted(false)
|
||||
{}
|
||||
|
||||
static bool op_less(const AOp &a, const AOp &b)
|
||||
{
|
||||
return std::tie(a.u, a.su, a.v, a.sv, a.sub, a.swapped, a.norm) <
|
||||
std::tie(b.u, b.su, b.v, b.sv, b.sub, b.swapped, b.norm);
|
||||
}
|
||||
|
||||
// Enumerate odd coefficients reachable from a pair of graph nodes.
|
||||
const std::vector<AOp> &enumerate_pair(int u, int v)
|
||||
{
|
||||
if (u > v)
|
||||
std::swap(u, v);
|
||||
auto key = std::make_pair(u, v);
|
||||
if (op_cache.count(key))
|
||||
return op_cache.at(key);
|
||||
|
||||
auto &ops = op_cache[key];
|
||||
dict<int, AOp> unique;
|
||||
long long bound = 4LL * limit;
|
||||
for (int su = 0; su <= config.max_shift; su++) {
|
||||
if (su >= 63 || (long long) u > (bound >> su))
|
||||
break;
|
||||
long long us = (long long)u << su;
|
||||
for (int sv = 0; sv <= config.max_shift; sv++) {
|
||||
if (sv >= 63 || (long long) v > (bound >> sv))
|
||||
break;
|
||||
if (search_work >= config.work_budget) {
|
||||
budget_exhausted = true;
|
||||
return ops;
|
||||
}
|
||||
search_work++;
|
||||
long long vs = (long long)v << sv;
|
||||
long long results[3] = {us + vs, us - vs, vs - us};
|
||||
bool subtract[3] = {false, true, true};
|
||||
bool swapped[3] = {false, false, true};
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (results[i] <= 0 || results[i] > bound || results[i] > std::numeric_limits<int>::max())
|
||||
continue;
|
||||
AOp op;
|
||||
op.res = oddify((int)results[i]);
|
||||
op.u = u;
|
||||
op.su = su;
|
||||
op.v = v;
|
||||
op.sv = sv;
|
||||
op.sub = subtract[i];
|
||||
op.swapped = swapped[i];
|
||||
op.norm = shift_of((int)results[i]);
|
||||
if (!unique.count(op.res) || op_less(op, unique.at(op.res)))
|
||||
unique[op.res] = op;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto &it : unique)
|
||||
ops.push_back(it.second);
|
||||
return ops;
|
||||
}
|
||||
|
||||
bool build_frontier(CandidateMap &candidates)
|
||||
{
|
||||
for (auto &a : graph.depth) {
|
||||
for (auto &b : graph.depth) {
|
||||
if (a.first > b.first || 1 + std::max(a.second, b.second) > config.max_depth)
|
||||
continue;
|
||||
for (auto &op : enumerate_pair(a.first, b.first)) {
|
||||
if (graph.depth.count(op.res) || op.res == 1)
|
||||
continue;
|
||||
int d = 1 + std::max(a.second, b.second);
|
||||
if (!candidates.count(op.res) || d < candidates.at(op.res).depth ||
|
||||
(d == candidates.at(op.res).depth && op_less(op, candidates.at(op.res).op))) {
|
||||
AdderGraphCandidate candidate;
|
||||
candidate.value = op.res;
|
||||
candidate.depth = d;
|
||||
candidate.op = op;
|
||||
candidates[op.res] = candidate;
|
||||
}
|
||||
}
|
||||
if (budget_exhausted)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prefer a target we can produce immediately.
|
||||
bool select_direct_target(const CandidateMap &candidates, const pool<int> &remaining,
|
||||
AdderGraphCandidate &selected, const Scorer &scorer) const
|
||||
{
|
||||
for (auto &entry : candidates) {
|
||||
if (!remaining.count(entry.first))
|
||||
continue;
|
||||
if (scorer.better(entry.second, selected, true))
|
||||
selected = entry.second;
|
||||
}
|
||||
return selected.value >= 0;
|
||||
}
|
||||
|
||||
bool select_intermediate(const CandidateMap &candidates, const pool<int> &remaining,
|
||||
AdderGraphCandidate &selected, const Scorer &scorer)
|
||||
{
|
||||
for (auto &entry : candidates) {
|
||||
AdderGraphCandidate candidate = entry.second;
|
||||
if (!scorer.score_intermediate(*this, candidate, remaining, candidate.score))
|
||||
return false;
|
||||
if (scorer.better(candidate, selected, false))
|
||||
selected = candidate;
|
||||
}
|
||||
return selected.value >= 0;
|
||||
}
|
||||
|
||||
bool select_next_candidate(const CandidateMap &candidates, const pool<int> &remaining,
|
||||
AdderGraphCandidate &selected, const Scorer &scorer)
|
||||
{
|
||||
if (select_direct_target(candidates, remaining, selected, scorer))
|
||||
return true;
|
||||
if (candidates.empty())
|
||||
return false;
|
||||
return select_intermediate(candidates, remaining, selected, scorer);
|
||||
}
|
||||
|
||||
void commit(const AdderGraphCandidate &candidate)
|
||||
{
|
||||
graph.depth[candidate.value] = candidate.depth;
|
||||
graph.ops.push_back(candidate.op);
|
||||
}
|
||||
|
||||
pool<int> find_remaining_targets(const pool<int> &targets) const
|
||||
{
|
||||
pool<int> remaining;
|
||||
for (int target : targets)
|
||||
if (!graph.depth.count(target))
|
||||
remaining.insert(target);
|
||||
return remaining;
|
||||
}
|
||||
};
|
||||
|
||||
// Score an intermediate by how many targets it makes reachable in one step.
|
||||
bool HcubScorer::score_intermediate(SearchState &state, const AdderGraphCandidate &candidate,
|
||||
const pool<int> &remaining, int &score) const
|
||||
{
|
||||
pool<int> unlocked;
|
||||
for (auto &node : state.graph.depth) {
|
||||
if (1 + std::max(candidate.depth, node.second) > state.config.max_depth)
|
||||
continue;
|
||||
for (auto &op : state.enumerate_pair(candidate.value, node.first))
|
||||
if (remaining.count(op.res))
|
||||
unlocked.insert(op.res);
|
||||
if (state.budget_exhausted)
|
||||
return false;
|
||||
}
|
||||
if (1 + candidate.depth <= state.config.max_depth) {
|
||||
for (auto &op : state.enumerate_pair(candidate.value, candidate.value))
|
||||
if (remaining.count(op.res))
|
||||
unlocked.insert(op.res);
|
||||
if (state.budget_exhausted)
|
||||
return false;
|
||||
}
|
||||
score = GetSize(unlocked);
|
||||
return true;
|
||||
}
|
||||
|
||||
class Search {
|
||||
public:
|
||||
virtual void search(const SearchParams ¶ms, const Scorer &scorer) = 0;
|
||||
virtual ~Search() = default;
|
||||
};
|
||||
|
||||
class HcubSearch : public Search {
|
||||
public:
|
||||
void search(const SearchParams ¶ms, const Scorer &scorer) override
|
||||
{
|
||||
SearchState state(params.config);
|
||||
params.result = SearchResult();
|
||||
pool<int> targets;
|
||||
for (int target : params.targets) {
|
||||
int odd_target = oddify(target);
|
||||
if (odd_target > 1)
|
||||
targets.insert(odd_target);
|
||||
}
|
||||
state.limit = 1;
|
||||
for (int target : targets)
|
||||
state.limit = std::max(state.limit, target);
|
||||
|
||||
state.graph.depth[1] = 0;
|
||||
|
||||
int committed_nodes = 0;
|
||||
while (true) {
|
||||
pool<int> remaining = state.find_remaining_targets(targets);
|
||||
if (remaining.empty())
|
||||
break;
|
||||
if (committed_nodes >= state.config.max_nodes) {
|
||||
params.result.status = AdderGraphStatus::node_limit;
|
||||
return;
|
||||
}
|
||||
|
||||
CandidateMap candidates;
|
||||
if (!state.build_frontier(candidates)) {
|
||||
params.result.status = state.budget_exhausted ? AdderGraphStatus::budget_exhausted : AdderGraphStatus::no_graph;
|
||||
return;
|
||||
}
|
||||
|
||||
AdderGraphCandidate selected;
|
||||
if (!state.select_next_candidate(candidates, remaining, selected, scorer)) {
|
||||
params.result.status = state.budget_exhausted ? AdderGraphStatus::budget_exhausted : AdderGraphStatus::no_graph;
|
||||
return;
|
||||
}
|
||||
state.commit(selected);
|
||||
committed_nodes++;
|
||||
}
|
||||
params.result.graph = std::move(state.graph);
|
||||
params.result.status = AdderGraphStatus::success;
|
||||
}
|
||||
};
|
||||
|
||||
struct McmItem {
|
||||
Cell *cell;
|
||||
int coefficient;
|
||||
};
|
||||
|
||||
struct McmGroup {
|
||||
SigSpec input;
|
||||
bool is_signed = false;
|
||||
std::vector<McmItem> items;
|
||||
};
|
||||
|
||||
using McmGroupMap = std::map<std::pair<SigSpec, bool>, McmGroup>;
|
||||
|
||||
struct McmPlan {
|
||||
std::vector<int> active_items;
|
||||
std::vector<AOp> ops;
|
||||
dict<int, int> demand;
|
||||
dict<int, int> raw_width;
|
||||
int realized_depth = 0;
|
||||
long long shared_cost = 0;
|
||||
long long independent_cost = 0;
|
||||
};
|
||||
|
||||
struct McmWorker {
|
||||
Module *module;
|
||||
SigMap sigmap;
|
||||
McmConfig config;
|
||||
int n_groups = 0;
|
||||
int n_muls = 0;
|
||||
int n_adders = 0;
|
||||
|
||||
McmWorker(Module *m, const McmConfig &config) : module(m), sigmap(m), config(config) {}
|
||||
|
||||
SigSpec shl(SigSpec sig, int n, int w, bool is_signed)
|
||||
{
|
||||
SigSpec result;
|
||||
if (n > 0)
|
||||
result.append(SigSpec(State::S0, n));
|
||||
result.append(sig);
|
||||
result.extend_u0(w, is_signed);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool decode_multiplier(Cell *cell, SigSpec &input, int &coefficient, bool &is_signed)
|
||||
{
|
||||
if (cell->type != ID($mul) || cell->has_keep_attr())
|
||||
return false;
|
||||
|
||||
SigSpec sig_a = sigmap(cell->getPort(ID::A));
|
||||
SigSpec sig_b = sigmap(cell->getPort(ID::B));
|
||||
bool a_signed = cell->getParam(ID::A_SIGNED).as_bool();
|
||||
bool b_signed = cell->getParam(ID::B_SIGNED).as_bool();
|
||||
if (a_signed != b_signed)
|
||||
return false;
|
||||
is_signed = a_signed;
|
||||
|
||||
Const constant;
|
||||
if (sig_b.is_fully_const() && !sig_a.is_fully_const()) {
|
||||
input = sig_a;
|
||||
constant = sig_b.as_const();
|
||||
} else if (sig_a.is_fully_const() && !sig_b.is_fully_const()) {
|
||||
input = sig_b;
|
||||
constant = sig_a.as_const();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (!constant.is_fully_def())
|
||||
return false;
|
||||
|
||||
auto value = constant.try_as_int(is_signed);
|
||||
if (!value || *value == std::numeric_limits<int>::min()) {
|
||||
log_debug("Skipping constant multiplier %s: coefficient %s is outside the supported range.\n",
|
||||
log_id(cell), log_const(constant));
|
||||
return false;
|
||||
}
|
||||
|
||||
coefficient = *value;
|
||||
int magnitude = coefficient < 0 ? -coefficient : coefficient;
|
||||
if (magnitude == 0 || magnitude == 1)
|
||||
return false;
|
||||
if ((magnitude & (magnitude - 1)) == 0)
|
||||
return false;
|
||||
return magnitude >= config.min_const;
|
||||
}
|
||||
|
||||
McmGroupMap collect_groups()
|
||||
{
|
||||
McmGroupMap groups;
|
||||
for (auto cell : module->selected_cells()) {
|
||||
SigSpec input;
|
||||
int coefficient;
|
||||
bool is_signed;
|
||||
if (!decode_multiplier(cell, input, coefficient, is_signed))
|
||||
continue;
|
||||
|
||||
auto key = std::make_pair(input, is_signed);
|
||||
auto &group = groups[key];
|
||||
if (group.items.empty()) {
|
||||
group.input = input;
|
||||
group.is_signed = is_signed;
|
||||
}
|
||||
group.items.push_back(McmItem{cell, coefficient});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
bool select_active_items(const McmGroup &group, const AdderGraph &graph, McmPlan &plan)
|
||||
{
|
||||
for (int i = 0; i < GetSize(group.items); i++) {
|
||||
int odd = oddify(group.items[i].coefficient);
|
||||
int depth = graph.depth.at(odd) + (group.items[i].coefficient < 0 ? 1 : 0);
|
||||
if (depth > config.search.max_depth) {
|
||||
log_debug("Skipping constant multiplier %s: output negation exceeds depth %d.\n",
|
||||
log_id(group.items[i].cell), config.search.max_depth);
|
||||
continue;
|
||||
}
|
||||
plan.active_items.push_back(i);
|
||||
plan.realized_depth = std::max(plan.realized_depth, depth);
|
||||
}
|
||||
return !plan.active_items.empty();
|
||||
}
|
||||
|
||||
void prune_graph(const McmGroup &group, const AdderGraph &graph, McmPlan &plan)
|
||||
{
|
||||
dict<int, int> producer;
|
||||
for (int i = 0; i < GetSize(graph.ops); i++)
|
||||
producer[graph.ops[i].res] = i;
|
||||
|
||||
pool<int> live_values;
|
||||
std::function<void(int)> mark_live = [&](int value) {
|
||||
if (value == 1 || live_values.count(value))
|
||||
return;
|
||||
live_values.insert(value);
|
||||
log_assert(producer.count(value));
|
||||
auto &op = graph.ops[producer.at(value)];
|
||||
mark_live(op.u);
|
||||
mark_live(op.v);
|
||||
};
|
||||
for (int i : plan.active_items)
|
||||
mark_live(oddify(group.items[i].coefficient));
|
||||
|
||||
for (auto &op : graph.ops)
|
||||
if (live_values.count(op.res))
|
||||
plan.ops.push_back(op);
|
||||
}
|
||||
|
||||
void propagate_widths(const McmGroup &group, McmPlan &plan)
|
||||
{
|
||||
for (int i : plan.active_items) {
|
||||
int odd = oddify(group.items[i].coefficient);
|
||||
int shift = shift_of(group.items[i].coefficient);
|
||||
int need = std::max(1, GetSize(group.items[i].cell->getPort(ID::Y)) - shift);
|
||||
plan.demand[odd] = std::max(plan.demand[odd], need);
|
||||
}
|
||||
for (auto it = plan.ops.rbegin(); it != plan.ops.rend(); ++it) {
|
||||
auto &op = *it;
|
||||
log_assert(plan.demand.count(op.res));
|
||||
int width = plan.demand.at(op.res) + op.norm;
|
||||
plan.raw_width[op.res] = width;
|
||||
plan.demand[op.u] = std::max(plan.demand[op.u], std::max(1, width - op.su));
|
||||
plan.demand[op.v] = std::max(plan.demand[op.v], std::max(1, width - op.sv));
|
||||
}
|
||||
}
|
||||
|
||||
void estimate_cost(const McmGroup &group, McmPlan &plan)
|
||||
{
|
||||
for (auto &op : plan.ops)
|
||||
plan.shared_cost += plan.raw_width.at(op.res);
|
||||
for (int i : plan.active_items) {
|
||||
auto &item = group.items[i];
|
||||
int width = GetSize(item.cell->getPort(ID::Y));
|
||||
int multiply_width = std::max(1, width - shift_of(item.coefficient));
|
||||
plan.independent_cost +=
|
||||
(long long)std::max(0, naf_weight(item.coefficient) - 1) * multiply_width;
|
||||
if (item.coefficient < 0) {
|
||||
plan.shared_cost += width;
|
||||
plan.independent_cost += width;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool is_profitable(const McmPlan &plan) const
|
||||
{
|
||||
if (config.force)
|
||||
return true;
|
||||
return plan.independent_cost != 0 &&
|
||||
(long double)plan.shared_cost * 100 <=
|
||||
(long double)plan.independent_cost * (100 - config.min_gain);
|
||||
}
|
||||
|
||||
bool prepare_plan(const McmGroup &group, const AdderGraph &graph, McmPlan &plan)
|
||||
{
|
||||
if (!select_active_items(group, graph, plan))
|
||||
return false;
|
||||
prune_graph(group, graph, plan);
|
||||
propagate_widths(group, plan);
|
||||
estimate_cost(group, plan);
|
||||
if (is_profitable(plan))
|
||||
return true;
|
||||
log_debug("Skipping MCM group in module %s: estimated bit cost %lld vs %lld "
|
||||
"does not meet %d%% minimum gain.\n", log_id(module), plan.shared_cost,
|
||||
plan.independent_cost, config.min_gain);
|
||||
return false;
|
||||
}
|
||||
|
||||
void emit_plan(McmGroup &group, const McmPlan &plan)
|
||||
{
|
||||
dict<int, SigSpec> nodes;
|
||||
log_assert(plan.demand.count(1));
|
||||
SigSpec input = group.input;
|
||||
input.extend_u0(plan.demand.at(1), group.is_signed);
|
||||
nodes.emplace(1, std::move(input));
|
||||
|
||||
for (auto &op : plan.ops) {
|
||||
int width = plan.raw_width.at(op.res);
|
||||
SigSpec a = shl(nodes.at(op.u), op.su, width, group.is_signed);
|
||||
SigSpec b = shl(nodes.at(op.v), op.sv, width, group.is_signed);
|
||||
SigSpec y = module->addWire(NEW_ID, width);
|
||||
if (!op.sub)
|
||||
module->addAdd(NEW_ID, a, b, y, group.is_signed);
|
||||
else if (!op.swapped)
|
||||
module->addSub(NEW_ID, a, b, y, group.is_signed);
|
||||
else
|
||||
module->addSub(NEW_ID, b, a, y, group.is_signed);
|
||||
|
||||
SigSpec normalized = y;
|
||||
if (op.norm > 0)
|
||||
normalized = y.extract(op.norm, width - op.norm);
|
||||
nodes.emplace(op.res, std::move(normalized));
|
||||
}
|
||||
n_adders += GetSize(plan.ops);
|
||||
|
||||
for (int i : plan.active_items) {
|
||||
auto &item = group.items[i];
|
||||
int coefficient = item.coefficient;
|
||||
int odd = oddify(coefficient);
|
||||
int shift = shift_of(coefficient);
|
||||
log_assert(nodes.count(odd));
|
||||
SigSpec output = item.cell->getPort(ID::Y);
|
||||
SigSpec value = shl(nodes.at(odd), shift, GetSize(output), group.is_signed);
|
||||
if (coefficient < 0) {
|
||||
SigSpec negated = module->addWire(NEW_ID, GetSize(output));
|
||||
module->addNeg(NEW_ID, value, negated, group.is_signed);
|
||||
value = negated;
|
||||
n_adders++;
|
||||
}
|
||||
module->connect(output, value);
|
||||
module->remove(item.cell);
|
||||
n_muls++;
|
||||
}
|
||||
}
|
||||
|
||||
void process_group(McmGroup &group)
|
||||
{
|
||||
std::vector<int> targets;
|
||||
for (auto &item : group.items)
|
||||
targets.push_back(item.coefficient);
|
||||
|
||||
HcubSearch hcub_search;
|
||||
Search &search = hcub_search;
|
||||
HcubScorer scorer;
|
||||
SearchResult result;
|
||||
search.search(SearchParams{config.search, targets, result}, scorer);
|
||||
AdderGraphStatus status = result.status;
|
||||
if (status != AdderGraphStatus::success) {
|
||||
if (status == AdderGraphStatus::budget_exhausted)
|
||||
log(" mcm: search budget of %lld exhausted for %d constant(s), skipping.\n",
|
||||
config.search.work_budget, GetSize(group.items));
|
||||
else if (status == AdderGraphStatus::node_limit)
|
||||
log(" mcm: maximum node count %d reached for %d constant(s), skipping.\n",
|
||||
config.search.max_nodes, GetSize(group.items));
|
||||
else
|
||||
log(" mcm: no adder graph within depth %d for %d constant(s), skipping.\n",
|
||||
config.search.max_depth, GetSize(group.items));
|
||||
return;
|
||||
}
|
||||
|
||||
McmPlan plan;
|
||||
if (!prepare_plan(group, result.graph, plan))
|
||||
return;
|
||||
emit_plan(group, plan);
|
||||
n_groups++;
|
||||
|
||||
int negations = std::count_if(plan.active_items.begin(), plan.active_items.end(),
|
||||
[&](int i) { return group.items[i].coefficient < 0; });
|
||||
log(" mcm: %d constant multiplier(s) sharing one operand -> %d adder(s), depth %d, "
|
||||
"estimated bit cost %lld (independent %lld).\n", GetSize(plan.active_items),
|
||||
GetSize(plan.ops) + negations, plan.realized_depth, plan.shared_cost, plan.independent_cost);
|
||||
}
|
||||
|
||||
void run()
|
||||
{
|
||||
auto groups = collect_groups();
|
||||
for (auto &entry : groups)
|
||||
process_group(entry.second);
|
||||
}
|
||||
};
|
||||
|
||||
struct McmPass : public Pass {
|
||||
McmPass() : Pass("mcm", "replace constant multipliers with shared adder graphs") {}
|
||||
|
||||
void help() override
|
||||
{
|
||||
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
|
||||
log("\n");
|
||||
log(" mcm [options] [selection]\n");
|
||||
log("\n");
|
||||
log("Replaces $mul cells having a constant operand with a shared graph of shifts,\n");
|
||||
log("additions and subtractions \n");
|
||||
log("\n");
|
||||
log(" -depth <n>\n");
|
||||
log(" maximum adder depth of the graph (default: 3). Lower bounds delay at\n");
|
||||
log(" the cost of possibly more adders.\n");
|
||||
log("\n");
|
||||
log(" -max_shift <n>\n");
|
||||
log(" largest shift considered in an A-operation (default: 12).\n");
|
||||
log("\n");
|
||||
log(" -max_nodes <n>\n");
|
||||
log(" maximum number of nodes (default: 64).\n");
|
||||
log("\n");
|
||||
log(" -min_const <n>\n");
|
||||
log(" skip constants whose magnitude is below <n> (default: 3).\n");
|
||||
log("\n");
|
||||
log(" -min_gain <percent>\n");
|
||||
log(" require this estimated one-bit-adder saving over independent\n");
|
||||
log(" signed-digit implementations (default: 0).\n");
|
||||
log("\n");
|
||||
log(" -search_budget <n>\n");
|
||||
log(" maximum number of uncached shift-pair evaluations per operand group\n");
|
||||
log(" (default: 2000000).\n");
|
||||
log("\n");
|
||||
log(" -force\n");
|
||||
log(" ignore the profitability check.\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void execute(std::vector<std::string> args, RTLIL::Design *design) override
|
||||
{
|
||||
log_header(design, "Executing MCM pass (constant-multiplication adder graphs).\n");
|
||||
|
||||
McmConfig config;
|
||||
size_t argidx;
|
||||
for (argidx = 1; argidx < args.size(); argidx++) {
|
||||
if (args[argidx] == "-depth" && argidx + 1 < args.size()) {
|
||||
config.search.max_depth = atoi(args[++argidx].c_str());
|
||||
if (config.search.max_depth < 1)
|
||||
log_cmd_error("mcm: -depth must be >= 1\n");
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-max_shift" && argidx + 1 < args.size()) {
|
||||
config.search.max_shift = atoi(args[++argidx].c_str());
|
||||
if (config.search.max_shift < 1)
|
||||
log_cmd_error("mcm: -max_shift must be >= 1\n");
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-max_nodes" && argidx + 1 < args.size()) {
|
||||
config.search.max_nodes = atoi(args[++argidx].c_str());
|
||||
if (config.search.max_nodes < 1)
|
||||
log_cmd_error("mcm: -max_nodes must be >= 1\n");
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-min_const" && argidx + 1 < args.size()) {
|
||||
config.min_const = atoi(args[++argidx].c_str());
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-min_gain" && argidx + 1 < args.size()) {
|
||||
config.min_gain = atoi(args[++argidx].c_str());
|
||||
if (config.min_gain < 0 || config.min_gain > 100)
|
||||
log_cmd_error("mcm: -min_gain must be between 0 and 100\n");
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-search_budget" && argidx + 1 < args.size()) {
|
||||
config.search.work_budget = atoll(args[++argidx].c_str());
|
||||
if (config.search.work_budget < 1)
|
||||
log_cmd_error("mcm: -search_budget must be >= 1\n");
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-force") {
|
||||
config.force = true;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
extra_args(args, argidx, design);
|
||||
|
||||
int total_groups = 0;
|
||||
int total_muls = 0;
|
||||
int total_adders = 0;
|
||||
for (auto module : design->selected_modules()) {
|
||||
if (module->get_blackbox_attribute())
|
||||
continue;
|
||||
McmWorker worker(module, config);
|
||||
worker.run();
|
||||
if (worker.n_muls)
|
||||
log("Module %s: replaced %d constant multiplier(s) in %d group(s) "
|
||||
"with %d adder(s).\n", log_id(module), worker.n_muls, worker.n_groups, worker.n_adders);
|
||||
total_groups += worker.n_groups;
|
||||
total_muls += worker.n_muls;
|
||||
total_adders += worker.n_adders;
|
||||
}
|
||||
if (total_muls)
|
||||
log("Replaced %d constant multiplier(s) in %d group(s) with %d adder(s).\n",
|
||||
total_muls, total_groups, total_adders);
|
||||
else
|
||||
log("No constant multipliers found to restructure.\n");
|
||||
}
|
||||
} McmPass;
|
||||
|
||||
PRIVATE_NAMESPACE_END
|
||||
@@ -0,0 +1,134 @@
|
||||
# tests for mcm pass
|
||||
log -header "MCM normalized A-operation width"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [7:0] x,
|
||||
output wire [16:0] a,
|
||||
output wire [16:0] b,
|
||||
output wire [16:0] c
|
||||
);
|
||||
assign a = x * 17'd9;
|
||||
assign b = x * 17'd397;
|
||||
assign c = x * 17'd505;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm
|
||||
design -load postopt
|
||||
select -assert-count 0 t:$mul
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM does not truncate an unsupported wide coefficient"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [7:0] x,
|
||||
output wire [47:0] y
|
||||
);
|
||||
assign y = x * 48'd4294967299;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm
|
||||
design -load postopt
|
||||
select -assert-count 1 t:$mul
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM prunes dead greedy-search intermediates"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [7:0] x,
|
||||
output wire [15:0] y
|
||||
);
|
||||
assign y = x * 16'd397;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm -force
|
||||
design -load postopt
|
||||
select -assert-count 0 t:$mul
|
||||
select -assert-max 3 t:$add t:$sub
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM accepts a graph at the gain threshold"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [7:0] x,
|
||||
output wire [15:0] a,
|
||||
output wire [15:0] b
|
||||
);
|
||||
assign a = x * 16'd3;
|
||||
assign b = x * 16'd21;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm -min_gain 33
|
||||
design -load postopt
|
||||
select -assert-count 0 t:$mul
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM rejects a graph above its gain"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [7:0] x,
|
||||
output wire [15:0] a,
|
||||
output wire [15:0] b
|
||||
);
|
||||
assign a = x * 16'd3;
|
||||
assign b = x * 16'd21;
|
||||
endmodule
|
||||
EOF
|
||||
mcm -min_gain 34
|
||||
select -assert-count 2 t:$mul
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM accepts a profitable shared graph"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire [7:0] x,
|
||||
output wire [15:0] a,
|
||||
output wire [15:0] b
|
||||
);
|
||||
assign a = x * 16'd3;
|
||||
assign b = x * 16'd21;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm
|
||||
design -load postopt
|
||||
select -assert-count 0 t:$mul
|
||||
select -assert-count 2 t:$add t:$sub
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM accounts for negative-coefficient output depth"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire signed [7:0] x,
|
||||
output wire signed [15:0] positive,
|
||||
output wire signed [15:0] negative
|
||||
);
|
||||
assign positive = x * 16'sd3;
|
||||
assign negative = x * -16'sd3;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm -depth 1 -force
|
||||
design -load postopt
|
||||
select -assert-count 1 t:$mul
|
||||
select -assert-count 1 t:$add t:$sub
|
||||
|
||||
design -reset
|
||||
|
||||
log -header "MCM demanded widths preserve differently truncated outputs"
|
||||
read_verilog <<EOF
|
||||
module top(
|
||||
input wire signed [11:0] x,
|
||||
output wire signed [8:0] narrow,
|
||||
output wire signed [20:0] wide
|
||||
);
|
||||
assign narrow = x * 9'sd21;
|
||||
assign wide = x * 21'sd341;
|
||||
endmodule
|
||||
EOF
|
||||
equiv_opt -assert mcm -min_gain 0
|
||||
design -load postopt
|
||||
select -assert-count 0 t:$mul
|
||||
Reference in New Issue
Block a user