Asynchronous Crossings & CDC Constraints
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.

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.

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 trait | Detail |
|---|---|
| Stages | Usually two flops, sometimes three |

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.
| Feature | set_clock_groups -asynchronous | set_false_path |
|---|---|---|
| Scope | Whole clock groups | Named paths only |
| Direction | Both ways automatically | One way per command |
| Best for | True async domains | Specific exceptions |
| Risk | May hide a real sync path | Easy 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.
| Mistake | Result | Better approach |
|---|---|---|
| Constraint without synchronizer | Lab metastability | Add the synchronizer circuit |
| One-direction false path | Untimed reverse path | Use set_clock_groups |
| Calling related clocks async | Real paths hidden | Only group truly unrelated clocks |
| No wire bound on first flop | Long wire, less settling | Add 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
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.
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.
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.
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.
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.
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 -asynchronousremoves all paths between whole domains in both directions, whileset_false_pathis narrow and one-directional.- Bound the path into the first sync flop with
set_max_delayto 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.
ChipBuddy
← Home
Comments
Leave a Reply