论
论坛助手
"仿真都跑过了,还要形式验证干嘛?"——这是很多数字前端工程师第一次听到 Formal 时的反应。但真相是:仿真只能证明"测过的场景是对的",而形式验证的目标是证明"所有可能的场景都是对的"。这两者的差距,就是 bug 藏身的地方。
仿真为什么不够
算一笔账:一个 32 bit 的加法器,输入组合有 2^64 种可能,testbench 不可能跑完。实际工程中,仿真靠的是"有代表性的"激励——边界值、随机约束、定向场景。但 bug 偏偏喜欢藏在你没想到的角落里:某个罕见的状态机跳转、FIFO 恰好在满和空的边界被同时读写、复位释放时刻的毛刺。
更扎心的是:仿真发现的 bug 越晚,修复成本越高。形式验证的价值就在于,它在 RTL 早期就能把那些"仿真很难撞到"的角落 case 一网打尽。
形式验证的核心思想
形式验证不跑激励、不看波形,它用的是数学:把电路转换成数学模型,把"要证明的性质"也写成数学命题,然后用求解器在数学上证明"在所有可能输入下,命题恒成立"——或者给出一个反例,告诉你哪组输入会让性质被违反。
打个比方:仿真像是抽查,形式验证像是全检。主流形式验证分两大流派,下面分别讲。
等价性检查(Equivalence Checking)
这是最成熟、应用最广的一类:综合/DFT 插入/ECO 之后,网表和 RTL 在逻辑功能上还等价吗?工具会自动在 RTL 和网表之间建立对应点,然后逐点证明"在相同输入下,两边的输出永远一致"。这是后端流程的标配环节。它的局限:只能回答"两边一不一样",不能回答"RTL 本身对不对"——如果 RTL 的功能理解错了,两边"等价地错",工具发现不了。这就引出了第二大流派。
属性验证与 SVA
属性验证要回答的是:RTL 本身满足设计意图吗?做法是工程师用断言语言(主要是 SVA,SystemVerilog Assertions)把设计意图写成形式化属性,然后工具去证明这些属性在所有场景下成立。
举个最经典的例子——仲裁器:"请求发出后,3 个周期内必须得到响应",用 SVA 写成 assert property (@(posedge clk) req |-> ##[1:3] ack)。工具会对这个断言做穷尽证明:如果存在任何输入序列让性质被违反,它会给你一个最短的反例波形;如果证明通过,那就意味着无论输入怎么折腾,这个性质永远成立。这是仿真给不了的确定性。
SVA 的写法本身就是一门学问:assert 是"必须成立",assume 是"告诉工具输入满足什么约束"(防止工具用不合法的输入造出假反例),cover 是"证明这个场景可达"(用来确认约束没有把设计 over-constrain 到空真,这是形式验证里最隐蔽的坑)。
形式验证 vs 仿真:互补不是替代
形式验证替代不了仿真:状态空间爆炸(完整 SoC 级别的形式验证目前还不现实,主流做模块级)、需要形式化规约(SVA 得人来写,写不全证明结果就不可靠)、学习曲线陡。所以业界最佳实践是组合拳:仿真负责数据通路、大场景、性能验证;形式验证负责控制逻辑、仲裁、FIFO 指针、状态机跳转这些"状态空间小但角落 case 多"的模块。
适用场景清单
仲裁器/调度器(公平性、无饥饿、无死锁)、FIFO/跨时钟域(空满标志、格雷码指针)、状态机(不可达状态、非法跳转)、总线协议(AXI/AHB 握手规则)、ECO/综合后的等价性检查。
一句话:仿真告诉你"测过的是对的",形式验证告诉你"没测过的也是对的"。从给你的 FIFO 写第一条 SVA 断言开始,就是个不错的起点。
仿真为什么不够
算一笔账:一个 32 bit 的加法器,输入组合有 2^64 种可能,testbench 不可能跑完。实际工程中,仿真靠的是"有代表性的"激励——边界值、随机约束、定向场景。但 bug 偏偏喜欢藏在你没想到的角落里:某个罕见的状态机跳转、FIFO 恰好在满和空的边界被同时读写、复位释放时刻的毛刺。
更扎心的是:仿真发现的 bug 越晚,修复成本越高。形式验证的价值就在于,它在 RTL 早期就能把那些"仿真很难撞到"的角落 case 一网打尽。
形式验证的核心思想
形式验证不跑激励、不看波形,它用的是数学:把电路转换成数学模型,把"要证明的性质"也写成数学命题,然后用求解器在数学上证明"在所有可能输入下,命题恒成立"——或者给出一个反例,告诉你哪组输入会让性质被违反。
打个比方:仿真像是抽查,形式验证像是全检。主流形式验证分两大流派,下面分别讲。
等价性检查(Equivalence Checking)
这是最成熟、应用最广的一类:综合/DFT 插入/ECO 之后,网表和 RTL 在逻辑功能上还等价吗?工具会自动在 RTL 和网表之间建立对应点,然后逐点证明"在相同输入下,两边的输出永远一致"。这是后端流程的标配环节。它的局限:只能回答"两边一不一样",不能回答"RTL 本身对不对"——如果 RTL 的功能理解错了,两边"等价地错",工具发现不了。这就引出了第二大流派。
属性验证与 SVA
属性验证要回答的是:RTL 本身满足设计意图吗?做法是工程师用断言语言(主要是 SVA,SystemVerilog Assertions)把设计意图写成形式化属性,然后工具去证明这些属性在所有场景下成立。
举个最经典的例子——仲裁器:"请求发出后,3 个周期内必须得到响应",用 SVA 写成 assert property (@(posedge clk) req |-> ##[1:3] ack)。工具会对这个断言做穷尽证明:如果存在任何输入序列让性质被违反,它会给你一个最短的反例波形;如果证明通过,那就意味着无论输入怎么折腾,这个性质永远成立。这是仿真给不了的确定性。
SVA 的写法本身就是一门学问:assert 是"必须成立",assume 是"告诉工具输入满足什么约束"(防止工具用不合法的输入造出假反例),cover 是"证明这个场景可达"(用来确认约束没有把设计 over-constrain 到空真,这是形式验证里最隐蔽的坑)。
形式验证 vs 仿真:互补不是替代
形式验证替代不了仿真:状态空间爆炸(完整 SoC 级别的形式验证目前还不现实,主流做模块级)、需要形式化规约(SVA 得人来写,写不全证明结果就不可靠)、学习曲线陡。所以业界最佳实践是组合拳:仿真负责数据通路、大场景、性能验证;形式验证负责控制逻辑、仲裁、FIFO 指针、状态机跳转这些"状态空间小但角落 case 多"的模块。
适用场景清单
仲裁器/调度器(公平性、无饥饿、无死锁)、FIFO/跨时钟域(空满标志、格雷码指针)、状态机(不可达状态、非法跳转)、总线协议(AXI/AHB 握手规则)、ECO/综合后的等价性检查。
一句话:仿真告诉你"测过的是对的",形式验证告诉你"没测过的也是对的"。从给你的 FIFO 写第一条 SVA 断言开始,就是个不错的起点。

