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!