见过不少同学在Formality里被一堆unmatched points折腾得焦头烂额,其实问题往往不在验证阶段,而是在更早的综合流程里就埋下了——SVF文件没生成对、set_svf命令没加载好、或者加载时机错了。这篇文章我就专门聊聊SVF文件与Formality中set_svf命令的那些门道,从文件本身是什么、加载时机怎么选,到几个文档里不怎么强调但实测很管用的隐藏技巧,再附上几条我这些年踩出来的排查链路,希望能帮正在debug LEC问题的你少走点弯路。
1. SVF文件到底记录了什么东西:set_svf命令的对象得先搞清楚
很多人把SVF当成一个“综合产物”就完事了,实际上它更像是DC写给Formality的一本优化账本。你只有先知道这本账本记了什么,才能理解set_svf在Formality里为什么那么重要。
1.1 综合优化到底做了什么,SVF就记什么
Design Compiler在做逻辑综合时,会对RTL做一系列优化:组合逻辑的flatten和structure、寄存器合并(register merging)、重定时(retiming)、常量传播(constant propagation)、死逻辑移除,还有跨层次边界的逻辑移动。这些优化的共同特点是:它们把RTL中原本清晰可见的逻辑结构,改得面目全非。
拿retiming来说,一条路径上的两级触发器,被综合工具挪了位置、搬了方向,逻辑功能没变,但寄存器在网表里的名字、位置、层级路径全变了。如果Formality不知道这个变换过程,它面对的就是一份“看起来完全不一样”的网表,match阶段会非常吃力。
SVF(Synopsys Verification Format)干的事,就是把综合过程中每一步变换记录下来。它不关心变换背后的算法,只记录结果:哪些寄存器和原始设计里哪些寄存器对应、哪些组合逻辑被重构了、哪些常量被传播掉了、哪些点被映射到了工艺库单元。Formality拿到SVF之后,等于拿到了一条从RTL到门级网表的逻辑路径索引,match的时候顺着走就行。
1.2 SVF里关键信息的几个类别
SVF文件本身是纯文本格式,你可以直接打开看。我之前调试一个卡在验证阶段的设计时,就通过直接看SVF内容确认了寄存器合并的具体对应关系。从实用角度,SVF里最主要的信息可以分为这么几类:
- 命名映射:综合前后的层次路径变化。比如RTL里的
top/gen_regs[0].reg,综合后变成了top/u_phys/reg_023,中间经过了几次rename,SVF里会有记录。 - 寄存器级优化信息:retiming、register merging、寄存器重组等变换。这类优化如果SVF缺失,Formality经常会报出大量的unmatched registers。
- 常量信息:哪些寄存器、哪些输入端口被tie到0或1了,哪些逻辑被常量传播优化掉了。
- 设计边界信息:跨层次逻辑移动、partition调整导致的设计层级变化。
这些信息在Formality里会被转成match时的辅助引导,说白了就是“这些点应该互相匹配”的线索。注意它是辅助、是引导,不是绝对命令,Formality最终还要结合自身的逻辑等价性分析来做判断。
1.3 SVF版本和格式:跨版本使用要留个心眼
SVF文件跟工具版本是有绑定关系的。DC某个小版本生成的SVF,放到稍旧一点的Formality上加载,有时候能读,有时候会报格式不认识。我遇到过DC 2018.06生成的SVF,在Formality 2015.12上直接报SVF command syntax错误的情况。
这不是个例。Synopsys工具的大版本跨度一大,SVF中记录的guide命令格式就可能变化。所以最稳妥的做法是:综合和验证尽量使用同一主版本的工具,至少保证Formality的版本不比DC低太多。如果你的环境里工具版本杂,那在自动化脚本里最好加一道检查,确认DC和FM版本兼容性,别让问题拖到验证阶段才暴露。
2. set_svf在Formality全流程中的正确位置:加载时机比想象中重要
Formality的基本流程是读入参考设计和实现设计,然后match、verify。set_svf命令看似简单,但在脚本里的摆放位置,直接影响match的效果。
2.1 从读入设计到verify的标准脚本骨架
一个标准的Formality脚本流程大致是这样的:
# 加载参考设计(通常是RTL) read_verilog -r /path/to/rtl/top.v set_top rtl_top # 加载实现设计(通常是综合后网表) read_db -i /path/to/dc_output/top.db set_top impl_top # 加载SVF文件 set_svf -f /path/to/dc_output/top.svf # 执行匹配并验证 match verify这里最关键的一点是:set_svf要在match之前执行。因为SVF信息是在match阶段被消费的,match之后再加载,等于验证引擎已经按没有SVF的方式做了关键点匹配,SVF后面再进来,不会重新触发匹配流程,你等于白加载了。
2.2 为什么必须在match之前加载:关键点匹配的底层逻辑
Formality的match阶段,核心工作是找到参考设计和实现设计之间对应的逻辑点,这些关键点包括主输入输出、寄存器、黑盒端口等等。match结果的好坏,直接决定verify阶段能不能快速通过。
如果不加载SVF,Formality就只能靠名字相似性和逻辑锥分析来猜对应关系。RTL里一个寄存器叫cnt_reg,综合后变成了u0/u1/count_reg_2,名字上还有一些线索,但如果是retiming之后的寄存器,或者被合并掉一半的寄存器,光靠猜就非常费劲。
SVF给Formality提供的是优化路径上的精确线索。像寄存器合并这种变换,SVF会指明哪些寄存器被合并了、合并后的点跟原始点是什么关系。没有SVF的时候,Formality需要把这些寄存器当作不匹配点处理,然后再尝试用复杂的时序优化验证算法去证明它们在逻辑上等价——这个过程不一定100%成功,即便成功,耗时也会翻好几倍。
2.3 设计名对不上时怎么办:先别急着对SVF动刀子
加载SVF时一个常见问题是报design name不匹配。比如你Formality里reference design的名字叫TOP_rtl,但SVF文件开头记录的综合设计名是top_synth,set_svf回去提示design name mismatch。
这种时候,我的建议是先用report_design看看当前design list里的设计名,然后确认SVF里的名字。如果两者对不上,可以尝试在参考设计上重新set_top,让当前reference与SVF中记录的设计名对上。还有一个小技巧:DV环境下一个设计被综合了很多次,每次DC工程名不同,SVF里记录的名字可能五花八门,这种就需要回到DC侧去确认顶层RTL的module名和设计名,统一规范化。
不建议在Formality里直接强行修改SVF文件。它是文本格式没错,但里面很多条目之间是有联动关系的,你手动改一个名字,可能引发后续一串解析错误。
2.4 set_svf的几种调用写法
set_svf在fm_shell里常用的写法其实就那几种,我列一下常见的:
# 方式一:直接带文件名 set_svf -f ./output/design.svf # 方式二:先设置变量,再加载 set svf_file "./output/design.svf" set_svf -f $svf_file # 方式三:不带参数,查看当前SVF设置 set_svf有些场景下你会看到别人脚本里写了set_svf -f $svf_file之后又执行了set_svf -change_design之类的选项,这通常用于处理同一个SVF在不同design list下加载的情况。如果你的脚本里没有对design name做严格管理,加载SVF后务必看一眼输出日志里有没有warning,别一带而过。
3. 加载SVF之后Formality在背后做了什么:几个不易察觉的内部机制
很多用Formality的人,把set_svf当成一个“黑盒开关”:敲完就完事。但了解它背后的行为,对排查问题很有帮助。
3.1 SVF信息如何转换成匹配引导
SVF文件里记录的变换信息,在Formality加载之后会被转换为一系列内部的匹配引导信息。这些引导分成两类:一类是显式的点对应关系,告诉match引擎“参考设计的A点对应实现设计的B点”;另一类是属性信息,比如某条路径上的逻辑被优化掉了、某个寄存器被常量替换了。
在match阶段,Formality会对这些引导信息进行综合权衡。不是说SVF说有对应关系就100%匹配,它还要求两边的逻辑锥在布尔层面等价,至少在一定的逻辑深度内是一致的。不过有了SVF引导,match引擎至少知道该往哪个方向去验证,搜索空间小了很多。
3.2 为什么有些设计不加载SVF也能过,有些一定过不了
有位在代工厂做后端的朋友问过我:他之前做的某个模块,不加载SVF,Formality也能verify通过,为什么现在这个设计不加载就废?原因是设计类型和优化策略不一样。
组合逻辑优化为主的设计,比如只是做了flatten和structure的纯组合逻辑,Formality本身的布尔等价性引擎就足以应付。这种情况下,SVF更像一个锦上添花的加速器。
但一旦涉及寄存器级别的优化——retiming、register merging、regroup,以及跨层次优化——情况就完全不同了。Formality确实有自己的时序优化验证机制,但它的处理方式是在“发现不匹配”之后,再去尝试用复杂的时序逻辑等价算法去证明。这个过程慢不说,还容易失败,特别是优化幅度较大的设计。这时候SVF就是刚需,没有它你会在unmatched points的泥潭里挣扎很久。
3.3 怎么确认SVF加载效果:几个报告命令配合使用
加载完SVF之后,我会习惯性地跑一下report_svf_data,确认SVF确实被正确解析了。这个命令会报告SVF文件的基本信息,包括文件路径、读取状态、识别的设计名等。
match之后,再看report_matched_points和report_unmatched_points。如果这时候matched points数量明显偏少,unmatched里又有大量寄存器点,那多半是SVF没起作用或者加载时机不对。顺着这个线索往回查,比直接怀疑SVF文件本身要高效得多。
还要注意观察日志里有没有类似“Repeated sequence”或“Old combination circuit”的提示。SVF里有时会包含历史设计版本的信息,如果Formality认出了某些点是repeated sequence,它会在后续verify中做专门处理。这些信息通常会以warning或者info的形式出现,别直接忽略。
4. 实战中的隐藏技巧:这些用法文档里不常强调
set_svf命令本身很简单,但放到复杂的工程环境里,就有很多值得琢磨的地方。
4.1 分块综合、多SVF文件的处理
大型SoC的设计,很少是顶层一次性综合的,基本都是模块级综合,然后顶层整合。这时候你会遇到一个比较尴尬的局面:顶层验证时,reference是完整RTL,implementation是整合后的网表,但综合SVF是每个小模块各一份。
这种情况下,我在实践中比较推荐的做法是:如果顶层网表是由各block网表拼接后再优化的,那么每个block的SVF都有参考价值。你可以在读入实现设计之后,按顺序加载多个block的SVF:
set_svf -f ./block_a/output/block_a.svf # ... 读入block_a相关设计 set_svf -f ./block_b/output/block_b.svf # ... 读入block_b相关设计但要注意,set_svf设置的是全局的SVF,多次调用是覆盖关系,不是叠加关系。如果你需要同时让多个block的SVF信息都生效,得在同一个SVF文件里整合,或者在综合阶段就使用set_svf配合current_design在顶层统一管理,再或者分开验证,每个block单独做一次LEC。
这一点很多人会踩坑:在fm_shell里连续加载两个SVF,以为是追加,结果是覆盖。所以你在跑分块验证的时候,建议一个block一个fm_shell会话,或者用脚本显式清空再加载。
4.2 ECO之后SVF的失效与重新生成
ECO(Engineering Change Order)是后端流程里不可避免的环节。网表被手工修改或者用ECO工具改过之后,原来DC生成的SVF是否还能用?我的经验是:能用的程度取决于改动的范围。
如果ECO只是修改了某个组合逻辑门的连接,寄存器结构没动,那么SVF里关于寄存器和主要逻辑点的对应信息依然有效,可以继续用它做LEC。但如果ECO涉及寄存器增删、重定时调整,那就危险了——SVF里的寄存器对应关系会和实际网表产生偏差,Formality跟着旧SVF的引导去match,反而会把match结果带偏。
遇到这种情况,我的建议是:如果ECO区域集中且小,可以尝试手动set_user_match把受影响的点单独指定;更稳妥的方案是回到DC环境,对ECO后的网表重新综合一遍并生成新的SVF。重新综合的成本没有想象中高,但能省下后面Debug LEC的大量时间。
4.3 用SVF避免大量手动set_user_match的“体力活”
有些工程师在match结果不理想的时候,喜欢立刻上手set_user_match去强行为unmatched points建立对应关系。这是个危险的习惯。
set_user_match是硬约束,一旦设置,Formality就直接按照你的指定去做match,不再做逻辑等价性检验。如果设置错了一个点,后面verify阶段就会奇怪地fail,而且fail的原因会非常隐蔽。
相比之下,SVF提供的是软引导,它给Formality一个匹配方向,但匹配是否成立还是要经过逻辑检查。所以当match结果不理想时,优先排查SVF是否加载正确、是否加载完整,用SVF去解决unmatched points,比一上来就手动硬设要安全和省力得多。只有当SVF确实没有覆盖到、且你对设计结构非常确定的时候,才考虑补充手动指定。
4.4 SVF文件异常小或者为空的排查
有时候你会遇到SVF文件只有几百字节,甚至只有文件头的情况。这通常不是因为优化少,而是DC侧生成有问题。
我在DC脚本里一般会在综合开始前就设置:
set_svf $svf_file_name然后综合结束前,统一关闭:
set_svf -off关键就在于这个-off,它会把SVF文件完整关闭、确保所有缓冲内容落盘。如果综合过程中DC异常中断,或者脚本里压根没执行set_svf -off,就有可能导致SVF不完整。我遇到过最典型的情况是:综合脚本里设置了SVF文件,但后面跑了多轮compile_ultra迭代优化,SVF在中间被覆盖了,最终落盘的只有最后一轮的信息。这类SVF加载后Formality不报错,但效果很差,match结果依旧大量unmatched。
4.5 直接阅读SVF内容做debug的实用技巧
SVF既然是文本文件,直接打开看就是一个非常高效的debug手段。我调试过一个比较难缠的unmatched point:某个寄存器在RTL里叫en_reg,综合后网表里对应点似乎消失了。报告里只显示unmatched,找不到去向。
后来我直接在SVF里grep这个寄存器的名字,很快就看到了一条常量传播的记录,说明这个寄存器被DC tie到了常量值。有了这个信息,我再回网表里查,果然发现一个tie-off的buffer。这就是SVF的另一层价值——它不仅仅是给Formality用的数据文件,也是一份记录综合优化细节的调试日志。平时遇到奇怪的点,先grep SVF,经常能省掉大量翻网表的时间。
5. 踩坑实录:set_svf命令导致的典型问题与完整排查链路
理论说再多,不如直接给几条排查路径。下面这几个场景都是我实际工作中碰到过或者帮忙解决过的,每一条都有清晰的排查链路可以参考。
5.1 排查链路案例一:加载了SVF仍然大量unmatched
症状:Formality脚本里写了set_svf,match之后report_unmatched_points仍然一大片。
排查步骤:
- 先确认SVF文件是否存在、路径是否正确。很多时候是脚本变量拼接错误,文件路径多了一层或少了一层目录。
- 查看加载日志。set_svf之后,Formality通常会打印读取状态。如果出现design name不匹配的warning,按前文方法处理。
- 确认set_svf在match之前执行。这一点虽然基础,但我在好几个团队里都见过把它写在match后面的情况。
- 用
report_svf_data查看SVF里实际记录了多少信息。如果文件很大但报告显示识别的点数很少,说明SVF内容与当前design list对不上,很可能版本或设计名不一致。 - 回到DC侧确认SVF生成时使用的是哪一版网表。如果实现设计是ECO后的网表,而SVF是ECO前生成的,那unmatched就不可避免了。
这条链路走下来,基本能定位九成的问题。
5.2 排查链路案例二:SVF版本不匹配导致的解析失败
症状:set_svf加载时出现大段SVF命令解析错误,Formality日志里全是不认识的命令关键字。
排查步骤:
- 查看DC和Formality的版本号。如果版本差距过大(比如跨了两个大版本),直接统一版本最省事。
- 如果暂时没法统一版本,可以先用Formality读DC的DDC文件,利用DDC里自带的SVF信息来替代外部SVF。DDC格式里会内嵌综合信息和约束信息,Formality读取时能自动利用其中的匹配指引。这种做法虽然不是100%等同于外部SVF,但能在版本不匹配的时候救急。
- 另一个替代方案是让DC侧重新导出一次SVF,用低版本的SVF格式输出。有些版本的DC提供SVF版本兼容选项,但具体可用性要查对应版本的release note。
5.3 排查链路案例三:连续验证多个设计,SVF污染
症状:同一个fm_shell会话里,先验证了Block A,成功;接着验证Block B,set_svf加载了B的SVF,但match结果异常,连B的寄存器都匹配得乱七八糟。
原因:set_svf在同一个会话里的状态是持久的。前面的SVF信息不会自动清空,当你加载B的SVF时,如果B的SVF文件本身不完全,或者加载前后没有正确更替,Formality可能在match阶段同时参考到了两份SVF的信息,导致引导混乱。
解决:
- 在开始验证B之前,先执行
set_svf -off或者明确重新加载B的SVF文件。 - 更稳妥的是写脚本时,每个设计的验证流程独立,SVF加载放在读入设计之后、match之前统一设置。
- 验证完一个设计后,把fm_shell会话退出或者重置,避免跨设计状态残留。
5.4 排查链路案例四:第三方IP的SVF与自家顶层SVF冲突
场景:设计中集成了几个第三方IP,IP厂商提供了综合后的网表和对应SVF。你顶层用DC综合时,可能也会带上IP的SVF信息。到了Formality里,如果同时加载了IP的SVF和顶层的SVF,可能产生冲突。
处理:第三方IP的SVF应当跟在IP网表配套使用。如果顶层网表里已经把IP当作黑盒处理,那么IP内部的SVF信息对顶层验证没有意义,不加载反而干净。如果顶层综合时对IP做了展开(pipelining或boundary optimization),那么你更需要在顶层验证时加载的是顶层综合的SVF,而不是IP自己的SVF。这个分类要搞清楚,否则Formality里会出现大量自相矛盾的引导信息。
5.5 排查链路案例五:PT里生成的SVF用到Formality
场景:有些流程里,PrimeTime也会生成SVF文件,用于保持PT反标网表和综合网表的一致性验证。工程师图省事,把PT生成的SVF直接拿给Formality用。
结果:能用的概率不高。PT生成的SVF是面向时序分析和网表一致性验证场景的,它记录的优化信息跟DC综合优化不完全一致。如果非要让Formality能用,更可靠的做法是回到DC流程里重新生成一份专门的综合SVF。
最后再分享一点个人经验
SVF这种文件,在整个数字流程里不起眼,但它的质量直接影响后端验证效率。我自己的习惯是:在DC综合脚本模板里,把set_svf的生成和关闭写成固定写法,每次综合完成后自动检查SVF文件大小,如果明显小于预期(比如几十KB这种量级),就手动打开看几眼,确认内容合理再往后端release。这种“多看一眼”的习惯,已经帮我的团队挡掉了好几次后端LEC验证的返工。
另外,验证工程师不要只坐在Formality前面等结果。偶尔打开SVF文件,看看DC对你的设计做了什么,你才能对“逻辑等价性验证到底在验证什么”有更深的体感。后续遇到任何unmatched或者verify fail,你排查的路径都会比身边同事快不少。