
1. 项目概述为什么我们需要形式验证工具Formality在数字芯片设计的漫长流程里从RTL代码到最终交付给晶圆厂的GDSII版图中间要经历综合、布局布线、时钟树插入、物理优化等一系列复杂的转换。每一次转换理论上都应该保持设计的功能不变。但“理论上”这三个字恰恰是工程师最怕的。你辛辛苦苦写好的RTL经过综合工具转换成一个由标准单元门电路组成的网表你怎么能百分之百确信这个网表的功能和你的RTL描述一模一样没有因为工具优化引入一个隐蔽的反相器没有因为某个脚本的边界条件错误导致一个关键的多路选择器被优化掉了传统的验证方法是仿真。这就像给设计搭建一个庞大的测试游乐场输入各种测试向量观察输出是否符合预期。但仿真的致命缺陷在于它本质上是“抽样检查”。你的测试用例覆盖率再高也无法穷举所有可能的输入组合尤其是对于现代动辄几十亿个状态的设计。更别提门级网表的仿真速度极其缓慢跑一个大型设计的回归测试动辄以天甚至周为单位。想象一下这个场景你花了一周时间做门级仿真终于发现了一个深藏的时序bug。你回头修改了RTL重新综合得到了一个新网表。现在你是选择再花一周时间重新跑一遍仿真还是祈祷这次综合工具没有犯新的错误这时候形式验证Formal Verification的价值就凸显出来了。它不做仿真不跑测试向量。它的核心思想是数学证明将参考设计Reference Design比如你的RTL和实现设计Implementation Design比如综合后的网表都转换成形式化的数学模型通常是某种形式的逻辑电路图然后运用数学方法如等价性检查Equivalence Checking来严格证明这两个模型在功能上是否完全等价。Synopsys的Formality就是业界在这个领域最主流的工具之一。它不关心设计跑得快不快只关心“对不对”——这个网表是不是那个RTL的精确功能映射。对于数字前端和后端工程师来说掌握Formality不是“锦上添花”而是“雪中送炭”的必备技能。它主要用在两个关键场景RTL vs. Netlist和Netlist vs. Netlist。前者用于验证综合过程没有引入功能错误是前端设计签核Sign-off的关键一步后者则常用于验证物理实现如布局布线、ECO修改没有破坏原有逻辑。它能在几分钟到几小时内给你一个数学上严谨的“是”或“否”的答案替代那些耗时数日的仿真极大地提升了设计迭代的效率和信心。2. Formality核心原理与工作流程拆解要玩转Formality不能只停留在敲命令的层面必须理解它底层的工作逻辑。这样当工具报出成千上万个“Unmatched Points”时你才知道从哪里下手而不是两眼一抹黑。2.1 等价性检查的数学内核Formality进行等价性检查的核心可以通俗地理解为“比较两张电路图”。但它比较的不是晶体管或门的物理连接而是它们的布尔逻辑函数。建模首先Formality会将你的设计无论是RTL还是网表解析并编译成内部表示形式通常是一种称为“与或非图”AIG And-Inverter Graph或类似的数据结构。这个结构中的每一个节点代表一个逻辑函数如与门、或门、寄存器节点之间的连线代表信号的传播。关键点在于这个模型是“无时序”的纯组合逻辑视图对于时序元件如寄存器会将其输入/输出端口视为组合逻辑的边界。建立比较点Compare Points工具需要知道从哪个点开始比较以及比较到哪个点结束。这些点就是比较点主要包括主输入Primary Inputs, PIs设计的顶层输入端口。主输出Primary Outputs, POs设计的顶层输出端口。寄存器Flip-Flops, FFs存储元件。Formality会将当前状态的寄存器输出以及下一状态的寄存器输入都作为比较点。黑盒Black Boxes那些没有内部实现如第三方IP、存储器模型的模块的输入输出。匹配Matching这是最关键也是最容易出问题的一步。Formality需要将参考设计Ref中的每一个比较点与实现设计Impl中功能对应的点配对起来。它主要依靠两种策略名称匹配Name Matching最直观的方式。如果两个点的层次化路径名Hierarchical Path Name相同或高度相似工具会尝试将它们匹配。这是为什么保持综合前后信号名稳定使用set_dont_touch等命令如此重要的原因。特征匹配Signature Analysis当名称因优化而改变时Formality会计算每个比较点的“特征值”。这个特征值基于该点的逻辑函数即其输出值与所有主输入/寄存器输出的布尔关系。如果两个点的特征值相同即使名字不同工具也会认为它们是等价的并尝试匹配。这非常强大能处理大量由逻辑优化如重定时Retiming、资源共享引起的名称变化。验证Verification一旦所有比较点都成功匹配Formality会对每一对匹配的点进行数学证明。它构建一个“Miter电路”将Ref和Impl中对应的点连接到一个异或门XOR上然后试图证明这个异或门的输出在任何可能的输入组合下恒为0即两个输入永远相等。如果证明成功则这对点等价如果找到一种输入组合能使异或门输出为1则这对点不等价工具会报告一个失败点Failing Point并可能给出导致失败的输入向量Counterexample。2.2 关键概念SVF文件与“指引”模式如果你直接把综合前的RTL和综合后的网表扔给Formality匹配率很可能惨不忍睹。因为综合工具如Design Compiler为了优化面积、时序会进行大量重命名、重组、插入和删除逻辑的操作。这些操作在数学上是等价的但完全打乱了电路的原始结构。这时就需要SVFSetup and Verification文件。这个文件是Design Compiler在综合过程中自动生成的“操作日志”。它详细记录了综合工具对设计所做的每一次重要的转换操作例如将某个寄存器移到了另一个层次。将两个加法器合并成了一个。插入了一个时钟门控单元ICG。对某个信号进行了重命名。Formality在“指引模式”Guided Mode下读取这个SVF文件。SVF文件中的每一条“指引”Guide命令都会告诉Formality“嘿别看这两个点现在名字不一样其实是我把它从A改成了B它们是一回事。” 有了SVFFormality就能穿透综合工具制造的“迷雾”准确地找到对应关系。可以说没有SVF文件的Formality验证成功率会大打折扣甚至无法进行。在项目实践中确保SVF文件被正确生成并传递给Formality是流程中的重中之重。注意对于大型分层设计可能会生成多个SVF文件例如子模块综合一次产生一个顶层模块再综合一次又产生一个。你需要使用set_svf -append命令按顺序加载所有相关的SVF文件确保Formality获得完整的转换历史。3. Formality实战从环境配置到结果分析理解了原理我们来看一个完整的、贴近真实项目的Formality运行流程。假设我们有一个名为DESIGN_TOP的模块已经完成了RTL编写和逻辑综合。3.1 环境准备与启动首先你需要一个配置好的Synopsys Formality环境。通常通过source一个包含工具路径的设置脚本来完成。# 示例设置环境变量 source /synopsys/setup.sh # 启动Formality的交互式命令行界面更推荐便于调试 fm_shell # 或者启动图形界面GUI fm_shell -gui启动后你会进入Formality的命令行环境。建议将后续所有设置命令写在一个Tcl脚本如run_fm.tcl中以便复用和版本管理。3.2 关键变量设置详解在读取设计之前必须设置一系列环境变量这些变量控制着Formality的解析、匹配和验证行为。很多匹配问题都是由于变量设置不当引起的。# 1. 设置设计相关路径和名称 set DESIGN_NAME “DESIGN_TOP” set REF_DIR “./rtl” set IMPL_DIR “./netlist” set SVF_PATH “./syn_output” # 2. 设置DesignWare根目录如果设计中例化了Synopsys的DesignWare IP set hdlin_dwroot “/path/to/synopsys/libraries/syn” # 3. 关键变量设置 # (a) 忽略特定警告信息防止工具因某些非致命警告而停止 set hdlin_warn_on_mismatch_message “FMR_ELAB-146 FMR_ELAB-149” # (b) 时钟门控识别模式。综合工具会插入时钟门控单元ICG来降低动态功耗。 # ‘any’模式让Formality能识别各种类型的时钟门控。 set verification_clock_gate_hold_mode any # (c) 启用反相器推演。网表中的寄存器可能使用反相输出QN而RTL中用的是正相输出Q。 # 开启此选项能让工具识别这是同一个寄存器。 set verification_inversion_push true # (d) 名称匹配过滤规则。网表中的名字可能包含许多RTL中没有的特殊字符如层次分隔符/被替换成_。 # 这些设置帮助工具在比较名字时忽略这些字符。 set name_match_filter_chars “‘~!#$%^*()_-|\[]{}’:;?,./” set name_match_use_filter true # (e) 启用特征分析匹配。这是应对重命名优化的核心手段必须开启。 set signature_analysis_match_compare_points true # (f) 处理未解析模块。如果设计中有些模块没有提供源码/网表如硬核IP、模拟模块将其设为黑盒。 # Formality将只验证其端口连接关系不验证内部逻辑。 set hdlin_unresolved_modules black_box # (g) 设置失败点上限。避免在出现大量错误时工具无休止运行。 set verification_failing_point_limit 503.3 读取设计与加载指引这是流程的核心步骤顺序很重要先设置SVF再读参考设计最后读实现设计。# 4. 设置并加载SVF文件必须在read_design之前 # 假设综合产生了 DESIGN_TOP.svf set_svf ${SVF_PATH}/${DESIGN_NAME}.svf # 5. 读取参考设计RTL # 使用 -container r 将读取的设计放入名为‘r’的容器 read_verilog -container r ${REF_DIR}/${DESIGN_NAME}.v # 如果有多个文件依次读取 read_verilog -container r ${REF_DIR}/sub_module_A.v read_verilog -container r ${REF_DIR}/sub_module_B.v # 6. 链接参考设计并指定顶层模块 set_top r:/WORK/${DESIGN_NAME} # 7. 读取实现设计网表 # 注意使用 -netlist 选项告诉Formality这是门级网表 read_verilog -container i -netlist ${IMPL_DIR}/${DESIGN_NAME}_post_synth.v # 8. 链接实现设计 set_top i:/WORK/${DESIGN_NAME}3.4 设置验证约束与匹配在验证前我们需要告诉Formality一些设计的具体状态特别是测试逻辑如Scan链通常在实际功能模式下是不起作用的。# 9. 禁用测试逻辑以Scan链为例 # 假设网表中有一个全局扫描使能信号 TEST_MODE set_constant -type port r:/WORK/${DESIGN_NAME}/TEST_MODE 0 set_constant -type port i:/WORK/${DESIGN_NAME}/TEST_MODE 0 # 告诉Formality不要比较扫描链的输出端口 set_dont_verify_point -type port r:/*/SCAN_OUT* set_dont_verify_point -type port i:/*/SCAN_OUT* # 10. 执行匹配 match运行match命令后Formality会输出详细的匹配报告。这是你第一个需要仔细审查的节点。3.5 分析匹配报告与调试一个健康的匹配报告通常长这样*********************************** Matching Results *********************************** 8562 Compare points matched by name 1245 Compare points matched by signature analysis 0 Compare points matched by topology 150 Matched primary inputs, black-box outputs 15(8) Unmatched reference(implementation) compare points 2(0) Unmatched reference(implementation) primary inputs, black-box outputs 0(0) Unmatched reference(implementation) unread points匹配成功点matched by name名称匹配和matched by signature analysis特征匹配的数量是主力。特征匹配数量多说明综合优化力度大。未匹配点Unmatched Points这是关键。15(8)表示参考设计有15个点没找到对应实现设计有8个点没找到对应。我们的目标是让这两个数字都变为0。如果未匹配点数量不为0必须使用以下命令进行深入调试# 报告所有未匹配的点并查看其类型和层次路径 report_unmatched_points unmatched.rpt # 对于数据路径如乘法器、加法器综合工具可能做了特殊优化单独报告它们 report_unmatched_points -datapath unmatched_datapath.rpt # 查看匹配上的点确认关键寄存器、IO是否都已匹配 report_matched matched.rpt调试未匹配点的常见思路检查SVF首先确认SVF文件是否正确加载是否是最新版本是否包含了所有子模块的综合信息。检查黑盒确认所有该设为黑盒的模块如存储器、PLL都已正确设置。一个未声明为黑盒的缺失模块会导致其所有内部点都成为未匹配点。检查常数设置像TEST_MODE这类信号是否在参考设计和实现设计中都被设置为相同的常数通常是0。如果一边是0一边是1会导致下游逻辑完全不同无法匹配。检查设计读取确认读入的网表是否包含了所有必需的库文件.db或.lib特别是标准单元库和IP库。分析具体点打开unmatched.rpt找一个典型的未匹配点用report_point命令查看其详细信息分析为什么工具认为它无法匹配。3.6 执行验证与结果分析当未匹配点数量减少到可接受范围理想为0或者你确认剩余的未匹配点不影响核心功能如某些未连接的测试信号后就可以进行最终验证。# 11. 执行验证 if { [verify] } { echo “Formal Verification PASSED!” # 可以生成一些报告用于存档 report_passing passing_points.rpt } else { echo “Formal Verification FAILED!” # 验证失败必须生成详细报告进行调试 report_failing -verbose failing_points.rpt report_error_candidates error_candidates.rpt } quit验证通过PASS恭喜你从数学上证明了你的网表功能与RTL一致。你可以对这次综合结果抱有高度信心。验证失败FAIL工具找到了功能不等价的地方。这时需要深入研究failing_points.rpt。分析失败点报告 失败报告会列出所有被证明不等价的比较点对。对于每一个失败点你需要定位根据层次路径找到RTL和网表中对应的逻辑。理解原因Formality有时会提供一个反例Counterexample展示一组使输出不同的输入值。这是最直接的调试线索。常见失败原因SVF文件不完整或错误综合的某些转换没有被正确记录。常数设置不一致参考设计和实现设计中某些信号的值被固定为不同的常数。设计文件版本不一致你验证的网表可能不是由当前版本的RTL综合而来的。工具Bug或局限性极少数情况下可能是Formality或综合工具本身的Bug。此时需要简化案例联系Synopsys支持。4. 高级技巧与实战避坑指南掌握了基础流程下面这些从实际项目“踩坑”中总结的经验能帮你更高效地使用Formality。4.1 处理复杂情况多时钟域、门控时钟与ECO多时钟域Formality默认能处理多时钟域。但要确保所有时钟信号在参考和实现设计中都被正确识别为时钟通常通过SDC约束文件在综合时定义。如果某个时钟在网表中被优化成了普通信号会导致其控制的寄存器无法匹配。门控时钟集成这是最容易出错的地方之一。除了设置verification_clock_gate_hold_mode务必确保SVF文件中包含了时钟门控单元插入的指引。有时需要手动为门控时钟创建“时钟”对象使用create_clock命令。ECO工程变更指令验证这是Formality的强项。流程是将ECO前的网表作为参考设计ref。将应用了ECO修改可能只是手动替换或增加了几颗门的网表作为实现设计impl。关键必须使用综合工具为这次ECO生成的SVF文件或者对于小的手动ECO甚至需要工程师自己编写简单的SVF指引命令告诉Formality“这个新加的缓冲器只是为了修复保持时间没有改变逻辑功能”。运行验证。如果通过证明ECO只改变了时序没有改变功能。4.2 性能调优与大规模设计处理当设计规模很大超过百万门时Formality运行时间和内存消耗可能成为问题。分模块验证Bottom-Up不要总是验证整个芯片顶层。先对各个子模块Sub-block单独进行RTL vs. Netlist的验证。验证通过后将这些子模块在顶层设为黑盒set_black_box再验证顶层的集成逻辑。这能极大减少单次运行的工具负载。使用set_constant进行逻辑剥离如果设计中有一些复杂的、不关心其内部细节的配置逻辑或调试逻辑可以将其输入端口用set_constant固定为某个值从而简化验证模型。控制验证深度使用set_verification_effort命令可以调整验证算法的努力程度。“Low” effort更快但可能无法证明某些复杂逻辑的等价性“High” effort更彻底但更慢。通常先从“Medium”开始。利用多核较新版本的Formality支持多线程运行。可以通过set_host_options命令来分配更多的CPU核心。4.3 常见错误与解决方案速查表问题现象可能原因排查步骤与解决方案大量寄存器未匹配Unmatched FFs1. SVF文件未加载或加载顺序错误。2. 时钟/复位信号未被识别。3. 设计中有大量由综合工具生成的新信号如_n123。1. 检查set_svf命令确认文件路径正确且在read_design前执行。2. 检查网表中时钟/复位端口是否被优化。在综合约束中确保它们被set_dont_touch_network。3. 这是正常现象依赖特征匹配。确保signature_analysis_match_compare_points设为true并给工具足够时间匹配。验证失败报告组合逻辑不匹配1. RTL与网表版本不一致。2. 综合约束过于激进导致逻辑被优化掉。3. 设计中存在不定态X传播。1. 检查代码版本管理记录确认使用的文件版本。2. 检查综合日志看是否有“优化掉冗余逻辑”的警告。适当放松约束重新综合。3. 在RTL仿真中检查是否有X态传播到关键比较点。Formality可能将X态与0/1比较导致误报失败。工具运行极慢或内存溢出1. 设计规模太大。2. 存在未设置的黑盒导致工具试图验证不可知的逻辑。3. 存在复杂的算术单元如大位宽乘法器。1. 采用分模块验证策略。2. 确认所有第三方IP、存储器编译器生成的模块都已正确设为黑盒。3. 对数据路径模块单独验证或使用工具提供的专用数据路径验证流程。匹配通过但验证失败点很多1. 测试模式信号如Scan Enable未正确设置为常数。2. 多路选择器的选择信号在参考和实现中值不同。3. 存在锁存器Latch且行为不一致。1.这是最常见原因仔细检查所有测试模式、调试模式信号确保在功能模式下被禁用设为0。2. 使用report_failing -verbose查看失败点的逻辑锥追踪导致选择信号不同的源头。3. 检查RTL中是否无意中描述了锁存器而综合工具可能以不同方式实现了它。4.4 个人实操心得把Formality集成到CI流程在大型项目中我强烈建议将Formality检查集成到持续集成CI流程中比如每次RTL有提交或每晚自动构建时都自动运行。这能极早地发现综合脚本错误或工具版本兼容性问题。具体做法是编写健壮的Tcl脚本将上述所有步骤封装在一个Tcl脚本里接受设计名、路径等作为参数。定义明确的通过/失败标准不仅仅是看最终的verify返回结果。在CI中可以设定更严格的标准例如未匹配点必须为0失败点必须为0。解析日志并生成报告让CI脚本解析Formality的运行日志和报告提取关键指标匹配率、验证结果并生成一个简洁的HTML或Markdown格式的总结报告通过邮件或聊天工具发送给团队。版本控制所有输入将SVF文件、网表文件、Formality运行脚本和配置文件一并纳入版本管理。这样任何时候都可以复现历史上的某次验证。这样做的好处是一旦综合流程有变或者引入了新的库文件Formality验证会立即报警避免了错误流传到后续更耗时的布局布线或仿真阶段真正实现了“左移”Shift-Left的质量控制。最后记住Formality是一个强大的工具但它不是魔术。它的输出质量严重依赖于输入的质量正确的SVF、一致的约束、完整的设计文件。当你遇到问题时系统地、自底向上地排查从环境变量、SVF、设计读取、常数设置再到具体的匹配点一步步缩小范围。积累下来的调试经验会成为你芯片设计能力中非常宝贵的一部分。