Analyse async paths in TimingAnalyser (#1171)

This commit is contained in:
Rowan Goemans 2023-06-09 08:01:47 +02:00 committed by GitHub
parent 119b47acf3
commit 5b958c4d80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 33 deletions

View File

@ -30,9 +30,24 @@
NEXTPNR_NAMESPACE_BEGIN
namespace {
const char *edge_name(ClockEdge edge) { return (edge == FALLING_EDGE) ? "negedge" : "posedge"; }
std::string clock_event_name(const Context *ctx, ClockDomainKey &dom) {
std::string value;
if (dom.is_async())
value = "<async>";
else
value = (dom.edge == FALLING_EDGE ? "negedge " : "posedge ") + dom.clock.str(ctx);
return value;
}
} // namespace
TimingAnalyser::TimingAnalyser(Context *ctx) : ctx(ctx) {
ClockDomainKey key{IdString(), ClockEdge::RISING_EDGE};
domain_to_id.emplace(key, 0);
domains.emplace_back(key);
async_clock_id = 0;
};
void TimingAnalyser::setup()
{
init_ports();
@ -69,6 +84,8 @@ void TimingAnalyser::init_ports()
void TimingAnalyser::get_cell_delays()
{
auto async_clk_key = domains.at(async_clock_id);
for (auto &port : ports) {
CellInfo *ci = cell_info(port.first);
auto &pi = port_info(port.first);
@ -81,8 +98,7 @@ void TimingAnalyser::get_cell_delays()
pd.cell_arcs.clear();
int clkInfoCount = 0;
TimingPortClass cls = ctx->getPortTimingClass(ci, name, clkInfoCount);
if (cls == TMG_STARTPOINT || cls == TMG_ENDPOINT || cls == TMG_CLOCK_INPUT || cls == TMG_GEN_CLOCK ||
cls == TMG_IGNORE)
if (cls == TMG_CLOCK_INPUT || cls == TMG_GEN_CLOCK || cls == TMG_IGNORE)
continue;
if (pi.type == PORT_IN) {
// Input ports might have setup/hold relationships
@ -98,15 +114,21 @@ void TimingAnalyser::get_cell_delays()
}
}
// Combinational delays through cell
for (auto &other_port : ci->ports) {
auto &op = other_port.second;
// ignore dangling ports and non-outputs
if (op.net == nullptr || op.type != PORT_OUT)
continue;
DelayQuad delay;
bool is_path = ctx->getCellDelay(ci, name, other_port.first, delay);
if (is_path)
pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay);
else if (cls == TMG_COMB_INPUT) {
for (auto &other_port : ci->ports) {
auto &op = other_port.second;
// ignore dangling ports and non-outputs
if (op.net == nullptr || op.type != PORT_OUT)
continue;
DelayQuad delay;
bool is_path = ctx->getCellDelay(ci, name, other_port.first, delay);
if (is_path)
pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay);
}
}
// asynchronous endpoint
else if (cls == TMG_ENDPOINT) {
pd.cell_arcs.emplace_back(CellArc::ENDPOINT, async_clk_key.key.clock, DelayQuad {});
}
} else if (pi.type == PORT_OUT) {
// Output ports might have clk-to-q relationships
@ -119,15 +141,21 @@ void TimingAnalyser::get_cell_delays()
}
}
// Combinational delays through cell
for (auto &other_port : ci->ports) {
auto &op = other_port.second;
// ignore dangling ports and non-inputs
if (op.net == nullptr || op.type != PORT_IN)
continue;
DelayQuad delay;
bool is_path = ctx->getCellDelay(ci, other_port.first, name, delay);
if (is_path)
pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay);
else if (cls == TMG_COMB_OUTPUT) {
for (auto &other_port : ci->ports) {
auto &op = other_port.second;
// ignore dangling ports and non-inputs
if (op.net == nullptr || op.type != PORT_IN)
continue;
DelayQuad delay;
bool is_path = ctx->getCellDelay(ci, other_port.first, name, delay);
if (is_path)
pd.cell_arcs.emplace_back(CellArc::COMBINATIONAL, other_port.first, delay);
}
}
// Asynchronous startpoint
else if (cls == TMG_STARTPOINT) {
pd.cell_arcs.emplace_back(CellArc::STARTPOINT, async_clk_key.key.clock, DelayQuad {});
}
}
}
@ -194,9 +222,9 @@ void TimingAnalyser::setup_port_domains()
d.startpoints.clear();
d.endpoints.clear();
}
// Go forward through the topological order (domains from the PoV of arrival time)
bool first_iter = true;
do {
// Go forward through the topological order (domains from the PoV of arrival time)
updated_domains = false;
for (auto port : topological_order) {
auto &pd = ports.at(port);
@ -204,10 +232,14 @@ void TimingAnalyser::setup_port_domains()
if (pi.type == PORT_OUT) {
if (first_iter) {
for (auto &fanin : pd.cell_arcs) {
if (fanin.type != CellArc::CLK_TO_Q)
continue;
domain_id_t dom;
// registered outputs are startpoints
auto dom = domain_id(port.cell, fanin.other_port, fanin.edge);
if (fanin.type == CellArc::CLK_TO_Q)
dom = domain_id(port.cell, fanin.other_port, fanin.edge);
else if (fanin.type == CellArc::STARTPOINT)
dom = async_clock_id;
else
continue;
// create per-domain data
pd.arrival[dom];
domains.at(dom).startpoints.emplace_back(port, fanin.other_port);
@ -240,10 +272,14 @@ void TimingAnalyser::setup_port_domains()
} else {
if (first_iter) {
for (auto &fanout : pd.cell_arcs) {
if (fanout.type != CellArc::SETUP)
continue;
domain_id_t dom;
// registered inputs are endpoints
auto dom = domain_id(port.cell, fanout.other_port, fanout.edge);
if (fanout.type == CellArc::SETUP)
dom = domain_id(port.cell, fanout.other_port, fanout.edge);
else if (fanout.type == CellArc::ENDPOINT)
dom = async_clock_id;
else
continue;
// create per-domain data
pd.required[dom];
domains.at(dom).endpoints.emplace_back(port, fanout.other_port);
@ -254,7 +290,7 @@ void TimingAnalyser::setup_port_domains()
copy_domains(port, CellPortKey(pi.net->driver), true);
}
}
// Iterate over ports and find domain paris
// Iterate over ports and find domain pairs
for (auto port : topological_order) {
auto &pd = ports.at(port);
for (auto &arr : pd.arrival)
@ -370,6 +406,8 @@ void TimingAnalyser::identify_related_domains()
// Identify possible drivers for each clock domain
dict<IdString, dict<IdString, delay_t>> clock_drivers;
for (const auto &domain : domains) {
if (domain.key.is_async())
continue;
const NetInfo *ni = ctx->nets.at(domain.key.clock).get();
if (ni == nullptr)
@ -613,6 +651,8 @@ void TimingAnalyser::print_fmax()
auto &pd = ports.at(p);
for (auto &req : pd.required) {
if (pd.arrival.count(req.first)) {
if (domains.at(req.first).key.is_async())
continue;
auto &arr = pd.arrival.at(req.first);
double fmax = 1000.0 / ctx->getDelayNS(arr.value.maxDelay() - req.second.value.minDelay());
if (!domain_fmax.count(req.first) || domain_fmax.at(req.first) > fmax)
@ -670,6 +710,10 @@ void TimingAnalyser::compute_criticality()
auto &pd = ports.at(p);
for (auto &pdp : pd.domain_pairs) {
auto &dp = domain_pairs.at(pdp.first);
// Do not set criticality for asynchronous paths
if (domains.at(dp.key.launch).key.is_async() || domains.at(dp.key.capture).key.is_async())
continue;
float crit =
1.0f - (float(pdp.second.setup_slack) - float(dp.worst_setup_slack)) / float(-dp.worst_setup_slack);
crit = std::min(crit, 1.0f);
@ -728,8 +772,9 @@ void TimingAnalyser::print_report()
auto &dp = domain_pairs.at(i);
auto &launch = domains.at(dp.key.launch);
auto &capture = domains.at(dp.key.capture);
log("Worst endpoints for %s %s -> %s %s\n", edge_name(launch.key.edge), ctx->nameOf(launch.key.clock),
edge_name(capture.key.edge), ctx->nameOf(capture.key.clock));
log("Worst endpoints for %s -> %s\n", clock_event_name(ctx, launch.key).c_str(),
clock_event_name(ctx, capture.key).c_str());
auto failing_eps = get_failing_eps(i, 5);
for (auto &ep : failing_eps)
print_critical_path(ep, i);

View File

@ -73,7 +73,7 @@ struct ClockDomainPairKey
struct TimingAnalyser
{
public:
TimingAnalyser(Context *ctx) : ctx(ctx){};
TimingAnalyser(Context *ctx);
void setup();
void run(bool update_route_delays = true);
void print_report();
@ -157,7 +157,9 @@ struct TimingAnalyser
COMBINATIONAL,
SETUP,
HOLD,
CLK_TO_Q
CLK_TO_Q,
STARTPOINT,
ENDPOINT,
} type;
IdString other_port;
@ -224,6 +226,8 @@ struct TimingAnalyser
std::vector<CellPortKey> topological_order;
domain_id_t async_clock_id;
Context *ctx;
};