UVM(universal verification method)作为通用验证方法学,解决了什么问题?
验证环境包括激励输入和输出数据的比对。UVM将激励、比对、reference model分别实现为不同的类。在子IP验证环境中使用的组件,可以在IP验证环境中复用。
System Verilog引入了类似C++,java的语法,可以实现为类。类给我们带来的好处就是封装、继承、多态。
展开说一下,
UVM的整体环境封装在uvm_env中,这可以看作是与真正实例化module的tb同级的环境。
UVM Sequence是负责发送transaction包的激励来源。
比如axi总线的一个transaction,在真正的verilog中,是按照clock的一拍一拍的数据和地址,在UVM中,可以定义一个uvm_sequence_item,将这一整包的数据和地址封装为成员为int address, int data[]的类;sequence就是负责产生各种各样的transaction。
注意:
1)并不是验证环境中只有一个sequence,而是可能会存在多个sequence。
比如存在以下三种sequence:
专门发送长包的sequence
专门发送短包的sequence
发送存在错包的sequence
将这三种sequence包装为一个virtual sequence,即可复用其他的验证环境,发送随机间隔的长包、短包、错包sequence。
2)sequence和transaction都是uvm_sequencet_item类型,都是object,而非component。
UVM Sequencer主要负责将Sequence产生的transaction,转发给driver。
在case中可以通过设置*sequence.start(*sequencer)指明特定sequence是运行在哪个sequencer上的。
在sequence中包括m_sequencer成员,在调用sequence的start方法时,会调用set_item_context方法,而这个方法又进一步调用了set_sequencer方法,将uvm_sequence_item中的m_sequencer成员指定了sequencer。
通过m_sequencer这个方式,sequence可以明确当前运行在哪个sequencer上,获取sequencer的信息(需要将m_sequencer通过cast转换为扩展类sequencer类型)。
UVM Driver的作用:
张强的《UVM实战》上打了比分:sequence好比是弹夹,driver好比是手枪,这个比喻
比较形象,sequence里面装满了一颗一颗的子弹,子弹的上膛,发射都是driver作用的,driver如果通用性实现的比较好,是可以发送不同类型的子弹的,这也体现了验证平台的复用。
需要注意的是,driver一般可以实现成forever的,持续的发送transaction,直到结束。这也可以理解成一个只要有子弹,就一直发射的手枪。
UVM Reference Model一般是用C/C++完成的,用于与DUT进行比对的算法,在平台中的作用:
UVM Monitor用于检测interface接口上的数据,其作用:
UVM Scoreboard用于将uvm reference model的期望输出与monitor接收到的dut实际输出进行比对。
UVM Agent负责封装sequencer、driver、monitor;之所以封装这三者,存在以下原因:
UVM 环境是基于SystemVerilog类的,设计代码在tb中是使用verilog的方式例化的,如何将设计代码的接口与driver连接呢?通过interface。
在tb_top中例化interface,与dut的接口连接;
在driver 类中例化virtual interface;使用uvm_config_db的方式即可将driver中的接口与dut的接口连接。
此外,介绍一些UVM中比较关键的概念,UVM借鉴了设计模式中的工厂模式,代理模式,单例模式等,使得UVM更加便于使用。本质上这些模式也是基于继承、多态;通过指针或者引用的形式,使基类指针可以指向各式各样的继承类。
1.Factory 工厂
在声明继承类型,如uvm_driver的子类my_driver,可以通过uvm_component_utils的形式,来对通过工厂进行注册。在创建类时,也是通过my_driver::type_id::create()进行创建,type_id本质上是uvm_object_registry的别名,通过registry,在单例factory中,对该类进行了创建。
利用工厂的一个关键好处在于可以使用重载。比如,目前使用的是my_driver,如果想批量的替换成your_driver,可以调用set_override_by_type的方法,将my_driver直接重载掉。
2.uvm_config_db方法
uvm_config_db方法可以实现在不同的component之间传递信息,即set、get。
即比如之前介绍的sequence,如果负责产生axi_transaction,而具体是怎么类型的transaction,burst的大小,地址的大小,是可以通过配置sequence而改变的。那么如何配置sequence呢?testcase是class,sequence也是class,这些component通过什么进行信息交互呢?
就是uvm_config_db,可以在使用uvm_config_db#(**)::set(*,*,*)来配置,那么uvm_config_db是如何具体工作的呢?
uvm_config_db继承自uvm_resource_db,在其内部存在静态的uvm_pool类型的关联数组m_rsc。m_rsc的索引为component,值为对应component需要set的uvm_pool。
uvm_pool的索引为loopup = {inst_name,”_M_UVM_”,filed_name},其值为uvm_resource类型的r。
如果是set:
通过r.write(value,cntxt)对r赋值,通过r.set_overide(),向全局静态的uvm_resource_pool写入这个r。
如果是get:
通过uvm_resource_pool::get()获得uvm_resouce_pool类型的rp,通过rp.lookup_regex_names获得uvm_reource_types::rsrc_q_t类型的rq,在rq中存放包括优先级的resource 队列,获取优先级最高的uvm_resource,即为返回值。
说了好多,总之,uvm_config_db的set,get是通过静态的uvm_resource_pool进行信息传递的。
uvm_resource_pool理解可以参考源码:./src/base/uvm_resource.svh
3.::type_id::create::
UVM约定俗成的要求在class中使用`uvm_component_utils(****_driver)进行注册,在创建时使用my_driver = ***_driver::type_id::create(“my_driver”, );进行创建。
这背后的机制是什么呢?
`uvm_component_utils(T)使用宏定义,其进一步调用了`m_uvm_component_regsitery_internal(T,T),在这个宏中,定义了uvm_component_registry #(T,`“S`”)类型的type_id,在uvm_component_registry中实现了create()函数。Create()函数是创建的重头戏:
Create()函数首先调用uvm_factory::get()方法,得到全局的uvm_factory类型的f,通过obj=f.create_component_by_type(get(),contxt,name,parent())创建这个obj。
4.Phase机制
Phase机制理解起来比较简单,如果把验证平台中的不同的component理解成一个班级里上课的同学,那么虽然不同的同学每节课写作业有快有慢,但是他们都是整齐划一的按照上课的时间,下课的时间,共享一个作息表。