sequence,sequencer 和 driver之间sequence_item的传递

我们先来看源码

1,Driver 和 Sequencer的源码

uvm_driver源码

class uvm_driver #(type REQ=uvm_sequence_item,
                   type RSP=REQ) extends uvm_component;


  // 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;

  uvm_seq_item_pull_port #(REQ, RSP) seq_item_prod_if; // alias

  // 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;

  REQ req;
  RSP rsp;

  .......

endclass

uvm_sequencer源码

class uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
                                   extends uvm_sequencer_param_base #(REQ, RSP);

  typedef uvm_sequencer #( REQ , RSP) this_type;

  bit sequence_item_requested;
  bit get_next_item_called;

  `uvm_component_param_utils(this_type)

  // Variable: seq_item_export
  //
  // This export provides access to this sequencer's implementation of the
  // sequencer interface, <uvm_sqr_if_base #(REQ,RSP)>, which defines the following
  // methods:
  //
  //| Requests:
  //|  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:
  //|  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   ();
  //
  // See <uvm_sqr_if_base #(REQ,RSP)> for information about this interface.

  uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export;

......

endclass  

2,Driver 和 Sequencer 之间的握手

在uvm_driver的源码中有一个 uvm_seq_item_pull_port #(REQ,RSP) 类型的 seq_item_port,从源码上的注释看,这个tml_port是用来向sequencer请求数据 的;

在uvm_sequencer的源码中,uvm_seq_item_pull_imp #(REQ, RSP, this_type) 类型的seq_item_export,同样也是用来与 driver进行通信的;

说白了这块的逻辑也非常简单,就是driver不断的通过 seq_item_pull_port 向 sequencer请求数据,由于是pull的动作,driver是发起人,也是消费者,而sequencer是接收人,只要有seq_item,就把seq_item丢给driver,driver再通过虚接口,将seq_item发送给dut,完成数据的传输,最后在调用item_done通知sequence数据发送完了,可以发送下一笔数据了。既然是port 和imp之间的链接,那么必然在验证环境中要实现port和imp之间的连接,由于driver和sequencer都是“属于”agent的,所以我们可以在agent的connect_phase将两者连接起来:

class my_agent extends uvm_agent;
    `uvm_component_utils(my_agent)
    
    my_driver    #(my_sequence_item)  m_drv;
    my_sequencer #(my_sequence_item)  m_seqr;

    virtual function void connect_phase(uvm_phase phase);
    // other connection ...
        m_drv.seq_item_port.connect(m_seqr.seq_item_export);
    endfunction

endclass

有一个很别扭的点是,明明在uvm_sequencer中定义的是imp,为啥却要起个 seq_item_export的名字,为啥不叫 seq_item_imp,这种做法应该是想要用户注意,sequencer只是一个中转站,是将sequence的 seq_item 通过 sequencer的调度将seq_item送给driver,不然起个_export的名字实在是歧义满满。

3,Sequencer和sequence之间的通信

sequencer和sequence之间的通信在源码上看到的结果不是很清晰,一共分了6个步骤,靠如下六个函数(task)完成,代码如下:

// The following methods are called, in order
//
//|
//|   sequencer.wait_for_grant(prior) (task) \ start_item  \
//|   parent_seq.pre_do(1)            (task) /              \
//|                                                      `uvm_do* macros
//|   parent_seq.mid_do(item)         (func) \              /
//|   sequencer.send_request(item)    (func)  \finish_item /
//|   sequencer.wait_for_item_done()  (task)  /
//|   parent_seq.post_do(item)        (func) /

从上到下依次是:

1,wait_for_grant请求sequencer,是否可以获取seq_item,

2,在seq_item随机化之前的callback函数,可以对item进行修改,或者做一些打印

3,在seq_item随机化之后,送给driver之前的callback函数,可以对item进行修改(这很重要)

4,将seq_item送给sequencer,

5,等待接收driver调用item_done,

6,seq_item发送之后的callback函数(用户无法直接调用)。

4,sequence->sequencer->driver 的数据传递以及 driver -> sequence 的反馈、

这块我简单的说明一下整个数据流的过程,大前提是:driver的pull_port一直处于“饥渴”状态,等待sequencer把seq_item送过来。

1,sequence发起 wait_for_grant ,请求sequencer是否能够接收seq_item;

2,sequencer内部等待sequence aribtration调度完成,可以接收新数据;

3,sequence 通过函数 send_request(item)将seq_item发送给sequencer;

4,driver调用 get_next_item获取seq_item;

5,driver将 seq_item通过virtual interface将item发送给dut;

6,driver调用 item_done 通知sequence当前的seq_item已经发送完成了

7,sequence的函数:wait_item_done() 结束条件满足(实际上在seq_item传输过程中,会产生seq_id,item_done之后,将id通过sequencer发送给sequence,sequence通过比对seq_id来判断当前的item是否发送完,尽管id=-1也有握手的过程)。

8,进行下一个seq_item;

借用下面这个图,来说明整个过程:

driver-sequencer-get-next-item-flow

当然,在整个过程中,我们没必要将整个过程的代码都完整的实现一边,大部分的代码uvm的源码已经帮我们完成了,我们只需要完成很小的一部分如下:

1,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);

        // 调用get_next_item 获取新的seq_item
        seq_item_port.get_next_item(req);

        // 发送seq_item,我们这里用#20ns 来代替
        #20ns;

        // 发送完seq_item之后,通知sequence,数据已经发送完
        seq_item_port.item_done();
    endtask

......
......
......
endclass

2,sequence中需要实现的代码:

class my_sequence extends uvm_sequence;
    `uvm_object_utils(my_sequence)
    

    virtual task body();
    
    //1 ,create item
    my_data_tx = my_data::type_id::create("tx");
    
    // 我们这里用start_item代替上面uvm源码里面的那几个task/fun
    start_item(tx);


    // 随机化
    tx.randomize(); // 这里也可以用randomize.with({constraint})

    // 发送seq_item

    finish_item(tx);

    endtask

endclass

上面我们讲的这部分数据流中都隐式的包含了driver的put_reponse 和 sequence的get_response ,下面说明如何显示的实现这种driver和sequence的应答。

5,sequence->sequencer->driver 的数据传递以及 driver -> sequence 的反馈

数据流与上面的大部分类似,这里只把response的把部分加上。

这块我简单的说明一下整个数据流的过程,大前提是:driver的pull_port一直处于“饥渴”状态,等待sequencer把seq_item送过来。

1,sequence发起 wait_for_grant ,请求sequencer是否能够接收seq_item;

2,sequencer内部等待sequence aribtration调度完成,可以接收新数据;

3,sequence 通过函数 send_request(req)将seq_item发送给sequencer;

4,driver调用 seq_item_port.get(req)获取seq_item;

5,driver将 seq_item通过virtual interface将item发送给dut;

6,driver调用 seq_item_port.put(rsp) 通知sequence当前的seq_item已经发送完成了,并且反馈给sequence rsp,

7,sequence的函数:wait_item_done() 等待driver反馈 item_done();

8,get_response(),获取这个rsp

9,进行下一个seq_item;

整个过程借用下面这个图说明:

driver-sequencer-get-and-put-flow

上面我们所说的response机制是:driver将rsp推送给sequencer,sequencer内部有一个rsp队列,当有新的response进入时,就push_back进入队列,sequence中的get_response()就是从这个队列中pop_front一个数据,尽管这个队列的深度只有8,你可以可以通过函数set_response_queue_depth(-1)将队列的深度设置为无限大(设为-1之后,由于是int型,所以最大深度应该是32767),但是如果不设置深度的话,超过8这个深度,就是报溢出的错。

这个深度设置其实也非常有意义,比如说,我们在连续进行寄存器的读写测试时,我们可以连续的对一片寄存器进行读操作,将所有rsp即dut返回的读数据全部存储在这个rsp的队列中,当所有的读操作都进行完成之后,我们在把这个rsp的数据读空,并根据id信息将读出来的数据一一还原,毕竟UVM自带的ral方案实在是太难弄了。。。

关于sequence arbitration,uvm源码里面有很多函数参与sequence的仲裁,关于这部分可以参考张强的《UVM实战》这本书,这里面不展开了,关于如何启动sequence会重新开一题。

个人感觉,就整个UVM的设计来看,不管他采用了什么高大上的设计模式,sequencer,sequence 和 driver 这三部分之间的通信和数据传递可以说设计的非常巧妙和复杂,满足对各种场景的验证需求,可以说是非常完善了。


 

  • 14
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一般来说,m_sequencer不会同时作为sequencesequence item的成员变量,因为它们是不同的类别。一般情况下,sequencesequence item是在SystemVerilog Testbench中使用的,用于驱动和监测DUT。下面是一个示例代码,其中包含sequencesequence item,并且它们都有自己的成员变量: ``` class my_sequence extends uvm_sequence #(my_sequence_item); rand int data; my_sequence_item seq_item; function new(string name = "my_sequence"); super.new(name); endfunction virtual task body(); // Generate sequence items repeat(10) begin seq_item = my_sequence_item::type_id::create("seq_item"); seq_item.data = $urandom_range(0, 10); seq_item.addr = $urandom_range(0, 1024); seq_item.size = 4; `uvm_info("my_sequence", $sformatf("Sequence item generated: addr=%0d, data=%0d", seq_item.addr, seq_item.data), UVM_MEDIUM) // Send sequence item to driver seq_item.randomize(); seq_item.set_starting_phase(get_starting_phase()); seq_item.set_parent_sequence(this); if(!seq_item.randomize()) `uvm_error("my_sequence", "Failed to randomize sequence item") if(!seq_item.send_request()) `uvm_error("my_sequence", "Failed to send sequence item") // Wait for response from DUT if(!seq_item.grab_response()) `uvm_error("my_sequence", "Failed to grab response for sequence item") end endtask endclass class my_sequence_item extends uvm_sequence_item; rand int data; int addr; int size; `uvm_object_utils(my_sequence_item) function new(string name = "my_sequence_item"); super.new(name); endfunction function void do_print (uvm_printer printer); super.do_print(printer); printer.print_field("data", data, $bits(data), UVM_HEX); printer.print_field("addr", addr, $bits(addr), UVM_HEX); printer.print_field("size", size, $bits(size), UVM_DEC); endfunction endclass ``` 在这个示例代码中,my_sequence类表示一个sequence,my_sequence_item类表示一个sequence item。它们都有自己的成员变量,例如my_sequence类中的data和seq_item,以及my_sequence_item类中的data、addr和size。没有一个名为m_sequencer的成员变量,因为它们都是在uvm_sequence和uvm_sequence_item的基础上定义的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值