timing: Add topological sort from Yosys
Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
parent
d0772ce1e3
commit
7a546b1554
@ -34,6 +34,7 @@ void TimingAnalyser::setup()
|
|||||||
{
|
{
|
||||||
init_ports();
|
init_ports();
|
||||||
get_cell_delays();
|
get_cell_delays();
|
||||||
|
topo_sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimingAnalyser::init_ports()
|
void TimingAnalyser::init_ports()
|
||||||
@ -43,6 +44,7 @@ void TimingAnalyser::init_ports()
|
|||||||
CellInfo *ci = cell.second;
|
CellInfo *ci = cell.second;
|
||||||
for (auto port : sorted_ref(ci->ports)) {
|
for (auto port : sorted_ref(ci->ports)) {
|
||||||
auto &data = ports[CellPortKey(ci->name, port.first)];
|
auto &data = ports[CellPortKey(ci->name, port.first)];
|
||||||
|
data.type = port.second.type;
|
||||||
data.cell_port = CellPortKey(ci->name, port.first);
|
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(); }
|
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); }
|
PortInfo &TimingAnalyser::port_info(const CellPortKey &key) { return ctx->cells.at(key.cell)->ports.at(key.port); }
|
||||||
|
@ -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); }
|
||||||
|
inline bool operator<(const CellPortKey &other) const
|
||||||
|
{
|
||||||
|
return cell == other.cell ? port < other.port : cell < other.cell;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NetPortKey
|
struct NetPortKey
|
||||||
@ -104,6 +108,7 @@ struct TimingAnalyser
|
|||||||
private:
|
private:
|
||||||
void init_ports();
|
void init_ports();
|
||||||
void get_cell_delays();
|
void get_cell_delays();
|
||||||
|
void topo_sort();
|
||||||
// To avoid storing the domain tag structure (which could get large when considering more complex constrained tag
|
// 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
|
// cases), assign each domain an ID and use that instead
|
||||||
typedef int domain_id_t;
|
typedef int domain_id_t;
|
||||||
@ -153,6 +158,7 @@ struct TimingAnalyser
|
|||||||
{
|
{
|
||||||
CellPortKey cell_port;
|
CellPortKey cell_port;
|
||||||
NetPortKey net_port;
|
NetPortKey net_port;
|
||||||
|
PortType type;
|
||||||
// per domain timings
|
// per domain timings
|
||||||
std::unordered_map<domain_id_t, PortDomainData> domains;
|
std::unordered_map<domain_id_t, PortDomainData> domains;
|
||||||
// cell timing arcs to (outputs)/from (inputs) from this port
|
// 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::unordered_map<ClockDomainKey, domain_id_t, ClockDomainKey::Hash> domain_to_id;
|
||||||
std::vector<ClockDomainKey> id_to_domain;
|
std::vector<ClockDomainKey> id_to_domain;
|
||||||
|
|
||||||
|
std::vector<CellPortKey> topological_order;
|
||||||
|
|
||||||
Context *ctx;
|
Context *ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -181,6 +181,88 @@ template <typename ForwardRange> inline auto get_only_value(ForwardRange r)
|
|||||||
return get_only_value(b, e);
|
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
|
NEXTPNR_NAMESPACE_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
Reference in New Issue
Block a user