Two boards each have a 100 MHz oscillator with a ±300 ppm tolerance, and they exchange data over a cable. A reviewer asks how the receiver handles the clock difference. The designer answers that both clocks are 100 MHz, so there is no difference. The link fails within a fraction of a millisecond of starting.
When are two clocks "the same", and what does each kind of difference demand from the protocol?
The obvious answer treats clocks as either the same or different: if the nominal frequency matches, sample with your own clock; otherwise use a clock-domain crossing. But "the same frequency" from two separate oscillators only means "within tolerance of the same number". A small difference in frequency becomes a steadily growing difference in phase. Sooner or later one clock has ticked one more time than the other, and a receiver that assumed lockstep has either dropped or duplicated a word.
Relationship | Frequency | Phase | Typical cause | What crossing it needs |
|---|---|---|---|---|
Synchronous | Same (or integer ratio) | Known and fixed | One clock source and a designed clock tree | None: ordinary static timing |
Mesochronous | Exactly the same | Fixed but unknown | Same source, different, unknown delays (for example a clock forwarded across a chip) | Find the phase once, then sample safely (a small FIFO or a trained delay) |
Plesiochronous | Nominally the same, differing by ppm | Drifts slowly and steadily | Two oscillators with the same nominal frequency | A buffer that absorbs the drift and periodic adjustment (lesson 1.7) |
Asynchronous | Unrelated | No relationship | Independent clocks of any frequencies | A full clock-domain crossing: synchronisers, handshakes or an async FIFO |
The relationship is a property of the sources, not of the numbers on a datasheet. Two 100 MHz clocks from one oscillator are synchronous or mesochronous; two 100 MHz clocks from two oscillators are plesiochronous, however close their tolerances.
If one clock runs at f(1 + a) and the other at f(1 − b), their difference is f(a + b). The faster one gains one whole cycle every time that difference adds up to one cycle:
The interval in cycles depends only on the ppm difference, not on the frequency. A 600 ppm difference means one slip every 1/0.0006 ≈ 1,667 cycles at any frequency; at 100 MHz that is every 16.7 µs.
The model takes the worst case for two ±300 ppm clocks at 100 MHz, one fast and one slow, counts each clock's edges over 1 ms, and checks the result against the formula.
"""Two clocks that are 'the same frequency' still slip.
Each clock is 100 MHz with a tolerance of +/-300 ppm. In the worst case one runs
300 ppm fast and the other 300 ppm slow. Count the edges of each over 1 ms: the
difference is how many cycles one gains on the other.
"""
F = 100e6
f_fast, f_slow = F * (1 + 300e-6), F * (1 - 300e-6)
window = 1e-3 # 1 ms
edges_fast = int(f_fast * window)
edges_slow = int(f_slow * window)
slips = edges_fast - edges_slow
interval_cycles = F / (f_fast - f_slow) # cycles between slips
print(f"edges in 1 ms: fast clock {edges_fast}, slow clock {edges_slow}")
print(f"cycles gained per ms: {slips}")
print(f"one cycle gained every {interval_cycles:.0f} cycles ({interval_cycles / F * 1e6:.1f} us)")
assert slips == 60 and round(interval_cycles) == 1667
print("PASS: a 600 ppm difference at 100 MHz slips one cycle every 1667 cycles")It prints:
edges in 1 ms: fast clock 100030, slow clock 99970
cycles gained per ms: 60
one cycle gained every 1667 cycles (16.7 us)
PASS: a 600 ppm difference at 100 MHz slips one cycle every 1667 cyclesSixty times per millisecond, one side has one more cycle than the other. The opening link assumed zero, so its first slip, 16.7 µs after start, dropped or duplicated a word. The fix is not a better oscillator, which only stretches the interval, but a mechanism that expects slips and absorbs them: an elastic buffer, next lesson.
A mesochronous crossing never slips, because both sides come from one source. The only problem is that the receiver does not know where the transmitter's edges fall relative to its own. Once that phase is measured, by training at start-up or by a small FIFO whose read and write pointers settle at a safe distance, it stays put. This is cheaper than a full asynchronous crossing and common in large chips, where one clock is distributed over long distances and arrives with different, unknown delays.
Crossing | Cost | Use when |
|---|---|---|
Static timing, no crossing | None at run time | Synchronous clocks only |
Phase training or a small settled FIFO | A little logic and a start-up step | Mesochronous clocks |
Elastic buffer with filler insertion and deletion | A buffer plus filler symbols in the protocol | Plesiochronous clocks on a link that carries a continuous stream |
Async FIFO, handshake or synchronisers | Synchronisers, latency, design care | Asynchronous clocks; also works for the other cases, at a higher cost |
Choosing the cheapest correct crossing requires knowing the relationship. Choosing "synchronous" when the clocks are really plesiochronous is the most common and most costly mistake on this page.
Protocol | Clock relationship | Its answer |
|---|---|---|
PCI Express | Treated as plesiochronous: the two ends of a link may differ by up to 600 ppm (±300 ppm each) in PCIe 1.0–4.0, so receivers must tolerate it even when a shared reference clock makes the difference smaller | An elastic buffer at each receiver that adds or removes SKP symbols in SKP ordered sets (lesson 1.7) |
UART | Plesiochronous, with percent-level tolerance | Short frames that resynchronise on every start bit (lesson 1.2) |
On-chip buses between clock domains (for example AXI) | Asynchronous | A clock-domain-crossing bridge, typically an async FIFO per channel (lesson 1.9) |
A design passes data between two blocks that a specification described as "both 200 MHz" (illustrative). The integrator treated them as synchronous and connected them directly. In silicon the two clocks came from different PLLs locked to different references. They slipped once every few thousand cycles, and on each slip a register captured a value in transition. The symptom was a rare corrupted word, roughly at a steady rate, which pointed at the slip rate once someone thought to compute it.
Symptom: rare corruption at a steady average rate, independent of traffic content.
Evidence: the failure rate matched the ppm difference between the two PLLs' references.
Fix: a proper crossing, and an interface specification that names the clock sources, not just the frequencies.
1 / 0.0001 = 10,000 cycles, at any frequency.
Mesochronous: same frequency, unknown fixed phase. Measuring the phase once (training, or a small FIFO that settles) is enough; it will not drift.
It does not say whether the clocks share a source. Shared source: synchronous or mesochronous. Separate oscillators: plesiochronous, which slips and needs compensation.
Clock relationships are synchronous, mesochronous, plesiochronous or asynchronous, decided by the clock sources, not the nominal frequencies.
Plesiochronous clocks slip one cycle every 1/Δ cycles, where Δ is the fractional difference: 600 ppm means every 1,667 cycles.
Each relationship has a cheapest correct crossing; mistaking plesiochronous for synchronous is the costliest error.