跨时钟域(CDC)是 FPGA 设计中极易引发亚稳态和系统故障的关键环节。在多时钟系统中,信号跨越不同频率或相位无关的时钟域传输时,若处理不当,将导致数据错误或系统死机。以下介绍三种工程中稳定可靠的 CDC 处理方案。
跨时钟域基础
简单理解 CDC 需关注三点:
- 核心场景:信号从一个时钟域(如 clk_a)传到另一个时钟域(如 clk_b);
- 触发条件:两个时钟频率不同,或相位无固定关系;
- 直接后果:不做处理直接打拍会出现亚稳态,进而导致数据错误,严重时系统死机。
只要是多时钟系统,就必须做 CDC 处理,这是企业级开发的基本要求。
单比特信号:两级寄存器同步
适用于按键输入、使能信号、标志位等单 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 控制信号。禁止直接打拍,否则不同 bit 延迟不一致会导致数据错乱。
核心流程:
- 发送方准备好数据,发送 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;
// 第一步: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
注意:
- 修改位宽只需调整
data定义和复位值; - 通过'valid 同步→数据锁存→ack 同步'闭环确保稳定性。
批量数据传输:异步 FIFO
适用于高速图像、串口、以太网等批量数据。企业级标准方案。
核心要点:
- 读写指针必须用格雷码编码后再跨时钟域同步;
- 格雷码相邻数值仅 1bit 变化,避免采样错误。
Vivado IP 核配置步骤:
- 打开 Vivado,进入 IP Catalog,搜索并双击
FIFO Generator; - Basic 页:命名(如
async_fifo),勾选 Native; - FIFO Type 页:选择
Independent Clocks Block RAM(关键); - Write Interface 页:设置写时钟频率和数据位宽;
- Read Interface 页:设置读时钟频率和数据位宽(需与写一致);
- FIFO Depth 页:根据数据量设置深度;
- Flags 页:勾选 Full Flag 和 Empty Flag;
- Summary 页:核对参数后 Generate。
调用示例:
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 核默认高电平复位,需取反适配全局低电平复位;
- 端口名称必须与生成的一致;
wr_en仅在写时钟域控制,rd_en仅在读时钟域控制。
工程铁律
- 单 bit 信号统一用两级寄存器同步;
- 多 bit 信号禁止直接打拍,必须握手或 FIFO;
- 跨时钟域信号保持一个周期以上有效时间;
- 复位信号也需做跨时钟域同步;
- 工具报出的 CDC 警告不要忽略,90% 都是真实风险。
