芯片验证系列:验证的计划篇(良心大作,验证必看)
芯片验证的方法篇(上篇)
验证的方法篇之一:计划的概述
验证的方法篇之二:计划的内容
验证的方法篇之三:计划的实现
验证的方法篇之四:计划的进程评估
验证的计划篇之一:计划的概述
在选择验证方法和构建验证环境之前,我们首先需要清楚验证计划是什么。在展开设计之前,设计人员和验证人员都会阅读功能描述文档,以理解设计的各项功能为前提,来考虑如何验证它。如果功能描述本身不清晰,则需要同系统人员沟通来修改功能描述文档;如果设计和验证双方人员对于某一项功能理解有不同的地方,也需要最后同系统人员的解释保持统一。
那么一旦完成了验证计划书,还需要对其进行修改吗?答案是需要。因为在实际项目执行过程中,功能描述文档和设计会不断更新,直到芯片到流片前都有可能在一直进行,那么验证人员就需要做好相应的验证计划更新。所以,验证计划的生命在设计被构建之前就诞生了,伴随着设计的周期,直到流片。
伴随着验证计划的创建,计划流程可以分为若干个步骤,它们包括:
创建验证计划
选择验证方法
人力资源调配
构建验证平台和环境组件
开发测试用例
创建一份验证计划是首要的任务,通过收集下列材料可以更好地组织出有价值的计划:
结构功能描述
设计的各种操作使用模式
在正常输入和错误输入情形下设计的行为
设计的接口
在一些边界情况下设计的行为
设计在现实中使用的场景描述
通常上面的这些资料可以从硬件功能描述和系统文档中找到,同时,也可以从硅后测试、固件开发人员那里得到设计的实际使用配置情况。
通过合理的验证计划,可以为芯片开发带来很多好处:
使得设计和验证人员对于功能描述文档的理解和翻译保持一致。
将自然语言描述的功能通过可测试的语言来描述。
可更合理地评估出工作量、人力安排和进度节点。
为验证人员提供更清晰的验证目标、任务和进度安排。
为功能文档提供反馈,修改文档中不明确、有歧义的描述。
从更宽泛的意义上来看,一份验证计划几乎可以囊括所以跟验证相关的东西,这其中不单单包括要验证的设计功能,还包括验证方法、人力安排、进度评估等等。由于验证计划的生命期很长,在实际环境中,有很多因素会不断影响计划的更新,这些可能的因素包括:
会有不同人员更新验证计划。一份充分的验证计划,需要系统、设计、验证、软件人员给出意见,共同参与制定。
需要更新上百上千的测试用例,并且与计划中的待测功能映射。
考虑选择不同的验证方法,如果有多种方法并用还需要使得它们之间保持兼容和跨越式的复用。针对不同的设计,需要考虑选择动态仿真、形式验证或者硬件加速方法,如果采用两种以上的方法,还需要考虑如何实现技术平台上的跨越复用。
如果有新的设计要求,需要更新计划,同时设法对人力和进度的影响降低到最小。在设计的过程中,设计人员仍然会收到新的功能需求,那么一旦确定下来添加新的功能,就需要考虑额外的人力和对于进度的影响。
如果有多个组参与验证,需要考虑如何协调。对于大型的SoC项目,一般会有多个功能组参与,甚至他们会在不同的城市办公,这时候去协调组跟组之间的工作,并综合出整体进度结果就很重要了。
通过在早期制定出一份验证计划,并且伴随着设计更新和验证过程不断修改和跟踪,就可以提高验证的质量,同时降低项目的风险。同时对于人力和时间进度的合理估计,也使得验证进度和流程更加透明。
验证的计划篇之二:计划的内容
在制定验证计划的具体过程中,我们会将技术部分和项目部分都考虑进来。从技术角度而言,我们需要考虑的有验证的功能点、验证的层次、测试用例、验证方法和覆盖率要求,从项目部分来看,我们也需要考虑使用的工具、人力安排、进度安排和风险评估。接下来,我们逐个分析技术部分和项目部分。
技术部分
验证的功能
需要验证的功能点主来自于功能描述文档,设计和验证人员在阅读文档的过程中,会将设计的功能、参数、性能从自然语言拆分转化为一个个可以单独验证的功能点,并且需要用定性定量的语言限定描述这些功能。
我们可以将功能点按照优先级分为:
基本功能:通常包括时钟、电源、复位、寄存器访问和基本特性,这些可以在模块级完成验证。
互动功能:一些需要同其它模块互动的特性,需要在更高层次的子系统级或者芯片级完成验证。
次要功能:通常这些功能没有要求必须在流片之前完成验证,例如性能验证、效能验证。它们没有通过验证,并不会造成芯片的功能错误。
验证的层次
结合验证的功能点,需要清楚该功能点是否可以在较低的层次完成验证。从验证效率和激励自由度来看,我们应该尽量在较低的层次验证更多的功能点。在较高的层次,例如芯片级,应该侧重于系统集成测试。
验证方法
需要考虑采取何种验证方法,动态仿真、形式验证还是硬件加速?采取什么样的透明度,黑盒、白盒还是灰盒?采用直接测试还是随机约束激励?在之前的验证方法篇中,我们对比了不同方法适用的场景。
测试用例
有了带验证的目标功能,选择合适的层次和方法,在完成了验证平台搭建以后,我们就需要考虑如何利用现有的验证平台给出适当的激励,检查测试结果。
覆盖率要求
覆盖率是衡量激励生成种类和设计功能点验证的量化指标,无论通过何种验证方法,我们都需要采用覆盖率来确保给出了足够多的想要的激励类型,以及设计边界和内部穷历了可能的状态。除了给出合法的激励之外,也需要考虑给出一些错误的激励,来测试设计的稳定性和纠错能力。
项目部分
工具选择
对于项目而言,需要通过验证计划中选择的方法,来考虑选择相应的工具,它们包括:
仿真工具
形式验证工具
验证IP
断言IP
调试器
硬件加速器
高层次验证语言(High-level Verification Language,HVL)
选择了不同的验证方法和工具,接下来就需要考虑安排有合适技能的验证人员完成工作。
人力安排
在确定下来验证方法以后,验证经理就可以考虑投入的人力了。由于不同验证方法存在显著差别,除过考虑个人的实际经验以外,也需要考虑他们是否熟悉该模块,知识和技术背景越贴合,越倾向于选择这样的验证人员。一般在同一个项目完整周期内,我们会考虑让固定的人员跟踪同一个设计模块,从搭建环境开始,经历模块级、子系统级和芯片系统级验证过程,这样对于项目的风险较低,人员的成长也更快。
进度安排
在安排人力的过程中,我们同时也将进度考虑了进来。一般而言,进度是从上向下传达的,验证经理事先会有一个大致的时间表,通过简单的计算:
工作量 = 人力 x 时间
来安排合适的人力投入到验证中去。而往往陷入的境地是,人力不够充分,或者时间不够宽裕,面对这样的困难,时间是没有弹性的,更多地需要在人力角度上考虑如何恰当地安排人力,做好动态的人力分配,实现高效的资源配置。那么对于验证经理而言,进度是否是不可修改的,必须严格遵循呢?这样的问题可能难以给出一个是或者否的答案,但是如果可以在计划中将设计的交付时间、验证的验收时间、不同模块的集成时间等等重要信息拆分开来,做到更细致的量化和评估,那么项目执行中的风险就可以在早期发现,同时朝着按时交付的目标共同迈进。
风险评估
在项目执行中,无论是设计人员、验证人员还是项目经理,都会面临诸多不确定的因素:
芯片结构不稳定因素。如果在项目执行后期,突然面临结构的变化,这肯定会给对应的设计和与它关联的设计带来很大影响,而验证任务量和时间也需要发生改变。
工具的不稳定因素。在新的项目中,我们倾向于使用更新的工具版本,因为它们会带来新的性能提升和特性,而在新版本工具使用中也会有适应期,并非一帆风顺。如果我们需要替换工具,那么面临的工具替换成本、环境流程更新、技术培训都要更大一些。
人力的不稳定因素。我们都希望在项目中人员结构可以稳定,这样就不会出现模块的验证人员被临时替换,加大验证风险的问题。同时,如果一个人投入到两个以上的项目,那么他在不同项目中的精力分配也需要考虑进来。
模块交付时间的不稳定因素。由于验证的展开与设计的交付时间密不可分,所以HDL设计的交付时间对于验证进度的影响非常大,所以在计划初期,验证经理应该从设计团队那里获取清晰的交付时间,然在在此基础上做进度和人力安排。
在清楚了一份验证计划中需要包含的各项因素之后,我们接下来就需要考虑如何在项目初期准备这样一份关键的计划,以及在项目执行过程中怎样针对不确定因素的相应更新计划,确保项目的进度受到最小的影响。
验证的计划篇之三:计划的实现
一份细致的验证计划会包括详细的项目动向、更新和进度,面对人员总是保持紧张的窘境,只有清晰的计划才能够合理运用人力资源,保证时间和人力的平衡。在上一篇计划的内容中,我们列举出了诸多项目中不稳定的因素,这就使得验证计划需要时常保持更新,给出合理的安排,这样的过程就蕴含着从计划到实践再到反馈,最后再进行计划修改。
计划变更的周期在不断地发生,如下图:
在对设计进行验证以后,我们需要衡量验证的完备性,这时候需要对覆盖率进行分析。当发现覆盖率无法满足要求时,我们需要针对覆盖率漏洞,更改验证计划并且相应添加测试用例,通过这样的反馈环路,我们才可以循序渐进地逼近功能验证的收敛目标。
那么如何制定验证计划呢?通常我们按照如下的步骤:
邀请相关人员参加会议
开会讨论
确定测试场景
创建验证环境
邀请相关人员
通常我们会邀请跟系统设计和功能模块相关的人员到会,共同展开讨论,参加会议的人一般包括:
设计人员
验证人员
硅后测试人员
软件开发人员
系统人员
验证经理(或项目经理)
这些人员在看待如何验证一个模块的问题上面都有着不同的角度,例如系统人员会关注功能描述是否被实现,测试场景是否可以覆盖到这些功能点,设计人员会考虑具体的设计细节是否会被测试到,软件开发人员会关心如何组合硬件的寄存器配置来完成某一项功能的正确配置和使用场景,我们将这些利益相关者对于验证某一个模块给出的不同角度列举如下:
在实际中,我们不一定可以面面俱到同时邀请到这么全的项目角色,而且,我们也要考虑这么多不同的角色一起开会,沟通起来难免存在一些障碍和分歧。所以实际的建议可以变成分阶段进行:
验证经理、设计人员和验证人员开会,确定大致需要验证的功能点、进度和人力安排。
系统人员、设计人员和验证人员沟通对于功能描述文档存在歧义的地方,确定理解一致。
设计人员、验证人员、轨后测试人员和软件人员在最后来为实际软件配置的场景添加测试用例,将软件配置添加到硅前验证阶段。
开会讨论
在开会讨论前,作为会议的组织者,需要搞清楚开会的目的和议题分别是什么?
验证计划的内容组成
需要确定的验证功能点
在开会之前,我们需要一份合适的验证计划的模板用来指导我们在会议上讨论的主要内容,一份验证计划的模板(或者组织结构)可以像下面这样:
设计功能描述
硬件实现框图
待验证的功能点
验证环境搭建
测试用例构成
编译脚本和递归测试
覆盖率分析
在上面这样的计划模板中,我们开会前需要了解的是功能描述和硬件实现方案,在开会中只需要讨论和确定哪些功能点是要验证的,而哪些事不需要验证的,至于验证环境搭建和测试用例构成则是验证工作展开以后需要更新到计划中去的。
面对着不同背景的项目人员,我们在会议中需要注意几个方面,使得会议最终可以达成我们想要的结果。这些值得注意的地方包括有:
由于与会人员本身具有不同的背景,在讨论中遇到分歧,试着从对方的角度看待这个问题,给予理解。
需要覆盖设计在实际过程中软件的使用情况和在系统中的角色扮演,探明真实运用场景。
弄明白哪些功能是基本核心功能,哪些功能是次要功能。
确定所有需要验证的功能点,以及声明哪些功能点不需要验证和哪些场景是伪场景(即不实际的运用)。
通过和不同系统层次的人沟通,充分交流不同层面上的观点,我们对于验证的功能点和它们在系统运用中的角色认识才会更加清晰。
确定测试场景
经过了细致的讨论,我们就可以确定下来哪些功能点需要测试,进而模拟实际场景给出激励进而生成测试场景。在考虑如何生成测试场景的时候,我们需要注意思考下面几个地方:
针对某些功能点,我们如何给出特定的测试场景。这些场景是否同实际情况一致或者类似,比如我们给出的时钟信号频率是否同设计要求的频率一致,不同时钟之间的同步异步关系是否参照系统要求。
需要测试的场景,会需要待验设计的哪些功能模块参与。这种情况一般在模块级测试中,往往需要较多的子模块参与进来,而随着测试的层次升高,我们需要唤醒使能的模块数量就逐渐减少了。一旦在心中有了这个习惯,这就方便与我们在构建测试用例前,大脑已经模拟出完成整个功能的序列,懂得参与进来的模块,以及如何配置寄存器、等待某些状态信号完成下一步功能设置,直到最后完成整个复杂功能的测试。
一些场景如果跟电源开关有关,我们需要考虑是否需要在PA(Power Aware)场景中完成测试。
一些场景如果跟性能有关,我们需要考虑如何发送大规模的数据量实现压力数据传输场景。
针对不同的功能点,我们需要考虑选择合适的验证层次,以及对应的验证方法,进而考虑怎么在验证环境中做好准备。
创建验证环境
在确定了测试场景和验证方法以后,我们构建验证环境产生激励来实现场景。那么我们需要针对设计模块的接口信号设计对应的激励发生组件,通过控制协调不同的激励组件来构建场景。在实现激励发生组件中,我们需要考虑接口信号是否是标准总线或者是系统控制信号例如时钟、复位,如果有可以复用的验证资源,那么毫无疑问会节省我们的时间。在有些时候,如果接口是标准总线,且没有现有资源可以利用的情况下,我们需要自己实现,那么从成本的角度来看,只需要实现设计中所实现的总线功能即可。例如,如果设计实现的是AHB总线协议,但是只支持单次的读写访问,那么我们在实现AHB激励组件的时候,也不必要实现AHB协议的全部,而只需要实现单次读写协议,满足设计接口的协议要求即可。
同时,我们也需要考虑收集数据和检查对比结果,这就需要有监视信号组件和检查组件的实现。监视信号组件的主要任务就是监视设计的接口信号以及内部信号,如果是总线接口,那么需要在解析总线的情况下将观察到的数据打包整理,如果是控制或者其它信号,也需要按照信号的使能定义,在特定的事件下捕捉有效信号。监视信号组件最终会将分析整理好的数据发送给检查组件,最后由检查组件进行数据比较,给出比较信息和报告,最终判定该次测试是否成功。
验证的计划篇之四:计划的进程评估
在验证过程中,我们需要不断地更新验证的进度,从各项参数综合评估验证的完备性。在不同的验证层次过程中,我们通过收集以下信息来评估验证计划的实施进程:
递归测试通过率(regressioin pass rate)
代码覆盖率(code coverage)
断言覆盖率(assertion coverage)
功能覆盖率(function coverage)
缺陷曲线(bug rcurve)
接下来我们分别介绍上述信息的具体收集和分析过程。
递归测试通过率
一份递归测试表是将测试设计所有功能点的用例合并为一个测试集。递归测试表的主要功能就是用来在设计经过缺陷修复或者性能提高后测试原有的所有功能点,确保设计仍然可以正常工作。这种往复的测试方式不仅在于确保新的设计变化不会影响之前的功能,也可以用来避免新的对话对于别的模块造成的功能失效,所以,设计的维护不仅在于按照设计需求提供新的功能,而且也需要保证新功能不会影响原有的功能。
不同的公司和团队之间,往往有着不同的递归测试工具和方法,在这里需要注意的是工具和脚本的版本可能会对递归测试造成影响。例如,如果我们切换了仿真器的版本,那么可能会出现新的问题需要我们调试,所以在项目后期阶段设计区域稳定时,我们不建议切换工具或者脚本的版本。
另外一个重要的地方时,递归测试表中的测试用例需要确保是可以重现激励场景的。这一点对于直接测试方法(例如C/C++)是容易实现的,而对于随机约束测试而言,我们需要在测试中打印出每次测试用例使用到随机种子(random seed),只有通过这个特定的钟子号,我们才可以重新产生之前的激励,并且跟踪调试失败的用例。
我们将递归测试的流程归纳为下图,值得注意的是,如果在某一个层次的递归测试通过,我们接下来可以向上迁移到新的验证层次,展开新的递归测试流程,或者在设计需求发生变化时,重新从模块级开始递交测试表。
不同层次的递归测试表,每个测试用例的仿真时间消耗也不一样,一般而言,模块级是最快的,到了芯片级,一个递归测试表如果是数千级别的测试用例,往往需要若干天时间才能最终运行完毕得出结果。所以,不同层次,不同设计规模大小,不同测试场景复杂度,都会影响测试用例的仿真时间。递交测试表的重要因素就是仿真速度,由于考虑到递交测试表主要是计算资源的消耗和验证结构的性能表现,我们对验证平台的优化和运算资源都会在此时提出更高的要求,因为只有更快速地往复递交和得出结果,才能更快得知新的设计变动是否是可靠的。
代码覆盖率
代码覆盖率是用来衡量RTL代码是否被充分运行的指标,目前的仿真器也都提供方法来收集代码覆盖率,并且进行合并和分析。通过递归测试表,我们可以产生基于测试用例的代码覆盖数据,并且在递归测试完成后,通过合并数据,生成总的数据来分析各个模块的覆盖率情况。常见的代码覆盖率包括:
语句覆盖率(statement coverage):指的是程序的每一行代码是否被执行过。
条件覆盖率(condition coverage):指的是每个条件中的逻辑操作数被覆盖的情况。
决策覆盖率(branch coverage):指的是在if, case, while, repeat, forever, for和loop语句中各个分支执行的情况。
事件覆盖率(event coverage):该覆盖率用来记录某一个事件被触发的次数。
跳转覆盖率(toggle coverage):用来记录某个设计边界信号数据位的0/1跳转情况,例如从0到1,或者从1到0的跳转。
状态机覆盖率(finite stage machine coverage):仿真器的覆盖率功能可以识别出设计中的状态机部分,记录各种状态被进入的次数,以及状态之间的跳转情况。
值得注意的一点是,仿真器在收集覆盖率数据的时候会牺牲一些运行效率,这是因为它需要对代码保持“更多的关注”,所以资源消耗要更多一些。所以,我们建议只有在需要收集覆盖率的时候,需要传入一些仿真命令触发覆盖率收集,而在更多情况下,不需要传入这些命令,也不需要编译带有支持覆盖率收集的仿真目标。
在项目执行中,我们一般会在模块级验证节点结束后开始收集模块级的代码覆盖率,而在芯片级验证节点结束后,收集芯片级的代码覆盖率。在两部分的数据都收集结束后,我们会进行这两个级别的覆盖率数据融合,生成总的数据库。一般项目中会有专人来负责收集和分析覆盖率,各个模块的覆盖率数据会分发给相应的验证人员,等待他们的分析、过滤或者添加新的测试用例,再次递交测试收集新的数据,以此往复,来提高总体的覆盖率。
通常,我们会比较关注语句覆盖率、决策覆盖率和跳转覆盖率,对于总体指标和各个模块在这三项覆盖率上也有相应的指标,只有至少达到了90%以上的覆盖率,才会有足够的信心进行分析下面的两类覆盖率。
断言覆盖率
断言描述本身也支持覆盖率收集,一般可以通过仿真或者硬件加速的方式来收集,也可以通过形式验证的工具来收集。在常见的仿真中,仿真器会记录断言的先决条件是否被触发,以及判断语句成功或者失败。
根据选择的验证方法不同,我们可以将断言覆盖率分为:
基于动态仿真或者硬件加速的断言覆盖率
基于形式验证的静态断言覆盖率
功能覆盖率
功能覆盖率是为了衡量设计的各项功能要求是否都实现了,并且按照预想的行为执行。功能覆盖率会关注设计的输入、输出和内部状态,通常它会通过如下组合的方式来描述功能覆盖率要求:
对于输入而言,它会检测数据端的输入和命令组合类型,以及各种控制信号与数据传输的组合情况。
对于输出而言,它会检测是否有完整的数据传输类别,和各种情况的反馈时序。
对于设计内部而言,需要检查的点会跟验证计划中需要覆盖的功能点相对应,通过组合或者时序的功能覆盖率来检查目标功能是否被触发、以及执行是否正确。
缺陷曲线
在验证过程中,我们会不断地发现新的设计缺陷,通过缺陷记录表或者已有的商业工具记录下来,进而提交给设计人员。设计人员在分析了缺陷,构思并修复了设计缺陷以后,也会修改该缺陷的记录,并且通知验证人员。验证人员会递交原有的递归测试,在有必要的情况下添加新的测试用例,直到所有的测试通过以后,才能宣布新修复的缺陷是成功的。
在缺陷被记录的过程中,我们通过时间坐标和特定时段的缺陷数量绘制出缺陷率曲线。在之前《验证的任务》一文中,我们就指出了缺陷曲线对于验证计划的影响,从下图中我们看到,如果我们可以尽早地将缺陷曲线增长状态收敛,这意味着我们后期发现缺陷数量和可能性也就越小。可有些时候,我们需要当心的是,如果到了验证后期,我们发现了一个基本功能存在重大缺陷,这就是一个危险信号,代表我们很可能在之前验证当中遗漏一些重要的激励部分。
至此,我们将用来评估验证进度的几种标准概括介绍完了。对于验证计划的制定,不仅仅是验证人员会参与其中,其它角色也会被邀请到会议当中。而到后来验证进度的更新和评估过程,则主要是验证人员和验证经理的在执行。实际项目的经在重复告诉我们,一份详尽准确、不断更新维护的验证计划是迈向成功验证的基石。
来源:路科验证(微信订阅号ROCKER-IC) 及 EETOP BLOG