cswork / libraryOpen practice workspace

Articles

Clock Domain Crossing Interview Exercises: Handshake Failures and Async FIFO Verification

Practice clock domain crossing interviews with handshake failure traces, reset recovery, Gray pointers, async FIFO scoreboards, and verification exercises.

Clock domain crossing interview exercises test whether a design transfers information correctly between clocks that do not share a guaranteed timing relationship. A synchronizer can reduce the probability of metastability propagating, but it does not by itself guarantee that every event arrives, that a multi-bit word is coherent, or that both domains recover consistently after reset.

This guide focuses on handshake failures and asynchronous FIFO verification. The exercises are original and use explicit assumptions: independent running clocks, a defined reset protocol, and transfers counted only when the interface accepts them. Vendor macros have their own contracts; a generic explanation is not a substitute for those instructions.

Here, we go deeper on what happens after you choose a crossing structure: how to prove its protocol, test recovery, and distinguish safety from eventual progress.

What must I clarify before choosing a CDC structure?

Ask what crosses: a slowly changing level, an event, a multi-bit control word, or a stream of data. Then ask how often it changes, whether loss is allowed, whether the source can wait, and whether either clock can stop.

A status level may tolerate synchronization latency. A one-cycle event may not remain visible long enough for a slower destination to sample it. A multi-bit word requires a coherence contract; independently synchronizing each bit does not guarantee that all bits belong to the same source value.

State throughput and backpressure requirements. A full request/acknowledge handshake can transfer coherent values when the source holds data appropriately, but its round-trip latency limits how quickly a single outstanding transfer can repeat. A sustained stream may need an asynchronous FIFO.

Finally, define reset behavior before drawing logic. Are in-flight transfers discarded, retried, or guaranteed to complete? Can only one side reset? If the answer is unknown, you cannot yet prove that the crossing preserves the intended event history.

Why are two flip-flops not a complete protocol?

A synchronizer chain gives a potentially metastable first stage time to resolve before downstream logic uses a later stage. It reduces risk; it does not create an absolute guarantee of zero metastability. Physical implementation, timing constraints, placement, and clock/data rates affect the reliability analysis.

More importantly, synchronization and delivery are different properties. A level that changes too quickly can be missed. Two independently synchronized control signals can arrive in different cycles. A binary counter crossing bit by bit can be observed as an incoherent combination during multi-bit transitions.

An interview answer should therefore name both the structural synchronizer and the protocol around it. For example: "The request is synchronized into the destination, while the source holds the associated data stable until acknowledgment returns." That is much more informative than "I add two flops."

Do not put combinational logic between synchronizer stages casually or fan out the first stage into functional logic. Use established vendor structures or reviewed patterns, then check the implemented CDC report. Clean RTL simulation cannot demonstrate analog metastability behavior.

Can you trace a four-phase handshake?

Consider an original single-outstanding-word interface. The source presents word A, holds it stable, and raises request. The destination synchronizes request, captures A under the design's data-stability contract, and raises acknowledgment. The source synchronizes acknowledgment and lowers request. The destination observes request low and lowers acknowledgment.

Only after the source observes acknowledgment low again is the full four-phase exchange complete and ready for another transfer. Treating acknowledgment high as permission to immediately start a new request can merge transactions if the return-to-zero phase has not completed.

PhaseSource obligationDestination obligation
IdleRequest low; wait for acknowledgment lowAcknowledgment low
RequestHold payload stable; request highCapture once after valid request recognition
AcknowledgeWait for synchronized acknowledgmentHold acknowledgment high
Return to idleLower request; do not start earlyLower acknowledgment after request drops

AMD's handshake macro documentation similarly requires a full handshake before another transfer. Exact signal names, capture timing, and internal versus external acknowledgment modes belong to the selected macro contract, not this generic table.

Exercise: why did the destination capture the second word twice?

Suppose the source launches A, then changes the payload to B before acknowledgment returns. The destination's synchronized request arrives later than the source expected. It captures B for the first request, and a later request captures B again. No individual synchronizer needs to fail for the protocol to lose A.

The broken invariant is payload stability during an outstanding transfer. Repair the ownership rule: after launch, the source must retain the payload until the protocol allows it to release or replace it. If new data arrives earlier, buffer it or apply backpressure according to the interface contract.

Your verification should record accepted source transfers and destination deliveries by ID, not just compare the most recent payload. Repeated equal payloads can hide duplication; use distinct sequence numbers in the test.

Now make the destination clock much slower. The repaired design should still preserve A, with increased latency. If the source assumes acknowledgment arrives within two source cycles, the timing assumption is still wrong. Synchronizer depth is measured in the receiving domain's edges, not a universal wall-clock deadline.

What happens if one clock domain resets mid-handshake?

Reset can destroy one side's memory of the protocol while the other side still believes a transfer is outstanding. A request left high may look like a new event after reset, or an acknowledgment reset low may cause a source to wait forever. The outcome depends on the chosen encoding and reset sequence.

Define a recovery policy. One valid system-level choice is to quiesce both sides, reset the crossing coherently, discard in-flight work, and restart in an agreed idle state. Another design may include an epoch or explicit recovery handshake. Neither policy should be assumed without a requirement.

Asynchronous assertion with synchronous deassertion is a common reset practice, but it does not automatically establish protocol agreement across two independent domains. Each side still needs a safe release and a shared understanding of when transfers may resume.

Test reset during idle, request propagation, capture, acknowledgment propagation, and return-to-idle. For each phase, state whether A is allowed to disappear, be retried, or appear exactly once. Without that expected behavior, a waveform is merely a picture, not a verification result.

When should I choose an asynchronous FIFO instead?

Use an asynchronous FIFO when producer and consumer need decoupled progress across independent clocks and a bounded buffer fits the traffic contract. It can accept bursts without completing a full round-trip handshake for every individual word, while full and empty provide local flow-control decisions.

A common architecture stores data in dual-port memory, advances a write pointer in the write domain, and advances a read pointer in the read domain. Encoded pointer information crosses between domains so each side can conservatively determine available space or data.

Do not describe a FIFO as an infinite rate converter. If the long-term producer rate exceeds the consumer rate, any finite buffer eventually fills. The system must throttle, drop according to an explicit policy, or increase downstream capacity.

Depth selection should therefore use a burst and stall model. For an original example, a producer can issue a 12-word burst while the consumer is stalled. A buffer smaller than that burst cannot absorb it without backpressure unless some words were already consumed or loss is allowed. Add margin from actual timing and occupancy requirements, not an arbitrary power-of-two guess.

Why are Gray-coded pointers used?

In a common power-of-two asynchronous FIFO design, binary pointers address memory locally while Gray-coded pointer representations cross domains. Adjacent Gray-code values differ by one bit, reducing ambiguity from a single pointer increment compared with a binary transition that flips several bits.

For a three-bit reflected Gray sequence, the first values are 000, 001, 011, 010, 110, 111, 101, 100. The encoding is gray = binary ^ (binary >> 1). This is an original small trace of the coding rule, not a complete FIFO implementation.

Gray coding does not remove the need for synchronizers or physical timing constraints. Excessive skew between routed bits can undermine the intended transition behavior. Also, a slower destination may skip several source counts, so successive sampled pointer values need not differ by one bit even though each source increment does.

That last distinction matters for assertions. Check a one-bit transition property in the source pointer's own domain for one-step advances. Do not blindly assert that every pair of destination samples differs by at most one bit when the source may advance multiple times between them.

How do full and empty checks avoid overwriting data?

The read domain compares its local pointer with synchronized write information to determine empty. The write domain compares its local pointer with synchronized read information to determine full. Because remote information arrives late, flags can remain conservative for a while after space or data becomes available.

For the conventional extra-bit reflected-Gray implementation, full detection involves the wrap relationship, commonly expressed by comparing the next write Gray pointer with a version of the synchronized read Gray pointer whose top two bits are inverted. This rule belongs to that specific encoding and architecture; it is not a universal formula for every FIFO.

In an interview, derive the pointer-width and wrap assumptions before writing the comparison. A depth-eight FIFO typically uses three address bits plus an extra wrap bit in this common design. Comparing only address bits cannot distinguish equal positions after a full lap from an empty queue.

Do not bypass a full flag because you believe the consumer "probably just read." The write domain must follow its local acceptance contract. A delayed full deassertion costs throughput; an optimistic unsupported write can destroy unread data.

How should I verify an asynchronous FIFO?

Build a scoreboard from accepted transactions. On a valid accepted write, enqueue the payload in a reference queue. On a valid accepted read, compare the returned payload with the oldest expected value, accounting for the FIFO's documented output latency and read mode.

Do not equate rd_en with same-cycle output validity unless the selected FIFO mode guarantees it. Standard-read and first-word-fall-through interfaces differ. Vendor reset-busy signals also matter: AMD's asynchronous FIFO documentation specifies restrictions around reset and operation readiness.

Exercise empty-to-nonempty and full-to-not-full transitions, repeated pointer wrap, simultaneous activity, long producer bursts, long consumer stalls, and unrelated clock periods. Vary relative phase as well as frequency. A test using equal clocks with aligned edges can miss the most interesting cases.

Assertions should protect safety: no accepted write when full, no accepted read when empty, payload order preserved, and no unexpected duplication within a reset epoch. Define how reset flushes the scoreboard and when transfers are permitted again.

Liveness needs assumptions. "Every accepted word is eventually read" is impossible to prove if the consumer may stop forever. State that clocks keep running and the consumer eventually accepts data, then formulate the progress property under those conditions.

What can simulation prove, and what remains outside it?

RTL simulation can expose protocol violations, incorrect flags, reset mismatches, and ordering bugs. It does not model analog metastability faithfully enough to prove a synchronizer's physical reliability. Structural CDC analysis, timing constraints, implementation review, and device-specific guidance complement functional tests.

Formal verification can strengthen safety and liveness reasoning under explicit assumptions, but an unrealistic model can prove an irrelevant property. Review clock, reset, environment, and fairness assumptions just as carefully as the assertions.

For your final practice exercise, explain the lost-A handshake trace and the depth-eight FIFO scoreboard without code. Name the invariant, the failure mechanism, the repair, and the evidence needed after implementation.

The strongest CDC answer never stops at "use a synchronizer." It identifies what information must survive, which side owns it during transfer, and how both domains know when they may safely move on.

Sources and Further Reading

Checked September 23, 2026. Macro-specific behavior is scoped to the linked AMD versions; generic examples do not replace an IP integration guide.