FPGA 跨时钟域 CDC 处理的三种工程方案
跨时钟域(CDC)是 FPGA 设计中容易引发亚稳态和系统不稳定的关键问题。以下介绍三种工程中稳定可靠的解决方案。
什么是跨时钟域 CDC?
核心场景涉及信号从一个时钟域传到另一个时钟域。触发条件为两个时钟频率不同或相位无关。若不处理直接打拍会出现亚稳态,导致数据错误或系统死机。多时钟系统必须进行 CDC 处理。
方案 1:单比特信号 —— 两级寄存器同步
适用场景包括按键输入、使能信号、标志位等单 bit 控制信号。
module sync_2d(
input wire clk_dst,
input wire rst_n,
input wire din,
output wire dout
);
reg q1, q2;
always @(posedge clk_dst or negedge rst_n) begin
if(!rst_n) begin
q1 <= 1'b0;
q2 <= 1'b0;
end else begin
q1 <= din;
q2 <= q1;
end
end
assign dout = q2;
endmodule
关键点:两级寄存器足够抵御大部分亚稳态,工程里单 bit 信号统一用此方案。绝对不要只打一拍。
方案 2:多比特信号 —— 握手机制
适用场景为数据总线、地址信号等多 bit 控制信号。多 bit 信号禁止直接打拍,否则会导致不同 bit 信号同步延迟不一致,出现数据错乱。
核心思路:发送方准备好数据并发送 valid 信号;接收方同步 valid 后锁存数据并发送 ack 应答;ack 同步回发送方确认。
module cdc_handshake(
input wire clk_a,
input wire rst_n,
input wire [15:0] data_a,
input wire data_vld_a,
input wire clk_b,
output reg [15:0] data_b,
output reg data_vld_b
);
reg valid_a_sync1, valid_a_sync2;
reg ack_b, ack_b_sync1, ack_b_sync2;
reg data_lock;
always @(posedge clk_b or negedge rst_n) begin
if(!rst_n) begin
valid_a_sync1 <= 1'b0;
valid_a_sync2 <= 1'b0;
end else begin
valid_a_sync1 <= data_vld_a;
valid_a_sync2 <= valid_a_sync1;
end
end
always @(posedge clk_b or negedge rst_n) begin
if(!rst_n) begin
data_b <= 16'd0;
data_vld_b <= 1'b0;
ack_b <= 1'b0;
data_lock <= 1'b0;
end else begin
case(valid_a_sync2)
1'b1: begin
if(!data_lock) begin
data_b <= data_a;
data_vld_b <= 1'b1;
data_lock <= 1'b1;
ack_b <= 1'b1;
end else begin
data_vld_b <= 1'b0;
end
end
1'b0: begin
data_vld_b <= 1'b0;
ack_b <= 1'b0;
data_lock <= 1'b0;
end
endcase
end
end
always @(posedge clk_a or negedge rst_n) begin
if(!rst_n) begin
ack_b_sync1 <= 1'b0;
ack_b_sync2 <= 1'b0;
end else begin
ack_b_sync1 <= ack_b;
ack_b_sync2 <= ack_b_sync1;
end
end
endmodule
代码要点补充:
- 数据位宽:可根据需求修改位宽和复位值。
- 时钟适配:clk_a 和 clk_b 可任意频率。
- 复用性:仅需根据多 bit 信号位宽修改,无需调整握手逻辑。
方案 3:异步 FIFO
适用场景为高速数据传输、批量数据处理。核心要点:读写指针必须用格雷码编码后再跨时钟域同步。
基本结构:写时钟域负责写入及产生满信号,读时钟域负责读出及产生空信号。指针同步通过格雷码编码加两级同步器实现。
Vivado 异步 FIFO IP 核配置步骤:
- 打开 Vivado 软件,进入项目,点击'IP Catalog',搜索'FIFO Generator'。

