一种基于Cycle-Accurate Level SystemC参考模型验证大规模ASIC芯片的方法
华为技术有限公司高端以太网芯片项目组
yujun@huawei.com
摘要
逻辑规模达到一定程度以后,芯片的验证用传统的方法已经难以驾驭,参考模型验证正在得到越来越广泛的应用。在不同场合下,模型精度也不同。本文描述了采用SystemC建立Cycle-Accurate级参考模型,测试向量同时激励参考模型和RTL代码,通过比较输出模块中间结果来定位故障, 达到快速准确验证目标的一种验证方法。本方法已经在多款大规模数据通信芯片上成功使用,结果表明验证排错和故障定位的时间大为缩短,验证自动化、标准化水平极大提高。本文同时也探讨了在使用IP CORE后,由此引入逻辑黑盒对Cycle-Accurate级参考模型设计的影响及应对策略。
1 引言
目前ASIC业界对于较大规模的芯片设计,如CPU、SoC等芯片,普遍在系统设计阶段建立行为级Reference Model(RM)来辅助设计。实践表明,行为级RM对于确定体系结构、选择算法、验证规格、描述设计甚至评估性能都非常有效。在结构、算法确定以后,或者本来体系结构或算法就相对成熟的芯片,项目的难点就转移到如何验证系统,如何保证内部数据可靠的传递,各种复杂协议的支持,以及队列间的调度等等满足设计的要求。从验证的角度来看,行为级RM有时候就显得过于简单。本文结合一款大容量高速通信芯片开发验证的实践经验,来描述Cycle_Accurate Level(CAL) RM在验证领域的使用方法。
2 基于RM的验证思路
我们构造了1150条测试向量,后期通过Cover Meter工具检测代码覆盖率补充向量。同时考虑扫描到尽可能多的corner cases,后期还要做随机测试。对于这样的验证规模,它的验证方法需要仔细考虑。
2.1 传统验证结构vs. 基于RM验证结构
传统验证方法的基本思路如下图:

这种验证方法的checking工作依靠复杂的 BFM 设计并结合人工完成,对于中小规模的芯片来说没有问题,但随着逻辑规模的增加,向量的数量和输出结果的判断难度也随之上升,人工checking的难度不断加大。当向量数目数以千计的时候,这种验证方法就力不从心了。为解决验证效率问题,Assertion Based Verification(如 OVA)提供了一类适于向量构造及结果检查的验证语言及验证环境开发工具,可适应较复杂的芯片,但目前的应用还不广泛。
当前使用较多的方法是引入RM,如下图:
向量同时激励RTL和RM 以产生两套输出结果,由于 RM 和 RTL 代码是由不同的工程师依据同样的芯片规格开发的,两个模型出现同样错误的概率非常小,因而通过输出结果的一致性比较来判断向量的通过与否,就可以确定 RTL 设计是否达到了设计规格的要求。

输出结果的比较有即时比较(on the fly)和事后比较两种方式。
即时比较是在向量运行的同时对向量输出结果进行比较,如有不一致的状况出现可立即通知工程师,不需要等向量全部运行完毕。对于即时比较方式,如果 RM 使用 C 语言开发而 RTL 模型及 Testbench 使用 Verilog 开发,则通常需要通过 PLI 接口才能使 C 模型和 VCS等 Verilog仿真器协同工作,项目开发过程将涉及较多的技术问题,向量运行速度也比较慢。如果使用 OpenVera 开发 RM 及 Testbench,因 Vera 已经和 VCS 等 Verilog 仿真器无缝衔接,不再需要开发 PLI 接口。即时比较方式下 BFM 的设计是验证工作的主要工作内容。即时比较的方式比较适合向量复杂程度高,运行时间长的场合。
使用事后比较的方式,向量运行时用文件方式记录 RM 和 RTL 模型的结果,向量运行完毕后通过检查结果文件确定向量是否通过。事后比较的方式比较适合向量复杂程度低,运行时间短的环境,否则事后检查及故障定位将会比较困难,这就要求对向量的设计和构造进行精心考虑。事后比较的方式可以简化 RM 及 RTL 为验证进行的调整,Testbench 的设计也比较简单,但需要为结果文件的比较开发 scripts。另外,由于事后比较不需要 RM 和 RTL 的同时仿真,RM 的开发可以使用任何设计语言。
在我们的高端以太网交换芯片验证方案中,我们采用了事后比较方式的 RM 验证结构。
2.2 内部监测点便于定位问题
与仅比较系统最终运行结果的事后比较方式不同的是,为了便于分析结果不一致的向量,我们在系统内部添加监测点,如图3,所有的模块接口信号都dump出来,一旦出错,马上能定位到模块,这对于验证人员提交给设计人员很有帮助。

对于比较复杂的模块,监测点可以加到内部子模块一级,以及一些关键参数。比如队列管理模块4涉及一百多个优先级队列,多种调度算法,非常复杂,所以内部又安排了多个监测点,每个监测点dump出相关的信息,以出队监测点qmdq.dump文件为例:

该dump文件监控一个包调度出队时刻的相关参数,每一行代表一次出队操作。参数包含了时间信息、端口、队列状态、当前队列长度、当前端口占用系统资源等。由于队列是链表数据结构,dump文件也打印出链表头尾指针的值。可以看出,即便一个监测点的RM和RTL比较也非常细致,在多个监测点覆盖之下,任何一点出错都会马上暴露出来。
为进行文件比较,我们开发了一个小工具,可以很方便地显示出哪些向量pass,哪些fail以及fail的具体位置。

图5中m015005向量就是模块4的问题导致后续模块出错,测试人员只需要提交模块4的设计者即可。而模块4的设计者又可以通过命令cktest –t int_top –blk 模块4 –v m015005
进一步定位到模块4内部子模块一级,通过下图:

容易看到子模块1、5出错,分析出子模块5的调度算法有问题,影响到子模块1出队也通不过。
总之内部监测点越多,定位越准。我们的芯片监测点超过100个,不仅能迅速定位到责任人,而且可以帮助责任人debug,测试自动化很高。
2.3 建模语言选择SystemC
在建模语言的选择上业界一直存在争论,我们这里采用的是SystemC语言。在最早开始设计的时候,由于涉及到HASH算法和查找表的算法的选择,我们用C语言做了一些仿真。后来在确定缓存容量的时候,由于没有算法可套,只能建模仿真硬件实现,我们在原来的仿真环境加上调度程序,初步实现了缓存申请释放仿真过程,形成了一个系统建模的雏形。当仿真要继续深入下去时,对调度程序要求越来越高,调度程序逐渐成为一个瓶颈。其实,有了SystemC,调度程序根本不必要自己编写。SystemC内核包含了一个功能完善的调度机制,我们只要在自己的C函数里指明触发条件,剩下的就交由SystemC去实现。这一点和verilog仿真的原理一样,SystemC这里扮演了VCS的角色。
图7演示了一个简单的SystemC描述的硬件模块,

其中sc_uint是SystemC定义的一个类,用于描述任意宽度的数据类型,sensitive_pos << clk 这一句指明clk时钟上升沿是process的触发条件,对应verilog里面敏感表的概念,除此之外都是C++里的东西。可以看出,有了SystemC的帮助,C++语言也可以方便地描述硬件,用户只用把注意力集中到过程的描述。
相对其他建模语言,我们以为SystemC主要有下列几个好处:
● 基于程序员熟悉的C++语言,建模工程师不必学习新语言;
● SystemC的源代码开放,使用者可以去深入地研究,甚至加以改动。
● 集成工具良好的用户界面帮助下,比如Synopsys的CoCentric System Studio,模型设计快速成型,上手十分容易。
2.4 行为级RM vs. 时序级RM
模型实现的精度是实现RM首先要考虑的问题,不同精度的模型建模工作量相差很大。行为级的RM工作量可能只有RTL设计的10%,CAL的RM工作量则大很多,有时候甚至可能接近RTL设计。
行为级RM是目前使用最普遍的建模方式。因为好的行为级RM,可以验证方案和算法的可行性,功能的正确性和完备性,以及提前和软件的协同仿真,工作量相对小,性价比非常高。但是,在有些场合下,行为级RM就不够了,需要做到时序级的,Cycle_Based,甚至Cycle_Accurate才有意义。对于把RM用于验证场合,就面临这样的问题。比如行为级的参考模型也可以通过模块间的比较来定位问题,但在存在调度的场合下会给comparing带来麻烦。不幸的是,在大型通信芯片里,调度功能几乎都是必不可少的。

图8演示一个单队列的入出队过程,timeslot(TS)表示调度的最小时间单位。入队报文是以10TS为间隔匀速准备入队,入队之前有一些处理如队列空满检查、流量的计算(如果是超标流量则不允许入队)等,在 RTL 设计实现时这些入对处理会耗费5个TS,而 RM 设计时因高级语言不受硬件资源限制,可以在一个 TS 内做完所有的计算和判断。这种时延的不一致导致 RM 和 RTL 的出队时刻的不一致,comparing自然出错。不过这个问题很容易规避,比如在比较文件小工具中不去比较时间信息,或者干脆不打印时间信息即可。
但是在存在调度的场合下问题变得棘手:

在图8的基础上再增加一个队列,这种场合下RM和RTL可能导致包“乱序“,如图9中红字部分所示。假设出队也是以10TS匀速调度的,即TS2、TS12、TS22时刻进行一次调度。RM由于在TS1完成入队,所以TS2这一次出队调度可以调度到数据包0003,但RTL的入队则由于延时的关系,导致TS2时刻队列1还看不到包0003,所以只好调度出队列2里面已经在等待的包0001,结果RM和RTL间出包次序不一致,形成了“乱序”。虽然这种“乱序“不是功能错误——同一条队列内的包没有乱序——但却给文件的比较带来麻烦。就算可以通过编程识别出这是一种功能正确的乱序,但由于后面所有的文件都受到乱序的影响(前一模块的输出是后一模块的输入激励),难道以后所有的文件比较都通过编程来解决吗?另外,由于引起队列调度“乱序”的原因非常多,一些不是功能错误,另一些则可能是因为调度算法出现错误而引起,如果我们在文件比较时以屏蔽的方式予以放过,则可能会漏掉可能的“Bug”。
合理的做法是建立时序精度级的RM,RM 设计时候充分考虑RTL的实现以插入必要的延时,取得关键时间点上——这里是入队动作——RM和RTL在时序精度级的完全一致,这将不仅有助于向量结果的比较,也有助于发现“埋藏”很深的 “Bug”。
判断关键点和估计RTL时延是时序精度级RM设计的一个非常重要的工作。但准确预测RTL的时延往往并不容易,复杂情况下尤其如此。解决这个问题要从两点入手:
● RM编程时延要参数化;
● 方案设计的时候在满足速度要求的前提下,留出一定的裕度,方便后面RTL的设计。
由于 RM 是在芯片设计规格完成后独立开发的,此时验证向量的构造也刚刚开始,因而在 RM 方案设计和代码开发时容易专注于功能的实现,而忽略了监测点的设计——根本原因是方案设计常常不能完全预见到验证的需求,如果到了向量构造阶段才进行 RM 和RTL监测点的设计往往会引起很多困难,特别是RTL人员monitor代码设计的困难(很多参数可能已经回写到RAM里,再读出来一方面有时延,一方面可能还有仲裁问题,不像RM保存在数组里,随时可以读写),甚至不得不修改RTL编码来迎合监测点的添加,导致验证的时候相当多的工作是花在monitor调试上,延误验证进度。因而在 RM 设计开发过程中保持和 RTL 设计人员的适度联系是提高验证效率的重要手段。
2.5 IP CORE带来的CAL建模难题
对于大规模的芯片设计来说,常常会使用到IP CORE。一般这些IP CORE对用户来说是一个黑盒子,用户看到的只是接口信号,只知道接口信号间的逻辑关系,但不知道输入激励到输出的时延关系。由于内部实现未知,如果该IP CORE没有自带模型的话RM设计者将难以实现CAL的建模。
我们也曾遇到这个问题。我们的输出模块是购买的CORE,没有附带SystemC模型。虽然有RTL源码,但由于没有文档,很难做到CAL的建模。为了早期调试RM,我们建立了一个Cycle Based Level的简化版本,包含该模块的基本功能如数据缓存,CRC计算等以便 RM 可以形成一个完整运行的系统。
等到RTL代码完成并进入与 RM 比较结果的向量验证阶段后,我们遇到了输出模块反压回来的信号时间在 RM 和 RTL 中不一致的问题。如图10所示,

当输出模块内的FIFO满后,必然向前一级模块反压,引起调度模块停止调度该端口,直到该FIFO内数据转发出去,反压解除,调度才能恢复。由于RM的输出模块和RTL的输出模块不是CAL级匹配,那么反压产生的时间也就不能保证一致,这最终引起队列调度行为不一致,如前面分析,可能导致“乱序“。

为解决这个问题, RM引入RTL的输出模块反压信号,不再使用自己的输出模块模型,而改用一个“快速输出模块“模型,如图11所示。这个快速模型有更大的FIFO和更快的发送速度,不拘泥于实际物理发送速率,可以迅速把FIFO的数据读空,目的就是保证不会比RTL先产生反压,不会发生FIFO溢出和数据被覆盖,为了使 RM 看到来自 RTL 的反压信号,需要在 RTL 模型中设计 monitor 将反压信号及时间点保存在文件中,RM 在运行时使用 BFM 读取该文件作为激励,RM 的有关处理模块就可以在 RTL 输出模块的反压信号下进行操作,从而保证了 RM 与 RTL 的完全一致。这样做的唯一问题是输出模块没有得到验证,但作为购买的IP CORE,本来也不是我们的验证的目标。
3 结论
相对行为级模型来说,CAL级的模型工作量更大,要考虑的问题更细。但磨刀不误砍柴功,有了CAL级的模型,问题定位、验证自动化等问题便迎刃而解。验证工程师得以从繁杂的数据、波形的观察工作中解放出来,把精力集中到向量设计上,通过Cover Meter这样的覆盖率工具来不断提高代码覆盖率水平,完善向量集,最终达到验证的目标。



