尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

IC验证的底层思维:证伪、场景建模与覆盖率闭环

IC验证的底层思维:证伪、场景建模与覆盖率闭环 IC验证这行聊“技术”的文章满天飞聊“理解”的却少得可怜。很多人入行三五年工具链用得滚瓜烂熟UVM环境搭得飞快,可一旦换个项目、换个协议、换个场景立马抓瞎。问题出在哪儿出在对IC验证这件事本身的底层认知上。今天我不打算讲某个工具的用法也不想贴一段代码告诉你“照着抄就行”而是想结合我自己这些年在多个项目里摸爬滚打的经验聊聊那些真正影响验证产出质量的思维方式和判断准则。这篇内容适合刚入行的验证工程师、正在转型做验证的开发者以及那些已经在做验证但总觉得卡在瓶颈期的人。我会尽量用大白话把一些“只可意会”的东西讲透看完你可能不会多会一条命令但对“验证到底在做什么、怎么做才不白做”这件事应该会有一个更清晰的框架。1. 重新定义IC验证找茬只是表象建模才是内核1.1 验证的本质是“证伪”不是“证明正确”很多新手对验证的理解是把设计DUT跑起来写一堆测试用例确保功能没错。听起来没毛病但这个理解里藏着一个巨大的陷阱——你默认了“验证是在证明设计正确”。图灵讲过一句非常狠的话我们无法证明一个程序没有bug只能证明它存在bug。IC验证的逻辑和软件测试一脉相承。你跑一万个用例全通过只能说明“在这些用例覆盖的场景里设计的行为符合预期”不能说明“设计在所有情况下都对”。所以成熟验证工程师的第一课就是把心态从“证明它没问题”翻转为“想方设法找出它哪里有问题”。这种“证伪”思维落实到日常工作中会带来两个非常具体的改变。第一你会主动去找设计的薄弱点而不是顺着设计文档的舒服路径走。文档说这个FIFO在满的时候会拉高full信号你就专门在满边界上反复踩写中断读写、连续写、背靠背写直到把这条路径真正逼到极限。第二你会天然地不信任任何“看起来正常”的现象波形里一个意外的毛刺、总线上一次非预期的重试别人可能忽略你会停下来追问一句“为什么”。说白了验证工程师的职责不是给设计盖章而是给设计找茬。这种“找茬”心态才是整个验证工作的第一性原理。1.2 三条命脉规格理解、场景建模、覆盖率闭环如果把IC验证比作打仗那“证伪”思维是战略层面的指导思想具体执行层面还需要几个关键能力支撑我习惯把它们归纳为三条命脉。第一是规格理解。没有对芯片规格Spec的深入理解验证就是瞎忙。你连这个模块要完成什么功能、接口时序长什么样、异常情况下该怎么处理都没吃透怎么可能写出有价值的场景很多新手上来就翻RTL代码对着信号猜功能这其实是本末倒置。RTL是设计人员对规格的一种实现可能存在实现偏差你拿实现去反推规格等于用可能有bug的东西去验证另一个可能有bug的东西两边互相印证最后啥也验证不了。正确的顺序永远是先啃规格文档啃到能在脑子里把数据流、状态机、边界条件盘清楚为止。第二是场景建模。验证的核心产出其实不是“跑了几条用例”而是“建模了多少个场景”。场景建模的关键不是覆盖所有的输入组合——那是不可能的而是抓住最容易出错的那几个维度正常路径、异常路径、边界条件、并发冲突、时序压力。你需要站在系统的角度去思考这个模块在整颗芯片里工作的时候会经历哪些真实场景。举个例子验证一个DMA控制器光测“搬运数据正确”远远不够还要考虑它在搬运过程中被中断、源地址没对齐、目标FIFO满了、总线错误等各种情况下的行为。把这些场景在脑子里建模出来再转化成具体的激励序列这才是验证工程师真正的核心竞争力。第三是覆盖率闭环。场景建模是“你想验证什么”覆盖率是“你到底验证了什么”。没有覆盖率闭环你根本无法回答“验证做完了吗”这个问题。代码覆盖率告诉你RTL里哪些行、哪些分支没执行到功能覆盖率告诉你你关心的那些功能点有没有被测到。我见过不少团队天天跑回归跑了几十万次仿真自我感觉良好结果一看功能覆盖率报告关键场景覆盖率不到60%——之前的大量仿真都在低水平重复。覆盖率不是用来汇报的PPT素材它是验证进度的真实刻度盘。2. 方法论背后的抉择为什么UVM不是验证的全部2.1 约束随机验证把“碰运气”变成“可控的碰运气”聊IC验证方法论绕不开约束随机验证CRV。很多人听到“随机”两个字就误解了以为随机就是乱来。实际上约束随机验证的核心在于“约束”二字也就是在合法的输入空间里让仿真器自动帮你探索那些你可能想不到的组合。这里有个很朴素的道理人的思维是有盲区的。当你手写定向用例的时候往往会顺着自己理解设计的方式去写思路和写RTL的人高度重合这反而成了最大的盲区——你会和你自己的认知错误一起跳舞。约束随机验证至少能打破这一层让仿真器在你的约束框架内去“闯荡”经常能撞出你完全没预料到的场景。拿一个总线桥接模块举例。你约束好了地址范围、突发长度、对齐方式、间隔周期剩下的交给随机。可能跑了几万次之后突然某一次随机序列里出现了你从来没考虑过的“读请求紧跟着写请求、且地址相差正好跨越页边界”的组合然后在波形里看到桥接器出现了异常。这种场景靠人肉想可能半年都想不出来。这就是约束随机验证的价值它不是替代思考而是用机器的穷举能力反过来逼你思考得更全面。但约束随机也有它的边界。纯随机往往效率低而且容易集中在某些“容易到达”的区域所以现在的主流做法是“定向随机”双轨并行关键路径和边界条件用定向用例精确打击广撒网式的组合探索交给约束随机去跑。这两者之间的比例怎么拿捏没有固定公式取决于你对设计风险点的判断而这恰恰是经验的价值所在。2.2 参考模型与scoreboard你拿什么当“标准答案”约束随机产生激励之后DUT跑出来的结果怎么判断对错这就需要参考模型。参考模型Reference Model的本质是你用另一种方式通常是行为级代码对DUT的理想行为进行建模然后比较DUT的实际输出和参考模型的预期输出不一致就报错。这里面有个非常有意思的哲学问题参考模型本身也可能有bug。如果你参考模型的实现思路和RTL设计人员一模一样那两边可能犯同样的错误——这就是前面提到过的“实现偏差”问题。所以高手在搭建参考模型时会刻意使用完全不同的实现路径。比如DUT是用状态机实现的复杂调度逻辑参考模型就尝试用事务级的队列模型去模拟两者越独立交叉验证的价值越大。我自己的习惯是参考模型尽量往“简单粗暴”里写用最直白的数据结构、最朴素的逻辑追求“结果正确”不追求“风格优雅”。因为它只是验证环境里的一个参照物不是要交付的代码。它越简单越不容易有隐藏bug作为“标准答案”的可信度就越高。Scoreboard计分板则是负责比较的执行者。它做的事情本身不复杂——收集DUT输出和参考模型输出逐项比对。但实际情况中时序的不确定性、乱序完成、多通道并发等都会让“比对”变得很麻烦。你需要在scoreboard里维护一个数据队列按某种顺序规则去匹配容得下合法乱序、分辨得出非法乱序。这块设计得好不好直接决定调试环境本身会不会产生误报。2.3 断言和功能覆盖率验证环境的两根拐杖如果说参考模型和scoreboard是在回答“输出对不对”那断言Assertion和功能覆盖率就是在回答“设计内部状态是否符合预期”“我们想测的东西有没有测到”。断言是一种把“不该发生的事情”写成代码的技术。比如总线协议规定两个master不能同时占用总线那你就可以写一条断言监控总线占用信号一旦出现两个master同时申请并且都拿到了授权断言立即拉警报。断言的价值在于它的检查粒度可以非常细细到单个时钟周期内的信号关系这是靠人为抽查波形根本做不到的。很多工程师写断言的心态是“应付差事”随便写几条放在那儿。但真正有效的断言是你在写测试计划阶段就该想清楚的这个模块最不能违反的规则有哪几条把这些规则逐条翻译成断言代码嵌入到环境里让它们像哨兵一样日夜值守。我遇到过有人问我断言到底该写多少条说实话没有一个绝对数字但你写完环境之后如果发现自己写的断言都是“无关痛痒的过场”那就等于没写。好的断言应该是在回归里突然红掉时你能一眼看出是哪个模块、哪个行为违反了哪条核心规则。功能覆盖率则是“场景建模”的量化体现。你关心哪些场景就定义哪些覆盖率数据点。比如你关心突发传输的三种长度就定义一个covergroup覆盖这个字段你关心FIFO在“满”状态下的写操作就定义一个cross覆盖“full标志”和“写请求”的组合。功能覆盖率数据不是越多越好关键是将它映射回测试计划让每一个functional point都有迹可循。2.4 形式验证静态的“穷举”该出手时就出手聊完仿真验证得专门提一下形式验证Formal Verification。它和动态仿真最大的区别是仿真是一条时间线一条时间线地跑形式验证则是在数学层面上穷举所有可能的输入序列去验证某些属性是否永远成立。形式验证最擅长处理的是“小而硬”的问题。比如模块里的仲裁逻辑、电源管理状态机、跨时钟域的同步器结构这些逻辑规模不大但出错代价极高且纯靠仿真很难覆盖全。形式验证配合断言可以在几分钟内完成动态仿真跑几天都跑不完的穷举。不过形式验证也不是万能药它最大的瓶颈是状态空间爆炸——模块稍微大一点数学求解就可能跑不动。所以我自己的经验是在项目早期就把形式验证的适用范围划定好把它用在刀刃上复杂控制逻辑、安全关键路径、协议核心状态机。而那些数据通路很宽、行为很流水线化的模块老老实实回归仿真更现实。3. 可落地的验证实操从测试计划到回归收敛3.1 测试计划先画“作战地图”再动手写代码我见过太多人拿到一个模块第一件事就是打开编辑器开始搭UVM环境这个习惯非常危险。连要去哪儿都不知道就开始造车造出来的车多半到不了终点。正确的第一步永远是写测试计划。一份靠谱的测试计划至少要包含几大块模块基本功能清单、接口协议要点、关键场景描述、异常与边界条件、覆盖率定义、以及环境架构草图。写测试计划的过程本质上是逼你逐条梳理规格文档的过程。很多时候你在梳理时就会发现规格本身有矛盾、有含糊不清的地方这些就是潜在的设计bug来源——趁早找设计确认清楚后续会省下大量的返工成本。测试计划的颗粒度也是个学问。写得过于粗执行层不知道该做什么写得过于细变成流水账又没人看。我的经验是场景描述到“能让人判断测试是否覆盖到”的程度就够了具体激励怎么构造、约束怎么配交给执行时的实际情况去发挥。比如“验证FIFO满状态下连续写入不发生数据覆盖”这就是一条好的场景描述但如果细化到“第152个周期拉高写使能第153个周期检查计数器的值”那就过度了这种细节应该在环境参数层面灵活配置而不是写死在计划里。3.2 环境架构分而治之层与层之间别“拉丝”UVM环境搭得好不好直接决定你后续调试效率。一个清爽的验证环境通常遵循分层和隔离的原则激励产生层sequence、数据驱动层driver、观察采样层monitor、比较判定层scoreboard、配置层config各司其职互相之间通过TLM端口或sequence机制通信而不是互相乱抓信号。这里有个很关键的实操心得层与层之间传递数据时务必使用“事务级对象”而不是直接抓DUT内部信号。为什么因为一旦你从driver直接抓DUT内部信号相当于把环境跟DUT的RTL实现细节绑死了RTL一重构你的环境就得跟着改维护成本直线上升。事务级的做法是driver负责把transaction转成端口的时序波形monitor负责把端口的时序波形再抓回来转成transaction之后所有比较、统计都基于transaction展开。这样环境相对稳定DUT内部怎么改只要接口行为不变环境就不动。另外寄存器层RAL模型的处理也值得多说一句。凡是涉及到寄存器配置的模块一定不要用“直接force信号”这种暴力方式老老实实通过RAL寄存器模型去做前门写或后门写。RAL模型让你能够跟踪哪些寄存器被写过、哪些没被初始化、寄存器的实际值和期望值是否一致这些能力在调试时都是救命的。3.3 调试三板斧把“复现问题”变成“制造问题”调试是验证工程师的日常也是新人最头疼的部分。仿真挂了、断言红了、覆盖率上不去怎么定位我总结了三个层面的调试思维分别对应三种不同复杂度的场景。第一板斧是“看波形”。波形是最底层、最原始的信息不管多复杂的问题最终都要落到波形里的信号翻转上。建议你在环境里养成一种仪式感每次跑挂之后先把关键节点信号拉出来看分清“激励是否符合预期”“DUT内部状态机走到了哪一步”“输出是在哪个节拍开始异常的”。很多时候问题一眼就出来了根本不需要猜。新人容易犯的错是抓着一堆波形从头看看到哪算哪。正确做法是先确定一个大致的异常窗口再逐步缩小范围前后对比锁定第一个不一致点。第二板斧是“看日志”。日志是你在环境里埋下的“路标”。优秀的环境会把transaction的开始、结束、关键事件、断言失败时的上下文打印得清清楚楚。打印语句不是随便写的每个打印都应该包含时间、模块名、事务ID以及关键数据。这样出问题时grep一下日志就能串出完整的因果关系链。我见过一些团队日志打印得很少出了问题全靠人眼盯波形效率低到让人抓狂。第三板斧是“最小化复现”。一个复杂的随机用例挂了你把原始种子跑一遍能复现但波形根本看不过来因为激励序列太长。这时候需要做“最小化”——通过调整约束、缩短序列长度、逐段裁剪找出能触发bug的最短激励序列。这个过程的本质是场景抽象你不再关心原先是“哪2500个随机事务”触发了bug而是找出触发bug的“核心矛盾”是什么是哪个边界条件、哪两个事件的相对次序。最小化之后的用例通常可以直接转成一条定向回归用例永远留在回归集里防止问题回归。3.4 回归收敛什么时候才能说“验证做完了”回归收敛是验证阶段的收尾动作目的只有一个用可持续的、自动化的方式反复跑全部用例集确认设计在迭代过程中没有引入新问题。实操层面回归要跑得“聪明”而不是“傻跑”。聪明的回归会做几件事按模块或风险等级给用例分级日常迭代只跑快速子集定期或重要里程碑跑全量回归结果的差异自动比对上次过了这次挂了的用例自动标红随机用例固定种子保证可复现性同时每天换一批新种子做持续探索。回归收敛的判断标准必须是“覆盖率达标断言之上的连续干净回归”。注意“连续”这个词一次全绿说明不了什么至少要连续多轮回归全绿且功能覆盖率没有新的增长点、代码覆盖率没有明显盲区才有底气说“验证可以收尾了”。如果把验证比作考试那回归就是这个考试的“最终成绩单”一张全是红叉的成绩单你没资格说自己考完了。4. 验证路上的常见坑与高手的化解之道4.1 接口协议中的边界问题小心“合法但罕见”的组合做接口验证最常见的坑不是协议不满足而是满足协议文法的“合法但罕见”序列。比如AXI协议允许burst的地址不按自然对齐方式排布大多数时候设计者默认大家都会用对齐地址但协议本身就允许非对齐。如果你在验证时没有构造非对齐的burst组合就可能漏掉设计里一个“非对齐处理逻辑”的bug。我自己遇到过类似问题一个PCIe控制器协议规定TLP头里的地址字段可以支持各种对齐但实现里某条路径只在特定对齐下才会正确转发。仿真跑了很久都没问题直到有一天随机序列里出现一个非对齐的写请求DUT直接内部错误。这个bug回头去找根因发现实现逻辑里确实少处理了一个地址位。这种问题靠的就是验证工程师对协议边界的敏感度——你要专门去翻协议文档里那些“供应商自定义”“必须为零”“保留字段”一类的小字内容主动构造对应的激励去试探。4.2 跨时钟域处理的三个经典翻车现场CDC跨时钟域相关的bug几乎每个做过数字验证的人都遇到过大大小小的。根据我的经验最容易翻车的有三个现场。第一个是单bit信号的跨时钟传递。很多设计人员知道要用两级同步器但同步器只能解决“电平”的稳定传输解决不了“脉冲丢失”的问题。如果源端是一个单周期脉冲目标端时钟频率低脉冲可能根本没被采到。验证时你要根据两侧时钟的频率关系专门构造超短脉冲看目标端能不能正确“看到”它。第二个是异步FIFO的空满标志。空满标志的计算天然存在“历史视角”需要经过格雷码跨时钟转换而格雷码转换又天然存在多拍延迟。这就导致一个经典问题空标志拉低之后目标端真的能安全读吗满标志拉低之后源端真的能安全写吗如果同步延迟过大可能FIFO里其实还有空间但满标志还没拉低设计又没做额外保护就会出现漏数据。验证环境里你一定要在FIFO深度、读写带宽比上做极端压力测试把空满标志的时序抖动真正逼出来。第三个是时钟域交叉的调试困境。出了CDC问题波形里根本看不出是CDC问题因为它表现为“偶尔一次数据错乱”复现概率很低。这种问题的定位建议用formal的CDC检查工具在流片前跑一遍静态分析把同步器的结构、跨时钟路径上的逻辑都扫一遍很多时候能在仿真之前就把隐患堵住。4.3 随机种子失效的“玄学”背后“昨天同一个种子还能复现今天就不行了”这种话在项目组里我听了无数次。不少工程师遇到这种情况第一反应是“仿真器抽风了”。但真实的根因往往更接地气环境代码改了某个部件的初始顺序变了或者序列里某个随机数的消耗顺序变了。为了避免这种“玄学”有两个实操建议。第一环境里所有随机数都通过统一的随机源生成并且严格记录每轮回归的种子建立种子和结果之间的对应关系。第二一旦发现某个种子触发了bug立刻跑第二遍确认不要再修改任何环境代码确认能稳定复现后再开始简化。如果你复现bug前先改了代码很可能就永远丢了那个触发条件——我在这上面吃过不止一次亏后来养成了“先复制现场再动手”的职业习惯。4.4 覆盖率“虚高”的真相代码覆盖率全绿功能覆盖率一片荒芜代码覆盖率达到100%是很多验证报告里的标配但代码覆盖率全绿真的代表验证充分吗远不是。代码覆盖率只说明RTL的每一行、每个分支都执行到过不说明执行时是不是处于正确场景、有没有做过正确的数据比对。我遇到过一次真实的“假绿”一个数据通路的RTL代码覆盖率100%但功能覆盖率里最重要的“数据包长超过256字节”场景覆盖率却是0。为什么因为环境里那个约束随机配置的包长范围最高只有255从来没有一个用例生成过超过256字节的包。代码覆盖率自然是全绿因为代码走的是同一套逻辑只是数据长度不同。这种情况下如果只看代码覆盖率就会得出“验证完成”的错误结论。功能覆盖率和代码覆盖率必须配合着看代码覆盖率告诉你“有没有遍历”功能覆盖率告诉你“遍历的是不是你要的场景”。前者低、后者高说明用例写偏了前者高、后者低说明用例覆盖的深度不够。两边都对不上就得回测试计划重新梳理。5. 再谈“牛人对IC验证的独特理解”是什么说了这么多我想回到标题本身。所谓“牛人”对IC验证的理解到底独特在哪里我的体会有三条。第一条牛人都把验证当成“设计行为”的一部分而不是“设计完成之后的一个流程”。他们在芯片架构定义阶段就会介入和架构师聊规格、聊总线协议、聊模块划分把验证的约束条件提早嵌入到设计决策里。这样做的好处是很多会导致“难验证”的架构问题在设计段就消解掉了而不是留到验证阶段花几倍力气去硬啃。第二条牛人都具备极强的抽象建模能力。他们不纠结于某一个信号的翻转而是能把一个复杂协议、一个大模块的行为抽象成若干个清晰的模型再用这些模型去指导环境搭建、场景设计和数据比对。说白了他们的脑子里跑的是一座虚拟芯片RTL只是这座虚拟芯片的一种具体实现验证环境则是另一座同样虚拟的芯片。两座芯片互相印证、互相约束才能真正逼近“正确”二字。第三条也是最容易被忽略的一条牛人都对“人”有深刻理解。验证是团队作战验证工程师要面对的是设计工程师、架构师、项目经理甚至客户的支持工程师。一个验证结论怎么表达能让设计人员心服口服地接受而不是防御性地反驳一个bug的描述能让项目经理意识到严重性而不是觉得你在小题大做区分“验证环境自己的bug”和“DUT的bug”在给设计工程师提bug单之前先用五分钟自己核对一遍环境别让设计把时间浪费在跟你的环境问题纠缠上。这种协作智慧很多时候比技术本身更能决定项目能不能按时高质量交付。我自己的经验是IC验证是一门越做越“宽”的职业。刚入行两年你会觉得自己在写UVM、跑仿真五年之后你开始理解验证策略、覆盖率闭环和项目管理的深层关系再往后你对架构、协议、甚至芯片的商业模式都会有属于自己的判断。这个过程没有捷径唯一能做的就是带着脑子干活在每一次波形异常、每一次回归失败、每一次覆盖率“上不去”里多问一句“为什么”把你对验证的理解打磨得更接近那批牛人的状态。
返回列表