☰
CDC跨时钟域:两级同步器为什么会丢脉冲?电平、脉冲与多位数据的选型
2026/10/11 6:09:04 网站建设 项目流程

CDC跨时钟域:两级同步器为什么会丢脉冲?电平、脉冲与多位数据的选型

本文由 AI 生成并进行自动化资料核查;所附 Python 数字行为模型已实际运行。未进行人工技术审校、RTL 仿真或板级测试,使用前请结合器件文档自行验证。

跨时钟域设计中,done明明拉高过,目的域却没有反应,未必是亚稳态。一个完全理想、只有 0 和 1 的仿真,也能稳定复现这个问题。

本文按信号语义选择 CDC 结构,再用可运行的数字模型对比窄脉冲与握手。核心问题是:目的域只需知道当前状态,还是必须收到每一次事件?

1. 两级同步器解决了哪一部分问题

常见结构如下,两个寄存器都由目的时钟驱动:

// 结构示意,未包含复位和完整模块 (* ASYNC_REG = "TRUE" *) reg sync1, sync2; always @(posedge dst_clk) begin sync1 <= src_level; sync2 <= sync1; end

异步输入可能违反第一级的建立、保持时间。后续级为亚稳态恢复提供时间,降低异常传播到业务逻辑的概率。MTBF 是统计指标,两级不是绝对可靠的承诺;级数还需结合器件、目的频率、源翻转率及实现结果确定。Intel 亚稳态分析、AMD MTBF 指南

第一级没有采到的事件,第二级无法补回来。把两级改成三级,也不会自动增加输入脉冲的持续时间。

2. 10 ns 的脉冲怎样消失

设源时钟为 100 MHz,目的时钟为 25 MHz。源脉冲在 10 ns 拉高、20 ns 拉低;目的采样沿位于 3、43、83 ns……第一级每次看到的都是 0,后级自然一直为 0。这是采样遗漏,无须发生亚稳态。

因此,单比特电平同步适合“当前是否使能”这类状态。输入必须保持足够久,目的域也必须允许观察延迟、允许跳过不需要保留的中间变化。AMD 的XPM_CDC_SINGLE明确要求输入被目的时钟采样至少两次。官方使用条件

若每次上升沿都有业务含义,就要重新设计事件传递。固定展宽必须依据最慢目的时钟及采样裕量推导,还要保证脉冲之间的低电平能被辨认;时钟暂停、变频和连续事件都可能破坏原先假设。

3. Toggle 和握手各自需要什么条件

Toggle 的思路是:源域每发生一次事件,就翻转一个寄存器;目的域同步这个状态,再与延迟一拍的状态异或,恢复事件。

这样不再依赖原始脉冲宽度,但仍依赖事件间隔。目的域尚未观察到第一次翻转时,源域若又翻转一次,状态回到原值,两次事件可能一起消失。开环 toggle 适用于能够证明最小事件间隔的接口,不能充当事件队列。

不能证明间隔时,可以使用带反馈的握手。下面的二相握手只允许一个在途事件:

  1. 源域在req == ack_sync时才接收新事件,并翻转req
  2. 目的域同步req,发现它与本地ack不同后,消费一次事件并更新ack
  3. ack同步回源域,重新允许发送

关键是上游遵守 ready/busy。busy 期间突然到来的不可暂停事件,仍需先缓存或计数。目的域不能立即处理时,也应延后确认。握手的吞吐率受同步往返和接收端处理时间限制;某个仿真相位的延迟不能当作所有实现的固定延迟。

二相握手依赖两端状态一致。单边复位可能制造虚假事件或丢失在途事件,应约定复位期间停止发送、丢弃或重试策略,以及重新对齐后再放行业务。厂商宏也有自己的协议:XPM_CDC_HANDSHAKE必须完成整轮握手后才能开始下一笔,不能直接套用上述二相接口时序。AMD 握手宏说明

4. 多位数据为什么不能逐位打两拍

假设数据从0111变为1000。各位跨域后可能在不同目的周期更新,拼出既非旧值也非新值的组合。独立状态位可以各自同步,具有整体含义的总线需要一致性协议。AMD 单比特数组宏的限制

选型可以从这里开始:

要传的内容常见结构必须检查
稳定单比特状态电平同步器稳定时间、允许的观察延迟
稀疏事件脉冲宏或 toggle最小间隔、复位、时钟变化
必须逐次接收的事件握手上游背压、在途容量
偶发配置字数据保持加握手整字保持、采样窗口、路径约束
连续数据流异步 FIFO深度、满空保护、复位协议

配置字应先在源域锁存,并按协议保持到接收完成。控制信号同步后再使目的域采样总线,还必须验证数据路径能在该窗口内稳定。Gray 编码适用于相邻值符合单比特变化条件的状态或指针,不适合任意跳变的数据;即使使用 Gray,也需要检查相应物理约束。AMD Gray 宏说明

连续数据流通常使用异步 FIFO,将存储与跨域控制封装起来;满时不能继续写、空时不能继续读,还需遵守所选 IP 的复位及 busy 条件。AMD 异步 FIFO 文档

5. 用理想数字模型复现

下面是可直接保存为cdc_demo.py运行的 Python 3 模型,无第三方依赖。它按边沿前快照更新寄存器,源周期 10 ns、目的周期 40 ns。两个时钟同沿时也统一读取旧状态。

这是离散数字行为模型,并非 Verilog 仿真或板级测试;它不能重现物理亚稳态,也不能计算 MTBF。所有状态从 0 开始,目的端始终能够接收,未模拟传输中复位。相位扫描的命中比例仅属于这个模型,不能当作硬件失效率。

"""Original ideal edge model, Python 3, no dependencies. Registers update from one pre-edge snapshot, including simultaneous clocks. Not RTL simulation; no metastability, setup/hold, routing or analog effects. All states start at 0; no reset-in-flight behavior is modeled. """defsimulate(phase=3,offers=(10,),queued=0,stop=20000):# src=100 MHz, dst=25 MHz. offers occur at source rising edges.assertall(t>=0andt%10==0fortinoffers)s=dict(pulse=0,p1=0,p2=0,pd=0,req=0,r1=0,r2=0,ack=0,a1=0,a2=0)accepted,rejected,raw,received=[],[],[],[]offers=set(offers)fortinrange(stop+1):n=s.copy()ift%10==0:n['a1'],n['a2']=s['ack'],s['a1']ready=s['req']==s['a2']fire=tinoffersor(queued>0andready)n['pulse']=int(fire)iffire:ifready:n['req']=1-s['req']accepted.append(t)ifqueued>0:queued-=1else:rejected.append(t)ift>=phaseand(t-phase)%40==0:n['p1'],n['p2'],n['pd']=s['pulse'],s['p1'],s['p2']ifs['p2']andnots['pd']:raw.append(t)n['r1'],n['r2']=s['req'],s['r1']ifs['r2']!=s['ack']:received.append(t)n['ack']=s['r2']# consumer always readys=nreturndict(accepted=accepted,rejected=rejected,raw=raw,received=received,remaining=queued)defopen_loop_two_toggles():# Two source events at 10 and 20 ns; destination edges 3,43,83,...toggle=q1=q2=last=count=0fortinrange(201):old=toggle,q1,q2,lastiftin(10,20):toggle=1-old[0]ift>=3and(t-3)%40==0:q1,q2,last=old[0],old[1],old[2]count+=int(old[2]!=old[3])returncountdefmain():one=simulate()assertone['accepted']==[10]andone['received']==[123]assertone['raw']==[]print('single phase=3ns: offered=1 accepted=1 raw_seen=0 handshake_seen=1 receive_time=123ns')seen_raw=seen_hs=0forphaseinrange(40):r=simulate(phase=phase,stop=500)assertlen(r['accepted'])==len(r['received'])==1seen_raw+=len(r['raw'])seen_hs+=len(r['received'])assertseen_raw==10andseen_hs==40print(f'phase sweep 0..39ns: raw_seen={seen_raw}/40 handshake_seen={seen_hs}/40')forphaseinrange(40):r=simulate(phase=phase,offers=(),queued=100)assertlen(r['accepted'])==len(r['received'])==100assertr['remaining']==0andnotr['rejected']print('ready-aware producer: 40 phases x 100 events, accepted=received=4000, PASS')r=simulate(offers=(10,20),stop=500)assertr['accepted']==[10]andr['rejected']==[20]assertlen(r['received'])==1print('busy violation: offered=2 accepted=1 rejected=1 received=1 (no input queue)')assertopen_loop_two_toggles()==0print('open-loop toggle: events=2 received=0 (two flips between destination samples)')print('ALL ASSERTIONS PASSED; ideal digital model only, no physical metastability validation')if__name__=='__main__':main()

实际执行python3 cdc_demo.py后,断言全部通过:

single phase=3ns: offered=1 accepted=1 raw_seen=0 handshake_seen=1 receive_time=123ns phase sweep 0..39ns: raw_seen=10/40 handshake_seen=40/40 ready-aware producer: 40 phases x 100 events, accepted=received=4000, PASS busy violation: offered=2 accepted=1 rejected=1 received=1 (no input queue) open-loop toggle: events=2 received=0 (two flips between destination samples) ALL ASSERTIONS PASSED; ideal digital model only, no physical metastability validation

最后两项很重要:握手保住的是被接口接受的事件;没有输入队列时,被 busy 拒绝的事件不会自动重发。

测试接口时,除了看波形,还应统计“已接受数”和“已接收数”。发送结束并排空通道后,两者应一致,接收事件不能重复。再分别覆盖最短合法间隔、busy 期间请求、不同频率比和复位恢复。若测试仅发送相隔很远的几个脉冲,很容易漏掉吞吐率或初始化问题。上面的扫描只覆盖一组频率比及整数纳秒相位,不能替代完整验证。

6. 上板前再核对这些边界

优先使用器件对应的 CDC 宏,开启其仿真检查,并检查 CDC 报告、同步链属性及实现后的时序。不要把跨域 false path 当作功能正确证明,也不要屏蔽同步链内部本应分析的目的域路径。

例如XPM_CDC_PULSE要求前一个输入脉冲下降沿到下一个上升沿至少间隔2 × max(Tsrc, Tdst);启用复位时,两端还需同时保持复位至少(DEST_SYNC_FF + 2) × Tdst + 2 × Tsrc,复位期间不发送、输出无效。这些是该宏的条件,不能推广为所有 pulse/toggle 电路的公式。AMD 脉冲宏说明

同样,set_max_delay只约束指定路径,不能替代协议或证明绝不亚稳;Vivado 中更高优先级的时钟分组、false path 还可能覆盖它,需检查实际生效的约束。AMD 约束优先级说明

设计 CDC 前,先写下允许漏掉什么、是否允许等待、数据需不需要保持整体一致,再选同步结构。这样才能同时检查亚稳态可靠性和业务事件是否完整。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询