Digital Design / Module 10: Clock domain crossing
56. Multi-bit transfers and handshakes
See why synchronizing each bit breaks a bus.
Lab: Clock crossing lab
The idea
Suppose you put a two-flop on each bit of a bus. Each bit can settle independently, so when several bits change together, some may show up a cycle later than others. For a moment, the destination holds a value that the source never sent.
For example, a bus changing from 0111 to 1000 flips all four bits. If some arrive early, the destination may briefly read 1111, or 0000, or anything else in between. That's why this approach is wrong for general data.
Solution 1: request and acknowledge
Don't synchronize the data. Hold it stable, and synchronize a single control signal.
- Source places data on the bus, then raises
req. - Destination synchronizes
reqand sees it high. The data has been stable for cycles now, so it's safe to capture. - Destination raises
ack. - Source synchronizes
ack, dropsreq, and can change the data on a later cycle.
// Source side
always_ff @(posedge clk_a)
if (!rst_a_n) req <= 1'b0;
else if (send && !req && !ack_sync) begin
data_hold <= data;
req <= 1'b1;
end else if (ack_sync) begin
req <= 1'b0;
endThis is safe at any clock ratio, but each transfer takes many cycles.
Solution 2: Gray code
If the value only ever steps by one, like a counter, then Gray encoding guarantees that one bit changes at a time. See the next lesson.
Solution 3: an async FIFO
This is the right answer when you need continuous data at speed. Its memory holds stable data while the Gray-coded pointers do the crossing.
How to choose
| Situation | Use |
|---|---|
| Single level signal | Two-flop synchronizer |
| Single pulse | Toggle synchronizer |
| Occasional multi-bit value | Request and acknowledge |
| Counter or pointer | |
| Streams of data | Async |
Common mistake
Believing that matching wire lengths would make all the bits arrive together. Even with perfect routing, each synchronizer's first flop resolves independently, which can create the of a whole cycle. Static timing can't save you here.
Key ideas
- Bits may settle in different destination cycles.
- Hold data stable and synchronize a single control signal.
- Request-acknowledge round trips add latency.
Your exercise
Interview questions
coreCan you put a 2-flop sync on each bus bit?
Not for general data: bits can be captured a cycle apart, producing a value that never existed.
basicGive the usual crossing method for each case: a single level, a single pulse, an occasional multi-bit value, a counter, and a continuous data stream.
A single level uses a two-flop synchronizer. A single pulse uses a toggle synchronizer. An occasional multi-bit value uses a request and acknowledge handshake, holding the data still. A counter or pointer that steps by one uses Gray code. A continuous stream uses an asynchronous FIFO, where the memory holds the data and only Gray pointers cross.
basicIn a request and acknowledge transfer, why does the data bus not need its own synchronizers?
The source loads the data into a holding register and only then raises request, and it does not change the data until the acknowledge comes back. By the time the destination sees the synchronized request, the data has been stable for a couple of cycles. So every bit is captured at a moment when none of them is changing. Only the single-bit request and acknowledge cross through synchronizers.
coreSoftware writes a 32-bit configuration register in clk_a, and the value is used in clk_b. It changes rarely. How would you cross it?
Hold the value in a register in clk_a and cross a single control bit that says it has changed, using a handshake or a toggle that is then synchronized in clk_b. When clk_b sees that synchronized control bit, it loads all 32 bits into its own register at once, because the source value has been stable for several cycles. The source must not write a new value until the previous one has been taken. Per-bit synchronizers on the 32 bits would be wrong, because bits could arrive on different edges.
coreA request and acknowledge handshake takes about 11 source cycles per word on a 100 MHz source clock. The application needs 50 million words per second. Is the handshake suitable?
At 100 MHz, 11 cycles per word gives 100 / 11, about 9.1 million words per second. That is far below the 50 million needed. Each word needs four synchronized crossings, so there is no way to make the round trip much shorter. A stream at this rate needs an asynchronous FIFO.
coreA colleague says the per-bit synchronizer problem on a bus can be fixed by matching every wire length exactly. Is that right?
No. Matched wires remove the skew from routing, but when an edge lands near the change, every first flop is in its window at the same moment, and each one resolves metastability on its own. One bit can settle to the new value and its neighbour to the old one, so the captured word can still be a mix that was never sent. Static timing does not help either, since these crossing paths are usually excluded from normal timing.
coreReview the source side of this handshake. What can go wrong if send stays high for several cycles?
always @(posedge clk_a)
if (!rst_a_n) req <= 1'b0;
else begin
if (send) data_hold <= data; // loads whenever send is high
if (send && !req && !ack_sync) req <= 1'b1;
else if (ack_sync) req <= 1'b0;
enddata_hold reloads whenever send is high, even while req is already high and a transfer is in flight. The destination may then capture the bus while it is changing, which is exactly the problem the handshake was meant to avoid. The data must only load at the moment a transfer starts, under the same condition that raises req. With that fix, the held value cannot change until the acknowledge has come back and req has dropped.
advancedA four-phase handshake needs request up, acknowledge up, request down and acknowledge down for every word. How can you make it faster without changing the safety argument?
Use a two-phase handshake: every change of the request level, up or down, means a new word, and every change of acknowledge answers it. Each side detects changes with an XOR after its synchronizer, as in a toggle synchronizer. That halves the number of crossings per word, from four to two, while the data is still held stable until the answering change returns. The cost is slightly more logic for the change detectors and a little more care at reset.
advancedIn a hold-and-signal transfer, which timing assumption about the data wires must still hold, and how is it usually checked?
The data must reach the destination flops before the synchronized control signal tells the destination to capture it. The control bit takes about two destination cycles to come through its synchronizer, so the data paths must arrive well within that time. If the data wires were very slow, the destination could capture before they settle. Teams usually bound those crossing data paths with a maximum delay constraint instead of leaving them fully unchecked.