The RTL circuit is correct on paper. Optimisation, retiming, placement, and routing can still break it.
Unguided tools may merge the flops, retime logic through them, or place them far apart. Each of these silently destroys settling time.
Risk | What goes wrong | Counter-measure |
|---|---|---|
Logic merged between the flops | Settling time shrinks | Keep the synchroniser in its own module; mark it do-not-touch / keep hierarchy |
Retiming moves logic across | Same | Exclude the synchroniser from retiming |
Flops placed far apart | Long wire between flop 1 and 2 eats the resolution time | Constrain placement together; use library synchroniser cells ( |
First flop's output used elsewhere | Fanout of a possibly metastable signal | Use only the second flop's output |
Timing analysed as a normal path | Tool tries to meet setup on an inherently asynchronous input and wastes effort or reports false failures | Constrain the crossing explicitly |
Reset on the first flop differs from the second | Release behaviour changes | Reset both the same way, or neither |
The crossing needs an explicit constraint, not an ordinary setup path.
# The crossing is an asynchronous path. Do NOT leave it as an ordinary setup path, and do NOT blanket false-path it either.
# Option 1: bound the delay of the path into the first synchroniser flop (data-only, ignore clock skew)
set_max_delay 2.0 -datapath_only -from [get_cells src_reg*] -to [get_cells u_sync/ff1_reg*]
# Option 2: ONLY when no crossing between these clocks requires a max/min-delay bound.
# This replaces option 1; it must not be combined with it on the same clock pair.
set_clock_groups -asynchronous -group [get_clocks clk_a] -group [get_clocks clk_b]# Mixed crossings between clk_a and clk_b: no clock-group exception on this pair.
# Bound the bus; cut only a separate, structurally checked single-bit input.
set_max_delay 2.0 -datapath_only -from [get_cells held_data_reg*] -to [get_cells dst_data_reg*]
set_false_path -from [get_cells status_reg] -to [get_cells u_status_sync/ff1_reg]
# Keep synchroniser stage 1 -> stage 2 timed normally in the destination domain.
# Signoff: report the bus paths and ignored/overridden exceptions.
# Require an active 2.0 ns max-delay check on every bus bit, not merely an SDC line.Read each row from the source register on the left to the destination flop on the right; the label on each arrow is the constraint that path gets.
The bus is held stable by its protocol, but its bits must still all arrive within 2.0 ns, so it keeps a real bound.
The status bit enters a synchroniser, so only its first hop is cut; ff1 to ff2 stays an ordinary clk_b path.
A clock-group exception would cover both rows at once and silently remove the bus bound, which is why this pair uses narrow exceptions instead.
No. Clock groups have higher priority. Remove the broad group exception, apply selective exceptions to other crossings, and inspect the effective bus timing and overridden-exception reports.
The source of a crossing is a flop output in the source domain, directly connected.
The synchroniser is two (or more) flops of the destination clock with no logic between them.
Only the last flop feeds the destination logic.
The synchroniser is identified to the tools (naming, attributes, library cells).
A CDC tool runs on the whole design and reports every crossing and its classification.
set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b] “to fix the timing report”. What is missing?A guarantee that every crossing between those clocks is properly synchronised. The false path silences the timing checks, but a missing synchroniser on some signal will now pass unnoticed. Run a CDC structural check and constrain specific crossings with set_max_delay where needed.