Driver Sequencer Handshake

本文围绕UVM中sequencer与driver展开,介绍了sequencer控制sequence item流向、提供终止方法及通信端口。阐述了driver与sequencer通过TLM端口通信,还探讨了driver获取sequence item的get_next_item()、get()方法及返回反馈的put()等方法,给出使用示例并提出代码编写建议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

 

sequencer

Driver Sequencer Handshake

Using get_next_item()

Using get() and put()


sequencer

sequencer控制着sequence与driver之间的sequence item流向,一个sequence必须挂载在一个sequencer上运行 ;uvm_sequencer派生自uvm_sequencer_param_base,其定义如下所示:

class uvm_sequencer #(
type REQ = uvm_sequence_item,
RSP = REQ//定义了传递的参数类
) extends uvm_sequencer_param_base #(REQ, RSP)

除了构造方法new之外,uvm_sequencer还提供了stop_sequences()方法用来终止挂载在该sequencer上的sequence,使sequencer回到复位状态,该方法的定义如下:

virtual function void stop_sequences()

uvm_sequencer内建了两个TLM端口seq_item_export和rsp_export来进行与driver之间的通信:

uvm_seq_item_pull_imp #(REQ,RSP,this_type) seq_item_export;//imp端口

uvm_analysis_export #(RSP) rsp_port;//分析端口

通常用来与driver通信的是第一个imp端口;上面的端口带有下面这些方法,可以通过driver的端口来调用下面的一系列方法进行通信:

Requests://REQ
virtual task get_next_item (output REQ request);
virtual task try_next_item (output REQ request);
virtual task get (output REQ request);
virtual task peek (output REQ request);
Responses://RSP
virtual function void item_done (input RSP response=null);
virtual task put (input RSP response);
Sync Control:
virtual task wait_for_sequences ();
virtual function bit has_do_available ();

(1)get_next_item

virtual task get_next_item (
output REQ t
)//采用blocking的方式从sequence获取下一个sequence item

 (2)try_next_item

virtual task try_next_item (
output REQ t
);采用non_blocking的方式从sequence获取下一个sequence_item,如果返回的结果为null表明sequence没有准备好

它与get_next_item()的区别就是,它是非阻塞的,而后者是阻塞;get_next_item只有获取到新的item后才会继续往下执行,否则就一直等待; 

(3) item_done

virtual function void item_done (
RSP item = null
)//用来通知sequence表明当前sequence item已经由driver处理完成,可选择性的传递RSP

(4) wait_for_sequences ()

virtual task wait_for_sequences ();

//等待当前的sequence直至产生下一个sequence_item;

(5)has_do_available 

virtual function bit has_do_available ();

//如果sequence已经准备好,并且可以获取下一个sequence_item则返回1,否则返回0

(6)get

task get (
output REQ t
)//采用get方法获取下一个sequence item

 (7)peek

task peek (
output REQ t
);//采用peek的方法获取下一个sequence_item

 (8)put

virtual task put (
RSP t
)//以blocking的方式返回RSP给sequence

(9)put_response

function void put_response (
RSP t
)//以non_blocking的方式返回RSP给sequence,如果成功返回1,否则返回0

Driver Sequencer Handshake

driver中有一个叫做uvm_seq_item_pull_portTLM端口,该端口与sequencer的TLM端口uvm_seq_item_pull_imp对应。driver可以利用sequencer中imp端口定义的方法从sequencer获取下一个sequence item。也就是说driver和sequencer之间的通信是利用TLM端口来完成的。

在uvm_driver和uvm_sequencer这两个基类中有内建的TLM端口,所以当用户自定义driver和sequencer继承这两个基类时,不需要再实例化TLM端口,只需要调用即可。TLM端口在uvm_driver和uvm_sequencer中的定义情况如下:

// Definition of uvm_driver
class uvm_driver #(type REQ=uvm_sequence_item,
                   type RSP=REQ) extends uvm_component;//REQ的类默认为是uvm_sequence_item
  // Port: seq_item_port
  // Derived driver classes should use this port to request items from the
  // sequencer. They may also use it to send responses back.
 
  uvm_seq_item_pull_port #(REQ, RSP) seq_item_port;//创建TLM端口
 
  // Port: rsp_port
  // This port provides an alternate way of sending responses back to the
  // originating sequencer. Which port to use depends on which export the
  // sequencer provides for connection.
 
  uvm_analysis_port #(RSP) rsp_port;//TLM分析端口,分析端口一般用于广播
  REQ req;
  RSP rsp;
 
  // Rest of the code follows ...
 
endclass

rsp_port只有在driver使用该端口向sequencer写入回复(write response)时才需要与sequencer中的rsp_export分析端口连接; 

// Definition of uvm_sequencer
class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
                                   extends uvm_sequencer_param_base #(REQ, RSP);
  // Variable: seq_item_export
  // This export provides access to this sequencer's implementation of the
  // sequencer interface.
 
  uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;//创建TLM端口

  uvm_analysis_export #(RSP) rsp_export;//TLM分析端口
 
  // Rest of the class contents follow ...
 
endclass

两个TLM端口要在agent中的connect_phase中连接,如下:

class my_agent extends uvm_agent;
  `uvm_component_utils (my_agent)
 
  my_driver      #(my_sequence_item)   m_drv;
  uvm_sequencer   #(my_sequence_item)    m_seqr;
 
  virtual function void connect_phase (uvm_phase phase);
    // Always the port is connected to an export
    m_drv.seq_item_port.connect(m_seqr.seq_item_export);//注意,发起端都是driver,所以driver的端口调用connect()方法,sequencer的端口为connect()方法的参数
  endfunction
 
endclass

用户在自定义sequencer或者driver的时候,它们可以使用缺省类型type REQ=uvm_sequence_item,以及RSP与REQ类型一致。同时这会带来一个潜在的类型转换($cast(destination,source))要求,即driver得到REQ对象(uvm_sequence_item)在进行下一步处理时,需要进行动态的类型转换,将REQ转换为uvm_sequence_item的子类型才可以从中获取有效的成员数据;而另外一种可行的方式是在自定义sequencer和driver时就标明了其传递的具体item类型这样就不用再进行二次的类型转换了。通常情况下,RSP类型与REQ类型保持一致,这么做的好处是为了便于统一处理,方便item对象的拷贝、修改等操作 ;

driver在消化完当前的REQ后,可以通过item_done(input RSP) 方法来告知sequence当前sequence_item已经处理完毕,返回的RSP可选。driver也可以利用put_response()或put()方法来单独发送RSP;除此之外,还可以利用driver与sequencer之间的分析端口来完成RSP的发送!! 

Using get_next_item()

driver从squencer获得要求的sequence item(request sequence item REQ),同时可以选择的返回一个反馈sequence item(response sequence item RSP) ,通常REQ和RSP是同一类的,也可以设置为不同类。driver发送完RSP后,接着再获取下一个REQ。获取REQ的方法有两种,我们来讨论第一种get_next_item().

可以通过TLM端口句柄seq_item_port调用该方法例子如下:

class my_driver extends uvm_driver #(my_data);
  `uvm_component_utils (my_driver)
 
   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
 
      // 1.调用get_next_item()获取下一个REQ
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get_next_item(req);
 
      // 2. For simplicity, lets just assume the driver drives the received packet
      // during this time and consumes 20ns to complete driving the transaction
      //driver将REQ发送给DUT
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
 
      // 3. After driver has finished the transaction, it has to let the sequencer know
      // by calling item_done()
      //调用item_done()告诉sequencer,driver已经发送完了这个REQ
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      seq_item_port.item_done();
   endtask

一旦driver获得了REQ,它就可以通过一个虚接口将REQ发送到DUT中,发送完毕后调用item_done() 告诉sequencer发送完成.

在driver的run_phase()中的主要操作包括

  • 通过seq_item_port.get_next_item(REQ)从sequencer获取有效的request item;

  • 从request item中获取数据,进而产生数据激励;

  • 对request item进行克隆生成新的对象response item;

  • 修改response item中的数据成员,最终通过seq_item_port.item_done(RSP)将response item对象返回给sequence;

REQ也就是sequence item是通过sequence发送到sequencer的REQ FIFO中去的,sequence在sequencer上执行,而sequence item 在sequence中实例化并随机化.如下:

class my_sequence extends uvm_sequence(# my_data);
  `uvm_object_utils (my_sequence)
 
 
  virtual task body();
    // 1. 产生一个sequence item 对象
    my_data tx = my_data::type_id::create("tx");
    `uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
 
    // 2. Call the start_item() task which will send this object to the driver
    start_item(tx);
    `uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
 
    // 3. Because the class handle passed to the driver points to the same object, we 
    // can do late randomization
    tx.randomize();
    `uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
 
    // 4. Call finish_item method so that the sequence waits until the driver lets the 
    // sequencer know that this item has finished
    finish_item(tx);
    `uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
  endtask
endclass

来看下面一个完整的过程:

(1)定义sequence item

// Note that this class is dervide from "uvm_sequence_item"
class my_data extends uvm_sequence_item;
  rand bit [7:0]   data;
  rand bit [7:0]   addr;
 
  // Rest of the class contents come here ...
endclass

(2)定义sequence

class my_sequence extends uvm_sequence #(my_data);
    // Rest of the sequence code
 
    virtual task body();
      // 1. Create a sequence item of the given type
      my_data tx = my_data::type_id::create("tx");
      `uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
 
      // 2. Start the item on the sequencer which will send this to the driver
      start_item(tx);
      `uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
 
      // 3. Do some late randomization to create a different content in this transaction object
      tx.randomize();
      `uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
 
      // 4. Call finish_item to let driver continue driving the transaction object or sequence item
      finish_item(tx);
      `uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
    endtask
endclass

没有指明sequence在哪个sequencer上执行,就使用默认的sequencer!

(3)定义driver

class my_driver extends uvm_driver #(my_data);
   `uvm_component_utils (my_driver)
   // Other parts of the driver code if they exist
 
   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
 
      // 1. Get the next available item from the sequencer. If none exists, then wait until
      // next item is available -> this is blocking in nature
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get_next_item(req);
 
      // 2. Let us assume that the driver actively does the pin wiggling of the DUT during this time and 
      // consider it takes 20ns
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
 
      // 3. After the driver has driven all data to the DUT, it should let the sequencer know that it finished 
      // driving the transaction by calling "item_done". Optionally the response packet can be passed along with
      // the item_done method call and it will be placed in the sequencer's response FIFO
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      seq_item_port.item_done();
   endtask
endclass

(4)定义test

class base_test extends uvm_test;
  // Rest of the test code is here
 
  // The sequencer is parameterized to accept objects of type "my_data" only
    my_driver                  m_drv0;
    uvm_sequencer #(my_data)   m_seqr0;//使用默认的sequencer,即uvm_sequencer
    my_sequence           m_seq;
 
  // Build the sequencer and driver components
    virtual function void build_phase(uvm_phase phase);
       super.build_phase(phase);
       m_drv0 = my_driver::type_id::create ("m_drv0", this);//创建sequencer和driver对象
       m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
    endfunction
 
     // Connect the sequencer "export" to the driver's "port"
     virtual function void connect_phase (uvm_phase phase);
       super.connect_phase (phase);
       m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);//连接sequencer和driver的TLM端口
     endfunction
 
  // Start the sequence on the given sequencer
    virtual task run_phase(uvm_phase phase);
      m_seq = my_sequence::type_id::create("m_seq");//创建sequence对象
      phase.raise_objection(this);
      m_seq.start(m_seqr0);//启动sequence
      phase.drop_objection(this);
    endtask
endclass

将上面的步骤整合如下:

`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// This is the main sequence item class that will be used to create
// transactions such that it is sent to the driver from the sequencer
class my_data extends uvm_sequence_item;
  rand bit [7:0]   data;
  rand bit [7:0]   addr;

  `uvm_object_utils_begin (my_data)
     `uvm_field_int (data, UVM_ALL_ON)
     `uvm_field_int (addr, UVM_ALL_ON)
  `uvm_object_utils_end

  function new (string name="my_data");
    super.new(name);
  endfunction
endclass   

// This driver class will call "get_next_item" and "item_done" methods to communicate
// with the sequencer. Note that this driver is parameterized with "my_data"
// and hence it can accept only sequence items of type "my_data" and anything
class my_driver extends uvm_driver #(my_data);
   `uvm_component_utils (my_driver)
   function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction

   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get_next_item(req);
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      seq_item_port.item_done();
   endtask
endclass

// This is the main sequence that will be executed by the sequencer in
// this environment. The sequence constructs sequence items and the sequencer
// FIFO is filled with these sequence items. When the driver asks for the next
// sequence item, the sequencer will pop the item and provided to the driver
class my_sequence extends uvm_sequence;
  `uvm_object_utils (my_sequence)
  function new(string name = "my_sequence");
    super.new(name);
  endfunction

  virtual task body();
    my_data tx = my_data::type_id::create("tx");
    `uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
    start_item(tx);
    `uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
    tx.randomize();
    `uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
    finish_item(tx);
    `uvm_info ("SEQ", $sformatf("finish_item() fn call done"), UVM_MEDIUM)
  endtask
endclass 


// This is the base test class in which we will start the main sequence
// on the given sequencer
class base_test extends uvm_test;
  `uvm_component_utils (base_test)
  function new (string name, uvm_component parent = null);
     super.new (name, parent);
  endfunction : new
  
   my_driver                m_drv0;
   uvm_sequencer #(my_data) m_seqr0;
  my_sequence   m_seq;

  virtual function void build_phase(uvm_phase phase);
     super.build_phase(phase);
          m_drv0 = my_driver::type_id::create ("m_drv0", this);
      m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
  endfunction
  
  // Connect the sequencer "export" to the driver's "port"
  virtual function void connect_phase (uvm_phase phase);
    super.connect_phase(phase);
    m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
  endfunction

  virtual task run_phase(uvm_phase phase);
    m_seq = my_sequence::type_id::create("m_seq");    
    phase.raise_objection(this);
    m_seq.start(m_seqr0);
    phase.drop_objection(this);
  endtask
endclass 

module tb_top;
   initial run_test ("base_test");
endmodule

 结果如下:

Using get() and put()

前面我们介绍了driverget_next_item()方法来获取下一个REQ,也可以采用get()方法获取下一个REQ,用put()返回一个RSP。

来看在driver中如何调用put():

class my_driver extends uvm_driver #(my_data);
   `uvm_component_utils (my_driver)
 
   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
 
    // 1. Get an item from the sequencer using "get" method
      seq_item_port.get(req);
 
    // 2. For simplicity, lets assume the driver drives the item and consumes 20ns of simulation time
      #20;
 
    // 3. After the driver is done, assume it gets back a read data called 8'hAA from the DUT
    // Assign the read data into the "request" sequence_item object
      req.data = 8'hAA;//该变REQ,作为RSP返回
 
    // 4. Call the "put" method to send the request item back to the sequencer
      seq_item_port.put(req);
   endtask
endclass

来看相应的sequence中是如何发送sequence item和停止发送的:

class my_sequence extends uvm_sequence #(my_data);
  `uvm_object_utils (my_sequence)
 
  // Create a sequence item object handle to store the sequence_item contents
  my_data tx;
 
  virtual task body();
    // 1. Create the sequence item using standard factory calls
    tx = my_data::type_id::create("tx");
 
    // 2. Start this item on the current sequencer
    start_item(tx);
 
    // 3. Do late randomization since the class handle pointers are the same
    tx.randomize();
 
    // 4. Finish executing the item from the sequence perspective
    // The driver could still be actively driving and waiting for response
    finish_item(tx);//从sequence的视角看,当前sequence item发送完毕,但是driver可以能还是驱动
 
    // 5. Because "finish_item" does not indicate that the driver has finished driving the item,
    // the sequence has to wait until the driver explicitly tells the sequencer that the item is over
    // So, wait unitl the sequence gets a response back from the sequencer.
    get_response(tx);//获取driver返回的RSP,这里才是当前sequence item的最终完成
 
    `uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), UVM_MEDIUM)
  endtask
endclass

来看下面一个完整的过程:

(1)定义sequence item

// Note that this class is dervide from "uvm_sequence_item"
class my_data extends uvm_sequence_item;
  rand bit [7:0]   data;
  rand bit [7:0]   addr;
 
  // Rest of the class contents come here ...
endclass

(2)定义sequence

class my_sequence extends uvm_sequence #(my_data);
    // Rest of the sequence code
 
    virtual task body();
      // 1. Create a sequence item of the given type. Note that this sequence has an internal
      // variable called "req" which can be directly used as well instead of "tx"
      my_data tx = my_data::type_id::create("tx");
      `uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
 
      // 2. Start the item on the sequencer which will send this to the driver
      start_item(tx);
      `uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
 
      // 3. Do some late randomization to create a different content in this transaction object
      tx.randomize();
      `uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
 
      // 4. Call finish_item to let driver continue driving the transaction object or sequence item
      finish_item(tx);
      `uvm_info ("SEQ", $sformatf("finish_item() fn call done, wait for rsp"), UVM_MEDIUM)
 
      // 5. Wait for the driver to finish driving the object, and respond back with a response object
      // The transaction is said to be complete at this point. This task is blocking in nature and hence
      // the sequence will end only after the driver returns a response item.
      get_response(tx);
      `uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), UVM_MEDIUM)
    endtask
endclass

(3)定义driver

class my_driver extends uvm_driver #(my_data);
   `uvm_component_utils (my_driver)
 
   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
 
      // 1. Get sequence item from the sequencer using "get" method.
      // "req" is a pre-defined variable that comes with the class "uvm_driver"
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get(req);
 
      // 2. Assume that the driver drives this data during this time. For our purpose let's consider that the driver
      // consumes 20ns of simulation time
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
 
      // 3. Lets also assume that the DUT returned some read data back to the driver, which needs to be sent back
      // to the sequence. Note that read data is put into the same "request" class object handle 
      `uvm_info ("DRIVER", $sformatf ("#20 delay over, curr data=0x%0h", req.data), UVM_MEDIUM)
      req.data = 8'hAA;
 
      // 4. The driver calls the "put" method and sends back the response data to the sequencer
      `uvm_info ("DRIVER", $sformatf ("About to call put() with new data=0x%0h", req.data), UVM_MEDIUM)
      seq_item_port.put(req);
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
   endtask
endclass

(4)定义test

class base_test extends uvm_test;
  // Rest of the test code is here
 
  // The sequencer is parameterized to accept objects of type "my_data" only
    my_driver                  m_drv0;
    uvm_sequencer #(my_data)   m_seqr0;//sequence在默认的sequencer上执行
    my_sequence           m_seq;
 
  // Build the sequencer and driver components
    virtual function void build_phase(uvm_phase phase);
       super.build_phase(phase);
       m_drv0 = my_driver::type_id::create ("m_drv0", this);
       m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
    endfunction
 
     // Connect the sequencer "export" to the driver's "port"
     virtual function void connect_phase (uvm_phase phase);
       super.connect_phase (phase);
       m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
     endfunction
 
  // Start the sequence on the given sequencer
    virtual task run_phase(uvm_phase phase);
      m_seq = my_sequence::type_id::create("m_seq");//创建sequence对象
      phase.raise_objection(this);
      m_seq.start(m_seqr0);//启动sequence
      phase.drop_objection(this);
    endtask
endclass

 将上面的步骤整合如下:

`timescale 1ns/1ns
`include "uvm_macros.svh"
import uvm_pkg::*;

// This is the main sequence item class that will be used to create
// transactions such that it is sent to the driver from the sequencer
class my_data extends uvm_sequence_item;
  rand bit [7:0]   data;
  rand bit [7:0]   addr;

  `uvm_object_utils_begin (my_data)
     `uvm_field_int (data, UVM_ALL_ON)
     `uvm_field_int (addr, UVM_ALL_ON)
  `uvm_object_utils_end

  function new (string name="my_data");
    super.new(name);
  endfunction
endclass   

// This driver class will call "get" and "put" methods to communicate
// with the sequencer. Note that this driver is parameterized with "my_data"
// and hence it can accept only sequence items of type "my_data" and anything
// else will give a compilation error.
class my_driver extends uvm_driver #(my_data);
   `uvm_component_utils (my_driver)
   function new (string name, uvm_component parent);
      super.new (name, parent);
   endfunction

   virtual task run_phase(uvm_phase phase);
      super.run_phase(phase);
      `uvm_info ("DRIVER", $sformatf ("Waiting for data from sequencer"), UVM_MEDIUM)
      seq_item_port.get(req);
      `uvm_info ("DRIVER", $sformatf ("Start driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
      #20;
      `uvm_info ("DRIVER", $sformatf ("#20 delay over, curr data=0x%0h", req.data), UVM_MEDIUM)
      req.data = 8'hAA;
      `uvm_info ("DRIVER", $sformatf ("About to call put() with new data=0x%0h", req.data), UVM_MEDIUM)
      seq_item_port.put(req);
      `uvm_info ("DRIVER", $sformatf ("Finish driving tx addr=0x%0h data=0x%0h", req.addr, req.data), UVM_MEDIUM)
   endtask
endclass

// This is the main sequence that will be executed by the sequencer in
// this environment. The sequence constructs sequence items and the sequencer
// FIFO is filled with these sequence items. When the driver asks for the next
// sequence item, the sequencer will pop the item and provided to the driver
class my_sequence extends uvm_sequence #(my_data);
  `uvm_object_utils (my_sequence)
  function new(string name = "my_sequence");
    super.new(name);
  endfunction

  my_data tx;

  virtual task body();
    tx = my_data::type_id::create("tx");
    `uvm_info ("SEQ", $sformatf("About to call start_item"), UVM_MEDIUM)
    start_item(tx);
    `uvm_info ("SEQ", $sformatf("start_item() fn call done"), UVM_MEDIUM)
    tx.randomize();
    `uvm_info ("SEQ", $sformatf("tx randomized with addr=0x%0h data=0x%0h", tx.addr, tx.data), UVM_MEDIUM)
    finish_item(tx);
    `uvm_info ("SEQ", $sformatf("finish_item() fn call done, wait for rsp"), UVM_MEDIUM)
    get_response(tx);
    `uvm_info ("SEQ", $sformatf("get_response() fn call done rsp addr=0x%0h data=0x%0h, exit seq", tx.addr, tx.data), UVM_MEDIUM)
  endtask
endclass 

// This is the base test class in which we will start the main sequence
// on the given sequencer
class base_test extends uvm_test;
  	`uvm_component_utils (base_test)
  	function new (string name, uvm_component parent = null);
     	super.new (name, parent);
  	endfunction : new
  
  	my_driver                	m_drv0;
  	uvm_sequencer #(my_data) 	m_seqr0;
  	my_sequence   				m_seq;

  	virtual function void build_phase(uvm_phase phase);
     	super.build_phase(phase);
     	m_drv0 = my_driver::type_id::create ("m_drv0", this);
     	m_seqr0 = uvm_sequencer#(my_data)::type_id::create ("m_seqr0", this);
  	endfunction
  
   	// Connect the sequencer "export" to the driver's "port"
   	virtual function void connect_phase (uvm_phase phase);
     	super.connect_phase (phase);
     	m_drv0.seq_item_port.connect (m_seqr0.seq_item_export);
   	endfunction

  	virtual task run_phase(uvm_phase phase);
    	m_seq = my_sequence::type_id::create("m_seq");
    	phase.raise_objection(this);
    	m_seq.start(m_seqr0);
    	phase.drop_objection(this);
  	endtask
endclass 

module tb_top;
   initial run_test ("base_test");
endmodule

 结果:

总结 :

  • 对于uvm_sequence::get_response(RSP)和uvm_driver::item_done(RSP)这种成对的操作,是可选的而不是必须的,即用户可以选择uvm_driver不返回response item,同时sequence也无需获取response item;
  • 在多个sequence同时向sequencer发送item时,就需要有ID信息表明该item从哪个sequence来,这个ID信息在sequence创建item时就赋值了,而在到达driver以后,这个ID也能用来跟踪它的sequence信息,这就跟食品加工从源头就标记二维码一样,使得运输和使用更加安全。这个ID信息在稍后driver假如要返回response item时,需要给定正确的信息,可以在driver中通过函数get_sequence_id()来获取标记,这也就使得sequencer可以根据ID信息来分发这些response item返回至正确的sequence源头;

  • 我们建议用户在driver中,通过clone()的方式单独创建response item,保证request item和response item两个对象的独立性。也许有的用户为了“简便”,而在使用了request item之后,就直接修改了它的数据作为要返回给sequence的response item。这么做看来,似乎节能环保,但实际上殊不知可能埋下隐患,一方面它延长了本来应该丢进垃圾桶的request item寿命,同时也无法再对request item原始生成数据做出有效记录。所以,谈到这里,请读者们记住一点,clone()和copy()是个好习惯,虽然多了那么几行代码,但你的代码稳健性无形中却提高了,这种代码方式可以帮助减少一些可能的隐患;

  • 为了统一起见,用户可以不在定义sequence或者driver时指定sequence item类型,使用默认类型REQ = uvm_sequence_item,但是用户需要注意在driver一侧的类型转换,例如get_next_item(REQ)的输出REQ句柄应当做出二次转换,得到正确类型之后再进行接下来的操作;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值