UVM白皮书复习
第二章
常用
- UVM是个库,所有东西用类实现Class,类有函数和任务,还有成员变量
- drv所做的事在main phase完成,需要vif【env中config db传递】,负责把tr级别信息转变为DUT端口级别信息;
- uvm_info打印宏,路径索引+输出,参数123【打印信息的归类别】【打印信息的具体内容】【冗余级别】,uvm_fatal类似,但是它表示出现了重大问题无法继续下去,不需要冗余级别,会在第二个信息打印出来后停止仿真;
- 类的实例化new,只定义不实例化的类没有意义,实例化会在内存分配空间,并把该空间的指针返回,利用指针可以查看类的各个成员变量和调用类的函数任务等
- include uvm_macros.svh,包含了众多的UVM宏定义
- import uvm_pkg::,这样*编译的时候才会认识uvm_driver等类名
import不会复制文本内容。但是import可package中内容引入import语句所在的作用域,以帮助编译器能够识别被引用的类,并共享数据
include将文件中所有文本原样插入包含的文件中。这是一个预处理语句,
include在import之前执行。他的主要作用就是在package中平铺其他文件,从而在编译时能够将多个文件中定义的类置于这个包中,形成一种逻辑上的包含关系。
- factory 机制:集成在一个宏内,uvm_component_utils,作用广泛,其中一个是把drv注册到表中
- run_test创造一个drv实例,并自动调用main phase,根据类名创建了一个实例并自动调用main phase——factory机制
- objection机制——finish语句,控制验证平台的关闭,每个phase都会检查是否有objection被提起,没有立马结束仿真,有的话要等objection撤销再结束,raise objection必须在main phase中第一个消耗仿真时间的语句之前出现
- 仿真时间,$time函数打印的时间
- 运行时间,一个测试用例的运行时间CPU时间
- virtual interface,避免了绝对路径,提高了验证平台的可移植性和可重用性,之所以要用virtual,因为只有module可以用interface,类中必须要加virtual;
- run test建立了新的层次结构,脱离了top tb,必须用config db机制传递参数和引用变量
- config db机制分为set和get,寄信和收信,四个参数,set参数目标的路径索引【前两个参数组成】,成员变量,传递的数据;第三个成员变量set和get要保一致;
- build phase在uvm启动后自动执行,在new函数后main phase前,在这里实例化成员变量与config db的set和get操作传递一些数据,且要super.build_phase,是函数phase,不消耗仿真时间,但是main phase是任务phase
- transaction,uvm组件间的信息传递都是基于tr级别的,tr代表了一个协议内的一种数据包,内部定义各种参数,不同的平台有不同的tr,继承与uvm_sequence_item【方便使用sequence机制】,用uvm_object_utils注册,与component不同,tr有生命周期,在仿真的某一时间段存在;
- pre_randmosize,randmosize,post_randomize,assert(xx.randmosize)会自动在之前之后调用pre和post函数;
- env容器类,run_test只能实例化一个,在env完成相关组件定义后的实例化,run_test传递的参数是更高级的类;实例化用drv::type_id::create(“drv”,this)【只有factory注册的类才能用,方便使用重载功能,this表示父节点是env,run_test创建的实例树根名字是uvm_test_top】
- build phase执行顺序自上而下
- monitor,监测DUT的行为,需要vif【env中config db传递】,监测DUT输入输出信号变化后判定DUT的行为是否正确,monitor负责收集DUT的端口级别信息,并转变为tr级别传给ref或者scb处理;env中实例化两个mon的必要性:由于drv产生并输出tr到DUT端口,所以drv可以直接将tr信号传给ref或scb,但是在drv侧设置mon可以减少出错的概率,drv和mon可能是不同人员编写的,对协议的理解可能不同,所以不仅方便代码重用还可以减少出错;
- agent,drv和mon代码高度相似,因为二者处理的是同一种协议,在一种规则下做不同的事罢了,所以通过agt将二者封装在一起,不同的agt代表不同的协议,【可重用性】,内部可以根据is_active实现是否实例化drv和sqr;因为往往输出侧只需要mon监测,不需要drv和sqr
- 将组件的实例化交到main phase会出错,不仅可以在build phase实例化,也可以在new函数实例化,但是new函数实例化将无法通过直接赋值的方式传递is_active的值【在build phase也一样】,所以在组件实例化之前要用config db传递好is active
- reference model,完成与DUT相同的功能,其输出传给scb用于与DUT的输出比较,这里主要是TLM通信,怎么从i_agt获取tr并传给scb
- TLM实现component组件间的tr级别通信,发送:uvm_analysis_port【参数化类,参数是tr】ap,ap需要在build phase通过new实例化,然后在main phase中ap.write往ap内写入tr;接收:uvm_blocking_get_port【参数化类,参数是tr】,与ap类似也要实例化,在main phase通过port.get获取tr;实现通信:需要定义uvm_tlm_analysis_fifo,【参数化类,参数是存储在内部的tr】在env中定义fifo并在build phase实例化,然在connect phase中连接mon的analysisi port和ref中的blocking get port,【i_agt.ap.conect(agt_mdl_fifo.analysis_export),mdl.port.connect(agt_mdl_fifo.blocking_get_export)】;需要fifo的原因:因为analysis_port是非阻塞的,ap.write调用马上返回不等待数据的被接收,如果正在write的同时blocking get port忙于其他事情,没有准备好接收新数据,这就需要fifo来暂存
- connect phase,在build phase后执行,执行顺序自下而上,能够保证指针非空
- scoreboard,main phase利用fork join执行两个进程,一个获取DUT输出数据【有延时】,一个获取来自于mon或者ref的数据【放入队列里,ref处理的数据是无延时,保证比DUT先到,DUT随后从队列里弹出数据进行比较即可】
- field automation机制,在tr中使用,利用uvm_object_utils_begin…..uvm_object_utils_end注册所有字段,field系列宏和字段类型有关,经过注册后,可以直接使用copy compare print等函数,无需自己定义,提高了效率,简化drv和mon;
- sequencer,sequence机制用于产生激励,sequencer定义非常简单【事实上无需怎么定义】【参数化类,表明产生的tr类型】,sequencer产生tr,drv驱动tr【所以drv也要参数化类,参数是tr类型,方便直接使用预先定义好的成员变量req】,要在env中实例化sqr
- sequence机制,sequence不属于验证平台任一部分,sqr帮助sequence产生的tr最终能送给drv,sqr只有在seq存在时才有价值【seq是弹夹,sqr是枪,tr是子弹】,sequence是object,和tr一样具有生命周期!sequence定义【参数化类,参数是tr】,内部有body任务,sequence启动后会自动执行pre_body body post_body,一般来说body内都会uvm_do(tr),uvm_do会干三件事:创建tr实例,随机化tr,将tr送给sqr;【用start_item+随机化+finish_item自定义也行】
- sequence向sqr发送tr前会发送请求,sqr会把此请求放入仲裁队列中,sqr只做两件事:1. 检测仲裁队列是否有来自seq的发送tr请求;2. 检测drv是否有申请tr的请求;
- 仲裁队列有请求,drv没请求,sqr会等drv直到drv申请新的tr请求,sqr允许seq的发送请求,seq开始向sqr发送tr,sqr把tr交给drv
- 仲裁队列没请求,drv有请求,sqr会等seq直到seq递交新的发送tr请求,sqr马上同意,seq给sqr发送tr,sqr把tr交给drv
- 仲裁队列有请求,drv有请求,sqr同意seq的发送请求,seq给sqr发tr,sqr把tr交给drv
drv申请tr的方法:drv内建成员变量seq_item_port,sqr内建成员变量seq_item_export,二者建立起了通道,通道类型是drv和sqr定义时指定的参数tr,所以在agt内的connect phase用drv.seq_item_port.connect(sqr.seq_item_export)连接好,接着drv用get_next_item【try_next_item是非阻塞的,会询问sqr是否有tr,有的话得到,没有的话直接返回】向sqr申请tr,然后while一直驱动tr,最后用seq_item_port.done()结束【告诉sqr我drv已经拿到了tr,它可以把存在它那的tr副本给删除了——一种握手机制增加可靠性】
seq发送tr的方法:uvm_do宏,开始:产生一个tr交给sqr,sqr把tr交给drv,drv拿到了tr后,发出item_done信号,uvm_do宏执行完毕;开始执行下一个tr;
seq定义好如何启动:1. 在某个component的main_phase启动seq即可,【先要创建seq的实例】利用sequence.start(xxx.sqr);要指明哪个sqr指针即将seq产生的tr交给哪个sqr;2. 在sqr中的main phase启动seq【提起和撤销objection也可以在这】;3. 用default_sequence,在某个component的build phase中通过config db的方式,存在很多phase,所以要制定是main phase还是configure phase还是reset phase等从而使得sqr知道在哪个phase启动这个seq;可以在uvm_test_top中写也可以在top_tb中的intial begin内写或者agt的build phase写都是可以的;使用default_sequence后,如何提起和撤销objection:在uvm_sequence基类中有starting_phase变量【类型是uvm_phase】,sqr启动default_sequence后会自动在sqr中做seq.starting_phase=phase;seq.start(this);所以可以在seq中用if(starting_phase≠null)的时候,starting_phase.raise_objection(this),#若干时间后,if(starting_Phase≠null),starting_phase.drop_objection(this)。这样关联起了objection和sequence,其他地方无需再设置objection!
uvm_config_db#(uvm_object_wrapper)::set(this,
”i_agt.sqr.main_phase”,
”default_sequence”,
my_sequence::type_id::get())
objection机制:一般伴随sequence,在seq出现的地方才提起和撤销【所以在main phase里面提起和撤销objection,这样各个组件的main phase会执行完毕】【所以上面的sqr中的main phase启动seq,也可以在这里提起撤销objection】,即seq是弹夹,弹夹里面的子弹用光了可以结束仿真了;
- base_test,真正的测试用例是base_test派生的类,在build phase内实例化env,并且设置sequencer的default_sequence【其他地方就不需要再设置了】,另外可以自定义加入report_phase,设置超时退出时间,利用config db设置某些参数的值;此时run_test(base_test),uvm_test_top是base_test的实例了;
- 测试用例数量一直增加,但是后面的测试用例不能影响到已经建好的,前面通过default_sequence启动seq1,如何才能不影响seq1的前提下启动seq2呢,1. 一个test对应一个seq,然后在seq的body中提起和撤销objection,中间发送tr;2. 在对应的test内部启动default_sequence设置是seq1的实例;3. 在命令行用UVM_TESTNAME=XX,决定启动哪一个test测试用例,即启动了对应的seq!
- 测试用例的启动过程:
- module top_tb
- run_test
- 启动验证平台
- 根据UVM_TESTNAME产生casen的实例
- 执行build phase自上而下形成完整的树结构
- 执行各个节点的connect phase main phase
- 所有phase执行完毕,结束仿真;
第三章 UVM基础
继承于uvm_component和uvm_object的相关类与用途【com拥有树形结构和phase自动执行等特点】
- uvm_sequence_item:封装了一定信息的类【tr是它的子类】,drv会从sqr得到tr,并转为端口级别信号,mon从DUT的pin上获取信息并转换成tr级别发给scb比较;
- uvm_sequence:是sequence_item的组合,sequence向sqr发送tr,sqr把tr交给drv
- uvm_sequencer:组织管理sequence,drv要求tr时把sequence生成的tr转发给drv
- uvm_agent:把drv和mon封装在一起,从可重新角度考虑!
- uvm_env:将验证平台固定不变的component封装在一起,不同的测试用例例化env即可;
- uvm_test:派生测试用例,任何一个测试用例都要实例化env
- config,uvm_object派生,规范行为,配置参数通过config db设置给需要这些参数component
- uvm_phase,控制uvm_component的行为方式,使得component平滑在各个phase之间运转
uvm_object和uvm_component相关宏
- 注册:uvm_object_utils uvm_component_utils
- 域的自动化相关:uvm_object_utils_begin(tr) …. uvm_object_utils_end,对于component来说,比如drv内有个num变量用了此机制,并在build phase调用super.build_phase,那么可以省略build phase中的config db get语句,省略get!
object和component区别
component不能用object中的clone函数,因为component作为UVM树节点存在会失去该方面的特征,因为clone出来的类其父辈无法指定,clone=new+copy
UVM树的根是uvm_root类【保证了验证平台的树根只有一个】,实例化名是uvm_top【唯一实例,是uvm_comp类型】,子类是casen即测试用例,对应实例化名是uvm_test_top,uvm_top的父辈是null,所以如果某个component的父辈是null,那么这个component的父类就是uvm_root类即uvm_top实例,这个comp就是casen,实例化名叫uvm_test_top;
层级结构的函数
- get_full_name,就可以得到该component的路径
- get_parent
- get_child需要string name
- get_children获取所有孩子,参数是一个队列类型
- get_first_child、get_next_child
- get_num_children,获取component所拥有的所有孩子的数量
field_automation机制具体的宏介绍、函数、标志位使用
UVM打印信息的控制
- UVM_INFO最后一个参数是冗余度
- 冗余度阈值UVM_LOW\MEDIUM\HIGH,低于该阈值的冗余度的信息都会被打印出来,支持在命令行设置阈值【+UVM_VERBOSITY=HIGH】!
- get_report_verbosity_level函数得到某个component的冗余度阈值
- set_report_verbosity_level函数设定某个component的冗余度阈值,如果想把该component树节点的下层全部设定统一的冗余度阈值,用递归的设置函数set_report_verbosity_level_hier
- set_report_id_verbosity函数区分不同ID的冗余度阈值,同样有递归
- 重载打印信息,比如将drv中的UVM_WARING重载为UVM_ERROR,用set_report_severity_id_override函数
- UVM_FATAL出现会立马停止仿真,也可以统计UVM_ERROR达到多少个停止仿真,用set_report_quit_count(5)即出现5个ERROR就会退出,不必等到所有objection的撤销!【get_max_quit_count获取阈值5,得到0表示无论多少ERROR都不会退出仿真】,用set_report_severity_action(UVM_WARNING,UVM_DISPLAY(UVM_COUNT))把WARNING加入到计数中!
config db机制,get_full_name会得到component的路径,所以component实例化时的名字就会出现在路径中,但是指针drv还是相对于casen的层次结构名,而不是实例化的名my_driver;
- 主要用于传递参数,set/get,第一、二参数是目标路径,符合路径的才会收到信!第三个参数是目标成员,第四个参数是设定的值或接收的值;
- 跨层次的多重设置,即设置多次,只获取一次,判断优先级:层次越高越优先,时间越到最后即越近的越优先,所以是uvm_test_top最优先,或者build_phase执行到最后的节点最优先
- 同一层次的多重设置,时间优先!
- 非直线的设置与获取,scb获取drv的config db参数就是非直线,如果按照层级结构来就是直线;需要仔细设定路径!同时由于build phase执行顺序在drv和scb是统一层级的,谁先谁后并不知道,所以设置的时候会有风险!不采用非直线会造成set的多次设置吗,即冗余浪费!
- 支持通配符
第四章 TLM通信
验证平台内部的通信方法有
- 全局变量
- 在scb设置public变量,在mon直接赋值
- config db传递object类,但是需要base_test参与设置,但是base_test派生类有可能会改变变量的值存在风险
- 如果有阻塞和非阻塞,上面完全不合适,也可以用sv的旗语、邮箱等实现,但是复杂
- 建立管道通信,信息只在管道内流动——TLM机制
TLM;分1.0和2.0;事务级建模
- 通信发起者与通信的目标
- PUT是发起者把tr发送给目标,发起者具有port端口【方框表示】
- GET是发起者要目标索要tr,目标具有export端口【圆圈表示】
- PUT与GET的数据流向不同,但是发起者是一样的,都是A主动方,B被动方
- transport操作,put+get,A向B提交请求,B回应了并返回
- PUT GET TRANSPORT都有阻塞与非阻塞之分
PORT和EXPORT,参数是数据流的类型,一般是tr
三个是指blocking,non_blocking,无
- put三个
- get三个
- transport三个
- peek三个
- get_peek三个
PORT15个,EXPORT15个
建立连接关系,即连接管道【在env中连接】:发起者A.port.connect(被动方.export),这只是连接管道,还没有通信【也没有存储tr的作用,只是转发tr而已】!connect之前要分别在各自的component通过new实例化好PORT和EXPORT,new的参数是名字+所在component【this即可】
IMP;实现tr的处理;与PORT类似,有15个,各自有阻塞非阻塞无三种,名字没具体的含义只是要求与对应的PORT或EXPORT连接对应的IMP
- PUT
- GET
- PEEK
- GET_PEEK
- TRANSPORT
参数【tr,B_component】,即IMP需要在组件B中实现一个PUT函数或任务,因为A的PORT连接B的EXPORT,B的EXPORT连接B的IMP,B的imp会调用B的put函数任务完成tr的处理【这是put操作的举例】
IMP才是UVM中连接关系的终点!PORT优先级>export>imp
PORT与IMP连接
EXPORT与IMP连接
PORT与PORT连接
EXPORT与EXPORT连接
blocking_get,发起者B向A索要tr即GET操作,B中get任务最终会落到A中的get任务,所以A要有imp和export,还要有get任务!
non_blocking端口的使用是非堵塞的,要用函数实现!
第三种通信方式:analysis_port和analysis_export
区别于blocking_put/get端口,analysis_port可以一对多IMP【必须是analysis_imp类型】,广播!
没有堵塞和非堵塞的概念,就是广播!
analysis_port/export只有一种操作就是write!【要在analysis_imp所在component定义好write函数!因为是port或export最终连到imp上,port或export的write操作最终由imp所在的component实现write函数,如果是fifo的话就不同了,无需定义write函数】
analysis_port/export不能相互连接,必须终点是imp【fifo也是imp】
一个port,多个imp,如在scb中要接受mon和ref的数据,需要两个imp,可以用uvm_analysis_imp_decl分别连接并定义不同的imp和不同的write函数,后缀!
使用fifo通信:agt有analysis_port端口连接到FIFO【本质一块缓存加两个IMP口】的analysis_imp口,scb有blocking_get_port口连接FIFO的get_imp口,在env中分别连接
agt.ap.connect(fifo.analysis_export);
scb.exp_port.connect(fifo.blocking_get_export);
之所以用export是因为fifo把IMP掩饰成了export;
利用fifo后,不用在scb中定义write函数了!
第五章 PHASE机制+OBJECTION机制
PHASE机制
- phase 细分了仿真的过程,不同的时间段做不同的事情!
- build_phase 函数型 完成实例化和config db操作
- connect_phase 函数型
- end_of_elaboration 函数型
- start_of_simulation 函数型
- run phase任务型,下面是12个动态运行phase,与run phase并行,同样是任务型
- pre_reset_phase
- reset_phase 对DUT复位初始化操作,可以通过phase跳转转回reset phase
- post_reset_phase
- pre_configure_phase
- configure_phase
- post_configur_phase
- pre_main_phase
- main_phase
- post_main_phase
- pre_shutdown_phase
- shut_down_phase
- post_shut_down_phase
- extract_phase
- check_phase
- report_phase
- final_phase
执行顺序自上而下【时间概念,不是build phase自下而上的空间概念】,函数phase同一时间只能执行一类;
除了build phase外,所有不消耗仿真时间的phase都是自下而上【空间概念】【函数型phase】,比如connect phase先执行底层的,最后执行顶层的connect phase;
从上得到了思考,为什么objection只在main phase提起和撤销【其实在动态运行12个phase中都可以,比如configure phase】,这是因为其他的phase是函数型,不消耗仿真时间,而raise objection操作一般放在不消耗仿真时间的最后面,放在第一个消耗仿真时间的前面!
- 执行顺序,按照实例化的名字的字典序,针对drv和mon这种兄弟关系的同层级的phase而言!
- 对于同个component的执行顺序,12个动态运行phase是顺序执行的!比如先reset、configure接着main最后shutdown!
- 不同component的之间的相同phase类一定是一起结束!比如A的main phase先结束也要等B的main phase结束才算整个main phase的结束!
- 同个component的run phase和post shutdown phase一起结束!
- 不同的component,A的run phase一定和B的post shutdown phase或run phase一起结束!
- 所有component的run phase或post_shutdown_phase结束才会进入extract phase
- 叔侄关系的component的build phase执行顺序:UVM采用的是a!
- 深度优先:agt>scb,字典序,那么agt的build phase先执行,接着drv mon sqr由于d>m>s所以依次是drv mon sqr的buildphase执行完毕,然后执行兄弟scb的build phase【从始至终】
- 广度优先:agt>scb,字典序,那么agt的build phase先执行,执行其他兄弟的,s应该在最后,因为s靠尾巴,所以scb的build phase靠后,兄弟的build phase执行完毕,接着开始agt的孩子,然后孩子的孩子!
- super.phase做了什么
- build phase,域的自动化机制注册变量字段+super.phase=可以省略config db的get操作!
- 其他的phase没必要写super.phase!【适用直接扩展自uvm_component类,如果自定义类的phase写了一些东西并且子类需要继承的就不该省略】
- 在end_of_elaboration、build phase、connect phase出现一个以上的UVM_ERROR,UVM认为出现了UVM_FATAL会停止仿真!【适用于大型设计,需要提前编译代码,不希望出现FATAL,只是希望编译后仿真的时候出现FATAL,可以用ERROR代替,这样编译的时候不会FATAL,只在仿真的时候FATAL,保留了编译的大量时间】
- phase的跳转phase.jump(uvm_reset_phase::get()),一定要撤销objection清理掉!不然会报warning
- build_phase到start_of_simulation_phase不能作为跳转的参数
- run_phase不能作为跳转的参数
- pre_reset_phase一直到final_phase都可以做跳转参数
Objection机制
超时退出:某些测试case会挂起,仿真时间一直走,利用uvm_top.set_timeout函数设置超时退出时间【在base_test内定义】;超过此时间会给出FATAL退出仿真;在命令行也可以设置+UVM_TIMEOUT=“300ns,YES”,YES代表覆盖define的超时时间设置宏
drop objection关闭验证平台,进入某个phase会收集该phase 提起的所有objection,并监测是否所有的drop objection都被撤销!接着关闭该phase!开始下一个phase的监测,所有phase监测完成后,调用$finish结束验证平台!
- drv的main phase提起了objection并且在100ns延迟后撤销了objection,与此同时,monitor的main phase正在无限循环某个函数,内部没有定义objection的提起与撤销,那么drv的objection撤销,UVM检查了所有objection已经撤销了就会关闭main phase,进入post main phase,所以进入post的时间是100ns,mon的无限循环会被强行杀死!
- 如果执行一些耗费时间的代码,在该phase下的任意个component提起和撤销objection!【objection伴随着seq,所以sequencer内的main phase提起和撤销objection,因为sqr启动seq后就代表转发完了seq产生的item,所以这时候撤销objection即可;或者用default_sequence方式挂载在某个component的mainphase或configurephase等上启动sequence【default_sequence会自动在对应sqr上seq.start(this)启动该sequence】,所以也可以用starting_phase≠null的方法在sequence内提起并延迟1000ns后继续撤销objection】
12个动态运行phase中如果提起了objection,run phase会与它们并行运行;如果12个动态运行phase没有提起过objection,反而run phase中提起过objection,那么只会执行各个component的run phase,12个动态phase根本不会被执行【只针对耗时的代码】;所以12个动态运行phase的objection提起会影响run phase的运行,但是run phase的objection只会影响同类的run phase,不会让12个动态运行phase受影响的!【因为run phase大,12个动态phase小】
总结:先看12个小phase,有objection检测直到post_shutdown_phase,再看run phase的drop objection,最后关闭,进入extract phase
先看12个小phase,没有objection,看run phase的drop,最后关闭,进入extract phase
在scb或seq中控制objection,一般用seq中控制objection!
phase.phase_done.set_drain_time(this,200),【为所有objection设置延时】,比如某个phase在所有objection被撤销后设置200ns的延时,后面如果没有再提起objection的phase,最终就到达了final_phase,一个phase对应一个drain_time!!!
第六章 sequence
sequence启动与执行;
- 先实例化然后用start任务指明挂载的sqr【产生tr交给哪个sqr】
- default_sequence利用config db【设定在哪个sqr的哪个phase内,设定实例化seq】启动【default自动在config db指定的sqr内部启动了对应的seq实例】
- 启动seq后会自动执行seq的body任务!【prebody和postbody】
同一个sqr启动多个seq
- 仲裁算法默认SEQ_ARB_FIFO,不考虑优先级,严格遵循先入先出规则
- SEQ_ARB_STRICT_FIFO严格按照优先级【遵循seq中tr的uvm_do_pri设置的优先级】
- sequence也有优先级,start(sqr,null,100)设定优先级【本质还是对seq内部的tr设定优先级!】
- lock操作,seq向sqr发送请求都会被放入仲裁队列中,当lock前面的请求都处理完毕后,lock操作开始独占sqr,直到unlock,该seq才不会独占sqr要求其发送自己的tr了,后面的seq会交替产生tr,如果多个seq调用lock【时间优先,先发起的lock先开始】
- grab操作,seq向sqr发送的请求放到冲裁队列最前方,即优先处理具有grap的seq的tr,【多个grap与lock一样遵循时间优先的原则】
- grab前有lock,会等待lock结束,再将grab操作放入队列的最前方
- sequence的有效性:sqr仲裁时会查看seq的is_relevant函数返回值【该函数可以重载然后自定义的】,如果为1,说明seq有效,否则无效!通过设置is_relevant可以使得sequence主动放弃sqr的使用权,即在一定时间范围内,seq不想发送tr给sqr!另外,wait_for_relevant是seq定义的,但是是sqr主动调用的;
与sequence相关的宏
- uvm_do系列:基础是uvm_do_on_pri_with(tr, sqr, 优先级,约束{tr.xxx== xxx}),第一个参数可以是tr或者是sequence,如果是tr会调用start_item和finish_item,如果是sequence,会调用start任务!类似的由uvm_send宏、uvm_rand_send、uvm_create
- uvm_create与uvm_send分别实例化tr和发送tr
- umv_rand_send系列:对已经实例化的tr进行随机化并发送,uvm_rand_send_pri_with(tr,优先级,约束),主要意义在于前后发送的tr使用同一块内存内容不同而已,节省内存
- 不使用宏产生item的方法:start_item和finish_item,使用前要实例化item即tr。在start和finish中间随机化item——uvm_do的内容,指定优先级需要在start和finish同时加
- pre_do mid_do post_do针对uvm_do而言
嵌套sequence,在一个sequence的body调用定义好的另一个sequence【实例化+start】
sequence也可以定义rand变量,从而利用uvm_do参数为sequence时一样可以随机化【如果tr里面有同类型的rand变量会优先使用tr,sequence的同名变量会不考虑】
sequence发送的tr类型:一般一个sqr只能产生一种tr,如果将不同的tr交给同一个sqr,需要将sequencer和driver定义时的参数化类的参数改为uvm_sequence_item即tr的基类,这样sequence定义不同的tr句柄并进行随机化发送是可行的,但是drv中如果要引用不同tr的内部成员变量,需要将uvm_sequence_item通过cast转换成对应的tr如my_tr【cast(my_tr, req),其中req是drv自带的成员变量,是与定义参数化类的参数相同即uvm_sequence_item】
sequence想要引用启动该seq所用的sequencer内的成员变量,需要通过m_sequencer;由于m_Sequencer继承于uvm_sequencer_base(uvm_Sequencer的基类),所以如果sequencer想要引用比如my_sqr【前提是该seq在my_sqr上启动,所以m_Sequencer代表了该sequence启动后所使用的的sqr指针】的成员变量,相当于m_Sequencer要指向my_sqr,即基类句柄指向子类句柄,需要通过cast转换,转换成功的条件是基类句柄确确实实指向了子类的对象;所以整体流程是:声明my_sqr指针x_sqr,通过cast(x_Sqr, m_sequencer)转换,然后可以通过x_sqr调用my_Sqr的成员变量;【多态的特性,重点看看!】
上面的过程:由于使用到sqr的成员变量情况很多,所以uvm内建宏:uvm_declare_p_sequencer(my_sqr),相当于声明了my_sqr指针p_sequencer+自动将m_sequencer转换为p_sequencer=直接用p_sequencer引用成员变量
sequence之间的同步:virtual sequencer和virtual sequence,虚拟的意思表示vseq根本不发送tr,只是控制其他的sequence;virtual sequencer包含指向其他真实sqr的指针!
base_test要实例化virtual sequencer,并在connect phase中将v_sqr.xxx_sqr0=env.agt.sqr即将真实的sqr赋值给相应vsqr内的对应指针!
在vseq的body中可以用uvm_do发送不同seq产生的tr,这里在vseq要uvm_declare_p_sequencer(my_vsqr),即什么vsqr的指针p_sequencer,然后利用p_sequencer.xxx_sqr即直接引用了vsqr内部的成员变量【即对应的真实sqr指针】,一般不同的seq需要挂载到不同的p_sequencer.xxx_sqr0/1/2上;
上面不用uvm_do发送替换成手动启动seq也是一样的,手动启动seq可以自定义很多操作比如指定文件名等;
vseq的使用可以减少config db的使用【没用vseq之前,需要一个seq对应一个sqr,并声明成default_sequence设置相关参数,有多少sqr就需要设置多少default_sequence即同等数量的config_db,用了vseq后只需要一次configdb将default_sequence设置实例化vseq并挂载到vsqr上在vsqr的main_phase上启动即可】,降低出错概率,解决了seq的同步问题!
再谈objection的控制
- 在sequencer【在其他component也可】的动态运行phase如main_phase手动raise和drop【seq.start(this)后面drop】
- default_sequence【在某个component设置default_sequence,通过config db设置,路径是某个sqr的某个任务phase如my_sqr的main phase或configure phase等待,最后一个参数丄my_sequence::type_id::get()】,默认会自动在对应sqr的对应任务phase中【比如my_sqr.main phase】做出:seq.staring_phase=phase【这里的phase就是main phase,所以phase不为null】,seq.start(this),所以可以在sequence中通过判断if(staring_phase != null)来提起和撤销objection,注意这里的starting_phase是uvm_sequence基类的成员变量,所以所有的seq都会该变量;
- 上面在seq内【假设是my_seq】通过starting_phase不为null提起和撤销objection,然后在objection关闭之前,使用uvm_do或者手动启动随机化tr发送出去都是可以的;但是如果在嵌套sequence中,即上面的my_seq在另一个seq中声明比如vseq,并在vseq通过uvm_do启动my_seq,这样会导致my_seq.starting_phase=null,即不会提起objection,所以由此得出结论,最好在vseq中提起objection,统一调度的作用再次体现!
sequence使用fork join_none慎用,因为none会直接endtask,杀死还未启动的seq进程,解决方法:wait_fork等待fork起来的进程执行完毕或者用for join【这样不能使用for循环了】
sequence使用config db;get_full_name()会返回component的路径或者返回某个sequence的sqr路径+实例化sequence时的名字;所以用通配符取代实例化sequence的名字,然后路径设为sqr的路径+通配符,即可set;那么get的话需要在sequence中用null或uvm_root::get()+get_full_name()+第三个参数+第四个参数的方式get
wait_modifield
response;与seq-sqr-drv单向数据传输机制不同,这里是seq-sqr-drv-sqr-seq,即drv会反馈信息给seq,其实是drv将rsp推给sqr,sqr内部有一个rsp队列,有新的rsp进入时就把rsp放入队列中,队列大小默认是8,超过8会给出错误提示【为什么会超过8因为一个tr可以有多个rsp,只要多次在seq中get_rsp和在drv中多次put_rsp,如果seq没有及时get_rsp,那么队列可能就会塞满rsp导致错误】;在seq使用get_rsp(rsp)函数,在drv使用put_rsp(rsp)函数【还要rsp.set_id_info(req),即将req的id等信息复制到rsp,因为存在多个sequence在同一sqr上启动的情况,该操作保证了sqr知道将rsp数据返回给哪一个seq】
上面的seq_get操作启动时,会一直从sqr的rsp队列里中获取rsp,如果drv没有put_rsp且sqr的队列中rsp一直空的话,那么该进程会一直堵塞,从而导致drv无法获得seq发送的新的tr了,所以如果将get_rsp和put_rsp线程分开——response handler的用法
sequence_library;一系列sequence的集合,指明参数为tr,new函数调用int_sequence_library表示内部队列不空才可以把seq放进去,用uvm_add_to_seq_lib加入seq注册到图书馆中
第七章 寄存器模型
• 来源:芯片验证工程师 • 2023-06-24 12:02 • 359次阅读
寄存器模型保持着DUT内部寄存器值的镜像(mirror)。
镜像值不能保证是正确的,因为寄存器模型只能感知到对这些寄存器的外部读写操作。
如果DUT内部修改了寄存器中的字段,镜像值就会过时(outdated)。
寄存器模型可以通过使用uvm_reg_field::mirror(), uvm_reg::mirror(), 或**uvm_reg_block::mirror()**方法将一个寄存器的镜像值更新为存储在DUT中的值。
更新寄存器中某个字段的镜像也会同时更新同一寄存器中所有其他字段的镜像。更新一个block的镜像会更新它包含的所有寄存器和字段的镜像。
如果使用前门访问,更新一个大block的镜像可能需要大量的仿真时间;而使用后门访问更新不需要消耗仿真时间。
可以通过使用uvm_reg_field::set()或uvm_reg::set()方法在不消耗仿真时间向寄存器模型中的写入镜像值,这个镜像值不会反应到DUT中相应字段或寄存器。
可以通过使用**uvm_reg::update()或uvm_reg_block::update()**方法更新DUT的寄存器以匹配镜像值。如果新的镜像值与旧的镜像值相匹配,寄存器就不会被更新,从而节省不必要的总线访问。
要想不消耗仿真时间访问一个field或register的当前镜像值,可以使用 uvm_reg_field::get() 或**uvm_reg::get()**方法。
Memories是没有镜像的
Memories比较大,所以通常使用稀疏阵列的方法来建模。只有被写入的位置才会被存储起来,然后再读回来。任何未使用的内存位置都不会被建模。
与寄存器不同,memory的行为非常简单,memory镜像将是一个ROM或RAM内存模型,所以可以通过提供对内存模型的后门访问来取代。因此,使用uvm_mem::peek()或uvm_mem::poke()方法提供与memory镜像完全相同的功能。此外,与基于观察到的读写操作的寄存器镜像不同,使用后门访问的memory镜像总是返回或设置DUT中一个内存位置的实际值。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。