15 June 2022

Lib: clock/xd

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. This post was last updated in October 2022.

Get in touch: GitHub Issues, 1BitSquared Discord, @WillFlux (Mastodon), @WillFlux (Twitter)

Sponsor My Work
If you like what I do, consider sponsoring me on GitHub.
I love FPGAs and want to help more people discover and use them in their projects.
My hardware designs are open source, and my blog is advert free.

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]. Verilator test benches will be available later.

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!

What’s Next?

Project F is full of tasty FPGA content to help you with your hardware project.

Check out our how to guides, tutorials, and demos.

©2022 Will Green, Project F