Static Timing Analysis & Timing Closure
Volume 01 promised to make propagation delay quantitative. Volume 03 stated that hold
violations cannot be fixed by slowing the clock and deferred the proof. Volume 05 warned
that a careless set_false_path destroys your metastability margin. This is
where all of that gets settled - with two equations that govern every synchronous digital
circuit ever built.
7.1 The fundamental timing path
Every timing check in a synchronous design examines the same structure: a launch flip-flop, some combinational logic, and a capture flip-flop - with a clock that reaches the two flops at slightly different times.
| Symbol | Meaning | Which check uses it |
|---|---|---|
| Tclk | Clock period | Setup only |
| tcq | Launch flop clock-to-Q delay | Both |
| tcomb | Combinational delay - max for setup, min for hold | Both |
| tsetup | Capture flop setup requirement | Setup only |
| thold | Capture flop hold requirement | Hold only |
| tskew | Capture clock arrival - launch clock arrival | Both, opposite signs |
| tunc | Clock uncertainty (jitter + margin) | Both |
7.2 Setup slack & maximum frequency
The setup check asks: does the data launched by this edge arrive at the capture flop early enough to be stable before the next edge? It is a race between the data path and one clock period.
required = Tclk + tskew - tsetup - tunc
slacksetup = Tclk + tskew - tsetup - tunc - tcq - tcomb(max)
Setting slack ≥ 0 and rearranging gives the minimum period the design can run at - and therefore the maximum frequency:
fmax = 1 / Tmin
Worked example
| Parameter | Value | Contribution |
|---|---|---|
| Tclk (200 MHz) | 5.00 ns | +5.00 |
| tskew (capture later) | 0.10 ns | +0.10 |
| tsetup | 0.15 ns | -0.15 |
| tunc (jitter + margin) | 0.05 ns | -0.05 |
| tcq | 0.25 ns | -0.25 |
| tcomb(max) | 3.80 ns | -3.80 |
| Setup slack | +0.85 ns | MET |
And the ceiling this path imposes:
fmax = 1 / 4.15 ns = 241 MHz
| Fixing a setup violation | How it helps |
|---|---|
| Lower the clock frequency | Directly increases Tclk. Always works, always costs performance |
| Pipeline the logic | Splits tcomb across two cycles. The real fix; costs latency and flops |
| Restructure the logic | Turn a chain into a tree - Volume 02's entire subject |
| Upsize cells / better placement | Reduces tcomb at a power and area cost |
| Useful skew | Deliberately delay the capture clock - steals margin from the next stage |
7.3 Hold slack: why hold violations kill silicon
The hold check asks a completely different question: does the data launched by this edge arrive so fast that it corrupts the capture flop's reading of the same edge? Both events belong to one edge - and that single fact changes everything.
required = tskew + thold + tunc
slackhold = tcq + tcomb(min) - thold - tskew - tunc
- A hold violation fails at every frequency - 1 GHz, 1 MHz, and at DC.
- You cannot debug it by slowing the board down. It will not go away.
- A setup violation ships as a chip with a lower speed grade. A hold violation ships as scrap.
Worked example - and the proof that frequency is irrelevant
| Parameter | Value | At 200 MHz | At 20 MHz |
|---|---|---|---|
| tcq(min) | 0.12 ns | +0.12 | +0.12 |
| tcomb(min) | 0.05 ns | +0.05 | +0.05 |
| thold | 0.12 ns | -0.12 | -0.12 |
| tskew | 0.15 ns | -0.15 | -0.15 |
| tunc | 0.03 ns | -0.03 | -0.03 |
| Hold slack | -0.13 ns | -0.13 ns |
Ten times slower. Identical violation. The only fix is to change the data
path or the clock tree - insert buffers so t_comb(min) rises from 0.05 ns to,
say, 0.25 ns:
7.4 Clock skew, jitter & distribution networks
Skew appears in both equations - with opposite signs. That single fact is the central tension of clock tree design.
| Skew | Jitter | |
|---|---|---|
| Nature | Spatial - different arrival times at different places | Temporal - same place, varies edge to edge |
| Predictable? | Yes - static, measurable, fixable by routing | No - random, from the PLL and supply noise |
| Can be exploited? | Yes - "useful skew" borrows time | Never. It is pure margin loss |
| In SDC | Modelled by the actual clock tree after CTS | set_clock_uncertainty |
7.5 SDC constraints & timing exceptions
STA is only as good as what you tell it. An unconstrained path is not checked, and an over-constrained one wastes weeks of effort chasing a violation that does not exist.
# ============ 1. Define the clocks ====================================
create_clock -name clk_core -period 5.000 [get_ports clk]
create_clock -name clk_io -period 20.000 [get_ports clk_slow]
# A clock produced by a divider must be DECLARED, not re-created, so the
# tool knows it is phase-related to its source.
create_generated_clock -name clk_div2 \
-source [get_ports clk] -divide_by 2 [get_pins div_reg/Q]
# Jitter + margin. Before clock tree synthesis this also stands in for
# the skew the tree has not been built yet to reveal.
set_clock_uncertainty -setup 0.050 [get_clocks clk_core]
set_clock_uncertainty -hold 0.030 [get_clocks clk_core]
# ============ 2. Constrain the I/O boundary ===========================
# Without these, paths to and from the pins are simply unchecked.
set_input_delay -clock clk_core -max 1.200 [get_ports data_in*]
set_input_delay -clock clk_core -min 0.300 [get_ports data_in*]
set_output_delay -clock clk_core -max 1.500 [get_ports data_out*]
set_output_delay -clock clk_core -min 0.200 [get_ports data_out*]
# ============ 3. Exceptions ===========================================
# Genuinely unrelated clocks (Volume 05). This is the RIGHT way to stop
# the tool trying to close timing between asynchronous domains.
set_clock_groups -asynchronous \
-group [get_clocks clk_core] \
-group [get_clocks clk_io]
# A static configuration register that is written once and never changes
# during operation: real, but never timed against a live edge.
set_false_path -from [get_cells cfg_mode_reg*]
# ============ 4. Multicycle paths - READ THE NOTE BELOW ==============
# A wide multiplier whose result is not consumed until 2 cycles later.
set_multicycle_path 2 -setup -from [get_cells mult_a_reg*] -to [get_cells result_reg*]
set_multicycle_path 1 -hold -from [get_cells mult_a_reg*] -to [get_cells result_reg*]
The multicycle path trap
Relaxing a setup check to N cycles also drags the hold check along with it, because by default hold is checked one edge before the setup capture edge. That leaves the tool demanding that data take at least one full cycle to arrive - a requirement almost no path can meet, producing a flood of impossible hold violations.
| Exception | Means | Danger |
|---|---|---|
| set_false_path | "Never check this path at all" | Also removes any delay bound - see the Volume 05 CDC warning |
| set_multicycle_path | "Data has N cycles to arrive" | Useless or harmful without the matching -hold |
| set_max_delay | "Bound this path to N ns regardless of clocks" | The right tool for CDC crossings |
| set_clock_groups | "These clocks are unrelated" | Cleaner than false paths between every register pair |
| set_case_analysis | "This signal is constant in this mode" | Forgetting to analyse the other mode too |
Interview grilling - "Your design has 4000 hold violations after place-and-route. What happened?"
The number is the clue. Four thousand violations is not four thousand bad paths - it is one systemic cause. Work through the possibilities in order of likelihood:
-
A missing or wrong
-holdmulticycle. Someone relaxed setup on a wide bus and the hold check drifted an edge, instantly failing every bit. This is the single most common cause of a sudden mass hold failure. - The clock tree was not built yet. Before CTS, tools often assume an ideal clock. The first post-CTS run reveals real skew, and thousands of previously "clean" short paths light up. Entirely expected; the tool fixes them with buffers.
- Wrong hold corner. Hold is checked at the fast corner. If the library or the corner setup is wrong, every path looks too fast.
- Clock uncertainty left at the pre-CTS value. A large pessimistic uncertainty is correct before CTS and wildly over-constraining after it.
- A genuinely bad clock tree - an unbalanced branch giving one region hundreds of picoseconds of skew.
The framing that lands: "Thousands of violations with tiny negative slack is a constraint or flow problem. A handful of violations with large negative slack is a design problem. I would check the multicycle constraints and the corner setup before touching any RTL."
hold = setup − 1 rule for multicycle paths - continue to
FPGA Mastery Volume 03.
Volume 07 recap
| Concept | The one thing to remember |
|---|---|
| Setup slack | T + skew - t_setup - t_unc - t_cq - t_comb(max) |
| Hold slack | t_cq + t_comb(min) - t_hold - skew - t_unc |
| The key asymmetry | Tclk is in the setup equation and absent from hold. |
| Consequence | Hold violations fail at every frequency, including DC. Setup violations just mean a slower part. |
| fmax | Set by the single worst path. Everything else is irrelevant to it. |
| Skew | Helps setup, hurts hold. There is a band of acceptable skew, not a target. |
| Insertion delay | Not skew. Only the difference between two flops on a path matters. |
| Corners | Setup at slow/low-V/hot; hold at fast/high-V/cold. Sign off on all of them. |
| Multicycle paths | N-cycle setup always pairs with (N-1)-cycle hold. |
| False paths | Remove the delay bound too. For CDC use set_max_delay instead. |