HomeSTA CourseDay 8
DAY 8 · ADVANCED STA

Timing Exceptions — False Paths, Multicycle, Max/Min Delay

By EcrioniX · Updated June 2026

Real designs have paths that the default single-cycle STA check doesn’t apply to. A path that can never be sensitised. A computation that deliberately takes two clock cycles. A CDC path between async domains. Timing exceptions are how you tell STA about these special cases — and incorrect exceptions are how real silicon failures get shipped by mistake.

1. The exception hierarchy

STA applies timing checks based on a priority hierarchy. When multiple exceptions apply to the same path, the tool applies the highest-priority one. Understanding this hierarchy prevents silent constraint overrides:

  1. set_false_path — highest priority; removes path from all analysis
  2. set_max_delay / set_min_delay — overrides the clock-period derived requirement
  3. set_multicycle_path — adjusts the number of cycles for setup/hold check
  4. Default single-cycle check — lowest priority; applies when no exception is present

The most specific exception wins. An exception with -through overrides one with only -from. An exception with both -from and -to overrides one with only -from.

2. set_false_path — deep dive

A false path is a path that exists structurally in the netlist but is never functionally exercised in a way that violates timing. There are two legitimate categories:

set_false_path — correct and incorrect usage
## ✅ CORRECT: Async reset path (not timing-critical in any real scenario) set_false_path -from [get_ports rst_n] ## ✅ CORRECT: CDC crossing handled by synchronizer ## The synchronizer guarantees safe capture; STA timing on the raw crossing is irrelevant 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] ## ✅ CORRECT: Test-mode only MUX bypass path ## When test_mode=0 (functional), this path is never active set_case_analysis 0 [get_ports test_mode] set_false_path -through [get_pins test_mux/B] ## ❌ WRONG: Removing a real path because it's hard to fix ## This hides a real silicon failure! set_false_path -from FF_A/Q -to FF_B/D ## ❌ WRONG: False path on a clock domain that has real synchronous paths set_false_path -from [get_clocks clk_core] ## This removes ALL paths from clk_core — including real setup/hold paths!

Every false path must have a written functional justification

A false path declaration in production SDC should have a comment explaining exactly why the path is false. If you can’t write the justification in one sentence, the false path may be incorrect. Common mistakes: (1) applying set_false_path to avoid fixing a real violation; (2) bidirectional CDC paths where only one direction is actually async; (3) over-broad specifications that catch unintended paths.

3. set_false_path vs set_clock_groups

For asynchronous CDC, two commands can achieve similar results but with important differences:

Aspectset_false_path -from clk_a -to clk_bset_clock_groups -asynchronous
What it doesMarks specific one-directional paths as falseMarks the entire clock relationship as having no timing requirement (bidirectional, all paths)
CoverageOnly A→B; B→A needs a separate commandBoth directions, all paths between all clocks in the groups
Generated clocksMay not cover generated clocks derived from clk_aApplies to all clocks in the group including generated ones
ReportsPath appears in report_exceptions as false pathPaths are excluded from analysis entirely; do not appear in reports
When to useWhen only one direction is false, or a specific subset of pathsWhen two clock domains have absolutely no timing relationship (preferred for CDC)
set_clock_groups vs set_false_path for CDC
## Preferred: set_clock_groups -asynchronous for true async domains ## Covers all derived clocks, both directions, cleaner SDC set_clock_groups -asynchronous \ -group {clk_core clk_core_div2} \ -group {clk_usb} \ -group {clk_ddr} ## vs. set_false_path (more verbose, error-prone for large designs) set_false_path -from [get_clocks clk_core] -to [get_clocks clk_usb] set_false_path -from [get_clocks clk_usb] -to [get_clocks clk_core] set_false_path -from [get_clocks clk_core] -to [get_clocks clk_ddr] set_false_path -from [get_clocks clk_ddr] -to [get_clocks clk_core] ## ... (many more lines for each direction and generated clock combination) ## Use set_false_path when ONLY ONE direction needs to be false: ## e.g., clk_a launches data that flows to clk_b domain but not back set_false_path -from [get_clocks clk_fast] -to [get_clocks clk_slow] ## Note: other direction (clk_slow to clk_fast) remains timing-checked!

4. set_multicycle_path — deep dive

A multicycle path allows data to travel across multiple clock cycles before being captured. This is used when a pipeline stage deliberately takes more than one cycle to complete — the result is only valid at cycle N and is not read until then.

2-Cycle Multicycle Path: Setup and Hold Check Windows Clock: Cyc 0 (launch) Cyc 1 Cyc 2 (capture MCP) Cyc 3 Without MCP: Setup check at Cyc 1 ← VIOLATION! With MCP: Setup checked at Cyc 2 (2 cycles) ✓ Hold check: Hold checked at Cyc 1 (MCP -hold 1) ✓ set_multicycle_path 2 -setup + set_multicycle_path 1 -hold (always pair them!)
set_multicycle_path — correct patterns
## ── 2-cycle path through a slow multiplier ── ## Setup: check at cycle 2 (data can take 2 cycles to be valid) set_multicycle_path 2 -setup \ -from [get_cells u_mult/launch_reg/*] \ -to [get_cells u_mult/capture_reg/*] ## Hold: adjust to cycle 1 (hold checked at adjacent cycle, not cycle 0) ## Without this: hold violation appears at the wrong clock edge! set_multicycle_path 1 -hold \ -from [get_cells u_mult/launch_reg/*] \ -to [get_cells u_mult/capture_reg/*] ## ── 3-cycle path through floating-point unit ── set_multicycle_path 3 -setup -from [get_cells fpu/*] -to [get_cells fpu/result_reg/*] set_multicycle_path 2 -hold -from [get_cells fpu/*] -to [get_cells fpu/result_reg/*] ## ── Multicycle based on path endpoint only ── ## When the destination register is always written at 4-cycle intervals: set_multicycle_path 4 -setup -to [get_cells low_rate_config_reg/*] set_multicycle_path 3 -hold -to [get_cells low_rate_config_reg/*] ## ── Verifying multicycle path is correctly applied ── report_timing -from [get_cells u_mult/launch_reg/*] \ -to [get_cells u_mult/capture_reg/*] \ -path_type full

The hold adjustment formula: always (N-1)

For set_multicycle_path N -setup, the hold adjustment is always set_multicycle_path (N-1) -hold. For N=2, hold=1. For N=3, hold=2. This holds true for same-clock-frequency paths. For different-frequency paths (e.g., CDC with synchronous relationship), the formula may differ — consult your tool’s documentation.

5. set_max_delay -datapath_only

set_max_delay overrides the clock-period-derived maximum delay for a path. Without -datapath_only, it still applies setup and hold checks relative to clocks. With -datapath_only, it treats the path as pure combinational logic with no clock relationship — only the total path delay is checked against the specified maximum.

The primary use case is asynchronous FIFO write-pointer → read-pointer crossing. In a Gray-code FIFO, the pointer bits cross clock domains asynchronously. You need to constrain the maximum propagation delay (to ensure synchroniser setup time is met) but cannot use a clock-relative constraint because there is no clock relationship.

set_max_delay -datapath_only for async FIFO
## Async FIFO Gray pointer crossing: ## write_ptr[N:0] registers (clocked by clk_wr) feed synchronizer inputs ## synchronizer registers are clocked by clk_rd ## Step 1: Remove clock relationship (no setup/hold check across domains) set_clock_groups -asynchronous \ -group [get_clocks clk_wr] \ -group [get_clocks clk_rd] ## Step 2: Constrain the combinational path delay from write FF outputs ## to the synchronizer input. This ensures the data is stable at the ## synchronizer input before the first sync stage captures it. ## The value (0.8 * Tclk_rd) is a common guideline — ensure stability ## within one cycle of the receiving clock for the sync to work. set_max_delay 3.0 -datapath_only \ -from [get_cells fifo_wr_ptr_reg[*]/Q] \ -to [get_cells sync_stage1_reg[*]/D] ## Note: -datapath_only means: ## - No clock jitter added to the requirement ## - No setup time subtracted ## - No hold check performed ## - Just checks: total comb delay <= 3.0 ns ## Similarly for read-to-write direction (if used): set_max_delay 3.0 -datapath_only \ -from [get_cells fifo_rd_ptr_reg[*]/Q] \ -to [get_cells sync_wr_stage1_reg[*]/D]

6. set_min_delay

set_min_delay overrides the minimum path delay requirement. It is less common than set_max_delay but has specific use cases:

set_min_delay usage
## Ensure minimum 0.5 ns from output FF to I/O pad ## (some GPIO standards require minimum stable data time before strobe) set_min_delay 0.5 -from [get_cells output_ff/*] -to [get_ports gpio_data] ## DDR interface: bound both max and min data path delay ## Ensures data window is centered around DDR strobe set_max_delay 1.5 -datapath_only \ -from [get_cells ddr_out_ff/*] -to [get_ports ddr_dq] set_min_delay 0.4 -datapath_only \ -from [get_cells ddr_out_ff/*] -to [get_ports ddr_dq] ## Note: set_min_delay -datapath_only is the hold equivalent of ## set_max_delay -datapath_only — checks only minimum combinational delay

7. Common mistakes with timing exceptions

MistakeEffectCorrect approach
Missing hold adjustment for MCPFalse hold violations or missed real hold violationsAlways pair -setup N with -hold (N-1)
False path to hide real violationSilicon failure; path is real but not fixedFix the path; document if exception is truly valid
Over-broad false path (-from a whole clock)Removes all paths from a clock, including real onesAlways use both -from and -to to narrow the scope
Missing -datapath_only on set_max_delay for CDCTool still applies clock jitter, giving incorrect resultAlways use -datapath_only for CDC path constraints
Unidirectional clock_groups for two-way CDCOne crossing direction left unconstrainedUse set_clock_groups -asynchronous (covers both directions)
False path on reset but reset is synchronousSynchronous reset setup violation hiddenApply false path only to truly async reset inputs

8. Auditing exceptions with report_exceptions

Auditing timing exceptions in PrimeTime
## List all exceptions and how many paths each covers report_exceptions -all -ignored ## Exceptions that were applied but match no paths (suspicious!) report_exceptions -ignored ## Show exceptions covering a specific path report_exceptions -from [get_cells u_mult/*] -to [get_cells result_reg/*] ## Show all false paths sorted by path count (broad ones = most risky) report_exceptions -type false_path -significant_digits 4 ## Check if a specific path has an exception report_timing -from [get_cells src_ff/Q] -to [get_cells dst_ff/D] \ -path_type exception ## Find all paths that are NOT covered by any exception ## (confirms default single-cycle check is applied to real paths) report_timing -delay_type max -nworst 100 -slack_lesser_than 0 \ -exceptions none ## Exception count summary puts "False paths: [llength [get_exceptions -type false_path]]" puts "Multicycle: [llength [get_exceptions -type multicycle_path]]" puts "Max delay: [llength [get_exceptions -type max_delay]]"

Day 8 Key Takeaways

Frequently Asked Questions

What is the difference between set_false_path and set_clock_groups?

set_false_path marks specific paths as not requiring timing analysis. set_clock_groups -asynchronous declares that two entire clock domains have no timing relationship — it removes all paths between them from analysis in both directions and covers all derived clocks. For CDC, set_clock_groups is preferred because it is harder to accidentally leave a direction uncovered.

Why must set_multicycle_path setup always have a hold adjustment?

Without the hold adjustment, STA checks setup at cycle N but checks hold relative to cycle 0 (the launch edge). For a 2-cycle setup path, the hold check at cycle 0 is incorrect — the actual hold window is at cycle 1 (one cycle before the capture at cycle 2). Always pair set_multicycle_path N -setup with set_multicycle_path (N-1) -hold on the exact same -from/-to specification.

What is set_max_delay -datapath_only used for?

It constrains the maximum combinational delay on a path without applying clock-related timing (no setup time, no jitter). It is primarily used for async FIFO Gray-code pointer crossings, where you need to bound the data propagation time but there is no synchronous clock relationship to reference. Always use -datapath_only for CDC paths or the tool will apply incorrect clock adjustments.

How do you audit timing exceptions to ensure they are correct?

Use report_exceptions -ignored to find exceptions that don’t match any paths (likely wrong specifications). Use report_exceptions -all to list every exception with its path count. For each exception, verify: (a) the path is truly false or multicycle, (b) both directions are covered for CDC, (c) MCP hold adjustments are present, and (d) the -from/-to scope is narrow enough not to catch unintended paths.

What happens if you forget the hold adjustment in set_multicycle_path?

STA checks setup at cycle N (correct) but checks hold at cycle 0 (incorrect edge). This typically produces phantom hold violations at impossible-to-fix short paths, or misses real hold violations because it’s checking the wrong edge. The hold check for a 2-cycle MCP should always be at cycle 1 — the cycle just before the intended capture. Always use set_multicycle_path 1 -hold after set_multicycle_path 2 -setup.

← Previous
Day 7: OCV, AOCV & POCV