4. 单比特电平同步器设计

单比特电平同步器是跨时钟域设计中最基础、使用最频繁的电路结构。它的任务非常明确:把一个在源时钟域中持续保持稳定的单比特电平信号,安全地传递到目的时钟域,并输出一个在目的时钟域中干净的、无亚稳态传播风险的电平信号。理解它的结构、参数选择与约束方法,是后续掌握脉冲同步、握手同步和异步FIFO的前提。

一、为什么两级触发器能消除亚稳态

当源时钟域的信号在目的时钟的建立/保持窗口内发生跳变时,目的触发器的输出可能进入既非高也非低的亚稳态。亚稳态不会永久保持,它会在一定时间内随机地衰减到稳定值,这个衰减时间服从指数分布。如果只用一级触发器,亚稳态可能直接传播到下游组合逻辑,造成不可预测的行为。

两级触发器同步器的核心思想是:让第一级触发器专门"承受"亚稳态,给它一个完整的时钟周期去衰减;第二级触发器在第一级输出已经稳定的前提下采样,从而输出干净的电平。这里有一个关键前提——源信号在目的时钟域看来必须是"慢变"的,即它在被采样期间保持稳定足够长的时间,否则同步器无法保证正确捕获。

单比特电平同步器结构与信号流 源时钟域 clk_a 源寄存器 data_a 电平信号 保持 ≥ 2 个 clk_b 周期 异步信号 目的时钟域 clk_b 第一级 FF 承受亚稳态 sync_meta 第二级 FF 输出稳定电平 data_b_sync 下游 逻辑 clk_b clk_b 第1个clk_b沿:可能采到亚稳态 → 第2个clk_b沿:输出稳定值 总延迟:2~3个clk_b周期(取决于信号到达相位)

二、结构细节与参数选择

标准的两级同步器由两个同沿触发的D触发器串联组成,共用目的时钟clk_b。第一级触发器的D端接异步输入,Q端接第二级触发器的D端,第二级触发器的Q端作为同步后的输出。两级之间不插入任何组合逻辑,这是硬性要求——任何组合逻辑都会破坏亚稳态衰减的时间预算。

参数选择主要围绕三个量:目的时钟频率、源信号的最小保持宽度、以及目标MTBF(平均无故障时间)。MTBF的经典估算公式为:

MTBF = e^(t_r / τ) / (T_0 × f_clk × f_data)

其中:
  t_r   = 留给亚稳态衰减的时间(通常取一个clk_b周期)
  τ     = 触发器亚稳态时间常数(PolarFire器件典型值约几十ps量级)
  T_0   = 亚稳态窗口宽度常数(约几十ps量级)
  f_clk = 目的时钟频率
  f_data= 异步数据跳变频率

在PolarFire器件中,τ和T_0由工艺决定,设计者能控制的是t_r和f_clk。提高MTBF最直接的方法是增加同步级数或降低目的时钟频率。对于绝大多数消费级和工业级应用,两级同步器在100MHz以下的clk_b下已经能提供足够的MTBF;如果源信号跳变极其频繁或可靠性要求极高,可以扩展到三级。

另一个关键参数是源信号的最小保持宽度。电平同步器要求源信号在被同步期间保持稳定至少两个clk_b周期(考虑最坏相位情况),否则可能完全丢失该电平。例如clk_b为50MHz(周期20ns),源信号至少要保持40ns以上。如果源信号是窄脉冲,必须改用脉冲同步器或握手协议。

三、PolarFire Libero中的实现

在Libero SoC中实现电平同步器有两种方式。第一种是直接写RTL,用两个always块或一个always块内的两级寄存器描述。第二种是调用Libero提供的同步器宏(如CORESYNC),但手写RTL更透明、更便于约束。

下面是一个可直接综合的Verilog示例,采用异步复位、同步释放的复位策略:

module level_sync (
    input  wire clk_b,      // 目的时钟
    input  wire rst_n,      // 异步复位,低有效
    input  wire data_a,     // 源时钟域电平信号
    output wire data_b_sync // 同步后输出
);

    reg sync_meta;
    reg sync_out;

    always @(posedge clk_b or negedge rst_n) begin
        if (!rst_n) begin
            sync_meta <= 1'b0;
            sync_out  <= 1'b0;
        end else begin
            sync_meta <= data_a;      // 第一级:承受亚稳态
            sync_out  <= sync_meta;   // 第二级:输出稳定值
        end
    end

    assign data_b_sync = sync_out;

endmodule

综合后需要在Libero的时序约束文件(SDC)中做两件事。第一,对data_a到第一级触发器的路径设置伪路径(false path),因为这是跨时钟域路径,静态时序分析无法也不应该分析它:

set_false_path -from [get_clocks clk_a] -to [get_clocks clk_b]

第二,确保两级触发器被布局在相邻位置,减少布线延迟差异。在Libero的Floorplanner中,可以把这两个寄存器手动约束到同一个Logic Element或相邻的LE中。PolarFire的SmartFusion2/MPF器件中,每个LE包含多个寄存器和LUT,把同步器两级放在同一LE内可以最小化t_r的损耗。

在Libero中综合后,建议打开Synplify的"Cross Clock Domain"报告,确认同步器被正确识别。如果工具把两级触发器优化成了一级(例如因为第二级输出未被使用),需要加syn_keep或preserve属性防止优化。

四、验证方法与结果判读

验证电平同步器需要覆盖三个场景:源信号在clk_b采样窗口外跳变、在采样窗口内跳变、以及源信号保持时间不足。在Libero中可以用Testbench加ModelSim仿真,也可以上板用SmartDebug观察。

仿真时,让clk_a和clk_b为不同频率的非整数倍关系(例如clk_a=37MHz,clk_b=50MHz),源信号data_a每隔一段随机时间翻转。观察data_b_sync:它应该在data_a变化后的2~3个clk_b周期内跟随变化,且不出现毛刺或振荡。如果看到输出在中间电平停留超过一个周期,说明亚稳态衰减时间不够,需要检查t_r是否被组合逻辑或布线延迟吃掉。

上板验证时,可以用一个LED或逻辑分析仪观察data_b_sync。更严格的方法是把data_b_sync再打一拍,用两级输出做边沿检测,统计在固定时间窗口内捕获到的跳变次数是否与源端一致。如果丢失跳变,说明源信号保持宽度不足或同步器级数不够。

检查项 预期结果 异常处理
同步延迟 2~3个clk_b周期 检查是否插入了组合逻辑
输出毛刺 无 确认两级FF同沿触发
跳变丢失 无 增大源信号保持宽度或加第三级
时序报告 跨时钟路径被设为false path 补充SDC约束

最后需要强调:电平同步器只适用于单比特、慢变信号。如果源信号是总线或多比特,必须使用异步FIFO或握手同步,否则各比特到达目的域的时间不一致会造成数据错误。这是CDC验证中最常见的误区之一。