FPGA 跨时钟域(CDC)处理:三种核心工程方案
在 FPGA 开发中,跨时钟域(Clock Domain Crossing, 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
设计要点:
- 必须使用两级寄存器,仅打一拍无法保证亚稳态完全恢复。
- 无需多打拍,会增加不必要的延迟和资源消耗。
- 该模板通用性强,适配不同频率的目标时钟。
2. 多比特信号:握手机制
对于数据总线、地址信号等多 bit 信号,直接打拍会导致各 bit 到达时间不一致,引发数据错乱。采用握手协议(Valid/Ack)可确保数据完整性。
流程说明:
- 发送方准备好数据,拉高 Valid 信号。
- Valid 信号经两级同步器传入接收方时钟域。
- 接收方检测到 Valid 后锁存数据,并拉高 Ack 应答。
- Ack 信号同步回发送方,通知其释放旧数据,准备下一组。
module cdc_handshake (
// 发送方 (clk_a)
input wire clk_a,
input wire rst_n,
input wire [15:0] data_a,
input wire data_vld_a,
// 接收方 (clk_b)
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;
// 1. Valid 同步到接收方域
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
// 2. 接收方逻辑:锁存数据 + 产生应答
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
// 3. Ack 同步回发送方域
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

