FPGA 跨时钟域 CDC 处理:3 种最实用的工程方案
1. 什么是跨时钟域 CDC?
核心场景:信号从一个时钟域(比如 clk_a)传到另一个时钟域(比如 clk_b); 触发条件:两个时钟的频率不同,或者相位无关(没有固定的时间关系); 直接后果:如果不做处理,直接打拍会出现亚稳态,进而导致数据错误,严重的还会让整个系统死机。
注意:只要是多时钟系统,就必须做 CDC 处理,这是企业级 FPGA 开发的基本要求。
2. 方案 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 信号统一用这个方案。
- 绝对不要只打一拍!只打一拍风险极大。
- 替换 clk_dst 即可适配不同频率。
3. 方案 2:多比特信号 —— 握手机制
适用场景:数据总线、地址信号、多 bit 控制信号。 核心思路:发送方准备数据并发送 valid,接收方同步 valid 后锁存数据并回传 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;
// 第一步:发送方 valid_a 同步到接收方 clk_b 域
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
// 第二步:接收方逻辑(锁存数据 + 产生应答 ack_b)
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
// 第三步:接收方 ack_b 同步到发送方 clk_a 域
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

