本文为验证平台添加了 sequence和sequencer两个组件,整个验证平台基本成型,文中并没有选择《UVM实战》2.4.3中的default_sequence 的方式,因为我在工作中也没有采用这种方式。

https://github.com/shirainbown/UVM-Step-by-Step/tree/master

之前就说了,driver 本身不应该负责激励的产生而是负责激励的驱动,激励的产生由sequence负责,而sequencer只需要源源不断把sequence产生的transaction 送给driver。

1. 添加sequencer

一个sequencer的定义如下:

`ifndef MY_SEQUENCER__SV
`define 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

`endif

上述需要注意几点:

  • sequencer 继承于uvm_sequencer,同时还有一个参数#(my_transaction),这里指定了该sequencer处理的对象是my_transaction。

  • 需要用 uvm_component_utils对其进行注册,其余所有东西不用再定义,uvm内置的功能已经满足了所有需求。

  • sequencer 是 agent的一个部分,需要和drier、monitor一起在agent中声明。

2. 添加 sequence

  • sequence 不属于验证平台的任何一部分,同样可以理解为transaction本质上也不属于平台的一部分,它们类似一种消耗品,都属于uvm_object,而其他组件都是始终存在于仿真期间的,属于uvm_component。sequence类似手枪,而transaction就是里面的子弹,sequencer控制手枪发送子弹。

    `ifndef MY_SEQUENCE__SV
    `define 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)  // 发送transaction
          end
          #1000;
       endtask
    
       `uvm_object_utils(my_sequence)
    endclass
    `endif
  • my_sequence 继承于 uvm_sequence,同样需要指定发送的参数transaction的类型。

  • uvm_do是一个宏,可以完成:

1) 随机化创建一个transaction,实例化后给m_trans

2) 发送给sequencer

sequencer 首先一直等待driver的请求,如果driver有请求新的trasaction,则sequencer同意接收sequence发送的transaction。

3. 修改driver

那么下一步需要修改driver,来增加向sequencer请求transaction的功能。

`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver#(my_transaction);

   virtual my_if vif;

   `uvm_component_utils(my_driver)
   function new(string name = "my_driver", uvm_component parent = null);
      super.new(name, parent);
   endfunction

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
         `uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
   endfunction

   extern task main_phase(uvm_phase phase);
   extern task drive_one_pkt(my_transaction tr);
endclass

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

task my_driver::drive_one_pkt(my_transaction tr);
   byte unsigned     data_q[];
   int  data_size;
   
   data_size = tr.pack_bytes(data_q) / 8; 
   `uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
   repeat(3) @(posedge vif.clk);
   for ( int i = 0; i < data_size; i++ ) begin
      @(posedge vif.clk);
      vif.valid <= 1'b1;
      vif.data <= data_q[i]; 
   end

   @(posedge vif.clk);
   vif.valid <= 1'b0;
   `uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask


`endif

同样的,我们需要给driver新增参数 #(my_transaction)。

seq_item_port 是uvm_driver中内置的成员变量,我们只需要使用get_next_item的方法就能向sequencer发送请求,请求完后,还需要使用item_done 来通知sequencer已经成功获取到了transaction。

uvm_sequence 中同样有 seq_item_export成员变量用于发送transaction。

4. 通过 agent连接seq_item_port和seq_item_export

`ifndef MY_AGENT__SV
`define MY_AGENT__SV

class my_agent extends uvm_agent ;
   my_sequencer  sqr;   // 声明 sequencer
   my_driver     drv;
   my_monitor    mon;
   
   uvm_analysis_port #(my_transaction)  ap;
   
   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction 
   
   extern virtual function void build_phase(uvm_phase phase);
   extern virtual function void connect_phase(uvm_phase phase);

   `uvm_component_utils(my_agent)
endclass 


function void my_agent::build_phase(uvm_phase phase);
   super.build_phase(phase);
   if (is_active == UVM_ACTIVE) begin
      sqr = my_sequencer::type_id::create("sqr", this);
      drv = my_driver::type_id::create("drv", this);
   end
   mon = my_monitor::type_id::create("mon", this);
endfunction 

function void my_agent::connect_phase(uvm_phase phase);  //新增该函数,将两个port连接起来
   super.connect_phase(phase);
   if (is_active == UVM_ACTIVE) begin
      drv.seq_item_port.connect(sqr.seq_item_export);
   end
   ap = mon.ap;
endfunction

`endif

connect_phase 在 build_phase之后执行,可以确保每个组件都已经实例化后再进行连接,但是实际上connect_phase的这部分代码也可以合入build_phase中。

5. 在env中启动sequence

`ifndef MY_ENV__SV
`define MY_ENV__SV

class my_env extends uvm_env;

   my_agent   i_agt;
   my_agent   o_agt;
   my_model   mdl;
   my_scoreboard scb;
   
   uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
   uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
   uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
   
   function new(string name = "my_env", uvm_component parent);
      super.new(name, parent);
   endfunction

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      i_agt = my_agent::type_id::create("i_agt", this);
      o_agt = my_agent::type_id::create("o_agt", this);
      i_agt.is_active = UVM_ACTIVE;
      o_agt.is_active = UVM_PASSIVE;
      mdl = my_model::type_id::create("mdl", this);
      scb = my_scoreboard::type_id::create("scb", this);
      agt_scb_fifo = new("agt_scb_fifo", this);
      agt_mdl_fifo = new("agt_mdl_fifo", this);
      mdl_scb_fifo = new("mdl_scb_fifo", this);
   endfunction

   extern virtual function void connect_phase(uvm_phase phase);
   extern virtual task main_phase(uvm_phase phase);
   
   `uvm_component_utils(my_env)
endclass

function void my_env::connect_phase(uvm_phase phase);
   super.connect_phase(phase);
   i_agt.ap.connect(agt_mdl_fifo.analysis_export);
   mdl.port.connect(agt_mdl_fifo.blocking_get_export);
   mdl.ap.connect(mdl_scb_fifo.analysis_export);
   scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
   o_agt.ap.connect(agt_scb_fifo.analysis_export);
   scb.act_port.connect(agt_scb_fifo.blocking_get_export); 
endfunction

// -------------------
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);   //指定发送给哪个sequencer
   phase.drop_objection(this);
endtask
// --------------------
`endif

phase.raise_objection(this)和 phase.drop_objection(this) 用于控制仿真的开始和结束,与sequence紧密相关,本质上就是在sequence发送前raise,在所有transaction发送完后结束一轮仿真。在后续我们可以看到如何重复多次启动一个sequence开启新的一轮测试。