1. sequence的由来
激励最初产生在driver中,如下所示:
task driver::main_phase(uvm_phase phase);
transaction tr;
phase.raise_objection(this);
tr = new;
assert(tr.randomize);
drive_one_tr(tr);
phase.drop_objection(this);
endtask
当只施加一种激励时,上述方法是可行的。但是如果需要对DUT施加不同的激励时,就需要对上述的main_phase进行修改。每次修改会很容易将之前对的地方改错,所以这种方法是不可取的,因为其扩展性太差。
想要实现上述目标,就应该将修改的部分从driver中独立出去,这时就引入了sequence机制。在不同的测试用例中,将不同的sequence设置成sequencer的main_phase的default_sequence。当sequencer执行到main_phase时,发现有default_sequence,那么它就会启动sequence。
2. sequence的启动与执行
启动方法一:sequence定义后,使用start任务启动:
sequence seq;
seq = sequence::type_id::create("seq");
seq.start(sequencer);
启动方法二:使用default_sequence启动:
uvm_config_db(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
case_sequence::type_id::get());
或者:
function void my_case::build_phase(uvm_phase phase);
case_sequence seq;
super.build_phase(phase);
seq = new("seq");
uvm_config_db(uvm_object_wrapper)::set(this,
"env.i_agt.sqr.main_phase",
"default_sequence",
seq);
sequence启动后或自动执行sequence的body任务,除此之外还会自动调用sequence的pre_body和post_body:
class case_sequence extends uvm_sequence #(transaction);
virtual task pre_body();
endtask
virtual task post_body();
endtask
virtual task body();
endtask
`uvm_object_utils(case_sequence);
endclass
三个函数的执行顺序是:pre_body() -> body() -> post_body()。
3. sequence的仲裁机制
(1)同一sequencer上启动多个sequence
UVM支持在同一时刻在同一sequencer上启动多个sequence:
task my_case::main_phase(uvm_phase phase);
sequence0 seq0;
sequence1 seq1;
seq0 = new("seq0");
seq1 = new("seq1");
seq0.start_phase = phase;
seq1.start_phase = phase;
fork
seq0.start(env.i_agt.sqr);
seq1.start(env.i_agt.sqr);
join
endtask
运行上述代码后,两个sequence会交替产生transaction。那么sequencer会根据什么选择使用哪个sequence的transaction呢?这就涉及到sequence的仲裁问题。sequence的仲裁算法包括:
SEQ_ARB_FIFO // 遵循先入先出顺序,不考虑优先级
SEQ_ARB_WEIGHTED // 加权的仲裁
SEQ_ARB_RANDOM // 完全随机选择
SEQ_ARB_STRICT_FIFO // 严格按照优先级
SEQ_ARB_STRICT_RANDOM // 严格按照优先级,当有多个同一优先级的sequence时,随机从最高优先级中选择
SEQ_ARB_USER // 用户自定义
transaction优先级设置:当使用uvm_do或者uvm_do_with宏时,产生的transaction的优先级是默认的优先级,即-1,可以通过uvm_do_pri及uvm_do_pri_with设置所产生的transaction的优先级。uvm_do_pri与uvm_do_pri_with的第二个参数是优先级,这个数值必须是大于等于-1的整数,数字越大,优先级越高。
sequence优先级设置:
task my_case::main_phase(uvm_phase phase);
env.i_agt.sqr.set_arbitration(SEQ_ARB_STRICT_FIFO);
fork
seq0.start(env.i_agt.sqr, null, 200);
seq1.start(env.i_agt.sqr, null, 400);
join
endtask
start任务的第三个参数是优先级。对sequence设置优先级其本质上是对其产生的transaction设置优先级。
4. sequencer的lock操作
lock操作的应用场景:某个sequence一旦开始执行,那么它所有的transaction必须连续交给driver,如果中间夹杂其他sequence的transaction,就会发生错误。
lock,即sequence向sequencer发送一个请求,并与其他sequence发送的transaction请求一起被放入sequencer的仲裁队列中。当其前面的所有请求被执行完毕后,sequencer开始响应该lock请求,此后sequencer会一直连续发送该sequence的transaction,直到unlock操作被调用。lock操作实例如下:
class sequence extends uvm_sequence #(transaction);
virtual task body();
lock();
repeat(3) begin
`uvm_do_with(my_trans);
end
unlock();
endtask
endclass
如果两个sequence都在使用lock任务来获取sequencer的所有权时,先获得使用权的sequence在执行完毕后才会将所有权交还给另外一个sequence。
5. sequence的grab操作
与lock操作一样,grab操作同样用于获取sequencer的所有权,只是grab操作优先级比lock操作更高。lock请求是被插入sequencer仲裁队列的最后面,等前面的仲裁请求都执行完毕之后才会响应lock操作。而grab请求则被放入sequencer仲裁队列的最前面,它几乎一发出就获得了sequencer的所有权。grab操作实例如下:
class sequence extends uvm_sequence #(transaction);
virtual task body();
grab();
repeat(3) begin
`uvm_do_with(my_trans);
end
ungrab();
endtask
endclass
如果两个sequence都在使用grab任务来获取sequencer的所有权时,先获得使用权的sequence在执行完毕后才会将所有权交还给另外一个sequence。
如果一个sequence在使用grab任务获得sequencer的所有权前,另一个sequence已经使用lock任务获得了sequencer的所有权,这时grab任务会一直等待lock操作的释放。
6. sequence的有效性
当有多个sequence同时在一个sequencer上启动时,所有的sequence都将会参与仲裁。如果想要在某一时间内sequence不参与仲裁,UVM可以通过重载is_relevant函数来使sequence失效,通过重载wait_for_relevant()任务使sequence恢复有效。实例如下:
class ssequence extends uvm_sequsence #(transaction);
transaction m_trans;
int num;
virtual function bit is_relevant();
if(delay_en == 1) return 0;
else return 1;
endfunction
virtual task wait_for_relevant();
#1000;
delay_en = 0;
endtask
virtual task body();
fork
repeat(10) begin
`uvm_do(m_trans);
num++;
if(num == 6) delay_en = 1;
end
join
endtask
endclass
该sequence在发送完6个transaction后开始失效,1000个时间单位后恢复有效。