Concurrent Clock & Data Optimization
Idea
From Balanced Trees to Real Slack
For a long time, clock tree synthesis (CTS) was a self-contained problem with a self-contained goal: take an unbalanced, ideal-clock netlist and build a physical distribution network that delivers the clock edge to every register at almost the same instant. "Almost the same" meant low skew. The implicit assumption was that if every flip-flop in a clock domain saw the edge simultaneously, the datapath optimization done before and after CTS would have the cleanest possible launch-and-capture relationship to work with. That assumption is comfortable but wrong in a subtle way. A perfectly balanced tree minimizes skew, but skew is only one term in the timing equation. The quantity an interviewer actually cares about — the quantity that determines whether the chip closes — is slack: the margin between when data arrives at a capture register and when that register needs it. Skew is a means; slack is the end. The moment you accept that distinction, the old workflow starts to look inefficient. You spent enormous effort forcing every clock arrival to be equal, and then handed the result to a timing-closure step that is forbidden from touching the one thing (the clock arrival) that could have helped it most.
Concurrent clock-and-data optimization (commonly abbreviated CCD, and described generically here as concurrent clock optimization) is the response to that inefficiency. Instead of treating the clock network as something to be made flat and then frozen, it treats each register's clock arrival time as a tunable variable that is optimized at the same time as the datapath logic feeding and following that register. The objective is no longer "make skew small." It is "make slack good."

The Key Insight: Slack Is Movable
Consider any flip-flop in the middle of a pipeline. It is the capture register for the stage that feeds it, and the launch register for the stage it drives. Its clock arrival time appears in both stages' timing equations — but with opposite sign:
- For the incoming stage, a later clock edge at this register gives the data more time to arrive. Later capture clock = more setup slack on the input path.
- For the outgoing stage, a later clock edge means data leaves later, so the next register has less time. Later launch clock = less setup slack on the output path. This is the whole game. The clock arrival at a single register is a lever that adds margin to one stage by removing it from the neighbor. If the incoming stage is tight and the outgoing stage is loose, you deliberately delay this register's clock, "borrowing" slack from the loose stage to rescue the tight one. This deliberate, beneficial imbalance is called useful skew, and the act of shifting margin across a register boundary is often described as slack borrowing or skew scheduling. A perfectly balanced tree throws this lever away. By insisting every register sees the edge at the same time, it forces every stage to live or die on its own logic delay, even when an idle neighbor is sitting on a pile of unused margin one boundary away.

Why Joint Beats Sequential
You might object that useful skew is an old idea and that classic flows already supported a "skew-thenfix" sequence: build the tree, run a separate useful-skew pass, then let datapath optimization clean up. So what does concurrent buy you? The answer is visibility into the true trade-off. A sequential flow optimizes the datapath under an ideal clock model, builds the tree, and only then sees the propagated reality. By the time the real (propagated) clock arrivals exist, the datapath decisions — gate sizes, buffer counts, logic restructuring — are already locked in. Each step optimizes against a model the next step will invalidate. Concurrent optimization collapses that loop. It evaluates clock arrival and datapath cost together, against a propagated clock model throughout, so every decision is scored on real numbers:
- When it considers delaying a register's clock by 60 ps, it simultaneously sees what that does to the downstream stage and whether a one-size upsize on a downstream gate recovers the loss more cheaply than the upstream gain.
- When it considers upsizing a datapath gate, it knows whether a small clock-arrival nudge would have achieved the same slack for less area and power. Sequential flows pick the clock fix or the data fix in isolation; concurrent flows pick the cheaper of the two for the same slack, because they price both at once. That joint pricing is the entire reason the approach exists. There is a second, quieter benefit that interviewers reward you for noticing: convergence. In a sequential flow, every model handoff is a chance for the next stage to undo the previous one. Datapath optimization sizes a gate up assuming an ideal clock; the tree is built and the real arrival turns out later, so that gate is now oversized for the actual slack; a post-CTS pass might then shrink it, undoing work and burning iterations. Each loop nudges the design but no single step ever sees the whole picture, so closure crawls and sometimes oscillates. A concurrent engine, scoring everything against one consistent propagated model, tends to converge in fewer iterations because it is not chasing a moving target. Fewer design-close iterations is itself a real engineering and schedule win, independent of the final slack number.
It is also worth being precise about when in the flow this happens. Concurrent optimization is not a magic switch you flip after a finished tree; it is woven into the clock-building step itself, so that the tree topology, the per-register latencies, and the surrounding datapath are decided as one coupled problem. That is why the approach is sometimes described as "clock-aware datapath optimization" and "datapath-aware clock building" — the two adjectives describe the same single pass viewed from opposite ends.
| Aspect | Traditional CTS | Concurrent Clock & Data Optimization |
|---|---|---|
| Primary objective | Minimize global/local skew | Maximize real slack (WNS/TNS) |
| Clock model during opt | Ideal until tree built | Propagated (real) throughout |
| Clock arrival per register | Forced equal (balanced) | Tunable variable (useful skew) |
| Datapath interaction | Separate before/after step | Optimized jointly with clock |
| Insertion delay | Minimized / balanced for skew | Balanced for slack, allowed to vary |
| Success metric | Low skew number | Improved WNS / TNS on path groups |
| Typical risk | Wasted margin, false closure | Higher runtime, power, complexity |
What Actually Gets Optimized
It helps to be concrete about the moves available to a concurrent engine. They fall into clock-side moves, data-side moves, and the joint decisions that only make sense when both are visible.
| Lever the engine can move | Side | Primary effect |
|---|---|---|
| Per-register clock latency (insertion | Clock | Schedules useful skew; borrows slack across a register |
delay)
| Clock buffer/inverter sizing & placement | Clock | Tunes arrival time and transition; controls power/OCV |
|---|---|---|
| Tree-level insertion delay balancing | Clock | Aligns related groups (e.g., across interface paths) |
| Datapath gate sizing | Data | Speeds the critical stage to complement a clock decision |
| Datapath buffer insertion / removal | Data | Fixes transition or recovers a stage the clock move |
loaded
Local logic restructuring Data Reduces depth where skew alone cannot close the Three things deserve emphasis for interviews:
- 1. Insertion delay balancing is now slack-driven, not skew-driven. The engine still cares that
clock arrivals are coordinated, but the target is the arrival profile that yields the best slack, which is generally not the flat profile.
- 2. Useful-skew scheduling is global. Delaying one register helps its incoming stage and hurts its
outgoing stage; that hurt ripples to the next register, whose clock may then also move. The engine solves this as a coupled system, which is exactly why doing it by hand on more than a handful of paths is hopeless.
- 3. Datapath buffering and sizing are driven by clock decisions. A buffer added on a data path
may exist purely because a clock move shifted where the new criticality landed. The data fix is a consequence of the clock schedule, not an independent activity. A useful way to internalize this is to picture the engine asking, at every register boundary, a single comparative question: to gain this 50 ps of setup slack, is it cheaper to move the clock or to touch the data? Moving the clock costs clock-network resources and pushes hold and OCV risk; touching the data costs area, leakage, and possibly transition degradation. Neither is universally better. On a path where the datapath is already maxed out — biggest gates, fewest stages — a clock move is often the only lever left, and useful skew earns its keep. On a path where a tiny upsize closes the stage and the neighboring stage has no margin to lend, the data fix wins. The strength of concurrent optimization is precisely that it asks this question with both prices on the table instead of committing to one answer in advance.

The Objective Function: Slack, Not Skew
The engine optimizes the same numbers your sign-off timer reports. Worst negative slack (WNS) is the single most violating path; pushing it toward zero is the headline goal. Total negative slack (TNS) is the sum of all violations; reducing it means fixing the population of bad paths, not just the worst one. Mature flows also let you weight or scope by path group — so register-to-register, input, output, and clock-gating paths can be prioritized differently, and a relaxed group is not "fixed" at the expense of a critical one. The practical consequence is a different mental model of success. A tree with 5 ps of skew and a stage stuck at −80 ps WNS is a failure. A tree with 120 ps of (useful) skew and +10 ps WNS everywhere is a success. Reporting skew alone tells you almost nothing; you must read the slack on
the critical path groups. This is the trap interviewers love to set: "Your skew went up after CCD — is that bad?" The correct answer is "Not necessarily — show me WNS and TNS." A subtle but important point: WNS and TNS are not interchangeable, and a good engineer optimizes for both deliberately. WNS is dominated by the single worst path, so an engine chasing WNS alone can fix the headline violator while ignoring a hundred near-critical paths just behind it. TNS rewards clearing the whole population, but a TNS-only objective can leave the single deepest violator untouched if many small fixes look more productive in aggregate. Real flows weight the two together, and a careful reviewer checks the slack histogram, not just the two summary numbers — a healthy concurrent run pulls the entire distribution rightward, not just its extremes. When you discuss results in an interview, framing them as "the slack distribution moved" rather than "WNS improved" signals that you understand what the engine is actually doing.
Trade-offs: Nothing Is Free
Concurrent optimization is more powerful and correspondingly more expensive. Be ready to name the costs honestly.
- Runtime and effort. Solving clock and data jointly against a propagated model is far heavier than building a balanced tree. Higher effort levels can multiply runtime; you spend it where slack is hard to get and dial it down elsewhere.
- Complexity and predictability. Useful skew makes the clock network and the timing report harder to reason about. Latencies vary by register, hold paths shift, and ECO behavior is less intuitive. Good constraints and skew limits keep this controlled.
- Power. Borrowing slack often means larger clock buffers and extra datapath gates, both of which burn dynamic power. Aggressive useful skew can also raise on-chip-variation (OCV) sensitivity along longer, more divergent clock paths.
- Hold-time exposure. Every clock arrival you move for setup reshapes the hold relationship on the same boundary. Concurrent flows must respect hold while chasing setup, or you simply trade one violation class for another. The discipline, then, is to bound the freedom: cap the useful-skew range, constrain insertion delay, weight power into the cost, and reserve high effort for the path groups that need it. One more trade-off deserves a sentence because juniors routinely miss it: useful skew can complicate downstream ECO and timing signoff. Once latencies vary register by register, a late functional ECO that adds logic near a heavily-skewed register can find that its borrowed margin is already spent, leaving little room to absorb the change. Designs that lean hard on useful skew are therefore less forgiving of late churn. Experienced teams keep some headroom in reserve rather than borrowing every last picosecond, precisely so that the inevitable late ECO does not detonate a carefully balanced schedule.
Generic Command Sketch
The exact syntax is tool-specific, but the shape of the flow is universal: confirm a propagated clock model, configure the concurrent objective and its bounds, run, then read slack — not skew — to judge it.
# 1. Ensure timing is evaluated against the REAL (propagated) clock,
# not an ideal one. Concurrent optimization is meaningless on ideal clocks.
set_clock_propagation -mode propagated
set_analysis_corners -setup {slow} -hold {fast}
Next, turn on concurrent clock-and-data optimization and tell it what freedom it has and what it is optimizing for:
# 2. Configure concurrent clock + data optimization
set_cts_mode -concurrent_clock_data true
set_optimization_objective -target {wns tns} \
-effort high \
-power_weight medium
# Bound the freedom so useful skew stays controllable
set_useful_skew -enable true \
-max_clock_latency 0.250 \
-max_skew_per_stage 0.120
set_path_group_priority -group reg2reg -weight 3 \
-group in2reg -weight 1 \
-group reg2out -weight 1
# Protect hold while chasing setup
set_optimization_constraint -fix_hold true -hold_margin 0.020
Then run the joint optimization and report against slack-based metrics:
# 3. Run concurrent clock + data optimization
optimize_clock_and_data -mode concurrent
# 4. Judge it by SLACK, not skew
report_timing -path_group {reg2reg in2reg reg2out} \
-metric {wns tns} -max_paths 50
report_clock_latency -per_register -summary ;# expect non-flat, by design
report_power -nets clock -summary ;# watch the power cost
When you read the output, resist the reflex to celebrate a low skew number or panic at a high one. Walk the WNS and TNS per path group, confirm hold is still clean, and check that clock power did not balloon past budget. That triplet — setup slack, hold slack, power — is how you grade a concurrent run.

Interview Q&A
balanced tree and then fixing timing? Because it optimizes clock arrival and datapath together against a propagated clock model, so it picks the cheaper of a clock move or a data move for the same slack — whereas a sequential flow makes datapath decisions under an ideal clock that the tree later invalidates, leaving wasted margin and false closure on the table.
that a problem? No — that is the intended behavior. The engine deliberately introduces useful skew, delaying some registers' clocks to borrow slack from loose stages into tight ones. Skew is a means, not the objective; slack is the objective. As long as WNS/TNS improved and hold is still met, higher skew is a feature, not a bug.
clock edge gives its incoming stage more setup margin while taking margin from its outgoing stage, because that register is the capture point for one stage and the launch point for the next. The catch is twofold: the lost outgoing margin ripples to the next register (so the schedule must be solved globally), and moving the edge for setup reshapes the hold relationship on the same boundary — so hold can break if it is not constrained alongside setup.
the main costs of the approach? Useful skew only has meaning when real clock arrivals exist; under an ideal clock every register sees the edge at time zero, so there is no per-register latency to schedule and the trade-offs are invisible. The costs are runtime and effort (joint solving against a real model is heavy), reduced predictability and harder debug from non-uniform latencies, extra dynamic power from larger clock buffers and added datapath gates, and increased OCV/hold sensitivity that must be bounded with skew and latency limits.
Key Takeaways
- The goal of modern clock implementation is maximum real slack (WNS/TNS), not minimum skew; skew is just one lever in the slack equation.
- Each register's clock arrival is a tunable variable. Delaying it borrows setup slack from a loose stage into a tight one — this is useful skew.
- Concurrent beats sequential because it scores clock moves and datapath moves together against a propagated clock model, choosing the cheaper fix for the same slack instead of optimizing each under a model the other will break.
- What gets optimized: per-register insertion delay (slack-driven, not flat), clock buffer sizing, and datapath sizing/buffering driven by the clock schedule.
- Grade a run on the triplet setup slack, hold slack, and power — never on the skew number alone.
- The price is runtime, complexity, power, and hold/OCV exposure; control it by bounding useful- skew range and insertion delay and by weighting power and path groups in the objective.
ChipBuddy
← Home
Comments
Leave a Reply