UVM 中的 sequence
从 driver 中剥离激励产生功能, 使用 sequence 机制之后,在不同的测试用例中,将不同的sequence 设置成sequencer 的 main_phase 的 default_sequence。当sequencer 执行到 main phase 时,发现有default_sequence,那么它就启动 sequence.
sequence 的启动与执行
当完成一个sequence 的定义后,可以使用 start 任务将其启动:
my sequence my seq;
my_seq = my_sequence: :type_id::create("my seg");
my_seq.start (sequencer);
除了直接启动之外,还可以使用 default_sequence 启动。事实上 default_sequence 会调用start 任务,它有两种调用方式,其中一种是前文已经介绍过的:
uvm_config_db# (uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase","default_seguence",
case0_sequence::type_id::get ());
另外一种方式是先实例化要启动的 sequence,之后再通过 default sequence 启动:
function void my case0::build phase (uvm phase phase);
case0_sequence cseq;
super.build phase(phase);
cseq = new("cseg") ;
uvm_config_db# (uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase","default_seguence",
cseq);
endfunction
当一个squence启动后会自动执行sequence 的 body 任务。其实,除了 body外,还自动调用 sequence 的 pre_body(在body前执行) 与 post_body(在body后执行).
sequence 的仲裁机制
在同一sequencer 上启动多个 sequence
UVM支持同一时刻在同一sequencer 上启动多个 sequence。在my_sequencr 上同时启动了两个 sequence: sequence0 和 sequence1,代码如下所示:
task my_case0::main_phase(uvm phase phase) ;
sequence0 seg0;
sequencel seql;
seq0 = new("seg0");
seq0.starting_phase = phase;
seq1 = new("seq1");
seql.starting_phase = phase;
fork
seq0.start(env.i agt.sgr);
seql.start(env.i agt.sqr);
join
endtask
seq0和seq1交替输出
对于 transaction 来说,存在优先级的概念,通常来说,优先级越高越部被选中。
当使用uvm do 或者uvm do_with 宏时,产生的 transaction 的优先级是认的优级,即 -1。
可以通过 uvm_do_pri 及 uvm_do_pri_with 改变所产生的 Transaction 的优先级.
'uvm_do_pri(m_trans,100)
'uvm_do_pri_with(m_trans, 200, (mtrans.pload.size < 500;))
uvm_do_pri与uvm _do_pri_with 的第二个参数是优先级,这个数值必须是一个大于等于 -1的整数。数字越大,优先级越高。
在默认情况下 sequencer 的仲裁算法是 SEQ_ARB_ FIFO。它会严格遵循先人先出的顺序,而不会考虑优先级。SEQ _ARB _WEIGHTED 是加权的仲裁;SEQ _ARB_ RANDOM是完全随机选择;SEQ_ARB _STRICT _FIFO是严格按照优先级的,当有多个同一优先级的sequence 时,按照先入先出的顺序选择;SEQ _ARB _STRICT _RANDOM 是严格按照优先级的,当有多个同一优先级的 sequence 时,随机从最高优先级中选择;SEQ _ARB _USER 则是用户可以自定义一种新的仲裁算法。
因此,若想使优先级起作用,应该设置仲裁算法为 SEQ_ ARB _STRICT _FIFO或者 SEQ
_ARB_ STRICT_ RANDOM:
task my case0::main phase(uvm phase phase);
env.i_agt.sqr.set_arbitration(SEO_ARB_STRICT_FIFO);
fork
seq0.start(env.i agt.sgr);
seql.start (env.i agt.sqr);
join
endtask
经过如上的设置后,会发现直到 sequencel 发送完 transaction 后,sequence0 才开始发送。
除transaction 有优先级外,sequence 也有优先级的概念。
可以在 sequence 启动时指定其优先级:
task my case0::main phase (uvm phase phase);
env.i_agt.sqr.set arbitration(SEQ ARB STRICT_FIFO)
fork
seq0.start(env.i_agt.sqr, null,100);
seql.start (env.i _agt .sqr, null, 200);
join
endtask
start 任务的第一个参数是sequencer,第二个参数是 parent sequence,可以设为第三个参数是优先级,如果不指定则此值为 -1,它同样不能设置为一个小于 -1 的数字。对 sequence 设置优先级的本质即设置其内产生transaction 的优先级。
sequencer的lock 操作
当多个sequence在一个sequencer 上同时启动时,每个 sequence 产生出的transaction都需要参与sequencer 的仲裁。那么考虑这样一种情况,某个sequence 比较奇特,一旦要执行,那么它所有的 transaction 必须连续地交给 driver,如果中间夹杂着其他sequence的transaction,就会发生错误。要解决这个问题,可以对此 sequence赋较高的优先级。但是假如有其他 sequence 有更高的优先级呢?所以这种解决方法并不科学。在 UVM中可以使用 lock 操作来解决这个问题。
所谓lock,就是sequence向sequencer发送一个请求,这个请求与其他 sequence发送transaction的请求一同被放入 sequencer 的仲裁队列中。当其前面的所有请求被处理定毕后,sequencer就开始响应这个 lock 请求,此后 sequencer 会一直连续发送此 sequence的transaction,直到unlock 操作被调用。从效果上看,此sequencer 的所有权并没有被所有的sequence共享,而是被申请lock 操作的sequence 独占了。一个使用lock 操作的 sequence为:
class sequencel extends uvm_sequence #(my_transact1on);
virtual task body();
repeat (3) begin
'uvm_do_with(m_trans,(m_trans,pload.size < 500;))
'uvm info("sequencel",,"send one transaction",UVM_MEDIUM)
end
lock();
'uvm info("sequencel",“locked the sequencer ",UVM_MEDIUM)
repeat (4) begin
'uvm_do_with(m_trans,m trans.pload.size < 500;))
'uvm_info("sequencel","send one transaction",UVM_MEDIUM)
'uvm_info("sequencel","unlocked the sequencer ", UVM MEDIUM)
unlock();
repeat (3) begin
'uvm do_with(m _trans, (m trans.pload.size < 500;))
'uvm info("sequencel","send one transaction", UVM MEDIUM)
end
endtask
endtask
在 lock 语句前,sequence0 和 seuquence1 交产生 ransaction ; 在 loc语句后,一直发送 sequencel 的 transaction,直到 unlock 语句被调用后,sequence0和euquencel 又开始交替产生 transaction。如果两个sequence都试图使用 lock 任务来获取 sequencer 的所有权则会如何呢? 先获得所有权的 sequence 在执行完后才会将所有权交还给另外一个sequence。
sequencer的 grab 操作
与lock操作一样,grab 操作也用于暂时拥有 sequencer 的所有权,只是 grab 操作比lock 操作优先级更高。lock 请求是被插人 sequencer 仲裁队列的最后面,等到它时,前的仲裁请求都已经结束了。grab 请求则被放入sequencer 仲裁队列的最前面。
class sequencel extends uvm sequence #(my_transaction);
virtual task body();
repeat (3) begin
'uvm_do_with(m trans, (m trans.pload,size < 500;])
'uvm_info("sequencel","send one transaction",UVM_MEDIUM)
end
grab()
'uvm_info("sequencel", "grab the sequencer ",UVM_MEDIUM)
repeat (4) begin
'uvm_do_with(m trans, (m trans.pload.size < 500;))
'uvm_info("sequence1","send one transaction",UVM_MEDIUM)
end
'uvm_info("sequencel", "ungrab the sequencer ",UVM_MEDIUM)
ungrab();
repeat (3) begin
'uvm_do_with(m trans, (m trans.pload.size < 500;))
'uvm_info("sequencel","send one transaction",UVM_MEDIUM)
end
endtask
'uvm_object_utils(sequence1)
endclass
如果两个sequence 同时试图使用grab 任务获取 sequencer 的所有权将会如何呢?这种情况与两个sequence 同时试图调用 lok 函数一样,在先获得所有权的 sequence 执行完毕才会将所有权交还给另外一个试图所有权的 sequence。
如果一个sequence 在使用 grab 任务获取sequencer 的所有权前,另外一个 sequence 已经使用 lock 任务获得了sequencer 的所有权则会如何呢? 答案是 rab 任务会一直等待 ock的释放。grab 任务还是比较讲文明的,虽然它会插队,但是绝不会打断别人正在进行的事情。
uvm do 系列宏
uvm_do_on 用于显式地指定使用哪个 sequencer 发送此 transaction。它有两个参数,第个是 transaclion的指针,第二个是 sequencer的指针。当在sequence 中使用uvm_do等时,其默认的 sequencer就是此sequence启动时为其指定的sequencer,sequence将这个sequencer的指针放在其成员变量 m_sequencer 中。
'uvm_ do_ on(tr, this.m _sequencer)
'uvm_do_on_pri,它有三个参数,第一个参数是transaction 的指针,第二个是sequencer 的指针,第三个是优先级:
'uvm do_on pri(tr, this, 100)
'uvm_do_on _with(tr, this, (tr.pload.size = 100;))
'uvm_do_on_with,它有三个参数,第一个参数是transaction的指针,第二个是sequencer 的指针,第三个是约束
'uvm_do_on_pri with(tr, this,100,(tr.pload.size == 100;))
'uvm_do_on_pri with,它有四个参数,是所有 uvm do宏中参数最多的一个。第一个数是 transaction 的指针,第二个是 sequencer 的指针,第三个是优先级,第四个是约束
通过start_item 和finish_item产生transaction
transaction 的方式要依赖于两个任务: start_item 和 finish_item。在使用两个任务前,必通要先实例化transaction 后才可以调用这两个任务:
tr = new("tr");
start_item(tr);
finish_item(tr);
pre do、mid do 与 post do
uvm_do宏封装 transaction 实例化到发送的一系列操作,封装的越多、则其灵活性越差。为了增加 uvm_do系列宏的功能,UVM 提供了三个接口: pre_do mid_do与 pre_do是一个任务,在stat_item 中被调用,它是 start_item 返回前执行的最后一行代码,在它执行完毕后才对transaction进行随机化。mid_do是一个函数,位于 finish_item最开始。在执行完此雨数后,finish_item 才进行其他操作。post_do 也是一个函数,也位finish_item 中,它是 finish_item 返回前执行的最后一行代码。
将两个截然不同的 transaction 交给同一个seqencer 呢?可以,只是要将sequencer 和driver 能够接受的数据类型设置为uvm_sequence_item:
class my_sequencer extends uvm_sequencer #(uvm sequence_item);
class my_driver extends uvm_driver (uvm sequence_item);
在sequence 中可以交替发送 my ransaction 和 your_transaction.
response 的使用
sequence 机制提供了一种sequence一sequencer一driver 的单向数据传输机制。但在复杂的验证平台中,sequence 需要根据 driver对 transaction 的反应来决定接下来要的transaction,换言之,sequence 需要得到 driver 的一个反馈。sequence 机制提供对效反馈的支持,它允许driver将一个response返回给sequence。
如果需要使用response,那么在 sequence 中需要使用 get response任务
class case0_sequence extends uvm sequence #(my transaction);
'uvm_object_utils(case0_sequence)
..
virtual task body();
repeat (10) begin
'uvm_do(m_trans)
get_response(rsp);
'uvm_info("seq","get one response",UVM_MEDIUM)
rsp.print();
end
endclass
endtask
在driver 中,则需要使用put_response 任务;
代码清单 6-90
task my_driver: :main_phase (uvm_phase phase) ;
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
rsp = new("rsp");
rsp.set_id_info(req);
seq_item_port.put_response (rsp);
seq_item_port.item_done();
end
endtask
这里的关键是设置 set_id_info 函数,它将req的id 等信息复制到rsp 中。由于可能存在多个sequence在同一个 sequencer 上启动的情况,只有设置了rsp的id 等信息sequencer才知道将 response 返回给哪个 sequence。
rsp与req 类型不同
UVM 也支持respo与req 类型不同的情况。
uvm_driver、uvm_sequencer 与uvm sequence 的原型分别是:
class uvm _driver #(type REQ=uvm _sequence _item,type RSP=REQ) extends uvm _component;
class uvm _sequencer #(type REQ=uvm _sequence _item,RSP=REQ)extends uvm _sequencer_param _base #(REQ, RSP);
virtual class uvm_ sequence #(type REQ = uvm _sequence_ item,type RSP = REQ) extends uvm _sequence_ base;
如果要使用不同类型的rsp 与req,那么 driver、sequencer 与 sequence 在定义时都要传人个参数:
class my_driver extends uvm driver #(my transaction, your transaction);
class my_sequencer extends uvm sequencer #(my transaction, your_transaction);
class case0_sequence extends uvm sequence # (my transaction, your_transaction);