1 /////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright (c) 2020, The Regents of the University of California
4 // All rights reserved.
5 //
6 // BSD 3-Clause License
7 //
8 // Redistribution and use in source and binary forms, with or without
9 // modification, are permitted provided that the following conditions are met:
10 //
11 // * Redistributions of source code must retain the above copyright notice, this
12 // list of conditions and the following disclaimer.
13 //
14 // * Redistributions in binary form must reproduce the above copyright notice,
15 // this list of conditions and the following disclaimer in the documentation
16 // and/or other materials provided with the distribution.
17 //
18 // * Neither the name of the copyright holder nor the names of its
19 // contributors may be used to endorse or promote products derived from
20 // this software without specific prior written permission.
21 //
22 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
26 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 // POSSIBILITY OF SUCH DAMAGE.
33 //
34 ///////////////////////////////////////////////////////////////////////////////
35
36 #include <QApplication>
37 #include <QDebug>
38 #include <fstream>
39 #include <iostream>
40 #include <string>
41
42 #include "db.h"
43 #include "dbShape.h"
44 #include "db_sta/dbNetwork.hh"
45 #include "db_sta/dbSta.hh"
46 #include "ord/OpenRoad.hh"
47 #include "sta/ArcDelayCalc.hh"
48 #include "sta/Corner.hh"
49 #include "sta/DcalcAnalysisPt.hh"
50 #include "sta/ExceptionPath.hh"
51 #include "sta/Graph.hh"
52 #include "sta/GraphDelayCalc.hh"
53 #include "sta/Liberty.hh"
54 #include "sta/Network.hh"
55 #include "sta/PathAnalysisPt.hh"
56 #include "sta/PathEnd.hh"
57 #include "sta/PathExpanded.hh"
58 #include "sta/PathRef.hh"
59 #include "sta/PatternMatch.hh"
60 #include "sta/PortDirection.hh"
61 #include "sta/Sdc.hh"
62 #include "sta/Search.hh"
63 #include "sta/Sta.hh"
64 #include "sta/Units.hh"
65 #include "staGui.h"
66
67 namespace gui {
68
69 const char* TimingPathDetailModel::up_down_arrows = u8"\u21C5";
70 const char* TimingPathDetailModel::up_arrow = u8"\u2191";
71 const char* TimingPathDetailModel::down_arrow = u8"\u2193";
72
73 // These two definitions need to stay in sync
74 const std::vector<std::string> TimingPathsModel::_path_columns
75 = {"Capture Clock", "Required", "Arrival", "Slack", "Start", "End"};
76 enum TimingPathsModel::Column : int
77 {
78 Clock,
79 Required,
80 Arrival,
81 Slack,
82 Start,
83 End
84 };
85
86 // These two definitions need to stay in sync
87 const std::vector<std::string> TimingPathDetailModel::_path_details_columns
88 = {"Pin", up_down_arrows, "Time", "Delay", "Slew", "Load"};
89 enum TimingPathDetailModel::Column : int
90 {
91 Pin,
92 RiseFall,
93 Time,
94 Delay,
95 Slew,
96 Load
97 };
98
99 gui::Painter::Color TimingPathRenderer::inst_highlight_color_
100 = gui::Painter::dark_cyan;
101 gui::Painter::Color TimingPathRenderer::path_inst_color_
102 = gui::Painter::magenta;
103 gui::Painter::Color TimingPathRenderer::term_color_ = gui::Painter::blue;
104 gui::Painter::Color TimingPathRenderer::signal_color_ = gui::Painter::red;
105 gui::Painter::Color TimingPathRenderer::clock_color_ = gui::Painter::yellow;
106 // helper functions
getRequiredTime(sta::dbSta * staRoot,sta::Pin * term,bool is_rise,sta::PathAnalysisPt * path_ap)107 float getRequiredTime(sta::dbSta* staRoot,
108 sta::Pin* term,
109 bool is_rise,
110 sta::PathAnalysisPt* path_ap)
111 {
112 auto vert = staRoot->getDbNetwork()->graph()->pinLoadVertex(term);
113 auto req = staRoot->vertexRequired(
114 vert, is_rise ? sta::RiseFall::rise() : sta::RiseFall::fall(), path_ap);
115 if (sta::delayInf(req)) {
116 return 0;
117 }
118 return req;
119 }
120
TimingPathsModel(bool get_max,int path_count)121 TimingPathsModel::TimingPathsModel(bool get_max, int path_count)
122 : openroad_(ord::OpenRoad::openRoad())
123 {
124 populateModel(get_max, path_count);
125 }
126
~TimingPathsModel()127 TimingPathsModel::~TimingPathsModel()
128 {
129 // TBD
130 }
131
rowCount(const QModelIndex & parent) const132 int TimingPathsModel::rowCount(const QModelIndex& parent) const
133 {
134 return timing_paths_.size();
135 }
136
columnCount(const QModelIndex & parent) const137 int TimingPathsModel::columnCount(const QModelIndex& parent) const
138 {
139 return TimingPathsModel::_path_columns.size();
140 }
141
data(const QModelIndex & index,int role) const142 QVariant TimingPathsModel::data(const QModelIndex& index, int role) const
143 {
144 const Column col_index = static_cast<Column>(index.column());
145 if (index.isValid() && role == Qt::TextAlignmentRole) {
146 switch (col_index) {
147 case Clock:
148 case Start:
149 case End:
150 return Qt::AlignLeft;
151 case Required:
152 case Arrival:
153 case Slack:
154 return Qt::AlignRight;
155 }
156 }
157
158 if (!index.isValid() || role != Qt::DisplayRole) {
159 return QVariant();
160 }
161
162 sta::dbSta* sta = openroad_->getSta();
163 auto time_units = sta->search()->units()->timeUnit();
164
165 unsigned int row_index = index.row();
166 if (row_index > timing_paths_.size())
167 return QVariant();
168 auto timing_path = timing_paths_[row_index];
169 switch (col_index) {
170 case Clock:
171 return QString::fromStdString(timing_path->getStartClock());
172 case Required:
173 return QString(time_units->asString(timing_path->getPathRequiredTime()));
174 case Arrival:
175 return QString(time_units->asString(timing_path->getPathArrivalTime()));
176 case Slack:
177 return QString(time_units->asString(timing_path->getSlack()));
178 case Start:
179 return QString(timing_path->getStartStageName().c_str());
180 case End:
181 return QString::fromStdString(timing_path->getEndStageName());
182 }
183 return QVariant();
184 }
185
headerData(int section,Qt::Orientation orientation,int role) const186 QVariant TimingPathsModel::headerData(int section,
187 Qt::Orientation orientation,
188 int role) const
189 {
190 if (role == Qt::DisplayRole && orientation == Qt::Horizontal)
191 return QString::fromStdString(TimingPathsModel::_path_columns[section]);
192 return QVariant();
193 }
194
resetModel()195 void TimingPathsModel::resetModel()
196 {
197 beginResetModel();
198 for (auto timing_path : timing_paths_)
199 delete timing_path;
200 timing_paths_.clear();
201 endResetModel();
202 }
203
sort(int col_index,Qt::SortOrder sort_order)204 void TimingPathsModel::sort(int col_index, Qt::SortOrder sort_order)
205 {
206 beginResetModel();
207 (void) col_index;
208 if (sort_order == Qt::AscendingOrder)
209 std::stable_sort(timing_paths_.begin(),
210 timing_paths_.end(),
211 [](const TimingPath* path1, TimingPath* path2) {
212 return path1->getStartClock() < path2->getStartClock();
213 });
214 else
215 std::stable_sort(timing_paths_.begin(),
216 timing_paths_.end(),
217 [](const TimingPath* path1, TimingPath* path2) {
218 return path1->getStartClock() > path2->getStartClock();
219 });
220 endResetModel();
221 }
222
populateModel(bool setup_hold,int path_count)223 void TimingPathsModel::populateModel(bool setup_hold, int path_count)
224 {
225 beginResetModel();
226 for (auto timing_path : timing_paths_)
227 delete timing_path;
228 timing_paths_.clear();
229 populatePaths(setup_hold, path_count);
230 endResetModel();
231 }
232
populatePaths(bool get_max,int path_count,bool clockExpanded)233 bool TimingPathsModel::populatePaths(bool get_max,
234 int path_count,
235 bool clockExpanded)
236 {
237 // On lines of DataBaseHandler
238 QApplication::setOverrideCursor(Qt::WaitCursor);
239 sta::dbSta* sta_ = openroad_->getSta();
240 sta_->ensureGraph();
241 sta_->searchPreamble();
242
243 auto sta_state = sta_->search();
244
245 sta::PathEndSeq* path_ends
246 = sta_state->findPathEnds( // from, thrus, to, unconstrained
247 nullptr,
248 nullptr,
249 nullptr,
250 false,
251 // corner, min_max,
252 sta_->findCorner("default"),
253 get_max ? sta::MinMaxAll::max() : sta::MinMaxAll::min(),
254 // group_count, endpoint_count, unique_pins
255 path_count,
256 path_count,
257 true,
258 -sta::INF,
259 sta::INF, // slack_min, slack_max,
260 true, // sort_by_slack
261 nullptr, // group_names
262 // setup, hold, recovery, removal,
263 get_max,
264 !get_max,
265 false,
266 false,
267 // clk_gating_setup, clk_gating_hold
268 false,
269 false);
270
271 bool first_path = true;
272 int path_index = 0;
273 for (auto& path_end : *path_ends) {
274 sta::PathExpanded* expanded = new sta::PathExpanded(path_end->path(), sta_);
275
276 TimingPath* path = new TimingPath(path_index++);
277 sta::DcalcAnalysisPt* dcalc_ap
278 = path_end->path()->pathAnalysisPt(sta_)->dcalcAnalysisPt();
279
280 path->setStartClock(path_end->sourceClkEdge(sta_)->clock()->name());
281 path->setEndClock(path_end->targetClk(sta_)->name());
282 path->setPathDelay(path_end->pathDelay() ? path_end->pathDelay()->delay()
283 : 0);
284 path->setSlack(path_end->slack(sta_));
285 path->setPathArrivalTime(path_end->dataArrivalTime(sta_));
286 path->setPathRequiredTime(path_end->requiredTime(sta_));
287 bool clockPropagated
288 = path_end->sourceClkEdge(sta_)->clock()->isPropagated();
289 if (!clockPropagated)
290 clockExpanded = false;
291 else
292 clockExpanded = true;
293 float arrival_prev_stage = 0;
294 float arrival_cur_stage = 0;
295 for (size_t i = 0; i < expanded->size(); i++) {
296 auto ref = expanded->path(i);
297 auto pin = ref->vertex(sta_)->pin();
298 auto slew = ref->slew(sta_);
299 float cap = 0.0;
300 if (sta_->network()->isDriver(pin)
301 && !(!clockExpanded && (sta_->network()->isCheckClk(pin) || !i))) {
302 sta::Parasitic* parasitic = nullptr;
303 sta::ArcDelayCalc* arc_delay_calc = sta_->arcDelayCalc();
304 if (arc_delay_calc)
305 parasitic = arc_delay_calc->findParasitic(
306 pin, ref->transition(sta_), dcalc_ap);
307 sta::GraphDelayCalc* graph_delay_calc = sta_->graphDelayCalc();
308 cap = graph_delay_calc->loadCap(
309 pin, parasitic, ref->transition(sta_), dcalc_ap);
310 }
311
312 auto is_rising = ref->transition(sta_) == sta::RiseFall::rise();
313 auto arrival = ref->arrival(sta_);
314 auto path_ap = ref->pathAnalysisPt(sta_);
315 auto path_required = !first_path ? 0 : ref->required(sta_);
316 if (!path_required || sta::delayInf(path_required)) {
317 path_required = getRequiredTime(sta_, pin, is_rising, path_ap);
318 }
319 auto slack = !first_path ? path_required - arrival : ref->slack(sta_);
320 odb::dbITerm* term;
321 odb::dbBTerm* port;
322 sta_->getDbNetwork()->staToDb(pin, term, port);
323 odb::dbObject* pin_object = term;
324 if (term == nullptr)
325 pin_object = port;
326 arrival_cur_stage = arrival;
327 if (i == 0)
328 path->appendNode(TimingPathNode(pin_object,
329 is_rising,
330 arrival,
331 path_required,
332 0,
333 slack,
334 slew,
335 cap));
336 else {
337 path->appendNode(TimingPathNode(pin_object,
338 is_rising,
339 arrival,
340 path_required,
341 arrival_cur_stage - arrival_prev_stage,
342 slack,
343 slew,
344 cap));
345 arrival_prev_stage = arrival_cur_stage;
346 }
347 first_path = false;
348 }
349 timing_paths_.push_back(path);
350 }
351 QApplication::restoreOverrideCursor();
352 delete path_ends;
353 return true;
354 }
355
getStartStageName() const356 std::string TimingPath::getStartStageName() const
357 {
358 auto node = getNodeAt(1);
359 return node.getNodeName();
360 }
361
getEndStageName() const362 std::string TimingPath::getEndStageName() const
363 {
364 auto node = path_nodes_.back();
365 return node.getNodeName();
366 }
367
getNodeName(bool include_master) const368 std::string TimingPathNode::getNodeName(bool include_master) const
369 {
370 if (pin_->getObjectType() == odb::dbObjectType::dbITermObj) {
371 odb::dbITerm* db_iterm = static_cast<odb::dbITerm*>(pin_);
372 return db_iterm->getInst()->getName() + "/"
373 + db_iterm->getMTerm()->getName()
374 + (include_master
375 ? " (" + db_iterm->getInst()->getMaster()->getName() + ")"
376 : "");
377 }
378 odb::dbBTerm* db_bterm = static_cast<odb::dbBTerm*>(pin_);
379 return db_bterm->getName();
380 }
381
getNetName() const382 std::string TimingPathNode::getNetName() const
383 {
384 if (pin_->getObjectType() == odb::dbObjectType::dbITermObj) {
385 odb::dbITerm* db_iterm = static_cast<odb::dbITerm*>(pin_);
386 return db_iterm->getNet()->getName();
387 }
388 odb::dbBTerm* db_bterm = static_cast<odb::dbBTerm*>(pin_);
389 return db_bterm->getNet()->getName();
390 }
391
rowCount(const QModelIndex & parent) const392 int TimingPathDetailModel::rowCount(const QModelIndex& parent) const
393 {
394 if (!path_)
395 return 0;
396 return path_->levelsCount();
397 }
398
columnCount(const QModelIndex & parent) const399 int TimingPathDetailModel::columnCount(const QModelIndex& parent) const
400 {
401 return TimingPathDetailModel::_path_details_columns.size();
402 }
403
data(const QModelIndex & index,int role) const404 QVariant TimingPathDetailModel::data(const QModelIndex& index, int role) const
405 {
406 const Column col_index = static_cast<Column>(index.column());
407 if (index.isValid() && role == Qt::TextAlignmentRole) {
408 switch (col_index) {
409 case Pin:
410 return Qt::AlignLeft;
411 case Time:
412 case Delay:
413 case Slew:
414 case Load:
415 return Qt::AlignRight;
416 case RiseFall:
417 return Qt::AlignCenter;
418 }
419 }
420
421 if (!index.isValid() || role != Qt::DisplayRole || !path_) {
422 return QVariant();
423 }
424
425 sta::dbSta* sta = ord::OpenRoad::openRoad()->getSta();
426 const auto time_units = sta->search()->units()->timeUnit();
427
428 const int row_index = index.row();
429 if (row_index > path_->levelsCount())
430 return QVariant();
431 const auto node = path_->getNodeAt(row_index);
432 switch (col_index) {
433 case Pin:
434 return QString(node.getNodeName(/* include_master */ true).c_str());
435 case RiseFall:
436 return node.is_rising_ ? QString(up_arrow) : QString(down_arrow);
437 case Time:
438 return time_units->asString(node.arrival_);
439 case Delay:
440 return time_units->asString(node.delay_);
441 case Slew:
442 return time_units->asString(node.slew_);
443 case Load: {
444 if (node.load_ == 0)
445 return "";
446 const auto cap_units = sta->search()->units()->capacitanceUnit();
447 return cap_units->asString(node.load_);
448 }
449 }
450 return QVariant();
451 }
452
headerData(int section,Qt::Orientation orientation,int role) const453 QVariant TimingPathDetailModel::headerData(int section,
454 Qt::Orientation orientation,
455 int role) const
456 {
457 if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
458 return QString::fromStdString(
459 TimingPathDetailModel::_path_details_columns.at(section));
460 }
461 return QVariant();
462 }
463
populateModel(TimingPath * path)464 void TimingPathDetailModel::populateModel(TimingPath* path)
465 {
466 beginResetModel();
467 path_ = path;
468 endResetModel();
469 }
470
TimingPathRenderer()471 TimingPathRenderer::TimingPathRenderer() : path_(nullptr)
472 {
473 TimingPathRenderer::path_inst_color_.a = 100;
474 }
475
~TimingPathRenderer()476 TimingPathRenderer::~TimingPathRenderer()
477 {
478 }
479
highlight(TimingPath * path)480 void TimingPathRenderer::highlight(TimingPath* path)
481 {
482 path_ = path;
483 highlight_node_ = -1;
484 }
485
highlightNode(int node_idx)486 void TimingPathRenderer::highlightNode(int node_idx)
487 {
488 if (path_ && node_idx >= 0 && node_idx < path_->levelsCount())
489 highlight_node_ = node_idx;
490 }
491
drawObjects(gui::Painter & painter)492 void TimingPathRenderer::drawObjects(gui::Painter& painter)
493 {
494 if (!path_)
495 return;
496 odb::dbObject* sink_node = nullptr;
497 odb::dbNet* net = nullptr;
498 int node_count = path_->levelsCount();
499 for (int i = node_count - 1; i >= 0; --i) {
500 auto node = path_->getNodeAt(i);
501 if (node.pin_->getObjectType() == odb::dbObjectType::dbITermObj) {
502 odb::dbITerm* db_iterm = static_cast<odb::dbITerm*>(node.pin_);
503 odb::dbInst* db_inst = db_iterm->getInst();
504 highlightInst(db_inst, painter, TimingPathRenderer::path_inst_color_);
505 auto io_dir = db_iterm->getIoType();
506 if (!sink_node
507 && (io_dir == odb::dbIoType::INPUT
508 || io_dir == odb::dbIoType::INOUT)) {
509 sink_node = db_iterm;
510 net = db_iterm->getNet();
511 continue;
512 } else if (sink_node) {
513 highlightNet(net, db_iterm /*source*/, sink_node, painter);
514 sink_node = nullptr;
515 net = nullptr;
516 }
517 } else {
518 odb::dbBTerm* bterm = static_cast<odb::dbBTerm*>(node.pin_);
519 highlightTerm(bterm, painter);
520 if (!sink_node
521 && (bterm->getIoType() == odb::dbIoType::OUTPUT
522 || bterm->getIoType() == odb::dbIoType::INOUT)) {
523 sink_node = bterm;
524 net = bterm->getNet();
525 continue;
526 } else if (sink_node) {
527 highlightNet(net, bterm, sink_node, painter);
528 sink_node = nullptr;
529 net = nullptr;
530 }
531 }
532 }
533 if (highlight_node_ >= 0 && highlight_node_ < node_count)
534 highlightStage(painter);
535 }
536
highlightStage(gui::Painter & painter)537 void TimingPathRenderer::highlightStage(gui::Painter& painter)
538 {
539 if (!path_)
540 return;
541 odb::dbObject* sink_node = nullptr;
542 int src_x, src_y;
543 int dst_x, dst_y;
544 auto getSegmentEnds = [&](int node_idx, int& x, int& y) {
545 auto node = path_->getNodeAt(node_idx);
546 if (node.pin_->getObjectType() == odb::dbObjectType::dbITermObj) {
547 odb::dbITerm* db_iterm = static_cast<odb::dbITerm*>(node.pin_);
548 odb::dbInst* db_inst = db_iterm->getInst();
549 highlightInst(
550 db_inst, painter, TimingPathRenderer::inst_highlight_color_);
551 db_iterm->getAvgXY(&x, &y);
552 auto io_dir = db_iterm->getIoType();
553 if (io_dir == odb::dbIoType::INPUT || io_dir == odb::dbIoType::INOUT)
554 sink_node = db_iterm;
555 } else {
556 odb::dbBTerm* bterm = static_cast<odb::dbBTerm*>(node.pin_);
557 bterm->getFirstPinLocation(x, y);
558 auto io_dir = bterm->getIoType();
559 if (io_dir == odb::dbIoType::OUTPUT || io_dir == odb::dbIoType::INOUT)
560 sink_node = bterm;
561 }
562 };
563 getSegmentEnds(highlight_node_, src_x, src_y);
564 int nxt_stage = -1;
565 if ((sink_node == nullptr && highlight_node_ < (path_->levelsCount() - 1))
566 || (sink_node != nullptr && highlight_node_ == 0))
567 nxt_stage = highlight_node_ + 1;
568 else if (highlight_node_ != 0)
569 nxt_stage = highlight_node_ - 1;
570 if (nxt_stage != -1)
571 getSegmentEnds(nxt_stage, dst_x, dst_y);
572 odb::Point pt1(src_x, src_y);
573 odb::Point pt2(dst_x, dst_y);
574
575 painter.setPen(TimingPathRenderer::inst_highlight_color_, true);
576 painter.drawLine(pt1, pt2);
577 }
578 // Color in the instances to make them more visible.
highlightInst(odb::dbInst * db_inst,gui::Painter & painter,const gui::Painter::Color & inst_color)579 void TimingPathRenderer::highlightInst(odb::dbInst* db_inst,
580 gui::Painter& painter,
581 const gui::Painter::Color& inst_color)
582 {
583 if (!path_)
584 return;
585 odb::dbBox* bbox = db_inst->getBBox();
586 odb::Rect rect;
587 bbox->getBox(rect);
588 painter.setBrush(inst_color);
589 painter.drawRect(rect);
590 }
591
highlightTerm(odb::dbBTerm * term,gui::Painter & painter)592 void TimingPathRenderer::highlightTerm(odb::dbBTerm* term,
593 gui::Painter& painter)
594 {
595 if (!path_)
596 return;
597 odb::dbShape port_shape;
598 if (term->getFirstPin(port_shape)) {
599 odb::Rect rect;
600 port_shape.getBox(rect);
601 painter.setBrush(TimingPathRenderer::term_color_);
602 painter.drawRect(rect);
603 }
604 }
605
highlightNet(odb::dbNet * net,odb::dbObject * source_node,odb::dbObject * sink_node,gui::Painter & painter)606 void TimingPathRenderer::highlightNet(odb::dbNet* net,
607 odb::dbObject* source_node,
608 odb::dbObject* sink_node,
609 gui::Painter& painter)
610 {
611 if (!path_)
612 return;
613 int src_x, src_y;
614 int dst_x, dst_y;
615 auto getSegmentEnd = [](odb::dbObject* node, int& x, int& y, bool& clk_node) {
616 if (node->getObjectType() == odb::dbObjectType::dbITermObj) {
617 odb::dbITerm* db_iterm = static_cast<odb::dbITerm*>(node);
618 db_iterm->getAvgXY(&x, &y);
619 } else {
620 odb::dbBTerm* bterm = static_cast<odb::dbBTerm*>(node);
621 bterm->getFirstPinLocation(x, y);
622 sta::dbSta* sta = ord::OpenRoad::openRoad()->getSta();
623 auto sta_term = sta->getDbNetwork()->dbToSta(bterm);
624 clk_node = sta->isClock(sta_term);
625 }
626 };
627 // SigType is not populated properly in OpenDB
628 bool clk_node = false;
629
630 getSegmentEnd(source_node, src_x, src_y, clk_node);
631 getSegmentEnd(sink_node, dst_x, dst_y, clk_node);
632
633 odb::Point pt1(src_x, src_y);
634 odb::Point pt2(dst_x, dst_y);
635
636 gui::Painter::Color wire_color = clk_node == true
637 ? TimingPathRenderer::clock_color_
638 : TimingPathRenderer::signal_color_;
639 painter.setPen(wire_color, true);
640 painter.drawLine(pt1, pt2);
641 }
642
643 } // namespace gui
644