Basic CDC: Slow → Fast

from FPGA Resources by GateForge Consulting Ltd.

You can design plain synchronous logic and be cocksure it'll work (and you'd be right)...or you can design Clock-Domain-Crossing (CDC) logic, and always be full of doubt. CDC logic is a well-understood, but subtle design problem. There are many ways to get it slightly wrong, causing intermittent failures.

Here, I'll explain the design of a custom bit of CDC logic connecting an external microcontroller unit (MCU) bus to an FPGA. The MCU bus has a synchronous protocol without wait states, which means that reads and writes take a fixed number of MCU clock cycles and you have to send/receive data at specific cycles in the transfer. The MCU and FPGA clocks are completely asynchronous, though the FPGA clock is always the faster one. Passing data across clock domains is a given, but the extra challenge here is passing the MCU clock also, so the FPGA can keep count of the MCU clock cycles despite operating on a completely unrelated, faster clock.


A synchronizer forms the core of any CDC solution: a chain of two closely placed registers, with no logic between them, and clocked by the receiving clock. The input register receives a bit synchronized to a sending clock, and the output register sends a version of that bit synchronized to the receiving clock. The output version may be of a different length than the original bit, with a new duration of some multiple of the receiving clock period.

// A basic Clock Domain Crossing synchronizer

// This can only ever be correct for 1 bit.
// DO NOT MAKE IT WORD-WIDE.

module cdc_synchronizer
(
    input   wire    data_from,
    input   wire    clock_to,
    output  reg     data_to
);

    // There should never be a need to change this.
    localparam DEPTH = 2;

    // Tell Vivado that these reg should be placed together (UG912),
    // and to show up as part of MTBF reports.
    (* ASYNC_REG = "TRUE" *)
    reg [DEPTH-1:0] sync_reg = 0;

    always @(posedge clock_to) begin
        sync_reg[0] <= data_from;
        sync_reg[1] <= sync_reg[0]; 
    end

    always @(*) begin
        data_to <= sync_reg[1];
    end

endmodule

Despite having two registers, a synchronizer has an unpredictable latency from the point of view of the sending clock. A few different cases may happen, which will also explain why a synchronizer is necessary:

  1. The receiving clock captures the incoming bit properly, which then exits the synchronizer on the next cycle. Latency: 2 cycles.
  2. The receiving clock captures the incoming bit just before a transition, so that transition will pass through after two more cycles. Latency: 3 cycles.
  3. The receiving clock captures the incoming bit as it transitions, causing the first register output to go metastable, which then settles into the correct post-transition state and exits in the following cycle. Latency: 2 cycles.
  4. The receiving clock captures the incoming bit as it transitions, causing the first register output to go metastable, which then settles into the incorrect pre-transition state (no change). The post-transition bit is properly captured in the next cycle, and exits in the cycle after that. Latency: 3 cycles.

Thus, depending on the alignment of the sending clock relative to the receiving clock at that particular point in time, the output high and low phases of a toggling bit may have different lengths, though the total period remains the same. Also, the synchronizer prevents metastable state from propagating into the rest of the logic, possibly causing an incorrect bit value to be created.

The variable latency of a synchronizer implies an important design rule of CDC: only one bit at a time may ever change when crossing clock domains. If you tried to synchronize two bits in parallel, there would be no guarantee that both bits would always see the same latency through the synchronizer. With only a single bit, the worst case is that its transition is missed, and will get captured in the next receiving clock cycle. This delay causes no errors in itself, and only alters the received bit duration by one receiving clock cycle.


How fast can we pass a bit through a synchronizer? The input bit needs to remain stable long enough for the receiving clock to be always able to properly capture the bit between two of its transitions (a cycle), else we could miss a pulse (which matters greatly in this design, but may not in others). We can guarantee a proper capture if the minimum time between bit transitions is equal or greater to 3 edges of the receiving clock, regardless of clock alignment:

  1. The 3 receiving clock edges are positive-negative-positive. Thus, if the first posedge misses the first bit transition or goes metastable (see above), the second one will capture the bit properly before the second transition.
  2. The 3 receiving clock edges are negative-positive-negative. Since the posedge is squarely in the middle between the bit transitions, the bit is properly sampled.

So, in the worst case of a bit which toggles each sending clock cycle, the receiving clock must run at least 1.5x faster to provide 3 edges between bit transitions. In the particular CDC design I am describing, the worst case is even worse: the fastest toggling bit is the sending clock itself, so each high and low phase must last 1.5x periods of the receiving clock, meaning the sending clock must be 3x slower than the receiving clock at a minimum.

This 1/3 sending/receiving ratio is another way to express the worst case synchronizer latency of 3 cycles explained earlier: 3 receiving clock edges per incoming bit transition means 6 edges total over a cycle (two transitions), which necessarily contain 3 positive edges.

This 1/3 ratio is a stiff penalty, but peripherals are usually slower than core logic, and this approach keeps the peripheral and the core logic in lock-step without any other information needed: the sending clock signal, synchronized to the receiving clock, allows us to count how much time has passed in the peripheral so we can respond to it at just the right moment, as if the core logic was running on the same clock as the peripheral.

In hindsight, passing a Grey counter value driven by the sending clock, instead of the sending clock itself, might double the performance of this circuit, while still keeping the core logic synchronized to the peripheral. It would be a much more complex circuit though.


Given a CDC synchronizer, we can start building a simple circuit to pass a slow clock and its associated data to a faster clock. We first capture all the data into registers driven by the slow clock. These registers would usually be the dedicated I/O registers at each pin of an FPGA, and serve multiple purposes:

Since these data were registered in their original clock domain, they will not be metastable, so we only have to hold them steady long enough to be properly captured by the fast clock.

The slow clock passes through a CDC synchronizer to filter out metastability, and then through a posedge pulse generator to convert the variable duration of the synchronized clock high phase into a single pulse lasting the duration of one fast clock cycle. This pulse then enables the fast clock to register the data after it has been stable for long enough and before it changes again. If we didn't use the pulse generator, the data might change after its first capture by the fast clock and get captured again while the synchronized slow clock bit was still high. We want to capture the data only once, as close as possible to the rising edge of the synchronized slow clock.

Finally, we delay the synchronized slow clock by one cycle to re-align it with the data, so from the fast clock's perspective the data changes at each rising edge of the synchronized slow clock. If we didn't do this, we could not pass the synchronized slow clock through any logic driven by the synchronized data: any gating of that clock while high would create a false rising edge and corrupt our cycle counting.


This article only scratches the surface of CDC, and glosses over accounting for propagation delay, setup, and hold times when determining the maximum data rate, or CAD tool issues regarding placement, timing analysis, and MTBF (Mean Time Between Failure) calculations. Designing even simple CDC circuits requires sweating a lot of details!

See Clifford E. Cummings' excellent paper Clock Domain Crossing (CDC) Design & Verification Techniques Using SystemVerilog for a larger and deeper overview of CDC.


fpgacpu.ca