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.
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:
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?
Discover other library modules or check out my FPGA & RISC-V Tutorials.
Get in touch on Mastodon, Bluesky, or X. If you enjoy my work, please sponsor me. 🙏