FPGA 跨时钟域(CDC)处理方案
1. 什么是跨时钟域(CDC)
跨时钟域是指信号从一个时钟域传输到另一个时钟域。主要特征包括:
- 核心场景:信号从源时钟域(如 clk_a)传到目标时钟域(如 clk_b)。
- 触发条件:两个时钟频率不同,或相位无关。
- 潜在风险:若未做处理直接打拍,会出现亚稳态,导致数据错误或系统死机。
在多时钟系统中,必须进行 CDC 处理,这是 FPGA 开发的基本要求。
2. 方案一:单比特信号同步(两级寄存器)
适用场景:按键输入、使能信号、标志位、单 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
注意事项:
- 两级寄存器足以抵御大部分亚稳态,无需多打拍。
- 禁止只打一拍,否则亚稳态未稳定即输出。
- 模板通用,适配不同频率的目标时钟。
3. 方案二:多比特信号握手机制
适用场景:数据总线、地址信号、多 bit 控制信号。
核心思路:
- 发送方准备好数据并发送 valid 信号。
- 将 valid 信号通过两级同步器同步到接收方时钟域。
- 接收方检测到 valid 后锁存数据,并发送 ack 应答。
- 将 ack 信号同步回发送方,确认接收完成。
注意:多 bit 信号禁止直接打拍,会导致位间延迟不一致引发数据错乱。
代码示例:
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;
// 第一步: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
// 第二步:接收方逻辑(锁存 + 应答)
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
4. 方案三:异步 FIFO
适用场景:高速数据传输、批量数据处理(图像、串口、以太网等)。
核心要点:
- 读写指针使用格雷码编码,避免跨时钟域时多 bit 同时变化导致采样错误。
- 工程通常直接调用 IP 核(如 Vivado FIFO Generator),而非手写。
Vivado 配置步骤:
- 打开 IP Catalog,搜索 "FIFO",选择 "FIFO Generator"。
- Basic:命名 IP 核,勾选 Native。
- FIFO Type:选择 "Independent Clocks Block RAM"(异步 FIFO)。
- Write Interface:设置写时钟频率、数据位宽。
- Read Interface:设置读时钟频率、数据位宽(需与写一致)。
- FIFO Depth:根据数据量设置深度。
- Flags:勾选 Full Flag 和 Empty Flag。
- Data Counts:可选勾选计数功能以便调试。
- Summary:核对参数后 Generate。
IP 核调用示例:
module async_fifo_top (
input wire clk_a, // 写时钟
input wire clk_b, // 读时钟
input wire rst_n, // 全局复位
input wire [15:0] wr_data,
input wire wr_en,
input wire rd_en,
output wire [15:0] rd_data,
output wire full,
output wire empty
);
async_fifo async_fifo_inst (
.rst (~rst_n),
.wr_clk (clk_a),
.rd_clk (clk_b),
.din (wr_data),
.wr_en (wr_en),
.rd_en (rd_en),
.dout (rd_data),
.full (full),
.empty (empty)
);
endmodule
调用说明:
- 注意 IP 核默认复位高电平有效,需适配全局低电平复位。
- 端口名称必须与 IP 核生成的一致。
- wr_en 仅在写时钟域控制,rd_en 仅在读时钟域控制。
5. CDC 设计原则
- 单 bit 信号统一用两级寄存器同步。
- 多 bit 信号禁止直接打拍,使用握手或异步 FIFO。
- 跨时钟域信号保持一个周期以上有效时间。
- 复位信号也需做跨时钟域同步。
- 重视开发工具报告的 CDC 警告,排查潜在风险。
