概述

跨时钟域(CDC, Clock Domain Crossing)是数字芯片设计中最高频的故障来源之一。当信号从一个时钟域跨越到另一个时钟域时,若采样边沿落入信号跳变窗口,触发器的输出将进入亚稳态(Metastable),导致:

  • 采样值不确定(0 或 1)
  • 亚稳态传播至下游逻辑
  • 多 bit 信号出现数据不一致(部分 bit 被新时钟采样、部分 bit 被旧时钟采样)

CDC 同步器是解决上述问题的基本单元。本文从亚稳态物理机制出发,逐一解析五种同步器结构的原理、适用场景与 RTL 实现。


目录

  1. 亚稳态与 MTBF
  2. 双级触发器同步器(2-FF)
  3. 三级/多级触发器同步器
  4. 握手同步器
  5. 异步 FIFO 同步器
  6. MUX 同步器(控制信号重收敛)
  7. 同步器选型对照表
  8. RTL 编码指南

1. 亚稳态与 MTBF

1.1 亚稳态的本质

触发器的输出由内部一对交叉耦合的反相器(双稳态锁存器)维持。当 setup/hold 时间窗口内数据变化时,内部节点无法在额定时间内稳定到有效逻辑电平,输出悬停在中间电压区域。

1.2 MTBF 计算

MTBF(Mean Time Between Failure)是衡量同步器可靠性的核心指标:

其中:

参数 含义 典型值(180nm)
t_r 允许的稳定时间 1~2 个时钟周期
\tau 触发器恢复时间常数 0.1~0.5 ns
f_{clk} 采样时钟频率 2.5 MHz
f_{data} 数据翻转频率 取决于信号
T_W 亚稳态窗口宽度 0.1~0.3 ns

工程经验

  • t_r 每增加一个 FF 级数,MTBF 提升约 e^{(T_{clk}/\tau)} 倍
  • 2.5 MHz 低频下 MTBF 非常充裕,但多 bit 同步时的数据一致性问题比亚稳态更致命

2. 双级触发器同步器(2-FF)

2.1 结构

最基础、最常用的 CDC 同步器,适用于单 bit 控制信号

2.2 原理

  • FF1 采样异步信号,允许进入亚稳态
  • FF1 在一个时钟周期内完成恢复
  • FF2 采样已稳定的 FF1 输出,输出有效同步信号

2.3 适用条件

条件 要求
信号类型 单 bit 电平信号
脉冲宽度 必须 > 2 倍 dst_clk 周期
数据速率 远低于 dst_clk 频率
多 bit 需求 不适用(各 bit 独立同步会导致数据不一致)

2.4 注意事项

  • 脉冲太窄被漏采:src_clk 域的脉冲宽度 < dst_clk 周期时,可能在两次采样之间消失
  • 输出毛刺:FF1 输出仍可能亚稳态时被下一级组合逻辑使用 → 增加第三级 FF

2.5 RTL

module sync_2ff #(
    parameter int WIDTH = 1
) (
    input  logic             clk_dst,
    input  logic             rst_n,
    input  logic [WIDTH-1:0] async_in,
    output logic [WIDTH-1:0] sync_out
);
    logic [WIDTH-1:0] meta;

    always_ff @(posedge clk_dst or negedge rst_n) begin
        if (!rst_n) begin
            meta     <= '0;
            sync_out <= '0;
        end else begin
            meta     <= async_in;
            sync_out <= meta;
        end
    end
endmodule

注意:上述 WIDTH > 1 时仅用于电平信号多 bit 同步,不适用于需要保持一致性的多 bit 总线。


3. 三级/多级触发器同步器

3.1 三级结构

当 dst_clk 频率较低或 \tau 较大时,2-FF 的 MTBF 可能不足。增加第三级 FF 可大幅提升可靠性。

3.2 什么时候需要三级

场景 推荐级数
f_{clk} < 10 \text{ MHz} 2 级足够
f_{clk} > 100 \text{ MHz} 2 级,但需检查 MTBF
极高可靠性要求(汽车/航空) 3 级
后仿中观察到 F2 仍有亚稳态 3 级

3.3 RTL

module sync_3ff (
    input  logic clk_dst,
    input  logic rst_n,
    input  logic async_in,
    output logic sync_out
);
    logic f1, f2;

    always_ff @(posedge clk_dst or negedge rst_n) begin
        if (!rst_n) begin
            f1 <= '0;
            f2 <= '0;
            sync_out <= '0;
        end else begin
            f1 <= async_in;
            f2 <= f1;
            sync_out <= f2;
        end
    end
endmodule

4. 握手同步器

4.1 适用场景

当 src_clk 域的信号是脉冲(单周期宽度)而非电平时,2-FF 可能漏采。握手同步器将脉冲转为电平 + 请求/应答协议,确保每个脉冲都被可靠传递。

4.2 结构

4.3 流程

  1. src 域脉冲到来 → toggle 寄存器翻转(电平变化)
  2. toggle 输出经 2-FF 同步到 dst 域
  3. dst 域检测到边沿 → 输出脉冲,同时翻转 ack toggle
  4. ack 经 2-FF 同步回 src 域
  5. src 域检测到 ack 边沿 → 允许发送下一个脉冲

4.4 RTL

module handshake_sync (
    input  logic clk_src,
    input  logic clk_dst,
    input  logic rst_n,
    input  logic pulse_in,
    output logic pulse_out
);
    logic toggle_src, toggle_dst_synced, ack_synced;

    // src domain: toggle on pulse
    always_ff @(posedge clk_src or negedge rst_n) begin
        if (!rst_n) begin
            toggle_src <= '0;
        end else if (pulse_in) begin
            toggle_src <= ~toggle_src;
        end
    end

    // synchronize toggle to dst domain
    sync_2ff sync_toggle (
        .clk_dst, .rst_n,
        .async_in(toggle_src),
        .sync_out(toggle_dst_synced)
    );

    // dst domain: edge detect -> pulse_out
    logic toggle_dst_dly;
    always_ff @(posedge clk_dst or negedge rst_n) begin
        if (!rst_n) begin
            toggle_dst_dly <= '0;
        end else begin
            toggle_dst_dly <= toggle_dst_synced;
        end
    end
    assign pulse_out = toggle_dst_synced ^ toggle_dst_dly;

    // dst domain: generate ack sync
    logic toggle_ack;
    always_ff @(posedge clk_dst or negedge rst_n) begin
        if (!rst_n) begin
            toggle_ack <= '0;
        end else if (pulse_out) begin
            toggle_ack <= ~toggle_ack;
        end
    end

    // sync ack back to src domain
    sync_2ff sync_ack (
        .clk_dst(clk_src),
        .rst_n,
        .async_in(toggle_ack),
        .sync_out(ack_synced)
    );
    // src domain: wait ack before allowing next pulse
    // (omitted for brevity - typically a small FSM)
endmodule

4.5 吞吐量

握手同步器每笔传输需要:

  • src → dst:2 个 dst_clk 周期
  • dst → src:2 个 src_clk 周期
  • 总延迟:约 4~5 个跨时钟周期

5. 异步 FIFO 同步器

5.1 适用场景

需要传输多 bit 数据总线高速连续数据流时,握手同步器吞吐量不足。异步 FIFO 是最高效的多 bit CDC 方案。

5.2 核心设计要点

要点 说明
格雷码指针 读写指针跨时钟域传递时必须使用格雷码,确保每次只翻转 1 bit
空/满判断 读空:读指针追上写指针;写满:写指针绕一圈追上读指针
指针同步 写指针 → 同步到读时钟域判断空;读指针 → 同步到写时钟域判断满

5.3 格雷码编码

二进制 格雷码
000 000
001 001
010 011
011 010
100 110
101 111
110 101
111 100

格雷码性质:相邻值仅 1 bit 变化 → 跨时钟域采样时最多错 1 bit → 错也错成相邻值 → 不影响空满判断正确性。

function automatic logic [WIDTH-1:0] bin2gray(input logic [WIDTH-1:0] bin);
    return (bin >> 1) ^ bin;
endfunction

5.4 空满判断

// Full: gray pointers are equal AND MSB differs (wrapped around)
assign full  = (wptr_gray == {~rptr_sync[$bits(wptr_gray)-1],
                              rptr_sync[$bits(wptr_gray)-2:0]});

// Empty: all bits equal
assign empty = (rptr_gray == wptr_sync);

5.5 异步 FIFO 架构


6. MUX 同步器(控制信号重收敛)

6.1 问题场景

当多个独立同步的控制信号在目的域中需要重新组合(如 MUX 选择、算术运算)时,即使每个信号都用 2-FF 同步,各信号到达时间仍可能因布局布线偏差而错开,导致短暂的错误组合

6.2 问题示例

// ❌ 错误:sel_a 和 sel_b 独立同步后用于 MUX 选择
logic sel_a_sync, sel_b_sync;
sync_2ff sync_a (.async_in(sel_a), .sync_out(sel_a_sync));
sync_2ff sync_b (.async_in(sel_b), .sync_out(sel_b_sync));
assign out = sel_a_sync ? data_a :
             sel_b_sync ? data_b : data_default;
// 危险:sel_a_sync 和 sel_b_sync 可能同时为 1(瞬态)

6.3 解决方案

方案 A:数据使能同步(推荐)

不再同步控制信号,而是同步数据使能,将控制逻辑放在 src 域完成:

// src domain: select and register
always_ff @(posedge clk_src) begin
    sel_data_valid <= ...;  // mux output + valid flag
end
// dst domain: sync valid + 2-ff data
sync_2ff #(WIDTH) sync_data  (.async_in(sel_data), .sync_out(sel_data_sync));
sync_2ff         sync_valid (.async_in(sel_data_valid), .sync_out(valid_sync));

方案 B:格雷码编码选择信号

若选择信号本身可编码为每次只变 1 bit(如状态机状态、环形计数器),使用格雷码传递。

方案 C:异步 FIFO

若控制信号是命令/数据流的一部分,直接用异步 FIFO 整体传递。


7. 同步器选型对照表

同步器类型 信号类型 多 bit 吞吐量 延迟 面积 适用场景
2-FF 电平 2 clk 极小 单 bit 复位、中断、标志位
3-FF 电平 3 clk 高可靠性单 bit 信号
握手同步器 脉冲 ✅※ 4~5 clk 低速脉冲传递
异步 FIFO 数据流 可变 多 bit 数据总线、连续流
MUX 同步器 控制组合 ✅※ 2~3 clk 小~中 目的域需要重组合的信号

✅※ 握手同步器和 MUX 同步器在正确设计下可以传递多 bit,但吞吐量和复杂度不同。


8. RTL 编码指南

8.1 DO

// ✅ 专用同步器模块,统一管理
sync_2ff u_sync_intr (
    .clk_dst(clk_sys),
    .rst_n  (rst_n),
    .async_in(intr_raw),
    .sync_out(intr_sync)
);

// ✅ 格雷码指针用于 FIFO
assign wptr_gray = (wptr_bin >> 1) ^ wptr_bin;

// ✅ 同步器输出仅连接 FF 输入,不直接连组合逻辑
always_ff @(posedge clk_dst)
    sync_pulse <= sync_pulse_meta;  // meta is the 2nd FF

8.2 DON'T

// ❌ 组合逻辑直接使用同步器第一级输出
assign glitch = sync_meta_ff1 & some_signal;  // FF1 仍可能亚稳态

// ❌ 独立同步多 bit 控制信号,认为"各 bit 过 2-FF 就安全"
// 各 bit 可能在不同时钟周期到达,产生短暂错误组合

// ❌ 同步器的复位与数据域混合
// 同步器使用 dst 域复位即可,不需要 src 域参与

8.3 综合/STA 约束

# 将同步器路径标记为 false path(不需要 timing optimization)
set_false_path -from [get_pins sync_2ff_inst/FF1/D] \
               -to   [get_pins sync_2ff_inst/FF1/Q]

# 跨时钟域分组
set_clock_groups -asynchronous \
    -group [get_clocks clk_src] \
    -group [get_clocks clk_dst]

总结

CDC 同步器是跨时钟域设计的基石,没有"万能同步器":

  • 单 bit 电平信号 → 2-FF 同步器(默认),高频或高可靠 → 3-FF
  • 单 bit 脉冲信号 → 握手同步器(toggle + 反馈)
  • 多 bit 数据总线 → 异步 FIFO(格雷码指针)
  • 控制信号重组合 → 前移控制到 src 域或用 FIFO 整体传递

对于低速设计,2-FF 同步器的亚稳态 MTBF 非常充裕,更需要关注的是:

  1. 多 bit 数据一致性(必须用格雷码或 FIFO)
  2. 同步器输出不要直接进入组合逻辑
  3. STA 中正确设置异步时钟组和 false path
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐