目录
1. UVM Sequence基本概念
UVM Sequence用来生产激励数据,并将激励数据送至driver。Sequence机制包括Sequencer和Sequence,之间的关系为:Sequencer将Sequence产生的激励数据(transaction的形式)送到driver,经driver将transaction喂进DUT。
2. Sequence/Sequencer定义&例化
2.1 定义
1)Sequencer定义:派生自uvm_sequencer(uvm_component),并使用uvm_component_utils宏来注册到factory中。uvm_sequencer是一个参数化的类,参数为my_transaction,即对应Sequence产生的transaction的类型,定义代码如下:
//my_sequencer.sv
class my_sequencer extends uvm_sequencer #(my_transaction);
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
`uvm_component_utils(my_sequencer)
endclass
2)Sequence定义:派生自uvm_sequence(uvm_object),定义时要指定要产生的transaction类型。每一个sequence都有一个body任务,当一个sequence启动之后,会自动执行body中的代码,定义代码如下:
//my_sequence.sv
class my_sequence extends uvm_sequence #(my_transaction);
my_transaction m_trans;
function new(string name= "my_sequence");
super.new(name);
endfunction
virtual task body();
repeat (10) begin
`uvm_do(m_trans)
end
#1000;
endtask
`uvm_object_utils(my_sequence)
endclass
上述代码示例用到了一个uvm_do宏,这个宏的主要作用:
①创建一个my_transaction的实例m_trans;
②产生随机化的transaction;
③将产生的transaction送给sequencer;
uvm_do系列的宏主要有:
`uvm_do(SEQ_OR_ITEM) //该宏有一个参数,为transaction的指针; //默认优先级为-1; `uvm_do_pri(SEQ_OR_ITEM, PRIORITY) //该宏有两个参数,1为transaction的指针,2为优先级; //优先级参数为大于等于-1的整数,数字越大,优先级越大 //示例:`uvm_do_pri(tr, 20) `uvm_do_with(SEQ_OR_ITEM, CONSTRAINTS) //该宏有两个参数,1为transaction的指针,2为约束; //示例:`uvm_do_with(tr, {tr.pload.size == 100;}) `uvm_do_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS) //该宏有三个参数,1为transaction的指针,2为优先级,3为约束; //示例:`uvm_do_pri_with(tr, 20, {tr.pload.size < 10;}) `uvm_do_on(SEQ_OR_ITEM, SEQR) //该宏显式地指定使用哪个sequencer发送产生的transaction; //有两个参数,1是transaction的指针,2是sequencer的指针; //uvm_do等价于: `uvm_do_on(tr, this.m_sequencer); `uvm_do_on_pri(SEQ_OR_ITEM, SEQR, PRIORITY) `uvm_do_on_with(SEQ_OR_ITEM, SEQR, CONSTRAINTS) `uvm_do_on_pri_with(SEQ_OR_ITEM, SEQR, PRIORITY, CONSTRAINTS)
除了uvm_do宏,uvm_create宏与uvm_send宏也可以用来产生transaction,示例如下:
//my_sequence.sv class my_sequence extends uvm_sequence #(my_transaction); my_transaction m_trans; ... virtual task body(); repeat (10) begin `uvm_create(m_trans) //用来实例化m_trans, 也可直接调用new进行实例化,如:m_trans = new("m_trans"); assert(m_trans.randomize()); `uvm_send(m_trans) //将m_trans送到对应的sequencer; end endtask endclass
uvm_send系列宏有:
`uvm_send_pri(SEQ_OR_ITEM, PRIORITY) //该宏有两个参数,1为transaction的指针,2为优先级; //优先级参数类似于uvm_do宏的优先级,为大于等于-1的整数; 数越大,优先级越高; //示例:`uvm_send_pri(m_trans, 200) `uvm_rand_send(SEQ_OR_ITEM) //该宏会自动对transaction进行随机化; `uvm_rand_send_pri(SEQ_OR_ITEM, PRIORITY) `uvm_rand_send_with(SEQ_OR_ITEM, CONSTRAINTS) `uvm_rand_send_pri_with(SEQ_OR_ITEM, PRIORITY, CONSTRAINTS)
除了用uvm_do和uvm_creat宏,start_item与finish_item也可以产生transaction,示例如下:
//my_sequence.sv class my_sequence extends uvm_sequence #(my_transaction); my_transaction m_trans; ... virtual task body(); repeat (10) begin tr = new("m_trans"); //start_item在使用前必须对transaction进行实例化 assert(tr.randomize() with {tr.pload.size == 200;}); start_item(tr); finish_item(tr); end endtask endclass
2.2 例化
1)Sequencer和driver同级,直接在agent的build_phase里进行例化,代码如下:
//my_agent.sv
class my_agent extends uvm_agent ;
my_sequencer sqr;
my_driver drv;
my_monitor mon;
...
endclass
function void my_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
sqr = my_sequencer::type_id::create("sqr", this);
drv = my_driver::type_id::create("drv", this);
mon = my_monitor::type_id::create("mon", this);
endfunction
sequencer在例化好以后,需要在agent中对driver做一个连接,只有连接成功后sequencer才知道将transaction传送给哪个driver。在uvm_driver中有成员变量seq_item_port,而在uvm_sequencer中有成员变量seq_item_export,这两者之间可以建立一个“通道”,通道中传递的transaction类型就是定义my_sequencer和my_driver时指定的transaction类型,连接代码如下:
//my_agent.sv function void my_agent::connect_phase(uvm_phase phase); super.connect_phase(phase); drv.seq_item_port.connect(sqr.seq_item_export); endfunction
当把二者连接好之后,就可以在driver中通过get_next_item任务向sequencer申请新的transaction:
//my_driver.sv task my_driver::main_phase(uvm_phase phase); vif.data <= 8'b0; vif.valid <= 1'b0; while(!vif.rst_n) @(posedge vif.clk); while(1) begin seq_item_port.get_next_item(req); drive_one_pkt(req); seq_item_port.item_done(); end endtask
通过get_next_item任务来得到一个新的req,并且驱动它,驱动完成后调用item_done通知sequencer,driver已经得到了这个transaction,接着sequencer就会把这个transaction删除;
换句话说,当sequence受到item_done信号后,uvm_do宏才算是执行完毕,返回后开始执行下一个uvm_do,并产生新的transaction。
2)sequence在某一个uvm_component(如sequencer,env等)的main_phase例化就可以;示例代码如下:
//my_env.sv
task my_env::main_phase(uvm_phase phase);
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(i_agt.sqr);
//sequence在例化后要进行启动, 并通过start函数指定要传送的sequencer的指针;
//如果要在sequencer中例化,启动代码应改为:seq.start(this);
phase.drop_objection(this);
endtask
除了在main_phase中手工启动sequence,还可通过default_sequence的方式进行启动,只需要在某个uvm_component的build_phase设置如下代码:
//my_env.sv virtual function void build_phase(uvm_phase phase); super.build_phase(phase); uvm_config_db#(uvm_object_wrapper)::set(this,"i_agt.sqr.main_phase", "default_sequence", my_sequence::type_id::get()); endfunction
除了在env的build_phase中设置default_sequence外,还可以在top_tb进行设置:
module top_tb; initial begin uvm_config_db#(uvm_object_wrapper)::set(null,"uvm_test_top.i_agt.sqr.main_phase", "default_sequence",my_sequence::type_id::get()); end endmodule
3. Sequence的仲裁机制
一个sequencer支持启动多个sequence,这样就会遇到一个问题,如果有多个sequence的transaction过来,sequencer根据什么原则去做选择?UVM采用了仲裁机制来解决,主要有优先级原则和锁住原则。
3.1 优先级原则
Sequencer根据transaction的优先级高低来进行选择,优先级仲裁算法主要有:
SEQ_ARB_FIFO //默认优先级算法,严格遵循先入先出的顺序,不考虑transaction的优先级; SEQ_ARB_WEIGHTED //按照优先级进行随机选择 SEQ_ARB_RANDOM //无视优先级,完全随机选择 SEQ_ARB_STRICT_FIFO //严格按照优先级进行选择,如有同一优先级,遵循先入先出的原则 SEQ_ARB_STRICT_RANDOM //严格按照优先级进行选择,如有同一优先级,随机从最高优先级进行选择 SEQ_ARB_USER //用户自定义优先级算法 //可以在tc的main_phase设置使用哪个优先级算法,示例如下: //my_case0.sv 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); seq1.start(env.i_agt.sqr); join endtask
3.2 锁住原则
即sequence向sequencer发送一个请求,要求锁住该sequencer的使用权,直到sequencer收到解锁信号,才开始根据仲裁算法处理别的sequence的transaction,主要有lock和grab操作。
1)lock操作:将来自某个sequence的锁住信号放入仲裁队列的最后,当sequencer处理完所有transaction后,锁住sequencer,直到收到解锁信号。如果有多个锁住信号,则根据先入先出原则,先执行最早发出锁住信号的transaction,示例代码如下:
//my_sequence1.sv class my_sequence1 extends uvm_sequence #(my_transaction); ... virtual task body(); repeat (3) begin `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) end lock();//锁住信号 repeat (4) begin `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) end unlock();//解锁信号 endtask ... endclass
2)grab操作:将来自某个sequence的锁住信号放入仲裁队列的最前,一开始就锁住sequencer的使用权,示例代码如下:
//my_sequence1.sv class my_sequence1 extends uvm_sequence #(my_transaction); ... virtual task body(); repeat (3) begin `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) end grab();//锁住信号 repeat (4) begin `uvm_do_with(m_trans, {m_trans.pload.size < 500;}) end ungrab();//解锁信号 endtask ... endclass
如果一个sequence在使用grab操作获取sequencer的使用权前,另外一个sequence已经使用lock操作获得了sequencer的使用权,则grab操作会一直等待lock的释放。
4. transaction传输数据流
一个sequence在向sequencer发送transaction前,要先向sequencer发送一个请求,sequencer把这个请求放在一个仲裁队列中。作为sequencer,它需做两件事情:
1:检测仲裁队列里是否有某个sequence发送transaction的请求;
2:检测driver是否申请transaction;
主要有下面几种情况:
1)如果仲裁队列里有发送请求,但是driver没有申请transaction,那么sequencer将会一直处于等待driver的状态,直到driver申请新的transaction。此时,sequencer同意sequence的发送请求,sequence在得到sequencer的批准后,产生出一个transaction并交给sequencer,后者把这个transaction交给driver。
2)如果仲裁队列中没有发送请求,但是driver向sequencer申请新的transaction,那么sequencer将会处于等待sequence的状态,一直到有sequence递交发送请求,sequencer马上同意这个请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction。
3)如果仲裁队列中有发送请求,同时driver也在向sequencer申请新的transaction,那么将会同意发送请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction。