The Concept of Chains & Worst Chains
When engineers first move from static timing closure into concurrent clock-and-data optimization, they tend to keep staring at individual timing paths. They pull up the worst violating endpoint, study its launch and capture, and try to fix it in isolation. This habit works fine for ordinary optimization, but it actively misleads you once useful skew enters the picture. The reason is structural: in a skew-aware flow, a single path is almost never the right unit of reasoning. The right unit is the chain. This chapter builds the chain mental model from scratch, explains why it is the correct lens for concurrent optimization, defines what a "worst chain" is, and shows how to read the reports the engine produces about it. By the end you should be able to look at a stubborn path that refuses to improve and diagnose, in seconds, whether the problem is the path itself or the company it keeps.
What a Chain Actually Is
A chain is a sequence of register-to-register stages that are linked together because they share clock arrival decisions. Picture flops F0, F1, F2, F3 in a line. Data launches from F0, lands at F1; launches again from F1, lands at F2; and so on. Each adjacent pair is one stage. The stages are not independent, because F1's clock arrival is simultaneously the capture edge for the first stage and the
launch edge for the second stage. Whatever you decide about when F1's clock fires, you are setting both a setup constraint and a hold constraint for two different stages at once. That shared register is the hinge of the whole idea. If you delay F1's clock slightly, you give the first stage (F0 to F1) more time to meet setup, but you simultaneously steal time from the second stage (F1 to F2) because its launch now happens later. Slack does not appear from nowhere; it is moved from one stage to its neighbor across the shared clock pin.

This is why we say a chain has a fixed total budget of slack that can be redistributed but not magically created. The clock network gives the engine a set of dials, one per shared register, and turning any dial shifts slack left or right along the chain. The art of useful-skew optimization is turning those dials so that the most starved stage gets relief without pushing any neighbor below zero.
| Term | Meaning in the chain model |
|---|---|
| Stage | One register-to-register timing arc (a launch flop to a capture flop) |
| Shared register | A flop that is the capture of one stage and the launch of the next |
| Clock arrival | The skew-adjusted time the clock edge reaches a given register |
| Slack budget | The total slack available across all stages of a chain |
| Redistribution | Moving slack between adjacent stages by adjusting a shared arrival |
| Binding stage | The stage whose slack limits how far the chain can improve |
| Worst chain | The chain whose slack distribution most constrains overall closure |
Why Chains, Not Paths, Are the Right Mental Model
Consider what happens if you optimize a path in isolation. You find stage F1 to F2 failing setup by 40 ps. The naive instinct is to advance F2's capture clock (make it arrive later) to relax the setup check. Done in isolation, that looks like a clean win. But F2 is a shared register: it captures stage 2 and launches stage 3. Pushing its clock later just transferred your 40 ps problem one stage downstream. If
stage 3 had only 10 ps of margin, you have now created a 30 ps violation where there was none, and your net QoR is worse. The chain model makes this trade explicit. Because moving a clock edge always helps one neighbor and hurts the other, the only sound way to reason about the move is to look at both sides simultaneously and, by extension, the whole chain those sides belong to. Concurrent optimization engines do exactly this: rather than fixing one endpoint, they treat the chain as a coupled system and solve for the set of clock arrivals that maximizes the minimum stage slack across the entire chain. The goal is to lift the floor, not to win any single check. A second, subtler reason chains matter: data-path effort and clock-path effort are fungible inside a chain. The engine can choose to resize a buffer in the data logic of stage 2, or it can shift the clock arrival at the shared register, and both produce the same slack movement. Treating the chain as the unit lets the engine pick whichever is cheaper. If you only look at one path, you never see that the better fix lives in the next stage over. Rule of thumb: if a single fix improves your target path but you have not checked what it did to the adjacent stages, you have not finished thinking. A path improvement that is not also a chain improvement is usually an illusion.
The Worst Chain
Every design has many chains, and they overlap and branch in real netlists, but the abstraction still holds locally. Among all of them, one chain will have the least favorable distribution of slack — the chain where, no matter how cleverly you redistribute, the best achievable minimum-stage slack is the lowest in the design. That is the worst chain. Notice the careful wording. The worst chain is not simply the chain containing the single worst stage. A chain can contain a deeply negative stage and still be easy to fix if its neighbors are flush with positive slack to donate. The worst chain is the one whose internal balance is poor: its stages are collectively tight, so there is little surplus anywhere to redistribute into the starved stage. The binding constraint is the chain's own slack budget, not one unlucky arc.
Worst chain limits the achievable gain
| Chain A — one deep negative, slack nearby | Chain B — uniformly tight (worst) | ||||||
|---|---|---|---|---|---|---|---|
| -0.30 | +0.25 | +0.20 | +0.15 | -0.10 | -0.08 | -0.09 | -0.07 |
| stage 1 | stage 2 | stage 3 | stage 4 | stage 1 | stage 2 | stage 3 | stage 4 |
Figure 8.2 Two chains side by side. Chain A has one very negative stage but large positive neighbors (easy to
balance). Chain B has several mildly negative stages and no surplus (the true worst chain). A caption notes that balancing potential, not single-stage slack, defines the worst chain. This distinction is the single most valuable idea in the chapter for interviews and for daily debugging. A junior engineer points at the most negative endpoint. A senior engineer asks which chain has the least room to breathe. There is also a scope subtlety worth internalizing. Real designs are not tidy single-file chains; registers fan out and fan in, so a given flop may participate in several overlapping chains at once. A capture register might be the endpoint of three different launch flops and the start of two more. The chain abstraction still applies, but the engine must reconcile arrival requests that come from multiple chains through the same shared register. When one chain wants the shared clock later and another wants it earlier, the register's arrival becomes a contended resource. Frequently the true worst chain is not the one with the most negative arc but the one whose shared registers are the most heavily contended — pulled in opposite directions by their neighbors so that no single arrival satisfies everyone. Recognizing contention as a distinct failure mode from plain slack starvation will save you from chasing the wrong fix.
How the Engine Redistributes Slack
Mechanically, the engine walks a chain and computes, for each candidate set of shared-register arrivals, the resulting per-stage slacks. It then searches for the arrival assignment that maximizes the minimum stage slack, subject to hold constraints not being violated anywhere. This is a classic minmax balancing problem. When the chain is well-balanced, every stage ends up at roughly the same slack; the chain is "flat." When you cannot flatten it — when pushing slack into the worst stage forces a neighbor negative — you have hit the chain's budget limit, and that limiting condition is precisely what makes it the worst chain. A simple way to see the balancing in action is to look at per-stage slack before and after optimization.
| Stage | Slack before (ps) | Slack after (ps) | What the engine did |
|---|---|---|---|
| F0 to F1 | +120 | +45 | Donated margin by moving F1 clock later |
| F1 to F2 | -60 | +40 | Received slack from both neighbors |
| F2 to F3 | +95 | +42 | Donated margin by moving F2 clock earlier |
| F3 to F4 | +50 | +44 | Lightly adjusted to keep the chain flat |
Before, the chain has a -60 ps worst stage. After redistribution, every stage sits near +42 ps and nothing is negative. The total has not increased — note the donors gave up exactly what the starved stage needed — but the minimum rose dramatically. That is a successful chain balance. If, instead, the donors had been near zero to begin with, the engine would have had nothing to give, the worst stage would have stayed negative, and you would be looking at the worst chain. It is worth being precise about why "flat" is the target the engine aims for. A flat chain is one where every stage carries the same slack. That state is optimal for robustness because it leaves no stage closer to failure than any other; the chain's margin against process, voltage, and temperature variation is maximized when no single stage is the obvious weak link. As soon as one stage sits well below its neighbors, that stage becomes the first to fail under any adverse shift, even if it is technically positive in the nominal corner. So the engine does not merely chase non-negative slack — it actively spreads the available budget evenly, because an even spread is the most defensible distribution. When you see a post-optimization report where the stages are nearly equal, that is the engine telling you it found the budget's natural balance point. When you see one stage stubbornly lower than the rest, that gap is your signal that something is preventing the balance — a constraint, a fixed arrival, or a genuine shortage of donatable margin. A related practical point: hold timing rides along with every setup decision. Each time the engine shifts a shared register's arrival to relieve a setup-bound stage, it changes the hold relationship on both adjacent stages too. A shift that looks free on the setup side can quietly erode a hold margin to the point where the move is no longer legal. This coupling is exactly why the engine solves setup and hold
together along the chain rather than fixing setup first and patching hold afterward. The chain view is what makes that simultaneous solve tractable, because all the affected arcs are already grouped.
Reading Worst-Chain Data
Reports that describe a worst chain typically expose a consistent set of fields. Knowing what each means lets you go straight to the binding constraint.
| Report field | Meaning |
|---|---|
| Chain ID / start register | Identifier and the launch flop where the chain begins |
| Stage count | Number of register-to-register stages in the chain |
| Worst-stage slack | The most negative (or least positive) stage slack in the chain |
| Achievable min slack | Best minimum-stage slack after ideal redistribution |
| Limiting stage | The stage that caps the achievable minimum |
| Donatable margin | Total positive slack available to redistribute |
| Bottleneck type | Whether the limit is setup-bound, hold-bound, or data-path-bound |
The two fields that matter most are achievable min slack and donatable margin. If achievable min slack is still negative, the chain cannot be balanced into the clear — this is a real worst chain and needs structural help (data-path restructuring, retiming, or floorplan change), not more skew tuning. If donatable margin is large but achievable min slack is poor, the redistribution is being blocked by a hold constraint or a register that cannot be shifted, and that is your investigation target.
Generic Command Sketches
These are vendor-neutral command shapes. Names vary by tool, but the intent maps cleanly across flows. First, ask the engine to report the worst chains, ranked.
# Report the N most constraining chains in the design
report_clock_chains \
-mode worst \
-max_chains 5 \
-include stage_slack \
-sort_by achievable_min_slack
Once you have a chain of interest, inspect its per-stage slack so you can see the distribution and find the limiting stage.
# Dump per-stage slacks for one chain, before vs after balancing
report_chain_stages \
-chain CHAIN_0042 \
-show {launch capture stage_slack arrival} \
-compare {pre_opt post_opt}
Finally, when you suspect a path is stuck because its neighbors are tight, query the chain that a specific endpoint belongs to, then read its donatable margin.
# Find the chain owning a given endpoint and check its balancing room
set chain [get_chain_of_endpoint -pin U_core/F2/D]
report_chain_stages -chain $chain -show {stage_slack donatable_margin}
# If donatable_margin is near zero across stages, the chain -- not the
# path -- is the problem; skew tuning will not help here.
Practical Diagnosis: Why a Path Won't Improve
Here is the workflow that this whole chapter exists to enable. You have a path that refuses to close. You have thrown effort at it — upsized cells, tightened the route, asked for more skew — and the slack barely moves. The chain model tells you why almost immediately. Pull the chain that owns the endpoint. Look at the per-stage slacks. If the neighboring stages are also tight or negative, the engine has no surplus to donate into your stage. You are not fighting a slow path; you are fighting an exhausted slack budget. No amount of clock tuning will help, because there is nothing to redistribute. The fix has to add budget: retime a register into a different position, restructure the data logic to shorten a stage, or change the floorplan so a stage's wire delay drops. Conversely, if you see fat positive margin in the neighbors but your stage is still negative after optimization, the redistribution is being blocked. The usual culprits are a hold constraint pinning a shared register's arrival, a clock that cannot be shifted because it is on a constrained domain, or a don't-touch on the shared register. Now you know exactly where to aim. The discipline is always the same: stop staring at the path, pull up its chain, and ask whether the budget exists and whether it can be moved. That single habit separates engineers who close skewaware designs efficiently from those who burn days fixing one path while quietly breaking its neighbors.
Interview Q&A
arrival of any shared register is simultaneously the capture edge of one stage and the launch edge of the next. Moving that edge helps one neighbor and hurts the other, so any fix made in isolation may just relocate the violation to an adjacent stage. The correct unit of reasoning is the chain, where both sides of every shared register are visible at once and the engine can balance globally instead of locally.
common trap. A chain can contain a very negative stage yet be trivial to fix if its neighbors carry large positive slack to donate. The worst chain is the one with the poorest balancing potential — collectively tight stages with little surplus anywhere — so its achievable minimum-stage slack is the lowest in the design even after ideal redistribution. Balance, not raw single-stage slack, defines it.
happened? You almost certainly pushed slack across a shared register into a neighbor that had no margin, creating a new violation downstream. The local fix borrowed from a stage that could not afford
to lend. Always check the adjacent stages, or better, the whole chain, before accepting a skew-based path fix. A path improvement that degrades a neighbor is not a real improvement.
What does that tell you and what do you do? It tells you slack exists in the chain but cannot be moved to where it is needed — redistribution is blocked. Likely causes are a hold constraint pinning a shared register's arrival, a clock domain that cannot be shifted, or a don't-touch on the shared register. The action is to find and relax the blocker, not to keep adding skew. If no blocker can be removed and the achievable minimum stays negative, the chain needs structural help such as retiming or floorplan change.
Key Takeaways
- A chain is a sequence of register-to-register stages linked by shared clock arrivals; slack can be moved between adjacent stages but not created from nothing.
- Moving a shared register's clock edge always helps one neighbor and hurts the other, which is why the chain, not the path, is the correct unit of reasoning in concurrent and useful-skew optimization.
- The engine balances a chain by maximizing the minimum stage slack subject to hold constraints — it lifts the floor rather than winning any single check.
- The worst chain is defined by poor balancing potential, not by containing the single most negative stage; its achievable minimum slack is the design's true limiter.
- Read worst-chain reports for achievable min slack and donatable margin: negative achievable min means the budget is genuinely exhausted; blocked redistribution means a constraint is pinning a shared arrival.
- When a path won't improve, pull its chain. If the neighbors are tight, the cure is more budget (retiming, restructuring, floorplan), not more skew.
ChipBuddy
← Home
Comments
Leave a Reply