FPGA 跨时钟域信号传输 —— 实用指南(中文速查)

跨时钟域(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 / 静态检查。
  • 做充分的仿真 + 板级压力测试。