ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

两级DFF同步器:跨时钟域亚稳态消除的经典实践

两级DFF同步器:跨时钟域亚稳态消除的经典实践 异步总线交互是一个绕不开的坎。只要你做数字IC设计或者FPGA开发只要你的系统里存在两个以上的时钟域就一定会在某个深夜被仿真波形里那些莫名其妙的高阻态、X态或者有时候灵有时候不灵的功能故障折磨得焦头烂额。而绝大多数此类问题的根源都指向同一个名字亚稳态Metastability。今天这篇是这个系列的第一篇聊的是最基础、也最常用的应对手段——两级DFF同步器。别看它简单就是两个寄存器串在一起但里面的门道和坑比你想象的多得多。本文会把这玩意儿为什么能解决跨时钟域问题、原理是什么、Verilog怎么写、时序约束怎么做、MTBF怎么估算、以及在异步总线场景下到底该怎么用一次性讲透。无论你是刚开始学跨时钟域的在校学生还是已经上了两年板子的初级工程师这篇都能给你一些在课本和白皮书上不容易翻到的实操经验。1. 亚稳态两级DFF同步器到底在解决什么问题在做任何方案选型之前先搞清楚问题的根源。跨时钟域之所以危险是因为触发器在采数据的时候可能踩在一个“不上不下”的尴尬区间里。1.1 触发器的建立时间和保持时间到底在说什么先回顾一下D触发器的时序模型。一个DFF在时钟上升沿采样数据要求在时钟沿到达之前数据端D的电平必须稳定一段时间叫建立时间t_setup在时钟沿到达之后数据端D的电平也必须继续保持稳定一段时间叫保持时间t_hold。这两个时间窗口内的输入数据不能有任何变化否则触发器的输出Q端就会进入一种不确定状态。这个不确定状态就是亚稳态。它不是逻辑0也不是逻辑1而是一个介于两者之间的中间电平而且这个中间电平不会一直稳定它会在一段时间后随机塌缩到0或者1。问题在于塌缩到0还是1完全不可预测塌缩需要多长时间也不可预测。这就带来了双重不确定性——值不确定、时间不确定。用生活化的方式去理解触发器采样过程就像一个人在玻璃门即将关上的一瞬间决定要不要冲过去。他可能刚好卡在门缝里进退两难也可能在最后一刻冲了过去也可能犹豫了一下退回来。整个过程既随机又不可控。1.2 亚稳态为什么会导致系统故障可能有人觉得亚稳态最终不还是会塌缩成0或者1吗那又怎么样问题就在“最终”这两个字上。如果这个DFF的输出Q端直接接到下一级组合逻辑而亚稳态还没有塌缩到确定电平时下一级组合逻辑读到的就是一个中间电平。中间电平在CMOS工艺下意味着什么意味着下一级的PMOS和NMOS可能同时导通形成从电源到地的贯通电流。更麻烦的是这个中间电平会沿着组合逻辑链一路传播后面每个门都可能被这个非标准电平“部分导通”整个逻辑链的输出就会变成一团浆糊。在最坏情况下一个亚稳态事件可能导致好几个寄存器同时采到错误值进而让状态机跳到一个不存在状态总线上的控制信号错误翻转甚至把数据写进错误地址。而且这类故障是偶发的跟温度、电压、工艺角都有关可能在实验室跑一年都不出错一到现场就随机宕机。这也是为什么跨时钟域问题被称为数字设计里最难排查的故障类型之一。1.3 亚稳态的经典消除手段多级同步链既然单级触发器采到亚稳态后输出不可控那就再加一级寄存器让它再采一次。第一级采到亚稳态后经过一个时钟周期的时间亚稳态大概率已经塌缩并稳定下来。此时第二级再采样采到的就是一个有效电平。所以“等待再采”是消除亚稳态影响最直接的思路。现在业界最通用的做法是两级DFF级联也就是常说的2-FF同步器、同步链。它能在一级触发器的亚稳态产生后给足一个完整时钟周期的恢复时间让次级触发器以极高的概率采到稳定电平。两级以上如三级、四级能进一步提升可靠性但带来的延迟和面积成本也更大。从工程实践角度看对于普通的单bit电平信号两级是性能和可靠性的黄金平衡点。2. 两级DFF同步器的工作原理与经典电路结构两级DFF同步器的硬件电路逻辑上讲就是两个D触发器首尾相连第一级的输出接第二级的输入两级共用同一个目的时钟域的时钟。这个同步器负责把异步时钟域的单bit信号同步到当前时钟域来。2.1 为什么一级DFF同步不够非得要两级先说一级DFF的情况。如果异步输入的信号在目的时钟沿附近发生变化第一级DFF直接采到亚稳态。假设它花了非常长的时间比如超过一个时钟周期才塌缩到确定电平那么在这个期间Q端输出的非稳定电平会直接进入后续逻辑。虽然从概率角度讲亚稳态塌缩时间超过一个时钟周期的事件概率极低但“极低”不等于“零”。在高速系统、高数据率场景下这个概率乘上天量次采样的基数出错的绝对次数就不可忽略了。而且一级DFF的输出电平不稳定这个问题是结构性的——它没有任何后卫机制。两级DFF则不同第一个DFF用来“挡子弹”即使它进入亚稳态也不会直接进入下游逻辑而是给了它整整一个时钟周期恢复。第二个DFF在下一个时钟沿才采样第一级的输出。此时第一级输出大概率已经稳定成了0或1。第二级的输出就是干净、确定的逻辑电平。打个比方一级同步就像让一个保安直接放行一个来路不明的人两级同步就是这个保安先把他拦在缓冲间关了门等一分钟确认这个人状态正常了再放进内部区域。缓冲间就是第一级寄存器确认过程就是那一个时钟周期的等待。2.2 同步器输出频率、采样频率与耐失效能力的关系两级DFF同步器的抗亚稳态能力是有量化指标的。在同步器设计里有一组衡量系统抗亚稳态能力的参数吸收窗口时间窗口参数τ、亚稳态窗口参数T0以及时钟频率和数据频率。工程上最常关注的是MTBF即平均无故障时间。MTBF的经典估算公式MTBF e^(t_r / τ) / (T_0 · f_clk · f_data)变量说明t_r允许亚稳态信号恢复稳定的时间从这里到下一个采样沿之间的时间对两级同步器来说约等于一个时钟周期减去建立时间τ同步器第一级寄存器的亚稳态恢复时间常数由工艺库决定τ越大意味着恢复越慢T_0与触发器工艺相关的参数量纲是秒f_clk目的时钟频率f_data异步输入数据的翻转频率。从公式可以看出MTBF对t_r极其敏感因为t_r在指数项上。举个例子假设特定工艺库下τ20psT_030psf_clk100MHzf_data1MHzt_r9.8ns。那么MTBF e^(9.8e-9 / 20e-12) / (30e-12 · 1e8 · 1e6) e^490 / 3e6这个数字已经是天文数字了约等于e^490次方除以百万实际数值极大这意味着理论上几十亿年都不出一次错。但如果把同步器改成单级t_r就直接小很多。假设t_r只有0.1ns比如后端时序收敛得勉强留给亚稳态恢复的时间只有0.1ns那MTBF急剧下降MTBF e^(100e-12 / 20e-12) / (30e-12 · 1e8 · 1e6) e^5 / 3e6 ≈ 148.4 / 3e6 ≈ 4.9e-5秒也就是说大约每5e-5秒就会出一次错1秒出错两万次。这个对比足够触目惊心了吧两级同步器相比一级同步器MTBF提升的幅度是指数级别的。这也从数学上解释了为什么一级同步在工程上不可接受。2.3 什么时候需要三级甚至更多级同步链既然两级就能把MTBF抬到天文数字为什么还会有人用三级、四级同步器场景主要有这么几类工作频率极高比如GHz级别的接口电路。此时时钟周期才几纳秒两级同步器中第一级的恢复时间t_r太短MTBF可能降到无法接受的水平于是加一级。工艺角较差寄存器的τ很大恢复慢。有些低功耗、高阈值电压库的τ值较大两级恢复时间不够需要增加级数。系统对可靠性要求极其苛刻比如车规、医疗、航天。这类场景会把MTBF要求推到10的20次方年甚至更高单纯两级不够稳的时候就用三级乃至四级。输入的异步信号变化与时钟沿相对时序极端恶劣。比如信号翻转率远高于时钟频率的多bit总线场景单独靠同步器已经不够不过那属于另一个话题了。在本系列针对异步bus交互的基础场景里两级DFF同步器是默认标配。三级以上的使用更多是在高性能项目里做保守加强代价是路径上多了一拍延迟。3. 异步bus交互中的两级DFF同步器什么时候能用什么时候千万别用两级DFF同步器是一个非常“挑食”的工具。它只适合处理特定类型的信号用错地方不但不能解决问题反而会引入新的故障。这部分把适用边界划清楚。3.1 适用场景单bit电平信号两级DFF同步器的核心限定词是“电平信号”。什么是电平信号就是长时间保持为0或者1、其状态代表某个含义的信号。典型的例子包括握手协议里的valid、ready、ack状态标志位比如FIFO的空/满标志在格雷码同步场景下也算异步中断请求信号配置寄存器里的某种状态位。这类信号的特点是它们在被下游采样时会维持足够长的时间。只要信号宽度大于同步器的总采样周期大概率能稳定采到不会丢脉冲。以异步总线常用的valid-ready握手机制为例发送端拉高valid表示总线上的数据有效。接收端先对valid做两级同步采到高电平后再去采样数据线。因为valid是高电平有效且会一直保持直到接收端返回ready、发送端撤掉valid为止所以这个握手信号是典型的电平信号用两级DFF同步器非常合适。3.2 不适用场景脉冲信号如果你把一个单时钟周期的窄脉冲直接送到两级DFF同步器那就踩了大坑。原因很简单第一级DFF的采样率受限于目的时钟频率。如果脉冲宽度小于目的时钟周期那这个脉冲很有可能在时钟沿到来之前就已经消失了第一级DFF压根看不到这个脉冲同步器输出永远是0。更常见的情况是脉冲宽度介于一个到两个时钟周期之间第一级DFF有时采得到、有时采不到输出就跟抽奖一样。这不是亚稳态而是采样丢失属于另一个层面的问题。脉冲信号跨时钟域怎么处理常规做法是把脉冲展宽成电平或者用脉冲同步器结构Toggle synchronizer把脉冲信号先经T触发器翻转成电平信号同步后再通过检测边沿还原成脉冲。这个后面如果有机会可以单开一篇细讲。3.3 绝对禁忌直接把多bit数据总线同步过去这是初学者最容易犯的错误——发现控制信号不稳就顺手把整个数据总线用DFF同步器同步一遍。如果是异步FIFO里的读写指针这种按格雷码编码的数据还好说但如果是普通二进制编码的多bit数据总线两级DFF同步器是绝对不能用的。为什么因为每个bit是独立采样的。不同的bit相对时钟沿的到达时间存在差异当一个多bit总线上的多个位同时变化时目的时钟沿附近可能有的bit采到了新值、有的bit采到了旧值尤其当走线延迟不一致时下游收到的就是一个完全错误的新旧混合数据。这个现象叫作总线偏斜bus skew导致的采样不一致问题。多bit数据正确跨时钟域的办法异步FIFO通过格雷码指针同步实现多bit数据的可靠传递握手协议配合数据保持数据源在握手成功后保持数据不变直到接收方确认数据先打到寄存器再用MCP多周期路径约束和两级同步配合使用MUX同步数据选择器同步法多路数据各自同步后通过本地选择信号选择。总而言之两级DFF同步器锁定的永远是单bit控制类信号。想在异步bus场景里搬运多bit数据需要的是系统级方案不是拿同步器一遍一遍刷数据线。3.4 异步bus里两级DFF同步器的一个经典正确用法示例用最典型的异步总线寄存器读写场景来展示正确姿势。假设总线发起方在时钟域A发送写请求希望在时钟域B的某个寄存器写入一个值但数据线和写使能是分开的。正确做法是先将数据线与写使能信号在时钟域A内部稳定保持多拍时钟域B先将写使能信号做两级同步等待同步后的写使能稳定拉高时钟域B再对数据线进行正常采样。此时数据线在时钟域B采样时已经是稳定的因为写使能同步过程中已经消耗了若干源时钟周期而源端在此期间保持了数据不变。这种“同步控制信号 延迟采样数据”的做法就是异步bus控制与数据分离思想的雏形。整个后续的异步桥、寄存器配置总线底层逻辑都是这么展开的。4. 实战两级DFF同步器的RTL实现、仿真与综合约束理论讲完了直接上代码和流程。4.1 可综合的RTL实现与写法要点两级DFF同步器的Verilog实现很简单但有几个细节写不好会出大问题。先看最标准的RTL写法module sync_2ff #( parameter WIDTH 1 )( input wire clk, input wire rst_n, input wire [WIDTH-1:0] async_in, output wire [WIDTH-1:0] sync_out ); (* async_reg true *) reg [WIDTH-1:0] sync_ff1; (* async_reg true *) reg [WIDTH-1:0] sync_ff2; always (posedge clk or negedge rst_n) begin if (!rst_n) begin sync_ff1 {WIDTH{1b0}}; sync_ff2 {WIDTH{1b0}}; end else begin sync_ff1 async_in; sync_ff2 sync_ff1; end end assign sync_out sync_ff2; endmodule几个关键点复位必须有复位且用异步复位或者同步复位没有严格规定但复位必须是安全的不会因为复位释放时间的差异导致亚稳态。推荐的复位方式是异步复位、同步释放或者对复位信号本身也做两级同步。非阻塞赋值必须使用非阻塞赋值。这是时序逻辑的硬性要求阻塞赋值会在仿真里引入不可预期的竞争行为综合出来的电路也会有问题。中间寄存器声明时加(* async_reg true *)这个综合属性告诉综合工具这两个寄存器是被用作异步同步的不要把它优化合并掉也不要挪位置。建议对每个输入信号单独实例化一个同步器模块不要用一个多bit的同步器统一同步一组总线信号。原因前面说过多bit总线有偏斜。WIDTH参数仅用于技术上支持多位宽实际使用时建议在顶层按单bit反复实例化避免误用。4.2 同步器综合约束set_false_path该怎么写很多人在综合的时候完全不约束同步器导致工具把异步输入路径当作一个普通时序路径去优化结果可能为了满足看似紧的时序而插入多余逻辑甚至报出时序违例。正确的约束思路是从异步输入端口或者源时钟域寄存器Q端到同步器第一级寄存器的D端这条路径是一条“伪路径”它没有实际的时间要求。而同步器第一级FF的Q到第二级FF的D这条路径要确保第二级有足够的恢复时间recovery时间所以它是一条真实的时序路径需要约束。SDC里的常见写法# 假设异步输入的管脚是 async_in # 同步器第一级寄存器 sync_ff1_reg 的 D 端路径设置为 false path set_false_path -from [get_ports async_in] -to [get_cells sync_ff1_reg/D] # 同步器内部第一级到第二级需要正常约束工具默认会约束 # 如果希望给第一级恢复留足够余量可加 set_multicycle_path但此法不通用 # 在同步链较长时可用 set_max_delay 给第一级到第二级路径设置合理上限如果是FPGA工程强迫综合工具不要破坏同步器结构的方式通常有两种一是在RTL里加综合属性比如Xilinx的(* ASYNC_REG TRUE *)或者Altera的(* altera_attribute -name ADV_NETLIST_OPT_ALLOWED NEVER *)二是在综合脚本里使用set_false_path。两者配合最稳。不少团队还会额外做一层保护对同步器的第一级寄存器设置DONT_TOUCH确保布局布线时不会把它合并到相邻IO或者逻辑单元里。这是实战经验很多返工都是因为这块没约束死。4.3 Xilinx和Altera/Intel FPGA中的两级DFF同步器原语FPGA厂商其实提供了比手写两级DFF更可靠的方案——直接用原语。XilinxAMD推荐的是XPM_CDC_SINGLE这是Vivado里XPMXilinx Parameterized Macro库提供的跨时钟域专用原语内部就是两级同步器而且经过了充分验证综合和布局布线的时序行为都被正确约束。用起来很简单XPM_CDC_SINGLE #( .DEST_SYNC_FF(2), // 同步器级数建议2-4 .INIT_SYNC_FF(0), .SRC_INPUT_REG(0), .WIDTH(1) ) xpm_cdc_single_inst ( .dest_out(sync_out), .dest_clk(dest_clk), .src_in(async_in) );IntelAltera类似对应IP是Altera Sync或者标准单元里的altclkctrl等一系列时钟与跨域原语。实际工程里直接用厂商原语是省事又稳妥的办法尤其适合FPGA工程师。如果是ASIC那就是标准单元库里已验证的同步器单元或者手写两级FF再配合后端约束。手写两级FF并不是不能用而是必须把约束做对否则千辛万苦写出来的代码在综合后可能被优化得面目全非。FPGA上用原语就是让工具把“已验证”和“正确约束”这两件事一起给你包办好。4.4 功能仿真模型模拟亚稳态的可选做法功能仿真不模拟亚稳态行为两级FF在RTL仿真里看起来就是干净的两个寄存器打拍。也因此RTL仿真中永远测不出跨时钟域问题因为仿真器在时钟沿是理想对齐的数据也总是稳定在建立时间之前。想要在仿真里“看”到亚稳态可以用模拟行为模型。给第一级DFF的输出在采样违例时赋一个X态等一个随机时间后再恢复。这种模型叫“behavioral metastability model”。常见的做法是用一个自定义模块模拟亚稳态恢复时间module dff_meta ( input wire clk, d, output reg q ); real recovery_time; always (posedge clk) begin if ($time % 100) begin recovery_time $urandom_range(1, 50); #(recovery_time * 0.1ns); q d; end end endmodule这种仿真模型看起来高级但实际工程里很少在项目层面铺开用因为会拖慢仿真而且模型参数未必贴合工艺。比较务实的做法是在跑混合信号仿真或者gate-level带SDF反标仿真时用库里的标准单元模型自然模拟亚稳态那个模型是工艺厂校准过的。RTL仿真阶段老老实实做功能验证就够了跨时钟域的可靠性更多靠约束和数学来兜底。5. 常见问题与排查技巧实录聊几个我做项目过程中真实踩过的坑和同行交流中经常出现的问题整理成一个速查表帮助你在设计和调试时快速定位。5.1 两级DFF同步器的低频陷阱脉冲丢失之前提到过脉冲信号不能直接同步但实际项目里很容易犯“原理都知道、一写代码就忘”的毛病。最典型的是中断信号。比如总线上有一个中断请求信号源端只拉高了一个时钟周期而目的时钟频率只有源时钟频率的一半。两级DFF同步器大概率采不到这个脉冲。我在一个项目中就遇到过两个板卡之间用异步总线通信中断信号总是偶发丢失排查了整整两天才发现是中断源输出的是一个单周期脉冲而不是一个持续电平。排查技巧用逻辑分析仪或chipscope抓同步器第一级和第二级的输出。如果第一级输出永远看不到高电平说明脉冲在采样前就已经消失。解决方式是源端把中断请求保持到被确认或者改用脉冲展宽电路后再同步。5.2 综合之后发现同步器被优化掉了手写两级FF综合后检查网表发现自己写的两个FF不见了这在优化等级比较高的综合策略下偶有发生。当你对异步输入路径设置了false path同时又没加DONT_TOUCH属性时综合工具可能认为这两个寄存器是冗余的直接合并优化掉。避免办法很简单一是加综合属性比如前文提到的(* async_reg true *)和DONT_TOUCH二是在综合后检查网表确认同步器逻辑还在这种检查应该写进项目流程里当作一个必须项来做三是在SDC里给同步器寄存器设置名称前缀方便脚本统一处理。另外说个冷门知识点有些综合工具看到两级FF都受同一个复位控制且复位值相同会尝试把它们合并成带复位的普通移位寄存器。如果这个合并发生在异步输入路径上危害极大。这也是为什么很多芯片级设计团队不会用通用寄存器来做同步器而要求必须例化库里的同步器单元库单元内部两个FF之间做了专门匹配比如增加衬底接触降低闩锁风险。5.3 复位释放与同步器一起出问题的case项目里有一个很隐蔽的坑同步器的复位撤除时刻如果和输入信号的翻转时刻挨得很近也会导致两级同步器输出中间状态。假设rst_n是异步复位复位释放时刚好有异步输入信号翻转第一级FF的输出可能因为复位释放和输入到达的竞争关系产生亚稳态。虽然第二级FF也会过滤这个亚稳态但如果复位释放本身就不干净比如复位信号在目的时钟域没有做过同步释放处理整个同步器的输出在复位后第一拍就可能出现一个毛刺。推荐做法异步复位必须要做“异步复位、同步释放”处理把复位信号先经过两级FF同步一次再用同步后的复位去复位同步器如果同步器本身对复位时序极其敏感考虑对同步器的两个FF不接复位只做上电初始化用initial语句这样可以彻底规避复位释放和输入翻转的竞争问题。第二个做法在某些资深工程师的代码里很常见两级同步器不连复位因为同步器的输入输出在复位状态下本身不重要与其冒复位竞争的风险不如让它上电后自然锁定到稳定态。但这个做法要求上电时输入不能抖动否则输出可能初始化为一个随机值只能靠后续逻辑保证无副作用。5.4 常见问题速查表问题现象可能原因排查方法解决思路同步器输出没有跟随输入变化输入是脉冲信号宽度小于采样周期逻辑分析仪抓第一级FF输出展宽脉冲或改用脉冲同步器综合后同步器消失没有加同步属性或被工具优化合并检查综合网表添加async_reg属性和DONT_TOUCH输出出现X态或毛刺复位释放与输入翻转竞争检查复位同步处理使用异步复位、同步释放或去掉同步器复位多个bit同步后数据错乱多bit总线直接做同步检查数据路径改用异步FIFO或握手协议MTBF不满足要求时钟频率太高或同步级数不够计算MTBF增加同步级数或优化第一级寄存器选型仿真正常但上板偶发错误RTL仿真不模拟亚稳态做gate-level仿真检查约束和恢复时间余量第二级输出仍出现不定态第一级到第二级恢复时间不足查时序报告检查第二级建立时间余量考虑加级数5.5 验证同步器强度的实用技巧最后分享几个我在验证环境里常用的土办法。第一做formal等价性检查时特意对同步器做“LEC黑盒”处理把同步器内部逻辑抽象成理想模型不参与逻辑等价检查。这样能避免综合工具在优化同步器时导致formal tool误报。第二做FPGA上板验证时给异步输入信号加随机抖动delay用延迟链或者ODDR的相位偏移制造输入时序滚动。跑一段时间看有没有偶发故障。这块可以用Xilinx的IDELAYE2精确移相模拟输入信号在时序上遍历整个时钟周期的情况。我在一个PCIe转并行总线的桥接项目里用过这个办法真能扫出一个波特率配置错误导致偶尔采到错值的bug。第三在做可靠性测试时用set_false_path去掉同步器第一级到第二级的时序约束看功能还能不能通过。这其实是一种负向测试在降低约束余量后发现bug能反向证明当前设计的裕量是不是足够。注意这只是验证手段正式综合时千万不要取消这个约束。结尾小记个人在实际项目里体会最深的一点是两级DFF同步器看着简单但它绝不是一个“加上就万事大吉”的保险丝。它只负责解决单bit电平信号的亚稳态传播问题而异步bus交互里更复杂的多bit数据安全传输要结合异步FIFO、握手协议、格雷码、MCP等一系列手段来打组合拳。从两级DFF这个最小单元出发把底层原理吃透后面看那些复杂结构时会通透很多。另外提醒一句如果你的fpga工程里同时存在多个异步时钟域建议在建工程的第一天就把所有跨时钟域路径梳理成一张表标注清楚每条路径的信号类型、同步方案和约束状态。不要等项目跑起来再去补那时候你看到的问题全是表面现象底下盘根错节排查成本会和拖延时间呈指数关系往上涨。本系列后面会继续聊异步bus交互的进阶话题比如握手协议的状态机设计、异步FIFO的格雷码指针同步以及如何在多bit数据总线上应用MCP与MUX同步方案。
返回列表