|
| 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