3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2026-07-15 03:35:40 +00:00
yosys/kernel/utils.h
2026-06-22 17:53:19 +02:00

414 lines
11 KiB
C++

/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
// This file contains various c++ utility routines and helper classes that
// do not depend on any other components of yosys (except stuff like log_*).
#include "kernel/yosys.h"
#include <iterator>
#include <optional>
#ifndef UTILS_H
#define UTILS_H
YOSYS_NAMESPACE_BEGIN
// ------------------------------------------------
// A map-like container, but you can save and restore the state
// ------------------------------------------------
template<typename Key, typename T>
struct stackmap
{
private:
std::vector<dict<Key, T*>> backup_state;
dict<Key, T> current_state;
static T empty_tuple;
public:
stackmap() { }
stackmap(const dict<Key, T> &other) : current_state(other) { }
template<typename Other>
void operator=(const Other &other)
{
for (auto &it : current_state)
if (!backup_state.empty() && backup_state.back().count(it.first) == 0)
backup_state.back()[it.first] = new T(it.second);
current_state.clear();
for (auto &it : other)
set(it.first, it.second);
}
bool has(const Key &k)
{
return current_state.count(k) != 0;
}
void set(const Key &k, const T &v)
{
if (!backup_state.empty() && backup_state.back().count(k) == 0)
backup_state.back()[k] = current_state.count(k) ? new T(current_state.at(k)) : nullptr;
current_state[k] = v;
}
void unset(const Key &k)
{
if (!backup_state.empty() && backup_state.back().count(k) == 0)
backup_state.back()[k] = current_state.count(k) ? new T(current_state.at(k)) : nullptr;
current_state.erase(k);
}
const T &get(const Key &k)
{
if (current_state.count(k) == 0)
return empty_tuple;
return current_state.at(k);
}
void reset(const Key &k)
{
for (int i = GetSize(backup_state)-1; i >= 0; i--)
if (backup_state[i].count(k) != 0) {
if (backup_state[i].at(k) == nullptr)
current_state.erase(k);
else
current_state[k] = *backup_state[i].at(k);
return;
}
current_state.erase(k);
}
const dict<Key, T> &stdmap()
{
return current_state;
}
void save()
{
backup_state.resize(backup_state.size()+1);
}
void restore()
{
log_assert(!backup_state.empty());
for (auto &it : backup_state.back())
if (it.second != nullptr) {
current_state[it.first] = *it.second;
delete it.second;
} else
current_state.erase(it.first);
backup_state.pop_back();
}
~stackmap()
{
while (!backup_state.empty())
restore();
}
};
// ---------------------------------------------------
// ---------------------------------------------------
//
// Many passes that split a multi-bit cell or word-level FF into smaller
// pieces share the same shape:
//
// 1. iterate bits of the output
// 2. for each bit, compute a key describing where it should go (or
// decide it stays where it is)
// 3. emit one piece per distinct key, covering exactly that group's bits
// 4. either consume the un-grouped "remaining" bits as a smaller original,
// or report that everything was grouped.
//
// BitGrouper handles (1)+(2)+(3)'s bookkeeping; callers do (3)'s
// per-group emission and (4)'s remainder handling.
//
// Construction is one-shot: pass a width and a `key_fn(int) -> optional<Key>`.
// `key_fn` returning std::nullopt leaves the bit in `remaining()`.
//
// Group iteration order matches first appearance, so emission is
// deterministic across runs.
template<typename Key>
class BitGrouper
{
public:
struct Group {
Key key;
std::vector<int> indices;
// True when `indices` is exactly [front, front+size). Callers can
// use this to pick the cheap contiguous slice op when available.
bool is_contiguous() const {
if (indices.empty())
return true;
int start = indices.front();
for (size_t i = 0; i < indices.size(); i++)
if (indices[i] != start + (int)i)
return false;
return true;
}
};
template<typename KeyFn>
BitGrouper(int width, KeyFn key_fn) {
std::map<Key, size_t> idx;
for (int i = 0; i < width; i++) {
std::optional<Key> k = key_fn(i);
if (!k) {
remaining_.push_back(i);
continue;
}
auto it = idx.find(*k);
if (it == idx.end()) {
idx.emplace(*k, groups_.size());
groups_.push_back({*k, {i}});
} else {
groups_[it->second].indices.push_back(i);
}
}
}
const std::vector<Group> &groups() const { return groups_; }
const std::vector<int> &remaining() const { return remaining_; }
bool fully_grouped() const { return remaining_.empty(); }
bool nothing_grouped() const { return groups_.empty(); }
// Pick the bits of `sig` at the group's indices. Uses contiguous-range
// slicing when possible.
static RTLIL::SigSpec extract(const RTLIL::SigSpec &sig, const Group &g) {
if (g.is_contiguous() && !g.indices.empty())
return sig.extract(g.indices.front(), (int)g.indices.size());
RTLIL::SigSpec out;
for (int i : g.indices)
out.append(sig[i]);
return out;
}
private:
std::vector<Group> groups_;
std::vector<int> remaining_;
};
// ---------------------------------------------------
// Best-effort topological sorting with loop detection
// ---------------------------------------------------
template <typename T, typename C = std::less<T>> class TopoSort
{
static_assert(!(std::is_pointer<T>::value && std::is_same<C, std::less<T>>::value),
"std::less is run-to-run unstable for pointers");
public:
// We use this ordering of the edges in the adjacency matrix for
// exact compatibility with an older implementation.
struct IndirectCmp {
IndirectCmp(const std::vector<T> &nodes) : node_cmp_(), nodes_(nodes) {}
bool operator()(int a, int b) const
{
log_assert(static_cast<size_t>(a) < nodes_.size());
log_assert(static_cast<size_t>(b) < nodes_.size());
return node_cmp_(nodes_[a], nodes_[b]);
}
const C node_cmp_;
const std::vector<T> &nodes_;
};
bool analyze_loops;
// The stability doesn't rely on std::less of T, so pointers are safe
std::map<T, int, C> node_to_index;
// edges[i] is the set of nodes with an edge into node i
std::vector<std::set<int, IndirectCmp>> edges;
std::vector<T> sorted;
std::set<std::vector<T>> loops;
TopoSort() : indirect_cmp(nodes)
{
analyze_loops = true;
found_loops = false;
}
int node(T n)
{
auto rv = node_to_index.emplace(n, static_cast<int>(nodes.size()));
if (rv.second) {
nodes.push_back(n);
edges.push_back(std::set<int, IndirectCmp>(indirect_cmp));
}
return rv.first->second;
}
void edge(int l_index, int r_index) { edges[r_index].insert(l_index); }
void edge(T left, T right) { edge(node(left), node(right)); }
bool has_node(const T &node) { return node_to_index.find(node) != node_to_index.end(); }
bool sort()
{
log_assert(GetSize(node_to_index) == GetSize(edges));
log_assert(GetSize(nodes) == GetSize(edges));
loops.clear();
sorted.clear();
found_loops = false;
std::vector<bool> node_is_sorted(edges.size(), false);
std::vector<bool> node_is_on_stack(edges.size(), false);
// Only used with analyze_loops
std::vector<int> stack;
sorted.reserve(edges.size());
for (const auto &it : node_to_index)
sort_worker(it.second, node_is_sorted, node_is_on_stack, stack);
log_assert(GetSize(sorted) == GetSize(nodes));
return !found_loops;
}
// Build the more expensive representation of edges for
// a few passes that use it directly.
std::map<T, std::set<T, C>, C> get_database()
{
std::map<T, std::set<T, C>, C> database;
for (size_t i = 0; i < nodes.size(); ++i) {
std::set<T, C> converted_edge_set;
for (int other_node : edges[i]) {
converted_edge_set.insert(nodes[other_node]);
}
database.emplace(nodes[i], converted_edge_set);
}
return database;
}
private:
bool found_loops;
std::vector<T> nodes;
const IndirectCmp indirect_cmp;
void sort_worker(const int root_index, std::vector<bool> &node_is_sorted, std::vector<bool> &node_is_on_stack, std::vector<int> &stack)
{
if (node_is_on_stack[root_index]) {
// We've been here before, meaning we have a loop
found_loops = true;
if (analyze_loops) {
std::vector<T> loop;
for (int i = GetSize(stack) - 1; i >= 0; i--) {
const int index = stack[i];
loop.push_back(nodes[index]);
if (index == root_index)
break;
}
loops.insert(loop);
}
return;
}
// We're done if we've already sorted this subgraph
if (node_is_sorted[root_index])
return;
if (!edges[root_index].empty()) {
if (analyze_loops)
stack.push_back(root_index);
node_is_on_stack[root_index] = true;
for (int left_n : edges[root_index])
sort_worker(left_n, node_is_sorted, node_is_on_stack, stack);
if (analyze_loops)
stack.pop_back();
node_is_on_stack[root_index] = false;
}
node_is_sorted[root_index] = true;
sorted.push_back(nodes[root_index]);
}
};
// this class is used for implementing operator-> on iterators that return values rather than references
// it's necessary because in C++ operator-> is called recursively until a raw pointer is obtained
template<class T>
struct arrow_proxy {
T v;
explicit arrow_proxy(T const & v) : v(v) {}
T* operator->() { return &v; }
};
inline int ceil_log2(int x)
{
#if defined(__GNUC__)
return x > 1 ? (8*sizeof(int)) - __builtin_clz(x-1) : 0;
#else
if (x <= 0)
return 0;
for (int i = 0; i < 32; i++)
if (((x-1) >> i) == 0)
return i;
log_abort();
#endif
}
template <typename T>
auto reversed(T& container) {
struct reverse_view {
reverse_view(T& container) : container(container) {}
auto begin() const { return container.rbegin(); }
auto end() const { return container.rend(); }
T& container;
};
return reverse_view{container};
}
template <typename T>
auto reversed(const T& container) {
struct reverse_view {
reverse_view(const T& container) : container(container) {}
auto begin() const { return container.rbegin(); }
auto end() const { return container.rend(); }
const T& container;
};
return reverse_view{container};
}
// A range of integers [start_, end_) that can be iterated over with a
// C++ range-based for loop.
struct IntRange {
int start_;
int end_;
struct Int {
int v;
int operator*() const { return v; }
Int &operator++() { ++v; return *this; }
bool operator!=(const Int &other) const { return v != other.v; }
};
Int begin() const { return {start_}; }
Int end() const { return {end_}; }
bool operator==(const IntRange &other) const { return start_ == other.start_ && end_ == other.end_; }
bool operator!=(const IntRange &other) const { return !(*this == other); }
};
YOSYS_NAMESPACE_END
#endif