A multi-cycle path is a timing path that the design intends to take more than one clock cycle, so static timing analysis (STA) should check it against a later capture edge. A false path is a path that can exist structurally but is never required to carry a change that must be captured in a given time, so STA should not time it at all. Both are timing exceptions: constraints that override the default single-cycle check. Set correctly, they stop the tools wasting area and power on paths that do not matter. Set wrongly, they hide real failures that only show up in silicon.
This post started as a summary of an EDN article on multi-cycle and false paths (2014, by engineers at Freescale Semiconductor India). It now adds the STA mechanics, SDC commands, a worked example with numbers and the questions interviewers ask.
Key takeaways
- By default STA assumes every register-to-register path must settle in one clock cycle. Exceptions tell it otherwise.
- A false path is removed from timing entirely. A multi-cycle path is still timed, only against a later edge.
- A multi-cycle path is only valid if the RTL guarantees it, usually with an enable that holds the launch data stable for N cycles.
- When you move the setup check by N cycles, you almost always need a matching hold multiplier of N-1, or the hold check becomes impossible.
- Exceptions should be identified up front, reviewed with the designers and verified, not discovered one by one from failing timing reports.
Why timing exceptions matter
The EDN article makes the point that designers often miss timing exceptions early in RTL design, and only find them through repeated iterations on timing reports. Until then, synthesis and timing tools treat every path as a single-cycle path. They upsize cells, add buffers and restructure logic to meet a requirement that the design never had. The result is extra area and power, longer run times, and a backlog of "violations" that hide the paths that really matter.
The opposite mistake is worse. An exception that is too broad, or that is applied to a path that is not truly multi-cycle or false, tells STA to stop checking a real path. The design will pass sign-off and can fail on silicon.
What STA checks by default
For a path between two registers on the same clock with period T, STA runs two checks:
- Setup: data launched at edge 0 must arrive before the capture edge at T, less the setup time of the capturing register.
- Hold: data launched at edge 0 must not arrive so early that it corrupts what the capturing register is taking at edge 0, plus its hold time.
A timing exception changes which edges are used for these checks (multi-cycle path), or removes the checks (false path). Two related exceptions, set_max_delay and set_min_delay, replace the edge-based requirement with a fixed value.
False paths
A false path is a timing arc where a change at the source register is not expected to be captured by the destination register within a particular time interval. The EDN article gives several typical cases, summarised here in my own words:
| Case | Why the path is false | Usual constraint |
|---|---|---|
| Static false path in logic | Logic structure means a change at the source can never reach the destination, whatever the input values | set_false_path on the specific arc |
| Clock-gate enable set before a mode change (the article's DMA controller example) | The enable register is written before the block stops, and does not change while the block runs | set_false_path, with a design rule that software only changes it while the block is stopped |
| Configuration registers driving I/O pads | The value is fixed before data transfer and does not change during it | set_false_path or a relaxed set_max_delay |
| Reset de-assertion to blocks whose clocks are gated at start-up | Clocks are off when reset releases, so recovery timing is not needed | set_false_path on those recovery and removal arcs |
| Asynchronous clock domain crossing | The clocks have no fixed phase relationship, so a single-cycle check is meaningless | set_clock_groups -asynchronous, plus a synchroniser in RTL |
| Pseudo-asynchronous clocks from the same source | A large uncommon clock path makes the relationship impractical to close, so the crossing is designed as asynchronous | Treat as asynchronous, with a synchroniser |
The article also gives a counter-example: half-duplex or bidirectional memory interfaces, where the data direction changes on the fly. These paths look like candidates for a false path but must still be timed.
Static and mode-dependent false paths
It helps to separate two kinds. A static false path is never sensitised in any mode. A mode-dependent path is false only in some modes, such as a test-mode multiplexer in functional mode. For mode-dependent paths, set_case_analysis on the mode signal, with a separate timing mode per configuration, is safer than a blanket false path, because the path is still timed in the mode where it is real.
False paths at clock domain crossings
A false path or asynchronous clock group only tells STA not to time the crossing. It does not make the crossing safe. The RTL must still have a two-flop synchroniser for single-bit signals, and a mux synchroniser, handshake or asynchronous FIFO with Gray-coded pointers for multi-bit data. For a Gray-coded pointer bus, many teams use set_max_delay -datapath_only instead of a false path, so that skew between the bits stays below one destination clock period.
Multi-cycle paths
A multi-cycle path is a timing arc where the destination register is expected to capture data from the source register after more than one clock cycle. The EDN article's examples are all cases where the design itself guarantees the extra time:
- A 1:2 clock ratio between two modules, where the capturing flop is enabled every second cycle, giving a multi-cycle path of two.
- A shift-register pulse scheme across n flops that delays the capture enable, giving a multi-cycle path of n-1 cycles.
- Two-phase peripheral bus reads and writes, where address, data and select registers hold for two cycles.
- A slow peripheral at half the system clock, where a wait-state block creates a two-cycle access.
An earlier version of this post listed FIFOs and clock domain crossings as multi-cycle path examples, and said some multi-cycle paths exist because of clock skew. That was not accurate: a crossing between asynchronous clocks is handled as a false path or asynchronous clock group, and clock skew is never a reason to relax a path.
RTL that makes a multi-cycle path valid
The constraint is only as good as the RTL behind it. In this example, the operands and the multiplier result all load on the same enable, which is high every second clock. The operands therefore stay stable for two cycles, and the multiplier has two clock periods to settle.
// Two-cycle multiplier path: operands and result load on the same enable
module mcp_mult (
input wire clk,
input wire rst_n,
input wire [15:0] a_in,
input wire [15:0] b_in,
output reg [31:0] p_q
);
reg en; // high on every second clock
reg [15:0] a_q, b_q;
always @(posedge clk or negedge rst_n)
if (!rst_n) en <= 1'b0;
else en <= ~en;
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
a_q <= 16'd0; b_q <= 16'd0; p_q <= 32'd0;
end else if (en) begin
a_q <= a_in; // launch: operands change only when en is high
b_q <= b_in;
p_q <= a_q * b_q; // capture: two clocks after a_q and b_q last changed
end
endmodule
The matching SDC for the paths from the operand registers to the result register is:
set_multicycle_path 2 -setup -from [get_cells {a_q_reg* b_q_reg*}] -to [get_cells p_q_reg*]
set_multicycle_path 1 -hold -from [get_cells {a_q_reg* b_q_reg*}] -to [get_cells p_q_reg*]
Register names after synthesis vary by tool, so check that the patterns match the intended cells and nothing else.
Worked example: why the hold multiplier is needed
The values below are illustrative, with ideal clocks and no skew. Clock period T = 2.0 ns. Clock-to-Q = 0.15 ns. Setup time = 0.10 ns. Hold time = 0.05 ns. The slowest multiplier path is 2.90 ns and the fastest is 0.60 ns.
| Constraint | Setup check edge | Setup slack | Hold check edge | Hold slack |
|---|---|---|---|---|
| None (single cycle) | 2.0 ns | (2.0 - 0.10) - (0.15 + 2.90) = -1.15 ns | 0 ns | (0.15 + 0.60) - 0.05 = +0.70 ns |
-setup 2 only | 4.0 ns | (4.0 - 0.10) - 3.05 = +0.85 ns | 2.0 ns | 0.75 - (2.0 + 0.05) = -1.30 ns |
-setup 2 and -hold 1 | 4.0 ns | +0.85 ns | 0 ns | +0.70 ns |
With only the setup multiplier, the tool moves the hold check to the edge one cycle before the new setup edge. It would then insert about 1.3 ns of hold buffers on a path that is perfectly safe. The hold multiplier of N-1 moves the hold check back to edge 0.
Multi-cycle paths between related clocks
When launch and capture clocks differ, the -start and -end options decide which clock's cycles are counted. By default, setup multipliers count capture-clock cycles (-end) and hold multipliers count launch-clock cycles (-start).
| Case | Typical constraint |
|---|---|
| Slow to fast clock (capture clock 2x faster), data valid for 2 fast cycles | set_multicycle_path 2 -setup -end and set_multicycle_path 1 -hold -end |
| Fast to slow clock (launch clock 2x faster), launch held for 2 fast cycles | set_multicycle_path 2 -setup -start and set_multicycle_path 1 -hold -start |
Timing reports and verifying exceptions
Timing reports are the outputs of the synthesis and timing tools that show the critical and failing paths, with slack and delay values. They can reveal false and multi-cycle paths that are not yet defined as exceptions, and they are the first check that an exception has been applied as intended. Good practice includes:
- Review the exception report. STA tools can list every exception, and the ones that are ignored or overridden. An exception that matches no path, or far more paths than expected, is usually a typo or a wildcard that is too broad.
- Prove the RTL behaviour. For a multi-cycle path, write an assertion that the source stays stable for N cycles before each capture. Structural and formal timing-exception checkers can do this for the whole constraint set.
- Prefer narrow exceptions. Use
-from,-throughand-toon specific pins rather than whole clocks or wide wildcards. - Document each exception. Record the reason, the RTL mechanism that guarantees it and the owner, so the exception can be re-checked when the RTL changes.
Common mistakes
- Adding a multi-cycle path to close timing when the RTL has no enable that holds the data. The path is then a real single-cycle path with no check.
- Setting
-setup Nwithout-hold N-1, so the tool adds large hold buffers. - Using
set_false_pathbetween clocks that are actually synchronous, hiding paths that should have been multi-cycle paths or single-cycle paths. - Treating a false path at a crossing as if it made the crossing safe, with no synchroniser in RTL.
- Keeping exceptions after the RTL changed, so an old exception now covers a new, real path.
How interviewers probe this topic
- "What is the difference between a false path and a multi-cycle path?" A false path is not timed. A multi-cycle path is timed against a later edge.
- "If you set a multi-cycle path of 3 for setup, what happens to hold?" The hold check moves to two cycles after launch, so you add
-hold 2to bring it back to edge 0. - "How do you know a path is really multi-cycle?" Point to the enable or protocol in the RTL, and to the assertion or formal check that proves it.
- "Why not just false-path an asynchronous crossing and move on?" Because metastability and bus skew still have to be handled by design.
Conclusion
Identifying timing exceptions up front reduces synthesis and timing optimisation effort, improves area and power, and avoids late timing closure surprises. Each exception should be reviewed with the designers before synthesis, kept as narrow as possible, and checked against the RTL behaviour that justifies it. As the EDN authors put it, designers should be "very careful while designing or providing constraints", because a wrong exception can cause device failure.
FAQ
Is a multi-cycle path the same as a slow path?
No. A slow path that fails timing is just a failing path. It becomes a multi-cycle path only when the design guarantees that the data is not needed for N cycles.
Should I use set_false_path or set_clock_groups for asynchronous clocks?
set_clock_groups -asynchronous states the intent between whole clocks in one line and in both directions. set_false_path is better for specific arcs within a clock domain.
Do timing exceptions affect simulation?
No. Exceptions are only for synthesis and STA. Gate-level simulation with timing, assertions or formal checks are needed to confirm the design behaves as the exception assumes.


Your comments will be moderated before it appears here.