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.
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:
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.
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:
## ✅ 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!
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.
For asynchronous CDC, two commands can achieve similar results but with important differences:
| Aspect | set_false_path -from clk_a -to clk_b | set_clock_groups -asynchronous |
|---|---|---|
| What it does | Marks specific one-directional paths as false | Marks the entire clock relationship as having no timing requirement (bidirectional, all paths) |
| Coverage | Only A→B; B→A needs a separate command | Both directions, all paths between all clocks in the groups |
| Generated clocks | May not cover generated clocks derived from clk_a | Applies to all clocks in the group including generated ones |
| Reports | Path appears in report_exceptions as false path | Paths are excluded from analysis entirely; do not appear in reports |
| When to use | When only one direction is false, or a specific subset of paths | When two clock domains have absolutely no timing relationship (preferred 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!
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 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
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.
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.
## 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]
set_min_delay overrides the minimum path delay requirement. It is less common than set_max_delay but has specific use cases:
## 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
| Mistake | Effect | Correct approach |
|---|---|---|
| Missing hold adjustment for MCP | False hold violations or missed real hold violations | Always pair -setup N with -hold (N-1) |
| False path to hide real violation | Silicon failure; path is real but not fixed | Fix the path; document if exception is truly valid |
| Over-broad false path (-from a whole clock) | Removes all paths from a clock, including real ones | Always use both -from and -to to narrow the scope |
| Missing -datapath_only on set_max_delay for CDC | Tool still applies clock jitter, giving incorrect result | Always use -datapath_only for CDC path constraints |
| Unidirectional clock_groups for two-way CDC | One crossing direction left unconstrained | Use set_clock_groups -asynchronous (covers both directions) |
| False path on reset but reset is synchronous | Synchronous reset setup violation hidden | Apply false path only to truly async reset inputs |
## 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]]"
N -setup with (N-1) -hold; use identical -from/-to for bothreport_exceptions -ignored to catch exceptions that match no paths (likely wrong specifications)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.
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.
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.
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.
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.