sequence的仲裁机制


概述

sequence可通过sequencer向drive发送你的待测case,如果一个sequencer只发送一个sequence是不需要仲裁的,但在实际使用中,如果一个sequencer接收了两个sequence或者更多的时候,会怎样发送各自sequence的case呢?如果你要发送的case需要存在同步关系,要怎么来做这样的同步关系呢?这个时候就能感受到uvm的强大了,可以使用的他的仲裁机制。总结了sequence的几种仲裁关系。

  1. 通过指定优先级决定仲裁
  2. 通过lock或者grap决定仲裁
  3. 通过is_relevant和对wait_for_relevant重载进行仲裁
  4. 通过virtual sequence和virtual sequencer进行复杂同步

指定sequence优先级

在sequence被启动是会自动执行它的body、pre_body和post_bosy三个任务,我们常用的是body任务。body任务中可以通过uvm_do_pri及uvm_do_pri_with改变所产生的transaction的优先级。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

如下面的这段代码指定了sequence0的优先级为100,sequence1的优先级为200,sequence1的优先级高于sequence0的优先级,同事设定了sequencer采用优先级仲裁机制,这样则会将sequence1的transcation都发送完成后再发送sequence0的transaction。对sequence设置优先级的本质即设置其内产生的transaction的优先级
 

`ifndef MY_CASE0__SV
`define MY_CASE0__SV
class sequence0 extends uvm_sequence #(my_transaction);
   my_transaction m_trans;

   function  new(string name= "sequence0");
      super.new(name);
   endfunction 
   
   virtual task body();
      if(starting_phase != null) 
         starting_phase.raise_objection(this);
      repeat (5) begin
         `uvm_do_pri(m_trans, 100)
         `uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
      end
      #100;
      if(starting_phase != null) 
         starting_phase.drop_objection(this);
   endtask

   `uvm_object_utils(sequence0)
endclass

class sequence1 extends uvm_sequence #(my_transaction);
   my_transaction m_trans;

   function  new(string name= "sequence1");
      super.new(name);
   endfunction 
   
   virtual task body();
      if(starting_phase != null) 
         starting_phase.raise_objection(this);
      repeat (5) begin
         `uvm_do_pri_with(m_trans, 200, {m_trans.pload.size < 500;})
         `uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
      end
      #100;
      if(starting_phase != null) 
         starting_phase.drop_objection(this);
   endtask

   `uvm_object_utils(sequence1)
endclass


class my_case0 extends base_test;

   function new(string name = "my_case0", uvm_component parent = null);
      super.new(name,parent);
   endfunction 
   `uvm_component_utils(my_case0)
   extern virtual task main_phase(uvm_phase phase);
endclass

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);
      seq1.start(env.i_agt.sqr);
   join
endtask

`endif

lock和grap独占

lock和grap其实对sequence的仲裁是相似的,都是在lock/grap期间独占sequencer进行transaction传输,区别在于grab操作比lock操作优先级更高。 lock请求是被插入
sequencer仲裁队列的最后面, 等到它时, 它前面的仲裁请求都已经结束了。 grab请求则被放入sequencer仲裁队列的最前面, 它几乎是一发出就拥有了sequencer的所有权
。如果是两个lock或者grap都试图独占sequencer,则先占用的发送完成再执行后占用的。这样前面说grap有比lock更高的优先级,如果发生在使用lock一个sequence独占sequencer发送transaction没有unlock时来了一个grap请求要怎么处理呢,这个时候grap会等lock的sequence执行完毕再执行自己的请求。

`ifndef MY_CASE0__SV
`define MY_CASE0__SV
class sequence0 extends uvm_sequence #(my_transaction);
   my_transaction m_trans;

   function  new(string name= "sequence0");
      super.new(name);
   endfunction 
   
   virtual task body();
      if(starting_phase != null) 
         starting_phase.raise_objection(this);
      repeat (2) begin
         `uvm_do(m_trans)
         `uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
      end
      lock();
      `uvm_info("sequence0", "locked the sequencer ", UVM_MEDIUM)
      repeat (5) begin
         `uvm_do(m_trans)
         `uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
      end
      `uvm_info("sequence0", "unlocked the sequencer ", UVM_MEDIUM)
      unlock();
      repeat (2) begin
         `uvm_do(m_trans)
         `uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
      end
      #100;
      if(starting_phase != null) 
         starting_phase.drop_objection(this);
   endtask

   `uvm_object_utils(sequence0)
endclass

class sequence1 extends uvm_sequence #(my_transaction);
   my_transaction m_trans;

   function  new(string name= "sequence1");
      super.new(name);
   endfunction 
   
   virtual task body();
      if(starting_phase != null) 
         starting_phase.raise_objection(this);
      repeat (3) begin
         `uvm_do_with(m_trans, {m_trans.pload.size < 500;})
         `uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
      end
      lock();
      `uvm_info("sequence1", "locked 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("sequence1", "unlocked the sequencer ", UVM_MEDIUM)
      unlock();
      repeat (3) begin
         `uvm_do_with(m_trans, {m_trans.pload.size < 500;})
         `uvm_info("sequence1", "send one transaction", UVM_MEDIUM)
      end
      #100;
      if(starting_phase != null) 
         starting_phase.drop_objection(this);
   endtask

   `uvm_object_utils(sequence1)
endclass


class my_case0 extends base_test;

   function new(string name = "my_case0", uvm_component parent = null);
      super.new(name,parent);
   endfunction 
   `uvm_component_utils(my_case0)
   extern virtual task main_phase(uvm_phase phase);
endclass

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;
   fork
      seq0.start(env.i_agt.sqr);
      seq1.start(env.i_agt.sqr);
   join
endtask

`endif

is_relevant和wait_for_relevant进行重载

可以对is_relevant和wait_for_relevant进行重载来决定仲裁。sequencer在仲裁时, 会查看sequence的is_relevant函数的返回结果。 如果为1, 说明此sequence有效, 否则无效。 

class sequence0 extends uvm_sequence #(my_transaction);
   my_transaction m_trans;
   int num;
   bit has_delayed;

   function  new(string name= "sequence0");
      super.new(name);
      num = 0;
      has_delayed = 0;
   endfunction 
  
   virtual function bit is_relevant();
      if((num >= 3)&&(!has_delayed)) return 0;
      else return 1;
   endfunction

   virtual task wait_for_relevant();
      #10000;
      has_delayed = 1;
   endtask

   virtual task body();
      if(starting_phase != null) 
         starting_phase.raise_objection(this);
      repeat (10) begin
         num++;
         `uvm_do(m_trans)
         `uvm_info("sequence0", "send one transaction", UVM_MEDIUM)
      end
      #100;
      if(starting_phase != null) 
         starting_phase.drop_objection(this);
   endtask

   `uvm_object_utils(sequence0)
endclass

virtual sequence和virtual sequencer

如果验证平台中存在两个agent,每个agent下的driver接收的sequence还需要保证一定的同步关系,这个时候就需要virtual sequence和virtual sequencer出马了。如果不适用virtual sequence还要保证drv0_seq和drv1_seq有同步关系,这个时候就需要引入全局变量来做同步了,但是如果是中间存在多次的同步,如sequence A要先执行, 之后是B, B执行后才能是C, C执行后才能是D, D执行后才能是E。 这依然可以使用上面的全局方法解决, 只是这会显得相当笨拙。这种情况下使用virtual sequence将非常方便。

`ifndef MY_CASE0__SV
`define MY_CASE0__SV
class drv0_seq extends uvm_sequence #(my_transaction);
   my_transaction m_trans;
   `uvm_object_utils(drv0_seq)

   function  new(string name= "drv0_seq");
      super.new(name);
   endfunction 
   
   virtual task body();
      repeat (10) begin
         `uvm_do(m_trans)
         `uvm_info("drv0_seq", "send one transaction", UVM_MEDIUM)
      end
   endtask
endclass

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

class case0_vseq extends uvm_sequence;
   `uvm_object_utils(case0_vseq)
   `uvm_declare_p_sequencer(my_vsqr) 
   function new(string name = "case0_vseq");
      super.new(name);
   endfunction

   virtual task body();
      my_transaction tr;
      drv0_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)
      fork
         `uvm_do_on(seq0, p_sequencer.p_sqr0);
         `uvm_do_on(seq1, p_sequencer.p_sqr1);
      join 
      #100;
      if(starting_phase != null) 
         starting_phase.drop_objection(this);
   endtask
endclass

class my_case0 extends base_test;

   function new(string name = "my_case0", uvm_component parent = null);
      super.new(name,parent);
   endfunction 
   extern virtual function void build_phase(uvm_phase phase); 
   `uvm_component_utils(my_case0)
endclass


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
`endif

在case0_vseq中, 先使用uvm_do_on_with在p_sequencer.sqr0上发送一个最长包, 当其发送完毕后, 再启动drv0_seq和drv1_seq。在使用uvm_do_on宏的情况下, 虽然seq0是在case0_vseq中启动, 但是它最终会被交给p_sequencer.p_sqr0, 也即env0.i_agt.sqr而不是v_sqr。 这个就是virtual sequence和virtual sequencer中virtual的来源。 它们各自并不产生transaction, 而只是控制其他的sequence为相应的sequencer产生transaction。 virtual sequence和virtual sequencer只是起一个调度的作用。 由于根本不直接产生transaction, 所以virtual sequence和virtual sequencer在定义时根本无需指明要发送的transaction数据类型。

virtual sequencer的代码使用为下面两图