Skip to content

Commit 0690392

Browse files
authored
Merge pull request #10294 from The-OpenROAD-Project-staging/gpl-virtual-cts
gpl: add virtual clock tree synthesis during global placement
2 parents 7049515 + ce5b0c5 commit 0690392

24 files changed

Lines changed: 2161 additions & 3 deletions

src/gpl/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ cc_library(
3838
name = "gpl",
3939
srcs = [
4040
"src/AbstractGraphics.cpp",
41+
"src/clockBase.cpp",
42+
"src/clockBase.h",
4143
"src/fft.cpp",
4244
"src/fft.h",
4345
"src/fftsg.cpp",

src/gpl/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ add_library(gpl_lib
3636
src/fftsg2d.cpp
3737
src/routeBase.cpp
3838
src/timingBase.cpp
39+
src/clockBase.cpp
3940
src/graphicsNone.cpp
4041
src/solver.cpp
4142
src/mbff.cpp
@@ -66,6 +67,7 @@ target_link_libraries(gpl_lib
6667
Eigen3::Eigen
6768
odb
6869
OpenSTA
70+
dbSta_lib
6971
rsz_lib
7072
grt_lib
7173
ortools::ortools

src/gpl/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ global_placement
105105
[-disable_revert_if_diverge]\
106106
[-disable_pin_density_adjust]\
107107
[-enable_routing_congestion]
108+
[-virtual_cts]
109+
[-virtual_cts_max_skew_fraction virtual_cts_max_skew_fraction]
108110
```
109111

110112
#### Options
@@ -131,6 +133,8 @@ global_placement
131133
| `-disable_revert_if_diverge` | Flag to make gpl store the placement state along iterations, if a divergence is detected, gpl reverts to the snapshot state. The default value is disabled. |
132134
| `-disable_pin_density_adjust` | Flag to disable instance pin density area adjustment. The pin density area adjustment is enabled by default. |
133135
| `-enable_routing_congestion` | Flag to run global routing after global placement, enabling the Routing Congestion Heatmap.|
136+
| `-virtual_cts` | Flag to build a lightweight virtual clock tree during global placement. Clock tree is used to compute clock network latency per clock sink to model clock skew during timing-driven placement. Virtual CTS runs before each timing-driven iteration. |
137+
| `-virtual_cts_max_skew_fraction` | Set max insertion delay as fraction of clock period. Valid range is [0, 1]; out-of-range values are clamped. Default is 0.10 (10%). |
134138

135139
#### Initial-Placement Arguments
136140

src/gpl/include/gpl/Replace.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class NesterovBaseCommon;
3737
class NesterovBase;
3838
class RouteBase;
3939
class TimingBase;
40+
class ClockBase;
4041

4142
class InitialPlace;
4243
class NesterovPlace;
@@ -70,6 +71,11 @@ struct PlaceOptions
7071
bool disableRevertIfDiverge = false;
7172
bool disablePinDensityAdjust = false;
7273
bool enable_routing_congestion = false;
74+
bool virtualCtsMode = false;
75+
// Maximum clock insertion delay as a fraction of the clock period.
76+
// The MST leaf farthest from the virtual clock root gets this delay;
77+
// all others are scaled proportionally. Default: 10% of the period.
78+
float virtualCtsMaxSkewFraction = 0.10f;
7379
float minPhiCoef = 0.95;
7480
float maxPhiCoef = 1.05;
7581
float initDensityPenaltyFactor = 0.00008;
@@ -165,6 +171,7 @@ class Replace
165171
std::vector<std::shared_ptr<NesterovBase>> nbVec_;
166172
std::shared_ptr<RouteBase> rb_;
167173
std::shared_ptr<TimingBase> tb_;
174+
std::shared_ptr<ClockBase> cb_;
168175

169176
std::unique_ptr<InitialPlace> ip_;
170177
std::unique_ptr<NesterovPlace> np_;

src/gpl/src/clockBase.cpp

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
// SPDX-License-Identifier: BSD-3-Clause
2+
// Copyright (c) 2018-2025, The OpenROAD Authors
3+
4+
#include "clockBase.h"
5+
6+
#include <algorithm>
7+
#include <cmath>
8+
#include <cstddef>
9+
#include <limits>
10+
#include <queue>
11+
#include <utility>
12+
#include <vector>
13+
14+
#include "db_sta/dbNetwork.hh"
15+
#include "db_sta/dbSta.hh"
16+
#include "odb/db.h"
17+
#include "sta/Clock.hh"
18+
#include "sta/MinMax.hh"
19+
#include "sta/NetworkClass.hh"
20+
#include "sta/Sdc.hh"
21+
#include "sta/SdcClass.hh"
22+
#include "sta/Sta.hh"
23+
#include "sta/Transition.hh"
24+
#include "utl/Logger.h"
25+
26+
namespace gpl {
27+
28+
using utl::GPL;
29+
30+
ClockBase::ClockBase(sta::dbSta* sta, odb::dbDatabase* db, utl::Logger* log)
31+
: sta_(sta), db_(db), log_(log)
32+
{
33+
}
34+
35+
ClockBase::~ClockBase() = default;
36+
37+
bool ClockBase::executeVirtualCts()
38+
{
39+
if (!sta_ || !db_) {
40+
return false;
41+
}
42+
43+
// Remove any previous virtual insertions before building a fresh model.
44+
removeVirtualCts();
45+
46+
const sta::ClockSeq& clocks = sta_->cmdSdc()->clocks();
47+
if (clocks.empty()) {
48+
log_->warn(GPL, 160, "Virtual CTS: no clocks defined in design. Skipping.");
49+
return false;
50+
}
51+
52+
int total_insertions = 0;
53+
for (const sta::Clock* clk : clocks) {
54+
const size_t before = virtual_inserts_.size();
55+
buildVirtualTreeForClock(clk);
56+
total_insertions += static_cast<int>(virtual_inserts_.size() - before);
57+
}
58+
59+
if (total_insertions == 0) {
60+
log_->warn(
61+
GPL, 161, "Virtual CTS: no register clock pins found. Skipping.");
62+
return false;
63+
}
64+
65+
log_->info(GPL,
66+
162,
67+
"Virtual CTS: set {} virtual clock insertion delays.",
68+
total_insertions);
69+
return true;
70+
}
71+
72+
void ClockBase::buildVirtualTreeForClock(const sta::Clock* clk)
73+
{
74+
if (!clk) {
75+
return;
76+
}
77+
78+
// Skip clocks with invalid or infinite periods (e.g. generated clocks
79+
// before propagation).
80+
const float period = clk->period();
81+
if (period <= 0.0f || std::isinf(period)) {
82+
return;
83+
}
84+
85+
// Build a one-element ClockSet for the query.
86+
sta::ClockSet clk_set;
87+
clk_set.insert(const_cast<sta::Clock*>(clk));
88+
89+
// Find all register clock sink pins for this clock.
90+
sta::PinSet sink_pins
91+
= sta_->findRegisterClkPins(&clk_set,
92+
sta::RiseFallBoth::riseFall(),
93+
/*registers=*/true,
94+
/*latches=*/true,
95+
sta_->cmdMode());
96+
97+
if (sink_pins.empty()) {
98+
return;
99+
}
100+
101+
// Collect sink positions.
102+
struct SinkInfo
103+
{
104+
const sta::Pin* pin;
105+
int x;
106+
int y;
107+
};
108+
std::vector<SinkInfo> sinks;
109+
sinks.reserve(sink_pins.size());
110+
111+
for (const sta::Pin* pin : sink_pins) {
112+
int x = 0;
113+
int y = 0;
114+
if (getPinLocation(pin, x, y)) {
115+
sinks.push_back({pin, x, y});
116+
}
117+
}
118+
119+
if (sinks.empty()) {
120+
return;
121+
}
122+
123+
// With a single sink, there is no meaningful skew to assign.
124+
if (sinks.size() == 1) {
125+
return;
126+
}
127+
128+
const int n = static_cast<int>(sinks.size());
129+
130+
// -----------------------------------------------------------------------
131+
// Build a minimum spanning tree (Prim's, O(n^2)) using Manhattan distance.
132+
// This approximates the topology a balanced CTS would produce: sinks
133+
// connected by short branches get low relative skew; those on long
134+
// branches get higher skew.
135+
// -----------------------------------------------------------------------
136+
std::vector<int> mst_parent(n, -1);
137+
std::vector<double> mst_edge_dist(n, 0.0);
138+
std::vector<bool> in_mst(n, false);
139+
std::vector<double> key(n, std::numeric_limits<double>::max());
140+
key[0] = 0.0;
141+
142+
for (int step = 0; step < n; ++step) {
143+
// Pick the minimum-key vertex not yet in the MST.
144+
int u = -1;
145+
for (int i = 0; i < n; ++i) {
146+
if (!in_mst[i] && (u == -1 || key[i] < key[u])) {
147+
u = i;
148+
}
149+
}
150+
in_mst[u] = true;
151+
152+
// Update keys for remaining vertices.
153+
for (int v = 0; v < n; ++v) {
154+
if (!in_mst[v]) {
155+
const double d = std::abs(sinks[u].x - sinks[v].x)
156+
+ std::abs(sinks[u].y - sinks[v].y);
157+
if (d < key[v]) {
158+
key[v] = d;
159+
mst_parent[v] = u;
160+
mst_edge_dist[v] = d;
161+
}
162+
}
163+
}
164+
}
165+
166+
// -----------------------------------------------------------------------
167+
// Root the MST at the node geometrically closest to the centroid of all
168+
// sinks. This acts as the virtual clock source (H-tree hub).
169+
// -----------------------------------------------------------------------
170+
double sum_x = 0.0;
171+
double sum_y = 0.0;
172+
for (const auto& s : sinks) {
173+
sum_x += s.x;
174+
sum_y += s.y;
175+
}
176+
const double cx = sum_x / n;
177+
const double cy = sum_y / n;
178+
179+
int root = 0;
180+
double best_centroid_dist = std::numeric_limits<double>::max();
181+
for (int i = 0; i < n; ++i) {
182+
const double d = std::abs(sinks[i].x - cx) + std::abs(sinks[i].y - cy);
183+
if (d < best_centroid_dist) {
184+
best_centroid_dist = d;
185+
root = i;
186+
}
187+
}
188+
189+
// Build an undirected adjacency list from the MST edges.
190+
std::vector<std::vector<std::pair<int, double>>> adj(n);
191+
for (int i = 0; i < n; ++i) {
192+
if (mst_parent[i] != -1) {
193+
adj[mst_parent[i]].emplace_back(i, mst_edge_dist[i]);
194+
adj[i].emplace_back(mst_parent[i], mst_edge_dist[i]);
195+
}
196+
}
197+
198+
// BFS from the root to compute each sink's path distance through the tree.
199+
std::vector<double> tree_dist(n, -1.0);
200+
std::queue<int> bfs;
201+
tree_dist[root] = 0.0;
202+
bfs.push(root);
203+
while (!bfs.empty()) {
204+
const int u = bfs.front();
205+
bfs.pop();
206+
for (auto& [v, w] : adj[u]) {
207+
if (tree_dist[v] < 0.0) {
208+
tree_dist[v] = tree_dist[u] + w;
209+
bfs.push(v);
210+
}
211+
}
212+
}
213+
214+
// -----------------------------------------------------------------------
215+
// Normalize so the farthest sink in the tree gets exactly
216+
// max_skew_fraction_ * period of insertion delay. All others scale
217+
// proportionally, preserving the relative skew structure.
218+
// -----------------------------------------------------------------------
219+
const double max_tree_dist = *std::ranges::max_element(tree_dist);
220+
221+
if (max_tree_dist < 1.0) {
222+
// All sinks are co-located; no meaningful skew to assign.
223+
return;
224+
}
225+
226+
const double scale
227+
= static_cast<double>(max_skew_fraction_) * period / max_tree_dist;
228+
229+
sta::Sdc* sdc = sta_->cmdSdc();
230+
231+
for (int i = 0; i < n; ++i) {
232+
// Preserve user-specified clock latency; do not overwrite or delete it.
233+
if (hasUserClockLatency(clk, sinks[i].pin)) {
234+
continue;
235+
}
236+
const float delay = static_cast<float>(tree_dist[i] * scale);
237+
// Use setClockLatency (network latency) rather than setClockInsertion
238+
// (source latency). During global placement the clock is ideal
239+
// (non-propagated), and OpenSTA only honours per-pin network latency
240+
// for ideal clocks; per-pin source latency is silently ignored.
241+
sta_->setClockLatency(const_cast<sta::Clock*>(clk),
242+
const_cast<sta::Pin*>(sinks[i].pin),
243+
sta::RiseFallBoth::riseFall(),
244+
sta::MinMaxAll::all(),
245+
delay,
246+
sdc);
247+
virtual_inserts_.push_back({clk, sinks[i].pin});
248+
}
249+
}
250+
251+
void ClockBase::removeVirtualCts()
252+
{
253+
if (virtual_inserts_.empty()) {
254+
return;
255+
}
256+
257+
sta::Sdc* sdc = sta_->cmdSdc();
258+
for (const auto& vi : virtual_inserts_) {
259+
sta_->removeClockLatency(vi.clk, vi.pin, sdc);
260+
}
261+
virtual_inserts_.clear();
262+
263+
debugPrint(log_,
264+
GPL,
265+
"virtual_cts",
266+
1,
267+
"Virtual CTS: removed virtual clock insertion delays.");
268+
}
269+
270+
bool ClockBase::getPinLocation(const sta::Pin* pin, int& x, int& y) const
271+
{
272+
if (!pin) {
273+
return false;
274+
}
275+
276+
sta::dbNetwork* network = sta_->getDbNetwork();
277+
278+
odb::dbITerm* iterm = nullptr;
279+
odb::dbBTerm* bterm = nullptr;
280+
odb::dbModITerm* moditerm = nullptr;
281+
network->staToDb(pin, iterm, bterm, moditerm);
282+
283+
if (iterm) {
284+
odb::dbInst* inst = iterm->getInst();
285+
if (!inst || !inst->isPlaced()) {
286+
return false;
287+
}
288+
inst->getLocation(x, y);
289+
return true;
290+
}
291+
292+
if (bterm) {
293+
// Clock port on the block boundary – use its placement location.
294+
int px = 0;
295+
int py = 0;
296+
if (bterm->getFirstPinLocation(px, py)) {
297+
x = px;
298+
y = py;
299+
return true;
300+
}
301+
}
302+
303+
return false;
304+
}
305+
306+
bool ClockBase::hasUserClockLatency(const sta::Clock* clk,
307+
const sta::Pin* pin) const
308+
{
309+
sta::Sdc* sdc = sta_->cmdSdc();
310+
for (const sta::RiseFall* rf :
311+
{sta::RiseFall::rise(), sta::RiseFall::fall()}) {
312+
for (const sta::MinMax* mm : {sta::MinMax::min(), sta::MinMax::max()}) {
313+
float latency = 0.0f;
314+
bool exists = false;
315+
sdc->clockLatency(clk, pin, rf, mm, latency, exists);
316+
if (exists) {
317+
return true;
318+
}
319+
}
320+
}
321+
return false;
322+
}
323+
324+
} // namespace gpl

0 commit comments

Comments
 (0)