跨时钟域(Clock Domain Crossing, CDC)是 FPGA 设计里最容易引发隐蔽 bug 的地方。要点:避免元稳态(metastability)并保证数据完整性。下面给出常用模式、示例代码与注意事项。
基本原理(一句话)
当一个域的信号在另一个域采样时,触发器可能进入元稳态;要用结构和协议把“异步边界”转成可接受的“可靠同步”行为(延迟、握手或双口FIFO等)。
常见场景与解决方法
1) 单比特控制信号(例如使能、脉冲、复位)
推荐方法:两级或三级同步器(2-FF/3-FF)
用于稳定的电平信号(非短脉冲)。
Verilog 示例(2级同步器):
// src_clk -> dst_clk
reg sync_ff1, sync_ff2;
always @(posedge dst_clk or negedge dst_rst_n) begin
if (!dst_rst_n) begin
sync_ff1 <= 0;
sync_ff2 <= 0;
end else begin
sync_ff1 <= async_sig_from_src;
sync_ff2 <= sync_ff1;
end
end
wire synced_sig = sync_ff2;
脉冲(单周期)问题:短脉冲可能在目的域被丢失。常用方法:
在源域将脉冲拉长到至少目标域采样周期(pulse-stretch)。
或使用 toggle(翻转)+ 同步器 + 边沿检测。
Toggle 示例:
// 源域
reg toggle;
always @(posedge src_clk) if (src_event) toggle <= ~toggle;
// 目的域
reg t_ff1, t_ff2;
always @(posedge dst_clk) begin
t_ff1 <= toggle; // 通过2-FF同步器
t_ff2 <= t_ff1;
end
wire dst_event = t_ff2 ^ t_ff1_delayed; // 边沿检测(需要保存上一个)
2) 多比特并行数据(例如总线、寄存器组)
不要直接并行同步! 会出现不同位不同步导致“撕裂”。常见解决方案:
异步 FIFO(Dual-Clock FIFO):最常用,数据在写时钟域写入,在读时钟域读出。用灰码指针做跨域指针同步,或使用厂商提供的异步FIFO IP(推荐)。
握手协议(handshake):Req/Ack 方法,适合低带宽控制/配置数据。
双寄存器切换(double-buffer + toggle):写两份缓冲,toggle 切换索引,目的域通过同步的 toggle 读取正确的缓冲区(适合数据尺寸固定且写/读不要同时发生的场景)。
异步 FIFO 要点:
写指针用二进制计数,读指针用灰码转换后跨域同步,反之亦然。
防止溢出/空读(full/empty 判定需在各自域做本地判断)。
使用厂商的 FIFO IP 可避免许多陷阱并获得经过验证的时序宏。
简单示意:
src_clk domain ---> write FIFO ---> async FIFO ---> read FIFO ---> dst_clk domain
3) 时钟有关(两个时钟有关联)
若两个时钟频率或相位已知且固定比例(例如 2:1、相位对齐),可以用跨时钟边界的时序约束(通过 PLL/BUFG 管理)来让时钟成为静态关系,可能用时钟域转换逻辑或直接将路径做为时序路径处理(但极其小心)。
若两个时钟来自同一源但经过不同布线(skew 问题),优先使用全局时钟资源(BUFG/BUFR 等)并依靠时序约束优化,不要把常规信号当作时钟跨域。
CDC 设计模式汇总(快捷表)
单比特稳定电平 → 2-FF 同步器
单周期脉冲 → pulse stretch 或 toggle + 边沿检测
多比特数据(异步)→ 异步 FIFO 或 握手/双缓冲
时钟相关(已知关系)→ 时序约束 / 特殊时钟资源
复位(异步)→ 在目的域做同步解除,或使用同步复位
验证与约束(必做项)
静态时序约束(SDC):明确 create_clock、set_false_path(将跨时钟异步路径标记为 false_path),告诉时序工具不要对这些路径做常规时序分析。
CDC Lint/检查工具:使用厂商/第三方 CDC 检查工具(Xilinx Vivado CDC checker、Intel CDC lint、Mentor Questa CDC 等)找潜在问题。
仿真:事件驱动仿真 + 随机激励(强调脉冲、握手边界条件、溢出/空读场景)。
形式验证 / 静态分析(有条件时):可以证明某些握手/协议正确性。
系统测试:在实际板级环境长期运行(MTBF、压力测试)观察 rare 条件。
常见坑与注意事项
不要用“简单的寄存器传递”跨域并行数据(会产生撕裂)。
忽略 reset 的同步:异步复位要在目的域解除时同步。
误用时钟网做信号同步(不要把普通信号作为时钟去布线或反之)。
忽视 metastability 的概率(MTBF):多数设计用两级同步器可将元稳率降到可接受范围,但不能“完全消除”。
FIFO 的 full/empty 判定、接近满/空条件要设计妥当,防止读写指针误判导致数据丢失或死锁。
小结(快速行动清单)
判断信号类型(单比特/脉冲/多比特/时钟相关)。
单比特用 2-FF;脉冲用 toggle 或拉长;多比特优先用异步 FIFO 或握手。
在 FPGA 中优先使用厂商验证过的 CDC IP(async FIFO、CDC synchronizer)。
写好 SDC(create_clock, set_false_path…)并运行 CDC lint / 静态检查。
做充分的仿真 + 板级压力测试。