timing: Add topological sort from Yosys

Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
gatecat 2021-03-01 10:36:23 +00:00
parent d0772ce1e3
commit 7a546b1554
3 changed files with 130 additions and 0 deletions

View File

@ -34,6 +34,7 @@ void TimingAnalyser::setup()
{
init_ports();
get_cell_delays();
topo_sort();
}
void TimingAnalyser::init_ports()
@ -43,6 +44,7 @@ void TimingAnalyser::init_ports()
CellInfo *ci = cell.second;
for (auto port : sorted_ref(ci->ports)) {
auto &data = ports[CellPortKey(ci->name, port.first)];
data.type = port.second.type;
data.cell_port = CellPortKey(ci->name, port.first);
}
}
@ -118,6 +120,44 @@ void TimingAnalyser::get_cell_delays()
}
}
void TimingAnalyser::topo_sort()
{
TopoSort<CellPortKey> topo;
for (auto &port : ports) {
auto &pd = port.second;
// All ports are nodes
topo.node(port.first);
if (pd.type == PORT_IN) {
// inputs: combinational arcs through the cell are edges
for (auto &arc : pd.cell_arcs) {
if (arc.type != CellArc::COMBINATIONAL)
continue;
topo.edge(port.first, CellPortKey(port.first.cell, arc.other_port));
}
} else if (pd.type == PORT_OUT) {
// output: routing arcs are edges
const NetInfo *pn = port_info(port.first).net;
if (pn != nullptr) {
for (auto &usr : pn->users)
topo.edge(port.first, CellPortKey(usr));
}
}
}
bool no_loops = topo.sort();
if (!no_loops) {
log_info("Found %d combinational loops:\n", int(topo.loops.size()));
int i = 0;
for (auto &loop : topo.loops) {
log_info(" loop %d:\n", ++i);
for (auto &port : loop) {
log_info(" %s.%s (%s)\n", ctx->nameOf(port.cell), ctx->nameOf(port.port),
ctx->nameOf(port_info(port).net));
}
}
}
std::swap(topological_order, topo.sorted);
}
CellInfo *TimingAnalyser::cell_info(const CellPortKey &key) { return ctx->cells.at(key.cell).get(); }
PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); }

View File

@ -45,6 +45,10 @@ struct CellPortKey
}
};
inline bool operator==(const CellPortKey &other) const { return (cell == other.cell) && (port == other.port); }
inline bool operator<(const CellPortKey &other) const
{
return cell == other.cell ? port < other.port : cell < other.cell;
}
};
struct NetPortKey
@ -104,6 +108,7 @@ struct TimingAnalyser
private:
void init_ports();
void get_cell_delays();
void topo_sort();
// To avoid storing the domain tag structure (which could get large when considering more complex constrained tag
// cases), assign each domain an ID and use that instead
typedef int domain_id_t;
@ -153,6 +158,7 @@ struct TimingAnalyser
{
CellPortKey cell_port;
NetPortKey net_port;
PortType type;
// per domain timings
std::unordered_map<domain_id_t, PortDomainData> domains;
// cell timing arcs to (outputs)/from (inputs) from this port
@ -168,6 +174,8 @@ struct TimingAnalyser
std::unordered_map<ClockDomainKey, domain_id_t, ClockDomainKey::Hash> domain_to_id;
std::vector<ClockDomainKey> id_to_domain;
std::vector<CellPortKey> topological_order;
Context *ctx;
};

View File

@ -181,6 +181,88 @@ template <typename ForwardRange> inline auto get_only_value(ForwardRange r)
return get_only_value(b, e);
}
// From Yosys
// https://github.com/YosysHQ/yosys/blob/0fb4224ebca86156a1296b9210116d9a9cbebeed/kernel/utils.h#L131
template <typename T, typename C = std::less<T>> struct TopoSort
{
bool analyze_loops, found_loops;
std::map<T, std::set<T, C>, C> database;
std::set<std::set<T, C>> loops;
std::vector<T> sorted;
TopoSort()
{
analyze_loops = true;
found_loops = false;
}
void node(T n)
{
if (database.count(n) == 0)
database[n] = std::set<T, C>();
}
void edge(T left, T right)
{
node(left);
database[right].insert(left);
}
void sort_worker(const T &n, std::set<T, C> &marked_cells, std::set<T, C> &active_cells,
std::vector<T> &active_stack)
{
if (active_cells.count(n)) {
found_loops = true;
if (analyze_loops) {
std::set<T, C> loop;
for (int i = int(active_stack.size()) - 1; i >= 0; i--) {
loop.insert(active_stack[i]);
if (active_stack[i] == n)
break;
}
loops.insert(loop);
}
return;
}
if (marked_cells.count(n))
return;
if (!database.at(n).empty()) {
if (analyze_loops)
active_stack.push_back(n);
active_cells.insert(n);
for (auto &left_n : database.at(n))
sort_worker(left_n, marked_cells, active_cells, active_stack);
if (analyze_loops)
active_stack.pop_back();
active_cells.erase(n);
}
marked_cells.insert(n);
sorted.push_back(n);
}
bool sort()
{
loops.clear();
sorted.clear();
found_loops = false;
std::set<T, C> marked_cells;
std::set<T, C> active_cells;
std::vector<T> active_stack;
for (auto &it : database)
sort_worker(it.first, marked_cells, active_cells, active_stack);
NPNR_ASSERT(sorted.size() == database.size());
return !found_loops;
}
};
NEXTPNR_NAMESPACE_END
#endif