我跟你谈SV接口类,你却以为我跟你谈接口?

这个话题已经积郁在胸口很久了,然而各种事情每天都在侵占着路桑的大(ling)脑(hun),关于接口类(interface class)的介绍和应用一拖再拖,直到今天无意间翻出一篇很好的文章,来自于ARM公司Stan Sokorac。

SystemVerilog Interface Classes – More Useful Than You Thought (2nd place best paper award)

在发表这篇论文的时候,他已经是ARM的senior principle…看了他的LinkedIn,虽然在论文上面不多产,但是他的另外一篇,也是我特别想介绍的,就是2017年US DVCon的最佳论文。

Optimizing Random Test Constraints Using Machine Learning Algorithms (1st place, best paper award)

从他的计算机工程背景和技能树来看,做验证如果想要突破,必须得从软件开发入手,重新学习设计模式,继而在UVM的结构平台上面做定制化开发,不然也只能吃UVM标准的套餐了。Stan Sokorac先生已经从ARM离职,到一家Tenstorrent开发深度学习处理器去了…不得不说,SoC模式限制了我的想象力啊。

接下来那我主要就Sokorac先生的这篇论文来谈接口类在验证平台中的实践

概述

interface在SV中已经被“接口”即通信连接所指的语义所占领了。而在软件世界中,例如Java、C#、Swift、D等语言都支持另外所指的interface软件编程方式。所以诸君请对这篇文章中的interface(接口)加以辨别,即我们会谈到SV标准中的interface class的用法。不过由于接口类的概念是在SV 2012标准中提出,而在OVM、UVM的标准制定已经更早,因此SV与UVM在这一有用的接口类推广上面失之交臂。

实际上,这一影响要更加深远,由于缺少UVM标准的助力,而大多数verifier又直接面向UVM而非SV的验证平台,所以接口类这一新的特性并没有被很好地推广开来,甚至被低估或者不被喜爱。而对于习惯软件思维模式的资深verifier,例如Sokorac先生,接口类简直是任其在UVM接口中冲浪的帆板,别提多自由了。我们有幸得到这份非常高质量而又语言简明易懂的论文,可从中窥得ARM公司在处理器验证时的一些定制化使用方式。

实际上路桑所在的公司也有基于UVM深度定制的一些验证平台和软件包,其中一些已经在或者将在DVCon、SNUG这些会议中发表,一些仍然还身处深闺人未识。有兴趣的童鞋,可以在路桑的个人网页下载路桑以及其它的优秀论文。路桑的个人网址如下:

www.rockeric.com

接下来路桑将参考论文,给出四种接口类的典型使用方式。尽量取其精华(实际上全部都是精华,很难再分割摘取),使诸君认识或者重新认识接口类的蓝筹地位,也希望好学的童鞋可以下载此文,多加阅读几遍,在接下来的大型UVM结构模式中,如果你发现uvm_event或者uvm_analysis_port的使用让你代码增多、维护变得繁重的时候,希望你可以想起来还有接口类这样一种清爽的框架实现方式。

A. 观察者(subscriber)模式

在以往monitor检测到数据需要发送至各个checker或者scoreboard,甚至多个的情况下,需要进行如下操作:

  • 在更高层环境的connect阶段就确定连接关系,即monitor(publisher)与其它收听组件(listener)的关系,例如checker或者scoreboard。
  • 在连接时,需要使用uvm_analysis_port完成“一对多”的数据传送要求,并且在各个收听组件侧,需要实现相应的write()方法

不过这样做也有一些限制,例如对于由SV而非UVM搭建的测试平台无法很好地实现一对多的数据传送,同时由于连接关系是“静态”的,使得无法在run的阶段随时添加数据传送管道,并且也只有uvm_component才可以参与到TLM端口连接关系当中,因此如果是sequence与test或者driver之间发生事件控制的订阅关系,那么sequence只能通过其p_sequencer延伸到组件层次关系,再利用p_sequencer与其它组件完成通信,非常地掣肘。

另外一个不方便的地方在于,每次通信只能先于一种单一的事务,如果想要传递两个以上不同种类的事务,则需要将一个事务作为成员,封装在另一个事务中,也不方便,所以这是由于write()方法的单一参数所带来的不便。

那如果我们使用了接口类,世界会不会变得有序而灵活呢?来看下面这个例子,首先声明一个接口类resolve_listener,接下来,所有需要从monitor接收数据的组件在定义时,需要使用声明“implements resolve_listener”,即表示它们的定义中会实现new_resolve()方法,即处理从monitor接收到数据的方法。那么monitor呢?它只需要将所有的listener声明为队列m_resolve_listener[$],因为它也不知道会有多少个监听它的组件,而在run阶段,当它每次广播的时候,它就会利用foreach每次轮询调用各个监听组件的new_resolve()方法。

那么monitor与所有监听组件例如resolve_checker的联系发生在哪儿呢?

  • 凡是监听组件创建时,需要调用m_config.monitor.add_listener(this),添加其到monitor的广播列表里
  • 凡是monitor在广播时,将会对收听列表中的所有组件一一广播其事务arm_txn_resolve。

再来看,通过接口类实现了什么帮助呢?

  1. 不再依赖于UVM的uvm_analysis_port,即在任何SV环境中都可以完成一对多的广播-收听(publish-listen)软件设计模式。
  2. 不再要求所有的连接需要发生在UVM的connect阶段,也不需要广播方或者收听方必须是uvm_component,这大大提高了实现便利性,脱离了UVM的组织结构限制。例如sequence与test或者driver可直接实现通信,不再需要间接通过sequencer来周折完成。
  3. 对于广播的方法,不再限定于其内容和参数个数,因此当需要传递多个参数时,利用接口类将比TLM传送事务更加方便。

B. 伪多继承(pseudo-multiple-inheritance)

SV无法直接实现多继承,使得不少从specman或者软件世界迁移过来的人产生遗憾。多继承是AOP的典型特性,可以查看我们之前的一篇文章:

OOP(面向对象)的硬件设计思路就够头疼了,还搞什么AOP?

这篇文章中提到了在处理器的指令验证中,难免有一些“复合”指令,即C指令的特性可能是由A指令和B指令的合集。例如Load、Store与DMB的类型指令是在继承树上是分开的,但是Load-Acquire(LDAR)或者Store-Release(STLR)则是由之前两种指令类型的组合

在实现LDAR的指令定义时,按照SV的局限,我们可能依然继承于Load指令,而采用代码“拷贝”的方式,将另外一部分指令代码从DMB类从完全拷贝过来,但这难免会给代码维护带来麻烦。或者也可以将它们之间的公共代码放在公共文件内,继而凡是使用到这些方法的类,例如DMB或者LDAR都采用`include的方式来完成代码编辑工作,不过这还是会让代码不那么直接,曲径通幽。

而我们可以利用接口类,将这一共同部分抽象出来,再分别由LADR和DMB指令类“implement barrier”,并实现预定义的方法。对于LADR,它的定义形式则变为了“class ladr extends load implements barrier”,实现了从分别继承于load和barrier的目的。

C. 数据串行组织

对于复杂环境的调试,小伙伴们是怎么做的?路桑指的是,如果有多个UVC在发送、监测不同种类的事务,那么你是如何记录和分析他们的?一些仿真器有较灵活的监测方式,例如Questasim的事务记录功能就不错,还有VCS的SmartLog也能提供一些帮助。不过还是比不上自己定制化的来得贴心好用,而Sokorac分享的一种在原有验证环境基础上可以直接扩展的,只需添加少量代码的解决方案,可以将仿真时间窗口中,任何类型的事务都统一到一个调试方案中,例如统一的TLM事件定义:

而对于项目中新定义的事务,只需要在以往继承于uvm_sequence_item的事务,附带“implements arm_event”,实现其预定义的方法即可完成统一的事件格式,继而在事务创建时(new()函数),可利用中心化的arm_event_recorder完成统一的、串行的仿真数据组织,继而将数据格式高度统一化、串行化,便于后期的数据跟踪调试,也使得数据的来源、目的地、数据之间的联系一目了然。

那么如果是已有的环境,已经定义过的sequence item的旧环境,还有救吗?当然!可以在原有代码(如果允许修改的话),添加如上的代码即可;如果是商业VIP库,那么我们可以考虑采取定义子类,再由顶层覆盖(override)的方式,使得原有环境中所有的sequence item可以最终取得一致的数据类型,并且在创建时可以自动完成数据记录工作。

D. 对象化计时方式

最后这个话题更加有趣,在验证环境中,我们经常会采用@clk的方式来增加延迟,然而这种方式使得代码修改时,非常容易影响随机数生成器(RNG, Random Number Generator)的稳定性。关于随机发生器稳定性的讨论,可以阅读路桑这篇文章:

SV组件实现篇之六:激励器的随机化(下)

为了提高随机的稳定性,可以将所有的时钟等待方法都交由中心化的计时器对象处理。这里中心化的计时对象类型为clocking_center,它也采取轮询的方式为每一个需要时钟的对象进行“授时”。而对于以前需要独立等待时钟的各个组件,例如interface_timeout_monitor类,则可以一开始进行授时需求登记(connect阶段),而在接口类clockable的计时方法中tock()实现中,进行计时的逻辑实现。由此完成了中心化的授时工作,而分散的组件则只需要实现每次计时的逻辑,不再需要出现“@clk”的语句。简单来看,更新后的组件则不再需要任何时钟等待,这也促进了随机过程的稳定性。

总结

这篇论文介绍的四种方式都非常有帮助,可谓让人大开眼界。

广播-收听的设计模式可以利用接口类进一步提高灵活性,不过值得探讨的是,UVM的哲学是重视结构稳定性的,即TLM端口都是为了完成组件之间的通信,而在设计时并没有为sequence与uvm_component通信预留方法。如果广泛地引入接口类实现任何对象之间的通信,那需要考虑代码的一致性,而不是各种混杂使用容易给代码维护造成负担。另外,路桑还是认为UVM新手不应该太自由太任性,需要UVM套路的管束。待其理解UVM哲学后,使用SV的接口类,则可以协助其完成平时只有依靠uvm_event或者其它方式才能完成的对象通信。

多继承的模式也能减轻代码的体积,简化类的继承层次。由此,verifier可在类定义时进一步抽象,哪些行为可能是介于多种不同类的共同行为,而考虑将其抽象为接口类。数据的串行结构化可以在已有环境上完成数据格式的统一化,便于后期的数据统计分析和调试跟踪

这篇文章值得多看多回顾,在将来如果遇到上述的一些困难,希望你脑海里还能闪过一个概念——“接口类(interface class)”而不是“接口(interface)”。

谢谢你对路科验证的关注,你的支持是我们保持前行的动力。

点击查看原版论文

 

发表评论

邮箱地址不会被公开。 必填项已用*标注

陕ICP备18003383号-1