STA — Constraints

SDC Constraints &
Timing Exceptions

An STA tool is only as good as the constraints you give it. Synopsys Design Constraints (SDC) is the industry-standard language for describing your design's timing environment — clock definitions, I/O delays, timing exceptions, and operating conditions. Without correct SDC, timing signoff is meaningless.

35 min read
Tcl / SDC
Interactive Builder

What is SDC and Why It Matters

SDC stands for Synopsys Design Constraints. It is a Tcl-based constraint format originally developed by Synopsys and now supported universally across synthesis (Design Compiler, Genus), STA (PrimeTime, Tempus), and physical design (Innovus, ICC2) tools. An SDC file tells the tool three essential things:

1. Clocking

Clock names, frequencies, waveform shapes, source latency, and relationships between clocks (synchronous, asynchronous, derived). Without this, the tool cannot perform any timing check.

2. I/O Timing

How much of the clock period is consumed externally before signals arrive at input ports, and how much must be reserved for external logic after output ports. This closes the timing loop at chip boundaries.

3. Timing Exceptions

Paths that should not be analyzed with default single-cycle constraints — false paths that are structurally impossible, multicycle paths where data takes N cycles by design, and case analysis to model specific operating modes.

Wrong SDC = wrong signoff. Overly tight constraints cause unnecessary design effort to fix phantom violations. Overly loose constraints mask real timing problems that escape to silicon. Getting SDC right is as important as fixing the violations it reveals.

Clock Definition — create_clock

create_clock is the most fundamental SDC command. It defines a clock object — its period, waveform, and the port or pin it originates from. Every sequential timing check in the design traces back to a clock defined here.

# Basic clock: 500 MHz (2 ns period), 50% duty cycle
create_clock -name CLK -period 2.0 -waveform {0 1.0} [get_ports clk]

# 250 MHz clock with 40% duty cycle (high from 0 to 0.8 ns)
create_clock -name CLK_SLOW -period 4.0 -waveform {0 1.6} [get_ports clk_slow]

# Clock defined on an internal pin (e.g., after a clock mux)
create_clock -name CLK_INT -period 2.0 [get_pins u_clk_mux/Y]

# Ideal clock (no uncertainty modeled — pre-CTS default)
create_clock -name SYS_CLK -period 1.0 [get_ports sys_clk]
set_clock_uncertainty 0.05 [get_clocks SYS_CLK]  ;# 50 ps jitter budget

-period

Clock period in nanoseconds. This defines the available timing budget for every register-to-register path in the clock domain. 1 / period = frequency. A 2 ns period means 500 MHz.

-waveform {rise fall}

The rising and falling edge times within one period. Default for 50% duty cycle is {0 half-period}. For non-50% duty cycles, specify explicitly. The tool uses both edges for both-edge-triggered and half-cycle path analysis.

For clocks derived internally — from a PLL, clock divider, or clock mux — use create_generated_clock:

# Clock divided by 2 inside the design
create_generated_clock -name CLK_DIV2 \
  -source [get_ports clk] \
  -divide_by 2 \
  [get_pins u_div/clk_out]

# Clock multiplied by PLL (2 GHz from 100 MHz reference)
create_generated_clock -name PLL_OUT \
  -source [get_ports ref_clk] \
  -multiply_by 20 \
  [get_pins u_pll/clk_out]

# Gated clock — same frequency, different enable
create_generated_clock -name GCLK \
  -source [get_clocks CLK] \
  -divide_by 1 \
  [get_pins u_icg/GCLK]
Why generated clocks matter: If you do not define a generated clock on a divided/muxed output, the STA tool loses track of the clock relationship and cannot correctly analyze CDC paths or apply the right period to downstream registers.

Clock Uncertainty, Latency, and Transition

Beyond defining the clock period, STA needs to know how much variation exists in clock arrival and what the clock signal quality looks like at each flip-flop.

# Clock uncertainty: accounts for jitter, PLL noise, margin
# Applied as a pessimistic deduction from setup slack
set_clock_uncertainty -setup 0.05 [get_clocks CLK]   ;# 50 ps setup uncertainty
set_clock_uncertainty -hold  0.02 [get_clocks CLK]   ;# 20 ps hold uncertainty

# Between two clocks (inter-clock uncertainty for CDC paths)
set_clock_uncertainty -from [get_clocks CLK_A] \
                      -to   [get_clocks CLK_B] 0.08

# Clock transition (slew) at the clock source pin
set_clock_transition 0.05 [get_clocks CLK]            ;# 50 ps transition time

# Source latency: PLL to clock port (off-chip delay)
set_clock_latency -source 0.3 [get_clocks CLK]

# Network latency: estimated clock tree delay (pre-CTS only)
set_clock_latency 0.8 [get_clocks CLK]
CommandWhat it modelsAffects
set_clock_uncertaintyJitter + skew marginSetup & Hold
set_clock_transitionClock slew at sourceAll paths
set_clock_latency -sourceOff-chip PLL/PCB delayAll paths
set_clock_latencyOn-chip tree estimate (pre-CTS)All paths
set_propagated_clocksUse actual annotated delays (post-CTS)All paths

I/O Timing — Input and Output Delays

A chip does not exist in isolation. Input signals arrive from external devices and output signals must meet the setup time of downstream chips. set_input_delay and set_output_delay model this external timing environment.

Input path budget = T_period − T_input_delay − T_su_internal
Output path budget = T_period − T_output_delay − T_cq − T_comb
# Input delay: external logic consumes 0.4 ns before reaching our input port
# Remaining budget for internal input-to-FF path = period - 0.4 ns
set_input_delay -max 0.4 -clock CLK [get_ports data_in]
set_input_delay -min 0.1 -clock CLK [get_ports data_in]   ;# for hold check

# Output delay: downstream chip needs data 0.3 ns before its clock edge
set_output_delay -max 0.3 -clock CLK [get_ports data_out]
set_output_delay -min -0.05 -clock CLK [get_ports data_out]  ;# for hold

# Multiple ports at once
set_input_delay -max 0.5 -clock CLK [get_ports {a b c ctrl}]

# Input referenced to a different clock (common for dual-clock interfaces)
set_input_delay -max 0.6 -clock CLK_B [get_ports ext_data]

-max vs -min

-max is used for setup analysis (worst-case late arrival). -min is used for hold analysis (best-case early arrival). Always specify both for complete timing closure. If only -max is given, hold analysis uses the same value — which may be over-optimistic.

The 40/60 rule of thumb

When external timing is unknown, a common starting point is: input delay = 40% of clock period, output delay = 40% of clock period. This leaves 60% of the period for internal combinational logic. Adjust based on actual board-level timing once the external components are specified.

False Paths — set_false_path

A false path is a timing path that physically exists in the netlist but can never be activated during functional operation. STA excludes it from timing analysis entirely. Applying false paths correctly avoids wasting design effort on violations that are structurally impossible.

When to use false paths

Asynchronous reset/set paths (reset deactivates long before normal operation begins), paths between asynchronous clock domains where the CDC handshake guarantees no timing relationship, configuration register paths written only during reset, and test mode paths never active during functional operation.

When NOT to use false paths

Paths that are difficult to time but functionally real — that is a constraint problem, not a false path. Using set_false_path to silence real violations is a common and dangerous mistake that allows silicon to ship with unanalyzed timing paths.

# Async reset path — reset deasserts long before functional operation
set_false_path -from [get_ports rst_n]

# Between two asynchronous clock domains (no timing relationship)
set_false_path -from [get_clocks CLK_A] -to [get_clocks CLK_B]

# Test mode mux path — only active during scan, not functional
set_false_path -through [get_pins u_test_mux/S]

# Specific point-to-point false path
set_false_path -from [get_cells u_cfg_reg/Q] \
               -to   [get_cells u_core/D]

# False path on output port driven by static logic
set_false_path -to [get_ports status_out]
Document every false path. A false path with no comment explaining why it is false is a maintenance hazard. If the design changes and the path becomes functionally active, the SDC will silently mask a real violation. Always comment: # reason: async reset, only deasserts during power-up

Multicycle Paths — set_multicycle_path

A multicycle path is a real, functional timing path where the design protocol guarantees that data takes N clock cycles to travel from launch to capture — intentionally. The capture flip-flop is not sampled on the very next clock edge after launch, so the default single-cycle constraint is unnecessarily tight.

Common examples: wide multipliers that take 2 cycles to compute, DSP pipelines with known latency, memory read paths where data is valid 2 cycles after address is applied.

Default setup check: data must arrive within 1 × T_period
2-cycle multicycle setup: data must arrive within 2 × T_period
Hold check shifts too: must add -hold adjustment to keep hold correct
# 2-cycle multicycle path — setup relaxed by 1 extra cycle
set_multicycle_path -setup 2 \
  -from [get_cells u_mult/A_reg] \
  -to   [get_cells u_mult/result_reg]

# IMPORTANT: hold must be adjusted too, or hold check becomes too tight
# -hold N means hold check uses (N-1) cycles before the setup edge
set_multicycle_path -hold 1 \
  -from [get_cells u_mult/A_reg] \
  -to   [get_cells u_mult/result_reg]

# 3-cycle path (e.g., memory read with 2-cycle read latency)
set_multicycle_path -setup 3 -from [get_cells u_mem] -to [get_cells u_dsp]
set_multicycle_path -hold  2 -from [get_cells u_mem] -to [get_cells u_dsp]

# Multicycle on a specific clock-domain crossing with known relationship
set_multicycle_path -setup 2 \
  -from [get_clocks CLK_FAST] \
  -to   [get_clocks CLK_SLOW]
The hold adjustment rule: When you set -setup N, always also set -hold (N-1). If you omit the hold adjustment, the STA tool moves the capture edge forward by N cycles for setup but keeps the hold check at cycle 0 — creating an over-pessimistic hold check that fires incorrectly. The pair must always be specified together.
Cycles (N)-setup value-hold valueSetup window
1 (default)101 × T_period
2212 × T_period
3323 × T_period
NNN−1N × T_period

Case Analysis and Operating Modes

set_case_analysis forces a specific logic value (0 or 1) onto a net or port for the purpose of timing analysis, without changing the design netlist. The tool propagates this constant through combinational logic and prunes timing paths that become unreachable — exactly as if that signal were tied to that constant in the actual silicon for a given mode.

# Functional mode: scan_en = 0, so scan paths are pruned
set_case_analysis 0 [get_ports scan_en]

# Test mode: scan_en = 1, so functional paths through scan muxes are pruned
set_case_analysis 1 [get_ports scan_en]

# Mode select: analyze only mode 0 paths (mux selects input A)
set_case_analysis 0 [get_ports mode_sel]

# Force a clock mux selection (CLK_A selected, CLK_B path not analyzed)
set_case_analysis 1 [get_pins u_clkmux/sel]

# Multiple scenarios — typically managed through tool scenario/mode framework
# Scenario 1: functional mode
set_case_analysis 0 [get_ports scan_en]
set_case_analysis 0 [get_ports test_mode]

# Scenario 2: scan shift mode
set_case_analysis 1 [get_ports scan_en]
set_case_analysis 1 [get_ports test_mode]
Multi-mode STA: Modern STA flows run multiple scenarios simultaneously — functional setup, functional hold, scan setup, scan hold, at-speed test, low-power mode. Each scenario has its own set of case analysis values, operating conditions, and exceptions. Tools like PrimeTime MSISTA and Tempus can run all scenarios in parallel.

Operating Conditions and Load Constraints

STA must know what process corner and environmental conditions to use when looking up cell delays from the library. set_operating_conditions selects the PVT corner. Additional constraints model the physical environment at I/O ports.

# Set operating conditions (PVT corner from library)
set_operating_conditions -max WORST  ;# SS corner for setup analysis
set_operating_conditions -min BEST   ;# FF corner for hold analysis

# Drive strength at input ports (models external driver strength)
# 0.0 = ideal (no external resistance); realistic: match external driver
set_drive 0 [get_ports clk]                          ;# ideal clock driver
set_driving_cell -cell BUF4 [get_ports data_in]      ;# external BUF4 drives input

# Output load: external capacitance at output port (pF)
set_load 0.1 [get_ports data_out]       ;# 0.1 pF external load

# Wire load model (for pre-layout parasitic estimation)
set_wire_load_model -name WireLoadModel [get_nets *]

# Max fanout constraint (synthesis + STA)
set_max_fanout 20 [current_design]

# Max transition (slew) constraint
set_max_transition 0.15 [current_design]   ;# 150 ps max slew everywhere

Complete SDC Template — Real Design

A production SDC file for a typical SoC block looks like this — combining all the commands above into a structured, commented file:

############################################################
# Design: uart_top  |  Tool: PrimeTime  |  Corner: SS/FF
############################################################

## ─── CLOCKS ─────────────────────────────────────────────
create_clock -name SYS_CLK -period 2.0 -waveform {0 1.0} \
  [get_ports sys_clk]

create_clock -name UART_CLK -period 8.68 -waveform {0 4.34} \
  [get_ports uart_clk]   ;# 115200 baud × 16 oversampling

create_generated_clock -name CLK_DIV -source SYS_CLK \
  -divide_by 4 [get_pins u_clkdiv/clk_out]

## ─── CLOCK QUALITY ───────────────────────────────────────
set_clock_uncertainty -setup 0.05 [get_clocks SYS_CLK]
set_clock_uncertainty -hold  0.02 [get_clocks SYS_CLK]
set_clock_latency -source 0.25 [get_clocks SYS_CLK]
set_clock_transition 0.04 [get_clocks SYS_CLK]

## ─── ASYNC CLOCK DOMAINS ─────────────────────────────────
set_clock_groups -asynchronous \
  -group {SYS_CLK CLK_DIV} \
  -group {UART_CLK}

## ─── I/O CONSTRAINTS ─────────────────────────────────────
set_input_delay  -max 0.6 -clock SYS_CLK [get_ports {data_in[*] ctrl}]
set_input_delay  -min 0.1 -clock SYS_CLK [get_ports {data_in[*] ctrl}]
set_output_delay -max 0.4 -clock SYS_CLK [get_ports {data_out[*]}]
set_output_delay -min 0.0 -clock SYS_CLK [get_ports {data_out[*]}]

## ─── RESETS (async — deassert before any functional operation) ──
set_false_path -from [get_ports rst_n]

## ─── MULTICYCLE PATHS ────────────────────────────────────
# 16-bit multiplier takes 2 cycles
set_multicycle_path -setup 2 -from [get_cells u_mult/*] \
                              -to   [get_cells u_accum/*]
set_multicycle_path -hold  1 -from [get_cells u_mult/*] \
                              -to   [get_cells u_accum/*]

## ─── OPERATING CONDITIONS ────────────────────────────────
set_operating_conditions -max WORST -min BEST
set_load 0.05 [all_outputs]
set_driving_cell -cell BUF4 [all_inputs]

## ─── CASE ANALYSIS ───────────────────────────────────────
set_case_analysis 0 [get_ports scan_en]   ;# functional mode
Interactive Lab — SDC Constraint Builder
Fill in your design parameters and generate a starter SDC snippet instantly.
Clock Settings
I/O & Exceptions
Generated SDC
Click any field to generate SDC...

Frequently Asked Questions

Without an SDC file, the STA tool has no knowledge of your clock frequency, I/O timing requirements, or timing exceptions. It will either report all paths as unconstrained (no violations found — falsely green), or use default assumptions that have no relationship to your actual design requirements. Synthesis tools without SDC will optimize for area rather than timing, often producing slow designs. A missing or wrong SDC is one of the most common reasons chips fail first silicon.
A false path is a path that is never functionally active — the design protocol or structural topology makes it impossible for the path to propagate data in normal operation. STA skips it entirely. A multicycle path is functionally real — data does propagate, but the protocol guarantees the capture FF samples it N cycles later. STA still checks it, just with a relaxed N-cycle budget. Misusing false paths to silence real multicycle violations is a sign-off error that can cause silicon failures.
When you set -setup N, the tool moves the setup capture edge N cycles forward, giving the data more time. But by default, the hold check is still performed at cycle 0 (one cycle before the setup edge). If you set -setup 2 without -hold 1, the hold check remains extremely pessimistic — it checks that data does not change before cycle 0, which is trivially violated on almost any realistic path. The -hold (N-1) moves the hold check to the cycle immediately before the actual capture edge, which is the correct reference point.
set_clock_groups -asynchronous tells the tool that the specified clock groups have no timing relationship — they are from independent oscillators or PLLs with no guaranteed phase relationship. The tool will not analyze inter-group paths with a timing check. This is different from set_false_path -from CLK_A -to CLK_B — clock groups applies symmetrically in both directions and is the recommended approach for asynchronous CDC boundaries. The CDC handshake logic itself must be verified separately using CDC analysis tools.
A clock mux passes one of several input clocks to its output based on a select signal. You need to: (1) define all input clocks with create_clock, (2) define a generated clock on the mux output with create_generated_clock referencing the appropriate source, and (3) use set_case_analysis to fix the select input for each analysis scenario. You may also use set_clock_groups -physically_exclusive between the clocks driving the mux inputs, since only one can be active at a time — this prevents false inter-clock paths through the mux from being analyzed.
-max specifies the latest the input signal can arrive (worst-case late) — used for setup analysis. The tool assumes the data consumes this much of the clock period externally, leaving less budget for internal combinational logic. -min specifies the earliest the input can arrive (best-case early) — used for hold analysis. If the external data arrives very early (small min delay), it might change before the internal FF's hold window closes. Always specify both for complete timing closure. A common mistake is specifying only -max and assuming hold is automatically correct.

Explore Further

← Clock Tree & Skew