3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-07-15 03:35:40 +00:00

Merge pull request #185 from Silimate/opt_priority_onehot

opt_priority_onehot
This commit is contained in:
Akash Levy 2026-06-09 23:10:41 -07:00 committed by GitHub
commit aa3ebe430e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 1365 additions and 0 deletions

View file

@ -29,6 +29,7 @@ OBJS += passes/opt/opt_argmax.o
OBJS += passes/opt/opt_balance_tree.o
OBJS += passes/opt/opt_parallel_prefix.o
OBJS += passes/opt/opt_prienc.o
OBJS += passes/opt/opt_priority_onehot.o
OBJS += passes/opt/peepopt.o
GENFILES += passes/opt/peepopt_pm.h

View file

@ -0,0 +1,828 @@
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2026 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/consteval.h"
#include <algorithm>
#include <cctype>
#include <map>
#include <queue>
#include <set>
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
static int clog2_int(int x)
{
int r = 0;
while ((1 << r) < x)
r++;
return r;
}
static bool is_power_of_two(int x)
{
return x > 0 && (x & (x - 1)) == 0;
}
// Pack a per-lane integer vector into a Const with elem_w bits per lane.
static Const pack_lanes(const vector<int> &vals, int elem_w)
{
vector<State> bits(vals.size() * elem_w, State::S0);
for (int k = 0; k < GetSize(vals); k++)
for (int b = 0; b < elem_w && b < 31; b++)
if ((vals[k] >> b) & 1)
bits[k * elem_w + b] = State::S1;
return Const(bits);
}
struct OptPriorityOnehotWorker {
// One detected priority-onehot region ready to be rewritten.
struct Candidate {
Wire *out_wire = nullptr; // one-hot output O (width W = 2^idx_w)
Wire *valid_wire = nullptr; // request / valid bus V (width N)
SigSpec valid_sig; // N bits
SigSpec field_sig; // N * idx_w bits: concatenated per-lane index fields
std::string index_name;
int n = 0; // number of lanes
int w = 0; // output width (power of two)
int idx_w = 0; // clog2(w) == per-lane field width
bool msb_first = false; // priority direction (false: lowest index wins)
Cell *anchor = nullptr; // a driver cell of O, used for naming/src
};
struct TestVector {
vector<int> valid;
vector<int> field;
};
Module *module;
SigMap sigmap;
dict<SigBit, Cell *> bit_to_driver;
pool<SigBit> input_port_bits;
Cell *cell = nullptr;
int min_width = 4;
int max_width = 64;
int regions_rewritten = 0;
int cells_added = 0;
OptPriorityOnehotWorker(Module *module) : module(module), sigmap(module)
{
build_indexes();
}
bool is_sequential(Cell *c)
{
return c->type.in(
ID($ff), ID($dff), ID($dffe), ID($adff), ID($adffe),
ID($sdff), ID($sdffe), ID($sdffce), ID($dffsr), ID($dffsre),
ID($_DFF_P_), ID($_DFF_N_),
ID($_DFFE_PP_), ID($_DFFE_PN_), ID($_DFFE_NP_), ID($_DFFE_NN_),
ID($_DFF_PP0_), ID($_DFF_PP1_), ID($_DFF_PN0_), ID($_DFF_PN1_),
ID($_DFF_NP0_), ID($_DFF_NP1_), ID($_DFF_NN0_), ID($_DFF_NN1_),
ID($dlatch), ID($adlatch), ID($dlatchsr),
ID($mem), ID($mem_v2), ID($meminit), ID($meminit_v2),
ID($memrd), ID($memrd_v2), ID($memwr), ID($memwr_v2),
ID($fsm),
ID($assert), ID($assume), ID($cover), ID($live), ID($fair),
ID($print), ID($check),
ID($anyconst), ID($anyseq), ID($allconst), ID($allseq),
ID($initstate));
}
void build_indexes()
{
for (auto c : module->cells()) {
if (is_sequential(c))
continue;
for (auto &conn : c->connections()) {
if (!c->output(conn.first))
continue;
for (auto bit : sigmap(conn.second)) {
if (!bit.wire)
continue;
auto it = bit_to_driver.find(bit);
if (it == bit_to_driver.end())
bit_to_driver[bit] = c;
else if (it->second != c)
it->second = nullptr;
}
}
}
for (auto w : module->wires()) {
if (!w->port_input)
continue;
for (auto bit : sigmap(SigSpec(w)))
if (bit.wire)
input_port_bits.insert(bit);
}
}
// Combinational fanin cone of `from`. Leaves are port-input bits or bits
// driven by sequential cells / undriven. Returns false if size limits are
// exceeded.
bool get_cone(SigSpec from, pool<Cell *> &cone_cells, pool<SigBit> &leaf_bits,
int max_cone_cells, int max_leaf_bits)
{
pool<SigBit> visited;
std::queue<SigBit> worklist;
for (auto bit : sigmap(from)) {
if (!bit.wire)
continue;
if (visited.insert(bit).second)
worklist.push(bit);
}
while (!worklist.empty()) {
SigBit bit = worklist.front();
worklist.pop();
if (input_port_bits.count(bit)) {
leaf_bits.insert(bit);
if (GetSize(leaf_bits) > max_leaf_bits)
return false;
continue;
}
Cell *drv = bit_to_driver.at(bit, nullptr);
if (drv == nullptr) {
leaf_bits.insert(bit);
if (GetSize(leaf_bits) > max_leaf_bits)
return false;
continue;
}
if (!cone_cells.insert(drv).second)
continue;
if (GetSize(cone_cells) > max_cone_cells)
return false;
for (auto &conn : drv->connections()) {
if (!drv->input(conn.first))
continue;
for (auto in_bit : sigmap(conn.second)) {
if (!in_bit.wire)
continue;
if (visited.insert(in_bit).second)
worklist.push(in_bit);
}
}
}
return true;
}
// Discover the per-lane index field within candidate bus `d_sig_in`. The bus
// is viewed as `n` lanes of stride `s = width/n`; each lane must contribute
// exactly `idx_w` contiguous bits to the cone at a common within-lane offset
// (handles packed fields, where s == idx_w, and sub-fields like id[*][4:1]).
// `d_sig_in` may be a single flat input wire or a concatenation of per-lane
// split-port wires (see collect_split_input_buses).
bool infer_field(const SigSpec &d_sig_in, int n, int idx_w, int s,
const pool<SigBit> &leaf_bits, SigSpec &field_sig)
{
SigSpec d_sig = sigmap(d_sig_in);
dict<SigBit, int> d_offset;
for (int i = 0; i < GetSize(d_sig); i++)
if (d_sig[i].wire)
d_offset[d_sig[i]] = i;
vector<std::set<int>> lane_used(n);
for (auto lb : leaf_bits) {
auto it = d_offset.find(lb);
if (it == d_offset.end())
continue;
int off = it->second;
lane_used[off / s].insert(off % s);
}
int field_lo = -1;
for (int k = 0; k < n; k++) {
auto &used = lane_used[k];
if (GetSize(used) != idx_w)
return false;
int lo = *used.begin();
int expect = lo;
for (int v : used) {
if (v != expect)
return false;
expect++;
}
if (lo + idx_w > s)
return false;
if (field_lo < 0)
field_lo = lo;
else if (field_lo != lo)
return false;
}
if (field_lo < 0)
return false;
field_sig = SigSpec();
for (int k = 0; k < n; k++)
field_sig.append(d_sig.extract(k * s + field_lo, idx_w));
return true;
}
bool leaves_are_candidate_inputs(const pool<SigBit> &leaf_bits, const SigSpec &valid_sig,
const SigSpec &field_sig)
{
pool<SigBit> allowed;
for (auto bit : sigmap(valid_sig))
if (bit.wire)
allowed.insert(bit);
for (auto bit : sigmap(field_sig))
if (bit.wire)
allowed.insert(bit);
for (auto bit : leaf_bits)
if (!allowed.count(bit))
return false;
return true;
}
struct InputBus {
SigSpec sig;
std::string name;
int entries = 0;
int elem_width = 0;
};
// Parse a split-port name of the form "base[index]" (Verific lowers packed
// multi-dimensional ports into per-lane wires named this way).
bool parse_indexed_port_name(Wire *wire, std::string &base, int &index)
{
std::string name = wire->name.str();
size_t rbrack = name.size();
if (rbrack == 0 || name[rbrack - 1] != ']')
return false;
size_t lbrack = name.rfind('[');
if (lbrack == std::string::npos || lbrack + 1 >= rbrack - 1)
return false;
for (size_t i = lbrack + 1; i < rbrack - 1; i++)
if (!isdigit(name[i]))
return false;
base = name.substr(0, lbrack);
index = atoi(name.substr(lbrack + 1, rbrack - lbrack - 2).c_str());
return true;
}
// Group per-lane split-port wires into contiguous, equal-width buses. The
// run may start at any base index (Verific lowers both [N-1:0] -> id[0..N-1]
// and [N:1] -> id[1..N]); we only require consecutive indices and matching
// widths. The resulting sig is the ascending-index concatenation, so lane k
// is sig[k*elem_width ...] = the (base+k)-th port.
vector<InputBus> collect_split_input_buses(const vector<Wire *> &inputs)
{
std::map<std::string, vector<std::pair<int, Wire *>>> groups;
for (auto w : inputs) {
std::string base;
int index = -1;
if (parse_indexed_port_name(w, base, index))
groups[base].push_back({index, w});
}
vector<InputBus> buses;
for (auto &it : groups) {
auto entries = it.second;
std::sort(entries.begin(), entries.end(),
[](const std::pair<int, Wire *> &a, const std::pair<int, Wire *> &b) {
return a.first < b.first;
});
if (entries.empty())
continue;
bool contiguous = true;
int base_index = entries.front().first;
int elem_width = GetSize(entries.front().second);
for (int i = 0; i < GetSize(entries); i++) {
if (entries[i].first != base_index + i ||
GetSize(entries[i].second) != elem_width) {
contiguous = false;
break;
}
}
if (!contiguous)
continue;
SigSpec sig;
for (auto &entry : entries)
sig.append(SigSpec(entry.second));
buses.push_back({sig, it.first, GetSize(entries), elem_width});
}
return buses;
}
bool find_anchor_driver(Wire *out_wire, Cell *&anchor)
{
for (auto bit : sigmap(SigSpec(out_wire))) {
Cell *drv = bit_to_driver.at(bit, nullptr);
if (drv != nullptr) {
anchor = drv;
return true;
}
}
return false;
}
vector<TestVector> make_test_vectors(int n, int w)
{
vector<TestVector> vs;
auto base_field = [&](int mul, int add) {
vector<int> f(n);
for (int k = 0; k < n; k++)
f[k] = ((k * mul + add) % w + w) % w;
return f;
};
// All invalid: output must be all-zero.
{
TestVector t;
t.valid.assign(n, 0);
t.field = base_field(1, 0);
vs.push_back(t);
}
// Single valid lane: exercises every lane's field routing.
for (int k = 0; k < n; k++) {
TestVector t;
t.valid.assign(n, 0);
t.valid[k] = 1;
t.field = base_field(1, 0);
t.field[k] = k % w;
vs.push_back(t);
TestVector t2;
t2.valid = t.valid;
t2.field = base_field(3, 1);
vs.push_back(t2);
}
// All valid: distinguishes priority direction.
{
TestVector t;
t.valid.assign(n, 1);
t.field = base_field(1, 0);
vs.push_back(t);
}
{
TestVector t;
t.valid.assign(n, 1);
t.field = base_field(-1, w - 1);
vs.push_back(t);
}
// Prefix masks valid[k..n-1]: lowest-index winner is k (LSB-first).
for (int k = 0; k < n; k++) {
TestVector t;
t.valid.assign(n, 0);
for (int j = k; j < n; j++)
t.valid[j] = 1;
t.field = base_field(5, 2);
vs.push_back(t);
}
// Suffix masks valid[0..k]: highest-index winner is k (MSB-first).
for (int k = 0; k < n; k++) {
TestVector t;
t.valid.assign(n, 0);
for (int j = 0; j <= k; j++)
t.valid[j] = 1;
t.field = base_field(7, 3);
vs.push_back(t);
}
// Pairs with distinct fields: rejects OR-of-all and wrong direction.
for (int i = 0; i < n; i++)
for (int j = i + 1; j < n && j < i + 3; j++) {
TestVector t;
t.valid.assign(n, 0);
t.valid[i] = 1;
t.valid[j] = 1;
t.field = base_field(2, 0);
t.field[i] = (i + 1) % w;
t.field[j] = (j + 5) % w;
vs.push_back(t);
}
// Pseudo-random coverage.
uint64_t lfsr = 0x1234567089abcdefULL;
for (int r = 0; r < 32; r++) {
lfsr ^= lfsr << 13;
lfsr ^= lfsr >> 7;
lfsr ^= lfsr << 17;
TestVector t;
t.valid.resize(n);
t.field.resize(n);
for (int k = 0; k < n; k++)
t.valid[k] = (lfsr >> (k % 64)) & 1;
uint64_t f = lfsr * 2654435761ULL;
for (int k = 0; k < n; k++)
t.field[k] = (int)((f >> ((k * 3) % 60)) % (uint64_t)w);
vs.push_back(t);
}
return vs;
}
int expected_winner(const vector<int> &valid, int n, bool msb_first)
{
if (!msb_first) {
for (int k = 0; k < n; k++)
if (valid[k])
return k;
} else {
for (int k = n - 1; k >= 0; k--)
if (valid[k])
return k;
}
return -1;
}
bool fingerprint(const Candidate &cand, bool msb_first)
{
ConstEval ce(module);
SigSpec out_sig = sigmap(SigSpec(cand.out_wire));
SigSpec valid_sig = sigmap(cand.valid_sig);
SigSpec field_sig = sigmap(cand.field_sig);
vector<TestVector> vectors = make_test_vectors(cand.n, cand.w);
for (auto &tv : vectors) {
ce.push();
ce.set(valid_sig, pack_lanes(tv.valid, 1));
ce.set(field_sig, pack_lanes(tv.field, cand.idx_w));
SigSpec out = out_sig;
SigSpec undef;
bool ok = ce.eval(out, undef);
ce.pop();
if (!ok || !out.is_fully_const())
return false;
Const oc = out.as_const();
int winner = expected_winner(tv.valid, cand.n, msb_first);
for (int p = 0; p < cand.w; p++) {
bool expect = (winner >= 0) && (p == (tv.field[winner] % cand.w));
bool actual = (p < GetSize(oc)) && (oc[p] == State::S1);
if (expect != actual)
return false;
}
}
return true;
}
bool check_candidate(Candidate &cand, const pool<SigBit> &leaf_bits)
{
if (cand.n < min_width || cand.n > max_width)
return false;
if (!is_power_of_two(cand.w) || (1 << cand.idx_w) != cand.w)
return false;
if (!leaves_are_candidate_inputs(leaf_bits, cand.valid_sig, cand.field_sig)) {
log_debug(" reject %s (valid=%s index=%s): cone leaves outside valid+field\n",
log_id(cand.out_wire), log_id(cand.valid_wire), cand.index_name.c_str());
return false;
}
if (!find_anchor_driver(cand.out_wire, cand.anchor))
return false;
if (fingerprint(cand, false)) {
cand.msb_first = false;
return true;
}
if (fingerprint(cand, true)) {
cand.msb_first = true;
return true;
}
log_debug(" reject %s (valid=%s index=%s): fingerprint mismatch (both directions)\n",
log_id(cand.out_wire), log_id(cand.valid_wire), cand.index_name.c_str());
return false;
}
struct Record {
SigBit valid;
SigSpec index;
};
// Priority merge of two records: the left (higher-priority) record wins
// unless it is invalid.
Record combine(Cell *anchor, const Record &lhs, const Record &rhs)
{
Cell *cell = anchor;
SigBit lhs_invalid = module->Not(NEW_ID2_SUFFIX("prionehot_ninv"), SigSpec(lhs.valid))[0];
cells_added++;
SigBit take_rhs = module->And(NEW_ID2_SUFFIX("prionehot_take"),
SigSpec(lhs_invalid), SigSpec(rhs.valid))[0];
cells_added++;
Record out;
out.valid = module->Or(NEW_ID2_SUFFIX("prionehot_orv"),
SigSpec(lhs.valid), SigSpec(rhs.valid))[0];
cells_added++;
out.index = module->Mux(NEW_ID2_SUFFIX("prionehot_mux"), lhs.index, rhs.index,
SigSpec(take_rhs));
cells_added++;
return out;
}
Record emit_tree_rec(Cell *anchor, const vector<Record> &leaves, int begin, int end)
{
log_assert(begin < end);
if (begin + 1 == end)
return leaves[begin];
int mid = begin + (end - begin) / 2;
Record lhs = emit_tree_rec(anchor, leaves, begin, mid);
Record rhs = emit_tree_rec(anchor, leaves, mid, end);
return combine(anchor, lhs, rhs);
}
// Log-depth binary decoder gated by `valid`: returns a w-bit one-hot where
// bit p is set iff valid && index == p.
SigSpec emit_decode(Cell *anchor, SigSpec index, SigBit valid, int w, int idx_w)
{
Cell *cell = anchor;
vector<SigBit> cur;
cur.push_back(valid);
for (int b = 0; b < idx_w; b++) {
SigSpec idx_bit(index[b]);
SigBit idx_bit_n = module->Not(NEW_ID2_SUFFIX("prionehot_ndec"), idx_bit)[0];
cells_added++;
int group = GetSize(cur);
vector<SigBit> nxt(group * 2);
for (int g = 0; g < group; g++) {
nxt[g] = module->And(NEW_ID2_SUFFIX("prionehot_dec0"),
SigSpec(cur[g]), SigSpec(idx_bit_n))[0];
cells_added++;
nxt[g + group] = module->And(NEW_ID2_SUFFIX("prionehot_dec1"),
SigSpec(cur[g]), idx_bit)[0];
cells_added++;
}
cur = std::move(nxt);
}
SigSpec out;
for (int p = 0; p < w; p++)
out.append(cur[p]);
return out;
}
SigSpec emit_priority_onehot(const Candidate &cand)
{
vector<Record> leaves;
for (int k = 0; k < cand.n; k++) {
// Leftmost leaf has the highest priority.
int lane = cand.msb_first ? (cand.n - 1 - k) : k;
Record r;
r.valid = cand.valid_sig[lane];
r.index = cand.field_sig.extract(lane * cand.idx_w, cand.idx_w);
leaves.push_back(r);
}
Record root = emit_tree_rec(cand.anchor, leaves, 0, GetSize(leaves));
return emit_decode(cand.anchor, root.index, root.valid, cand.w, cand.idx_w);
}
void disconnect_old_output(const Candidate &cand)
{
pool<SigBit> target_bits;
for (auto bit : sigmap(SigSpec(cand.out_wire)))
if (bit.wire)
target_bits.insert(bit);
pool<Cell *> seen_cells;
for (auto target : target_bits) {
Cell *drv = bit_to_driver.at(target, nullptr);
if (drv == nullptr || seen_cells.count(drv))
continue;
seen_cells.insert(drv);
for (auto &conn : drv->connections()) {
if (!drv->output(conn.first))
continue;
SigSpec orig = conn.second;
SigSpec replacement = orig;
bool changed = false;
Cell *cell = drv;
Wire *dangling = module->addWire(NEW_ID2_SUFFIX("prionehot_dangling"),
GetSize(orig));
for (int i = 0; i < GetSize(orig); i++) {
if (target_bits.count(sigmap(orig[i]))) {
replacement[i] = SigBit(dangling, i);
changed = true;
}
}
if (changed)
drv->setPort(conn.first, replacement);
}
}
}
void run()
{
if (module->has_processes_warn())
return;
vector<Wire *> inputs;
vector<Wire *> outputs;
for (auto w : module->wires()) {
if (w->port_input)
inputs.push_back(w);
if (w->port_output && !w->port_input)
outputs.push_back(w);
}
// Per-lane split-port buses ("id[0]".."id[N-1]") are grouped once.
vector<InputBus> split_buses = collect_split_input_buses(inputs);
vector<Candidate> rewrites;
pool<Wire *> claimed_outputs;
for (auto out : outputs) {
if (claimed_outputs.count(out))
continue;
int w = GetSize(out);
if (w < 2 || !is_power_of_two(w))
continue;
int idx_w = clog2_int(w);
pool<Cell *> cone_cells;
pool<SigBit> leaf_bits;
int max_cone_cells = std::max(256, max_width * 96);
int max_leaf_bits = max_width * max_width + max_width * w + max_width;
if (!get_cone(SigSpec(out), cone_cells, leaf_bits, max_cone_cells, max_leaf_bits)) {
log_debug("output %s (w=%d): cone exceeds size limits\n", log_id(out), w);
continue;
}
if (cone_cells.empty())
continue;
log_debug("output %s (w=%d, idx_w=%d): cone %d cells, %d leaf bits\n",
log_id(out), w, idx_w, GetSize(cone_cells), GetSize(leaf_bits));
bool done = false;
for (auto valid : inputs) {
if (done)
break;
int n = GetSize(valid);
if (n < min_width || n > max_width)
continue;
// Candidate index sources: a flat input bus split into n lanes,
// or a per-lane split-port bus with n entries.
vector<std::pair<SigSpec, std::string>> sources;
for (auto d : inputs) {
if (d == valid)
continue;
if (GetSize(d) % n == 0)
sources.push_back({SigSpec(d), d->name.str()});
}
for (auto &bus : split_buses)
if (bus.entries == n)
sources.push_back({bus.sig, bus.name});
for (auto &src : sources) {
int total = GetSize(src.first);
if (total % n != 0)
continue;
int s = total / n;
if (s < idx_w)
continue;
SigSpec field_sig;
if (!infer_field(src.first, n, idx_w, s, leaf_bits, field_sig)) {
log_debug(" valid=%s index=%s (n=%d, s=%d): field layout inference failed\n",
log_id(valid), src.second.c_str(), n, s);
continue;
}
Candidate cand;
cand.out_wire = out;
cand.valid_wire = valid;
cand.valid_sig = SigSpec(valid);
cand.field_sig = field_sig;
cand.index_name = src.second;
cand.n = n;
cand.w = w;
cand.idx_w = idx_w;
if (!check_candidate(cand, leaf_bits))
continue;
rewrites.push_back(cand);
claimed_outputs.insert(out);
log(" %s: %s <- priority_onehot(valid=%s, index=%s) "
"[N=%d, W=%d, IDX_W=%d, %s]\n",
log_id(module), log_id(out), log_id(valid), src.second.c_str(),
cand.n, cand.w, cand.idx_w,
cand.msb_first ? "MSB-first" : "LSB-first");
done = true;
break;
}
}
}
for (auto &cand : rewrites) {
cell = cand.anchor;
SigSpec new_out = emit_priority_onehot(cand);
disconnect_old_output(cand);
module->connect(SigSpec(cand.out_wire), new_out);
regions_rewritten++;
}
}
};
struct OptPriorityOnehotPass : public Pass {
OptPriorityOnehotPass() : Pass("opt_priority_onehot",
"rewrite priority-select + one-hot index scatter into balanced trees") {}
void help() override
{
// |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|
log("\n");
log(" opt_priority_onehot [options] [selection]\n");
log("\n");
log("This pass uses functional fingerprinting to detect combinational regions\n");
log("that compute a priority (first-set) select of a per-lane index field and\n");
log("scatter the winning field into a one-hot output, regardless of how the\n");
log("RTL was written (unrolled for-loops, leading-one chains, |= scatter, etc.).\n");
log("\n");
log("Concretely, for a request/valid bus of N lanes and a per-lane index field,\n");
log("the region computes:\n");
log("\n");
log(" winner = first lane with valid[lane] set (lowest index by default)\n");
log(" out = (winner exists) ? (1 << field[winner]) : 0\n");
log("\n");
log("where out has power-of-two width W = 2^idx_w and field is idx_w bits wide.\n");
log("Each detected region is replaced with a balanced (log-depth) priority\n");
log("selection tree feeding a log-depth one-hot decoder, replacing the\n");
log("linear leading-one and scatter chains in the original RTL.\n");
log("\n");
log("The per-lane index field may be a sub-slice of a wider flat input bus\n");
log("(e.g. id[*][4:1] of a packed [N][5] bus); the lane stride and field\n");
log("offset are inferred from the fanin cone. Both LSB-first (lowest index\n");
log("wins) and MSB-first priority directions are detected.\n");
log("\n");
log(" -max-width N, -max_width N\n");
log(" maximum lane count to consider (default 64).\n");
log("\n");
log(" -min-width N, -min_width N\n");
log(" minimum lane count to consider (default 4).\n");
log("\n");
log("After rewriting, the original cone cells become unused and are removed\n");
log("by the trailing 'clean -purge'.\n");
log("\n");
}
void execute(std::vector<std::string> args, RTLIL::Design *design) override
{
log_header(design, "Executing OPT_PRIORITY_ONEHOT pass (priority select + one-hot scatter).\n");
int max_width = 64;
int min_width = 4;
size_t argidx;
for (argidx = 1; argidx < args.size(); argidx++) {
if ((args[argidx] == "-max-width" || args[argidx] == "-max_width") &&
argidx + 1 < args.size()) {
max_width = std::stoi(args[++argidx]);
continue;
}
if ((args[argidx] == "-min-width" || args[argidx] == "-min_width") &&
argidx + 1 < args.size()) {
min_width = std::stoi(args[++argidx]);
continue;
}
break;
}
extra_args(args, argidx, design);
int total_regions = 0;
int total_cells_added = 0;
for (auto module : design->selected_modules()) {
OptPriorityOnehotWorker worker(module);
worker.max_width = max_width;
worker.min_width = min_width;
worker.run();
total_regions += worker.regions_rewritten;
total_cells_added += worker.cells_added;
}
log("Rewrote %d region(s); emitted %d new cell(s).\n",
total_regions, total_cells_added);
if (total_regions)
Yosys::run_pass("clean -purge");
}
} OptPriorityOnehotPass;
PRIVATE_NAMESPACE_END

View file

@ -0,0 +1,235 @@
// Test designs for opt_priority_onehot.
//
// Each "positive" module computes a lowest/highest-index priority select of a
// per-lane index field and scatters the winning field into a one-hot output,
// mirroring the qor_spi_ra_binary_tree shape. The "negative" modules look
// similar but compute a different function and must be left untouched.
// Main shape: N=16 lanes, 5-bit id lanes, 4-bit field id[*][4:1], LSB-first.
module pri_onehot_basic (
input wire [15:0] req,
input wire [15:0][4:0] id,
output reg [15:0] oneh
);
always_comb begin
reg [15:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 16; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][4:1]] |= sel[I];
end
end
endmodule
// One-based ports [N:1] / id[I][IDX_W:1], matching the qor_spi_ra_binary_tree
// regression exactly (Verific splits id into id[1]..id[16]).
module pri_onehot_onebased (
input wire [16:1] req,
input wire [16:1][4:0] id,
output reg [15:0] oneh
);
always_comb begin
reg [16:1] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 1; I <= 16; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][4:1]] |= sel[I];
end
end
endmodule
// Packed field: no per-lane gap (ID_W == IDX_W == 4), field id[*][3:0].
module pri_onehot_packed (
input wire [15:0] req,
input wire [15:0][3:0] id,
output reg [15:0] oneh
);
always_comb begin
reg [15:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 16; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][3:0]] |= sel[I];
end
end
endmodule
// Scaled down: N=8, 4-bit id lanes, 3-bit field id[*][3:1], W=8.
module pri_onehot_w8 (
input wire [7:0] req,
input wire [7:0][3:0] id,
output reg [7:0] oneh
);
always_comb begin
reg [7:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 8; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][3:1]] |= sel[I];
end
end
endmodule
// Scaled up: N=32, 6-bit id lanes, 5-bit field id[*][5:1], W=32.
module pri_onehot_w32 (
input wire [31:0] req,
input wire [31:0][5:0] id,
output reg [31:0] oneh
);
always_comb begin
reg [31:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 32; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][5:1]] |= sel[I];
end
end
endmodule
// Lane count != output width: N=8 lanes, 4-bit field, W=16.
module pri_onehot_n8_w16 (
input wire [7:0] req,
input wire [7:0][4:0] id,
output reg [15:0] oneh
);
always_comb begin
reg [7:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 8; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][4:1]] |= sel[I];
end
end
endmodule
// MSB-first priority: highest set index wins (W=8 to keep SAT cheap).
module pri_onehot_msb (
input wire [7:0] req,
input wire [7:0][3:0] id,
output reg [7:0] oneh
);
always_comb begin
reg [7:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 7; I >= 0; I--) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][3:1]] |= sel[I];
end
end
endmodule
// Two independent priority-onehot regions in one module.
module pri_onehot_two_regions (
input wire [7:0] req_a,
input wire [7:0][3:0] id_a,
input wire [7:0] req_b,
input wire [7:0][3:0] id_b,
output reg [7:0] oneh_a,
output reg [7:0] oneh_b
);
always_comb begin
reg [7:0] acc, sel;
acc = '0; sel = '0; oneh_a = '0;
for (int I = 0; I < 8; I++) begin
sel[I] = req_a[I] & ~(|acc);
acc[I] = req_a[I];
oneh_a[id_a[I][3:1]] |= sel[I];
end
end
always_comb begin
reg [7:0] acc2, sel2;
acc2 = '0; sel2 = '0; oneh_b = '0;
for (int I = 0; I < 8; I++) begin
sel2[I] = req_b[I] & ~(|acc2);
acc2[I] = req_b[I];
oneh_b[id_b[I][3:1]] |= sel2[I];
end
end
endmodule
// One-hot output also consumed by a downstream parity output.
module pri_onehot_shared_consumer (
input wire [7:0] req,
input wire [7:0][3:0] id,
output reg [7:0] oneh,
output wire par
);
always_comb begin
reg [7:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 8; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][3:1]] |= sel[I];
end
end
assign par = ^oneh;
endmodule
// ---------------------------------------------------------------------------
// Negative / near-miss modules: must NOT be rewritten.
// ---------------------------------------------------------------------------
// No priority: OR of all decoded fields (output is generally not one-hot).
module pri_onehot_orall (
input wire [15:0] req,
input wire [15:0][4:0] id,
output reg [15:0] oneh
);
always_comb begin
oneh = '0;
for (int I = 0; I < 16; I++)
oneh[id[I][4:1]] |= req[I];
end
endmodule
// Nonzero default when no request: function is not "0 when all-invalid".
module pri_onehot_nonzero_default (
input wire [15:0] req,
input wire [15:0][4:0] id,
output reg [15:0] oneh
);
always_comb begin
reg [15:0] acc, sel;
acc = '0; sel = '0; oneh = '1;
for (int I = 0; I < 16; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][4:1]] |= sel[I];
end
end
endmodule
// Non-power-of-two output width.
module pri_onehot_nonpow2 (
input wire [11:0] req,
input wire [11:0][3:0] id,
output reg [11:0] oneh
);
always_comb begin
reg [11:0] acc, sel;
acc = '0; sel = '0; oneh = '0;
for (int I = 0; I < 12; I++) begin
sel[I] = req[I] & ~(|acc);
acc[I] = req[I];
oneh[id[I][3:1]] |= sel[I];
end
end
endmodule
// Unrelated mux logic.
module pri_onehot_unrelated (
input wire [15:0] a,
input wire [15:0] b,
input wire s,
output wire [15:0] y
);
assign y = s ? a : b;
endmodule

View file

@ -0,0 +1,301 @@
# Tests for opt_priority_onehot.
# Helper pattern per positive module: import a gold copy, import a gate copy,
# rewrite the gate, assert the rewrite fired, then prove gold == gate by SAT.
log -header "Basic priority-onehot self-equivalence (N=16, field id[*][4:1])"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_basic
proc; opt_clean
rename pri_onehot_basic gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_basic
proc; opt_clean
select -module pri_onehot_basic
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_basic gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "One-based [N:1] ports self-equivalence (regression shape)"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_onebased
proc; opt_clean
rename pri_onehot_onebased gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_onebased
proc; opt_clean
select -module pri_onehot_onebased
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_onebased gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "Packed field self-equivalence (ID_W == IDX_W, no gap)"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_packed
proc; opt_clean
rename pri_onehot_packed gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_packed
proc; opt_clean
select -module pri_onehot_packed
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_packed gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "Scaled N=8 self-equivalence"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_w8
proc; opt_clean
rename pri_onehot_w8 gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_w8
proc; opt_clean
select -module pri_onehot_w8
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_w8 gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "Lane count != output width self-equivalence (N=8, W=16)"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_n8_w16
proc; opt_clean
rename pri_onehot_n8_w16 gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_n8_w16
proc; opt_clean
select -module pri_onehot_n8_w16
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_n8_w16 gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "MSB-first priority self-equivalence"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_msb
proc; opt_clean
rename pri_onehot_msb gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_msb
proc; opt_clean
select -module pri_onehot_msb
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_msb gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "Two independent regions self-equivalence"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_two_regions
proc; opt_clean
rename pri_onehot_two_regions gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_two_regions
proc; opt_clean
select -module pri_onehot_two_regions
opt_priority_onehot
select -clear
opt_clean
select -assert-min 2 w:*prionehot*
rename pri_onehot_two_regions gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "Shared consumer of one-hot output stays equivalent"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_shared_consumer
proc; opt_clean
rename pri_onehot_shared_consumer gold
read -sv opt_priority_onehot.sv
verific -import pri_onehot_shared_consumer
proc; opt_clean
select -module pri_onehot_shared_consumer
opt_priority_onehot
select -clear
opt_clean
select -assert-min 1 w:*prionehot*
rename pri_onehot_shared_consumer gate
miter -equiv -flatten -make_assert gold gate miter
hierarchy -top miter
proc; opt; memory; opt
sat -prove-asserts -verify
design -reset
log -pop
log -header "Scaled N=32 structural rewrite"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_w32
proc; opt_clean
opt_priority_onehot
opt_clean
select -assert-min 1 w:*prionehot*
design -reset
log -pop
log -header "Negative: OR-of-all (no priority) unchanged"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_orall
proc; opt_clean
opt_priority_onehot
select -assert-none w:*prionehot*
design -reset
log -pop
log -header "Negative: nonzero all-invalid default unchanged"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_nonzero_default
proc; opt_clean
opt_priority_onehot
select -assert-none w:*prionehot*
design -reset
log -pop
log -header "Negative: non-power-of-two output width unchanged"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_nonpow2
proc; opt_clean
opt_priority_onehot
select -assert-none w:*prionehot*
design -reset
log -pop
log -header "Negative: unrelated mux logic unchanged"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_unrelated
proc; opt_clean
select -assert-count 1 t:$mux
opt_priority_onehot
select -assert-none w:*prionehot*
select -assert-count 1 t:$mux
design -reset
log -pop
log -header "Max-width below lane count leaves design unchanged"
log -push
design -reset
verific -cfg veri_optimize_wide_selector 1
verific -cfg db_infer_wide_muxes_post_elaboration 0
read -sv opt_priority_onehot.sv
verific -import pri_onehot_basic
proc; opt_clean
opt_priority_onehot -max_width 8
select -assert-none w:*prionehot*
design -reset
log -pop