timing: Add port-domain tracking
Signed-off-by: gatecat <gatecat@ds0.me>
This commit is contained in:
parent
7a546b1554
commit
534e69fbff
@ -35,6 +35,7 @@ void TimingAnalyser::setup()
|
||||
init_ports();
|
||||
get_cell_delays();
|
||||
topo_sort();
|
||||
setup_port_domains();
|
||||
}
|
||||
|
||||
void TimingAnalyser::init_ports()
|
||||
@ -80,6 +81,8 @@ void TimingAnalyser::get_cell_delays()
|
||||
if (cls == TMG_REGISTER_INPUT) {
|
||||
for (int i = 0; i < clkInfoCount; i++) {
|
||||
auto info = ctx->getPortClockingInfo(ci, name, i);
|
||||
if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr)
|
||||
continue;
|
||||
pd.cell_arcs.emplace_back(CellArc::SETUP, info.clock_port, DelayQuad(info.setup, info.setup),
|
||||
info.edge);
|
||||
pd.cell_arcs.emplace_back(CellArc::HOLD, info.clock_port, DelayQuad(info.hold, info.hold),
|
||||
@ -102,6 +105,8 @@ void TimingAnalyser::get_cell_delays()
|
||||
if (cls == TMG_REGISTER_OUTPUT) {
|
||||
for (int i = 0; i < clkInfoCount; i++) {
|
||||
auto info = ctx->getPortClockingInfo(ci, name, i);
|
||||
if (!ci->ports.count(info.clock_port) || ci->ports.at(info.clock_port).net == nullptr)
|
||||
continue;
|
||||
pd.cell_arcs.emplace_back(CellArc::CLK_TO_Q, info.clock_port, info.clockToQ, info.edge);
|
||||
}
|
||||
}
|
||||
@ -158,6 +163,89 @@ void TimingAnalyser::topo_sort()
|
||||
std::swap(topological_order, topo.sorted);
|
||||
}
|
||||
|
||||
void TimingAnalyser::setup_port_domains()
|
||||
{
|
||||
for (auto &d : domains) {
|
||||
d.startpoints.clear();
|
||||
d.endpoints.clear();
|
||||
}
|
||||
// Go forward through the topological order (domains from the PoV of arrival time)
|
||||
for (auto port : topological_order) {
|
||||
auto &pd = ports.at(port);
|
||||
auto &pi = port_info(port);
|
||||
if (pi.type == PORT_OUT) {
|
||||
for (auto &fanin : pd.cell_arcs) {
|
||||
if (fanin.type != CellArc::CLK_TO_Q)
|
||||
continue;
|
||||
// registered outputs are startpoints
|
||||
auto dom = domain_id(port.cell, fanin.other_port, fanin.edge);
|
||||
// create per-domain data
|
||||
pd.domains[dom].has_arrival = true;
|
||||
domains.at(dom).startpoints.emplace_back(port, fanin.other_port);
|
||||
}
|
||||
// copy domains across routing
|
||||
if (pi.net != nullptr)
|
||||
for (auto &usr : pi.net->users)
|
||||
copy_domains(port, CellPortKey(usr), false);
|
||||
} else {
|
||||
// copy domains from input to output
|
||||
for (auto &fanout : pd.cell_arcs) {
|
||||
if (fanout.type != CellArc::COMBINATIONAL)
|
||||
continue;
|
||||
copy_domains(port, CellPortKey(port.cell, fanout.other_port), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Go backward through the topological order (domains from the PoV of required time)
|
||||
for (auto port : reversed_range(topological_order)) {
|
||||
auto &pd = ports.at(port);
|
||||
auto &pi = port_info(port);
|
||||
if (pi.type == PORT_OUT) {
|
||||
// copy domains from output to input
|
||||
for (auto &fanin : pd.cell_arcs) {
|
||||
if (fanin.type != CellArc::COMBINATIONAL)
|
||||
continue;
|
||||
copy_domains(port, CellPortKey(port.cell, fanin.other_port), true);
|
||||
}
|
||||
} else {
|
||||
for (auto &fanout : pd.cell_arcs) {
|
||||
if (fanout.type != CellArc::SETUP)
|
||||
continue;
|
||||
// registered inputs are startpoints
|
||||
auto dom = domain_id(port.cell, fanout.other_port, fanout.edge);
|
||||
// create per-domain data
|
||||
pd.domains[dom].has_required = true;
|
||||
domains.at(dom).startpoints.emplace_back(port, fanout.other_port);
|
||||
}
|
||||
// copy port to driver
|
||||
if (pi.net != nullptr && pi.net->driver.cell != nullptr)
|
||||
copy_domains(port, CellPortKey(pi.net->driver), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimingAnalyser::domain_id_t TimingAnalyser::domain_id(IdString cell, IdString clock_port, ClockEdge edge)
|
||||
{
|
||||
return domain_id(ctx->cells.at(cell)->ports.at(clock_port).net, edge);
|
||||
}
|
||||
TimingAnalyser::domain_id_t TimingAnalyser::domain_id(const NetInfo *net, ClockEdge edge)
|
||||
{
|
||||
NPNR_ASSERT(net != nullptr);
|
||||
ClockDomainKey key{net->name, edge};
|
||||
auto inserted = domain_to_id.emplace(key, domain_to_id.size());
|
||||
if (inserted.second) {
|
||||
domains.emplace_back(key);
|
||||
}
|
||||
return inserted.first->second;
|
||||
}
|
||||
void TimingAnalyser::copy_domains(const CellPortKey &from, const CellPortKey &to, bool backward)
|
||||
{
|
||||
auto &f = ports.at(from), &t = ports.at(to);
|
||||
for (auto &dom : f.domains)
|
||||
if (backward ? dom.second.has_required : dom.second.has_arrival)
|
||||
(backward ? t.domains[dom.first].has_required : t.domains[dom.first].has_arrival) = true;
|
||||
}
|
||||
|
||||
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); }
|
||||
|
@ -84,6 +84,7 @@ struct ClockDomainKey
|
||||
{
|
||||
IdString clock;
|
||||
ClockEdge edge;
|
||||
ClockDomainKey(IdString clock_net, ClockEdge edge) : clock(clock_net), edge(edge){};
|
||||
// probably also need something here to deal with constraints
|
||||
inline bool is_async() const { return clock == IdString(); }
|
||||
|
||||
@ -109,6 +110,7 @@ struct TimingAnalyser
|
||||
void init_ports();
|
||||
void get_cell_delays();
|
||||
void topo_sort();
|
||||
void setup_port_domains();
|
||||
// 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;
|
||||
@ -123,6 +125,7 @@ struct TimingAnalyser
|
||||
// Data per port-domain tuple
|
||||
struct PortDomainData
|
||||
{
|
||||
bool has_arrival = false, has_required = false;
|
||||
ArrivReqTime arrival, required;
|
||||
delay_t setup_slack = std::numeric_limits<delay_t>::max(), hold_slack = std::numeric_limits<delay_t>::max();
|
||||
delay_t budget = std::numeric_limits<delay_t>::max();
|
||||
@ -167,12 +170,25 @@ struct TimingAnalyser
|
||||
DelayPair route_delay;
|
||||
};
|
||||
|
||||
struct PerDomain
|
||||
{
|
||||
PerDomain(ClockDomainKey key) : key(key){};
|
||||
ClockDomainKey key;
|
||||
// these are pairs (signal port; clock port)
|
||||
std::vector<std::pair<CellPortKey, IdString>> startpoints, endpoints;
|
||||
};
|
||||
|
||||
CellInfo *cell_info(const CellPortKey &key);
|
||||
PortInfo &port_info(const CellPortKey &key);
|
||||
|
||||
domain_id_t domain_id(IdString cell, IdString clock_port, ClockEdge edge);
|
||||
domain_id_t domain_id(const NetInfo *net, ClockEdge edge);
|
||||
|
||||
void copy_domains(const CellPortKey &from, const CellPortKey &to, bool backwards);
|
||||
|
||||
std::unordered_map<CellPortKey, PerPort, CellPortKey::Hash> ports;
|
||||
std::unordered_map<ClockDomainKey, domain_id_t, ClockDomainKey::Hash> domain_to_id;
|
||||
std::vector<ClockDomainKey> id_to_domain;
|
||||
std::vector<PerDomain> domains;
|
||||
|
||||
std::vector<CellPortKey> topological_order;
|
||||
|
||||
|
@ -263,6 +263,16 @@ template <typename T, typename C = std::less<T>> struct TopoSort
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct reversed_range_t
|
||||
{
|
||||
T &obj;
|
||||
explicit reversed_range_t(T &obj) : obj(obj){};
|
||||
auto begin() { return obj.rbegin(); }
|
||||
auto end() { return obj.rend(); }
|
||||
};
|
||||
|
||||
template <typename T> reversed_range_t<T> reversed_range(T &obj) { return reversed_range_t<T>(obj); }
|
||||
|
||||
NEXTPNR_NAMESPACE_END
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user