电子说
SoC基本仿真环境介绍
我在论坛上写过一个。《如何搭建SoC项目的基本Testbench(我的流程)》,这里挑重要的和有改变的地方说一下。
假设这个SoC有CPU系统、内存控制器、总线拓扑、PAD、Clockreset和一些逻辑功能模块。
1. 仿真环境中有嵌入式软件(firmware)
这里包括两部分,一是初始化的bootloader(一般是固化在rom或者存放在外部的flash里),一是boot起来以后放在外部易失性存储介质上的应用层面的程序。
2. 使用指令集模拟器(ISS)来代替CPU-IP.
有开源的也有不开源免费的ARM多款处理器IP的ISS. 考虑到ISS本身并不真实,如果不是为了验证bootrom代码的话,个人建议找一个开源的就可以了。 ISS可以编译成.so的库文件,这样在仿真的时候就不用编译整套ISS的C代码了(需要设置LD_LIBRARY_PATH,告诉仿真器仿真的时候去哪里找;编译过程中需要告诉链接库地址和库名称)。
ISS需要一个配置文件来告诉它CPU的地址访问空间,像程序区、堆栈区是ISS管理的空间(假设叫memory空间),像DUT的内存地址以及DUT的寄存器空间就是DUT管理的空间(假设叫IO空间),ISS要能看到所有的地址空间,并且根据地址来判断是memory空间还是IO空间来做不同的操作。
3. 共享空间 CPU(ISS)和Testbench交互的时候可以指定一块地址空间(比如0x3000_0000 ~ 0x3100_0000),这块空间就是testbench中的一个数组。 比如要实现对寄存器的随机配置,由于ISS的C程序不方便做约束随机,可以在Testbench组件中把约束随机产生好的数值写入到这块共享空间中,然后让ISS的C读出共享空间的数值再配到寄存器中。
4. Define文件的维护
SoC项目中Testbench的一个define可能同时在汇编程序、嵌入式C程序、Cmodel的C程序、Testbench的SV代码都要使用。 同时维护多个类似的define文件肯定容易出错,所以只维护一个,其他的由脚本程序自动产生。
5. 内存控制器模块(DDRC)的替换
DDRC是系统中最主要BUS-Slaver,通常需要初始化过程(也有的ddrc不需要),还通常需要DDR-PHY的模型,这些都比较影响仿真速度。而且在验证功能模块的时候,使用DDRC也不容易模拟带宽不同变化的场景。因此验证的时候可以考虑用一个BUS-Slaver的BFM来代替DDRC.
6. 打印的实现在SoC环境的Testcase的嵌入式C语言程序中没有标准的stdio,所以要实现printf. printf是“不定参数个数的函数”,利用“参数从右向左压栈,最开始的参数在最接近栈顶的位置,字符串最后一个字符是\0”来实现printf. 在C端只需要把第一个参数的地址(肯定不是0)传到共享空间指定位置,在SVTB中获取到地址以后按照%和参数地址来实现(SV端的实现和C语言里printf的实现很类似),需要注意的是1)C端程序要保证参数地址及时写入到共享空间中,不要停在cache或者传输太长;2)使用ISS要打印的信息存在memory空间里,要能让SV端看到memory空间。当使用多核CPU-RTL的时候,注意不同core的打印控制(可以为每个core分配一个共享的空间,在print函数中根据不同的core-id来执行不同的操作)。
malloc等函数的实现也可以利用共享空间来实现。
7. 多个模块的协同验证在系统级下经常要跑系统级case来模拟整个系统一起工作起来的场景(一般该case也适用于power分析)。这种case可能要花比较大的精力在Testcase的构造上,如果有硬件仿真加速器还好,如果只能在纯粹的仿真环境下做的话,尽量做简化处理。
8. 单一case流程编译dut和testbenchà编译firmwareà开始仿真(在合适的时机loadfirmware和产生随机控制数据写入到共享空间。如果要把firmware load到ddr model中,并且DDRC初始化流程会做data-training这种写数据的操作,那要保证初始化的数据不要被冲掉)
模块级验证相对于系统(子系统)级仿真环境的优势:
1)仿真速度快
2)随机可控性好
3)更容易做Error-Injection
4)更容易做开关切换和模式切换
仿真工具(2010年以后的版本)都支持模块级和系统级的覆盖率合并,可以加速收敛。
需要注意的是:虽然模块级环境理论上可以覆盖所有的系统级环境里的情况,但是在有限的人力和时间资源的情况下,很可能达不到100%的覆盖。举个例子: 视频外同步的dataenable信号变化情况过多,以至于random不到实际系统中可能出现的情况。 总之:关键是模块级环境有可能没有覆盖到实际中可能出现的情况。
模块级环境基础结构
模块级基础环境中除了有验证组件(monitor driver scoreboard等),还有一条总线连接Master和Slave、CPU-Model控制DUT. 注意:这里所说的模块级环境里的DUT是一个完整的模块,不考虑一个模块内部的子模块.
假定DUT是一个总线的Master设备,会发起访存操作。CPU-Model负责配置寄存器和一些访存,模块级环境为了简化,让CPU-Model不用通过总线而是直接访问Bus-Slaver的空间。
模块级环境Testcase在系统级上的重用
在人员和时间资源有限的情况,要保证模块级代码在系统级上的重用。一般来说Testbench组件(Driver Monitor Model Scoreboard等)重用比较简单,比较麻烦的是testcase在SoC系统级(CPU上的C程序)环境下的重用比较麻烦。Testcase构建是由TB架构中CPU-Model的实现方法决定的。下面两种我主要用的方案Testcase都是C的.
方案一:
使用ISS实现。与SoC基本仿真环境比较一致。
方案二:
使用DPI实现。CPU-Model是一个SV的model,实现寄存器访问和内存地址空间访问的task,比较复杂的是对中断的模拟。可以使用sv的 fine-grain-process(process::self())来实现main-task和irq-task,模拟“CPU进入中断以后挂起主程序,执行完中断以后返回主程序”(main_task.suspend(); ….; main_task.resume();)的行为。使用DPI来把底层硬件接口的SV-Task传递给C程序。
DPI实现的方案中要注意底层硬件接口的代码,以及C代码中直接访问的代码(例如指向DUT内存的指针的操作),尤其是IP-Vendor提供的参考代码里很可能有类似代码。比如,usb ethernet的软硬件交互的协议栈放在内存中,软件代码中一般会维护一个数据结构,然后指针指向数据结构地址操作。dpi环境下这样的代码就没法直接用了(指针的操作就不行了,得换成硬件底层代码实现)。这样模块的firmware代码可能是现成的,仿真应该尽量复用。简单的方案就直接放弃模块级环境,上系统级环境里验证,或者用iss。iss环境下,firmware代码可能也要修改,比如上述的数据结构地址就要注意分配到dut内存地址上去(比如一个大的struct的赋值操作,先malloc出一块空间,然后向这个空间填数。只要malloc到dut内存地址上就可以了)。
其他方案:
Testcase不用C直接使用SV实现;或者把CPU-Model换成真实的CPU-IP,带上boot-rom和boot-ram。
架构评估
个人首推还是硬件加速器,需要注意的是架构评估最好保证频率的比例关系(访存模块的总线访问频率、总线拓扑中各组件频率、内存控制器和内存工作频率),个人感觉可能Palladium和Veloce这种方案的硬件加速器更加适合。
如果采用仿真的方法做评估的话,需要注意:
1)pattern的真实性。现成的模块RTL虽然可以反映真实的访存行为和latency的容忍度,但是构建环境偏复杂,仿真速度偏慢,通常还需要有初始化流程才能工作。个人建议构造BFM模拟访存行为:BFM可以吃配置文件,从而模拟比较真实的场景。
2)基础架构的自动集成。稍微复杂一点的SoC架构中,总线拓扑的集成可能就很复杂了(统一的组件不太方便用emacs自动集成的功能来做,而且一些特殊信号位宽的匹配手动做起来很容易出错)。多年前就有自动化的工具来实现这个集成。但是商业化的工具虽然提供了很多的功能,但是未必能直接满足个体项目的需求,建议考虑开发自动集成的工具。目前的low-powerflow里已经不用在架构集成的RTL中写入多少额外的代码了,简化了自动集成的难度。
架构评估环境里还需要有内存控制器和总线架构模块的performance-monitor,来统计吞吐量、内存控制器效率、功能模块访存行为的latency等,根据吞吐量来看架构评估环境是否和期望的访存数据量比较一致,在这个前提下看内存控制器的效率达到了多少,以及系统中哪个模块会出现不合理的比较大的latency(导致该模块可能设计的时候需要加大fifo深度)。通过调整内存控制器和总线拓扑模块的优先级策略、时钟频率或者增减总线拓扑组件以及Master/Slave口的数量来做不同的仿真。
VIP的使用
通常我们对复杂的标准接口协议的数通模块采用vip做bfm来验证。
vip的好处是不用特别开发bfm,有完整的tb组件,有的文档中有比较完善的测试计划和实例,随机性和error-injection完备。坏处是代码不可见,遇到问题容易抓瞎;vip作为工具安装使用可能挑OS和仿真器;EDA vendor本地support人员不足等问题,通常VIP跑的速度比较慢。
能力和资源允许的情况下可以自己开发bfm.一种比较快的方案是reuse靠谱的rtl ip。比如我们要验证usbhost,那就找一个usb device或者uotg的rtl ip做bfm.这种方法的优势:BFM代码质量比较高,debug可见性高,将来在硬件加速器上可以更好的移植(有的可以不用phy,数字接口直接连)。劣势:开发代码量也不小,有的phy模型可能是个问题(比如一些单向数据传输的,Master端是并行数据转LVDS,Slave端是LVDS转并行数据,这样的PHY model可能需要另外开发),rtl通常需要初始化过程,随机性和error-injection不够友好。
我觉得如果DUT是内部开发的话,因为代码质量可能不够,用商用的vip更合适。如果DUT是买的已经silicon-verification的IP的话,我觉得自己开发的BFM就够了。
Coverage-Driven 和Assertion-Based
个人认为代码覆盖率是最重要的,是一定要统计和仔细的检查的。
功能覆盖率(包括assertion的覆盖率)应该是Testplan的反映,我觉得只是提供对Testplan覆盖的数据统计,Testplan本身可能是不完备的,所以功能覆盖率的100%并不能代表验证充分了。
Assertion对于一些不太复杂的协议时序验证还是比较适合的,但是感觉最近几年EDA公司都不太在assertion上投入资源了。我觉得对于一个大模块内部的子模块,assertion是非常适合的,可以针对子模块的接口具体的做assertion的描述和验证。
仿真层面的加速
最快的加速技术肯定是硬件仿真加速器。
在“SoC基本仿真环境介绍”中的CPU和DDRC的替换都有加速仿真的功能。除此之外还有一些我在用的加速方法:
1)系统级仿真环境中把不需要的模块dummy掉。 ----- 需要两个脚本程序,一个是自动产生dummy文件,一个是自动把dummy文件替换掉原有的RTL.
2)缩短SoC上电启动仿真时间。---- 通常是一个状态机根据若干个counter的技术来实现状态跳转。方法是更改counter的初始值或者跳转需要的数值,RTL级比较容易实现,门级仿真找信号比较麻烦,最好提前和flow的同事沟通好,把信号名保持住。
3)有些DUT里的IP比较耗仿真资源,可以考虑用简化的model代替。比如PLL、DCM以及某些PHY-Model.
4)某些RTL代码的写法可能很耗仿真资源。 比如在clock的每个上升沿当reset有效的时候把一个比较大的二维数组附初始值。 这种代码最好加ifdefelse endif来改写成Initial-block.
5)减少DPI过于频繁的交互
6)谨慎使用separate-compilepartition-compile等技术,也许带来负面效果。
门级仿真
功能仿真中一般有如下几类门级仿真:
1)综合后网表仿真
2)DFT后网表仿真
3)PR后反标SDF的网表仿真
4)FPGA综合后网表仿真
5)Gtech网表的仿真
综合后和DFT后的网表比较类似,一般跑DFT以后的就可以了。除PR后的网表要反标SDF以外,其他的都是跑0delay的门级仿真。一般额外做几个处理:
1)给std-lib cell库文件中给时序逻辑(dff等)加上clk2q的delay
2)保证SRAM model的输出端在不工作的时候不要输出X.
3)门级网表中如果有不带reset端的dff,一般要找出来做$deposit处理(找的方法可以请flow的同事拿对应的网表出一版sdf,然后parser sdf文件就可以得到对应instance里的dff)。
4)反标SDF的门级仿真如果checktiming的话,要注意把2dff等跨时钟域的逻辑的timingcheck去掉。
FPGA综合后的网表仿真一般不用做,但是当FPGA timing报告、FPGA功能仿真、CDC和Lint-check都没有问题,怀疑FPGA综合有问题的时候值得做一下。我在做FPGA综合后网表仿真中发现过几个问题: 1)Xilinx-V7默认把RTL中复杂的case语句用blockram实现,结果实现的时候时序差了一拍 2)FPGA把RTL里的一些运算直接用内部的DSP来实现,结果DSP的功能综合错误。 FPGA综合网表的信号名太乱,Testbench如果拉了一些内部信号进行观测或者force的话,很难编译通过。
Gtech网表的仿真很少需要做,如果FPGA上跑Gtech网表来代替RTL的话,需要注意FPGA版本Gtech单元的行为描述要保证和Asic的一致,否则容易有“坑”.
PR后门级仿真重点是出比较准确的IR-Drop和Power数据,以及保证时序约束的完整和正确性,建议只跑典型的case就可以了。
仿真验证自动化
举例一些Testbench的自动检查机制以外的自动化技术(很多是用crontab自动执行)。
1)每天自动checkout出一份代码做mini-regression,并且把结果自动发email通知给项目组。
2)每隔几个小时一旦检查到有新tag就自动update到新tag上做mini-regression并自动发email
3)每天自动把所有checkin的代码列出来并自动发email
4)每天自动把未解决的问题总结并发邮件(bug-zilla、issue-tracker、Jira等bug-tracking系统有的自带这个功能,有的可能没有需要自己实现)
5)自动代码备份(有的代码可能还在开发过程中,所以不想checkin到代码库里去,如果MIS没有针对这类代码做自动备份,可能需要一个自动备份的程序,)
6)除了自动比对环境以外,还要有一个parse 编译和仿真log的程序在regression的时候调用。
如果某个功能上没有自动检查机制,也要尽量想办法减少人工比对的工作量。例如,有一个图像算法模块的c-code找不到了,但是RTL是golden的,跑实际图形pattern一幅图一幅图看会比较耗时间,可以把regression中产生的图像打包到一个网页中,然后用浏览器去看。
脚本语言是仿真工作中非常重要的一环。 我们一般会用到shell\perl\tcl\python等脚本语言,建议在学习脚本语言期间,强迫自己遇到问题使用正在学的语言来实现。
验证项目管理
http://bbs.eetop.cn/thread-581216-1-1.html 《多媒体类SoC项目Verification Project Leader工作内容介绍(讨论)》我在这个帖子里列的比较详细了,VerificationEngineer的工作基本上是Verification Project Leader的子集。
帖子里是按照项目开始前的准备à项目启动但未提供第一版integration代码à0.5版本à0.75版本à0.9版本à1.0版本àTO前到TO后的项目开发时间来写的。我在这里列一下帖子里几个没有提到的内容。
Leader要注意把握验证流程,上FPGA验证前除了仿真和检查FPGA时序以外,还要做CDC和Lint等静态验证检查。
Leader及时给项目组内新同事培训工作环境基本技能,避免组内同事出现因为工作环境影响工作效率(比如不同的工具对机器的要求侧重点不同,有的要求cache大,有的要求memory大,可能要综合考虑 cache\memory\cpu-core-num\cpu-frequency等因素。如果MIS没有按照机器性能来进行LSF计算资源划分的话,使用LSF预先配置的分配策略可能会把任务提交到不合适的机器上.)
注意避免验证工程师过度依赖模块级环境。比如FPGA上报了问题,模块级上复现不出来就认为没有功能问题了。
Leader一定要及时总结所有仿真遗漏的bug,开发阶段通常是设计工程师项目后期模块级环境跑仿真暴露以及FPGA上发现的bug. 有的bug可能是由于FPGA在仿真验证过之前就开始导致遗漏的,对于的确是验证工程师遗漏的bug要特别关注。
一般Power数据是来源于门级仿真的VCD或者Saif文件,随着设计越来越大,有可能导致VCD文件过大,这种情况下及时与跑power分析的同事沟通,看有没有合适的手段解决。有的硬件加速器可以内部分析哪段时间翻转剧烈,可以根据这个信息来dump vcd.注意一点:当要dump的信号特别多的时候,其实dump vcd和dump fsdb这种压缩格式的文件大小是差不多的(大量的存储用来构建信号名的表了),这种情况下直接dump vcd就可以了,还可以避免引入dump波形的PLI和后面的格式转换工作。
理论上说验证是做不完的,有所为有所不为。leader不能把事情大包大揽,有些事情在资源有限的情况下要推出去。
及时总结记录和分析
重用程度高的项目很容易犯经验错误,千万谨慎。改动的地方加强review.
全部0条评论
快来发表一下你的评论吧 !