SV组件实现篇之十二:比较器和参考模型(下)

 

SV组件实现篇之十二:比较器和参考模型(下)...



整体检查(参考模型)

除了独立拆分的检查方法之外,我们也可以按照硬件设计描述文档(端口、协议、状态机)来建立一个参考模型(reference model)。而模型的细致程度也依赖于checker的要求,检查得越细致,对参考模型的准确程度也要求越高。在日常中,除了verifier会自己写这样一个参考模型之外,有时候从系统工程师那里也可能得到了虚拟原型(virtual prototype)或者算法模型(algorithm model),该原型也是对于设计更具象的要求。对于虚拟原型和算法模型在验证环境的嵌入,我们也会在本书后面的章节中介绍。在这里,我们先讨论如何实现一个轻松一点,维护较少易复用的参考模型吧。









在整体检查的这段示例代码中,首先将参考模型定义为一个类fmt_refmod,它承担的任务就是将从fmt_ini_mon监测到的fmt_ini_trans数据通过自定义的逻辑转换为fmt_rsp_trans,因此它内部分别需要两个mailbox ini_mb和exp_mb。对于它做数据打包的成员方法fmt_refmod::ini2rsp_fmt(),有兴趣的读者可以发现,这个方法实际上是之前fmt_checker::ini2rsp_fmt()和fmt_checker::chk_len()的集成版本。因为它用来做数据打包长度的依据变为根据即将要发包时的寄存器配置数值,而不是之前从fmt_ini_trans::length得来。这就将检查数据包长度也同数据打包功能合并在了一起,而到了后期进行数据比对时,如果发生错误,那么可以想到的是,有可能是数据包长度的功能错误,也可能是本身formatter输入端数据采集的功能错误。

那么,这样一个完整的参考模型fmt_refmod一旦完成之后,就可以方便地集成到fmt_checker中。在上面的fmt_checker中除了需要例化fmt_checker::refmod之外,也需要注意新添加了方法fmt_checker::connect()。这一方法是用来对fmt_checker内部的组件同自身相连接的,而该方法的调用需要在稍后展示的外部formatter_tb中完成。通过这一方法使得fmt_checker::refmod中的虚接口和事件得到赋值,同时也将fmt_checker::refmod的mailbox赋值给fmt_checker中声明的mailbox指针。

在更新过的fmt_checker::chk_data()方法中,可以看到fmt_checker一方面从自身的rsp_mb得到来自于fmt_rsp_mon监测到的数据,另外一方面也从refmod.exp_mb中得到期望的数据,这样便可以做出数据比对了。

通过上面两个用来实现fmt_checker的不同例子,我们可以发现拆分检查和整体检查的特点

  • 拆分检查更加独立,且可以由不同的检查控制变量分开控制;整体检查更喜欢讲设计功能融合为一个整体,偏向于将硬件设计功能软件化实现,功能的独立控制能力较弱。
  • 拆分检查的实现模式是用若干个方法实现以及互相调用,整体检查则是将软件化的功能包装为一个独立的类,即参考模型(reference model)。
  • 拆分检查中,checker即担任了模拟硬件功能的角色,也承担了数据比较和报告的责任在整体检查中,我们倾向于将硬件模拟的任务交给参考模型,而checker只需要取得实际数据和期望数据,做数据比较和报告即可
  • 拆分检查的难点在于,需要确认检查的多个功能点足够涵盖设计整体的功能;而整体检查的难点在于,需要考虑参考模型的精细程度和维护成本


时序检查

最后让我们将目光聚焦在时序检查上面。读者可以回顾之前fmt_rsp_mon::mon_trans()方法,在采集数据时,只需要关心vif.mon_fmt_start从何时跳转为1便开始采集数据,而无需注意formatter其它输出信号是否满足协议要求。因为我们只需要将协议检查的任务独立开来,变使得采集数据部分变得更加干净一些。就之前的检查方式来看,它们有如下特点:

  • 事件触发端或者数据收集端均在monitor一侧。
  • 根据事件触发或者传输的数据进行比较的任务则在checker一侧。


那么,我们是否需要也将时序检查放置在monitor内呢?这里,我们倾向于将时序检查即协议检查部分放置在interface中。这其中的好处在于,一旦协议检查与interface相绑定,那么interface的作用除了DUT与TB的连接、数据驱动和信号采样之外,又添加了时序检查的功能。而且,就时序检查的功能来看,如果有时候只需要时序检查的功能,那么只需要通过SV绑定(bind)的功能就可以让interface承担时序检查的任务,这也扩展了interface的作用。

下面是对两个interface fmt_ini_if和fmt_rsp_if扩展后的示例代码:







首先,在这两个接口中,都定义了控制检查协议的变量en_chk_prot,便于顶层控制。接下来,在接口中定义了若干个检查接口时序的检查方法,这些方法的功能分别是:

  • fmt_ini_if::chk_val_ack 要求ack为高时,valid也应该保持为高。
  • fmt_rsp_if::chk_req_grant 要求在grant置为高之后的下一周期,req应该拉低。
  • fmt_rsp_if::chk_stable_id_length 要求自req拉高之后到end拉高的时间内,chid和length都应该保持不变。
  • fmt_rsp_if::chk_grant_start 要求grant置为高之后的下一周,start应该拉高。
  • fmt_rsp_if::chk_start_pulse 要求start信号为高的周期为一个时钟周期。
  • fmt_rsp_if::chk_end_pulse 要求end信号为高的周期为一个时钟周期。
  • fmt_rsp_if::chk_packet_len 要求从start拉高到end拉高的周期内,发送数据的个数与length数值相同。


可以看到,通过上述的方法,可以将协议检查中的各个时序要求细分为若干个需要检查的时序要求,而分别对这些时序要求用独立的方法进行检查

在介绍完这三个常见的检查分类:异常检查、常规检查和时序检查之后,读者可以从中发现,异常检查和时序检查相同的地方在于,触发它们执行检查任务的都是一些触发了的事件。异常检查的事件触发频率较低,而时序检查的事件触发频率较高,对于事件触发的检查,我们还可以通过SV的断言检查来实现。这一点,在本书的后续章节也会涵盖到。

组件连接

最后,我们来看看,要完成interface、monitor和checker之间的连接和整体运转,在顶层应该如何连接?下面的示例代码实formatter_tb的一部分,用来说明上述组件之间的建立和连接。





从上面formatter_tb的实现来看,它通过了事件build_end_e和connect_end_e将组件的建立、连接和运行分为了三个阶段,即三个initial过程语句块build、connect和run阶段。这三个过程之间有执行的先后顺序

  • 先需要等待build过程执行完,保证各个组件例化完成并且做完相应的配置(这里指的是控制检查功能的开关)。
  • 然后再进入connect过程,在该过程中完成了虚接口、事件和信箱的连接,这一过程可以保证各个组件都可以指向正确地接口,并且通过事件和信箱可以通知其他的组件和进行数据传输。
  • 最后进入了run过程,在该过程中各个组件保证在同一时间开始运行,各自执行自身的任务,例如monitor需要时刻监测有效数据,而checker则需要等待数据的到来并且进行数据比较。


如果将上面的过程执行绘制为更容易理解的流程框图,则从下图中可以看到,在连接阶段完成了各个组件和接口之间的连接,而这一步骤也极为重要,关系到了后面的运行阶段是否可以正常工作。



细心的读者可以注意到,上面的过程划分也是针对于软件世界里面的各个对象的,这是因为只有软件对象的创建才是从仿真开始之后才执行的,这一点并不想硬件中的实例,它们的建立和连接则是在仿真开始前就已经建立好了。从生命周期来看,硬件的实例生命贯穿到整个仿真从开始到结束,可以看做是静态的“对象”和“连接”;而软件对象的生命则是会在仿真开始后不同的时间点开始创建、连接和销毁,是一种“动态”的存在

也正因为软件对象的这一特点,就要求我们对于软件对象的建立、连接和引用要格外注意,避免对象句柄的悬空(空指针索引)。而要避免这一点,最好的方式就是将上面的各个阶段有条不紊地贯穿下去,不单单是formatter_tb要完成内部对象的三个阶段(connection、build和run),对于其内部对象自身又例化嵌套的对象,也需要完成这三个阶段。要实现这样一种层次化的过程执行顺序,我们会在下一章《SV环境集成篇》中详细探讨。

至此,本文关于checker如何实现,以及常见的三种检查类型就为读者介绍完了。下一节将作为本篇《SV组件实现篇》的收尾,将带领读者思考在验证环境中的报告如何有条理的组织,并且能够容易控制输出的信息和方便阅读

谢谢你对路科验证的关注,也欢迎你分享和转发真正的技术价值,你的支持是我们保持前行的动力。


    关注 路科验证


微信扫一扫关注公众号

0 个评论

要回复文章请先登录注册