☰
VC Spyglass CDC检查实战:跨时钟域验证的TCL脚本与SDC约束
2026/10/7 9:17:49 网站建设 项目流程

去年做一颗无线SoC的时候,我在项目启动第一周就把VC Spyglass的CDC检查环境搭了起来。当时很多人不理解,RTL还在天天改方案,这么早跑CDC能查出什么?结果第三周第一个完整block代码整合进来,工具一口气报了四百多条跨时钟域问题,其中有三条是真正能在芯片启动时把系统卡死的硬伤。从那以后,我再也不敢把CDC检查拖到流片前才做。

CDC是Clock Domain Crossing的缩写,也就是跨时钟域。只要一个设计里存在两个以上异步时钟,就一定有跨时钟域路径。这类路径上最经典的风险就是亚稳态,而亚稳态不像功能bug那样可以通过仿真稳定复现,它是概率性的、偶发的,环境一变可能就消失,到了现场才爆发。所以业内才专门有VC Spyglass这种静态工具,在不跑仿真、不依赖测试向量的情况下,用结构分析和形式化验证把跨时钟域风险全部摸一遍。这篇内容主要写给做数字前端设计、验证和集成的工程师,尤其是要把SpyGlass从零搭起来、还要把TCL脚本和SDC约束写明白的团队参考。

1. 为什么VC Spyglass的CDC检查越早跑越好

1.1 亚稳态不是玄学,是可以量化的风险

先说亚稳态。一个寄存器如果没有满足建立时间和保持时间,输出端就可能停在中间电平,然后经过不确定的时间才稳定到0或1。这个“不确定时间”可能长达几十纳秒,甚至在某些工艺角下更夸张。稳定的结果也是随机的,可能是0也可能是1,完全没法预测。

更麻烦的是亚稳态会传播。如果第一级寄存器进入了亚稳态,它输出端连接的组合逻辑会把中间电平继续往后推,下一级寄存器采到的可能是错误数据。所以跨时钟域的第一条设计规则就是:任何跨时钟域信号,都要用同步器把它“挡一下”。最常见的同步器就是两级寄存器串接,第一级采样原始信号,第二级再采样第一级的输出,给第一级留一个整拍的时间去恢复稳定。

这里有一个工程上常用来评估风险的指标,叫MTBF,平均无故障时间。MTBF跟同步器的二级寄存器分辨率时间、时钟频率、数据翻转频率都有关系,公式大概是:

MTBF = exp(t_r / τ) / (T0 * f_clk * f_data)

t_r是第二级寄存器允许的额外稳定时间,τ是工艺相关的恢复时间常数。这个公式想说明的是:同步器设计得越保守,比如增加三级、增加t_r,风险下降是呈指数级的。反过来,如果只是简单地把信号接到目标时钟域的寄存器上,没有任何同步器,MTBF可能短到按小时甚至分钟计算,这样的芯片根本没法量产。

1.2 CDC检查、功能仿真和时序分析三者分工

很多刚接触CDC的工程师会问:我们已经有UVM环境在跑功能验证,也有PT在做静态时序分析,CDC检查是不是多余的?其实三者解决的问题完全不一样。

功能仿真靠的是你给的激励。跨时钟域的时序问题高度依赖信号到达寄存器时钟边沿的相对位置,仿真器默认会假设所有寄存器采样都在理想时间点完成,或者稍微带一点随机但不精确的延迟,很难真实覆盖亚稳态窗口。你跑十万个测试用例,可能也触发不了一次真正的冲突。

静态时序分析也就是STA,它主要查建立时间和保持时间,关心的是“路径能不能在一个时钟周期内走完”。但对于两个异步时钟域之间的路径,时序根本不可比,两者之间没有一个确定的相移关系,STA没办法给出严格约束,所以通常在SDC里直接设成false path。这部分时序不吃紧,但设计上必须要有同步机制,这正是CDC检查的战场。

VC SpyGlass在流程中更像是做“设计架构审查”。它从RTL结构上识别哪些信号跨了时钟域,判断这些信号有没有经过同步器,同步器结构合不合理,多bit数据有没有用FIFO或者握手协议,是否存在可能让不同bit被不同时刻采样的风险。它可以在前端就发现结构性问题,而不是等到后仿真甚至测试阶段才暴露。

2. 搭建CDC检查工程:目录、TCL脚本和第一个run

2.1 环境准备与推荐目录结构

如果团队之前完全没用过SpyGlass,第一步不是写脚本,而是先把环境整理清楚。需要确认几件事:工具的license是否支持CDC相关的goal,比如CDC Verification和CDC FTD;本机可用的临时磁盘空间够不够,SpyGlass在elaborate大设计时会生成大量中间文件,目录满了会非常痛苦;以及RTL代码的编译顺序是否确定,有没有依赖IP的库文件。

我习惯在一个项目根目录下面维护这样一套结构:

.project_root/ rtl/ top.v sub_block.v sdc/ top.sdc top.cdc.sdc scripts/ run_cdc.tcl setup_env.tcl work/ spyglass_project/ reports/ cdc_report/

scripts目录放所有流程脚本,rtl只读不改,sdc分两套:一套是顶层常规约束,另一套专门放CDC相关的异步约束。这样做的好处是后端起流程的时候,可以直接复用这里整理好的时钟和时钟组关系,不会出现前后端约束各写各的、对不上号的情况。

2.2 TCL脚本骨架与关键命令解读

VC SpyGlass本身支持命令行批处理和图形界面两种方式,工程上我强烈建议用脚本驱动。下面是一份最简可运行的TCL脚本骨架,基本流程是:建工程、读RTL、指定顶层、读约束、选goal、运行。

# run_cdc.tcl # 1. 创建工程 new_project -name cdc_check -directory ./work/spyglass_project -force # 2. 读入RTL文件列表 read_design -filelist ./rtl_filelist.f -top top # 3. 指定当前分析顶层 current_design top # 4. 读入SDC约束,重点是时钟定义和时钟组 read_sdc ../sdc/top.sdc read_sdc ../sdc/top.cdc.sdc # 5. 设置目标goal并运行 set_option enable_gui no run_goal -goal cdc_verify

先说明一点,不同版本SpyGlass的命令参数会有差异,比如read_design可能还需要指定语言类型,run_goal的goal名称可能是{/top/cdc_verify}这种带路径的格式。以你手上版本的User Guide为准,这里强调的是流程骨架。

read_sdc这一步很多人会忽略,觉得CDC检查不是只需要知道哪些时钟是异步的吗?实际上,SpyGlass需要SDC来获取时钟频率、端口约束、时钟组关系,这些信息直接影响它对跨时钟域场景的判断。没有时钟组声明,工具就会把两个实际异步的时钟当成同步关系来分析,结果就是大面积假violation,或者更糟,该报的没报。

2.3 编译和elaborate阶段最常翻车的三个原因

第一次跑SpyGlass,最容易挂在elaborate或者compile这一步。总结下来有三个高频原因。

第一个是RTL文件编译顺序不对。SpyGlass底层还是要先把Verilog或者VHDL代码“读进去”,再build成内部数据库。如果某个module被例化时还没编译,工具会报找不到module,有时候是warning,有时候直接归档error。解决方法是维护一个干净的filelist,按依赖顺序排列,最好直接用工具支持的编写方式。

第二个是缺少IP的仿真模型或者行为模型。现在SoC基本都会集成很多第三方IP、memory、PLL,SpyGlass读RTL的时候需要对应的库单元描述。如果你只有门级网表没有行为模型,工具可能无法完整理解IP内部逻辑,导致自动同步器识别失败。我的经验是,要么给SpyGlass提供可以elaborate的IP模型,要么直接在约束里把这些IP设为黑盒,然后用抽象模型代替。

第三个是work目录的旧数据残留。SpyGlass的工程目录如果不清理,第二次跑的时候容易出现工程状态错乱,比如new_project报工程已存在,或者读到的还是旧设计。所以在脚本开头用-force参数强制重建,或者每次run之前手动删掉work目录,都是避免这种低级问题的手段。

3. TCL脚本和SDC约束怎么配合才算完整

3.1 SDC在CDC检查里到底管什么

SDC本身是给综合和STA用的约束格式,但SpyGlass做CDC检查时也需要它,而且需要比STA更细的时钟关系信息。常规SDC里必须包含两部分:时钟定义和时钟组。

时钟定义最简单,比如:

create_clock -name clk_a -period 10.0 [get_ports clk_a] create_clock -name clk_b -period 6.0 [get_ports clk_b]

时钟组描述的是时钟之间是同步还是异步关系:

set_clock_groups -asynchronous \ -group [get_clocks clk_a] \ -group [get_clocks clk_b]

这里有一个特别容易犯的错:很多人觉得既然STA里已经把异步时钟设成了false path,CDC检查里就不用再管了。其实false path和set_clock_groups是两回事。STA关心的是这条路径要不要做时序收敛,而CDC检查关心的是这条路径上有没有同步机制。你在SDC里写false path,SpyGlass会认为这是一个异步跨时钟信号,然后重点检查它有没有被正确同步。如果你不写false path也不写异步时钟组,工具反而可能把它当成同步路径,直接漏掉检查。

所以正确的做法是,在标准SDC里把异步关系用set_clock_groups声明清楚,再用另一份CDC相关的约束文件,告诉SpyGlass哪些路径是经过同步器的、哪些复位是异步复位的、哪些信号在特定模式下面是常数值。

3.2 SpyGlass专用CDC约束:同步器、复位和常量

SpyGlass有一套自己的约束命令,官方叫SGDC或者DCT语法,用于补充标准SDC里表达不了的CDC信息。

最常见的用途是手动标记同步器。SpyGlass定义了两级寄存器串接结构,并且要求第一级采样时钟与第二级采样时钟不同,它才会认为这是一个同步器。但实际设计里有各种变体,比如同步器中间插了一个反相器,或者第一级到第二级中间有一个选择器,工具可能识别不出来。这种情况下就需要手动约束:

set_cdc_sync -from [get_cells u_sync/src_ff] \ -to [get_cells u_sync/dst_ff]

不同版本语法不同,也可能是set_sync_cell这类写法,但思路是一致的:明确告诉工具这条路径就是同步器,让工具不要把它当成未保护路径报出来。

另一个重要约束是异步复位的处理。芯片里大量复位信号是异步置位、同步释放的,CDC检查如果不知道复位域,可能把复位信号到普通寄存器的路径报警报错。一般需要在约束里声明异步复位相关信号,或者用set_case_analysis把复位置成常数,从而消除复位路径对CDC分析的干扰。

还有test_mode。DFT模式的时钟和功能时钟完全可能是异步的,scan链上会有大量跨时钟连接,这些在功能CDC检查里都是假的。通常做法是给test_mode端口设置case analysis为0,告诉工具在功能模式下这个信号是固定值,这样Scan相关的路径就不会参与分析。

set_case_analysis 0 [get_ports test_mode]

这个操作虽然简单,但能省下大量排查假violation的时间。

3.3 约束书写的顺序问题

TCL脚本是顺序执行的,SDC和SGDC命令之间的顺序有时会影响结果。举一个实际场景:如果你先set_clock_groups声明了两个时钟是异步的,再在后面用set_false_path或者set_case_analysis,这个false path会被正确理解。但如果顺序反过来,某些版本的工具在内部解析时,可能会因为false path的存在而把时钟组关系覆盖掉,导致后续CDC分析认为它们还是同步的。

为避免这类诡异问题,我通常固定用一套顺序:

  1. 读设计、指定顶层
  2. 先读基础SDC,包含时钟定义和常规时序约束
  3. 再读CDC专用约束,包含set_clock_groups异步组、set_case_analysis、set_cdc_sync这些CDC语义
  4. 最后再启动goal

这样下来,后加的CDC约束信息会覆盖在前面基础约束之上,逻辑上比较清晰,也方便在报告里回溯哪条规则在哪个阶段引入。

4. Goal选择与运行策略:结构检查、验证、FTD怎么串

4.1 CDC_SETUP、DETECT、VERIFY、FTD逐级说明

VC SpyGlass的CDC流程通常不是一个大goal一把梭,而是分阶段的。不同版本叫法不完全一致,但核心逻辑都类似:先setup,再detect,再verify,最后做formal等价性验证。

CDC Setup阶段主要是把设计编译、elaborate好,把时钟、复位、常量这些约束都加载进去,并生成一个干净的数据库。这个阶段如果约束不全,后面任何结论都不可靠。它更多是基础设施,不是用来直接找CDC violation的。

Detect CDC阶段做的是结构检测。工具会扫描所有寄存器,确定每个寄存器的采样时钟,找出所有跨时钟域的路径,再分析这些路径上有没有同步结构。这个阶段输出的报告里有大量的“跨时钟域路径”列表,其中包括已经受保护的、未受保护的,以及不确定的。我一般先过这个报告,把大规模结构问题,比如某个模块完全没有同步器,快速筛选出来。

Verify CDC阶段就更深一层。它不只是看有没有同步器,还会验证同步器是否有效、数据路径是否存在收敛性问题、多bit跨时钟域是否使用了匹配的协议、FIFO指针和格雷码是不是成对出现。很多复杂的CDC问题,比如两个跨时钟信号汇聚到一个逻辑门上再被采样的reconvergence问题,是在这个阶段被发现的。

FTD,也就是formal equivalent checking related to CDC? 更准确的说是Formal Testability DRC,也有人叫CDC FTD,一般是做一种更严格的formal分析,来确认同步器和相关逻辑在某个抽象模型下是功能正确的。这个阶段对工具性能要求更高,通常在大模块或者整个chip上跑。

4.2 命令行批处理与图形化调试的配合

我推荐的做法是:平时回归全用命令行批处理跑detect和verify,一旦报告里出现看不懂的violation,再打开GUI加载已保存的工程,用图形界面看电路结构。

GUI的好处是可以直接把source、destination、同步器高亮出来,还可以展开时钟路径,看每个寄存器到底被哪个时钟采到。但GUI不适合做大工程运行,太占内存太慢。所以工程做法是把重活放命令行,把排查放GUI。

还有一个细节:保存工程后,最好同时把当前约束和RTL快照一起归档。有时候你会遇到这种情况,一份violation report是上周跑出来的,但本周RTL已经改了三版,回头想复现当时的电路结构,没有快照根本做不了。我一般会在run命令前加一条记录当前git commit的操作,或者把关键文件copy到report目录。

5. Violation解读与调试实录

5.1 常见Violation类别

CDC报告里会按严重级别分类,常见有Error、Warning、Info。Error不一定是真正的硬件bug,但一定是需要人工review的;Info多数是工具认为需要关注的跨时钟域情况,但不一定有问题。

从内容上,我习惯把violation拆成几类:

类别典型表现通常原因
无保护跨时钟路径source到destination之间没有任何同步器漏加同步器,或约束没标同步器
同步器识别问题明明有双触发器还被报成未保护同步器命名或结构不符合工具默认规则
数据总线跨时钟多bit信号直接跨时钟域,没有FIFO/握手设计结构不合理,需要改架构
复位域问题异步复位路径被当成普通跨时钟路径复位约束缺失
收敛性问题多个跨时钟信号在目的逻辑汇聚需要检查汇聚后逻辑是否有逻辑竞争
协议不匹配发送端用格雷码,接收端却按普通数据同步FIFO设计不规范

这些类别里,最容易误报的是第二类同步器识别问题,其次是第四类复位约束缺失。这两类问题的解决方式往往不是改RTL,而是补约束。

5.2 一个跨时钟域误报的排查经历

有一次,工具报了一条violation,说某个控制信号从clk_a域到clk_b域没有经过同步器。我打开RTL一看,明明有两级寄存器,第一级由clk_a采样,第二级由clk_b采样,结构上就是典型同步器。

为什么工具没认出来?后来发现这个信号的路径上,第一级寄存器输出没有直接接第二级寄存器输入,中间隔了一个AND门。这个AND门在功能上不影响同步器效果,因为另一个输入恒定是1,但工具不会去分析常量传播,默认把中间有组合逻辑的结构判为“非标准同步器”。

解决办法有两个:要么改RTL把AND门去掉,要么在约束里显式声明这两级寄存器构成同步器。我选择的是先查这个AND门为什么会存在,发现是上游代码风格问题,一个冗余逻辑被保留了下来。清掉之后,工具就识别了。这个案例说明,工具误报有时候真的能暴露代码里不必要的逻辑,及时清掉对后端也有好处。

5.3 Waive和Review的正确姿势

面对几百条violation,逐条waive是很危险的,但完全不waive也不行。关键是waive要有依据、可追溯。

我建议团队内部建立一个review流程:每一条waive都必须在report里写明原因、影响分析、处理时间、处理人。比如“该信号为测试模式下才跨时钟域,功能模式已由test_mode=0屏蔽”,这种属于合理waive。但如果只写“与设计无关”这种话,后面检查的人完全没法判断,等于没waive。

SpyGlass本身支持在report里标记waive状态,也可以在TCL脚本里统一处理。但即便工具支持,我仍然建议把waive清单维护到项目wiki或者bug系统里,跟tool报告对起来。这是因为工具报告可能会在下次run时被覆盖,而wiki记录可以长期保留。

6. 避坑清单与项目复盘

6.1 高频坑位整理

这些年跑VC SpyGlass,踩过不少重复的坑,我整理了一份清单,基本每次新项目都能用上。

坑位现象避坑方式
没建异步时钟组该异步的路径被当同步查,漏报所有异步时钟必须显式set_clock_groups
同步器中间有组合逻辑标准两级同步器报未保护要么改结构,要么手动set_cdc_sync
省略elaborate直接跑verify报出大量异常确保setup阶段无fatal
test_mode未初始化扫描链跨时钟域全报成真实violation对test_mode端口set_case_analysis 0
复位信号未设异步复位到数据路径产生大量虚报正确声明异步复位或设为常数
IP无模型黑盒内部跨时钟域无法分析提供模型或使用abstract model
只跑detect不跑verify结构同步器OK但协议错误漏掉最终必须跑到verify/FTD
数据总线直接双触发器同步工具没报单bit风险,但多bit数据容易错依赖工具的多bit协议检查,并人工review

其中最危险的是最后一条。很多人知道单bit信号要用双触发器,但多bit总线直接把每个bit各接一个双触发器,这种结构在CDC上是大问题。因为每个bit在源时钟域的变化时刻不同,经过各自同步器后,目标时钟域采样到的可能不是同一个时刻的值。对这种多bit跨时钟,正确做法是异步FIFO、握手协议、格雷码编码,或者保证所有bit都满足同一个稳定窗口并经过仔细验证。SpyGlass能帮你识别一部分,但设计者在源头就要想清楚。

6.2 把CDC检查纳入日常回归的建议

最后一个建议,把CDC检查放到每天自动回归里,哪怕一天只跑一次detect和快速verify。原因很简单,CDC问题有个特点:它跟代码改动的关系很隐蔽。你可以连续两周每天只改一个小模块的接口,最终某一天把两个改动叠加起来,突然冒出一条新的跨时钟域路径。如果等到流片前统一检查,发现一条严重的CDC问题,改RTL可能牵动多个模块,排期上非常被动。

我现在的习惯是每天merge代码后自动触发一次SpyGlass batch run,并把report diff发送到邮件或IM群里。只看新增的violation,旧的处理掉就不要再刷屏。如果今天有人改动了同步器命名规则,或者某个模块之间新增了握手信号,第二天早上就能在report里看到变化。这种早期预警机制,比任何一次集中式检查都更有效。

再说回TCL脚本和SDC约束。这份东西不是写一次就完事的,随着设计演进,时钟结构、test_mode、复位方案都可能调整,脚本里的约束、文件列表都要跟着维护。把它当成一份活文档,每次改动设计后都要review一遍约束是否仍然匹配。只要约束不出大问题,VC SpyGlass给出的CDC结论基本就能让你在流片前睡得着觉。

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

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

立即咨询