芯片验证漫游指南4验证的计划
1 计划概述
在选择验证方法和构建验证环境之前,需要搞清楚验证计划是什么。在展开设计之前,设计人员和验证人员会阅读功能描述文档,以理解设计的各项功能为前提,考虑如何实现或验证各项功能。如果功能描述本身不清晰,则需要与系统人员沟通来修改功能描述文档;如果设计和验证双方人员对某一项功能理解有分歧,也需要与系统人员的解释保持统一。完成验证计划后,还需要对其进行修改吗?答案是肯定的。
因为在实际项目执行过程中功能描述文档和设计不断更新,直到流片前都有可能在进行更新,验证人员需要做好相应的验证计划更新。所以,验证计划的生命在设计被构建之前就诞生了,伴随着设计的周期,直到流片。验证计划从创建到执行分为以下几个阶段:
- 创建验证计划;
- 选择验证方法;
- 人力资源调配;
- 构建验证平台和环境组件;
- 开发测试用例。
创建一份验证计划是首要的任务,通过收集下列材料可以更好地组织出有价值的计划:
- 结构功能描述;
- 设计的各种操作使用模式;
- 在正常输入和错误输入情形下设计的行为;
- 设计的接口;
- 在一些边界情况下设计的行为;
- 设计在实际使用中的场景描述。
这些资料通常可以从硬件功能描述和系统文档中找到。同时,也可以从硅后测试、固件开发人员那里得到设计的实际使用配置情况。合理的验证计划可以为芯片开发带来很多好处:
- 使得设计和验证人员对功能描述文档的理解和翻译保持一致。
- 将自然语言描述的功能通过可测试的语言来描述。
- 可以更合理地评估出工作量、人力安排和进度节点。
- 为验证人员提供清晰的验证目标、任务和进度安排。
- 为功能文档提供反馈,修改文档中不明确、有歧义的描述。
从更宽泛的意义上来看,一份验证计划几乎囊括所有与验证相关的东西,其中不只包括要验证的设计功能,还包括验证方法、人力安排、进度评估,等等。验证计划的生命期很长,在实际环境中,很多因素会不断影响计划的更新,这些可能的因素包括:
- 会有不同人员更新验证计划。一份充分的验证计划,需要系统、设计、验证、软件人员给出意见,共同参与制定。
- 需要更新上百上千的测试用例,并与计划中的待测功能映射。
- 考虑选择不同的验证方法。针对不同的设计,需要考虑选择动态仿真、形式验证或者硬件加速方法。如果采用两种以上的方法,还需要考虑如何实现技术平台上的兼容和跨越式复用。
- 如果有新的设计要求,需要更新计划,同时设法把对人力和进度的影响降低到最小。设计人员在设计的过程中仍然可能收到新的功能需求,一旦确定要添加新的功能,就需要考虑额外的人力和进度受到的影响。
- 如果有多个组参与验证,则需要考虑如何协调。对于大型的SoC 项目,一般会有多个功能组参与,甚至他们可能工作在不同的城市,这时,协调组与组之间的工作并综合出整体进度结果就很重要了。
在早期制定一份验证计划,随着设计更新和验证进度跟踪,提高验证的质量,降低项目的风险。同时,验证计划对人力和时间进度的合理估计,也使得验证的流程和进度更加透明。
2 计划的内容
在制定验证计划的具体过程中,我们将技术部分和项目部分都考虑进来。从技术角度而言,需要考虑的有验证的功能点、验证的层次、测试用例、验证方法和覆盖率要求。从项目管理角度,也需要考虑使用的工具、人力安排、进度安排和风险评估。
2.1 技术的视角
验证的功能
需要验证的功能点来自于功能描述文档,设计和验证人员在阅读文档的过程中,会将设计的功能、参数、性能从自然语言拆分转化为一个个可以单独验证的功能点,并用定性定量的语言描述这些功能。
我们将功能点按照优先级分为:
- 基本功能:通常包括时钟、电源、复位、寄存器访问和基本特性,这些可以在模块级完成验证。
- 互动功能:一些需要同其他模块互动的特性,需要在更高层次的子系统级或芯片级完成验证。
- 次要功能:通常这些功能在项目后期完成验证,如性能验证、效能验证。即使它们没有通过验证要求,也不会对芯片造成致命影响。
验证的层次
结合验证的功能点,需要清楚该功能点是否可以在较低的层次完成验证。从验证效率和激励自由度来看,我们应该尽量在较低的层次验证更多的功能点。在较高的层次,如芯片级,应该侧重于系统集成测试。
验证方法
需要考虑采取何种验证方法,动态仿真、形式验证还是硬件加速?采取什么样的透明度,黑盒、白盒还是灰盒?采用定向测试还是随机约束激励?在第3章,我们对比了不同方法适用的场景。
测试用例
有了验证的目标,选择合适的层次和方法,在完成了验证平台搭建以后,我们就需要考虑如何利用验证平台给出适当的激励,检查测试结果。
覆盖率要求
覆盖率是衡量激励生成种类和功能点验证的量化指标。无论通过何种验证方法,都需要采用覆盖率来确保给出了足够多的激励类型,并且设计的边界和内部穷历了可能的状态。除了给出合法的激励之外,也需要考虑给出一些错误的激励,测试设计的稳定性和纠错能力。
2.2 项目的视角
工具选择
对项目而言,需要通过验证计划中选择的方法考虑选择相应的工具,包括:
- 仿真工具;
- 形式验证工具;
- 验证IP;
- 断言IP;
- 调试器;
- 硬件加速器;
- 高层次验证语言(HVL,High-level Verification Language)。
选定验证方法和工具后,接下来需要考虑安排具备合适技能的验证人员完成工作。
人力安排
进度安排
风险评估
在项目执行中,无论是设计人员、验证人员还是项目经理,都面临诸多不确定的因素:
- 芯片结构不稳定因素。在项目执行后期,如果突然面临结构的变化,肯定给相关设计带来很大影响,而验证任务量和时间也需要改变。
- 工具的不稳定因素。在新的项目中,我们倾向于使用新的工具版本,因为它们会带来新的性能提升和特性;而新版本工具的使用需要适应期,并非一帆风顺。替换工具时面临的工具替换成本、环境流程更新、技术培训都要更大一些。
- 人力的不稳定因素。我们希望在项目中人员结构可以稳定,这样就不会出现模块的验证人员被临时替换、加大验证风险的问题。同时,如果一个人投入到两个以上的项目,那么他在不同项目中的精力分配也需要考虑进来。
- 模块交付时间的不稳定因素。验证的展开与设计的交付时间密不可分,HDL 设计的交付时间对验证进度的影响非常大。所以,在计划初期,验证经理应从设计团队那里获取清晰的交付时间,在此基础上做进度和人力安排。
在清楚了一份验证计划中需要包含的各项因素之后,接下来就要考虑如何在项目初期准备这样一份关键计划,以及在项目执行过程中怎样针对不确定因素相应地更新计划,确保项目的进度受到的影响最小。
3 计划的实现
一份细致的验证计划包括项目动向、更新内容和工程进度,面对人力资源总是紧张的窘境,只有清晰的计划才能够合理运用人力资源,保证时间和人力的平衡。在4.2节,我们列举了项目中诸多不稳定因素,它们使得验证计划需要时常保持更新,给出合理的安排,这样的过程就蕴含着从计划到实践再到反馈,最后到修改计划的周期。计划变更的周期在不断地发生,如图4.1所示。

在对设计进行验证以后,我们需要衡量验证的完备性,这时需要对覆盖率进行分析。当发现覆盖率无法满足要求时,要针对覆盖率漏洞更改验证计划并添加新的测试用例。通过这样的反馈环路,循序渐进地逼近功能验证的收敛目标。那么如何制定验证计划呢?通常按照如下步骤:
- 邀请相关人员参加会议;
- 开会讨论;
- 确定测试场景;
- 创建验证环境。
3.1 邀请相关人员
邀请与系统设计和功能模块相关的人员参加会议,共同讨论。参加会议的人员一般包括:
- 设计人员;
- 验证人员;
- 硅后测试人员;
- 软件开发人员;
- 系统人员;
- 验证经理(或项目经理)。
这些人员在看待如何验证一个模块的问题上各有不同的角度。例如,系统人员关注功能描述是否被实现,测试场景是否可以覆盖到这些功能点;设计人员考虑具体的设计细节是否会被测试到;软件开发人员关心如何配置寄存器来使用某一项功能。我们将这些利益相关者看待验证模块的不同角度总结在表4.1。

在实际工作中,我们不一定可以面面俱到地同时邀请到这么多的项目角色,而且,这么多不同的角色一起开会,沟通起来难免存在一些障碍和分歧。所以,实际的建议可以变成分阶段进行:
- 验证经理、设计人员和验证人员一起开会,确定大致需要验证的功能点、进度和人力安排。
- 系统人员、设计人员和验证人员一起沟通对功能描述文档存在的分歧,确保理解一致。
- 设计人员、验证人员、硅后测试人员和软件人员一起为模块应用的实际场景添加测试用例。
3.2 开会讨论
在开会讨论前,作为会议的组织者,需要搞清楚开会的目的和议题分别是什么。
- 验证计划的内容组成;
- 需要确定的验证功能点。
同时,需要一份合适的验证计划模板指导会议讨论的内容。验证计划的模板(或组织结构)应包括下面的内容:
- 设计功能简要描述;
- 硬件实现框图;
- 待验证的功能点;
- 验证环境搭建;
- 测试用例构成;
- 编译脚本和回归测试;
- 覆盖率分析。
在计划模板中,会议前需要了解的是功能描述和硬件实现方案;开会中只需要讨论和确定哪些功能点是要验证的、哪些是不需要验证的。至于验证环境搭建和测试用例构成,则是验证工作展开以后更新到计划中。面对不同背景的项目人员,我们在会议中需要注意几个方面,以使会议最终可以取得预期的结果。这些值得注意的方面包括:
- 由于与会人员具有不同的背景,在讨论中遇到分歧时,应换位思考,从对方的角度看待这个问题,给予理解。
- 需要覆盖设计在实际过程中软件的使用情况和在系统中的角色扮演,探明真实运用场景。
- 弄明白哪些功能是核心功能、哪些功能是次要功能。
- 确定所有需要验证的功能点,以及声明哪些功能点不需要验证、哪些场景是伪场景(不实际的运用)。
只有不同系统层面的人相互沟通,充分交流不同视角和观点,我们对验证功能点及其在系统运用中的认识才会更加清晰。
3.3 确定测试场景
经过细致的讨论,可以确定哪些功能点需要测试,继而模拟实际场景给出激励。在考虑如何生成测试场景时,我们需要思考下面几个地方:
- 针对某些功能点,我们如何给出特定的测试场景。这些场景是否同实际情况一致或者类似,比如我们给出的时钟信号频率是否同设计要求的频率一致,不同时钟之间的同步异步关系是否参照系统要求。
- 需要测试的场景,需要待验设计的哪些功能模块参与。这种情况一般在模块级测试中,往往需要较多的子模块参与进来,而随着测试的层次升高,我们需要唤醒使能的模块数量就逐渐减少了。我们在构建测试用例前,心中已经模拟出测试序列,明确了参与进来的模块,以及如何配置寄存器、等待某些状态信号完成下一步功能设置,直到最后完成整个功能测试。
- 如果一些场景涉及电源开关,要考虑是否在PA(Power Aware)场景中完成测试。
- 如果一些场景与性能有关,要考虑如何发送大规模的数据量实现压力数据传输场景。
- 针对不同的功能点,要考虑选择合适的验证层次,以及对应的验证方法。
3.4 创建验证环境
确定测试场景和验证方法后,要构建验证环境产生激励来实现场景。构建环境时,针对设计模块的接口信号需要实现对应的激励发生组件,通过控制协调不同的激励组件来构建场景。在实现激励发生组件中,需要考虑接口信号是标准总线还是系统控制信号。如果有可以复用的验证资源,那么会节省构建平台的时间。有些时候,如果接口是标准总线,且没有可复用的验证资源,就需要自己实现总线激励模型。从成本的角度来看,只需要实现设计中所纳入的总线特性即可。例如,如果设计实现的是 AHB 总线协议,但是只支持单次的读写访问,那么我们在实现AHB激励组件时,不必要实现AHB协议的全部,而只需要实现单次读写协议,满足设计接口的协议要求即可。
同时要考虑收集数据和对比结果,这就需要监视信号组件和检查组件的实现。监视信号组件的主要任务是监视设计的接口信号以及内部信号。如果是总线接口,那么需要在解析总线的情况下将观察到的数据打包整理;如果是控制信号或者其他信号,要按照信号的定义,在特定事件下捕捉有效信号。监视信号组件最终将分析整理好的数据发送给检查组件,由检查组件进行数据比较,给出比较信息和报告,最终判定测试是否成功。
4 计划的进程评估
在验证过程中需要不断地更新验证进度,从各项参数综合评估验证的完备性。通过收集以下信息来评估验证计划的实施进程:
- 回归测试通过率(regression pass rate);
- 代码覆盖率(code coverage);
- 断言覆盖率(assertion coverage);
- 功能覆盖率(function coverage);
- 缺陷曲线(bug curve)。
接下来分别介绍这些信息的收集和分析过程。
4.1 回归测试通过率
回归测试表是将测试设计所有功能点的用例合并为一个测试集。回归测试表的主要功能是在设计经过缺陷修复或性能提高后测试原有的所有功能点,确保设计正常工作。这种往复的测试方式不仅在于确保新的设计变化不影响之前的功能,也可以用来避免修改后的设计对别的模块造成的功能失效。所以,设计的维护不仅按照设计需求提供新的功能,也要保证新功能不影响原有的功能。不同的公司和团队之间,往往有着不同的回归测试工具和方法。这里需要注意的是工具和脚本的版本可能会对回归测试造成影响。例如,如果切换了仿真器的版本,那么可能出现新的问题需要调试,所以在项目后期阶段设计趋于稳定时,不建议切换工具或脚本的版本。另外一个重要的地方是,回归测试表中的测试用例需确保是可以重现激励场景的。这一点对于定向测试方法(例如C/C++)是容易实现的,而对随机约束测试而言,要在测试中显示出每次测试使用的随机种子(random seed),只有通过这个特定的种子,才可以重新产生之前的激励,跟踪调试失败的用例。
我们将回归测试的流程归纳为图4.2。值得注意的是,在某一个层次的回归测试通过,接下来可以向上迁移到新的验证层次,展开新的回归测试流程;或者在设计需求发生变化时,重新从模块级开始递交测试表。

不同层次的回归测试表,每个测试用例的仿真时间消耗也不一样。一般而言,模块级是最快的,到了芯片级,一个回归测试表如果包含数千规模的测试用例,往往需要若干天时间才能最终运行完毕得出结果。所以,不同层次、不同设计规模、不同测试场景复杂度,都会影响测试用例的仿真时间。递交测试表的重要因素就是仿真速度,由于考虑到递交测试表主要依靠计算资源和验证结构的性能表现,我们对验证平台的优化和运算资源都会在此时提出更高的要求。因为只有更快速地往复递交和得出结果,才能更快得知新的设计变动是否可靠。
4.2 代码覆盖率
代码覆盖率是用来衡量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%以上的覆盖率,才有足够的信心来分析下面的两类覆盖率。
4.3 断言覆盖率
断言描述本身支持覆盖率收集,一般通过仿真或者硬件加速的方式收集,也可以通过形式验证的工具收集。在常见的仿真中,仿真器记录断言的先决条件是否被触发,以及判断语句成功还是失败。根据选择的验证方法,我们可以将断言覆盖率分为:
- 基于动态仿真或者硬件加速的断言覆盖率;
- 基于形式验证的静态断言覆盖率。
4.4 功能覆盖率
功能覆盖率衡量是否实现设计的各项功能,且是否按预想的行为执行。功能覆盖率关注设计的输入、输出和内部状态,通常以如下方式描述信号采样要求:
- 对于输入,它检测数据端的输入和命令组合类型,以及控制信号与数据传输的组合情况。
- 对于输出,它检测是否有完整的数据传输类别,以及各种情况的反馈时序。
- 对于设计内部,需要检查的信号与验证计划中需要覆盖的功能点相对应。通过对信号的单一覆盖、交叉覆盖或时序覆盖来检查功能是否被触发,以及执行是否正确。
4.5 缺陷曲线
验证过程中会不断发现新的设计缺陷,使用缺陷记录表或已有的商业工具将这些缺陷记录下来,提交给设计人员。设计人员在分析缺陷、修复缺陷后,也会修改缺陷记录,并通知验证人员。验证人员递交原有的回归测试,必要时添加新的测试用例,直到所有的测试通过,才能宣布新修复的缺陷是成功的。在缺陷被记录的过程中,我们通过时间坐标和特定时段的缺陷数量绘制出缺陷率曲线。在1.4节中,我们指出了缺陷曲线对验证计划的影响。从图1.8我们看到,尽早地将缺陷曲线收敛,意味着后期发现缺陷的数量和可能性越小。有时要当心的是,如果到了验证后期发现了一个基本功能存在重大缺陷,那就是一个危险信号:意味着很可能在之前验证过程中遗漏了一些重要的测试场景。实际项目的经验重复告诉我们,一份详尽准确、不断更新维护的验证计划是迈向成功验证的基石。
5 作者结束语
验证计划的制定不只是需要验证师,还需要其他相关领域的同事共同参与。验证师与验证经理对同一份验证计划的关注角度也不相同。在验证前期,整理好的验证功能测试点会便于验证的回顾;在验证中期,验证环境的结构框图让代码变得更加清晰易懂;在验证尾期,验证师需要收集回归测试通过率、代码覆盖率、断言覆盖率、功能覆盖率和缺陷曲线。这些内容将综合构成验证的量化指标,也让验证经理更容易评估验证的完备性。
芯片验证漫游指南4验证的计划