Timing Exceptions
Chapter 8 — Timing Exceptions
Why Exceptions Exist
A static timing analyzer (STA, the tool that checks chip timing) starts as a pessimist. It assumes one fixed rule. Every path between two registers must launch data on one clock edge. It must capture that data on the very next clock edge. This is the single-cycle assumption. That rule drives setup and hold checks. For most paths in a synchronous design, it is correct. But a real chip is not so uniform. Some paths are never actually used. Some are allowed to take several cycles. Some cross between unrelated clocks. Some pins are tied to a fixed value in silicon. The tool does not know any of this. So it applies its default rule everywhere. It then reports failures that can never happen. Worse, it pushes the build flow to fix timing that was never broken. Think of it like a strict gym coach. He times every lap with one stopwatch rule. He does not know some runners are warming up, not racing. He still yells at them for being slow. That over-fixing has a hidden cost. A false failure still burns effort. The tool adds buffers, makes cells bigger, and reroutes wires. It spends area and power closing a path that does not exist. A timing exception fixes this. It tells STA the functional truth about your design. The tool then stops checking things that are not real. It also stops over-designing for them. So an exception is not a trick to pass timing. It is a constraint. It carries design intent the netlist (the gate-level wiring list) cannot show on its own. There are four main exceptions. They are false paths,
multicycle paths, min/max delay overrides, and case analysis. Each one removes or reshapes a default check. Default single-cycle vs the exception types default single-cycle false path (excluded) multicycle (N cycles) min/max delay (bounded) case analysis (pruned)
Figure 8.1 Default single-cycle check vs. the four exception types reshaping launch/capture relationships
| Exception | What it means | Command | Primary risk if misused |
|---|---|---|---|
| False path | Path exists in wiring but is | set_false_path never used; do not check it | Hiding a path that is real, masking a true failure |
| Multicycle | Data is allowed N cycles to path | set_multicycle_path arrive; relax the capture | Forgetting the matching hold fix, creating a real hold failure |
window
| Max delay | Bound the path delay | set_max_delay | Over- or under-constraining a |
|---|---|---|---|
| override | directly to a chosen value | path the tool would size |
correctly
| Min delay | Set a lower bound on a path | set_min_delay | Conflicting with hold intent if |
|---|---|---|---|
| override | delay directly | applied carelessly | |
| Case | Tie a pin to a constant to analysis | set_case_analysis pick a mode and prune logic | Disabling paths valid in another mode you forgot to check |
False Paths
A false path exists in the netlist but is never used. No legal input sequence sends a real, timingrelevant change down it. It cannot carry meaningful data in the check being run. So forcing it to meet setup or hold is pointless. Common sources of false paths include the following.
- Mutually exclusive mux selects. Two muxes (multiplexers, which pick one input) have select signals that can never both pick the crossed inputs at once. The diagonal path through both is present but logically impossible.
- Mode-isolated logic. A block used only in test mode shares wiring with functional logic. The two are never active together.
- Quasi-static / configuration paths. Some signals change only at boot or reconfiguration. Then they hold steady. They do not need single-cycle timing.
- Asynchronous clock-domain crossings. Paths between unrelated clocks have no real launch- capture link. These are often set as false paths. (You can also use
set_clock_groups.) The tool then stops trying to line up edges that never align.
WORKED EXAMPLE: A MODE-ISOLATED PATH
Picture a register cfg_reg . Its output feeds a block read only while func_en is low. During that window, the launching flop data_reg is held by clock gating (the clock is paused). So the path data_reg -> ... -> cfg_capture_reg never sees a real change that must be caught in one cycle. The tool knows none of this. It flags a setup failure because the logic is deep. The honest fix is to mark the path false.
# standard (SDC)
set_false_path -from [get_pins data_reg/Q] \
-through [get_pins mode_mux/A] \
-to [get_pins cfg_capture_reg/D]
The -from , -through , and -to switches scope the exception. Be as exact as the case needs. A broad set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] fits a whole async boundary. But one sneaky diagonal mux path needs -through . Otherwise you may disable good sibling paths by accident. The danger of false paths is bluntness. Mark too much false and you blind the tool to real logic. Every false path should trace to a written reason. Ideally someone who knows the RTL intent reviews it. Do not add one just to silence a red number.
Multicycle Paths
A multicycle path truly gets more than one cycle. The hardware allows data to take several cycles to arrive and settle before capture. This is real design, not a workaround. Maybe the source register updates only every fifth cycle. Maybe an enable on the destination flop opens its window once every N cycles. The path then has N cycles of real time. Forcing it into one cycle would over-constrain it badly. You tell the tool with set_multicycle_path . You give a multiplier. You say if it applies to setup or hold. The setup multiplier moves the capture edge later. The hold check must then be fixed too, or it gets wrongly strict. This hold interaction is the most common multicycle mistake, in interviews and in real work.
THE HOLD-EDGE GOTCHA
By default, the hold check is tied to the setup check. Hold is checked one launch edge before the setup capture edge. When you push the setup capture edge out by N cycles, the default hold edge moves out too. It lands N−1 cycles from the launch edge.
That is almost never what you want. It would demand data launched at edge 0 still be stable many cycles later. No logic can meet that without huge delay padding. The fix is a hold multiplier. It snaps the hold check back to the launch edge (position 0). The rule for a path with setup multiplier N is a hold multiplier of N−1. This pulls the hold edge back next to the launch. The normal hold relationship returns.
Setup capture Hold check edge Hold edge after correct
| edge | (default) | fix | |
|---|---|---|---|
| Single-cycle (default) | Edge 1 | Edge 0 | Edge 0 |
| Multicycle, setup = 4, no hold | Edge 4 | Edge 3 | (wrong — too strict) |
fix
Multicycle, setup = 4, hold = 3 Edge 4 Edge 0 Edge 0 (correct)
| Setup check | Hold check | |
|---|---|---|
| launch | capture (T) | same edge |
| data must arrive before T −Tsu | data must NOT arrive before Thold |
Figure 8.2 Timeline of launch edge and capture edges showing setup moved to edge 3 and hold pulled back to
edge 0
WORKED EXAMPLE: A 4-CYCLE DATAPATH
Suppose mult_src feeds a deep multiplier. Its result is used by mult_dst . The control logic makes sure mult_dst enables its capture only every fourth clock. So the path has four full cycles to settle. The constraint pair is below.
# standard (SDC)
# Setup: allow 4 cycles instead of 1
set_multicycle_path 4 -setup \
-from [get_pins mult_src/Q] -to [get_pins mult_dst/D]
# Hold: pull the hold edge back so it checks against the launch edge
set_multicycle_path 3 -hold \
-from [get_pins mult_src/Q] -to [get_pins mult_dst/D]
Always write the hold line with the setup line. A setup multicycle with no hold multicycle is a hidden hold bug. Layout will expose it later. When source and destination use different clocks, be clear about which clock the multiplier counts. The -start and -end qualifiers exist for this. Pick based on which clock defines the N-cycle relationship.
Min/Max Delay Overrides
Sometimes you do not want to relax a check by edges. You want to bound the delay to a number directly. set_max_delay and set_min_delay do this. They override the clock-based requirement with an absolute value. These fit when there is no clean clock relationship. Common uses follow. Timing a pure combinational path from an input port to an output port. There is no register on either end, so no edges to multiply. Bounding an async interface to a maximum propagation. Putting a tight ceiling on a feedback or resetrelease path.
# standard (SDC)
# Bound a combinational in-to-out path to 6 ns
set_max_delay 6.0 -from [get_ports cfg_in] -to [get_ports status_out]
# Hold a minimum on a fast bypass path
set_min_delay 0.8 -from [get_pins bypass_reg/Q] -to [get_pins out_reg/D]
Use these surgically. A set_max_delay replaces the normal setup requirement on the affected paths. Scope it too broadly and you may loosen or tighten many paths the tool already handled fine. These are precision tools, not blanket policy.
Case Analysis
Case analysis tells STA a pin is held at a constant value. With set_case_analysis you fix a modeselect or scan-enable pin to 0 or 1. The tool then sends that constant through the logic. Two things happen. It prunes paths that become unreachable. A mux side the constant can never pick is effectively cut off. It also selects a mode. So you check the chip as it will really run in that setting.
# standard (SDC)
# Analyze the design in functional mode (scan disabled)
set_case_analysis 0 [get_ports scan_enable]
# Force a configuration mux to its default selection
set_case_analysis 1 [get_ports clk_sel]
The key with case analysis is completeness across modes. Say your chip has functional, test, and low-power configurations. Each is a separate case-analysis setting. Each ideally gets its own analysis run. Setting scan_enable to 0 removes scan paths from the functional run. That is correct. But you also need a test-mode run where it is 1. Otherwise the scan-shift timing is never checked. The risk of case analysis is silent under-checking. Paths valid in a mode you never ran simply vanish from the report. Case analysis is also often used to disable an unused clock at a clock-mux input. This can prune huge amounts of logic. It cleans up reports a lot. But you must also analyze the other clock selection.
Exception Priority and the Over-Constraint Trap
Several exceptions may apply to one path. The tool does not combine them. It picks one by a fixed order. Here it is, highest priority first.
| Priority | Exception | Notes |
|---|---|---|
| 1 | set_false_path (highest) | Removing a check always beats relaxing or bounding it |
| 2 | set_max_delay / | Explicit numeric bounds beat edge-based relaxation |
set_min_delay
| 3 | set_multicycle_path | Relaxes the default edge relationship |
|---|---|---|
| 4 (lowest) | Default single-cycle check | Applies when no exception matches |
Within the same type, more specific scoping usually wins. A -from/-through/-to exception beats a broad -from -only one when both could match. The tool favors the tighter spec. Case analysis sits apart. It changes which paths exist before priority is even checked. So a path pruned by
set_case_analysis never reaches this contest.
Priority matters because it explains surprising reports. Say you add a multicycle path. But a broader false path already covers the same endpoints. The multicycle then does nothing. The false path has higher priority, so the path is not checked. Engineers waste hours debugging multicycle constraints that were simply outranked. The big hazard across all exceptions is over-constraining. That means piling on exceptions until the report turns green. Each false path is a check you turned off. Each broad multicycle is a window you widened. Do this carelessly and you tape out a chip with real failures hidden by your own constraints. The professional standard is simple. Every exception must be justified, scoped, and reviewed. It should trace to a written reason. It should be scoped as narrowly as that reason allows. Someone who knows the intent should sign off. A clean report bought with sloppy exceptions is not a clean chip. It is a silicon bug with good paperwork. Default single-cycle vs the exception types default single-cycle false path (excluded) multicycle (N cycles) min/max delay (bounded) case analysis (pruned)
Figure 8.3 Decision flowchart — is the path real? does it need >1 cycle? is there a clock relationship? pick the
exception
Interview Q&A
and what multiplier do you use? Relaxing the setup edge by N cycles also pushes the default holdcheck edge out to N−1 cycles. That demands the data hold stable far longer than real logic can promise. The result is a false hold failure or massive padding. You apply a hold multicycle of N−1. This pulls the hold check back to the launch-adjacent edge (position 0). The normal hold relationship returns. Setup multicycle with no hold multicycle is a hidden hold bug.
precedence. A set_false_path or a set_max_delay covering the same endpoints outranks the multicycle. So the path is removed or bounded before the multicycle is even considered. Check for a broader, higher-priority exception on those pins. A scoping mismatch is the other common cause. Your -through may not be on the real sensitized path.
no clean clock-edge relationship to multiply. Most often this is a pure combinational input-to-output path with no register on either end. Or an async interface that just needs a hard ceiling.
set_max_delay bounds the delay to an absolute number. Multicycle only makes sense when clock
launch and capture edges define the relationship.
across modes. Fixing a mode pin to a constant prunes the paths unreachable in that mode. That is correct for that mode. But any path valid only in the other setting vanishes from the analysis. If you never run the complementary case, like test mode with scan on, that logic is never timed. Case analysis must be complete. Run one scenario per operating mode.
Key Takeaways
- Exceptions tell STA the functional truth the netlist cannot express. They remove false failures and prevent wasteful over-design.
- False paths delete checks on paths that exist but are never used. Sources include mutually exclusive muxes, mode isolation, and async crossings. Scope them tightly.
- Multicycle paths truly grant N cycles. Always pair the setup multicycle (N) with the hold multicycle (N−1). This avoids a false or real hold problem.
- Min/max delay overrides bound a path to an absolute number. They fit combinational and async interfaces with no clock relationship.
- Case analysis fixes a pin to a constant to prune logic and pick a mode. It demands a complete set of mode runs, or it silently under-checks.
- Precedence is false path, then min/max delay, then multicycle, then default single-cycle. More specific scoping wins within a type.
- Every exception is a check you weaken or remove. Justify it, scope it, review it. A green report built on sloppy exceptions hides real violations.
ChipBuddy
← Home
Comments
Leave a Reply