Skip to content

Commit a5b10a7

Browse files
authored
Merge pull request #9807 from Pinata-Consulting/timing-api-py-test
Add Timing Python API with idiomatic Bazel py_test
2 parents e5b9f1d + cc6305b commit a5b10a7

5 files changed

Lines changed: 476 additions & 0 deletions

File tree

include/ord/Timing.h

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#pragma once
55

6+
#include <string>
7+
#include <utility>
68
#include <vector>
79

810
#include "sta/Clock.hh"
@@ -35,6 +37,58 @@ namespace ord {
3537

3638
class Design;
3739
class OpenRoad;
40+
41+
// ── Timing report structs (top-level for SWIG compatibility) ──
42+
struct EndpointSlack
43+
{
44+
odb::dbITerm* iterm = nullptr;
45+
odb::dbBTerm* bterm = nullptr;
46+
float slack = 0.0f;
47+
};
48+
49+
struct ClockInfo
50+
{
51+
std::string name;
52+
float period = 0.0f;
53+
std::vector<float> waveform;
54+
std::vector<odb::dbITerm*> source_iterms;
55+
std::vector<odb::dbBTerm*> source_bterms;
56+
};
57+
58+
struct TimingArcInfo
59+
{
60+
odb::dbITerm* from_iterm = nullptr;
61+
odb::dbBTerm* from_bterm = nullptr;
62+
odb::dbITerm* to_iterm = nullptr;
63+
odb::dbBTerm* to_bterm = nullptr;
64+
odb::dbMaster* master = nullptr; // nullptr for net arcs
65+
float delay = 0.0f;
66+
float slew = 0.0f;
67+
float load = 0.0f;
68+
int fanout = 0;
69+
bool is_rising = false;
70+
};
71+
72+
struct TimingPathInfo
73+
{
74+
float slack = 0.0f;
75+
float path_delay = 0.0f;
76+
float arrival = 0.0f;
77+
float required = 0.0f;
78+
float skew = 0.0f;
79+
float logic_delay = 0.0f;
80+
int logic_depth = 0;
81+
int fanout = 0;
82+
odb::dbITerm* start_iterm = nullptr;
83+
odb::dbBTerm* start_bterm = nullptr;
84+
odb::dbITerm* end_iterm = nullptr;
85+
odb::dbBTerm* end_bterm = nullptr;
86+
std::string start_clock;
87+
std::string end_clock;
88+
std::string path_group;
89+
std::vector<TimingArcInfo> arcs;
90+
};
91+
3892
class Timing
3993
{
4094
public:
@@ -82,6 +136,22 @@ class Timing
82136
void makeEquivCells();
83137
std::vector<odb::dbMaster*> equivCells(odb::dbMaster* master);
84138

139+
// ── Summary metrics ─────────────────────────────────────────
140+
float getWorstSlack(MinMax minmax = Max);
141+
float getTotalNegativeSlack(MinMax minmax = Max);
142+
int getEndpointCount();
143+
144+
// ── Endpoint slack map (histogram data source) ──────────────
145+
std::vector<EndpointSlack> getEndpointSlacks(MinMax minmax = Max);
146+
147+
// ── Clock domain info ───────────────────────────────────────
148+
std::vector<ClockInfo> getClockInfo();
149+
150+
// ── Timing paths with arc detail ────────────────────────────
151+
std::vector<TimingPathInfo> getTimingPaths(MinMax minmax = Max,
152+
int max_paths = 100,
153+
float slack_threshold = 1e30);
154+
85155
private:
86156
sta::dbSta* getSta();
87157
const sta::MinMax* getMinMax(MinMax type);

src/OpenRoad-py.i

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
%include <std_string.i>
55
%include <std_vector.i>
6+
%include <std_pair.i>
67
%include <stdint.i>
78

89
%{
@@ -52,9 +53,17 @@ get_db_block();
5253
%template(Corners) std::vector<sta::Scene*>;
5354
%template(MTerms) std::vector<odb::dbMTerm*>;
5455
%template(Masters) std::vector<odb::dbMaster*>;
56+
%template(Floats) std::vector<float>;
57+
%template(ITerms) std::vector<odb::dbITerm*>;
58+
%template(BTerms) std::vector<odb::dbBTerm*>;
59+
%template(EndpointSlacks) std::vector<ord::EndpointSlack>;
5560
%include "ord/Tech.h"
5661
%include "ord/Design.h"
62+
5763
%include "ord/Timing.h"
64+
%template(ClockInfos) std::vector<ord::ClockInfo>;
65+
%template(TimingArcInfos) std::vector<ord::TimingArcInfo>;
66+
%template(TimingPathInfos) std::vector<ord::TimingPathInfo>;
5867

5968
#ifdef BAZEL
6069
%include "src/gpl/src/replace-py.i"

src/Timing.cc

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <array>
88
#include <cstring>
99
#include <set>
10+
#include <unordered_set>
1011
#include <utility>
1112
#include <vector>
1213

@@ -20,14 +21,22 @@
2021
#include "sta/Clock.hh"
2122
#include "sta/DelayFloat.hh"
2223
#include "sta/Graph.hh"
24+
#include "sta/GraphDelayCalc.hh"
2325
#include "sta/Liberty.hh"
2426
#include "sta/LibertyClass.hh"
2527
#include "sta/MinMax.hh"
2628
#include "sta/Mode.hh"
29+
#include "sta/Path.hh"
30+
#include "sta/PathEnd.hh"
31+
#include "sta/PathExpanded.hh"
32+
#include "sta/PathGroup.hh"
2733
#include "sta/PowerClass.hh"
2834
#include "sta/Scene.hh"
35+
#include "sta/Sdc.hh"
2936
#include "sta/SdcClass.hh"
3037
#include "sta/Search.hh"
38+
#include "sta/SearchClass.hh"
39+
#include "sta/StringUtil.hh"
3140
#include "sta/TimingArc.hh"
3241
#include "sta/TimingRole.hh"
3342
#include "utl/Logger.h"
@@ -404,4 +413,236 @@ std::vector<odb::dbMaster*> Timing::equivCells(odb::dbMaster* master)
404413
}
405414
return master_seq;
406415
}
416+
float Timing::getWorstSlack(MinMax minmax)
417+
{
418+
sta::dbSta* sta = getSta();
419+
cmdLinkedNetwork();
420+
return sta->worstSlack(getMinMax(minmax));
421+
}
422+
423+
float Timing::getTotalNegativeSlack(MinMax minmax)
424+
{
425+
sta::dbSta* sta = getSta();
426+
cmdLinkedNetwork();
427+
return sta->totalNegativeSlack(getMinMax(minmax));
428+
}
429+
430+
int Timing::getEndpointCount()
431+
{
432+
sta::dbSta* sta = getSta();
433+
cmdLinkedNetwork();
434+
return sta->endpoints().size();
435+
}
436+
437+
std::vector<EndpointSlack> Timing::getEndpointSlacks(MinMax minmax)
438+
{
439+
sta::dbSta* sta = getSta();
440+
cmdLinkedNetwork();
441+
442+
std::vector<EndpointSlack> result;
443+
for (sta::Vertex* vertex : sta->endpoints()) {
444+
const sta::Pin* pin = vertex->pin();
445+
float slack = sta->slack(
446+
pin, sta::RiseFallBoth::riseFall(), sta->scenes(), getMinMax(minmax));
447+
auto [iterm, bterm] = staToDBPin(pin);
448+
result.push_back({iterm, bterm, slack});
449+
}
450+
return result;
451+
}
452+
453+
std::vector<ClockInfo> Timing::getClockInfo()
454+
{
455+
sta::dbSta* sta = getSta();
456+
cmdLinkedNetwork();
457+
458+
std::vector<ClockInfo> result;
459+
for (const sta::Clock* clk : sta->cmdMode()->sdc()->clocks()) {
460+
ClockInfo info;
461+
info.name = clk->name();
462+
info.period = clk->period();
463+
if (clk->waveform()) {
464+
info.waveform = *clk->waveform();
465+
}
466+
for (const sta::Pin* pin : clk->pins()) {
467+
auto [iterm, bterm] = staToDBPin(pin);
468+
if (iterm) {
469+
info.source_iterms.push_back(iterm);
470+
}
471+
if (bterm) {
472+
info.source_bterms.push_back(bterm);
473+
}
474+
}
475+
result.push_back(std::move(info));
476+
}
477+
return result;
478+
}
479+
480+
std::vector<TimingPathInfo> Timing::getTimingPaths(MinMax minmax,
481+
int max_paths,
482+
float slack_threshold)
483+
{
484+
sta::dbSta* sta = getSta();
485+
cmdLinkedNetwork();
486+
sta::dbNetwork* network = sta->getDbNetwork();
487+
488+
const bool is_setup = (minmax == Max);
489+
sta::SceneSeq scenes = sta->scenes();
490+
sta::StringSeq group_names;
491+
492+
sta->ensureGraph();
493+
sta->searchPreamble();
494+
495+
sta::Search* search = sta->search();
496+
sta::PathEndSeq path_ends = search->findPathEnds(
497+
nullptr, // from
498+
nullptr, // thrus
499+
nullptr, // to
500+
false, // unconstrained
501+
scenes,
502+
is_setup ? sta::MinMaxAll::max() : sta::MinMaxAll::min(),
503+
max_paths, // group_count
504+
1, // endpoint_count (one per endpoint)
505+
true, // unique_pins
506+
true, // unique_edges
507+
-sta::INF, // slack_min
508+
slack_threshold, // slack_max
509+
true, // sort_by_slack
510+
group_names,
511+
is_setup, // setup
512+
!is_setup, // hold
513+
false, // recovery
514+
false, // removal
515+
false, // clk_gating_setup
516+
false); // clk_gating_hold
517+
518+
std::vector<TimingPathInfo> result;
519+
auto* graph = sta->graph();
520+
const sta::Sdc* sdc = sta->cmdScene()->sdc();
521+
sta::Mode* mode = sta->cmdScene()->mode();
522+
sta::GraphDelayCalc* gdc = sta->graphDelayCalc();
523+
sta::dbNetwork* db_network = sta->getDbNetwork();
524+
525+
for (auto& path_end : path_ends) {
526+
TimingPathInfo path_info;
527+
sta::Path* path = path_end->path();
528+
529+
path_info.slack = path_end->slack(sta);
530+
path_info.arrival = path_end->dataArrivalTime(sta);
531+
path_info.required = path_end->requiredTime(sta);
532+
path_info.skew = path_end->clkSkew(sta);
533+
534+
auto* path_delay = path_end->pathDelay();
535+
path_info.path_delay = path_delay ? path_delay->delay() : 0.0f;
536+
537+
auto* start_clk_edge = path_end->sourceClkEdge(sta);
538+
path_info.start_clock
539+
= start_clk_edge ? start_clk_edge->clock()->name() : "";
540+
541+
auto* end_clk = path_end->targetClk(sta);
542+
path_info.end_clock = end_clk ? end_clk->name() : "";
543+
544+
auto* path_group = path_end->pathGroup();
545+
path_info.path_group = path_group ? path_group->name() : "";
546+
547+
// Expand path to get arc detail
548+
sta::PathExpanded expand(path, sta);
549+
float arrival_prev = 0.0f;
550+
float logic_delay_total = 0.0f;
551+
int logic_depth_count = 0;
552+
int max_fanout = 0;
553+
std::unordered_set<sta::Instance*> logic_insts;
554+
555+
for (size_t i = 0; i < expand.size(); i++) {
556+
const auto* ref = expand.path(i);
557+
sta::Vertex* vertex = ref->vertex(sta);
558+
const sta::Pin* pin = vertex->pin();
559+
const bool is_rising = ref->transition(sta) == sta::RiseFall::rise();
560+
const float arr = sta::delayAsFloat(ref->arrival());
561+
const float slw = sta::delayAsFloat(ref->slew(sta));
562+
const float pin_delay = arr - arrival_prev;
563+
564+
// Compute fanout
565+
int node_fanout = 0;
566+
sta::VertexOutEdgeIterator iter(vertex, graph);
567+
while (iter.hasNext()) {
568+
sta::Edge* edge = iter.next();
569+
if (edge->isWire()) {
570+
const sta::Pin* to_pin = edge->to(graph)->pin();
571+
if (network->isTopLevelPort(to_pin)) {
572+
sta::Port* port = network->port(to_pin);
573+
node_fanout += sdc->portExtFanout(port, sta::MinMax::max()) + 1;
574+
} else {
575+
node_fanout++;
576+
}
577+
}
578+
}
579+
max_fanout = std::max(node_fanout, max_fanout);
580+
581+
// Compute load capacitance
582+
float cap = 0.0f;
583+
const bool is_driver = network->isDriver(pin);
584+
if (is_driver && i > 0) {
585+
cap = gdc->loadCap(
586+
pin, ref->transition(sta), ref->scene(sta), ref->minMax(sta));
587+
}
588+
589+
// Determine master, net arcs, logic depth, and build arc info
590+
if (i > 0) {
591+
const auto* prev_ref = expand.path(i - 1);
592+
sta::Vertex* prev_vertex = prev_ref->vertex(sta);
593+
const sta::Pin* prev_pin = prev_vertex->pin();
594+
sta::Instance* inst = network->instance(pin);
595+
sta::Instance* prev_inst = network->instance(prev_pin);
596+
597+
const bool same_inst = (inst == prev_inst && inst != nullptr);
598+
599+
// Track logic depth (non-clock, non-net arcs)
600+
bool pin_is_clock = sta->isClock(pin, mode);
601+
if (same_inst && !pin_is_clock) {
602+
if (logic_insts.find(inst) == logic_insts.end()) {
603+
logic_insts.insert(inst);
604+
logic_depth_count++;
605+
logic_delay_total += pin_delay;
606+
}
607+
}
608+
609+
TimingArcInfo arc;
610+
odb::dbModITerm* mod_iterm;
611+
db_network->staToDb(
612+
prev_pin, arc.from_iterm, arc.from_bterm, mod_iterm);
613+
db_network->staToDb(pin, arc.to_iterm, arc.to_bterm, mod_iterm);
614+
if (same_inst && arc.to_iterm) {
615+
arc.master = arc.to_iterm->getInst()->getMaster();
616+
}
617+
arc.delay = pin_delay;
618+
arc.slew = slw;
619+
arc.load = cap;
620+
arc.fanout = node_fanout;
621+
arc.is_rising = is_rising;
622+
path_info.arcs.push_back(arc);
623+
}
624+
625+
arrival_prev = arr;
626+
}
627+
628+
// Get startpoint/endpoint objects
629+
odb::dbModITerm* mod_iterm;
630+
db_network->staToDb(expand.path(0)->vertex(sta)->pin(),
631+
path_info.start_iterm,
632+
path_info.start_bterm,
633+
mod_iterm);
634+
db_network->staToDb(path_end->vertex(sta)->pin(),
635+
path_info.end_iterm,
636+
path_info.end_bterm,
637+
mod_iterm);
638+
639+
path_info.logic_delay = logic_delay_total;
640+
path_info.logic_depth = logic_depth_count;
641+
path_info.fanout = max_fanout;
642+
643+
result.push_back(std::move(path_info));
644+
}
645+
return result;
646+
}
647+
407648
} // namespace ord

0 commit comments

Comments
 (0)