有一种 bug 很会躲:板子大部分时间正常,偶尔数据错一位。复位后又好了,重新烧录也不一定复现。你盯着仿真波形看半天,所有时序都像是对的;拿逻辑分析仪抓,偏偏那一次错误不出现。
这类问题如果发生在两个时钟域之间,我会先把它当成 CDC(Clock Domain Crossing,跨时钟域)风险看。亚稳态本身不神秘,麻烦在于它是概率事件,而且经常把真正的问题伪装成“偶发数据错”。

两级同步器不是万能胶
很多人一听跨时钟域,就想到两级触发器同步。这个方法没错,但它主要适合单 bit 控制信号,比如 enable、flag、reset_done。它的作用是降低亚稳态继续传播的概率,不是保证多 bit 数据同时到达。
如果你把 8 bit、16 bit 数据总线直接打一排两级同步器,风险反而很明显:每一位被采样的瞬间不同,接收域可能看到一个“拼出来”的中间值。于是数据不是全错,而是偶尔错一位、错几位,现场就会特别难抓。

图:先分清信号类型,再决定同步方式。
先问一句:跨过去的到底是什么
单 bit 状态信号,可以用两级同步。窄脉冲信号,最好转成电平、toggle 翻转,或者加握手确认。多 bit 数据,不建议裸同步,应该配 valid/ready、请求/应答握手,或者直接用异步 FIFO。
计数器跨域还有一个常见做法:用 Gray Code。它的好处是相邻计数值只变化一个 bit,接收域即使采样在边界上,也不容易读到多个 bit 同时错乱的组合。异步 FIFO 的读写指针跨域,常常就是这个思路。
为什么“怎么抓都抓不到”
亚稳态触发需要碰上很窄的时间窗口。你加了探针、改了布局、换了编译选项,边界条件可能已经变了。更关键的是,错误出现时波形不一定留下明显痕迹,接收端只看到一个错误结果。
想抓这种问题,别只等最终错误。可以把触发条件前移:记录跨域数据变化次数、握手超时次数、FIFO 空满边界、非法状态码。现场日志里多留几个“错误前一拍”的快照,比单纯抓最终异常更有用。
落地时我会这样改
1. 列出所有跨时钟域信号。别只看 RTL 模块边界,复位、状态位、调试计数也要算进去。
2. 单 bit 控制走同步器。需要边沿检测时,在接收域里检测,不要把短脉冲直接跨过去。
3. 多 bit 数据加协议。用握手保证数据稳定窗口,吞吐量高就上异步 FIFO。
4. 指针和计数器用 Gray Code。跨域后再转回二进制,别把二进制计数器直接同步。
5. 验证里加 CDC 检查。仿真能看功能,CDC lint/约束检查能帮你提前发现结构风险。
跨时钟域最怕“看起来差不多”。两级同步器能救一类问题,但救不了所有问题。真正稳的做法,是先把信号类型分清,再选同步结构。等板子偶发错一位时再抓,成本就高多了。
全部0条评论
快来发表一下你的评论吧 !