Without constraints, STA produces nothing useful. The SDC (Synopsys Design Constraints) file is what you tell the tool about your design’s timing requirements: what the clocks are, how fast they run, how much time the I/O ports consume, and which paths are exceptions. Getting SDC right is as important as getting the RTL right — a wrong constraint is worse than a missing one, because it silently hides real violations.
SDC is a Tcl-based constraint format originally developed by Synopsys and now the universal standard across all EDA tools — PrimeTime, Tempus, DesignCompiler, Genus, Vivado, Quartus. An SDC file is a Tcl script that runs inside the STA tool. Every command in it queries or modifies the tool’s timing database.
The STA tool reads the SDC after loading the netlist and libraries. Without SDC, the tool has no idea what the clock period is, what the I/O interface timing budget looks like, or which paths between async domains should be excluded. The quality of your STA results is directly proportional to the accuracy of your SDC.
create_clockEvery STA session starts with defining clocks. create_clock tells the tool: “this port is a clock, it has this period, and here is its waveform.”
## Basic: 500 MHz clock on port 'clk' (period = 2 ns, 50% duty cycle)
create_clock -name clk_core -period 2.0 [get_ports clk]
## Custom waveform: rises at 0.0 ns, falls at 0.8 ns (40% duty cycle)
create_clock -name clk_io -period 4.0 -waveform {0.0 0.8} [get_ports clk_io]
## Generated clock: divided by 2 from clk_core
create_generated_clock -name clk_div2 \
-source [get_ports clk] \
-divide_by 2 \
[get_pins clk_div_reg/Q]
## PLL output clock (source is the PLL output pin)
create_clock -name clk_pll -period 1.0 [get_pins pll_inst/CLKOUT]
Create clocks on ports (primary inputs), not internal gates. The only exception is when a clock is genuinely generated inside the chip (PLL, clock divider). Creating clocks on arbitrary internal pins causes STA to analyse every downstream path as if a new clock source exists there, often producing thousands of false violations.
Paths from primary inputs and to primary outputs need external timing information. The chip does not exist in isolation — it connects to other chips with their own propagation delays.
## Input delay: external chip drives our input 1.2 ns after the clock edge
## This leaves (period - input_delay - setup_time) for internal logic
set_input_delay -max 1.2 -clock clk_core [get_ports data_in]
set_input_delay -min 0.4 -clock clk_core [get_ports data_in]
## Output delay: downstream chip needs data 0.8 ns before its clock edge
set_output_delay -max 0.8 -clock clk_core [get_ports data_out]
set_output_delay -min 0.1 -clock clk_core [get_ports data_out]
## Multiple ports at once
set_input_delay -max 1.5 -clock clk_core [get_ports {addr[*] data[*]}]
| Constraint | -max (setup) | -min (hold) |
|---|---|---|
| set_input_delay | Reduces time budget for internal logic (setup analysis) | Sets minimum data launch time (hold analysis) |
| set_output_delay | Reduces time budget before output must be valid (setup) | Sets minimum stable time after clock at output (hold) |
Real designs have paths that STA should not check with the default single-cycle constraint. These must be declared explicitly.
Removes a path from timing analysis entirely. Use when a path is physically impossible to activate, or when it crosses asynchronous domains where timing is handled by a synchroniser.
## Async clock domain crossing (handled by synchronizer — no timing relationship) set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] ## Reset path (async reset, not timing-critical in this direction) set_false_path -from [get_ports rst_n] ## Test mode only path (never active in functional mode) set_false_path -through [get_pins test_mux/S] ## Bidirectional: both A→B and B→A are false set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] set_false_path -from [get_clocks clk_b] -to [get_clocks clk_a]
Relaxes the timing requirement for a path to N clock cycles. Used when a computation legitimately takes more than one cycle (e.g. a divide unit, a floating-point multiplier that is only read every N cycles).
## Path through slow_unit gets 2 cycles for setup
set_multicycle_path 2 -setup -from [get_cells slow_unit/*] \
-to [get_cells result_reg/*]
## MANDATORY: adjust hold back by (N-1) cycles
## Without this, hold is checked at cycle 2 which is wrong
set_multicycle_path 1 -hold -from [get_cells slow_unit/*] \
-to [get_cells result_reg/*]
## 3-cycle path (e.g. FP multiplier pipeline read-out)
set_multicycle_path 3 -setup -from [get_cells fpmul/*] -to [get_cells accum_reg/*]
set_multicycle_path 2 -hold -from [get_cells fpmul/*] -to [get_cells accum_reg/*]
When you set -setup N, STA checks the path against the Nth cycle. But hold is still checked at cycle 1 by default, which is often pessimistic or wrong. The standard rule: -hold (N-1) to match. Forgetting this produces phantom hold violations that waste timing closure time.
When two clocks have no defined phase relationship (asynchronous), you must tell STA not to check paths between them. set_clock_groups is cleaner than per-path set_false_path for this.
## Two completely independent clock domains (e.g. core and USB) set_clock_groups -asynchronous \ -group [get_clocks clk_core] \ -group [get_clocks clk_usb] ## Three-domain chip: core, DDR, PCIe all asynchronous to each other set_clock_groups -asynchronous \ -group [get_clocks clk_core] \ -group [get_clocks clk_ddr] \ -group [get_clocks clk_pcie] ## Exclusive clocks (only one active at a time, e.g. functional vs scan) set_clock_groups -exclusive \ -group [get_clocks clk_func] \ -group [get_clocks clk_scan]
Real clocks are not perfectly periodic. Clock uncertainty models jitter (cycle-to-cycle variation), plus margin for process variation and any skew not yet accounted for by the clock tree.
## Pre-CTS: include estimated skew + jitter (larger value) set_clock_uncertainty -setup 0.2 [get_clocks clk_core] set_clock_uncertainty -hold 0.05 [get_clocks clk_core] ## Post-CTS: real tree delays are now in SPEF; only jitter remains set_clock_uncertainty -setup 0.08 [get_clocks clk_core] set_clock_uncertainty -hold 0.05 [get_clocks clk_core] ## Inter-clock uncertainty (different clocks from same PLL) set_clock_uncertainty -setup 0.1 \ -from [get_clocks clk_core] -to [get_clocks clk_div2]
## Set output load (capacitance of external pin in fF)
set_load 50 [get_ports data_out]
set_load 200 [get_ports {bus_out[*]}]
## Set drive strength of input ports (drive resistance in kohm)
set_driving_cell -lib_cell BUFX4 -library sc9_cln28hp \
[get_ports data_in]
## Limit max transition time on all output ports (ns)
set_max_transition 0.4 [get_ports {*}]
## Limit max fanout on internal nets
set_max_fanout 16 [current_design]
###############################################################
## SoC Top-Level SDC
## Technology: TSMC 28nm HP | Frequency: 500 MHz (clk_core)
###############################################################
## ── 1. Clock Definitions ──────────────────────────────────
create_clock -name clk_core -period 2.0 [get_ports clk_core]
create_clock -name clk_usb -period 16.667 [get_ports clk_usb]
create_clock -name clk_ddr -period 1.25 [get_ports clk_ddr]
create_generated_clock -name clk_div2 \
-source [get_ports clk_core] -divide_by 2 \
[get_pins clk_div_inst/Q]
## ── 2. Clock Groups (async domains) ───────────────────────
set_clock_groups -asynchronous \
-group {clk_core clk_div2} \
-group {clk_usb} \
-group {clk_ddr}
## ── 3. Clock Uncertainty ──────────────────────────────────
set_clock_uncertainty -setup 0.08 [get_clocks clk_core]
set_clock_uncertainty -hold 0.05 [get_clocks clk_core]
set_clock_uncertainty -setup 0.12 [get_clocks clk_usb]
set_clock_uncertainty -hold 0.05 [get_clocks clk_usb]
## ── 4. I/O Constraints ────────────────────────────────────
set_input_delay -max 0.6 -clock clk_core [get_ports {data_in[*]}]
set_input_delay -min 0.1 -clock clk_core [get_ports {data_in[*]}]
set_output_delay -max 0.5 -clock clk_core [get_ports {data_out[*]}]
set_output_delay -min 0.0 -clock clk_core [get_ports {data_out[*]}]
## ── 5. Timing Exceptions ──────────────────────────────────
## Async reset path
set_false_path -from [get_ports rst_n]
## Scan mode (only one mode active at a time)
set_clock_groups -exclusive \
-group [get_clocks clk_core] \
-group [get_clocks clk_scan]
## FP multiplier: 3-cycle multicycle path
set_multicycle_path 3 -setup \
-from [get_cells fpmul_inst/*] -to [get_cells result_reg/*]
set_multicycle_path 2 -hold \
-from [get_cells fpmul_inst/*] -to [get_cells result_reg/*]
## ── 6. Physical Constraints ───────────────────────────────
set_load 50 [get_ports {data_out[*]}]
set_driving_cell -lib_cell BUFX8 [get_ports {data_in[*]}]
set_max_transition 0.3 [current_design]
create_clockDefine primary clock from port or pin
create_generated_clockDefine derived clock (divide, multiply, invert)
set_clock_uncertaintyAdd jitter and skew margin
set_clock_latencyOverride clock network delay
set_input_delayTime consumed by external logic before our input
set_output_delayTime needed by external logic after our output
set_driving_cellDrive strength of input ports
set_loadCapacitive load on output ports
set_false_pathExclude path from all timing checks
set_multicycle_pathAllow N cycles for setup/hold
set_max_delayOverride max path delay directly
set_min_delayOverride min path delay directly
set_clock_groupsDefine async or exclusive clock groups
set_max_transitionLimit signal slew rate
set_max_fanoutLimit number of receivers per net
set_case_analysisTie signals to constant for analysis mode
If you write set_false_path -from clk_a -to clk_b and then later write set_multicycle_path 2 -from clk_a -to clk_b, the multicycle path wins for those specific paths. Order matters — exceptions follow last-writer-wins semantics within the same scope.
-waveform for non-50% duty cycles-max (setup) and -min (hold)-setup N with -hold (N-1)SDC (Synopsys Design Constraints) is a Tcl-based format for specifying timing constraints. It defines clocks, I/O delays, timing exceptions, and clock relationships. Without SDC, STA cannot produce meaningful results. SDC is the universal standard — accepted by PrimeTime, Tempus, DesignCompiler, Genus, Vivado, and Quartus.
It defines a clock in the design: the source port or pin, the period in nanoseconds, and the waveform (rise and fall edges). STA uses this to compute the required arrival time at every flip-flop driven by that clock. Without create_clock, paths involving that clock are unconstrained and ignored.
It removes a path from STA analysis. Used for paths between asynchronous clock domains, test-mode-only paths, or paths that are functionally impossible. A wrong false path declaration masks real violations — every false path must have a documented functional justification.
When you declare set_multicycle_path N -setup, STA checks setup at cycle N but still checks hold at the default (cycle 1). This produces incorrect hold analysis. The standard fix: always follow with set_multicycle_path (N-1) -hold on the same path specification.
set_clock_groups -asynchronous tells STA that two clock domains have no phase relationship — all paths between them are unconstrained (neither checked nor reported as violations). set_false_path excludes specific paths from analysis but still allows other paths between the same clocks to be checked. For truly async domains, set_clock_groups is cleaner and less error-prone.