The Language of Timing: Speaking to Your STA Tool

You understand timing fundamentals and can launch OpenSTA to analyze designs. But here's the catch: without proper constraints, your timing analysis is meaningless. An unconstrained design will always "pass" timing—because the tool has no requirements to check against. Constraints transform timing analysis from a theoretical exercise into a practical verification that ensures your chip will work at the target frequency.

SDC (Synopsys Design Constraints) files are how you communicate your design requirements to the timing analyzer. They specify clocks, define I/O timing relationships, set design rules, and declare timing exceptions. Writing effective SDC requires understanding not just the syntax, but the underlying timing relationships you're modeling. This module teaches you to create comprehensive, correct SDC files that enable accurate timing verification.

Creating SDC Files: Structure and Best Practices

SDC File Basics

SDC is a Tcl-based format, which means:

  • Commands follow Tcl syntax rules
  • You can use Tcl variables and programming constructs
  • Comments start with #
  • Line continuations use backslash \
  • Lists are enclosed in braces {}

Basic SDC File Structure

# design.sdc - Timing constraints for my_design
# Author: Your Name
# Date: 2025-01-15

# ============================================================
# Clock Definitions
# ============================================================
create_clock -name sys_clk -period 10.0 [get_ports clk]

# ============================================================
# Clock Characteristics
# ============================================================
set_clock_uncertainty 0.2 [get_clocks sys_clk]
set_clock_latency 1.0 [get_clocks sys_clk]

# ============================================================
# Input Constraints
# ============================================================
set_input_delay -clock sys_clk -max 2.0 [all_inputs]
set_input_delay -clock sys_clk -min 0.5 [all_inputs]

# ============================================================
# Output Constraints
# ============================================================
set_output_delay -clock sys_clk -max 1.5 [all_outputs]
set_output_delay -clock sys_clk -min 0.3 [all_outputs]

# ============================================================
# Design Rules
# ============================================================
set_max_transition 0.5 [current_design]
set_max_capacitance 0.2 [all_outputs]

# ============================================================
# Timing Exceptions
# ============================================================
# (false paths, multicycle paths, etc.)

SDC Organization Best Practices

1. Use Clear Section Headers

# ============================================================
# Section Name
# ============================================================

Makes the file scannable and maintainable.

2. Comment Your Constraints

# System clock: 100 MHz (10ns period)
# Includes 200ps uncertainty for jitter and skew
create_clock -name sys_clk -period 10.0 [get_ports clk]
set_clock_uncertainty 0.2 [get_clocks sys_clk]

Future you (or your colleagues) will appreciate the documentation.

3. Use Variables for Maintainability

# Design parameters
set CLK_PERIOD 10.0
set CLK_UNCERTAINTY 0.2
set INPUT_DELAY_MAX [expr {$CLK_PERIOD * 0.2}]
set OUTPUT_DELAY_MAX [expr {$CLK_PERIOD * 0.15}]

# Apply constraints using variables
create_clock -name sys_clk -period $CLK_PERIOD [get_ports clk]
set_clock_uncertainty $CLK_UNCERTAINTY [get_clocks sys_clk]
set_input_delay -clock sys_clk -max $INPUT_DELAY_MAX [all_inputs]

Changing frequency now requires updating one variable.

4. Separate Concerns into Multiple Files

# master.sdc
source clocks.sdc
source io_constraints.sdc
source exceptions.sdc
source design_rules.sdc

Modular organization for complex designs.

5. Version Control Your SDC

# design.sdc
# Version 1.2
# Modified: 2025-01-15 - Added multicycle path for divider
# Modified: 2025-01-10 - Tightened clock uncertainty

Track changes over time.

The create_clock Command: Defining Your Timing Reference

Basic Clock Creation

Syntax

create_clock -name <clock_name> -period <period> [get_ports <port_name>]

Simple Example

# 100 MHz clock (10ns period)
create_clock -name sys_clk -period 10.0 [get_ports clk]

What This Does:

  • Creates a clock object named sys_clk
  • Period is 10.0ns (implies 100 MHz frequency)
  • Source is the port named clk
  • Default waveform: rises at 0ns, falls at 5ns (50% duty cycle)

Specifying Waveforms

Default Waveform

create_clock -name clk -period 10.0 [get_ports clk]
# Equivalent to:
# create_clock -name clk -period 10.0 -waveform {0 5} [get_ports clk]

Custom Waveform (Non-50% Duty Cycle)

# 40% duty cycle: high for 4ns, low for 6ns
create_clock -name clk_40 -period 10.0 -waveform {0 4} [get_ports clk]

# 30% duty cycle: high for 3ns, low for 7ns
create_clock -name clk_30 -period 10.0 -waveform {0 3} [get_ports clk]

Inverted Clock (Starts Low)

# Starts low, rises at 5ns, falls at 10ns (one period later)
create_clock -name clk_inv -period 10.0 -waveform {5 10} [get_ports clk_n]

Multiple Independent Clocks

Asynchronous Clocks

# Fast clock for processor
create_clock -name cpu_clk -period 2.0 [get_ports clk_cpu]

# Slow clock for peripherals
create_clock -name peri_clk -period 50.0 [get_ports clk_peri]

# Declare them asynchronous (no timing relationship)
set_clock_groups -asynchronous \
    -group {cpu_clk} \
    -group {peri_clk}

Without set_clock_groups, the tool might try to analyze paths between these unrelated clocks.

Generated Clocks

Clock Divider

# Primary clock
create_clock -name main_clk -period 10.0 [get_ports clk]

# Divided-by-2 clock
create_generated_clock -name div2_clk \
    -source [get_ports clk] \
    -divide_by 2 \
    [get_pins divider/Q]

Clock Multiplier (PLL)

# Input reference clock
create_clock -name ref_clk -period 20.0 [get_ports ref_clk]

# PLL output (multiply by 4)
create_generated_clock -name pll_clk \
    -source [get_ports ref_clk] \
    -multiply_by 4 \
    [get_pins pll/clk_out]

Generated Clock from Internal Pin

# Clock gating cell output
create_generated_clock -name gated_clk \
    -source [get_ports clk] \
    -divide_by 1 \
    [get_pins clk_gate/gated_clk]

Clock Properties

Clock Uncertainty

# Jitter and skew
set_clock_uncertainty -setup 0.2 [get_clocks sys_clk]
set_clock_uncertainty -hold 0.1 [get_clocks sys_clk]

Clock Latency

# Source latency (before clock tree)
set_clock_latency -source 0.5 [get_clocks sys_clk]

# Network latency (clock tree delay)
set_clock_latency 1.0 [get_clocks sys_clk]

Clock Transition

# Clock edge slew rate
set_clock_transition 0.1 [get_clocks sys_clk]

Virtual Clocks

Use Case: I/O timing when external clock isn't in your design

# Virtual clock for external system
create_clock -name ext_sys_clk -period 8.0

# Input timing relative to virtual clock
set_input_delay -clock ext_sys_clk -max 2.0 [get_ports data_in]

# Output timing relative to virtual clock
set_output_delay -clock ext_sys_clk -max 1.5 [get_ports data_out]

The virtual clock exists only for constraint purposes—there's no physical pin.

Input and Output Delays: Modeling External Timing

Understanding I/O Delays

Your chip interfaces with the outside world—other chips, memories, sensors. I/O delays model the timing relationship between your internal clock and external signals.

Input Delays: set_input_delay

Syntax

set_input_delay -clock <clock_name> [-max|-min] <delay> [get_ports <port_pattern>]

Basic Example

# External device launches data with 3ns delay
set_input_delay -clock sys_clk -max 3.0 [get_ports data_in]

# Minimum delay (for hold check)
set_input_delay -clock sys_clk -min 1.0 [get_ports data_in]

What This Means:

  • Data arrives 3.0ns after the clock edge (worst case)
  • Data arrives 1.0ns after the clock edge (best case)

Setup Check Impact

Available time for internal logic = Period - Input_delay_max - Setup_time
Example: 10ns - 3ns - 0.1ns = 6.9ns

Hold Check Impact

Required internal delay > Input_delay_min + Hold_time
Example: Path must be > 1.0ns + 0.05ns = 1.05ns

Applying to Multiple Ports

# All input data ports
set_input_delay -clock sys_clk -max 2.5 [get_ports data_in*]

# Specific list of ports
set_input_delay -clock sys_clk -max 2.0 [get_ports {addr[0] addr[1] addr[2]}]

# All inputs except clock
set_input_delay -clock sys_clk -max 2.0 [all_inputs]

Different Delays for Rising and Falling

# Rising edge
set_input_delay -clock sys_clk -max 2.0 [get_ports data]

# Falling edge
set_input_delay -clock sys_clk -clock_fall -max 2.5 [get_ports data]

Output Delays: set_output_delay

Syntax

set_output_delay -clock <clock_name> [-max|-min] <delay> [get_ports <port_pattern>]

Basic Example

# External device needs data 1.5ns before its clock edge
set_output_delay -clock sys_clk -max 1.5 [get_ports data_out]

# Minimum output delay (for hold)
set_output_delay -clock sys_clk -min 0.3 [get_ports data_out]

What This Means:

  • Data must be stable 1.5ns before the external device's clock edge
  • Data can change 0.3ns after the external device's clock edge

Setup Check Impact

Available internal time = Period - Output_delay_max
Example: 10ns - 1.5ns = 8.5ns

Practical Example: Memory Interface

# Memory read timing
# Memory Tco = 6ns, board trace = 1ns
set_input_delay -clock sys_clk -max 7.0 [get_ports mem_data[*]]

# Memory write timing  
# Memory Tsu = 2ns, board trace = 0.8ns
set_output_delay -clock sys_clk -max 2.8 [get_ports mem_addr[*]]
set_output_delay -clock sys_clk -max 2.8 [get_ports mem_data[*]]

Add vs. Set: Cumulative Delays

Set (Default): Replaces previous value

set_input_delay -clock clk -max 2.0 [get_ports data]
set_input_delay -clock clk -max 3.0 [get_ports data]  # Overrides to 3.0

Add: Accumulates

set_input_delay -clock clk -max 2.0 [get_ports data]
set_input_delay -clock clk -max -add 1.0 [get_ports data]  # Total = 3.0

Load and Drive Specifications

Output Load: set_load

Purpose: Model capacitive load on output ports

Syntax

set_load <capacitance> [get_ports <port_pattern>]

Examples

# 50 fF load (typical board trace + input cap)
set_load 0.05 [get_ports data_out[*]]

# Different loads for different outputs
set_load 0.03 [get_ports control_signals[*]]
set_load 0.08 [get_ports high_cap_outputs[*]]

# Default load for all outputs
set_load 0.05 [all_outputs]

Units: Typically picofarads (pF) or femtofarads (fF) depending on Liberty file units

Impact on Timing:

  • Larger loads → slower transitions → longer delays
  • Synthesis/STA use this for accurate output delay calculation

Input Drive: set_driving_cell

Purpose: Model the strength of the external driver

Syntax

set_driving_cell -lib_cell <cell_name> [get_ports <port_pattern>]

Examples

# External chip drives with a medium-strength buffer
set_driving_cell -lib_cell BUF_X2 [get_ports data_in[*]]

# Weak driver (high impedance input)
set_driving_cell -lib_cell BUF_X1 [get_ports slow_inputs[*]]

# Strong driver (low impedance)
set_driving_cell -lib_cell BUF_X8 [get_ports fast_inputs[*]]

What This Does:

  • Uses the Liberty cell's output characteristics
  • Determines input transition time
  • Affects downstream timing calculations

Alternative: Direct Transition Time

# Specify input transition directly
set_input_transition 0.2 [get_ports data_in[*]]

Use when you know transition time but not the driving cell.

Load and Drive: Complete I/O Example

# ============================================================
# Input Constraints
# ============================================================
# External CPU drives our inputs with strong buffers
set_driving_cell -lib_cell BUF_X4 [all_inputs]

# Input timing from CPU datasheet
set_input_delay -clock sys_clk -max 3.0 [get_ports data_in[*]]
set_input_delay -clock sys_clk -min 1.0 [get_ports data_in[*]]

# ============================================================
# Output Constraints
# ============================================================
# Drive external SRAM (moderate capacitance)
set_load 0.06 [get_ports mem_addr[*]]
set_load 0.08 [get_ports mem_data[*]]

# Output timing per SRAM datasheet
set_output_delay -clock sys_clk -max 2.5 [get_ports mem_addr[*]]
set_output_delay -clock sys_clk -max 2.5 [get_ports mem_data[*]]

Max and Min Delay Constraints

Point-to-Point Timing: set_max_delay and set_min_delay

Sometimes you need to constrain specific paths without a clock relationship.

Maximum Delay: set_max_delay

Syntax

set_max_delay <delay> -from <from_list> -to <to_list>

Use Cases:

1. Asynchronous Paths

# Async reset distribution: must arrive within 5ns
set_max_delay 5.0 -from [get_ports async_rst] -to [all_registers]

2. Combinational Paths (No Clocks)

# Pure combinational logic from input to output
set_max_delay 3.0 -from [get_ports combo_in] -to [get_ports combo_out]

3. Cross-Domain Paths (Instead of False Path)

# Allow 10ns for synchronizer settling
set_max_delay 10.0 -from [get_clocks clk_a] -to [get_clocks clk_b]

4. Override Clock-Based Check

# Tighter requirement than clock-based
set_max_delay 2.0 -from [get_pins launch_ff/Q] -to [get_pins capture_ff/D]

Minimum Delay: set_min_delay

Syntax

set_min_delay <delay> -from <from_list> -to <to_list>

Use Cases:

1. Hold Margin on Critical Paths

# Ensure at least 0.5ns delay (prevent hold violations)
set_min_delay 0.5 -from [get_ports fast_input] -to [get_registers]

2. Clock Domain Crossing Hold

# Minimum delay for synchronizer
set_min_delay 0.2 -from [get_clocks clk_a] -to [get_clocks clk_b]

Combining Max and Min Delays

# Constrain path to be between 1ns and 5ns
set_min_delay 1.0 -from [get_ports in] -to [get_ports out]
set_max_delay 5.0 -from [get_ports in] -to [get_ports out]

Both constraints must be satisfied.

Timing Exceptions: False Paths and Multicycle Paths

False Paths: set_false_path

Purpose: Disable timing analysis on paths that will never propagate data

Syntax

set_false_path -from <from_list> -to <to_list>

Common Scenarios:

1. Asynchronous Clock Domain Crossing

# No timing relationship between clock domains
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

2. Configuration/Test Paths

# Config registers don't change during normal operation
set_false_path -from [get_ports config_mode] -to [all_registers]

# Test scan chain
set_false_path -from [get_ports scan_enable] -to [all_registers]

3. Through Specific Pins

# Ignore paths through debug multiplexer
set_false_path -through [get_pins debug_mux/debug_select]

4. Reset Paths

# Async reset doesn't need timing check
set_false_path -from [get_ports rst_n] -to [all_registers]

From/To/Through Options:

# From specific register
set_false_path -from [get_pins state_reg[*]/Q]

# To specific register
set_false_path -to [get_pins error_reg/D]

# Through specific net
set_false_path -through [get_nets debug_enable]

# Combinations
set_false_path -from [get_clocks clk_a] \
               -through [get_pins sync_ff/Q] \
               -to [get_clocks clk_b]

Multicycle Paths: set_multicycle_path

Purpose: Relax timing requirements for paths that take multiple clock cycles

Syntax

set_multicycle_path <num_cycles> -setup|-hold -from <from> -to <to>

Basic Example: 2-Cycle Path

# Path can take 2 clock cycles
set_multicycle_path 2 -setup \
    -from [get_pins launch_ff/Q] \
    -to [get_pins capture_ff/D]

# Adjust hold check (1 cycle, not 2)
set_multicycle_path 1 -hold \
    -from [get_pins launch_ff/Q] \
    -to [get_pins capture_ff/D]

Why Hold is N-1?

  • Setup checks at cycle N (e.g., cycle 2)
  • Hold checks at cycle N-1 (e.g., cycle 1)
  • Prevents overly relaxed hold check

Common Multicycle Scenarios:

1. Slow Functional Units

# 32-cycle integer divider
set_multicycle_path 32 -setup -from [get_pins divider/*] \
                               -to [get_pins result_reg/D]
set_multicycle_path 31 -hold  -from [get_pins divider/*] \
                               -to [get_pins result_reg/D]

2. Multi-Cycle State Machine

# State machine takes 4 cycles per state
set_multicycle_path 4 -setup -from [get_pins state_reg[*]/Q] \
                              -to [get_pins state_reg[*]/D]
set_multicycle_path 3 -hold  -from [get_pins state_reg[*]/Q] \
                              -to [get_pins state_reg[*]/D]

3. Data Valid Enable Signal

# Data updates every 3 cycles (controlled by enable)
set_multicycle_path 3 -setup -from [get_pins data_reg[*]/Q]
set_multicycle_path 2 -hold  -from [get_pins data_reg[*]/Q]

Setup and Hold Relationship

Setup cycles: N
Hold cycles: N-1

Example:
set_multicycle_path 5 -setup ...
set_multicycle_path 4 -hold ...

Always specify both setup and hold for multicycle paths.

Complete SDC Example: Putting It All Together

# ================================================================
# SDC File: processor_core.sdc
# Design: Simple processor core
# Author: Design Team
# Date: 2025-01-15
# ================================================================

# ================================================================
# Design Parameters
# ================================================================
set CPU_FREQ 200.0  ;# MHz
set CPU_PERIOD [expr {1000.0 / $CPU_FREQ}]  ;# 5.0 ns

set MEM_FREQ 100.0  ;# MHz
set MEM_PERIOD [expr {1000.0 / $MEM_FREQ}]  ;# 10.0 ns

# ================================================================
# Clock Definitions
# ================================================================
# CPU clock
create_clock -name cpu_clk -period $CPU_PERIOD [get_ports clk_cpu]
set_clock_uncertainty -setup 0.3 [get_clocks cpu_clk]
set_clock_uncertainty -hold 0.15 [get_clocks cpu_clk]
set_clock_latency 1.0 [get_clocks cpu_clk]

# Memory clock (asynchronous to CPU)
create_clock -name mem_clk -period $MEM_PERIOD [get_ports clk_mem]
set_clock_uncertainty -setup 0.2 [get_clocks mem_clk]
set_clock_uncertainty -hold 0.1 [get_clocks mem_clk]

# Debug clock (test mode only)
create_clock -name dbg_clk -period 50.0 [get_ports clk_debug]

# Declare clock groups
set_clock_groups -asynchronous \
    -group {cpu_clk} \
    -group {mem_clk} \
    -group {dbg_clk}

# ================================================================
# Input Constraints
# ================================================================
# Instruction memory interface
set_driving_cell -lib_cell BUF_X4 [get_ports imem_data[*]]
set_input_delay -clock cpu_clk -max 2.0 [get_ports imem_data[*]]
set_input_delay -clock cpu_clk -min 0.5 [get_ports imem_data[*]]

# Data memory interface
set_driving_cell -lib_cell BUF_X4 [get_ports dmem_data[*]]
set_input_delay -clock mem_clk -max 4.0 [get_ports dmem_data[*]]
set_input_delay -clock mem_clk -min 1.0 [get_ports dmem_data[*]]

# Interrupt inputs (async)
set_driving_cell -lib_cell BUF_X2 [get_ports irq[*]]
set_input_delay -clock cpu_clk -max 1.0 [get_ports irq[*]]

# ================================================================
# Output Constraints
# ================================================================
# Instruction memory address
set_load 0.05 [get_ports imem_addr[*]]
set_output_delay -clock cpu_clk -max 1.5 [get_ports imem_addr[*]]
set_output_delay -clock cpu_clk -min 0.3 [get_ports imem_addr[*]]

# Data memory interface
set_load 0.08 [get_ports dmem_addr[*]]
set_load 0.08 [get_ports dmem_data[*]]
set_output_delay -clock mem_clk -max 3.0 [get_ports dmem_addr[*]]
set_output_delay -clock mem_clk -max 3.0 [get_ports dmem_data[*]]

# ================================================================
# Design Rules
# ================================================================
set_max_transition 0.5 [current_design]
set_max_capacitance 0.2 [all_outputs]
set_max_fanout 20 [all_inputs]

# ================================================================
# Timing Exceptions
# ================================================================
# Reset path (async)
set_false_path -from [get_ports rst_n]

# Clock domain crossing from CPU to MEM
set_false_path -from [get_clocks cpu_clk] -to [get_clocks mem_clk]
set_false_path -from [get_clocks mem_clk] -to [get_clocks cpu_clk]

# Debug mode paths
set_false_path -from [get_ports debug_enable]
set_false_path -through [get_pins debug_mux/sel]

# Integer divider (32 cycles)
set_multicycle_path 32 -setup \
    -from [get_pins alu/divider/*] \
    -to [get_pins alu/result_reg[*]/D]
set_multicycle_path 31 -hold \
    -from [get_pins alu/divider/*] \
    -to [get_pins alu/result_reg[*]/D]

# ================================================================
# End of SDC File
# ================================================================

Best Practices and Common Pitfalls

Best Practices

1. Constrain Everything

# Check for unconstrained paths
check_timing -verbose

Every path should have a constraint.

2. Be Pessimistic (But Realistic)

# Add margin for uncertainty
set CLOCK_UNCERTAINTY 0.2  ;# Conservative
set_clock_uncertainty $CLOCK_UNCERTAINTY [all_clocks]

3. Document Your Assumptions

# External DRAM timing from datasheet page 23:
#   Tco = 6ns, Tsu = 2ns, board trace = 1ns
set_input_delay -clock sys_clk -max 7.0 [get_ports dram_data[*]]

4. Use Variables for Related Values

set CLK_PERIOD 10.0
set INPUT_DELAY [expr {$CLK_PERIOD * 0.2}]

5. Validate Your Constraints

report_clocks
report_port -verbose [all_inputs]
check_timing

Common Pitfalls

Pitfall 1: Clock Not Reaching Sequential Elements

# Clock defined but not connected
create_clock -period 10.0 [get_ports clk]
# But the port 'clk' doesn't drive any flip-flops!

Solution: Verify clock connectivity with report_clock_network.

Pitfall 2: Missing Hold Constraints for Multicycle

# BAD: Only setup specified
set_multicycle_path 3 -setup -from [get_pins a] -to [get_pins b]

Solution: Always specify hold (N-1 cycles).

Pitfall 3: Over-Constraining with False Paths

# BAD: Hiding real timing issues
set_false_path -from [get_pins critical_ff/Q]

Solution: Only use false paths for genuinely false timing.

Pitfall 4: Wrong Clock Reference

# Wrong clock name (typo)
set_input_delay -clock sys_clok -max 2.0 [get_ports data]
# Error: clock 'sys_clok' not found

Solution: Verify clock names with report_clocks.

Pitfall 5: Forgetting Virtual Clocks for I/O

# External system clock not in design
set_input_delay -clock external_clk -max 2.0 [get_ports data]
# Error: clock 'external_clk' not found

Solution: Create virtual clock first.

Summary

SDC constraint definition is essential for accurate timing analysis:

  • SDC structure: Use clear organization, comments, and variables
  • create_clock: Defines timing reference with period and waveform
  • set_input_delay/set_output_delay: Model external system timing
  • set_load/set_driving_cell: Specify I/O electrical characteristics
  • set_max_delay/set_min_delay: Point-to-point timing constraints
  • set_false_path: Disable analysis of impossible paths
  • set_multicycle_path: Relax timing for multi-cycle operations

Key skills you've developed:

  • Creating well-organized, maintainable SDC files
  • Defining clocks with proper characteristics
  • Modeling I/O timing accurately
  • Using timing exceptions correctly
  • Avoiding common constraint pitfalls

With comprehensive SDC constraints, your timing analysis becomes meaningful and accurate. In the next module, you'll run actual timing analysis with these constraints and interpret the results. Proper constraints are the foundation—without them, timing analysis is just an academic exercise. With them, you can confidently verify your design will work in silicon.

Did you find what you were looking for?

Quiz

No quiz questions available for this article.