Someone (not someone named "Someone" on this forum, someone else) did show me this paper:
http://web.cse.msu.edu/~cse820/readings/sutherlandMicropipelinesTuring.pdfLong 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:
: : : : : : : : : : : :
__:_______________:_______________:______________
handshake_data __X_______________X_______________X______________
: _______________: : : : __________
handshake_req ______/ : : : \_______________/ : :
: : : :_______________: : : : :__
handshake_ack ______________/ : : : \_______________/
: : : : : : : : : : : :
(1) (2) (3) (4) (1) (2) (3) (4) (1) (2) (3) (4)
- 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!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
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!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
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