UVM寄存器模型这么难学,有没有更容易点的?

rockeric.com

这篇论文很完整,如果读完路桑的分析你觉得确实解决了你们的痛点,不妨从【阅读原文】下载完整论文。在OVM时代,OVM package本身是没有寄存器模型方案的,于是Cadence的Verisity人马(Specman的EDA公司)就做了一个OVM_RGM package,那还是10年以前的事情了。路桑那会从Specman迁移到OVM,也就很快适应了这个专门给OVM做的寄存器模型,因为它的使用方法跟Specman中的寄存器模型几乎是代码的移植,而整体策略和API都是一样的。

后来,UVM提供了寄存器模型方案,而且还可以独立集成到OVM环境中,这个方案也就渐渐替代了原来的OVM_RGM方案,再到后来,OVM到UVM的迁移方案也变得有固定流程可以遵循(这一点可以在红宝书的第18章“OVM到UVM的移植”中获得更详细的内容)。UVM提供了我们想要的,一切看起来都很完美不是吗?可伴随着对UVM的理解越来越深入,对其它软件设计模式的学习,你发现可以在UVM基础上做不少优化,甚至用自己的类和包来替代UVM原生的方案。

这篇论文《Simpler Register Model Package for UVM Testbenches》来自于DVCon US 2018,Juniper Network公司的验证工程师Sanjeev Singh经过公司的许可,已经将他们自己的寄存器模型解决方案SRM(Simple Register Model)开源出来了(我很佩服这家公司的品德,其实路桑也有很多东西想要开源的…只不过…)。想下载这个寄存器模型查看源代码的可以看下面这个链接:

https://github.com/Juniper/simple_reg_model

想查看具体案例的,可以从下面这个链接获取:

https://github.com/sanjeevs/srm_sap1

接下来我将这篇文章核心的观点整理一下,尽量让你在5分钟内获取它的方案对你们有没有什么启示?

问题背景

之所以选择这篇论文,是我还记得今年DVCon China上海,我在演讲论文《A Full-scale System Monitor and Evaluation Solution for SoC Verification》的时候,就有听众问我一个问题,“你们的系统监测方案的一个重要特性,是可以监测全局的寄存器访问,并且统计寄存器访问的覆盖率,那么你们是如何对全局的寄存器建模的?”——Good question! 我们在对IP级的寄存器建模的时候不会考虑到几百个寄存器对象例化,然而在SoC级别,很轻松就会有上万个寄存器,我到现在还记得我们在一开始按照传统的寄存器模型集成方法,顶层的reg_block集成了几十个模块的reg_block以后,我们懵逼了,因为要例化上万的寄存器的时间比原有仿真时间增加了将近半个小时啊…怎么能允许这种事情呢…于是乎,我们不得不针对寄存器模型做了例化控制参数,无法允许同时有上万个寄存器参与例化。

这篇论文遇到的问题与我们遇到的问题类似,都是由于UVM寄存器模型的在构建的时候,伴随着寄存器的数量增加会带来无法忍受的内存消耗。只不过,他们的痛点是在为内存建模的时候。uvm_mem这个类在UVM寄存器模型中很少会用到,之所以这样讲是因为uvm_mem这个类有点鸡肋:

  • 无法支持mirror value,desired value,所以无法从寄存器模型直接获取值,而必须通过write/read在前门后者后门访问。
  • 如果要对内存建模,UVM没有太好的办法,只能将他们按照寄存器建模来处理。简单来说,如果你有32K的内存要建模,那么它们需要对应8K个寄存器,而且是静态的!要消耗很大的内存去建模。
  • 如果要对大规模的寄存器模型随机化,那么每次随机化模型要消耗的时间也是很恐怖的!因为随机化uvm_reg_block的时间不是线性的,而是指数增长的

此外,论文也在吐槽UVM寄存器模型不友好的API和背后的机制:

  • write/read/peek/poke/update/mirror/set/get… 这么多的API要分别对应到去更新desired value,mirror value和actual value,很多新手对于UVM寄存器模型的机制望而却步啊!
  • 路桑也同意他的观点,UVM寄存器模型的理解是不那么容易的,譬如你想获取某个寄存器的mirror value,那么你是无法在UVM环境中查找到它的值的,而你只可以查到它每个reg_field的mirror value!因为UVM环境中的最小单元是uvm_reg_field,而不是uvm_reg!

OK…以上描述的问题可以这样归类:

  • UVM无法对uvm_mem建立值映射的模型
  • UVM没有对大量的寄存器例化和随机化做优化
  • UVM寄存器模型的API方法容易让人混淆

那么这篇论文是怎么解决这个问题的呢?首先我从论文的背景来看,他们的主要产品在做路由、交换器和其它网络连接软硬件解决方案,从他的工作内容来看,他在Juniper做的事情是针对packet processing processors、packet forwarding planes、low latency memory controller,都是跟datapath processing有关的芯片设计 ,理解了这一背景,我们再来看他们是如何对处理器的寄存器和片上存储建模的。

解决方案核心要素一:简化和分离类的关系

第一,在他们的SRM模型中,不再区分mirror value,desired value,而是将它们合二为一,即模型一侧的映射数值。同时,模型中有reg和table(reg array,可类比on-chip mem)的概念,table也是由reg构建的关联数组,而reg依然是由field构成的。从源代码来看,srm_field相比于uvm_reg_field,只提供必要的write/read方法。

第二,无论是srm_reg还是srm_table,他们将值存储和值随机做了分离处理。也就是说,srm_reg和srm_table只有保存value的职责(实际由srm_field承担),而如果要随机化,则交给了另外的constraint类,例如下面的rl_constr类可以自动生成,而在寄存器随机化过程中,可以对其例化和随机处理。这种值存储和值随机的分离方式,使得我们不能随机一个reg和table,而是利用constraint类先产生随机值,再将其赋值到reg和table。

第三,从源代码的<4K行与UVM原生代码>22K行的对比来看,做了大幅度的剪裁只保留了必要的成员变量和方法,这使得同样的树状结构,SRM的模型体积要比UVM原生的模型小得多。

解决方案核心要素二:减低内存消耗

如果对于普通寄存器建模来看,对于类的简化使得树状结构带来的内存开销要比UVM原生模型要低,而对于SRM对于寄存器随机化的要求,即对寄存器增量随机的方式相比于对整个寄存器模型做递归式随机所需要的内存开销也要低。此外,尤其对于内存模型的建模也能够对我们的工作有启发。

srm_table即内存模型,也可以由srm_reg的数组构成。只要定义要每个entry(srm_reg)的field构成,则可以进一步构成table。这种定义table的方式在于一开始其内部只需要定义并且例化table的一个最小单元srm_table_entry。无论table多大,只有当接下来对其table中某个index entry做读写操作的时候,table才会从_prototype句柄克隆entry对象,为期添加实际需要的entry,这种关联数组的方式很大程度上降低了内存开销

解决方案核心要素三:精简访问方法

首先,SRM简化了寄存器访问的API,将容易让人头疼的UVM寄存器API简化为了下面四种方法:

其次,重组了寄存器模型的构建方式,即srm_reg和srm_table都继承于srm_node,srm_table可以包含多个srm_reg,就这么简单!srm_node这个基类为了能够在层次化模型中生成树状结构,需要有parent、_children[$]、offset_table[string]等,所以UVM原生的uvm_reg_map在SRM中已经融合到srm_node这个基本单元了,这表示无论是srm_reg还是srm_table,都可以与上层的srm_node形成新的树状结构。

又,srm_node可以对其内部的各个子节点leaf nodes做递归式的load/store/store_update,这对于批量操作存储很方便。

解决方案核心要素四:重构访问模式

原生的UVM寄存器访问模式,是将reg_block、bus_adapter与bus_agent挂载到一起,来实现前门访问(frontdoor);同时更新HDL path,来实现后门访问(backdoor),这种模式在SRM中得到了扩展,当子系统内部某些主端模块也更新寄存器时,无法从前门或者后门来模拟该激励,也无法监测该激励,因此它新添加了侧门访问模式(sidedoor)。这一更新非常符合SoC多路访问寄存器模块的需求。

而无论是哪一种访问方式,它都将其抽象为了adapter和对应agent的连接。例如sidedoor和frontdoor面对的总线不同,那么可以为其分配不同的adapter和bus agent,这就使得非常灵活。这种灵活的方式,在IP级的测试序列可能是前门访问需求,而到了系统级就变成了侧门或者后门,因此访问路径是不同的。如果是UVM原生访问,需要在write/read中表明FRONTDOOR/BACKDOOR,这对于测试用例的复用不友好。

在SRM中,不但可以为每一个srm_node制定其对应的若干个adapter,甚至还可以在调用write/read方法时,指定选择它们的adapter。这就使得,同一个测试用例,在不同场景中,只需要在顶层环境做好adapter的配置选择,而不需要修改具体的测试用例

这种方法确实让人眼前一亮,也就是说,以往UVM reg_block是将reg_map与adapter和bus_agent连接,而且只能选择一组对应的adapter和bus_agent,而在SRM,不但任何一个node(reg/table)可以指定多组adapter/bus_agent,还可以在顶层环境中,考虑到不同的测试层次、场景,指定其访问这些reg/table的adapter是frontdoor,backdoor还是sidedoor!

同时,SRM的寄存器模型也可以设置为passive模式,保持动态监测寄存器的数值,并且完成寄存器的覆盖率,这一点与路桑的论文《A Full-scale System Monitor and Evaluation Solution for SoC Verification》关于寄存器访问覆盖率有类似的实现机制。

总结

这是一篇非常新颖的论文,已经提出了一套高效解决自己所在验证领域的问题,结合作者的工作背景,如果你目前的环境也面临以下问题:

  • 需要对上万寄存器建模
  • 需要随机化数百个寄存器
  • 需要对存储建立值映射关系和前后门访问
  • 需要对同一个寄存器模型建立两种以上的adapter/bus_agent转换关系

你可以从这篇论文中获得一些启发,当然作者在该论文最后也指出了一个问题,尽管可以利用他给出的Python脚本来完成寄存器信息从描述到自动生成的流程,但是不得不面对的是,SRM与UVM原生寄存器方法在寄存器模型构成、访问方法和使用方式上面都有区别,无法完成兼容UVM原有的寄存器测试序列和集成模式。不过尽管这样,这篇有趣的论文也能够给我们平时在对UVM寄存器建模的时候注意以下几点:

  • 尽量不要构建一个包含数万个寄存器的模型,如果必须要,那请做更多的生成控制参数来动态剪裁模型。
  • 尽量不要随机化一个大型寄存器模型,而应该只针对你需要的若干寄存器做随机化。
  • 如果要对存储模型建模完成映射关系,可以扩展原有的uvm_mem,借鉴srm_table的方式,构建内部的关联数组和数据存储,因为uvm_mem自带的uvm_vreg只保留地址信息,但不会存储数据。
  • 如果你也有需求将一个reg_block映射两种以上的adapter/bus_agent,你将需要在同一个reg_block中创建多个reg_map,因为每一个reg_map只能映射一组adapter/bus_agent。在创建了多个reg_map以后,你将可以在顶层环境与各自的adapter/bus_agent连接。不过,在稍后寄存器的write/read访问当中,你还应该匹配map的参数,否则它将按照default_map,使用默认的map和bus访问。

发表评论

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

陕ICP备18003383号-1