跨时钟域(CDC, Clock Domain Crossing)是 FPGA 设计中极易引发亚稳态和系统故障的关键环节。在多时钟系统中,信号从一个时钟域传递到另一个时钟域时,若未做妥善处理,可能导致数据错误甚至系统死机。以下是工程中稳定可靠、可直接应用的 3 种 CDC 处理方案。
1. 什么是跨时钟域 CDC?
理解 CDC 需掌握 3 个关键点:
- 核心场景:信号从一个时钟域(如 clk_a)传到另一个时钟域(如 clk_b);
- 触发条件:两个时钟频率不同,或相位无关(无固定时间关系);
- 直接后果:若不处理直接打拍,会出现亚稳态,导致数据错误,严重时系统死机。
注意:只要是多时钟系统,就必须做 CDC 处理,这是企业级 FPGA 开发的基本要求。
2. 方案 1:单比特信号 —— 两级寄存器同步
适用场景:按键输入、使能信号、标志位、单 bit 控制信号(如中断请求、数据有效标志)。
代码模板:
module sync_2d (
input wire clk_dst, // 目标时钟(信号要传到的时钟域)
input wire rst_n, // 全局复位,低电平有效
input wire din, // 异步输入(来自另一个时钟域的单 bit 信号)
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 信号统一用此方案,无需多打拍;
- 绝对不要只打一拍,风险极大,亚稳态未稳定即输出易出 bug;
- 模板可直接复用,替换 clk_dst 即可适配不同频率。
3. 方案 2:多比特信号 —— 握手机制
适用场景:数据总线、地址信号、多 bit 控制信号(如 8bit 数据、16bit 配置信号)。多 bit 信号不能用方案 1 直接打拍。
核心思路:
- 发送方:准备好数据,发送 valid 有效信号;
- 同步 valid:将 valid 信号用方案 1 的两级同步器同步到接收方时钟域;
- 接收方:检测到同步后的 valid,锁存数据;
- 应答同步:接收方发送 ack 应答,同步回发送方,告知'数据已接收'。
重点提醒:多 bit 信号禁止直接打拍,会导致不同 bit 信号同步延迟不一致,出现数据错乱。
代码模板(16bit 数据为例):
// 多比特跨时钟域握手机制,发送方 clk_a,接收方 clk_b
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;
reg valid_a_sync2;
reg ack_b;
reg ack_b_sync1;
reg 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
代码要点:
- 数据位宽可修改(如 8bit、32bit),调整位宽和复位值即可;
- 时钟频率任意(无关、不同频均可);
- 通过'valid 同步→数据锁存→ack 同步'闭环,确保多 bit 数据稳定跨时钟域。
4. 方案 3:异步 FIFO(批量数据)
适用场景:高速数据传输、批量数据处理(如图像、串口、以太网、AD 采集数据)。
核心要点:
- 读写指针必须用格雷码编码后,再跨时钟域同步;
- 格雷码优势:相邻数值仅 1bit 变化,避免多 bit 同时变化导致采样错误。
基本结构:
- 写时钟域:写入数据,产生 full 信号;
- 读时钟域:读出数据,产生 empty 信号;
- 指针同步:格雷码编码后通过两级同步器同步到对方时钟域。
Vivado 异步 FIFO IP 核配置步骤:
- 打开 Vivado,进入项目,点击左侧'IP Catalog',搜索'FIFO Generator';
- Basic:配置 IP 核名称(如 async_fifo),勾选'Native',点击 Next;
- FIFO Type:选择'Independent Clocks Block RAM'(异步 FIFO),点击 Next;
- Write Interface:配置写时钟频率、写数据位宽,点击 Next;
- Read Interface:配置读时钟频率、读数据位宽(须与写位宽一致),点击 Next;
- FIFO Depth:配置 FIFO 深度(存储数据个数),点击 Next;
- Flags:勾选'Full Flag'和'Empty Flag',点击 Next;
- Data Counts:可选勾选读写数据计数,方便调试,点击 Next;
- Summary:核对参数,点击 Generate 生成 IP 核;
- 调用:在 Sources 中找到生成的 IP 核,例化到工程中。
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, // FIFO 满信号
output wire empty // FIFO 空信号
);
// 例化异步 FIFO IP 核
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)
// 若配置了数据计数,可添加 .wr_data_count 和 .rd_data_count
);
endmodule
调用要点:
- 复位适配:IP 核默认复位高电平有效,示例中用
~rst_n适配全局低电平复位; - 端口对应:例化端口名称须与 IP 核生成端口完全一致;
- 使能控制:wr_en 仅在写时钟域控制,rd_en 仅在读时钟域控制。
5. 工程中必须记住的 CDC 铁律
- 单 bit 信号:统一用两级寄存器同步,不打拍或只打一拍都不行;
- 多 bit 信号:禁止直接打拍,必须用握手机制或异步 FIFO;
- 有效时间:跨时钟域信号尽量保持一个周期以上有效,避免接收方采不到;
- 复位同步:复位信号也要做跨时钟域同步,不同时钟域复位不能直接复用;
- 工具警告:开发工具(如 Vivado)报的 CDC 警告不要忽略,90% 都是真实风险。
6. 工程建议
- 避免直接跨域:不要直接将一个时钟域的信号拉到另一个时钟域使用,不做同步处理等于埋雷;
- 核心概念:理解亚稳态、两级同步、多 bit 不能直接打拍、异步 FIFO 用格雷码;
- 稳定性:CDC 没做好会导致系统故障,且难以复现和定位,返工成本极高;
- 代码复用:两级同步器等模板可直接用于工程调试,适配大部分 FPGA 芯片。
