
CD校验报告里那片连续飘红的路径我盯了大半个月始终觉得是RTL里复位同步器写得不规范。直到某天晚上把约束文件翻到第三遍才发现问题根本不在设计代码而是.sgdc里漏了一条create_reset。后来跟几个做SoC集成的朋友聊发现这种被异步复位假报吓到的情况太普遍了。这篇文章就把SpyGlass CDC中用create_reset约束异步复位的完整思路、字段逻辑和实战坑位一次性讲清楚适合正在做CDC signoff、或者被各种跨时钟域告警搞得一头雾水的数字IC前端和验证工程师。1. 异步复位为什么会在CDC验证里被冤枉1.1 工具眼里没有复位这个角色先从一个最反直觉的点说起在SpyGlass做CDC分析的时候工具本身并不知道一个信号到底是复位还是普通数据。它能看到的是一个信号网络怎么连、被哪些触发器采样、从哪个时钟域跨越到哪个时钟域。至于这个信号在功能上是复位、是使能、还是数据总线工具没有概念。异步复位的问题就出在这。rst_n可以在任意时刻被拉低恢复它和时钟沿之间没有固定的相位关系。如果把这样一路信号扔给工具而不做任何约束SpyGlass会默认它是一个来路不明的数据信号。于是从rst_n驱动的每一个触发器、以及通过这些触发器继续展开的组合逻辑路径都会被当作跨时钟域数据传输来检查。我见过某个项目里一个顶层异步复位脚因为漏了约束跑完CDC后直接报了四百多条红色告警。团队里几个工程师轮流看RTL看了一整周谁也没想到根因是约束文件里少了一行。其实工具已经通过波形和架构分析给出了提示信号只是当时没人往那个方向想。1.2 复位信号与普通数据信号的本质差异调试这个问题的核心是要先想清楚一件事复位信号在CDC语境下跟普通数据信号的行为模型完全不同。普通数据信号跨时钟域时我们关心的是数据会不会丢、会不会重复采、两个时钟域之间的握手或者同步器是否足够。而异步复位信号的行为是激活沿比如下降沿立即生效它不依赖任何时钟沿释放沿则可能靠近某个时钟采样沿导致触发器输出进入亚稳态。所以异步复位要检查的不是同步器够不够而是复位释放后是否存在恢复时间和移除时间recovery/removal的违例风险。这正是create_reset约束存在的根本原因告诉SpyGlass这个信号是复位然后工具就会把检查策略从数据CDC检查模式切换到复位CDC检查模式。前者会追同步器、握手协议、FIFO指针同步后者才去查复位同步器结构、复位域覆盖、复位释放沿与时钟的关系。1.3 一个比较形象的类比可以这么理解异步复位信号就像一栋楼里的消防警报它可以在任何时刻被拉响不需要跟楼里的监控系统对齐。如果你不提前告诉物业这是警报器安保系统就会把每次警报误判成有陌生人闯入然后触发一连串根本不存在的问题响应。create_reset就是提前登记这个警报器身份的那张表——登记完之后系统才明白哦这是警报不是入侵随之切换到对应的处置流程。很多工程师在报告上看到几百条CDC红色路径第一反应是怀疑同步器写错了这其实走错了方向。正确做法是先确认这些路径的起点是不是复位相关信号如果是先查约束再回头查RTL。2. create_reset约束拆解一个字段对应一类检查逻辑2.1 约束文件与基本语法SpyGlass的约束文件一般以.sgdc结尾通过类似read_constraints的命令读入工程。下面是一个最常见的异步复位约束写法create_clock -name clk_a -period 10 [get_ports clk_a] create_reset -name async_rst_n \ -active low \ -async \ -domain {clk_a clk_b} \ [get_ports hard_rst_n]这里-async声明的是异步复位-domain声明了这个复位同时作用于clk_a和clk_b两个时钟域。工具一旦读到这条约束就会把hard_rst_n识别为复位域信号并且对clk_a、clk_b两个域内的相关触发器做复位结构检查而不是跨时钟数据路径检查。不同版本的SpyGlass对约束命令的支持情况略有差异有的用create_reset有的旧工程里还能看到set_reset_signal。碰到版本切换时第一件事就是对齐用户手册里约束字段的名字别拿上一个项目的脚本直接套用。2.2 字段与检查逻辑对照create_reset里的每个参数不是摆设它背后对应着一类检查规则。我在实际项目里习惯把字段含义和工具行为列成一张对照表方便评审时跟RTL工程师对齐。约束字段作用工具侧的影响-name给复位域起名字告警报告中会以此名字归并路径命名不唯一时会掩盖或混淆不同复位域的问题-active low/high声明复位有效电平决定工具判断复位激活/释放的逻辑影响recovery/removal违例的分析-async声明为异步复位触发复位同步器结构检查要求设计中存在完整的异步复位同步释放电路-sync声明为同步复位不检查同步释放结构转而要求复位信号本身满足目标时钟的时序要求-domain {clk列表}声明复位作用的时钟域决定哪些时钟域下的路径会纳入复位检查遗漏域会导致部分路径被当普通非约束路径处理[get_ports xx]指定约束对象用端口名或网络名指定约束目标综合后跑网表时要考虑改名问题2.3 一个容易忽略的参数-domain的粒度问题很多人会觉得-domain写一下就算完了其实这个字段的粒度直接影响告警质量。比如一个芯片顶层有clk_100和clk_200两个时钟域复位信号同时复位置位两个域中的寄存器。如果约束只写了-domain {clk_100}那么在clk_200域里所有被该复位驱动的触发器在工具眼中仍然是未约束复位照样会把复位路径当普通跨时钟路径来查。从报告形态上这类问题通常表现为告警数量在某个特定时钟域下密集出现而reset信息列表里却看不到该域的复位归属。遇到这种分布别急着逐条分析路径先回到约束里把-domain补全。3. 三种高发误用为什么约束写了还会爆一堆违例3.1 只约束复位输入漏掉同步释放后的复位这是我从多个项目里总结出的最高频错误尤其是在采用异步复位同步释放标准结构的设计里。来看一个常见的复位同步释放模块module reset_sync #(parameter STAGES 2) ( input wire clk, input wire async_rst_n, // 原始异步复位 output wire rst_sync_n // 同步释放后的复位 ); reg [STAGES-1:0] rst_chain; always (posedge clk or negedge async_rst_n) begin if (!async_rst_n) rst_chain {STAGES{1b0}}; else rst_chain {rst_chain[STAGES-2:0], 1b1}; end assign rst_sync_n rst_chain[STAGES-1]; endmodule这个模块的功能很清楚复位输入时异步置位释放时经过两级触发器同步输出一个与clk对齐的复位信号。功能正确性没问题但CDC验证时如果只对顶层async_rst_n写了create_reset而对rst_sync_n这条后续网络没有约束工具就会把同步释放后产生的复位信号重新驱动大量功能触发器这件事理解成一条普通的跨时钟域数据路径。结果就是哪怕RTL没有任何问题rst_sync_n产生的所有扇出路径都会被报一遍跨时钟域违例。修复方式很简单把同步释放后的复位信号也单独声明并配合-sync参数告诉工具它已经跟目标时钟对齐了。create_reset -name async_rst_n -active low -async \ -domain {clk_100} [get_ports async_rst_n] create_reset -name rst_sync_n -active low -sync \ -domain {clk_100} [get_nets rst_sync_n]这条经验后来我直接写进了项目组检查清单所有reset_sync模块的输出网络必须单独约束不能只依赖输入复位。3.2 异步复位被误标成同步复位第二种常见事故是把一个真正的异步复位约束成-sync同步复位。表面上看似乎只是多写了一个参数但工具行为会跑偏。当一个信号被标记为同步复位时工具推断的逻辑是这个复位信号在目标时钟沿到来之前已经稳定了足够长的时间满足建立保持时间要求。因此它不会去检查复位同步释放结构反而会去检查这个同步复位信号本身是否满足时序收敛要求。问题是真正的异步复位信号根本没有跟目标时钟对齐它的释放沿可以出现在时钟周期的任意位置。工具一旦拿同步复位的假设去查就会报出一大批关于复位端上升沿的时序违例或者直接把复位路径当作普通组合逻辑数据路径展开。这类告警看着像时序问题实际是约束类型给错了。我一般这样判断如果复位信号是从芯片引脚直接进来的或者来自没有经过同步器处理的模块边界那它大概率是异步复位必须标-async只有经过同步器与目标时钟对齐之后的复位才适合标-sync。3.3 多时钟域共用复位时-domain声明不完整第三种高发现象发生在一个原始复位信号同时驱动多个时钟域的设计里。比如SoC中常见的por_rst_n上电复位会同时复位CPU域、总线域和外设域的寄存器。如果你在约束里只写了其中一个时钟域那么其它没有被声明到domain列表里的时钟域工具会认定这些域的复位路径没有约束。结果就是一部分路径告警消失了另一部分路径却还在满天飞让人误以为这轮跑完还剩几个真问题实际上只是约束覆盖不完整。正确做法是在-domain里把所有相关时钟域列全或者如果设计层次上存在多个不同名复位要分别与各自所在模块的时钟域绑定。这种绑定的粒度越细后期分析告警时越省力。误用场景报告表现根因修复方式只约束原始复位不约束同步器输出同步释放复位信号驱动的路径大量报CDC工具把同步后复位当数据信号补充rst_sync_n的create_reset异步复位错标-sync复位端出现大量时序违例工具按同步复位假设做时序检查改成-async补复位同步结构-domain遗漏部分时钟域告警集中在某个时钟域复位域覆盖不全补全domain声明4. 一次真实违例的完整排查复盘从RTL改起到约束落定4.1 现场情况有一回我负责一个通信子系统的CDC收敛结构是典型的UART加SPI外加一组寄存器配置总线。UART模块内部有一个标准的异步复位同步释放结构从顶层进来的hard_rst_n先经过两级同步器之后产生的uart_rst_n驱动UART全部功能寄存器。跑完SpyGlass之后报告里UART接收路径上一片飘红。告警内容大体是检测到跨时钟域数据传输缺少同步器起点基本都指向uart_rst_n网络。当时第一反应是UART的复位同步器接错了比如第一级触发器的D端没接高电平、或者异步复位端接到了同步复位端口上。4.2 第一轮排查RTL结构Actually没问题我打开RTL把复位同步器逐级核对了一遍两级触发器、异步复位端接的是顶层hard_rst_n、第一级D端接1、第二级D端接第一级Q端、输出接uart_rst_n。标准到不能再标准。又用SpyGlass的结构检查功能单独看了这几条路径工具给出的结论是同步器结构完整复位信号能正常同步释放。到这一步已经很清楚了设计侧没有问题。问题大概率出在验证环境对复位信号的身份识别上。4.3 第二轮排查约束文件逆向对照我去翻约束文件发现整个工程里只定义了uart_clk、cfg_clk等时钟约束复位相关的create_reset一条都没有。也就是说SpyGlass从头到尾都不知道hard_rst_n是复位信号。在没有任何复位约束的情况下工具对hard_rst_n的处理方式是这样的它把所有被hard_rst_n驱动的信号全部归入一个未知域而这个未知域的任何信号只要进入uart_clk域就被描述为跨时钟域数据流。复位同步器的两级触发器虽然在RTL里是完整结构但工具根本没有这是复位同步器的前提自然也不会用复位同步器的检查方式去验证而是按普通CDC路径去追了。4.4 第三轮补约束后的连锁反应我在.sgdc里加上了hard_rst_n的异步复位约束声明它覆盖UART和SPI模块所有相关时钟域。create_reset -name hard_rst_n -active low -async \ -domain {uart_clk spi_clk cfg_clk} [get_ports hard_rst_n]重新跑完一轮之前大片飘红的跨时钟路径告警确实消失了。但紧接着出现了一个新的告警指向uart_rst_n的复位同步器输出工具提示这个复位网络的复位域属性不明确因为它没有被单独声明为复位对象工具无法确认同步后的复位属于哪个时钟域。这个告警其实是好消息说明hard_rst_n的约束已经开始生效工具已经进入复位检查模式了。它只是在提示下一步同步释放后的复位信号也需要自己的声明。随后我补上了第二条约束create_reset -name uart_rst_n -active low -sync \ -domain {uart_clk} [get_nets uart_rst_n]再跑一轮报告干净了。整个过程下来真正花时间的不是改约束本身而是扭转看到红色就觉得RTL有bug的第一直觉。如果一开始就带着约束可能不全的怀疑去查至少能省下一半时间。4.5 这个案例给我的排查顺序建议经过这次复盘我后来遇到CDC告警的排查顺序基本固定成了三条先看告警起点信号是不是复位相关信号如果是优先检查复位约束是否完整。再看的是约束与设计结构是否匹配比如异步复位是否配了同步释放结构、domain是否覆盖全面。最后才回到RTL本身检查同步器结构、握手逻辑等问题。这个顺序不能说百分百准确但复盘过多次后确实能把约束问题和真实设计问题更快分开。5. 判断约束是否写对的自检验证法5.1 用工具自带的复位报告交叉核对约束写完别急着高高兴兴去看绿灯先做一步交叉验证在SpyGlass里打印当前识别到的复位域清单对照设计规格书逐个确认。检查两个重点一是复位域的数量是否和设计一致二是每个复位域的domain列表是否覆盖全部相关时钟域。如果工具识别出的复位域比设计规格少说明有复位信号没约束上如果工具识别出一个设计中根本不存在的复位域说明某个数据信号被误标成了复位这种错误往往比漏约束更隐蔽。5.2 数据流反证法临时制造一个错误还有一个我比较喜欢用的验证手段叫反证法。具体来说是在RTL里临时改一条逻辑制造一个确定性的复位结构错误比如把复位同步器的第二级触发器D端接到固定0或者删掉某个功能寄存器上的复位引脚连接然后重新跑一遍CDC。如果工具在复位检查规则下没有报出任何新告警那说明约束可能压根没生效或者对应的检查规则没开启如果报出了预期中的告警说明复位约束和检查链路是通的前面看到的干净报告才是可信的。这个操作听起来有点自找麻烦但对于确认约束真的起作用比单纯看绿色报告要可靠得多。改完验证之后记得把RTL改回来别让临时改动混进代码库。5.3 把约束文件当RTL一样做评审和版本管理.sgdc文件在团队里经常被当成辅助脚本地位比RTL低一等出了问题才想起来去翻。这个习惯在CDC收敛期特别吃亏。约束文件本质上是对设计意图的另一种形式化描述一条create_reset写错效果可能比一段RTL同步器漏写还严重。我现在要求项目里的约束文件必须跟RTL一起走代码评审commit message里写清楚每条复位约束对应的模块和时钟域。评审时重点看三点复位是否按原始输入和同步释放后的输出分别约束、domain列表是否覆盖完整、异步复位是否都配了同步释放结构。这样把问题前置到评审阶段比等CDC跑完再回来改省事得多。5.4 几条核心判断准则根据这几年的经验我还总结了几条判断约束写得对不对的快速准则碰到不确定的时候用来兜底看到一个异步复位信号第一反应不是去看它的扇出寄存器而是看它有没有同步释放结构如果结构在约束里必须有对应声明。如果报告里大量告警的形状完全一致比如都是从同一个起点网络展开的大概率是约束覆盖问题而不是设计内部存在多种不同故障。复位信号别复用成数据信号。有些代码会把复位信号拉出来跟普通信号做组合逻辑比如assign data_valid valid rst_n;这类写法虽然功能上没错但工具对复位信号同时又当数据用的结构处理起来很别扭会额外产生复位风格相关的告警。跨时钟域的同步器输出如果本身是复位信号也要单独约束不能因为源端已经约束过就跳过。5.5 一点个人习惯我现在做CDC验证有个习惯拿到一个陌生设计先花10分钟过一遍.sgdc把里面的复位约束全部列出来对照模块层次和时钟结构走一遍然后再去碰日志和报告。这个习惯帮我避掉了好几轮无效的告警分析也让我慢慢意识到很多看起来设计有问题的CDC告警最早的根子都在约束文件那一行不起眼的create_reset里。把这个约束管好了后面的收敛工作会轻松不少。