Project F

Lib: clock/xd

Published · Updated

Sometimes you need to send a single pulse from one clock domain to another. This is a simple case of clock domain crossing or CDC. This post uses the xd module from the Project F Library to handle such situations simply and safely.

Crossing the Streams

The standard advice for safely handling an async signal is to pass it through a pair of flip-flops. This works well if your source signal is lower frequency than your destination, such as a button press or UART. However, this approach fails when the source signal has a similar or higher frequency than the destination.

A FIFO will work correctly for any frequencies, but this is relatively complex and logic-heavy. Instead, we can use a simple “toggle” trick for an isolated pulse to safely cross domains, irrespective of their frequencies.

Thanks to fpga4fun for this approach to CDC.

XD Module

Project F Library module [clock/xd.sv]:

module xd (
    input  wire logic clk_src,   // source domain clock
    input  wire logic clk_dst,   // destination domain clock
    input  wire logic flag_src,  // flag in source domain
    output      logic flag_dst   // flag in destination domain
    );

    // toggle reg when pulse received in source domain
    logic toggle_src = 1'b0;
    always_ff @(posedge clk_src) toggle_src <= toggle_src ^ flag_src;

    // cross to destination domain via shift reg
    logic [3:0] shr_dst = 4'b0;
    always_ff @(posedge clk_dst) shr_dst <= {shr_dst[2:0], toggle_src};

    // output pulse when transition occurs
    always_comb flag_dst = shr_dst[3] ^ shr_dst[2];
endmodule

There’s a Vivado test bench you can use to exercise the module with Vivado: [clock/xc7/xd_tb.sv].

Example

My designs often have separate pixel and system clocks. When a new frame starts, we must pass the event from the pixel to the system clock domain:

    logic frame;      // start of new frame (pixel clock domain)
    logic frame_sys;  // start of new frame (system clock domain)
    xd xd_frame (
        .clk_src(clk_pix),
        .clk_dst(clk_sys),
        .flag_src(frame),
        .flag_dst(frame_sys)
    );

You can see this module in action in many Project F designs, including Framebuffers and 2D Shapes.

Caution Advised

Before you rush to add new clock domains to your designs, you need to be aware of a significant limitation: this approach only works for isolated pulses of one clock cycle. The clock/xd module is ideal for sending well-spaced events but can’t be used to send arbitrary data from one clock domain to another: use dual-port BRAM or a proper FIFO instead.

The following simulation shows what happens if you abuse the module:

xd module simulation

Note how the final two-cycle pulse becomes two separate pulses when sent from slow to fast but disappears altogether when sent from fast to slow. You have been warned!