干了快八年SoC验证,我见过太多模块验证做到一半翻车的项目。有人UART验证做得风生水起,最后集成到系统总线上暴雷;有人功能覆盖率冲到95%,回归一跑全红,查了半天是验证环境里一个信号接错了;还有人拿着需求文档直接开干,三个月后拿给架构师看,发现整个验证范围都是错位的。这类问题十有八九都有一个共同根源:模块验证规格说明(Module Verification Specification)没写透。
这里说的验证规格说明,不等于验证计划(Verification Plan),它更偏向一份“该测什么、怎么测、测到什么程度算完”的执行级文档。SoC项目里模块多了,动不动几十个,每个模块的规模、风险、依赖都不一致,如果没有一份清晰的验证规格说明兜底,验证工程师大概率会在需求理解偏差、覆盖率漏点、环境返工这些坑里来回打滚。这篇内容就想聊清楚一件事:一份靠谱的SoC模块验证规格说明,到底该怎么拆、怎么写、怎么用,以及我在实际项目中踩过的坑和补上的窟窿。适合刚入行的验证工程师拿来当模板参考,也适合带项目的同事检查自己手里的规格说明是不是有漏项。
1. 验证规格说明到底解决什么问题
1.1 项目里的真实痛点
先说一个我自己带过的真实案例。某个项目里有一颗带安全功能的SPI控制器,负责片内安全子系统与外部的加密芯片通信。模块验证启动时,团队成员照着需求文档草草列了二三十个case,覆盖了基本读写、中断和CRC校验,觉得差不多了。结果模块验证阶段结束、进入SoC集成验证时,发现两个致命问题:第一,SPI控制器在CS信号异常拉高时,会把FIFO里的残留数据当成有效数据上报,这个场景模块验证阶段完全没覆盖;第二,安全功能里有一条“密钥清零后,寄存器回读字节序需按小端展开”的约束,验证环境里参考模型直接按大端算,两个数字对不上,计分板天天报错。
这两个问题都不是验证工程师偷懒,根因在于模块验证阶段没有一个完整的验证规格说明来约束“验证边界”和“功能点全集”。等到集成阶段才发现,返工成本直接翻了三倍不止。这种事情在SoC项目里实在太常见了。验证规格说明的核心价值,就是把“验证到底要覆盖什么”这个问题,在代码动手之前先用文档钉死,而不是靠个人记忆和口头禅支撑。
1.2 它和验证计划、需求规格的边界在哪
很多团队会把验证规格说明和验证计划混着用,这在中小型项目里可能还能凑合,一旦模块多、周期长、人员流动大,混用的后果就是文档失去可执行性。我个人的划分习惯是:
需求规格说明书(Design Specification)描述的是“硬件做了什么”,比如某个模块支持SPI Mode 0/1/2/3、发送FIFO深度是16、支持中断屏蔽。验证计划(Verification Plan)描述的是“验证策略和资源”,比如用UVM搭环境、做多少轮回归、使用哪种覆盖率工具。而验证规格说明(Verification Specification)描述的是“针对这个模块,验证工作具体要做哪些事”,包括功能点全集、场景分类、覆盖率模型、环境组件清单、收敛标准。
三者的关系,可以类比成盖房子:需求规格是设计蓝图,验证计划是施工组织设计,验证规格说明是每个房间的验收标准清单。蓝图告诉你墙在哪、门朝哪开,施工组织设计告诉你先扎钢筋还是先支模板,验收清单告诉你每一堵墙要检查垂直度、每一扇门要测试开启力矩。少了验收清单,施工队各自理解,最后交房时才发现客厅门和卫生间门装反了。
表格对比一下更直观:
| 文档类型 | 回答的核心问题 | 使用时机 | 负责人 |
|---|---|---|---|
| Design Specification | 模块实现了什么功能 | 设计完成前 | 设计工程师 |
| Verification Plan | 验证整体策略是什么、投入多少资源 | 验证启动前 | 验证负责人 |
| Verification Specification | 该测什么、怎么测、测到何种程度算完成 | 模块验证启动时 | 验证工程师 |
三者边界清晰之后,验证规格说明的定位就非常明确了:它是一份面向验证工程师的、可执行的、模块级的工作指令,而不是一份给领导和客户看的汇报材料。
1.3 一份规格说明要管住哪些事
按我目前的习惯,一份能打硬仗的模块验证规格说明至少要管住四件事:
第一,验证范围。明确这个模块验什么、不验什么。比如APB接口的时序由通用APB VIP保证,模块验证阶段不重复测;带安全属性的寄存器由硬件安全模块(HSM)侧统一验证,这里只做信号级对接检查。
第二,功能点全集。把模块需求拆成一个个可验证的功能点,每个功能点对应至少一个场景或一条断言。这是整个文档最核心的部分,也是后面覆盖率建模的依据。
第三,验证环境约束。规定用什么语言、什么方法学、哪些VIP、参考模型怎么建、计分板比对策略是什么。环境不约束,两个工程师能写出两套风格完全不同的UVM环境,返工的时候哭都来不及。
第四,收敛标准。覆盖率目标、断言通过率、回归轮数和bug收敛趋势,全部量化。没有量化收敛标准,验证结束与否全凭项目组长拍脑袋,这就是灾难的开始。
2. 搭建验证规格说明的整体框架
2.1 一个可以直接抄的模板框架
我翻过很多厂内部的验证规格说明模板,好的坏的五花八门。这些年沉淀下来,我个人习惯用下面这个框架,整体上足够通用,又能根据模块特性做裁剪:
| 章节编号 | 章节名称 | 内容要点 |
|---|---|---|
| 1 | 文档信息与修订记录 | 作者、评审人、修改历史、关联需求文档编号 |
| 2 | 模块概述 | 模块功能简介、位置框图、验证目标 |
| 3 | 验证范围 | 明确验收什么、不验收什么、与其他团队的边界 |
| 4 | 接口与协议描述 | 接口信号清单、时序要求、协议标准引用 |
| 5 | 功能点分解 | 按功能域划分的功能点全集,每点带唯一编号 |
| 6 | 验证场景与测试用例清单 | 场景分类、用例映射、优先级标注 |
| 7 | 覆盖率模型 | 功能覆盖率、代码覆盖率、断言覆盖率的目标与定义方式 |
| 8 | 验证环境架构 | 组件清单、参考模型策略、计分板比较规则 |
| 9 | 收敛标准与回归策略 | 量化指标、回归频率、失败处理流程 |
| 10 | 风险与依赖 | 遗留问题、对IP验证或SoC验证的依赖项 |
这个框架的核心思路是:从“是什么”到“验什么”再到“怎么验”再到“验到什么程度算完”,一条线串下来,逻辑上每一步都承接上一步。评审时按着这个顺序翻,很少会出现前面没定义、后面靠猜的情况。
2.2 粒度拿捏:别写得太粗也别写成教科书
写验证规格说明时,最常见的两个极端,一个是写得太粗,另一个是写得太细。写得太粗的典型表现是“验证SPI控制器所有功能,确保无残留风险”,这跟没写一样,SPI控制器几十个寄存器、几十种操作模式,一句话根本管不住。写得太细的典型表现是“DUT信号xxx在第3个时钟沿拉高,第4个时钟沿拉低”,这种细度应该属于testbench代码实现注释,写成文档既没人愿意看,也维护不住。
我建议的粒度是:以“可验证功能点”为最小单元。什么叫可验证功能点?就是“准备什么条件、施加什么激励、检查什么行为”这三要素描述清楚的一个功能行为。比如“当发送FIFO已满且TX_EN拉高时,模块产生FIFO_FULL中断并保持发送请求直到FIFO释放一个槽位”,这就是一个可验证功能点。它没有精确到时钟沿,也没有含糊到“验证FIFO功能”,粒度刚刚好。
另外要提醒一点:功能点的普遍数量级是可以预估的。一个中等复杂度的接口模块,功能点通常在50到100个;一个带算法或安全特性的子系统级模块,功能点可能冲到200个以上。如果你清单里只有十来个功能点,大概率是漏得厉害;如果你列了四五百个,大概率是拆到了寄存器位级,维护成本会拖垮整个验证周期。
2.3 写之前先定好命名和编号规则
这就是个看上去不起眼、实际能救命的细节。功能点、场景、测试用例、覆盖率模型、断言,这些元素之间有一大堆映射关系。如果命名规则不统一,后面做映射表时就会乱成一锅粥。
我自己惯用的编号规则是:模块名_功能域缩写_序号。比如spi_ctrl_irq_001表示SPI控制器中断功能域的第1个功能点。功能域用三个字母缩写,常见的有irq(中断)、dma(DMA交互)、fifo(数据通路)、cfg(配置)等。测试用例编号沿用功能点编号,加上后缀_tc,比如spi_ctrl_irq_001_tc1。覆盖率模型里covergroup的实例名直接带上功能点编号,断言命名也用类似规则。
这样做的最大好处是,任何评审会上被质疑“这个逗号为什么没有覆盖”,拿起文档一翻,就能从功能点一路追踪到覆盖率定义,整个过程不超过五分钟。没有编号体系的时候,想追踪一个问题经常要翻三个文档、问四个人,效率低到令人绝望。
3. 模块验证规格的核心拆解:六大关键块
3.1 接口与协议描述
接口与协议描述是验证规格说明里最容易被忽略、但返工代价最高的部分。很多验证工程师拿到模块RTL代码后,扫一眼接口列表就直接开始写UVM环境,结果环境建到一半发现APB总线的等待状态机制理解偏了,或者DUT的中断极性是低有效而不是默认的高有效,整个环境返工。
我的建议是,验证规格说明里一定要有一个专门的章节,列出:
- 所有接口信号:名称、方向、位宽、极性(高有效/低有效)、时钟域。
- 协议描述:模块挂在什么总线上(APB/AXI/AHB),遵循什么协议版本,有哪些等待状态、突发模式、保护机制。
- 时序要求:关键路径的时序约束,比如寄存器访问需要几个cycle完成、FIFO写满后多久能恢复。
- 时钟与复位:时钟频率、时钟门控策略、复位的同步方式和释放时序。
这块内容不需要验证工程师自己发明,直接从设计规格里摘录,但一定要由验证工程师自己复述一遍,确认自己真的理解了。我有一次复核接口描述时才发现,一个AHB从机接口的HREADY信号,设计实现里是组合逻辑输出,而通用AHB VIP默认是寄存器输出,星期的时序对不上。如果没在规格说明阶段发现,环境调起来又是一个星期的苦工。
3.2 功能点分解与场景设计
功能点分解是整个验证规格说明的灵魂。拆解的源头通常是设计规格里的功能列表,但设计规格往往按设计视角组织,验证需要按行为视角重新组织。举个简单的例子,设计规格可能写“模块拥有8级深度的发送FIFO”,验证工程师不能就这么写一个功能点“验证发送FIFO深度”,而应该拆出几个行为视角的功能点:FIFO空时写数据可立即发送、FIFO半满时中断拉高、FIFO满时写操作被忽略或置位溢出标志、FIFO读空后状态位正确清零。
从设计功能到验证功能点的拆解方式,我一般按以下步骤走:
第一,列出模块设计规格里的所有功能列表。第二,按功能域归类,比如数据通路、控制逻辑、中断、DMA接口、安全特性、低功耗接口。第三,在每个功能域里,针对每一项设计功能,问三个问题:正常路径是什么?异常路径是什么?边界条件是什么?第四,把三个问题的答案各提炼成一个或多个可验证功能点,并编号入库。
这里面有一个很重要的原则:宁可多写,不要漏写。功能点多了可以在覆盖率收敛时判定为低价值剪裁掉,但功能点漏了,覆盖率再高也守不住bug。我在实际项目里最惨的一次,就是在低功耗接口这块漏了一个“模块在时钟关闭请求拉高后,仍有未完成的事务需要向上游发起气泡请求”的场景,结果SoC低功耗验证阶段直接暴露了一个死锁bug,责任回溯时发现模块验证规格说明里压根没有这块内容,那叫一个被动。
用表格展示一下功能点拆解的产出形态:
| 功能域 | 设计功能 | 验证功能点编号 | 验证功能点描述 | 优先级 |
|---|---|---|---|---|
| 数据通路 | 发送FIFO深度8 | spi_ctrl_fifo_001 | FIFO空时写入数据可立即进入发送状态 | P0 |
| 数据通路 | 发送FIFO深度8 | spi_ctrl_fifo_002 | FIFO满时写操作保持请求并置位溢出标志 | P0 |
| 中断 | 支持发送完成中断 | spi_ctrl_irq_001 | 发送完成后中断拉高,读状态寄存器后自动清零 | P0 |
| 异常 | CS信号异常拉高 | spi_ctrl_err_001 | CS在传输中途拉高时,FIFO残留数据不产生有效数据上报 | P1 |
3.3 覆盖率模型定义
覆盖率模型是验证规格说明里把功能点“翻译”成可度量指标的关键环节。很多新人写功能覆盖率,就是对着代码里肉眼可见的分支和表达式瞎写covergroup,结果问起来,这个覆盖率到底证明哪个功能点验证过了,答不上来。正确的做法是:每个功能点,至少对应一个覆盖率模型项(coverpoint或cross),覆盖率模型项必须在验证规格说明里提前定义。
覆盖率的建法我习惯这样:先分功能域,再在功能域内定义覆盖点。每个覆盖点可以是数据值、状态组合、协议事件或者事件间的交叉。比如数据通路可以cover发送FIFO深度在不同水位时的值,中断域可以cover中断源的枚举值或者中断优先级的组合,CRC校验域可以cover多项式模式和种子值。
这块还要讲清楚一个概念:功能覆盖率验证的是“我们想测的行为有没有被刺激到”,代码覆盖率验证的是“代码里哪些行、哪些分支被执行过”。两者不可互相替代,但功能覆盖率在验证计划阶段就应该关注,代码覆盖率更多是回归后期的补漏手段。写验证规格说明时,两类覆盖率的目标都要量化,我常用的目标是功能覆盖率100%(按功能点逐一打勾),代码覆盖率行覆盖率90%以上、条件覆盖率85%以上,达不到就要评审是否需要补case。
另外还有断言覆盖率。断言是让验证规格说明“活起来”的关键手段,一条好的断言能把协议时序约束从文档文字变成仿真时自动检查的机器逻辑。在验证规格说明里列断言清单时,我会明确写出每条断言对应的时序或协议要求,比如“spi_cs_high_during_transfer”这个断言,就要求CS高电平期间SPI时钟不能发送有效数据。断言覆盖率最终目标我习惯定在95%以上,剩下的5%通常是重复性或者无法激励的死角,可以接受。
3.4 验证环境架构声明
验证环境架构这部分,核心是防止团队里每个人对testbench的理解不一致。UVM方法学虽然提供了标准的组件层次,但具体到每个模块,怎么搭、组件怎么划分、使用哪些VIP、计分板怎么比,还是会有很多细节差异。
一个通用的模块UVM环境应该包括以下几个标准组件:
- UVM测试用例层(test):定义测试场景、配置寄存器、启动sequence。
- UVM sequence层:生成激励序列,可以直接调底层driver接口。
- UVM driver层:把sequence层下来的事务转为接口信号时序。
- UVM monitor层:被动采样接口信号,转成事务对象。
- UVM reference model:模拟DUT行为,输出期望值。
- UVM scoreboard:比较reference model输出与DUT实际输出。
验证规格说明里要写清楚的是:每个组件对应到什么程度、有没有复用已有VIP、reference model从哪来(是C模型包装还是纯SV模型)、计分板的比较方式(实时比较还是事务级比较)等。
这里有一个我特别想强调的点:reference model只能由独立人员或独立工具生成,不能直接抄DUT的RTL逻辑。如果reference model和DUT由同一个人写,且思路完全一致,bug极容易同步复制,导致最后验证通过、实际上有逻辑缺陷。这一点我在规格说明里会直接写明“reference model必须由非DUT设计者实现或审核”,从流程上防呆。
3.5 收敛标准与回归策略
收敛标准没量化,验证结束就全靠团队“体感”,这在SoC项目里是致命的。模块验证阶段的风险和SoC集成阶段完全不在一个量级,模块验证漏掉的bug,集成阶段修起来成本翻倍。所以验证规格说明里必须有明确的收敛标准,我通常包含以下几条:
- 功能覆盖率:100%或接近100%,未覆盖点必须评审解释。
- 代码覆盖率:行覆盖≥90%,条件覆盖≥85%,状态机覆盖100%可达、90%以上覆盖。
- 断言覆盖率:≥95%。
- 回归结果:连续三轮全量回归零失败。
- bug收敛趋势:连续两周无新bug或严重级bug清零。
回归策略也要在验证规格说明里写清楚,包括什么时候做全量回归、什么时候可以只跑定向case、随机种子跑多少组、seed是不是固定(保证可重建性)、回归fail后的处理机制(先查环境问题还是DUT问题)等。
这里我明确说一下随机种子管理的看法:很多人喜欢每轮回归换新seed,觉得覆盖面大,但我更建议固定种子集合回归,另加一个小的随机seed探索池。原因是固定种子的回归失败可以精确重建逻辑,便于debug,而探索池的失败只记录现象,事后用固定seed补回。没有固定种子集的回归,bug环境一旦丢失,排查期长得让人头秃。
4. 实操:以SPI控制器为例写一版验证规格说明
4.1 模块背景与验证范围声明
空谈概念没意思,拿一个典型的中等复杂度模块——SPI控制器,演示一下从零怎么组织验证规格说明。假设这个SPI控制器挂在APB总线上,支持Mode 0/1/2/3四种模式,支持8到32位可变数据位宽,发送FIFO 16×32,接收FIFO 16×32,支持发送完成中断和接收FIFO溢出中断,支持CRC发送和接收校验,支持片选信号的软件控制模式。
验证范围声明我会这样写:本模块验证覆盖APB配置接口时序、SPI协议时序(包括四种模式)、FIFO数据通路、中断行为、CRC校验能力和片选控制功能。不覆盖以下内容:SPI外设芯片行为(由虚拟外设行为模型模拟)、APB总线协议本身(由APB VIP保证)、SoC级电源管理和时钟管理(不在模块级验证范围)。
为什么不覆盖APB总线协议本身,要特别说明原因:APB协议验证是总线VIP自带序列已经反复验证过的,模块验证阶段重复打APB时序不仅浪费时间,还容易造成覆盖率目标失焦。真正需要验证的是这个模块在APB协议下的正确性,也就是响应行为。VIP的机制保证了时序合法,目标转向DUT行为正确,这个边界不写清楚,新人就容易被VIP自带的APB测试用例带着跑偏。
4.2 功能点拆分实战
按照前面提到的拆解方法,把SPI控制器的功能点列出来。为了控制篇幅,这里只展示一部分重点功能域:
| 功能域 | 验证功能点编号 | 验证功能点描述 | 优先级 |
|---|---|---|---|
| 数据通路 | spi_ctrl_fifo_001 | 发送FIFO空时写入数据,SPI开始发送事务 | P0 |
| 数据通路 | spi_ctrl_fifo_002 | 发送FIFO满时继续写数据,写操作保持并置位溢出标志 | P0 |
| 数据通路 | spi_ctrl_fifo_003 | 接收FIFO满时继续接收数据,溢出标志置位,接收数据丢弃 | P0 |
| 协议时序 | spi_ctrl_mode_001 | Mode 0 模式下时钟极性CPOL=0,相位CPHA=0的采样时刻正确 | P0 |
| 协议时序 | spi_ctrl_mode_002 | Mode 3 模式下时钟极性CPOL=1,相位CPHA=1的采样时刻正确 | P0 |
| 协议时序 | spi_ctrl_len_001 | 8位数据位宽下,字节序按MSB先行发送 | P0 |
| 协议时序 | spi_ctrl_len_002 | 32位数据位宽下,连续四个字节按FIFO顺序发送 | P0 |
| 中断 | spi_ctrl_irq_001 | 发送完成后TX_EMPTY中断拉高,读IPSR寄存器后自动清零 | P0 |
| 中断 | spi_ctrl_irq_002 | 接收FIFO溢出时RX_OVF中断拉高,读IPSR后清零 | P1 |
| CRC | spi_ctrl_crc_001 | CRC使能时,每个接收数据自动计算并比较CRC值 | P1 |
| CRC | spi_ctrl_crc_002 | CRC校验失败时产生CRC_ERR中断,接收数据不更新有效标志 | P1 |
| 异常 | spi_ctrl_err_001 | CS在传输中途被软件拉高,当前字节传输完成后停止,FIFO残留数据不上报 | P0 |
每条功能点后面,至少要对应一个或几个验证场景。场景设计不是把功能点换个说法再说一遍,而是要具体到激励条件、DUT状态和期望行为。以spi_ctrl_err_001为例,场景可以设计为:先配置模块为Mode 0、8位数据位宽、软件控制CS模式;向发送FIFO连续写入32个字节并启动发送;仿真到传输第10个字节时软件拉高CS;检查DUT的行为是否满足功能点描述。
4.3 功能点到覆盖率模型的映射
功能点清单列好之后,下一步就是建立覆盖率模型。这里的核心原则是:覆盖率模型应该是功能点的“镜子”,而不是凭感觉另编一套。
在验证规格说明里,我会给出一个覆盖率映射关系表,并把主要的SystemVerilog覆盖组定义为文档附件或示例。比如:
covergroup spi_ctrl_cg @(posedge pclk); // FIFO水位覆盖 tx_fifo_level: coverpoint tx_fifo_status.fifo_level { bins empty = {0}; bins half_full = {[1:8]}; bins full = {16}; } // 模式覆盖 spi_mode: coverpoint cfg_reg.mode { bins mode0 = {0}; bins mode1 = {1}; bins mode2 = {2}; bins mode3 = {3}; } // 数据位宽覆盖 data_width: coverpoint cfg_reg.data_width { bins w8 = {8}; bins w16 = {16}; bins w24 = {24}; bins w32 = {32}; } // 中断事件覆盖 irq_event: coverpoint irq_status { bins tx_empty = {1}; bins rx_overflow = {2}; bins crc_error = {4}; } // 交叉覆盖:模式和位宽 mode_width_cross: cross spi_mode, data_width; endgroup这里有个容易掉进去的坑:覆盖组里的coverpoint值域,不能简简单单按代码行数或分支来写,而要按照验证规格说明的功能点来分割区间。比如tx_fifo_level里把0、1-8、16分别划分,是因为功能点明确规定了FIFO空、半满、满三种行为状态,分割粒度和功能点对齐,覆盖率结果反过来才能被用来解释功能点的验证完成度。我见过很多工程师把fifo_level分成0、1、2…16每一个值一个bins,覆盖率是漂亮了,但每个值到底对应哪个功能点,追问起来完全说不清。
覆盖率映射关系表是评审时的利器:
| 功能点编号 | 对应covergroup | 对应coverpoint/cross | 断言编号 |
|---|---|---|---|
| spi_ctrl_fifo_001 | spi_ctrl_cg | tx_fifo_level.bins empty | assert_fifo_tx_start |
| spi_ctrl_fifo_002 | spi_ctrl_cg | tx_fifo_level.bins full | assert_fifo_overflow |
| spi_ctrl_mode_001 | spi_ctrl_cg | spi_mode.bins mode0 | assert_spi_mode0_sampling |
| spi_ctrl_err_001 | spi_ctrl_cg | (异常功能点建议使用断言覆盖) | assert_cs_mid_transfer_hold |
4.4 收敛验收与回归策略示例
写到这里,验证规格说明最后要给出的,就是可执行的收敛验收标准。这部分我的写法通常是一张表加简单说明。
以这个SPI控制器模块为例,量化目标可以这样定:功能覆盖率100%(最终验收前必须全部达到,未达标的覆盖率点逐个评审、写明理由和风险);代码覆盖率行覆盖≥90%、条件覆盖≥85%;断言覆盖率100%(所有列出的断言至少被触发一次,且断言失败数为0);连续三轮全量回归零失败;bug收敛趋势上,P0/P1级bug清零,且连续两周没有新的P0/P1级bug出现。
回归策略上,固定seed集合48个,每轮回归全部跑;另有12个探索seed每轮轮换,用于发现非预期组合场景,探索seed的失败只记录现象,不阻塞回归,固定seed有失败则立即阻断合入并排查。测试用例的优先级分P0/P1/P2,P0和P1用例在每轮全量回归中运行,P2用例按模块改动风险评估后选择运行。
对于SPI控制器这类中等复杂度模块,我给的时间预估是:验证环境搭建两周、第一轮全量回归完成三周、覆盖率收敛到目标大约需要五到六周。如果功能点清单在两百个以上,时间预算要上调三到五成。这些估算写在验证规格说明里,是为了让项目计划和实际工作量对得上,避免模块验证被压缩到完全不合理的周期里。
5. 常见问题与排查技巧实录
5.1 功能点与覆盖率对不上
问题现象:验证都收尾了,功能覆盖率99%,但评审时对照功能点清单,发现某几个关键功能点根本没有对应的覆盖率模型项。
这类问题的根源,是覆盖率模型设计时脱离了功能点清单。很多团队建覆盖模型时,build A和build B各建各的,A负责整理功能点,B负责写covergroup,两个人中间缺少映射评审。解法无非是两条,缺一不可:第一个,验证规格说明里必须内置一张功能点到覆盖率模型项的映射表,评审时逐条核对;第二个,covergroup的实例化和功能点编号强关联,不允许出现没有编号来源的覆盖点。
我见过有些项目组用脚本工具,从验证规格说明的表格里自动生成covergroup模板,这种做法值得推荐。虽然初期花半天时间搭工具,但之后的覆盖率管理和文档同步轻松很多,功能点变更时也不会出现遗留的孤儿覆盖点。
5.2 把验证规格说明写成了环境设计文档
问题现象:规格说明里通篇都是UVM组件的类名、继承关系、端口连接,功能点部分反而只有寥寥几行。
这个问题特别容易出现在验证工程师自己动手写规格说明的场景里,因为写环境设计最顺手,写功能点最费脑子。但验证规格说明的读者,不只是写验证代码的人,还包括设计工程师、架构师、项目经理。如果一打开文档全是class定义和factory机制,别人根本读不下去,评审自然流于形式,本该提的问题全被文档噪音盖住了。
我的处理方式是:验证规格说明里只写环境的顶层架构图和组件职责,具体到UVM类的继承树和端口连接,单独放在环境设计文档里。规格说明的环境部分,控制在“让一个没写过这个模块的人看完后,知道环境哪里可以复用、哪里是新写的、计分板怎么比”的程度就够。环境设计细节越靠后越好,让功能点成为文档的主干。
5.3 收敛标准不敢量化
问题现象:验证规格说明里写“覆盖率尽可能高”“回归尽量稳定”,唯独没有数字。
这种情况基本都出在工程师怕写数字被打脸的心态上——写了95%,到时候做不到怎么办。我的经验是,数字定的不合理可以通过评审修正,但没有数字的规格说明等于没有约定,最后验收全靠拉扯。
建议的量化思路是分梯度:第一版先按行业通用水平拍,行覆盖90%、条件覆盖85%、断言覆盖95%起步;然后根据模块复杂度评审调整。如果模块里有大量状态机,可以把状态机覆盖率单独拉出来定目标;如果模块是纯组合逻辑,行覆盖指标适当降低、条件覆盖提高。目标要由验证工程师、设计工程师和架构师三方在评审会上共同确认签字,而不是自己默默定一个。
5.4 需求变更后规格说明没跟上
问题现象:设计规格加了一个FIFO深度可配置的特性,RTL代码都改了,验证规格说明里的功能点还是旧的,新特性完全没覆盖。
需求变更管理是验证工程里最不性感但最要命的部分之一。我的习惯是:每次SoC架构或设计规格变更时,验证负责人必须在一个工作日内评估此变更对验证规格说明的影响,给出“无影响、影响较小、影响较大”三个档位的判断,并在规格说明里同步记录变更log。涉及功能点增删时,在文档里使用修订记录表格,每个功能点都标注了创建版本和最近修订版本。
还有一条我自己非常坚持的做法:验证规格说明里的功能点编号一旦发布,不能复用。即使某个功能点后来被删了,也只是标记为废弃,不会把编号转给另一个功能点使用。这个习惯在追查历史问题时非常有用,不然别到了项目复盘的时候,同一个编号指代过三个不同功能点,所有的历史记录全部失效。
5.5 评审时机与评审对象选择
评审验证规格说明的时机,最理想的是模块RTL代码冻结前两周。太早评,设计还没稳定,改动频繁,评审意见白记;太晚评,验证环境都在写了,规格说明评审意见已经无法指导环境搭建设计。
评审对象上,我的建议是必须邀请三类人:第一类是设计工程师,他们负责核对功能点是否和设计规格一致;第二类是验证同事或验证负责人,负责核对覆盖率策略、环境架构和收敛标准是否合理;第三类是SoC集成验证负责人,负责核对验证范围的边界划分是否清晰——要不要模块验证阶段把某个场景测透,还是留给集成验证阶段再补。
评审会议最好开两轮:第一轮看覆盖范围是否完整,第二轮看量化指标和计划是否合理。第一轮和第二轮之间留出三天给所有评审人消化文档,不要想着一次性评完。规格说明是实打实用来指导工作的文档,评审质量直接决定模块验证的返工率,这块花的功夫永远值得。
写在最后,说几个自己的习惯
写文档这件事,技术含量看着没有写代码高,但实际里面对项目成败的影响力极大。我这么多年带下来,见过太多验证团队把文档当作“给大家交代”的形式任务,验证规格说明写完就压箱底,实际验证过程全凭脑子记,最后出了问题又互相甩锅,说“文档里没写清楚”。文档不是形式,文档是验证工程的地基。地基歪了,上面盖多少层UVM环境都白搭。
我个人在项目里坚持三个小习惯,随手分享出来供参考。第一,验证规格说明初稿的完成时间和验证环境代码启动时间严格挂钩,环境搭建前必须过一遍规格说明,哪怕边写边改,也不能跳步。第二,功能点、场景和覆盖率模型的映射表,每周更新一次,并同步给设计工程师和集成验证负责人,确保两边对验证进展的理解一致。第三,规格说明里必须留一个风险与依赖章节,把暂时无法验证或需要其他团队协作的事项写下来,经常去看,别让这些暗雷在验收时突然爆炸。
模块验证规格说明这件事,不存在“标准答案”,只有不断被项目打磨之后沉淀下来的方案。希望这篇文章的框架和实操示例能帮正在做SoC模块验证的同事少走几步弯路。下次动手之前,先把规格说明写透,后面很多人都会感谢你。