UVM(三)
时间:2022-10-14 08:30:00
45.sequence的启动方式
1.使用start手动启动任务
my_sequence my_seq; my_seq = my_sequence::type_id::create("my_seq"); my_seq.start(sequencer);
第一个参数是sequencer;
第二个参数是parent sequence,可以设置为null;
第三个参数是优先级,
2.使用default_sequence启动
uvm_config_db#(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase", "default_sequence", case0_sequence::type_id::get());
function void my_case0::build_phase(uvm_phase phase); case0_sequence cseq; super.build_phase(phase); cseq = new("cseq"); uvm_config_db#(uvm_sequence_base)::set(this, "env.i_agt.sqr.main_phase", "default_sequence", cseq); endfunction
两者的区别在于先例化吗?sequence
default_sequence本质也是调用start任务
46.当一个sequence启动后自动执行sequence的body任务。
除了body还会自动调用sequence的pre_body与post_body
47.sequence的仲裁机制
1.对于transaction和sequence都有优先概念,sequence本质上,它产生了优先级transaction的优先级;
2.优先级越高,越容易被选中(事实上,优先级必须先发生)
3.默认优先级为-1,这个值必须是一个≥-数字越大,优先级越高;
4.默认情况下sequencer仲裁算法是SEQ_ARB_FIFO,先入先出而不考虑优先级;
SEQ_ARB_WEIGHTED 加权仲裁SEQ_ARB_RANDOM 完全随机选择SEQ_ARB_STRICT_FIFO 严格按照优先级;优先级相同的,先入先出;SEQ_ARB_STRICT_RANDOM 严格按照优先级;同等优先级是随机的SEQ_ARB_USER 用户自定义
48.sequence的有效性
1.lock
所有前面的请求都已经处理好了,sqr开始响应lock;此后,sqr直到被独占unlock调用
2.grab
grab优先级比被列入仲裁队列前列lock高;独占直到ungrab调用;
3.多个lock
先取得所有权sequence执行完成后,轮到下一个了seq;
4.使用grab之前已有seq使用lock独占sqr
grab会等待lock的释放
5.is_relevant
sqr仲裁时会检查seq的is_relevant返回结果;如果1,说明seq有效;
与is_relevant相对的有wait_for_relevant,两者总是搭配使用。
当sqr其上所有seq无效时,会调用wait_for_relevant至少等一个seq有效;
49.uvm_do系列宏
1.`uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)
其他宏基本上是基于宏的变化
on sqr
pri pri
with constraint
2.uvm_do完成了什么
创建transaction
随机化transaction
发送transaction
3.uvm_create与uvm_send
除了使用uvm_do宏也可以用上述宏完成transaction
`uvm_create(m_trans) assert(m_trans.randomize()); p_sz = m_trans.pload.size(); {m_trans.pload[p_sz - 4, m_trans.pload[p_sz - 3], m_trans.pload[p_sz - 2], m_trans.pload[p_sz - 1]} = num; `uvm_send(m_trans)
这种方式更加灵活,但是更加繁琐;
4.不使用宏产生transaction,需要依赖start_item和finish_item(必须是trans的指针)
tr = new("tr"); start_item(tr); assert(tr.randomize() with {tr.pload.size == 200;}); finish_item(tr);
如果要指定优先级,则start和finish都要加入;
start_item(tr, 100); finish_item(tr, 100);
50.uvm_do的灵活性
为了增加uvm_do系列宏的功能, UVM提供了三个接口:pre_do、mid_do与post_do
sequencer.wait_for_grant(prior) (task) \ start_item \ parent_seq.pre_do(1) (task) / \ `uvm_do* macros parent_seq.mid_do(item) (func) \ / sequencer.send_request(item) (func) \finish_item / sequencer.wait_for_item_done() (task) / parent_seq.post_do(item) (func) /
pre_do start_item最后一行代码
mid_do finish_item最开始
post_do finish_item最后一行代码
51. m_sequencer和p_sequencer
m_sequencer 是每个sequence中都有的默认成员变量,它的类型是uvm_sequencer_base类型,它是指向当前sequence的sequencer句柄;
p_sequencer 需要使用`uvm_declare_p_sequencer(my_sequencer)声明,类型是my_sequencer类型(my_sequencer是自定义的类,继承于uvm_sequencer);直接使用m_sequencer会报错,因为m_sequencer是uvm_sequencer_base类型,必须转换为my_sequencer类型才行。这时候引入了p_sequencer。
通过宏声明my_sequencer类型的名为p_sequencer句柄,将m_sequencer类型转为p_sequencer,可以直接调用p_sequencer实现seq访问component类
52.virtual sequence
1.实现sequence之间同步的最好方式就是使用virtual sequence,为了使用virtual sequence,一般需要一个virtual sequencer。virtual sequencer里面包含指向其他sequencer的指针。
2.在base_test中,实例化vsqr,并将相应的sequencer赋值给vsqr中的sequencer的指针
3.在virtual sequene中则可以使用uvm_do_on系列宏来发送transaction
53.sequence library
sequence library就是一系列sequence的集合,本质上来说它是一个sequence。
它根据特定的算法随机选择注册在其中的一些sequence,并在body中执行这些sequence。
一.从 uvm_sequence 派生时要指明此 sequence library 所产生的 transaction类型;二.在其 new 函数中要调用 init_sequence_library ,否则内部的候选 sequence 队列就是空的;三.要调用 uvm_sequence_library_utils注册。一个sequence在定义时使用宏uvm_add_to_seq_lib来将其加入某个sequence library
54.寄存器模型
在没有寄存器模型之前,只能启动sequence通过前门(FRONTDOOR)访问的方式来读取寄存器,局限较大,在 scoreboard(或者其他component)中难以控制
前门访问
指的是通过模拟cpu在总线上发出读指令,进行读写操作。在这个过程中,仿真时间($time函数得到的时间)是一直往前走的
后门访问
不通过总线进行读写操作,直接通过层次化的引用来改变寄存器的值
广义上说,所有不通过DUT的总线而对DUT内部的寄存器或者存储器进行存取的操作都是后门访问操作
后门访问的意义:
后门访问操作能够更好地完成前门访问操作所做的事情(不耗时)
后门访问操作能够完成前门访问操作不能完成的事情(例如更改某些只读寄存器的值)
但是后门访问操作无法在波形文件中找到操作痕迹,增加了调试难度
55.寄存器模型中的基本概念
uvm_reg_field
寄存器模型中的最小单位,是具体存储寄存器数值的变量。
uvm_reg
比reg_field高一个级别的单位。一个寄存器中至少包括一个uvm_reg_field
uvm_reg_block
uvm_block中可以加入很多uvm_reg,也可以加入uvm_reg_block.一个寄存器模型中至少包含一个uvm_reg_block.
一般将具有同一基地址的寄存器作为整体加入一个uvm_reg_block中,每个 uvm_reg_block一般都有与其对应的物理地址空间。
uvm_reg_map
存储寄存器地址并将其转换为可以访问的物理地址
56.前门访问
无论是读或写,寄存器模型都会通过sequence产生一个uvm_reg_item类型的变量,此变量中存储着操作类型(读还是写)和操作的地址,如果是写操作,还会有要写入的数据。此变量中的信息要经过一个转换器( adapter)转换后交给bus_sequencer,随后交给bus_driver,由bus_driver实现最终的前门访问读写操作。
寄存器模型的前门访问操作最终都将由uvm_reg_map完成,因此在connect_phase中,需要将adapter和bus_sequencer通过 set_sequencer函数告知reg_model的default_map,并将default_map设置为自动预测状态
57.寄存器模型的相关函数
reg2bus和bus2reg
adapter中将sequence产生的uvm_reg_item变量与DUT能接收的类型相互转换
read和write
同时支持前门访问和后门访问方式。
在操作时会模仿DUT的行为。
peek和poke
只支持后门访问
58.寄存器模型操作流程
1.对每个寄存器进行定义
由于uvm_reg类是object类型,build函数不会自动执行,需要手动调用;
除此之外,还要调用field的configure对各个域进行详细配置;
2.将寄存器加入到uvm_reg_block中,并添加到对应的reg_map;
reg_block中的build函数,在其中要调用create_map函数完成default_map的实例化;
除此之外,还要调用reg的configure对寄存器完成配置(访问路径、指定block等)
最后,还要通过add_reg把每个寄存器加入到default_map中。
3.创建adapter;
adapter中要实现的主要是reg2bus和bus2reg;
4.顶层reg_block的创建及使用
一般在base_test中创建顶层reg_block,及adapter和predictor;
5.将defaulte map连接到bus sequencer和adapter;
寄存器模型的前门访问操作最终都将由uvm_reg_map完成,因此在connect_phase中,需要将adapter和bus_sequencer通过 set_sequencer函数告知reg_model的default_map,并将default_map设置为自动预测状态;
6.在sequence或其他component中使用寄存器模型
定义一个顶层的block指针并指向base_test中的reg_block,之后再调用;
59.镜像值和期望值
镜像值
镜像值是表示当前硬件的已知状态值。
往往由模型预测给出,即在前门访问时通过观察总线或者在后门访问时通过自动预测等方式来给出镜像值
期望值
期望值是先利用寄存器模型修改软件对象值,而后利用该值更新硬件值;
60. 显示预测和自动预测
显示预测
在物理总线上通过监视器来捕捉总线事务,并将捕捉到的事务传递给外部例化的predictor,该predictor由UVM参数化类uvm_reg_predictor例化并集成在顶层环境中。
monitor一旦捕捉到有效事务,会发送给predictor,再由其利用 adapter的桥接方法,实现事务信息转换,并将转化后的寄存器模型有关信息更新到map中。
自动预测
没有在环境中集成独立的predictor,而是利用寄存器的操作来自动记录每一次寄存器的读写数值,并在后台自动调用predict方法的话,这种方式称之为自动预测;
自动预测简单有效,但是如果一些seq跳过寄存器级别的R/W对寄存器进行操作,或者通过其他总线来访问寄存器,则都无法得到正确的值