From 7a546b15541a9f3b5be77f13250f3755d4f66e91 Mon Sep 17 00:00:00 2001 From: gatecat Date: Mon, 1 Mar 2021 10:36:23 +0000 Subject: timing: Add topological sort from Yosys Signed-off-by: gatecat --- common/timing.cc | 40 +++++++++++++++++++++++++++ common/timing.h | 8 ++++++ common/util.h | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) diff --git a/common/timing.cc b/common/timing.cc index 785f9e7b..f642153f 100644 --- a/common/timing.cc +++ b/common/timing.cc @@ -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 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); } diff --git a/common/timing.h b/common/timing.h index 9c15911d..e07dc105 100644 --- a/common/timing.h +++ b/common/timing.h @@ -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 domains; // cell timing arcs to (outputs)/from (inputs) from this port @@ -168,6 +174,8 @@ struct TimingAnalyser std::unordered_map domain_to_id; std::vector id_to_domain; + std::vector topological_order; + Context *ctx; }; diff --git a/common/util.h b/common/util.h index 55718344..ce2b4da1 100644 --- a/common/util.h +++ b/common/util.h @@ -181,6 +181,88 @@ template 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 > struct TopoSort +{ + bool analyze_loops, found_loops; + std::map, C> database; + std::set> loops; + std::vector sorted; + + TopoSort() + { + analyze_loops = true; + found_loops = false; + } + + void node(T n) + { + if (database.count(n) == 0) + database[n] = std::set(); + } + + void edge(T left, T right) + { + node(left); + database[right].insert(left); + } + + void sort_worker(const T &n, std::set &marked_cells, std::set &active_cells, + std::vector &active_stack) + { + if (active_cells.count(n)) { + found_loops = true; + if (analyze_loops) { + std::set 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 marked_cells; + std::set active_cells; + std::vector 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 -- cgit v1.2.3