原文标题:你真的了解数字验证吗?
一、什么是芯片验证及其方法学?
芯片验证就是采用相应的验证语言,验证工具,验证方法,在芯片生产之前验证芯片设计是否符合芯片定义的需求规格,是否已经完全释放了风险,发现并更正了所有的缺陷,站在全流程的角度,它是一种防范于未然的措施。我们知道芯片流片回来测试时发现硬件bug,那时基本就无力回天了(芯片内的硬件bug一般都没办法修正,当然部分bug是可以通过软件或烧efuse等措施来屏蔽),因此把问题拦截在流片之前非常之重要,这正是验证存在的意义。
什么是验证方法学?验证工程师绕不开的一个基本概念。芯片规模越大功能越复杂,潜在的问题也就越多,验证的难度也就越大,验证方法学,就是研究怎样降低验证工程复杂度的同时,还能保证验证的可靠性,提升验证效率的一门学问。从它被提出到今天,其核心依然是带约束的随机激励、覆盖率驱动以及重用,并利用面向对像语言的特性对常用功能进行高度封装,再提供统一的事物层接口使不同抽象层级的建模数据得以共享和有效通信,从而极大地提升验证平台的构建效率,并加速EDA仿真,最终对缩短芯片面市周期做出贡献。
二、芯片验证的主要工作
芯片验证的主要工作是根据规格文档和详细设计文档提取验证功能点,编写测试激励,并搭建验证环境,或利用已有的验证环境对DUT进行验证和回归。验证具体岗位还可以进行细分,例如功能仿真验证,FPGA原型验证,硬件模拟加速平台验证(Palladium,ZeBu,Veloce),后端时序验证等,常见工作如下:
①熟悉芯片规格
主要是学习各种相关的文档材料,包括但不限于协议、需求、规格、功能说明、历史芯片文档、重用环境评估与恢复。
②提取验证功能点
测试点一般分解过程如下,首先根据各种文档梳理出验证特性,然后根据验证特性细化出测试点。
测试点分为场景类、功能类、性能类、白盒测试点(设计人员提供)、接口类、异常类等维度,全面、明确、细致,无歧义的将所有验证特性细化为一个个不可分割的小点,每个点明确采用directtest还是coverage覆盖还是assertion覆盖。功能覆盖需要细化覆盖范围,比如典型值、边界值、异常值、cross。当然测试点也不是一次分解完成,在整个验证过程中会进行多次分解和review,直至完善。
测试点分解绝对算是芯片验证工作中的最重要的一环,是充分体现验证人员经验、能力、智慧、价值的一项工作。芯片中的bug往往都是没有想到的点或者没有覆盖到的点。所以测试点分解一定要追求完备、细致、无歧义,要做到测试点分解完成后,无论哪个验证人员测试,验证质量都是有保证的。过于粗糙的测试点会导致不同的验证人员在测试用例设计时有不同的理解和实现,或许就会遗漏掉一些corner点。同时粗糙的测试点也会造成工作量评估不准确,导致后期突发任务增多,造成项目延期。
③撰写模块级和系统级验证方案
对于验证方案,每个公司可能有不同的标准,目的是设计验证架构指导后续验证工作,保证所有测试点都能在该验证过程中覆盖到。标准化、参数化的方案设计是非常重要,要考虑到该模块的后期集成和重用,验证方案中一般还包括进度安排、风险评估等。
注意:设计参考模型或checker时一定不要过多参考设计方案或代码,理论上设计和验证从spec开始就要分开,是平行独立的两条线,过多的参考设计是非常不可取的,造成的后果就是参考模型/checker和设计代码实现一样,case总是一跑就过,完全测试不出设计bug。
④开发验证环境、参考模型和验证脚本
这是验证人员的基本功,一个足够完备灵活自动化的验证环境能节省后面测试用例实现的很多工作量,测试用例会变得很简单,不同的测试用例只需要开关某些配置和修改一些约束;反之一个糟糕的验证环境会让测试用例变得冗长、复杂、低效。软仿测试平台现在流行基于UVM搭建环境,一般使用高级硬件语言如SystemVerilog开发。
⑤验证执行
验证执行阶段,按照测试点一个一个进行覆盖,写测试用例(直接测试、随机测试)、debug,后期进行代码覆盖率和功能覆盖率的收集分析、用例增加以及最后用例检视。这个阶段把验证发现的debug做好登记。
⑥撰写验证报告
验证执行做完后,需要输出验证报告(一般都会有评审环境),报告主要包括各功能点覆盖情况、代码覆盖情况、性能分析、功耗分析、风险评估等类容。
⑦配合其他工程师的工作
主要是查找修复设计缺陷,带领其他工程师完成项目验证工作;协同设计和固件工程师在硬件加速器/FPGA平台进行验证调试;与硬件工程师共同确立硬件实现方案,提供硬件设计建议;协助测试工程师完成芯片测试等工作也是验证人员需要干的活。
三、数字芯片验证流程
一般意义上,数字芯片验证就是对RTL代码进行EDA仿真,并从中发现RTL代码BUG后,再提交设计工程师进行BUG修复的过程。实际项目操作中,验证工作的参与不仅仅是在RTL代码的仿真阶段,它应该贯穿整个项目的始终。如果说开发一颗芯片是一场战斗,那么设计与验证是同时投入,积粮草、挖战壕、冲锋陷阵都在一起,但拼杀结束后,设计可以陆续撤离,而验证则要负责打扫战场,确认所有敌人都不再喘气儿,保证万无一失。下面用这张图示意验证各重要环节在整体开发流程中的位置与配合关系。
架构师从原始需求提炼出来功能规格(FS)与架构规格(AS),两套流程使用不同的思路和方式对其进行实现,实现过程中互为对照同步收敛,产生分歧时以架构为根本决策依据,最后殊途同归。
验证流中,首要环节为测试点(亦称验证特性 Verification Feature,我习惯称测试点)提取,验证的后续所有动作基本都是围绕测试点来展开的。测试点实际就是把一个有机整体的功能分解成一系列单个的功能点,起到化整为零,化繁为简的作用,从而方便构建测试用例对其进行验证。分解的粒度没有一定之规,但有几个大原则要把握:完备性,即不能遗漏任何功能点,特别是异常处理,边界处理,容错处理这些往往容易被忽视;低耦合,不同测试点之间的相关性越低越好,这也直接决定了分解粒度,并影响testcase的开发难度;无歧义,测试点的描述要直接而明确,不同测试点之间不存在矛盾之处。
测试点的主要来源为FS和AS,这也就要求验证工程师在项目架构设计阶段并不能置身事外,而是和设计工程师一样,要参与到架构文档的Review活动中来,在不改变产品功能的前提下,甚至可以向架构师提出可验证性方面的设计建议或需求,参与度越高,理解越深刻,越能保证测试点的提取质量。测试点也有少部分来源于设计工程师的微架构或设计规格(DS),主要针对一部份corner case,来源于DS的测试点不能有悖于FS/AS的要求。
测试点是验证活动的核心,直接决定验证结果的可靠性与完备性,非常重要,但因为它是一个相对主观东西,很大程度上依赖于工程师的经验、理解能力、以及责任心,可以说衡量一名验证工程师的技术水准,不在于他的Testbench写得多漂亮,UVM玩得多熟,而在于他的测试点分解质量。为了保证验证源头的正确性,验证工程师应该对FS/AS反复研读,项目组内进行交叉串讲,并通过答辩后再行测试点分解,分解结果依然需要通过架构师、设计工程师、以及组内其他验证工程师(特别是相邻模块)的Review后,方可进入后续流程环节。这个过程看似繁琐,但磨刀不误砍柴功,切不可图省事而草草了之。
测试点明确以后,便可以针对性的设计测试激励与相应的功能覆盖点,并明确覆盖手段。功能覆盖率让自然语言描述的测试点变得更加具象和量化,由主观变成客观,是验证出口标准中最重要的依据之一。功能覆盖点的选取尤其要注意边界值与异常值,并设置合理的cross,确保完备。同样,相互之间反复Review是必不可少的操作。
验证平台的设计现在已经非常标准化,UVM一统天下后,基本框架都差不多,主要取决于对UVM的理解与System Verilog的编程能力,在这里没有太多可说的,当然它也有很多细节,可另外开文单独进行讲解。我觉得需要稍加注意的几个点,一是transaction中,对随机约束的设计要符合测试点的要求,每一个constraint的粒度把握好,方便在testcase中进行扩展与差异化控制;二是功能覆盖率编码不要遗漏,也不要加入毫无意义的cover bins,严格符合测试点阶段的结论;三是适当考虑组件的可重用性,提升整体效率。重用的问题,后面会讲到,这里不展开。
和设计工程师对自己的代码进行单元测试一样,验证环境搭建完成后,也应该进行自测试,以排除低级简单的错误。比如可以采用driver的输出接monitor输入的方式,检查测试激励是否可以生成,整个平台的数据通路是否畅通,自检测机制是否正常等,这是一个简单而必要的步骤,能使RTL与VE的第一次集成,即Sanity Test变得事半功倍。Sanity Test(冒烟测试),顾名思义,就是一跑就冒烟挂了。这是设计代码和验证环境都刚刚完成后的测试,目的就是确保寄存器读写OK和打通基本数据流。这个过程设计人员和验证人员高度配合,发现bug会立即修改。这个过程会发现很多代码问题,这个阶段发现的bug一般不提问题单。
冒烟测试完成后,DUT已经基本可以正常工作,这时候就正式开始进入验证执行阶段。验证是一个不断迭代的过程,需要不断编写或修改测试用例来覆盖功能点和rtl代码。仿真中发现问题,更正问题,记录问题,回归测试,往复循环的同时,分析功能覆盖率与代码覆盖的空洞,根据分析结果增加用例运行次数,或者调整随机约束,或者增加direct testcase,使覆盖率趋于收敛。Regression 在Sanity test后将一直持续到Tape Out。
流程走到Sanity Test,正式开启EDA动态仿真之旅。Sanity Test是一条最基本的测试用例,使用最正常的测试激励,最小的随机范围或不进行随机,以快速确认基本的数据流能通以及寄存器读写正常(如果有的话), 是后面复杂测试进行下去的前提。以后不论是RTL还是VE的每一次代码增改,在提交版本服务器之前,通过sanity test是最基本的要求。
流程再往后延伸,就是一个不断迭代的过程。根据最前面的测试点分解,验证工程师不断添加测试用例,对RTL代码进行仿真,发现问题,更正问题,记录问题,回归测试,往复循环的同时,分析功能覆盖率与代码覆盖的空洞,根据分析结果增加用例运行次数,或者调整随机约束,或者增加direct testcase,使覆盖率趋于收敛。在实际的项目管理过程中,往往会把这个阶段根据功能实现比率再进行细分,比如按85%网表、95%网表、100%网表分为三段,达到每一段的出口标准后,将对设计和验证代码进行固化锁定,作为后续的基线版本,这样既有利于项目进度的精细化控制,也方便问题的管理与回溯。100%网表以后,设计将全面进入后端设计流程,RTL代码的变更将变得很少且严格,但这并不意味着验证工作可以停步,Regression 在Sanity test后将一直持续到Tape Out。在我以往的工作经验中,一直都坚持Daily Regression的形式,即每天下班前,都会将所有测试用例提交服务器进行回归测试,在**CRV(constraint random verification)**机制下,用例跑得越多,最终的验证结果越可靠,但如果全部采用Direct testcase进行验证,daily regression将变得毫无意义。
EDA仿真到最后,完成标准主要有三个,一是所有testcase全部通过,二是代码覆盖率达标,最后是功能覆盖率达到100%。代码覆盖率通常情况下不要求100%,但所有空洞需要经过分析确认并记录在验证报告中,其它两个标准是硬性指标丝毫不能打折扣。即使以上标准全部达到,通常还需要稳定回归两个星期以上,如果回归期间出现了新的问题,一切归零,问题修复后重新开始计算回归周期。实际上,成熟规范的公司对出验证出口标准的要求比这个严格得多,会有一长串checklist需要逐一确认,每一条check项后面,是一系列的质量保证活动,比如文档检视、代码检视、文档代码一致性确认、Bug收敛趋势分析、Bug Review、Corner Case确认、End_of_Check确认、编译仿真Warning分析、异步路径分析、网表仿真结果、后仿真结果、寄存器遍历、接口信号翻转遍历…………,这样的checklist通常都是用惨痛的代价换来的,每一条都要认真对待。做芯片就是这样,永远要保持“战战兢兢,如履薄冰”的心态,验证尤为如此,毕竟如果流片回来存在BUG,板子往往是先打验证。
四、数字验证的定位
我始终认为验证工作是一项科学严谨重要的研发活动,验证人员也是核心研发人员,绝不是设计人员的附属品,更不是产线操作工。验证工作是芯片前端开发的最后一道保障,是保证芯片一版成功的重要基石,小的芯片或许重点依靠设计人员能够一版成功,大的SOC成功必然是设计验证紧密配合共同努力的结果。
那些把验证人员当designer附属品或产线操作工的公司,得到的也基本会是附属品或操作工的芯片质量;反之,把验证工作看做跟设计工作同等重要地位的公司,芯片质量往往会好很多。
五、需要掌握的技能
①linux基础,脚本语言(bash、tcl、perl、python等,要掌握一门或多门脚本语言);
③C/C++、Verilog、System Verilog编程语言;
④验证方法学(UVM)。
以下书籍可以作为入门教材:
《数字电子技术基础(第五版)》阎石 主编《Verilog数字系统设计教程》《SystemVerilog测试验证平台(中文版)》《UVM实战》《芯片验证漫游指南―从系统理论到UVM的验证全视界》
参考文献:https://zhuanlan.zhihu.com/p/165336811