← Home Design Constraints (SDC)
26 Design Constraints (SDC)

Asynchronous Crossings & CDC Constraints

Timing constraints — clocks, generated clocks, I/O delays, exceptions, OCV and MCMM setup

Asynchronous Crossings & CDC Constraints

Inside a chip, signals often jump from one clock to another. When the two clocks are unrelated, this jump is a clock domain crossing, or CDC. The word "asynchronous" means the two clocks have no fixed timing relationship. They drift against each other forever. This chapter explains why the timing tool cannot time a true asynchronous crossing. It shows the two main ways to tell the tool to stop trying: set_clock_groups -asynchronous and set_false_path . And it covers synchronizers, the small circuits that make these crossings safe, and how to constrain their first stage.

Why STA Cannot Time a True Async Crossing

Static timing analysis, or STA, checks every path against a clock. It measures the time from a launch edge to a capture edge. For that to make sense, the two edges must have a known relationship. Take two unrelated clocks. One runs at 311.2 MHz. The other runs at 274.9 MHz. These frequencies share no common beat. The edges slide past each other endlessly. There is no fixed gap between a launch edge and the next capture edge.

Technical diagram

The tool, forced to pick a relationship, picks the worst case. With unrelated clocks, the worst case is a tiny or even zero gap between launch and capture. That gap is impossible to meet. So the tool reports a huge violation that is not real. It is an artifact of pretending the clocks are related. Worse, a true async crossing can cause metastability. When data changes right at a capture edge, the flip-flop may land in an undefined state, neither one nor zero, for a short time. No timing constraint can prevent this. It is a physical fact of unrelated clocks. The fix is a circuit, not a constraint. So we do two things. We add a synchronizer circuit to make the crossing safe. And we tell the tool to stop timing the crossing path, because timing it gives meaningless numbers.

Synchronizers: The Real Fix

A synchronizer is a small chain of flip-flops in the receiving clock domain. The most common is the two-flop synchronizer. Two flip-flops sit back to back, both clocked by the receiving clock. The first flop may go metastable when it samples the async data. But it has a full receiving-clock period to settle before the second flop samples it. Most of the time it settles. The second flop then sees a clean value. The chance of a bad value passing through drops to a vanishingly small rate.

Technical diagram

The settling time is the key. At a receiving clock of 274.9 MHz, the period is about 3.64 ns. That gives the first flop 3.64 ns to settle before the second flop samples. More flops add more settling time for very high reliability.

A synchronizer only passes a single bit safely. For a multi-bit bus, you cannot just synchronize each bit alone, because the bits may settle on different cycles and split apart. Multi-bit crossings need extra structures like gray-coded counters or handshakes. The constraint story below is the same either way.

Synchronizer traitDetail
StagesUsually two flops, sometimes three
Technical diagram

There are two ways to tell the tool to stop timing a crossing. Both remove the path from analysis. They differ in scope and intent.

set_clock_groups -asynchronous  declares that whole groups of clocks are unrelated. The tool then

removes every path between those groups, in both directions, automatically. It is broad and clean. You declare the relationship once and all crossings between the groups are covered.

set_false_path  removes one specific path or set of paths. A false path is a path that the tool should

ignore because it never matters in real operation. It is narrow and surgical. You name a -from and a -to and only that direction is removed.

# standard SDC — portable across compliant tools
create_clock -name clk_a -period 3.21 [get_ports clk_a_in]
create_clock -name clk_b -period 3.64 [get_ports clk_b_in]
# declare the two clocks as mutually asynchronous
set_clock_groups -asynchronous -group {clk_a} -group {clk_b}

That one set_clock_groups line removes every path from clk_a to clk_b and from clk_b to clk_a. Compare with the narrower false-path style:

# standard SDC — portable across compliant tools
# remove only one direction of the crossing
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]

The false-path version needs both directions written out. The clock-groups version handles both directions in one command.

Featureset_clock_groups -asynchronousset_false_path
ScopeWhole clock groupsNamed paths only
DirectionBoth ways automaticallyOne way per command
Best forTrue async domainsSpecific exceptions
RiskMay hide a real sync pathEasy to miss a direction

The general rule: use set_clock_groups -asynchronous for whole unrelated domains. Use

set_false_path  for a narrow, one-off exception that is not about clock relationships.

Constraining the First Synchronizer Stage

Removing the crossing from timing is not the whole job. You still care about one thing inside the synchronizer: the path from the first flop to the second flop. Both flops are in the same receiving domain, so that path is timed normally. But there is a special concern at the first flop. The first flop can go metastable. When it does, it needs settling time before the second flop samples. You want the tool and the place-and-route flow to keep the two flops close, so the wire between them is short. A short wire leaves the most time for settling. Some flows use set_max_delay on the path into the synchronizer to bound how far the async data can travel, even though the path is otherwise async. This keeps the data wire short so the first flop sees a clean, fast edge. It does not make the crossing synchronous; it just limits skew between bits and bounds the wire.

# standard SDC — portable across compliant tools
# bound the data path into the first sync flop without timing it as sync
set_max_delay -from [get_pins src_reg/Q] -to [get_pins sync_a_reg/D] 1.82
set_false_path -hold -from [get_pins src_reg/Q] -to [get_pins sync_a_reg/D]

The set_max_delay caps the wire length. The set_false_path -hold removes the hold check, which is meaningless across async clocks. Together they say: keep the wire short, but do not pretend the edges relate. Here is a worked look at why settling time matters.

# Worked illustrative example — settling budget in a two-flop synchronizer
Receiving clock period               = 3.64 ns
Second-flop setup time               = 0.17 ns
Wire delay first flop to second flop = 0.21 ns
Time available for first flop to settle
= period - setup - wire
= 3.64 - 0.17 - 0.21
= 3.26 ns

The first flop gets 3.26 ns to resolve any metastable state. The shorter the wire, the more of the period is left for settling. That is why bounding the wire helps reliability.

Common CDC Constraint Mistakes

CDC constraints are a frequent source of silent bugs. The mistakes tend to be quiet, because the tool reports clean timing even when the design is unsafe. The biggest mistake is removing the crossing from timing but forgetting the synchronizer circuit. The constraint makes the violation disappear from the report. But the hardware still goes metastable in the lab. The constraint is not a fix; the synchronizer is the fix. A second mistake is using set_false_path in only one direction. Data may cross both ways. If you remove only clk_a to clk_b, the clk_b to clk_a paths still report false violations or, worse, get optimized wrongly. set_clock_groups avoids this by handling both directions. A third mistake is declaring clocks asynchronous when they are actually related. If two clocks come from the same source and have a clean ratio, they should be timed, not grouped as async. Declaring them async hides real setup paths that should be checked.

MistakeResultBetter approach
Constraint without synchronizerLab metastabilityAdd the synchronizer circuit
One-direction false pathUntimed reverse pathUse set_clock_groups
Calling related clocks asyncReal paths hiddenOnly group truly unrelated clocks
No wire bound on first flopLong wire, less settlingAdd set_max_delay

To verify, you ask the tool to list what was removed.

# illustrative — names vary by tool
report_clock_groups
report_timing -from [get_clocks clk_a] -to [get_clocks clk_b]

The second report should show the path as removed or unconstrained, confirming the crossing is no longer timed.

Interview Q&A

Q
Why can't STA time a true asynchronous crossing? STA measures time from a launch edge to

a capture edge, which needs a fixed relationship between the two clocks. Two unrelated clocks, say 311.2 MHz and 274.9 MHz, have edges that drift forever with no fixed gap. The tool picks the worst case, a near-zero gap, and reports an impossible violation. The number is meaningless, so we tell the tool to stop timing the path.

Q
What is metastability and can a constraint prevent it? Metastability is when a flip-flop,

sampling data that changes right at its clock edge, lands in an undefined state between one and zero for a short time. No constraint can prevent it, because it is a physical effect of unrelated clocks. The fix is a synchronizer circuit that gives the unstable flop time to settle before the next flop samples.

Q
How does a two-flop synchronizer work? Two flip-flops sit in series in the receiving domain.

The first samples the async data and may go metastable. It then has nearly a full receiving-clock period to settle. At a 3.64 ns period it gets about 3.64 ns. The second flop samples the now-settled value and passes a clean signal downstream, cutting the failure rate to a tiny level.

Q
When do you use set_clock_groups -asynchronous versus set_false_path ? Use
set_clock_groups -asynchronous  for whole domains that are truly unrelated; one command

removes all paths between the groups in both directions. Use set_false_path for a narrow, named exception, but remember it covers only one direction, so you must write both ways. Clock groups are cleaner and safer for real async domains.

Q
Why bound the data path into the first synchronizer flop? Even though the crossing is not

timed, you want the wire from the source to the first flop short. A short wire keeps bit skew low and gives the first flop a clean edge, leaving the most of the period for settling. A set_max_delay of, say, 1.82 ns caps the wire while a set_false_path -hold removes the meaningless hold check.

Q
What is the most dangerous CDC constraint mistake? Removing the crossing from timing

without adding the synchronizer circuit. The constraint makes the violation vanish from the report, so the design looks clean. But the hardware still goes metastable in the lab. The constraint silences the tool; only the synchronizer makes the crossing actually safe.

Key Takeaways

  • Unrelated clocks have no fixed edge relationship, so STA reports meaningless violations and must be told to stop timing the crossing.
  • A synchronizer circuit is the real fix; constraints only stop the false reports, they do not prevent metastability.
  • set_clock_groups -asynchronous removes all paths between whole domains in both directions, while set_false_path is narrow and one-directional.
  • Bound the path into the first sync flop with set_max_delay to keep the wire short and maximize settling time.
  • The worst mistake is silencing the tool without a synchronizer, which hides a real lab failure behind a clean timing report.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

Replying to