多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

数字IC验证必备:LEC与Formal Verification调试实战指南

数字IC验证必备:LEC与Formal Verification调试实战指南 做数字IC验证的都知道LECLogic Equivalence Checking逻辑等效性检查和Formal Verification形式验证这两样东西跑通流程不难真正磨人的是debug。很多时候工具给的反馈就一句话——“not equivalent”或者“prove failed”但这句话背后可能是RTL写错、网表约束漏配、时钟处理不一致甚至纯粹是工具配置没弄对。我在这个领域泡了十几年可以负责任地说LEC/Formal debug的本质不是“看懂工具报错”而是“建立一套可复现、可收敛的排查方法”把无数种可能快速归因到唯一根因。这篇内容算是LEC/FormAL系列的第III部分前两部分把原理和流程讲透了这篇专门聊debug。不管你是刚接触形式化验证的验证工程师还是被领导临时抓去“跑一下LEC”的数字后端开发又或者是在Formal验证上反复被counterexample折磨的资深选手这篇文章里都有可以直接抄作业的方法论。我会把常见错误分类、LEC debug的实操路径、Formal debug的波形分析方法、以及我这些年踩过的坑一整套都整理出来保证比读工具自带手册有用得多。1. 为什么要单开一篇讲debugLEC/FormAL问题定位的本质1.1 两者面向的问题不同等价性对比 vs 属性证明不少刚入行的同事会把LEC和Formal当成同一个东西甚至会在工具里来回切遇到fail就慌了。实际上这两个验证手段的debug思路差异很大必须先分清楚自己在跟什么问题打交道。LEC验证的是“两个设计是否等价”。拿Cadence Conformal LEC或者Synopsys Formality来说输入是RTL和综合后的网表工具会先把两侧设计映射成一组可对比的关键点key points比如寄存器输出、黑盒输出、顶层输出端口然后比较pair上所有状态下的逻辑是否一致。LEC的debug本质上是在找“这两个电路在某种场景下为什么不同”问题的边界很明确就是两边实现方式的差异。Formal Verification验证的是“设计是否满足某些性质property”。常见工具像JasperGold、VC Formal、Questa Formal核心做的事是把设计建模成形式化系统再用求解器去穷举证明property在所有可达状态下都成立。一旦证明不了工具会返回一个反例counterexample——一条具体的时间序列告诉你设计在哪一刻违反了哪条断言。所以Formal的debug工作量重心在于“从头走到尾把一个property拆干净”看它是环境约束没给够还是RTL本身真的有bug。LRc和Formal虽然是不同的验证手段但debug的底层步骤其实是同一套复现问题、缩小范围、观察内部信号、确认根因、修改后回归。理解了这套底层逻辑工具换什么版本或者换什么厂家的EDA套件你都不会慌。1.2 debug难在定位而不是难在原理我见过太多人debug进度卡住根本原因是把精力放在了重新“学工具命令”上而不是放在“怎么缩小怀疑范围”上。其实无论LEC还是Formal工具给的信息都是足够的关键是你有没有一套自己的分析路径。举一个经验层面的例子Formal跑一个property不通过新手第一反应是把波形打开一条信号一条信号去追。这做法不能说错但没有效率。反例波形通常是几十拍甚至上千拍一条一条看信号能把人看瞎。正确做法应该是先把property本身拆开确认它的前置条件antecedent和后置条件consequent分别依赖哪些信号然后重点看“最后一拍”和“翻转时刻”附近的状态。也就是说你要先让工具告诉你“哪个状态变量在这个时刻决定了成败”再去看波形验证这个怀疑而不是一头扎进波形里漫无目的地翻。LEC的debug也一样。两个design不匹配的时候report会告诉你哪个key point不匹配、工具跑不出匹配的寄存器对有哪些。但很多人只盯着“不匹配的point”忘了先检查整体设计环境问题——比如时钟没配对、常量寄存器没有设成constant、某些端口被错误地设成了dont verify。这一类环境层面的问题不解决后面所有精细分析都是白费功夫。debug的核心原则就一条先确认工具的环境假设是否正确再确认逻辑本身是否有差异最后才动手改代码。顺序反了你会在错误的方向上浪费大量时间。2. 先给错误“分分类”从fail结果锁定排查方向2.1 按工具反馈分类fail、abort、inconclusive、timeoutLEC和Formal工具跑完后结果不会只有“通过/不通过”两种。我习惯把工具反馈先归类成四类每一类对应的处理策略完全不同。第一类Fail明确失败。这是最好处理的一种。LEC里表示两个design在某个或某几个key point上确实不等价Formal里表示求解器找到了一个反例证明property不成立。这种结果通常伴随详细的报告只要按报告定位即可。第二类Abort验证中止。工具在一个点上因为内部资源限制比如某个组合锥太大、ALU乘法器结构太复杂、Solver超时等无法给出确定结论。很多人看到Abort会以为设计有bug其实不一定也有可能是这个比较点在结构上差别太大。这时候需要人为干预比如设置cutpoint、使用simulation-based hint、或者给工具加时间预算。第三类Inconclusive无法确定。Formality里很常见的“undetermined”Conformal里也经常出现。表示工具既没能证明等价也没能证明不等价某些状态可能是dont care也可能是工具无法解析。inconclusive往往和X态传播、未初始化寄存器、非确定行为有关。第四类Timeout超时。证明过程在指定时间内没有跑完。Timeout未必代表错误更多的是工具算力或约束效率的问题。遇到timeout我通常第一条建议是先把property拆小再加assumption而不是傻傻等工具跑通宵。为了更直观把这四种情况整理成一张速查表工具反馈含义不一定是bug优先排查方向Fail明确不匹配/反例找到否逻辑差异、约束遗漏Abort无法完成证明是组合锥过大、结构复杂度、工具资源Inconclusive无法确定结果是X态、dont care、未初始化状态Timeout时间超时是property过大、约束太弱、求解效率2.2 按设计根因分类逻辑不匹配、时序处理不当、约束错误按工具反馈分类是第一步接下来要按设计根因来分。我在实际debug中见过的问题几乎都可以归到三个门类下。第一类纯逻辑差异。RTL和门级网表在实现上确实不等价。RTL里写的是case语句或算术表达式综合工具做了优化、资源共享、常数传播后网表结构和RTL已经面目全非。这种差异通常需要比较点级别的分析才能看出来。第二类时序处理差异。LEC比对时两侧design的寄存器划分不一致比如RTL端的敏感列表、异步复位写法、时钟门控方式和网表端插入的clock gating cell对不上。Formal里则表现为property里包含时序延展或时钟复位序列没被正确建模。这类问题如果环境脚本里没做对应处理工具会报出大量假fail。第三类约束与假设错误。这是最隐蔽、也最坑人的一类。LEC里常见的是把某些关键输入错误地设成了constant或者把某个寄存器的初始态设成了XFormal里则是assume写得太强或太弱太强会漏掉真实错误太弱会导致反例无效。约束一旦错了后续看再多波形也是缘木求鱼——因为整个“问题空间”就定义错了。3. LEC debug实操从“不匹配”到“定位根因”的五步走3.1 跑通环境与初始比对确认工具配置正确很多人拿到LEC fail报告直接去看fail point我建议先别急。我遇到过太多次“假fail”了环境问题不解决后续debug全是白费。第一步要做的是回到最基础的setup阶段逐项确认三个东西。一是顶层模块是否正确指定二是时钟信号是否完整定义三是reset信号、blackbox等约束是否设定。用Formality做例子通常在setup之后用report_clock、report_port等命令看关键信息用Conformal LEC则用report design data、report clock去检查。确认完这些再跑一次matching看两侧design的寄存器配对情况。如果unmatched points很多停下来分析原因。寄存器不配对通常是时钟定义不全、异步逻辑处理过当或者工具没有正确识别某些DFF。匹配阶段就失败的话硬跑compare不会有任何有意义的结果。这一阶段的核心目的是把工具的运行环境调整到和真实设计语义一致。环境对了后面的fail才是“真fail”。3.2 锁定fail point组合锥分析和比较点映射当compare跑完工具报出某些point不等价接下来要做的不是直接改RTL而是先分析fail point所在的组合逻辑锥。以Conformal LEC为例在GUI里点开fail point工具能直接画出该点对应的逻辑锥图显示两侧design逻辑锥的结构差异。我发现一个很好用的习惯先看fail point输入端的信号列表然后把两侧的逻辑锥一比很快就能看出是“结构等价但内部逻辑顺序变了”还是“连功能都完全对不上”。Formality里同样可以在GUI里查看schematic命令行的report_failing_points也支持指定详细pattern。有时工具还会给出“附赠”证明信息这些input pattern下两侧输出不一样。这个pattern细节很重要——它能帮你快速反推出是哪一种输入组合触发了差异比如一个加法器进位链中间某一位的carry逻辑不同在输入组合里通常能看到高位进位被触发的pattern。拿到这些信息后再对照RTL代码手动走一遍相应分支效率会非常高。比起直接在几万行网表里找差异看逻辑锥输入pattern是标准解法。3.3 利用setup、dont verify、cutpoint等指令缩小范围修环境不对的时候盲目改代码没有意义LEC的调试之美在于它支持“假设型验证”——你可以在不修改RTL和网表的情况下通过指令去验证“如果不是这里是不是就等价了”。比如你怀疑某个reg在综合时被优化成常数但RTL端还保留着导致两边不等价。这时你可以在RTL端把这个reg设成constant重新跑compare如果结果变成等价就验证了你的推测。Conformal里用add constantFormality里用set_constant都能做。又如某个netlist单元缺失库单元工具找不到功能模型这个点会一直fail。这时用set_dont_verify将其排除能让你集中注意力在真正重要的点上。cutpoint也是我从后端同事那里学来的高级技巧把深度路径在某一点“切断”在两侧都建立相同的新key point可以显著降低等价比对的复杂度。工具提供的这些指令不是为了让你“掩盖问题”而是为了帮你做“假设实验”——把大问题拆成一连串小判断快速定位根因。这就是工程化的debug不追求一次搞定而是让每一步都产生信息量。3.4 和RTL/网表逐行对照找到“语义差一截”的具体位置当工具层面的分析都做完了最后一步往往是回归原始的代码对照。这个环节没什么捷径纯靠经验和耐心但有一个技巧能让你少走弯路——先对照环境配置再对照功能代码。我踩过的一个真实例子网表里某寄存器没有复位RTL里却有异步复位。LEC报fail我一开始怀疑综合优化把复位吃了后来仔细看综合脚本发现reset信号在SDC里被设成了false path综合工具理所当然地把它优化掉了。问题压根不在RTL功能而在约束把异步复位给“误伤”了。这种问题在逻辑锥图里也能看到网表端的寄存器根本没有reset pin。所以逐行对照时第一看信号连接关系第二看常量第三看时钟和复位接入方式。如果这三类都没问题再深入到组合逻辑本身。这样分层的对照法可以避免在错误层面卡住。3.5 修完以后回归验证LEC也要有“clean reg”代码改完后的回归同样重要。我见过有人在debug过程中把RTL改好了结果网表用的还是旧版跑出来的结果当然还是错的。更细一点的问题在库模型某些IP厂家的仿真模型和综合网表行为不完全一致这也会导致LEC无法收敛。所以我的习惯是每次修改后保留一版干净的回归脚本一次性把environment setup、match、compare全跑完。并且要在回归日志里记录下当前RTL和网表的版本号确保比对时两边确实是你要验证的那一版。这个习惯救了我很多次——尤其在项目后期RTL和网表频繁迭代的时候能省下大量的“人工确认时间”。4. Formal debug实操用counterexample撬开问题真相4.1 Formal验证环境搭建与常用性质写法Formal debug的前提是先有一套能跑通的环境。环境的搭建和仿真不太一样仿真直接加载testbench跑waveform即可Formal需要做的是三件事读入设计、设置时钟复位、声明property。读入设计这一步记得把约束文件一并读进来比如在JasperGold里可以这样写基础命令脚本read_file -format sverilog rtl/top.sv read_file -format sverilog rtl/mem.sv read_file -format sverilog constr/formal_constr.sv set_top top_module elaborate时钟和复位处理是Formal环境里最容易出问题的地方。我常用的方法是显式创建时钟并在约束文件里用assume声明异步复位的行为create_clock -name clk -period 10 reset_deassertion -sequence { rst_n } -clock clkproperty通常用SVA写基础格式长这样property p_req_ack; (posedge clk) req | ack within 1 to 3; endproperty assert property (p_req_ack);这里要提醒一点Formal里除了assert还有assume和cover。assume是给环境加的约束比如输入信号不能x、数据总线在复位释放后必须稳定cover则用来确认某些新功能是不是能被穷举到。三者配合使用才能既保证property有意义又不至于让求解器去探索一大堆无关状态。4.2 property fail后的第一手材料波形和反例Formal工具在property fail时会给出一个counterexampleCEX本质是一段波形。JasperGold里能直接打开trace也能导出成VCD/FSDB到外部波形工具看。拿到CEX以后我不看完整波形只看三段复位释放初期、property启动那一刻、和property失败的那一拍。因为绝大多数Formal bug都能归结为这三个时间点上的状态不对。比如property要求req后ack在3拍内到达那重点就看req拉高后的三拍ack是否真的没来再看这三拍里是数据路径delay太长了还是assert的使能条件根本没满足。这里有个新手最容易踩的坑Formal的反例不同仿真波形它不代表真实输入sequence可能是一个极端边角情况——比如地址总线在某一拍同时出现了多个请求并且还有乱序返回。不要轻易把这种corner case当成“时钟设计bug”很多时候它恰恰是好事帮你提前发现了仿真测不到的边界漏洞。4.3 用约束排查和solver信息快速定位如果property本身没问题但Formal就是报fail问题大概率出在约束上。过约束会让property无法证明过约束会让求解器“偷懒”找不到反例导致property误通过。判断过约束还是欠约束有一个简单方法把设计中的关键变量在property路径上的驱动关系拉出来看看。JasperGold里有不少好用的命令比如检查哪些信号被assume约束了、覆盖范围如何。把约束打印出来逐条看一遍往往比看波形更快定位问题。求解器本身也会提供一些信息虽然这点很多工程师不在意。比如JasperGold在证明过程中显示某个gate一直保持某值或者在超时时告诉你“证明尝试了状态空间范围的95%但卡在某个区域”这些都是线索——如果工具长时间反复证明某个子状态说明那里可能逻辑存在复杂耦合。这些solver hints结合CEX会让定位效率高出不少。5. 高频问题速查把踩过的坑直接列成清单5.1 五类高频debug问题与解法对照表为了便于日常工作我把这些年遇到的高频问题按“现象—根因—解法”整理成了一张速查表建议收藏起来debug卡壳时先过一遍这张表高频现象常见根因排查/解决建议LEC大量寄存器unmatched时钟定义不全/异步逻辑处理不当检查clock setting、reset settingLEC单个point反复fail综合优化了常量化信号用add constant验证假设LEC report但GUI不显示logical cone库单元missing或模型不一致检查库文件set dont verify后重跑Formal property有反例但仿真是过的约束过弱包含了仿真未覆盖的输入增强assume再把property拆细Formal一直timeoutproperty规模过大增加assume限制换multi-property证明方式Formal证明pass但仿真failproperty过约束/环境建模错误逐条检查assume优先怀疑时钟复位约束CEX波形和RTL预期大相径庭reset/clock建模错误在工具中显式set reset sequence工具报inconclusive无法判断X态未建模或未初始化reg定义好初始状态避免X态扩散这张表里除了问题本身更重要的是最后一列的行动模式。你会发现这些解法有个共同点都是回到环境/约束/假设层面做调整而不是一上来就改RTL或网表。先让工具环境符合设计语义再看逻辑差异永远是最稳的路径。5.2 长期受用的几个debug习惯这几条经验和具体命令无关更多是做事方式层面但我一直觉得这些反而才是真正区分经验丰富和刚入门的地方。第一保留现场。每一次fail/output/log/GUI session统统存好。Formal debug经常要来回比对多个版本没有现场记录你会面对一堆“我又忘了上次怎么跑出来的”问题。我一般按日期工具类型design名建目录脚本和脚本输出一并入库。第二建立“最小可复现case”。无论LEC还是Formal遇到复杂大设计跑不动或查不清时把环境代码精简到一个几百行的小模块把相关信号接成常量把状态空间尽量剪小然后单独跑。这个小case既是复现问题的工具也是你向同事请教时的“最短路径”。第三多用GUI但别依赖GUI。GUI能直观展示逻辑锥、波形很方便但凡是重要结论一定要用命令脚本固化下来。一方面GUI操作很难追溯另一方面等环境换到服务器上无界面运行你只能靠脚本。所以从第一天起就把所有关键步骤写成Tcl脚本GUI只用来做视觉确认。第四排查顺序别搞反先环境再约束最后才看逻辑。我好多次看到同事一头扎进RTL里苦读最后发现是时钟约束写错。让人崩溃的不是复杂设计而是方向性错误。每次debug启动前给自己三秒钟问一句“环境是否干净约束是否完整”再决定往哪走。第五和综合工具、仿真工程师多沟通。Formal和LEC的debug常常牵扯到综合约束、SDC、IP的交付形态这些信息在工具报告里看不全。跟写综合脚本的同事确认一下retiming、scan insertion是否打开往往比自己在工具里瞎试一个小时更管用。我个人在实际操作中的另一个体会是debug要分“天窗”时间。证明类问题有个特点盯着屏幕看半天不如出门走一圈换个思路。有一次Formal反复fail我下班路上突然想到可能是reset sequence没建模对第二天一验果然是这样。所以遇到烧脑的case别死磕记录好当前状态离开一下大脑后台线程往往已经帮你算起来了。冷静、分步、体系化这就是LEC/FORMAL debug的全部心法。
返回列表