ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Synopsys工具链的数字芯片验证全流程:VCS、Verdi与UVM实战总结

基于Synopsys工具链的数字芯片验证全流程:VCS、Verdi与UVM实战总结 做数字IC验证这些年Synopsys家的工具链基本是天天都在打交道。VCS跑仿真、Verdi看波形、UVM搭验证环境这三样东西用熟了大部分前端验证工作都能顺顺利利推进。最近正好把手头一个项目收尾趁脑子里的细节还热乎我把自己这套基于Synopsys EDA工具做验证的完整流程、踩过的坑、攒下来的经验系统梳理了一遍。这篇总结既适合理性验证的新人了解全貌也适合做了一段时间验证、想优化自己工作流的朋友对照参考。坦白说很多刚入行的同学对验证的理解停留在写几个testbench、跑一下仿真看波形对不对的层面但实际上在一个真实的芯片项目里验证的复杂度往往超过设计本身。设计是把规格变成RTL代码验证则要证明这些RTL代码在所有可能的情况下都符合规格。这句话说起来轻巧做起来就是成千上万个testcase、几百万行仿真代码、没日没夜的覆盖率分析和debug。Synopsys这套工具链给我们提供的正是把这套庞大工程高效组织起来的手段。我不想把这篇文章写成工具手册的搬运工而是想从一个实际做项目的验证工程师视角把为什么要这么干工具是怎么配合的哪里容易出问题讲清楚。里面所有的命令、代码片段、排查思路都是我在真实项目里用过、验证过的东西。1. 验证在芯片开发中的定位为什么这一环最耗人很多非IC行业的朋友听到验证两个字第一反应是测试觉得就是跑跑仿真看看有没有bug。实际上芯片验证的工作量在整颗芯片开发流程里占比相当惊人。一个成熟的SoC项目验证工程师和设计工程师的人数比例经常达到2:1甚至3:1项目周期里验证占掉的时间也超过一半。原因很简单芯片流片一次的成本动辄几百万人民币起步更别说流片失败导致的上市时间延迟。验证的目标就是在流片之前用软件仿真的方式把芯片的bug尽量都揪出来。芯片开发的常规流程大致是这样的从产品规格定义Spec开始架构师做系统架构设计前端设计工程师把架构翻译成RTL代码验证工程师围绕RTL搭建验证环境不断跑仿真、找bug、协助设计修改代码等到验证收敛功能覆盖率和代码覆盖率达标后再进入逻辑综合、DFT插入、布局布线、时序收敛最后才出带tapeout。如果验证环节没做好带着bug去流片拿回来的样片可能根本无法点亮问题定位和改版成本都是灾难级的。验证工作往前移是整个行业这些年的趋势。以前可能是RTL写完了再开始搭验证环境现在规格一出来验证工程师就要参与评审提前规划验证点甚至要做可验证性设计DFV的建议。越早介入越能避免后期推倒重来。我在实际项目中感受最深的是验证不只是找bug这么简单它还承担着规格澄清的作用。当你为了写一个断言assertion去仔细抠协议细节时经常会发现规格书写得模棱两可。这时候找架构师确认比设计代码写完了才发现理解不一致要省事太多了。2. Synopsys验证工具链全景VCS、Verdi与UVM的关系在EDA领域Synopsys、Cadence、Siemens EDA原Mentor是三巨头但做数字验证的项目里Synopsys家的工具组合出镜率确实最高。核心就是VCS和Verdi这一对搭档再加上UVM方法学。2.1 VCS那台跑的飞快的仿真引擎VCS是Synopsys的逻辑仿真器Logic Simulator全称是Verilog Compiler Simulator。它做的事情本质上就是把RTL代码、验证环境代码、IP模型等编译成一个可执行的仿真程序simv然后在你的服务器上运行这个仿真程序模拟芯片在真实电路中的行为。VCS支持Verilog、SystemVerilog、VHDL还集成了对UVM库的原生支持。我最早用VCS的时候还经历过一段旧版本对SystemVerilog支持不完整的时期动不动就要加一堆编译选项绕过bug但现在的VCS版本对SV和UVM的支持已经非常成熟了。VCS最核心的价值是仿真性能。一家公司里可能有几十个验证工程师同时跑回归regression每个人每天跑几十上百个testcase如果仿真器性能不行整个项目的验证收敛速度都会被拖垮。VCS在编译优化和仿真调度上做得确实不错同样是跑一个UVM环境VCS的仿真速度通常比开源仿真器快上好几倍。2.2 Verdi看了就懂的波形调试利器光有仿真结果还不够出了问题你得能看懂到底发生了什么。Verdi是Synopsys的调试平台Debug Platform它最核心的强项是波形分析。在Verdi里打开FSDB波形你能看到信号的跳变、总线的数据、状态机的状态切换还能自动追踪信号的驱动源实现从结果反推原因。Verdi有个功能我特别喜欢叫nWave它把波形显示和RTL源码联动起来你在波形里点一个信号它直接帮你跳转到RTL里面这个信号的赋值语句然后再从赋值语句跳转回波形。这种来回切换的效率比对着波形文件和代码两头找高太多了。另一个实用功能是结构树Structure Tree和原理图Schematic自动生成。当你搞不清楚RTL内部模块怎么连接、数据流怎么走的时候Verdi能帮你生成模块之间的互联关系图甚至能找到某个信号是被哪些信号驱动的。这套调试体验用过就回不去了。2.3 UVM验证环境的标准骨架UVMUniversal Verification Methodology是一个基于SystemVerilog的验证方法学标准库。它帮我们把验证环境里的公共组件driver、monitor、scoreboard等抽象成标准化的类并提供了一套成熟的机制来管理这些组件的构建、连接和运行。为什么验证行业最后统一到了UVM最核心的原因是可复用性和标准化。早期的验证环境每个工程师写得都不一样有的人用Verilog写testbench有的人用SystemVerilog但组织方式五花八门。项目之间想复用验证环境非常困难人员流动时交接成本也高。UVM把验证环境的标准骨架定下来了新人上手有章可循不同的IP之间、不同项目之间验证环境的组织结构是一致的复用和继承都容易得多。Synopsys的VCS对UVM的支持方式也很友好。你不需要手动编译UVM库源码直接用-uvm编译选项VCS会帮你链接对应版本的UVM库省去了很多环境配置上的麻烦。3. 从零搭一个UVM验证环境核心机制与设计思路很多UVM初学者最大的困惑是类那么多机制那么复杂我到底该从哪开始我自己的经验是不要试图一开始就搞懂UVM的所有类而是先理解UVM验证环境的长相再逐个理解它的几个核心机制。3.1 UVM环境的基本长相一个标准的UVM验证环境围绕着待测设计DUTDesign Under Test搭建核心组件包括interface连接验证环境和DUT的信号通道通常把同类型的信号封装在一起。driver把sequence产生的transaction激励事务驱动到DUT的接口上比如向总线发起一次读写操作。monitor从DUT接口上采集信号把信号还原成transaction并发送给参考模型或scoreboard。sequencer负责管理sequence的执行把sequence产生的transaction一个个分发给driver。agent把driver、monitor、sequencer封装在一起的容器一个agent对应一类接口协议。env把所有agent、scoreboard、reference model等组件对接起来的上层容器。scoreboard验证的核心判官比较DUT的实际输出和参考模型或时序检查的期望输出是否一致。reference model用高级语言SystemVerilog或者C/C模型实现的DUT行为的参考实现。test验证环境的顶层入口每个testcase通过创建不同的sequence、或配置不同的约束来决定跑什么场景。testbench顶层一个普通的SystemVerilog module在里面例化DUT、连接interface、启动UVM环境调用run_test。很多初学者画UVM结构图的时候总觉得很抽象其实类比一下就很好理解driver就像是一个信号操作员把指令翻译成具体的引脚电平monitor是一个观察员记录引脚上实际发生了什么scoreboard是一个裁判对照参考模型判断DUT的行为对不对testcase就是剧本决定了整个环境要演的剧情。3.2 Factory机制验证环境灵活性的基石UVM的factory机制工厂机制是整个UVM灵活性的关键。它让你可以用一个高层次的类去注册一种组件类型然后在运行时通过字符串名字来创建这个类型的实例并且可以在不修改原代码的情况下用另一个类**覆盖override**原始类。这个机制在实战中太有用了。比如你在某个testcase里想改一下driver的行为——正常情况下driver发完一笔读请求要等待固定延迟但你有个场景需要测试DUT在背靠背请求下的表现。用传统方法你得写一个新driver类然后手动去修改env的代码有了factory机制和override特性你只需要在testcase的build_phase里用一句uvm_resource_db或者直接调用set_type_override_by_type就能让整个环境自动使用你的新driverenv代码一行不用改。3.3 Sequence机制激励的产生者在UVM里产生激励的任务由sequence来承担。sequence就像一个剧本它定义了要发送哪些transaction按什么顺序发发送间隔是多少。一个简单的sequence可能只发送一次读写操作一个复杂的sequence可能包含了成千上万条命令甚至会在发送过程中根据DUT的反馈动态调整下一步操作。我在实际项目中用过的最复杂的sequence是CPU子系统验证里的一个启动流程sequence它模拟固件执行过程从取第一条指令到完成DDR初始化中间包含了几百个步骤每个步骤都要检查状态寄存器、等待中断、再发起下一步操作。这种复杂sequence如果不用UVM的sequence机制用传统testbench写起来会非常痛苦。3.4 Config机制让参数传递变得清晰可控UVM的config机制通常用uvm_config_db实现用于在验证环境的不同层次之间传递配置参数。比如你想让某个agent的driver运行在只读模式或者想让scoreboard开启某种检查功能都可以通过uvm_config_db在testcase里下发配置。这里有一个我从老工程师那里学来的经验配置尽量在build_phase里传递。因为build_phase的执行顺序是自上而下的父组件先build子组件后build所以父组件把参数set进config_db之后子组件在build_phase里get这个参数一定来得及。如果放在其它phase里经常会出现时序上的坑。4. 实操跑通第一个UVM测试用例的完整流程理论说再多不如跑一个真实例子。我用一个最简单的同步FIFO验证来演示整条链路的操作这里的思路可以平移到任何模块验证上。4.1 准备文件假设DUT是一个同步FIFO深度16数据位宽8位。验证环境文件目录大致如下fifo_uvm/ ├── rtl/ │ └── sync_fifo.v ├── tb/ │ ├── fifo_if.sv │ ├── fifo_test_pkg.sv │ └── top_tb.sv └── sim/fifo_if.sv定义接口信号fifo_test_pkg.sv包里包含所有UVM组件driver、monitor、agent、env、scoreboard、testcase等top_tb.sv是仿真顶层负责例化DUT、连接interface、启动仿真。4.2 编译与仿真进入sim目录用VCS编译整个工程vcs -sverilog -debug_accessall -uvm \ -f filelist.f \ -o simv \ -l compile.log这里简单解释一下几个选项-sverilog启用SystemVerilog语法支持。-debug_accessall开启全部调试能力后面才能用Verdi看FSDB波形。-uvm告诉VCS链接UVM库。-f filelist.f指定文件列表把RTL和TB文件都写进去。-o simv指定编译生成的可执行文件名。-l compile.log保存编译日志。编译成功后会生成simv这个可执行文件然后运行仿真./simv UVM_TESTNAMEfifo_write_read_test UVM_VERBOSITYUVM_MEDIUM -l run.logUVM_TESTNAME指定要运行的testcase类名UVM_VERBOSITY控制UVM打印信息的详细程度。仿真结束后打开run.log检查是否有报错有报错就用Verdi打开波形定位。4.3 波形生成与调试在testbench顶层里加上波形dump的语句才能在仿真过程中生成FSDB波形文件。通常可以写一个task来控制波形开关initial begin $fsdbDumpfile(fifo_tb.fsdb); $fsdbDumpvars(0, top_tb, all); end仿真结束后生成fifo_tb.fsdb然后启动Verdiverdi -f filelist.f -ssf fifo_tb.fsdb 打开波形后选中需要观察的信号添加到波形窗口就可以开始分析了。Verdi支持按信号名快速搜索、按跳变沿定位、按时间范围缩放还有强大的Add Signal to Waveform交互方式熟练之后效率很高。4.4 Phase机制UVM环境运行的节拍跑UVM仿真时你会发现一件奇怪的事环境里的代码不是从上到下顺序执行的而是被UVM的phase机制调度到不同的时间段执行。最常见的几个phase包括build_phase自上而下执行所有组件在这里创建和连接。connect_phase自下而上执行组件之间建立TLM端口连接。run_phase并行的主运行阶段所有的main_phase、reset_phase等都在这里并行执行。report_phase仿真结束时打印报告、检查覆盖率、汇总统计。理解phase机制对写UVM代码很重要。很多bug都是因为把应该在build_phase干的事放到了connect_phase或者把应该在run_phase干的放到了build_phase。我的经验是创建对象、配置参数在build_phase连接端口在connect_phase发起激励、检查行为在run_phase结果汇总在report_phase。顺序错了轻则warning重则环境跑不起来。5. 仿真调试的日常那些年我排过的验证bug验证工程师的日常就是不断遇到bug、排查bug、复现bug、确认修复。这里挑几个最经典的疑难杂症讲讲排查思路。5.1 仿真卡死或者仿真异常缓慢这个问题几乎每个验证工程师都遇到过。仿真跑着跑着突然卡住等了半小时还没跑完一看时间点根本没往前走。最常见的原因有两个死锁和仿真时间步长过小。死锁通常发生在多个sequence或者多个driver之间相互等待资源比如两个agent都在向DUT发送请求但DUT的资源只够一个agent使用而sequence的等待逻辑又写成了必须等到对方完成才继续结果两边都堵住了。排查死锁我用得最多的方法是小范围复现加打印。先把testcase缩小到最简能复现的序列然后在关键等待点加上$display打印信息看看卡在哪个语句上。UVM自带的UVM_VERBOSITY调整到UVM_DEBUG也能帮助你看到sequence之间的请求和完成握手。仿真时间步长过小的问题往往和RTL里的时钟生成方式有关。如果时钟生成的代码里用了#1这种极小的delay而且没有和timescale配合好仿真器会在每个时钟沿附近都做大量的时间步进计算导致仿真速度骤降。检查一下timescale设置把精度控制在合理范围比如1ns/1ps时钟用always #5 clk ~clk;的方式生成而不是用#1组合生成通常能改善很多。5.2 Race Condition仿真结果不稳定同一个testcase同一份代码跑三次仿真两次通过一次失败这种玄学问题多半就是race condition竞争冒险。典型场景是driver在某时刻向DUT接口发出信号monitor在同一个仿真时刻立刻采样接口但DUT内部还没有完成信号传播和状态更新导致monitor采到的是旧值。解决办法是在monitor采样时添加合理的同步机制。业界常用的做法是把driver驱动信号和monitor采样信号对齐到时钟边沿用(posedge clk)把采样时点稳定之后再进行。如果你控制不了DUT内部时序那就让monitor在接口上采样时加一个极小的delta delay比如#1step确保DUT在同一时刻的更新先完成再让monitor去采样。这个方法不优雅但确实能解决90%的采样竞争问题。5.3 文件编译顺序引发的UVM组件未定义UVM代码量一大文件编译顺序就成了一个隐患。SystemVerilog里类的引用和包package的依赖关系如果不注意顺序VCS编译时会报identifier not defined之类的错误。最稳妥的做法是把整个验证环境代码打成一个package比如fifo_test_pkg然后在文件列表里先编译这个package再编译引用了这个package的testbench顶层。package内部的编译顺序遵循基类先编译、子类后编译高层次的类先声明、低层次的类后声明的原则。我见过不少新人在多个package之间相互import结果编译顺序绕来绕去最后报错。我的建议是验证环境的代码尽量放一个package减少跨package的依赖编译顺序会自动很多。5.4 波形文件巨大磁盘爆了跑一个大型SoC验证如果从仿真开始到结束全程dump全部信号FSDB文件动不动就几十GB甚至上百GB。服务器磁盘爆掉可不是闹着玩的会导致仿真中途崩溃前面的运算全部白费。我的做法是分层控制波形记录调试阶段dump全部信号方便全方位排查。回归阶段只dump几个关键模块的信号或者干脆不dump。某些怀疑有问题的testcase定向dump特定模块层次。Verdi支持运行时动态控制dump范围你可以通过$fsdbDumpvars的层次参数来限制也可以在仿真过程中用$fsdbDumpoff/$fsdbDumpon控制时间段和层次范围。initial begin $fsdbDumpfile(case.fsdb); $fsdbDumpvars(0, top_tb.dut); end这段代码只dumptop_tb.dut这个层次以下的所有信号比dump全芯片小得多。6. 验证策略与收敛怎么证明验证做完了验证工程师最怕被问一个问题你确定没问题了要回答这个问题不能靠拍胸脯得靠数据。这个数据就是覆盖率。6.1 功能覆盖率功能覆盖率是收集这些功能点我测到了没有。用SystemVerilog的covergroup和coverpoint来定义要覆盖的内容比如FIFO的读写同时发生、满标志位置位、深度超过阈值等。功能覆盖率需要在写验证计划的时候就想清楚。我的习惯是先根据规格书列出功能点清单把每条功能点翻译成可量化的coverpoint然后再去开发对应的sequence去覆盖它们。如果最后发现某个coverpoint覆盖率不到90%通常意味着对应场景的激励不够需要补充testcase或者调整约束。6.2 代码覆盖率代码覆盖率是收集RTL代码的每一行、每一个分支、每一个条件表达式我在仿真中有没有执行到。VCS用-cm linecondfsmasserttgl选项开启覆盖率收集仿真结束后用urg工具汇总生成覆盖率报告。代码覆盖率是功能覆盖率之外的第二道保险。有时候你觉得自己功能点都测到了但代码覆盖率告诉你某个if-else分支从来没进过、某个状态机状态从没到过这往往意味着测试场景确实有盲区。把代码覆盖率和功能覆盖率结合起来看才能对验证收敛有把握。6.3 覆盖率收敛的实践方法覆盖率收敛的过程本质上是一个激励-反馈-调整的循环运行回归测试拿到当前的覆盖率报告。分析未覆盖的点判断是激励不够、约束太紧、还是RTL里有不可达代码。根据分析结果调整sequence、放宽/收紧随机约束、甚至新增testcase。再跑回归观察覆盖率变化。我个人的经验是覆盖率分析不要拖到最后一两个星期才开始。项目初期跑起来一套环境之后就顺手把覆盖率打开每次回归都收集一份覆盖率报告。这样做的原因是早期发现问题调整成本低如果拖到环境稳定了才开覆盖率一旦发现某一大块功能完全没覆盖到再补激励的工作量会非常大。6.4 断言SVA把规格变成可自动检查的规则断言SystemVerilog AssertionSVA是验证里极其有价值的一部分。它不是通过比较输出结果来发现bug而是在仿真过程中实时检查关键时序行为。比如FIFO的读使能和写使能不能同时生效取决于你的设计或者总线协议中地址必须对齐这类规则用断言写出来之后仿真跑着跑着断言报错就能立刻定位问题。断言之所以重要是因为它能把我肉眼观察波形才发现的问题变成仿真自动检测出的问题。在大型设计中输出结果正确不代表中间行为一定正确一些潜在的协议违例可能不会立即影响最终输出但会在某种极端场景下引爆问题。断言可以帮助提前捕获这些隐患。7. 工程化实践让验证环境高效运转的几条经验除了上面这些技术细节我把这几年做验证工程化的一些心得也放在这里不一定主流但都是我实际用下来觉得值得分享的。7.1 编译和仿真分离跑回归时只重编必要的部分在大型项目里全量编译一次VCS环境可能要花几十分钟如果每次修改一点点验证代码都要全量编译时间成本太高。VCS支持增量编译smart compile它通过比较文件的时间戳判断哪些文件需要重编能明显缩短迭代时间。另一个实用建议是环境和testcase尽量分离。环境代码env、agent、driver等相对稳定testcase代码经常变动。把testcase编译成单独的库每次修改testcase只需要重编testcase部分不需要动环境。这样回归测试时通常只花几分钟编译就能开始跑仿真。7.2 随机约束与Seed管理UVM的约束随机验证是发现边界bug的重要手段。随机约束不能只靠默认的random而是要精心设计约束和解约束的组合。比如FIFO的读写测试读地址约束在写地址附近随机和完全随机测到的行为会有很大差异前者更容易测到FIFO快满快空的状态后者更容易测到FIFO深水区的行为。Seed管理讲究的是可复现性。仿真崩溃了、断言挂了你需要用同一个seed重新跑一遍才能定位问题。我一般会在回归脚本里把seed独占一行打印到日志开头这样每次跑完都能精准定位到实际使用的seed。随机测试发现问题后第一时间固定seed复现不要让随机性干扰debug过程。我个人还有一个习惯就是把有效seed积累下来。一个seed跑出了某些覆盖率高的testcase就把这个seed永久保存到回归列表里作为黄金seed。这样后续回归不需要每次重新随机碰运气稳定性和覆盖率都会比较可预测。7.3 回归测试的组织回归测试Regression不是简单地跑完所有testcase就完了需要有一个脚本框架来管理编译、仿真、结果判断和报告生成。我比较习惯用Makefile加一套shell脚本组合每个testcase一个目录日志和波形都有固定命名规范脚本跑完自动检查UVM_ERROR的个数和覆盖率是否达标最后汇总成一份HTML报告。结果判断逻辑也要想清楚什么样的回归算通过通常标准是UVM_ERROR为0、所有断言通过、覆盖率达标。UVM_WARNING可以根据实际情况决定有些warning是代码里的冗余警告完全不需要关注但如果某个warning意味着testbench行为不符合预期就必须当error来对待。我的建议是建立warning白名单明确哪些warning可以忽略新增的warning一律提示人工确认避免重要信息淹没在大量无效日志里。8. 一些想对验证新人说的话最后聊点实际的。很多人觉得验证就是看点波形、写写用例没什么技术含量。但真正做深了你会发现验证工程师需要的能力非常全面要能读懂RTL代码、要能熟练使用EDA工具、要能写出结构清晰的UVM环境、要能设计有效的随机约束、要能分析覆盖率报告、还要能和设计工程师高效沟通。我见过太多新人在UVM的类继承和factory机制里迷路也见过不少人在仿真报错之后一头扎进波形里反复横跳找不到问题。这些都很正常验证工程师的成长路径本就是一个不断踩坑-总结-提升的过程。如果让我给新人三条最实在的建议第一条是先学会看波形波形是最诚实的设计行为记录任何仿真结果异常都能在波形里找到线索第二条是写验证环境时多想想复用一个模块的agent抽象好了另一个模块几乎可以直接拿来用省下的时间非常多第三条是别怕问设计工程师验证工程师对RTL实现细节的理解越深越容易设计出有针对性的测试方案也越容易在bug出现时快速判断是环境问题还是设计问题。Synopsys这套工具链确实是行业里的成熟之选但工具终究只是工具真正决定项目成败的还是驾驭工具的人对验证方法学的理解和实践经验。多写、多跑、多复盘验证能力就是这么一点点练出来的。
返回列表