Electronics > FPGA

A simple Clock Domain Crossing (CDC) strategy

(1/3) > >>

josuah:
Someone (not someone named "Someone" on this forum, someone else) did show me this paper:
http://web.cse.msu.edu/~cse820/readings/sutherlandMicropipelinesTuring.pdf

Long story (19 pages) short:
Two modules, each with its own clock domain are writing through one control wire each to the other module, as well as a data array of wires.
When the sender module changes its control wire "req", the other module adjusts its control wire "ack" likewise.
Before changing its wire, the sender module sets the data array.
Before adjusting its wire, the receiver module copies the data array.
That way, the data array is transferred when it is sure to be stable thanks to the control wires:


--- Code: ---                  :   :   :   :   :   :   :   :   :   :   :   :
                __:_______________:_______________:______________
handshake_data  __X_______________X_______________X______________
                  :    _______________:   :   :   :    __________
handshake_req   ______/   :   :   :   \_______________/   :   :
                  :   :   :   :_______________:   :   :   :   :__
handshake_ack   ______________/   :   :   :   \_______________/
                  :   :   :   :   :   :   :   :   :   :   :   :
                 (1) (2) (3) (4) (1) (2) (3) (4) (1) (2) (3) (4)

--- End code ---


* When the source has data to transfer, it first asserts `handshake_data` to the data to transfer (1) then inverts `handshake_req` (2).
* Once the destination notices it, it copies `handshake_data` to a local register (3) then sets `handshake_ack` to the same value as `handshake_req` (4).
If I got it right, it should give us something like this for the source that exports data:

See comment below: handshake_ack_x is one flip flop, there need to be two in series!


--- Code: ---module clock_domain_export #(
parameter SIZE = 8
) (
input wire clk,

// data submission
input wire [SIZE-1:0] data,
input wire stb,
output wire ready,

// handshake with the other clock domain
output reg [SIZE-1:0] handshake_data,
output reg handshake_req,
input wire handshake_ack
);
reg handshake_ack_x;

assign ready = (handshake_ack_x == handshake_req);

always @(posedge clk) begin
// prevent metastable state propagation
handshake_ack_x <= handshake_ack;

if (ready && stb) begin
handshake_data <= data;
handshake_req <= !handshake_req;
end
end
endmodule

--- End code ---

And this on the other clock domain, importing the data:

See comment below: handshake_req_x is one flip flop, there need to be two in series!


--- Code: ---module clock_domain_import #(
        parameter SIZE = 8
) (
        input wire clk,

        // data reception
        output reg [SIZE-1:0] data,
        output reg stb,

        // handshake with the other clock domain
        input wire [SIZE-1:0] handshake_data,
        input wire handshake_req,
        output reg handshake_ack
);
        reg handshake_req_x = 0;

        always @(posedge clk) begin
                // prevent metastable state propagation
                handshake_req_x <= handshake_req;

                stb <= 0;
                if (handshake_req_x != handshake_ack) begin
                        data <= handshake_data;
                        stb <= 1;
                        handshake_ack <= handshake_req_x;
                end
        end
endmodule

--- End code ---

This seems to work rather well in simulation, but I am surprised by the size of the code required.
In the end, CDC would be one of these topic hard to be proven right and debug (works on the test bench, fails on the client's hands, works again when returned back), but not necessarily requiring complex code?

What do you think with my naive approach?

Related post: https://www.eevblog.com/forum/fpga/learning-clock-domain-transitions-on-fpgas-(xilinx)/msg1286966/#msg1286966

ataradov:
Overall,l this is a very typical and standard way to synchronize a single word transfer.

But your implementation is not correct. Both REQ and ACK need to be 2FF synchronized to the target clock domain. You can't simply sample a signal generated in one domain with a clock from another domain. You will run into cases where setup/hold times are not met. 2FF synchronizer will prevent this.

Your single register (handshake_req_x) does not prevent meta-stability, and that potentially meta-stable level gets used in the logic. This is not going to work.

Doing those synchronization slows down the transfer a lot, and this method is not very useful for sustained transfers. In this case FIFO with Gray counter pointers is used.

Edit: wow, that's some vintage article.

AndyC_772:
I use this technique all the time. It's ideal for things like register settings, where data is written to an SPI slave interface in the FPGA, but must end up in the same clock domain as the bulk of the FPGA's internal logic.

Even without any data being transferred at all, it's still a useful handshake mechanism for two processes to communicate an event to each other. One changes the state of a signal to indicate that 'something has happened'; the other detects that the signal and its corresponding ack are different, performs some action, and sets the value of the ack so that the two are equal again. The actual state (ie. whether they're both 1 or both 0) doesn't matter.

It's a handy workaround for the fact that a signal can't be set in more than one place. In conventional software, we'd just use a flag which is set in one process and cleared in another, but in hardware we effectively manipulate the two inputs of an XOR gate to set the output to a desired value.

josuah:

--- Quote from: AndyC_772 on May 19, 2022, 06:13:38 am ---It's ideal for things like register settings, where data is written to an SPI slave interface in the FPGA, but must end up in the same clock domain as the bulk of the FPGA's internal logic.

--- End quote ---

This is my use-case! Well, for a Wishbone master driven by SPI...


--- Quote from: AndyC_772 on May 19, 2022, 06:13:38 am ---manipulate the two inputs of an XOR gate to set the output to a desired value

--- End quote ---

A very concise summary of what happens.


--- Quote from: ataradov on May 19, 2022, 05:56:01 am ---But your implementation is not correct. Both REQ and ACK need to be 2FF synchronized to the target clock domain. You can't simply sample a signal generated in one domain with a clock from another domain. You will run into cases where setup/hold times are not met. 2FF synchronizer will prevent this.

Your single register (handshake_req_x) does not prevent meta-stability, and that potentially meta-stable level gets used in the logic. This is not going to work.

--- End quote ---

Thank you a lot! The fact there was only one flip flop slipped through somehow. One of these cases that could not be caught by simulation.


--- Quote from: ataradov on May 19, 2022, 05:56:01 am ---Doing those synchronization slows down the transfer a lot, and this method is not very useful for sustained transfers. In this case FIFO with Gray counter pointers is used.

--- End quote ---

If I got it right, A FIFO with Gray Counter would still need a flip-flop pair but permit better throughput. I will read more on that.

AndyC_772:
The nice thing about using a FIFO is that your FPGA's tool chain almost certainly includes this functionality. You get to create and instantiate a FIFO component with the desired depth, width and logic signals (read request / acknowledge, full and empty flags etc), and the tools take care of the underlying logic.

It's academically interesting to note that there's double sampling and conversion to and from Gray codes going on in there somewhere, but you never have to actually see or do these things yourself.

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod