6. virtual sequence 如果被嵌套的sequence需要在不同的sequencer中启动,也就是需要sequence之间同步 ,那么此时的嵌套sequence就是virtual sequence。它不产生transaction,只是控制其它sequence产生transaction发送给不同的sequencer,这就是为什么是virtual。
virtual sequence可以在启动的时候不需要sequencer,把它设成null,这也是它被称为virtual的另一个原因。
6.1 sequence通过find函数拿到启动sequencer virtual sequence中的sequence需要拿到启动的sequencer,这可以通过uvm_top的find函数 ,find函数通过传入component实例的名字可以返回这个实例的句柄,find函数返回uvm_component类型,需要转化哼真正的sequencer类型 ,这样就拿到了每个要启动的sequencer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class runall_sequence extends uvm_sequence #(uvm_sequence_item) ; `uvm_object_utils(runall_sequence); protected reset_sequence reset; protected maxmult_sequence maxmult; protected random_sequence random; protected sequencer sequencer_h; protected uvm_component uvm_component_h; function new (string name = "runall_sequence" ); super .new (name); uvm_component_h = uvm_top.find ("*.env_h.sequencer_h" ); if (uvm_component_h == null ) `uvm_fatal("RUNALL SEQUENCE" , "Failed to get the sequencer" ) if (!$cast (sequencer_h, uvm_component_h)) `uvm_fatal("RUNALL SEQUENCE" , "Failed to cast from uvm_component_h." ) reset = reset_sequence::type_id::create("reset" ); maxmult = maxmult_sequence::type_id::create("maxmult" ); random = random_sequence::type_id::create("random" ); endfunction : new task body(); reset.start (sequencer_h); maxmult.start (sequencer_h); random.start (sequencer_h); endtask : body endclass : runall_sequence
virtual sequence的启动,因为上面通过find,每个sequence都拿到了sequencer,所以virtual sequence不需要指定sequencer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class full_test extends tinyalu_base_test; `uvm_component_utils(full_test); runall_sequence runall_seq; task run_phase(uvm_phase phase); runall_seq = new ("runall_seq" ); phase.raise_objection (this ); runall_seq.start (null ); phase.drop_objection (this ); endtask : run_phase function new (string name, uvm_component parent); super .new (name,parent); endfunction : new endclass
6.2 通过virtual sequencer指定每个sequence的sequencer 除了上面的find函数,还可以通过virtual sequencer指定每个sequence的sequencer。
virtual sequencer中保存多个sequencer的句柄,指向真正要发送transaction的sequencer。
virtual sequencer是对不同sequencer的封装,本身不发送transaction,主要让程序看起来舒服,容易理解——virtual sequence在virtual sequencer上启动,virtual sequence中的sequence在virtual sequencer中的sequencer启动。
Virtual sequencer 有三个属性:
(1)Virtual sequencer控制其他的sequencer
(2)Virtual sequencer并不和任何driver相连
(3)Virtual sequencer本身并不处理item
1 2 3 4 5 6 7 8 9 10 11 12 class my_vsqr extends uvm_sequencer; my_sequencer p_sqr0; my_sequencer p_sqr1; function new (string name, uvm_component parent); super .new (name, parent); endfunction `uvm_component_utils(my_vsqr)endclass
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class case0_vseq extends uvm_sequence; `uvm_object_utils(case0_vseq) `uvm_declare_p_sequencer(my_vsqr) ... virtual task body(); my_transaction tr; read_file_seq seq0; drv1_seq seq1; if (starting_phase != null ) starting_phase.raise_objection (this ); `uvm_do_on_with(tr, p_sequencer.p_sqr0 , {tr.pload .size == 1500 ;}) `uvm_info("vseq" , "send one longest packet on p_sequencer.p_sqr0" , UVM_MEDIUM) seq0 = new ("seq0" ); seq0.file_name = "data.txt" ; seq1 = new ("seq1" ); fork seq0.start (p_sequencer.p_sqr0 ); seq1.start (p_sequencer.p_sqr1 ); join #100 ; if (starting_phase != null ) starting_phase.drop_objection (this ); endtask endclass
1 2 3 4 5 6 7 8 9 function void my_case0::build_phase(uvm_phase phase); super .build_phase (phase); uvm_config_db#(uvm_object_wrapper) ::set(this , "v_sqr.main_phase" , "default_sequence" , case0_vseq::type_id::get());endfunction
6.3 只在virtual sequence中控制objection 上一节中只在virtual sequence中控制objection。
virtual sequence不仅调度transaction,也对objection进行调度,避免层层查找在哪里调度了objection。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class drv1_seq extends uvm_sequence #(my_transaction) ; my_transaction m_trans; `uvm_object_utils(drv1_seq) function new (string name= "drv1_seq" ); super .new (name); endfunction virtual task body(); repeat (10 ) begin `uvm_do(m_trans) `uvm_info("drv1_seq" , "send one transaction" , UVM_MEDIUM) end endtask endclass
除了手工启动(start函数)时需要给starting_phase赋值外,只有将sequence作为sequencer的某动态运行的phase的default_sequence时,starting_phase才不为null。如果将某sequence作为uvm_do宏的参数,那么此sequence的starting_phase为null。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 task my_case0::main_phase(uvm_phase phase); sequence0 seq0; sequence1 seq1; seq0 = new ("seq0" ); seq0.starting_phase = phase; seq1 = new ("seq1" ); seq1.starting_phase = phase; env.i_agt .sqr .set_arbitration (SEQ_ARB_STRICT_FIFO); fork seq0.start (env.i_agt .sqr , null , 100 ); seq1.start (env.i_agt .sqr , null , 200 ); join endtask
6.4 在sequence慎用fork join_none 1 2 3 4 5 6 7 8 9 10 class vseq extends uvm_sequence; my_sequence seq[4 ]; virtual task body(); for (int i=0 ;i<4 ;i++) fork automatic int j=i; `uvm_do_on(seq[j],p_sequencer.p_sqr [j]); join_none endtask endclass
上面例子中在fork join_none中并行调用`uvm_do,将for展开其实如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class vseq extends uvm_sequence; my_sequence seq[4 ]; virtual task body(); fork uvm_do_on(seq[0 ],p_sequencer.p_sqr [0 ]); join_none fork uvm_do_on(seq[1 ],p_sequencer.p_sqr [1 ]); join_none fork uvm_do_on(seq[2 ],p_sequencer.p_sqr [2 ]); join_none fork uvm_do_on(seq[3 ],p_sequencer.p_sqr [3 ]); join_none endtask endclass
当着四个进行都调用了,body任务就执行完了,sequence就执行完了,系统会杀死这个sequence,清空之前占据的内存,也会杀死启动的进程,那么这几个进程还没执行完就被杀死,所以sequence根本没有执行。
解决方法: wait fork
1 2 3 4 5 6 7 8 9 10 11 class vseq extends uvm_sequence; my_sequence seq[4 ]; virtual task body(); for (int i=0 ;i<4 ;i++) fork automatic int j=i; `uvm_do_on(seq[j],p_sequencer.p_sqr [j]); join_none wait fork ; endtask endclass
相当于:
1 2 3 4 5 6 7 8 9 10 11 class vseq extends uvm_sequence; my_sequence seq[4 ]; virtual task body(); fork uvm_do_on(seq[0 ],p_sequencer.p_sqr [0 ]); uvm_do_on(seq[1 ],p_sequencer.p_sqr [1 ]); uvm_do_on(seq[2 ],p_sequencer.p_sqr [2 ]); uvm_do_on(seq[3 ],p_sequencer.p_sqr [3 ]); join endtask endclass
7. 在sequence中使用config_db 7.1 在sequence中获得参数 UVM中使用get_full_name()可以获得对象的层次路径。 比如在一个sequence中调用这个函数,返回:
uvm_test_top.env.i_agt.sqr.seq0
启动这个sequence的sequencer路径和这个sequence实例化时的名字
1 2 3 4 5 6 7 8 9 10 11 function void my_case0::build_phase(uvm_phase phase); super .build_phase (phase); uvm_config_db#(int)::set(this, "env.i_agt.sqr.*", "count", 9) ; uvm_config_db#(uvm_object_wrapper) ::set(this , "env.i_agt.sqr.main_phase" , "default_sequence" , case0_sequence::type_id::get());endfunction
1 2 3 4 5 6 7 virtual task case0_sequence::pre_body(); if (uvm_config_db#(int)::get(null, get_full_name(), "count", count)) `uvm_info("seq0" , $sformatf ("get count value %0d via config_db" , count), UVM_MEDIUM) else `uvm_error("seq0" , "can't get count value!" ) endtask
7.2 在sequence中设置参数 在sequence可以向component设置参数;也可以向sequence中设置参数。
sequence是动态运行的,它什么时候要设置参数不固定,component需要知道什么时候sequence设置了参数,这通过wait_modified 任务实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 virtual task case0_vseq::body(); my_transaction tr; drv0_seq seq0; drv1_seq seq1; if (starting_phase != null ) starting_phase.raise_objection (this ); fork `uvm_do_on(seq0, p_sequencer.p_sqr0 ); `uvm_do_on(seq1, p_sequencer.p_sqr1 ); begin #10000 ; uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 0) ; #10000 ; uvm_config_db#(bit)::set(uvm_root::get(), "uvm_test_top.env0.scb", "cmp_en", 1) ; end join #100 ; if (starting_phase != null ) starting_phase.drop_objection (this ); endtask
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 task my_scoreboard::main_phase(uvm_phase phase); my_transaction get_expect, get_actual, tmp_tran; bit result; bit cmp_en = 1'b1 ; super .main_phase (phase); fork while (1 ) begin uvm_config_db#(bit)::wait_modified(this, "", "cmp_en") ; void '(uvm_config_db#(bit)::get(this, "", "cmp_en", cmp_en)) ; `uvm_info("my_scoreboard" , $sformatf ("cmp_en value modified, the new value is %0d" , cmp_en), UVM_LOW) end ... join endtask
sequence宏的wait_modified类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 virtual task drv0_seq::body(); bit send_en = 1 ; fork while (1 ) begin uvm_config_db#(bit)::wait_modified(null, get_full_name(), "send_en") ; void '(uvm_config_db#(bit)::get(null, get_full_name, "send_en", send_en)) ; `uvm_info("drv0_seq" , $sformatf ("send_en value modified, the new value is %0d" , send_en), UVM_LOW) end join_none repeat (10 ) begin `uvm_do(m_trans) end endtask
8. sequence的响应 UVM中的sequence机制提供了sequence->sequencer->driver的数据传输机制,有时sequence也需要知道driver的响应,进而sequence调整产生的transaction。
8.1 put_response和get_response rsp是UVM中定义的变量。
在sequence中get response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 virtual task body(); if (starting_phase != null ) starting_phase.raise_objection (this ); repeat (10 ) begin `uvm_do(m_trans) get_response(rsp); `uvm_info("seq" , "get one response" , UVM_MEDIUM) rsp.print (); end #100 ; if (starting_phase != null ) starting_phase.drop_objection (this ); endtask
在driver中put response:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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); rsp = new ("rsp" ); rsp.set_id_info (req); seq_item_port.put_response (rsp); seq_item_port.item_done (); end endtask
设置了id才知道哪个响应传给哪个sequence。
还可以直接在item_done中返回响应:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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); rsp = new ("rsp" ); rsp.set_id_info (req); seq_item_port.item_done (rsp); end endtask
调用get_response(rsp)的时候,如果response_queue中没有响应,会阻塞在哪里,不能继续发送transaction。
8.2 response handler和另类response 上面提到的get_response阻塞问题,主要是因为发送transaction和get_response在同一个进程中,
1 2 `uvm_do(m_trans) get_response(rsp);
如果分开就可以解决阻塞问题,需要使用response_handler:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class case0_sequence extends uvm_sequence #(my_transaction) ; my_transaction m_trans; ... virtual task pre_body(); use_response_handler(1 ); endtask virtual function void response_handler(uvm_sequence_item response); if (!$cast (rsp, response)) `uvm_error("seq" , "can't cast" ) else begin `uvm_info("seq" , "get one response" , UVM_MEDIUM) rsp.print (); end endfunction virtual task body(); if (starting_phase != null ) starting_phase.raise_objection (this ); repeat (10 ) begin `uvm_do(m_trans) end #100 ; if (starting_phase != null ) starting_phase.drop_objection (this ); endtask `uvm_object_utils(case0_sequence)endclass
8.3 另类的响应 uvm_do执行完后,它的第一个参数不是空指针,而是指向刚刚发送到driver的transaction;可以在driver将需要返回的信息传给这个transaction中,在sequence中调用完`uvm_do之后获取transaction中的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 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); req.frm_drv = "this is information from driver" ; seq_item_port.item_done (); end endtask
1 2 3 4 5 6 7 8 9 10 11 12 virtual task case0_sequence::body(); if (starting_phase != null ) starting_phase.raise_objection (this ); repeat (10 ) begin `uvm_do(m_trans) `uvm_info("seq" , $sformatf ("get information from driver: %0s" , m_trans.frm_drv ), UVM_MEDIUM) end #100 ; if (starting_phase != null ) starting_phase.drop_objection (this ); endtask
9. sequence library 根据特定算法随机选择注册在其中的sequence,并在body中执行这些sequence。
10. 补充一种启动sequence的方法 在sequence外,通过sequence句柄调用sequence中发送transaction的任务。
原理是:之前把发送transaction的语句写在了body中,sequence启动后,自动调用body,所以我们也可以把发送transaction语句写在其他任务中,让body为空。我们在sequence启动后,手动调用这些任务发送transaction。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class sequence1 extends uvm_sequence#(transaction) ; `uvm_object_utils(sequence1);function new (string name="sequence1" ); super .new (name);endfunction task write(); transaction tr; `uvm_info(get_type_name(),"write" ,UVM_LOW) `uvm_do(tr);endtask task read(); transaction tr; `uvm_info(get_type_name(),"read" ,UVM_LOW) `uvm_do(tr);endtask task pre_body(); if (starting_phase != null ) starting_phase.raise_objection (this ); `uvm_info(get_type_name(),"pre_body" ,UVM_LOW)endtask task body(); `uvm_info(get_type_name(),"body" ,UVM_LOW) wait (0 );endtask task post_body(); `uvm_info(get_type_name(),"post_body" ,UVM_LOW) if (starting_phase!=null ) starting_phase.drop_objection (this );endtask endclass class case0 extends base_test; `uvm_component_utils(case0); function new (string name="case0" ,uvm_component parent=null ); super .new (name,parent); endfunction task run_phase(uvm_phase phase); sequence1 seq1; seq1=new ("seq1" ); fork begin seq1.starting_phase = phase; seq1.start (env0.sqr ); end begin for (int i=0 ;i<10 ;i++) begin seq1.write (); seq1.read (); end end join_none endtask endclass
结果如下:
1 2 3 4 5 6 7 8 9 10 11 27 :UVM_INFO seq/sequence1.sv (13 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] write28 :UVM_INFO seq/sequence1.sv (25 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] pre_body29 :UVM_INFO seq/sequence1.sv (28 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] body 54 :UVM_INFO seq/sequence1.sv (19 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] read79 :UVM_INFO seq/sequence1.sv (13 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] write ....454 :UVM_INFO seq/sequence1.sv (19 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] read479 :UVM_INFO seq/sequence1.sv (13 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] write504 :UVM_INFO seq/sequence1.sv (19 ) @ 0 : uvm_test_top.env0 .sqr@@seq1 [sequence1] read529 :UVM_INFO seq/sequence1.sv (33 ) @ 1000000 : uvm_test_top.env0 .sqr@@seq1 [sequence1] post_body543 :[sequence1] 23