ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

IC验证全流程指南:从DUT分析到回归交付实战

IC验证全流程指南:从DUT分析到回归交付实战 项目标题里那句从DUT到回归交付看着简单实际上把验证工程师一整条工作链都点出来了。我见过不少刚入行的同事一上来就急着写sequence、搭UVM环境结果跑到覆盖率阶段才发现功能点拆错了回头改验证计划整个环境跟着大改白白浪费两三个星期。这篇文章就按我自己带项目的习惯把IC验证全流程捋一遍——从拿到DUT的第一天到最终回归通过、交付签核每个阶段该做什么、为什么这么做、有哪些坑一次讲清楚。适合正在入门验证的工程师也适合刚接手新项目的朋友做流程参照。1. 先于代码的工程准备DUT懂多少验证就能少走多少弯路很多新人觉得验证嘛不就是拿着spec写testbench然后灌激励看结果。这话说对了一半。另一半往往是没看懂DUT就动手后面全在给自己挖坑。1.1 DUT规格与接口的硬信息核对拿到一个新DUT最忌讳的事情就是直接看RTL代码。我自己的习惯是先收集五类材料设计规格书Design Spec这是“宪法”几乎所有验证决策都以它为准接口时序图Interface Timing Diagram特别是和上下游模块握手的信号寄存器描述文档Register Spec哪怕做模块级验证也要知道CSR读写规则架构文档或微架构说明用于理解数据通路已有的设计验证用例或参考模型如果有的话在这些材料里我最先翻的是接口时序图。因为验证环境里最难改的东西不是scoreboard而是interface和driver的时序适配。你interface的采样沿、复位方式、handshake协议搞错了后面所有用例都跑不对而且这种错误往往藏得很深——不是直接报错而是“看起来过了实际上结果错”。比如之前做一个DMA控制器规格里明确写了一次burst传输结束后ready信号要拉高至少两个周期。结果RTL实现只拉高了一个周期驱动侧按两个周期去等所有用例都超时。后来翻代码才发现是接口理解不一致——但这个问题通过读RTL其实一眼就能看出来完全不用等到跑用例。所以我的建议是拿到DUT之后先画一张模块级框图。不用画得很复杂就标清楚有哪些输入输出端口、内部大概分了几个子模块、数据从哪个端口进来、从哪个端口出去、中间经过哪些缓存或状态机这张图就是你整个验证环境的“导航地图”。1.2 功能点拆分把“文档描述”变成“可验证项”规格文档里一句话往往对应着验证环境里的一个或多个功能点。比如规格里写“支持突发传输模式”这背后至少要拆成单次突发长度长度范围是多少边界值1、最大值、非法值分别是什么表现地址对齐突发起始地址是否要求对齐不对齐时的行为是error还是自动修正与握手信号的配合请求发出后等待多久必须收到grant超时是否有中断与普通传输的切换突发传输中间能否被总线抢占抢占后能否恢复这种拆分有个非常实用的做法用表格把功能点编号每个功能点对应一个验证场景、一个或几个测试用例、一个覆盖率bin。表格是长这样的功能点编号功能描述验证场景对应用例覆盖率bin优先级FP_001CSR默认值检查复位后读取全部寄存器tc_csr_reset_value寄存器默认值P0FP_002普通单次写入写地址命中RAM区间tc_write_single写操作类型P0FP_003突发传输读8拍增量bursttc_read_burst_inc8burst长度/类型P1这个表就是之后验证计划Test Plan的雏形。不要觉得这是文档工作浪费时间真正到了覆盖率收敛阶段你需要的正是这张表——覆盖率没到100%的时候你一看表就知道还差哪个功能点没验证而不是对着几千行测试日志发呆。2. 验证计划把“测什么、怎么测、算达标”钉死在纸面上验证计划是整个验证阶段的“施工图”。我见过有的团队不做验证计划直接搭环境结果到了项目后期验证经理问“覆盖率90%能不能sign off”没人能回答——因为没有定义什么叫“测完了”。2.1 验证计划表的基本要素与写法验证计划不用写得很文学化但要包含四个关键要素第一是验证范围。明确哪些功能点属于本阶段验证哪些属于其他层级比如IP级、子系统级、SoC级验证。很多模块级验证遇到的问题本质上是把SoC级的场景硬塞到了IP级环境里导致各种不匹配。第二是环境结构。标明用哪些组件来验证哪些功能点。比如某个功能点需要通过配置寄存器来触发那就要有寄存器模型某个功能点关注DUT对外握手时序那就要有专门的monitor来采样接口信号。没写到计划里的组件后面大概率开始影响进度。第三是测试用例清单。不需要把每个用例的时序都写出来但至少要写明用例目的、主要激励、关键观测点。这一步是把“功能点”翻译成“怎么测”的过程。第四是覆盖率目标。一般代码覆盖率行覆盖率、跳变覆盖率要求90%-100%功能覆盖率要求100%或至少达到项目定下的阈值。但比数字更重要的是“覆盖率的定义”。比如总线协议中的反向操作要不要覆盖中断嵌套场景怎么定义bin——这些在计划阶段不定义清楚到收敛阶段就会开始扯皮。2.2 优先级排序P0/P1/P2的划分逻辑我习惯把功能点分成三个优先级P0不验证就没法交付的功能。比如寄存器读写、数据通路主流程、复位行为。这些用例在环境搭好之后第一时间就要能跑通。P1会影响系统稳定性的重要场景。比如FIFO满空、总线仲裁、中断响应、错误恢复。这类用例不需要第一天就通但必须在功能覆盖率收敛前全绿。P2边界和异常场景。比如非法地址访问、超时处理、保留位写入后的行为。这类用例通常数量多但单条执行时间短可以放在回归集里慢慢跑。优先级划分不光是为了排序人的工作节奏更是为了做“验收判定”——如果时间不够P2可以降级为已知风险在评审会上讨论但P0和P1必须全部通过才有资格谈交付。这个逻辑如果不提前定好最后验收的时候就会有人问“这几十个P2用例没过怎么办”你没法回答。3. 验证环境搭建从空目录到可跑通一条用例验证平台testbench是验证工程师的工地。UVM是主流的搭建方式但比UVM更核心的是架构设计思路。我在这一节把验证环境拆开讲清楚。3.1 TB的分层结构与复用原则一个典型的UVM验证平台分这么几层测试层包含所有testcase每个testcase定义激励的生成方式和使用场景场景层virtual sequence负责组合多个sequence实现复杂业务场景功能层agent、driver、monitor、sequencer完成具体协议收发数据层transaction、sequence item定义激励的数据结构检查层scoreboard、reference model、coverage collector完成数据比对与覆盖率采样这份分层有什么用最直接的好处是复用。一个写好的agent在IP级验证里能用来做模块级定向测试拿到SoC环境里还能用来做子系统集成测试——只要接口时钟复位一致内部组件基本不用改。我自己就吃过不分层的亏早期搭环境时把driver和scoreboard的代码耦合在一起换个场景就得整个重写后来改成标准分层式维护成本直线下降。复用的另一个层面是跨项目复用。如果你做的所有模块都用同一种总线接口比如APB、AXI那么APB agent、AXI agent这些基础设施第一次搭好之后后面几乎可以直接复制。这也是为什么很多公司会积累一套“公共验证IP库”——它的价值远大于任何一个单独的用例。3.2 激励、监测与自动比对三者缺一不可环境搭建的核心是三个支柱激励生成、信号监测、结果比对。激励生成是driver和sequence的工作。sequence负责定义“发什么”driver负责定义“怎么发”。这个分离的意义在于同一个driver可以跑不同的sequence也就是不同的测试场景同一个sequence也可以挂到不同driver上适配不同接口。从这个角度来说driver是执行者sequence是编排者。信号监测是monitor的工作。monitor要做的不仅仅是采样信号还要把采到的时序转换成有意义的transaction送给scoreboard参考模型。很多新手在这里有个误区monitor采信号时只关心数据对不对忽略了时序信息。总线上一次传输是1拍完成还是2拍完成在功能级验证里可能不影响最终结果但在性能验证里是核心指标。所以monitor的采样一定要带时间戳。结果比对是整个环境里最需要花心思的地方。常见的方式有这么几种比对方式原理适用场景优缺点数据镜像比对参考模型生成期望数据与DUT输出逐拍比对数据通路类模块FIFO、编解码器精确但对参考模型依赖大协议检查用断言assertion检查时序是否合法总线协议模块能抓时序问题不能验证数据正确性自校验比对用例里通过接口回读DUT内部状态判断是否符合预期寄存器配置类、控制类模块执行简单但容易漏场景计分板比对通过记分板依序匹配激励与响应支持乱序乱序执行类模块灵活但实现复杂3.3 寄存器模型与后门访问模块级验证里有一件事容易被忽略但也最容易出效率问题寄存器配置。如果所有寄存器的配置都通过前门front door总线读写一个配置动辄几百个时钟周期一个用例配置几十个寄存器时间全耗在等待上了。解决办法是引入寄存器模型reg model配合后门访问back door。后门的核心思路是不通过协议发送读写命令而是直接通过层次化路径去修改DUT内部的寄存器值。速度快而且不会引入协议干扰。但要注意后门访问适合做“环境配置”和“状态设置”不适合做“功能验证”——功能验证必须走前门总线否则测不出真实路径上可能存在的时序问题。4. 跑用例要看门道冒烟、定向与约束随机环境搭好之后第一个目标是“跑通一条用例”。但“跑通”和“真正在验证”之间还有很长的路。4.1 冒烟测试先把环境自身调对第一次跑用例目的不是验证DUT功能而是验证TB本身。我把冒烟测试的检查点列一下复位能不能正确释放复位结束后各个信号是不是都在预期状态DUT时钟是否正常翻转clk在monitor里能否被正确识别driver能否成功发送第一笔transaction这步错了往往是interface时序不匹配数据能否从DUT返回并送到scoreboard即使结果不对也要先看数据流有没有通是否有对比功能正常工作哪怕比对结果报错也说明比对机制在工作冒烟测试有一个很实用的经验第一个用例一定要写最简单的。比如“写一个寄存器读回来看看值对不对”。不要一上来就写几百拍的复杂场景——如果连最基础的读写都跑不通复杂场景报错了你根本定位不了是TB的问题还是RTL的问题。4.2 约束随机验证覆盖率驱动而非盲目撒数环境冒烟通过之后就进入大规模验证阶段。现在的验证主流是约束随机constrained random但很多人的随机是“假随机”——约束写得极其宽泛种子一换生成的transaction千奇百怪但覆盖不到目标场景。约束随机最关键的思路是用覆盖率来指导随机。也就是先写好覆盖率模型定义哪些场景是必须覆盖的设定约束条件使激励有倾向性地覆盖目标场景运行多组种子收集覆盖率数据分析未覆盖的bin调整约束或增加定向用例举个例子验证一个AXI总线的从机接口我们最关心的是burst类型FIXED/INCR/WRAP和burst长度1/4/8/16拍的组合以及写数据通道的对齐状态。如果不做约束仿真器可能连续几千个周期都只生成INCR、长度8的传输其他组合碰都没碰到。这时候就需要给burst类型和burst长度添加分布约束或者用列表约束保证每个组合至少出现一次。约束随机的一个好处是能跑到人工很难想到的边界组合但它的坏处是排障困难。一个随机用例失败了第一反应不是去修代码而是先保存种子、复现现场再用波形工具看触发条件。为了减少这种排障成本我会在用例里增加“场景标识打印”——每次测试开始前把本次的随机配置和种子打到日志里下次一旦失败就可以直接复跑。5. 覆盖率收敛验证从量到质的关卡覆盖率是整个验证项目中最难啃的骨头。很多项目进度延迟不是输在代码能力上而是输在覆盖率收敛上。5.1 代码覆盖率与功能覆盖率的差异使用先说清楚两种覆盖率的区别代码覆盖率RTL代码中被执行到的行、分支、条件、状态机状态的比例。它回答的问题是“RTL代码里有没有没跑到的逻辑”。代码覆盖率偏低说明有功能点漏测需要补用例。功能覆盖率由验证工程师定义的、用于衡量设计规格中的功能点是否被充分验证的指标。它回答的问题是“我们关心的功能场景是否被覆盖到了”。代码覆盖率是辅助指标功能覆盖率才是核心指标。但当两者出现不一致时比如功能覆盖率95%但代码覆盖率只有70%说明验证计划本身有疏漏——有功能点没被拆进来。这种情况必须回到验证计划重新补充功能点而不是通过“多跑几个随机种子”来强行刷代码覆盖率。5.2 覆盖率收敛的三板斧看缺口、锁场景、补定向覆盖率收敛阶段我通常按这个顺序操作第一板斧是打开覆盖率报告看未覆盖的bin。先把所有未覆盖的bin按模块和类型分组找出覆盖率最低的模块。这个模块往往就是风险最集中的地方先集中攻破。第二板斧是分析未覆盖bin背后的约束问题。很多bin覆盖不到不是功能没实现而是约束条件把激励限制死了。比如定义了一个长度为16拍的burst但序列里随机化的长度范围写的是1到8——那不是DUT不支持而是激励压根没生成过这种场景。这种问题调整约束就行不需要增加用例。第三板斧是补定向用例。当约束调整也无法覆盖的bin就需要人工写定向用例精确构造场景。这类用例要“精”不要“多”一个定向用例能覆盖多个bin是最好。覆盖率收敛阶段最容易出现的一个幻觉是“覆盖率已经100%了那验证就结束了”。这里我想提醒两点一是功能覆盖率高不代表验证充分还要看覆盖率模型建得对不对——如果bin定义本身没体现规格要求那覆盖率再高也没意义。二是交叉覆盖率cross coverage能比单点覆盖率发现更多问题。比如“写操作”和“FIFO满”这两个事件单独看可能都覆盖到了但“FIFO满时写入”这个组合场景可能从来没触发过——用cross bin就能把这个漏掉的场景抓出来。6. 回归测试与交付验证环境从“可用”到“可信”回归测试在验证流程里是最后一道防线但它做得不好时反而会成为最消耗时间的环节。有些项目回归一周跑了上千个用例结果版本迭代后RTL改动只影响了一个小模块却带着全套回归跑了一遍纯粹浪费机器和人力。6.1 回归集的设计与种子管理回归集不是“所有用例全跑一遍”那么简单。一个有效的回归集应该包含全量功能用例所有P0和P1用例必须进回归随机种子池对约束随机用例跑多组种子增加场景覆盖的随机性针对性回归当某个模块或某个接口改动后只跑与改动相关的用例快速验证sanative回归每晚或每次RTL更新后跑的全量冒烟用最小用例集保证环境没有崩溃种子管理这件事很多团队没做好。随机测试如果不固定种子每次跑结果都可能不一样——一旦某个种子在99%的覆盖率下出了问题你想复现现场却已经找不回当时的随机序列了。所以我在所有随机用例里都强制打印种子号并且把种子作为回归日志的一部分归档。将来出了问题可以随时按种子复现。6.2 失败分析与回归收敛节奏回归失败了第一件事并不是打开波形看RTL哪里错了而是先分类。我一般把失败原因分成四类TB/环境问题测试用例本身有bug或参考模型与RTL预期不一致——这类问题占一半以上DUT bugRTL功能实现和规格不符这是真正要找的bug用例自身问题用例约束或检查逻辑写错了比如期望值设得不合理随机种子偶发问题单一种子下出现的时序临界问题复跑同种子才能定位分完类之后再决定是修TB还是报bug给设计工程师。这个过程最忌讳的是看到一个失败就冲上去改代码改完再跑又挂了另一个用例——那样会把时间全部耗在无休止的“打地鼠”里。回归的收敛节奏也有讲究。项目后期设计工程师每天都在改代码回归集不可能每次改完都全量跑一遍。我习惯的做法是RTL日常迭代跑“快速回归”几十个核心用例半小时内跑完每周一次全量回归几百个用例跑一个晚上或一个周末临近交付连续跑三到五天全量回归每天换不同随机种子确保稳定性这样既保证了迭代效率也守住了稳定性底线。7. 交付前的最后一道关卡checklist与技术评审回归全绿不代表可以交付。在正式签核之前我这边还有一张检查清单要逐项确认功能覆盖率是否达到目标值未覆盖bin是否有明确理由代码覆盖率是否达标没有覆盖到的逻辑是否经过确认所有已知DUT bug是否有workaround或已修复回归日志是否有异常报错fatal、error级别的打印哪怕用例显示pass验证环境是否可回放代码版本、种子、仿真参数是否完整归档是否输出验证报告包括验证范围、结果、风险点这些检查项里最容易忽略的是“验证环境是否可回放”。很多项目交付半年后发现了新问题想回到当时的场景复现结果发现当时的UVM环境版本和现在完全对不上——那种无力感经历过一次就永远不会再忘。现在我做任何交付都会连带记录环境版本、工具版本和RTL版本。交付前通常还会做一次技术评审不光是验证内部评而是拉着设计工程师、架构工程师一起评。评审重点不是看覆盖率数字漂亮不漂亮而是看验证方案的“死角”在哪里——有没有哪些规格描述被完全忽略有没有哪些断言模型可能和RTL实现不一致。设计工程师最了解RTL实现的“习惯性偷懒”在哪里他们往往能指出那些测试用例没有覆盖的灰色地带。8. 带过多个项目之后的一些真实感受一路写下来似乎是在讲流程但真正做了多年验证之后我最大的感受是验证流程不是靠制度推着走的而是靠每一次复盘积累出来的。我个人体会最深的一点是验证计划永远是活的。最开始写好的计划不可能覆盖项目后期出现的所有情况。当你在调试一个用例时发现了规格里没写明的新行为当设计工程师告诉你“这个场景我们实现上和规格有点出入”第一反应不要是沮丧而是要回到验证计划里去更新它。计划不是为了好看写的它是为了让整个团队对“验证到什么程度才算完”有共识。另一个经验是新人最容易把时间花在写漂亮的TB组件上而忽略了“验证的核心价值是发现bug”。我见过太多人花一周时间优化一个scoreboard的比对性能却很少花时间思考“还有哪些场景没测到”。验证工作的成败不在于你写了多少行代码、做了多漂亮的封装而在于你有没有把DUT真正逼到极限、找到足够多的bug。回归交付只是一个节点真正有价值的是那些跑出来的问题。最后分享一个小习惯每完成一个验证任务我会花半小时写一份“经验手记”记下这次遇到的最难的问题、最耗时的坑、最后是怎么解决的。几年下来这份手记比任何工具书都有用——因为它记录的不是理论是你在真实项目里一点点趟出来的路。IC验证这行流程是骨架经验才是血肉两者都到位了交付才能放心。
返回列表