介绍调试Debug过程中常用的方法和技巧

电子说

1.3w人已加入

描述

调试(Debug),是个非常广泛的话题,让我先尝试给它下个定义:

调试指的是在遇到工程问题的时候,通过一些手段来进一步诊断问题原因,探索解决方法,最终使得系统功能正常运行的必要过程。

调试应该说是任何一个工程师解决问题的必备技能,对于芯片验证工程师来说更是如此。调试通常没有固定的章法,不可能按照某一个流程步骤就可以解决所有的工程问题,毕竟在实际环境中遇到的问题千差万别,可能只是你的某一个命令参数敲错了,可能是RTL逻辑设计上存在bug,也可能是设计规格(Specification)本身就存在纰漏,等等等等。

尽管调试方法不固定,但调试思想还是很一致的,那就是:尽可能的获取更多的有效信息,并对这些信息做综合分析。你细品,比如最常用的导出仿真日志(log)或者波形文件,这些方法本身就是为了获取更多的有效信息,这些信息能够帮助我们更好地去了解现在RTL的功能行为,帮助我们进一步对问题进行定位。

回到本文主题,本文要介绍的内容就是调试过程中常用的方法和技巧,这些方法的合理应用可以获得上面说的有效信息,从而提高我们解决问题的效率。而至于如何做信息的综合分析,需要根据具体场景和经验才能完成。

方法1:文件和参数索引的建立

之所以把建立文件和参数的索引也说成是调试方法,是因为在面对一个组件繁杂的验证环境,或者规模庞大的设计的时候,能够快速地检查相对应特性的规格文件、配置文件、宏定义、类型定义、参数配置、类原型、函数原型等信息,极有可能就可以解决掉一些低级的错误了。

要快速地找到这些有效信息,一方面依赖于工程师对当前验证环境和设计配置文件的熟悉程度,另一方面可以借助一些工具来找到它们。后者就是本节所要介绍的内容,下面就直接罗列我工作中最常用的一些工具和命令。

使用ctags扫描工作目录,建立基于语法元素的索引,配合Vim可以实现语法元素的快速跳转。

使用meld工具比对目录或者文件在不同版本之间的差异,功能比gvimdiff强那么一些。

Windows下使用Listary或者Everything软件来快速查找本地文件,可以参考《芯片工程师必备软件神器》。

Linux下使用grep命令来筛查文件内容,配合管道符“|”可以实现更多级的筛查。

掌握一两门脚本,Python、Perl、Shell等都可以,掌握正则表达式,随手就可以实现一些小功能。

以上提到的工具和命令,都可以在网上找到大量的教程。如果有时间,我再写一些小的使用Tip放到公众号上。

方法2:波形的导出和使用

通过波形可以很直观地看到RTL随时间变化的所有行为细节,尽管拉波形看信号变化看多了容易眼睛瞎掉,但不得不承认大多数情况下没有波形的话问题定位会变得寸步难行。波形文件是仿真过程的副产物,它按照一定的格式将每个仿真事件发生时刻的信号和变量状态记录下来,并最终以图形化的方式呈现出来。

RTL

波形文件的格式很多,比如VCD、FSDB、VPD、WLF等等。

VCD格式应该说是最通用的波形格式,它是SV标准中定义的一种ASCII文件格式,其全称是Value Change Dump。VCD包含了所有信号的变化信息,它可以被任何EDA调试工具打开。VCD文件可以在testbench中使用SV的内建任务$dumpfile("name.vcd")和$dumpvar()来导出。

FSDB的全称是Fast Signal Database,它是原来Novas公司(先被Synopsys收购)的工具Verdi专用的波形格式。在testbench中,可以使用Verilog PLI接口(可以像调用内建函数那样去调用C/C++函数)调用$fsdbDumpfile("name.fsdb")和$fsdbDumpvars(0, top)去导出。注意,要用着两个函数需要将Verdi安装目录中share/PLI下的相关库添加到动态链接库路径($LD_LIBRARY_PATH)中,或者有参数(比如Mentor工具用的-pli)去指定PLI库的路径。

VPD是Synopsys自家定义的波形压缩格式,叫VCD Plus(这个plus是不是有点似曾相识的感觉),在使用Synopsys VCS工具仿真时可以导出VPD波形,同时也可以使用使用配套的波形浏览器DVE(Discovery Visual Environment)去打开波形。VPD波形可以在testbench中使用$vcdpluson去导出,在VCS做编译和仿真的时候需要指定debug能力,比如加参数-debug_all。

WLF的名字就比较单纯,叫Wave Log Format,一看就知道是干啥的。WLF格式是Mentor家(现在属于SIEMENS)定义的,可以由QuestaSim或者ModelSim仿真工具可以生成和加载。如果你用ModelSim做过实验,当你打开波形界面,它会在工作目录下自动生成一个WLF文件。另外Mentor的工具还可以使用命令选项-qwavedb并附加一串选项,可以在仿真过程中生成仿真数据库文件(simulation database),该文件以.db为后缀,可以当成仿真波形被Visualizer调试工具打开。

以上提到的函数调用在参数上具有很大灵活性,可以指定具体要dump波形的RTL层级、scope范围甚至指定哪些信号。此外还有其他函数可以用来限制波形文件大小、波形dump开关、导出Memory数据等等。

尽管不同格式的波形文件有以上差异,但在应用的时候大部分情况下取决于你有什么EDA工具可以用,另一方面这些波形格式也有工具可以相互转化。

仿真波形在使用的时候通常有一些技巧,方便问题的定位和重现,下面列举几个常用的:

将功能相关的信号分组(group)放在一起,方便做逻辑或协议检查;

可以将多个信号组合成总线信号,或者进行自定义的逻辑运算;

为被关注的仿真时刻添加标签(Mark),方便来回观看;

通过添加标尺可以查看信号事件间距、周期信号频率、统计信号事件等功能;

将常用的状态信号、总线信号、某个用例调试用的信号等保存成do文件,即将当前波形界面的元素保存下来,方便波形重现;

每个人看波形的习惯和方式会有不同,找到自己习惯的方式就可以了;

方法3:仿真日志的导出和使用

仿真日志(常说的log文件)通常是我们查看仿真结果会首先打开的文件。该文件中可以包含整个仿真过程中由编译器和仿真器打印输出的各种文本信息,比如当前导入了哪些文件、本次编译和仿真分别用到了哪些参数、当前DUT的顶层是哪一层、仿真过程中不同时刻的关键动作和信息、本次仿真的结果和资源开销等等。

基于文本的仿真日志记录和对仿真日志的手动分析看起来是比较低级和低效的,特别是当我们看着仿真日志并顺着时间轴试图去将打印数据和RTL行为关联起来的时候,简直苦不堪言。尽管如此,仿真日志的导出和使用仍然在某些时刻起到了基础性的作用,特别是在UVM将report机制构造健全之后,其作用不可忽视。

SystemVerilog本身在打印信息上有好几个任务可以用:$display, $write, $strobe, $monitor, 这几个任务的使用上不完全相同。

最常用的是$display,$display可以用来做格式化输出,格式化方法跟C语言几乎一致,并且打印完成之后会自动换行。

$write则用的不多,它的功能跟$display很接近,区别在于在打印完成之后不会添加换行符,所以适用于想要在一行里输出多个信息的场景。

$strobe的打印就比较讲究了,通过$strobe打印的变量值是当前仿真时间槽(time slot)中该变量最后的值,比如对变量A做非阻塞赋值(<=)之后跟着用$strobe打印变量值,打印出来的是该变量完成赋值之后的值,而如果用$display则是打印出来该变量赋值完全之前的值。

$monitor的功能就跟它的名字一样,它相当于是在主线程之外开了一个监控线程。当通过$monitor打印的变量或者表达式发生改变时,打印的功能就会被触发,看起来就像是一个有了外部中断能力的$display。

在UVM的框架里,打印信息被赋予了严重性等级(severity)和冗杂等级(verbosity),这在信息控制上提供可很大的便利。

Severity分成了INFO、WARNING、ERROR和FATAL,分别使用宏uvm_info、uvm_warning、uvm_error和uvm_fatal来进行信息打印。

Verbosity等级共分为UVM_NONE、UVM_LOW、UVM_MEDIUM、UVM_HIGH、UVM_FULL、UVM_DEBUG六级,表示信息的冗杂程度由低到高。

INFO顾名思义就是简单的状态信息打印,这些信息并不是必须的,它的verbosity属性可以通过uvm_info宏的参数来指定。比如当仿真的verbosity设为UVM_DEBUG时,所有uvm_info信息都会被打印出来,仿真日志会显得非常冗杂;当仿真的verbosity设为UVM_NONE的时候,只有UVM_NONE属性的uvm_info信息才会被打印出来,仿真日志就看起来简洁很多。

WARNING指的是一些警告信息,提示潜在的问题,比如你显式地调用了某个phase,可能不会影响仿真的继续,但可能会让仿真场景不符合预期。WARNING的verbosity默认都是UVM_NONE,因此始终会被打印出来。

ERROR就是一些错误信息,错误信息的出现不会使得仿真马上停止,UVM给我们提供了阈值设置,当错误信息的数量达到某个值只有仿真就会自己退出。通常UVM_ERROR的信息被打印出来,我们都需要去把它们都解决掉。ERROR的verbosity等级默认都是UVM_NONE,所以也不要想着用verbosity来屏蔽它。

FATAL就是一些致命的错误,只要一出现仿真就会马上退出,这种问题同样必须解决,不解决你连仿真都跑不完。可想而知,这类信息同样verbosity默认是UVM_NONE,始终会被打印出来。

Verbosity的设置出了直接用仿真参数+UVM_VERBOSITY=UVM_DEBUG来配置,UVM还提供了+uvm_set_verbosity这一非常灵活的参数,具体使用方法这里就不做介绍了。UVM出了这套完备的打印信息控制机制之外,还提供了很多调试宏,比如+UVM_CONFIG_DB_TRACE、+UVM_PHASE_TRACE、+UVM_OBJECTION_TRACE等,用来方便用户从仿真日志中观测配置数据库(configuration database)状态、phase执行状态和objection状态等。

总而言之,信息的打印方法有很多,UVM在report机制上也给到了足够的控制手段,利用好这些方法,仿真日志将是我们调试用例的利器!

方法4:可视化调试

可视化调试主要分为Post-process和Interactive这两种模式。可视化调试工具是工程师在定位代码问题时的有力工具,也是现在验证工程师主流的调试工具。工具的使用一般可以参考官方的用户手册(User Guide),也能够在官网上找到相应的培训链接和视频。

常用的可视化调试工具有Synopsys家的Verdi,Siemens家的Visualizer,还有Cadence家的SimVision。对于个人用户来说,可能没有办法去实操体验,但通常所在公司会购买至少一家的License。三家公司的工具的操作流程和基础调试功能都差不多,然后又分别有自己调试的独特功能。

RTL

先介绍下后处理调试模式(post-process,即在仿真结束之后再去可视化和处理仿真结果,有些地方会叫做PPE,post-processing environment)的使用,因为这种方式在实际工作中用的比较多。在使用可视化调试工具之前,通常需要将testbench和RTL编译到同一个数据库中,该数据库包含了文件信息、RTL例化层次信息、信号连接关系等等,以供调试工具的追踪和分析。

如果使用Verdi工具,需要使用VCS在编译(Compilation=Analysis+Elaboration)的时候,通过加参数-kdb -lca来生成KDB库(Knowledge Database),其中lca(Limited Customer Availability Features)参数是为了指定工具特性。KDB数据库格式是Verdi专用的格式,所以KDB库有时候也可以叫Verdi库。打开verdi的时候使用命令verdi加参数-elab来选择该KDB库。

如果使用Visualizer工具,需要使用Questa/ModelSim在对设计完成编译(vlog/vcom)之后,使用vopt命令加参数-debug -designfile design.bin来生成.bin文件,同样该文件格式是Visualizer专用的。打开Visualizer的时候使用命令visualizer加参数-designfile来选择该bin文件,使用参数-wavefile来选择db波形文件。

如果使用SimVision工具,需要在仿真阶段使用NC仿真器或者XCelium仿真器(具有更高的仿真性能,比如支持多核等)将设计和波形都导出成shm格式。在仿真结束之后,你可以看到名为example.shm的目录,该目录下会有两个文件:.dsn文件和.trn文件,前者包含的是设计的信息(类似于我们上面说的数据库),后者包含的是波形信息。打开SimVision的时候使用命令simvision直接加example.shm来打开待调试的数据库。

RTL

再看看交互模式(interactive mode),交互模式相对于后处理模式增加了仿真控制的功能,即可以设置仿真断点、控制仿真的暂停、运行和重启等,并实时地观察到信号的行为。交互模式下,上述EDA工具的界面上会多出来一些调试控件。不过这种模式的仿真运行速度比较慢,且在分发和重现代码行为上不是很友好,所以在实际工作中也用的比较少,除非遇到非常棘手但却摸不着头脑的问题。以上提到的几家工具都支持交互模式调试,操作流程也都差不多,并且跟后处理模式一样也需要先编译出来一个数据库。

如果使用Synopsys家的工具,在设置完必要的环境变量之后,比如VCS_HOME和VERDI_HOME,需要使用VCS命令vcs -kdb -lca -debug_access+all 编译出KDB库和simv可执行的仿真文件,然后在执行simv的时候加上参数-verdi就可以打开交互模式下的Verdi了,这个时候调试器和仿真器是关联起来的。

如果使用Siemens家的工具,同样在设置完必要的环境变量并使用命令vopt编译出design.bin文件之后,可以使用命令vsim -visualizer=design.bin -qwavedb=+signal+class -f 打开交互模式下的Visualizer,便可以在调试工具界面去控制仿真器。

如果使用Cadence家的工具,那就相对复杂一点,因为Cadence前前后后有几个仿真器,比如verilog、ncsim、irun,并且进交互调试模式的方法也比较多样,但大致可以分两种:一种是可以通过参数-gui直接开启带SimVision的仿真器,另一种方式是单独启动SimVision,使用参数-connect host/pid连接到运行在本地或者远端的仿真上。

以上命令只是展示大概的使用过程,实际应以对应版本的用户手册为准哈。当你打开可视化调试工具调试界面之后,有这么几种常用的调试功能:

通过Hierarchy等窗口浏览源代码的例化层次结构,类继承关系等;

通过查找Driver和Load来定位信号的传播通路(这个是用的最多的);

通过Filter来分类查看当前文件包含的输入输出信号、参数、变量等;

通过查找来定位某一个module例化出来的所有模块;

原理图和状态机跳转图可以有限地帮助你理解代码行为;

配合波形文件查看各种信号随时间变化的行为;

调试工具的功能还有很多,具体可以查看各个工具的官方介绍和培训视频。

方法5:SVA断言在调试中的应用

概述:SystemVerilog Assertion(断言)主要用于验证设计的行为,并且可以提供功能覆盖率信息。Assertion可以应用于两种不同的验证方法中,一种是在动态仿真中去动态地检查各个既定属性(property)是否满足,另一种测试用于Formal验证工具去证明设计是否符合规范。

作用:如果你刚接触,可以把断言简单理解成checker或者monitor,它指的是在设计中嵌入一些工程师根据待测特性自行定义的一些属性,仿真的时候仿真工具会去判断这些属性是否成立,以此来判断某个特性是否实现正确。SVA在本文中作为调试的方法来介绍,就是因为断言可以帮助我们监测属性,为我们报出来哪些时刻行为正常、哪些时刻行为异常,且这些行为可以是有时序的!

分类:在SystemVerilog中,断言大致可以分为两类:立即断言(immediate assertion)和并发断言(concurrent assertion)。立即断言是基于仿真事件(simulation event)的,当它被执行到的时候就会立即对多定义的属性做出判断并给出结果;而并发断言是基于时钟的,断言的评估(evaluate)发生在时钟边沿,这也使得并发断言具有监测的能力,这也是下面要主要介绍的。

结构:断言的具体实现依赖于更基础的元素,比如sequence和property。Sequence是最底层的元素,它可以复用和嵌套。Sequence可以用来定义简单的布尔表达式,也可以用来描述多周期的时序行为。Property则可以实现跟sequence一样的内容,也可以通过组合不同的sequence来构造更加复杂的时序行为。为了规范化,建议将嵌入的时钟信号@(posedge clk)放在property这一层,而将sequence跟时钟独立开来,方便基础sequence的复用。

调度:SystemVerilog的仿真基于事件驱动模型,事件的调度机制在SV语言标准中有明确说明。该调度机制将每个仿真时刻(time slot)再划分成多个region,如下图所示,每个region都有自己明确的操作。仿真调度算法的确定,可以使得仿真环境跟DUT交互时显示出同步的效果。其中跟SVA相关的region有Preponed、Observed和Reactive。在Preponed中,SVA会对有关联的变量完成采样;在Observed中,多有的property完成评估,即判断断言描述是否成立;在Reactive中,执行断言评估结果需要采取的对应的操作。

RTL

应用:断言的应用主要可以分成四个步骤:1、构造基础布尔表达式;2、构造sequence序列;3、构造断言属性property;4、将属性代码插入或绑定(bind)到待测模块中。SVA提供了一些好用又强大的功能:判断信号边沿和状态、添加延时来构造信号时序行为、支持构造不定周期的时序窗口、判断过去的信号状态、支持断言的逻辑运算等等,本文篇幅显然是不够的了。

方法6:软件调试方法的借鉴和应用

这一节的内容更像是讨论,有哪些软件开发中用到的调试方法,或者问题定位策略是可以借鉴过来应用到芯片验证中的。

有个前提需要明确的是,硬件仿真始终是基于事件驱动的程序执行过程,尽管仿真调度机制简洁明了,但往往待测设计规模庞大(具体表现为硬件行为具备并行性质,一个时钟信号的翻转事件关联着成千上万的信号动作),所以硬件仿真的运行速度会非常的慢,这是跟单纯软件程序的一个显著区别。

运行速度上的差异带来了调试方法上的一些不同。软件调试中交互式的操作非常多,比如解释执行的脚本(比如Python)不需要编译就可以马上得到执行的结果,又比如基于断点的调试可以非常容易地检查变量值和堆栈跟踪。反观硬件调试,工程师很难快速地知道在哪里设置断点,往往需要反复的尝试,这会浪费掉很多时间。因此硬件的调试更多依赖于信息的导出,其形式通常是仿真日志和波形文件。

如何提高硬件调试的交互性可能是软件调试带来的启示,有这么一些不成熟的想法,比如是否可以增加调试信息(代码、波形和仿真日志)之间的关联,实现自动化跳转;是否可以增加工具对代码的理解或者记录调试过程来进行自动化分析;是否可以在增量编译的概念上实现增量仿真;等等等等。






审核编辑:刘清

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分