Volume 07 Advanced 5 sub-modules ~60 min read

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.

What "static" means STA checks every path in the design against every timing constraint, exhaustively, without simulating a single vector. It does not care what your logic computes - only how long the signals take. That is why it is the sign-off gate: a simulation proves the cases you thought of, while STA proves timing for cases nobody thought of.

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.

A timing path showing the launch flip-flop, combinational logic, capture flip-flop and the clock tree delays that produce skew D LAUNCH FF1 t_cq COMBINATIONAL LOGIC t_comb CAPTURE FF2 t_setup / t_hold Q clk t1 t2 (longer) clock skew = t2 - t1 (positive here: capture arrives later)
Figure 7.1 - Skew is the difference in clock arrival, not the absolute delay. Both flops can sit 800 ps from the clock source with zero skew; what matters is whether their arrivals differ. Sign convention throughout this volume: positive skew means the capture clock arrives later.
Symbol Meaning Which check uses it
TclkClock periodSetup only
tcqLaunch flop clock-to-Q delayBoth
tcombCombinational delay - max for setup, min for holdBoth
tsetupCapture flop setup requirementSetup only
tholdCapture flop hold requirementHold only
tskewCapture clock arrival - launch clock arrivalBoth, opposite signs
tuncClock 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.

Setup timing waveform showing data arrival time, required time and the resulting positive slack clk @ FF1 clk @ FF2 skew D @ FF2 arrival = t_cq + t_comb SLACK t_setup data must be stable before this window SETUP: DATA RACES THE NEXT CLOCK EDGE
Figure 7.2 - Slack is the gap between when the data actually settles and the latest moment it was allowed to. Positive slack means the path passes; the number is the margin.
Setup slack arrival = tcq + tcomb(max)
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:

Maximum operating frequency Tmin = tcq + tcomb(max) + tsetup + tunc - tskew
fmax = 1 / Tmin

Worked example

ParameterValueContribution
Tclk (200 MHz)5.00 ns+5.00
tskew (capture later)0.10 ns+0.10
tsetup0.15 ns-0.15
tunc (jitter + margin)0.05 ns-0.05
tcq0.25 ns-0.25
tcomb(max)3.80 ns-3.80
Setup slack +0.85 ns MET

And the ceiling this path imposes:

Tmin = 0.25 + 3.80 + 0.15 + 0.05 - 0.10 = 4.15 ns
fmax = 1 / 4.15 ns = 241 MHz
"What is the max frequency of this circuit?" The answer is always set by the single worst path in the design - the critical path. Every other path is irrelevant to fmax. That is why timing reports rank paths by slack and why optimisation effort concentrates almost entirely on the top of that list: improving anything else changes nothing.
Fixing a setup violationHow it helps
Lower the clock frequencyDirectly increases Tclk. Always works, always costs performance
Pipeline the logicSplits tcomb across two cycles. The real fix; costs latency and flops
Restructure the logicTurn a chain into a tree - Volume 02's entire subject
Upsize cells / better placementReduces tcomb at a power and area cost
Useful skewDeliberately 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.

Hold timing waveform showing fast data violating the hold window of the same clock edge, and the fix by adding buffer delay HOLD: DATA RACES THE SAME CLOCK EDGE t_hold clk @ FF1 clk @ FF2 D fast changes INSIDE the hold window - violation D buffered delay added - now safely after the window skew
Figure 7.3 - There is no "next edge" anywhere in this picture. The launch edge and the capture edge are the same edge, separated only by skew.
Hold slack arrival = tcq + tcomb(min)
required = tskew + thold + tunc
slackhold = tcq + tcomb(min) - thold - tskew - tunc
Look at what is missing: Tclk The clock period does not appear in the hold equation at all. Both the launch and the capture reference the same edge, so the period cancels. The consequences are severe:
  • 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
thold0.12 ns-0.12-0.12
tskew0.15 ns-0.15-0.15
tunc0.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:

slack = 0.12 + 0.25 - 0.12 - 0.15 - 0.03 = +0.07 ns   MET
Deliberately making a circuit slower to make it work Hold fixes are the one situation in digital design where you insert delay on purpose. Place-and-route tools do this automatically, littering short paths with buffers whose only job is to waste time. If you ever see a chain of buffers driving nothing but the next buffer, you are looking at a hold fix.

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.

Graph of setup slack and hold slack against clock skew, showing the window of skew where both checks pass BOTH CHECKS PASS skew slack 0 setup slack skew HELPS hold slack skew HURTS setup fails below here hold fails above here SKEW HELPS SETUP AND HURTS HOLD - SIMULTANEOUSLY
Figure 7.4 - Because the two lines slope in opposite directions, there is a band of acceptable skew, not a target value. Clock tree synthesis is the job of landing every flop pair inside that band.
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
Insertion delay is not skew Insertion delay (or clock latency) is how long the clock takes to get from the source to a flop - often a nanosecond or more on a large chip, and that is perfectly fine. What matters is the difference between two flops on a path. A clock tree with 2 ns of insertion delay and 20 ps of skew is excellent; one with 200 ps of insertion delay and 150 ps of skew is a problem.
On-chip variation, and why timing is signed off at corners Two identical gates on the same die are not identical - dopant fluctuation, temperature gradients and supply droop make them differ. STA handles this by derating: launch paths are slowed and capture paths sped up for setup, and the reverse for hold, so both checks assume the worst. And everything is repeated at multiple corners - slow process / low voltage / high temperature for setup, fast process / high voltage / low temperature for hold. A design is only signed off when it passes every corner.

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.

Multicycle path diagram showing the default capture edge failing, the relaxed capture edge passing, and the hold check needing to be pulled back A 2-CYCLE MULTICYCLE PATH clk edge 0 edge 1 edge 2 data slow path - takes 1.55 cycles default setup check - data has not arrived -setup 2 Hold is checked one edge before setup capture. With -setup 2 it drifts to edge 1 - demanding a full cycle of data delay. -hold 1 pulls it back to edge 0, where it belongs.
Figure 7.5 - The rule to memorise: for an N-cycle setup multicycle, always pair it with an (N - 1)-cycle hold multicycle. Forgetting the hold line is one of the most common constraint bugs in the industry.
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:

  1. A missing or wrong -hold multicycle. 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.
  2. 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.
  3. Wrong hold corner. Hold is checked at the fast corner. If the library or the corner setup is wrong, every path looks too fast.
  4. Clock uncertainty left at the pre-CTS value. A large pessimistic uncertainty is correct before CTS and wildly over-constraining after it.
  5. 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."

Where these equations meet a real toolchain Static timing analysis only checks the paths your constraints describe, so the equations above are worth exactly as much as the constraint file that feeds them. For the FPGA dialect - XDC ordering, generated clocks, I/O delay budgeting from a datasheet, and the 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.