UVM序列篇总结

UVM序列篇之一:新手上路

  • sequencer和driver之间使用TLM连接
  • sequencer和driver之间采用get 模式;driver作为发起方;主动去向sequencer要item

UVM序列篇之二:sequence和item(上)

item中包含的内容

  • 控制类。譬如总线协议上的读写类型、数据长度、传送模式等。
  • 负载类。一般即数据总线上的数据包。
  • 配置类。这往往是用来控制driver的驱动行为,例如命令driver的发送间隔或者有无错误插入。
  • 调试类。用来标记一些额外的信息,用来方便调试,例如该对象的实例序号、创建时间、被driver解析的时间始末等。

sequence可以分类为:

  • 扁平类(flat sequence)。这一类中往往只用来组织更细小的粒度,即item示例的组织。
  • 层次类(hierarchical sequence)。这一类则是由更高层的sequence用来组织底层的sequence,进而让这些sequence或者按照顺序的方式,或者按照并行的方式,挂载到同一个sequencer上。
  • 虚拟类(virtual sequence)。这一类则是最终控制整个测试场景的方式,鉴于整个环境中往往存在不同种类的sequencer和其对应的sequence,我们需要一个虚拟的sequence来协调顶层的测试场景。之所以称这个方式为virtual sequence,是因为该序列本省并不固定挂载于某一种sequencer类型上,而是它会将其内部的各种不同类型的sequence最终挂载到不同的目标sequencer上面。这也是最大的不同于hierarchical sequence的一点。

UVM序列篇之三:sequence和item(下)

在这里插入图片描述

  • sequence中也可以做constraint对item中的field做修改;但是这个一般建议在item中完成;下图中仅是个在seq中做约束的例子;
    在这里插入图片描述

  • test中也可以加约束;

在这里插入图片描述

  • hierarchical sequence可以同时使用其它hierarchical sequence、flat sequence和sequence item;

UVM序列篇之四:sequencer和driver

为了便于item传输,UVM专门定义了匹配的TLM端口供sequencer和driver使用:

  • uvm_seq_item_pull_port #(type REQ=int, type RSP=REQ)
  • uvm_seq_item_pull_export #(type REQ=int, type RSP=REQ)
  • uvm_seq_item_pull_imp #(type REQ=int, type RSP=REQ, type imp=int)

由于driver是请求发起端,所以在driver一侧例化了下面的两种端口:

  • uvm_seq_item_pull_port #(REP, RSP) seq_item_port
  • uvm_analysis_port #(RSP) rsp_port

而sequencer一侧则为请求的响应端,在sequencer一侧例化了与上面对应的两种端口:

  • uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export
  • uvm_analysis_export #(RSP) rsp_export

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

  • 在定义sequencer时,默认了REQ类型为uvm_sequence_item类型,这与稍后定义driver时采取默认REQ类型保持一致。

  • 实际发送的类型是用户自定义的一个user_item类型;

  • 多个sequence都试图要挂载到同一个sequencer上时,涉及sequencer的仲裁功能,

  • 可以指定sequencer和driver中传递的类型是user_item, 或者默认uvm_sequence_item;如果选择默认的话,就要进行类型转换
    在这里插入图片描述
    代码建议:

  • 在多个sequence同时向sequencer发送item时,就需要有ID信息表明该item从哪个sequence来,这个ID信息在sequence创建item时就赋值了,而在到达driver以后,这个ID也能用来跟踪它的sequence信息,这就跟食品加工从源头就标记二维码一样,使得运输和使用更加安全。这个ID信息在稍后driver假如要返回response item时,需要给定正确的信息,如上面例码中通过函数set_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句柄应当做出二次转换,得到正确类型之后再进行接下来的操作。

  • 有的时候,如果要复用一些验证IP,用户需要修改原有的底层sequence item。这个时候,处于验证复用的角度,我们建议通过继承于原有sequence item的方式定义子类,同时在顶层通过factory override的方式用新的sequence item替换原有的sequence item类型。

UVM序列篇之五:sequencer和sequence(上)

在这里插入图片描述

关于发送sequence/item的几点建议:

  • 无论sequence处于什么层次,都应当让sequence最终在test结束前执行完毕。

  • 尽量避免使用fork-join_any或者fork-join_none来控制sequence的发送顺序。因为这背后隐藏的风险是,如果用户想终止在背后运行的sequence线程而简单使用disable的方式,那么就可能在不恰当的时间点上锁住sequencer。一旦sequencer被锁住,而又无法释放,接下来也就无法无法其它的sequence。所以,如果用户想实现类似fork-join_any或者fork-join_none的发送顺序,还应当在使用disable前,对各个sequence线程的后台保持关注,尽量在发送完item完成握手之后,再终止sequence, 这样才能避免一些问题。

  • 如果用户要使用fork-join的方式,那么应当确保有方法可以让sequence线程在满足一些条件后停止发送item。否则,只要有一个sequence线程无法停止,则整个fork-join无法退出。面对这种情况,仍然需要用户考虑监测合适的时间点,才能够使用disable来关闭线程。

在上面谈到的建议中,用户需要认识到,disable的手段对于这种需要严格完成握手的传送方式,是需要额外处理的。否则,轻易的停止sequence可能导致整个sequence与sequencer之间的传送瘫痪。

UVM序列篇之六:sequencer和sequence(下)

  • 优先级
  • lock()与unlock() 部分没看

第二段:sequence/sequencer的调用
在这里插入图片描述

sequence如何将item放入 sequencer中

uvm_sequence_base.svh
mid_do是一个callback;
finish_item调用sequencer.send_request()

  virtual task finish_item (uvm_sequence_item item,
                            int set_priority = -1);
    uvm_sequencer_base sequencer;
    sequencer = item.get_sequencer();

    if (sequencer == null) begin
        uvm_report_fatal("STRITM", "sequence_item has null sequencer", UVM_NONE);
    end

    mid_do(item);
    sequencer.send_request(this, item);
    sequencer.wait_for_item_done(this, -1);
    `ifndef UVM_DISABLE_AUTO_ITEM_RECORDING
    sequencer.end_tr(item);
    `endif
    post_do(item);
  endtask

uvm_sequencer_base.svh

  // Function: send_request
  //
  // Derived classes implement this function to send a request item to the
  // sequencer, which will forward it to the driver.  If the rerandomize bit
  // is set, the item will be randomized before being sent to the driver.
  //  
  // This function may only be called after a <wait_for_grant> call.

  extern virtual function void send_request(uvm_sequence_base sequence_ptr,
                                            uvm_sequence_item t,
                                            bit rerandomize = 0);

// send_request
// ------------

function void uvm_sequencer_base::send_request(uvm_sequence_base sequence_ptr,
                                               uvm_sequence_item t,
                                               bit rerandomize = 0);
  return;
endfunction


finish_item中使用的是下边这个函数;
class uvm_sequencer_param_base #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequencer_base;

// send_request
// ------------

function void uvm_sequencer_param_base::send_request(uvm_sequence_base sequence_ptr,
                                                     uvm_sequence_item t,
                                                     bit rerandomize = 0);
  REQ param_t;

  if (sequence_ptr == null) begin
    uvm_report_fatal("SNDREQ", "Send request sequence_ptr is null", UVM_NONE);
  end

  if (sequence_ptr.m_wait_for_grant_semaphore < 1) begin
    uvm_report_fatal("SNDREQ", "Send request called without wait_for_grant", UVM_NONE);
  end
  sequence_ptr.m_wait_for_grant_semaphore--;
  
  if ($cast(param_t, t)) begin
    if (rerandomize == 1) begin
      if (!param_t.randomize()) begin
        uvm_report_warning("SQRSNDREQ", "Failed to rerandomize sequence item in send_request");
      end
    end
    if (param_t.get_transaction_id() == -1) begin
      param_t.set_transaction_id(sequence_ptr.m_next_transaction_id++);
    end
    m_last_req_push_front(param_t);
  end else begin
    uvm_report_fatal(get_name(),$sformatf("send_request failed to cast sequence item"), UVM_NONE);
  end

  param_t.set_sequence_id(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
  t.set_sequencer(this);
//  uvm_tlm_fifo #(REQ) m_req_fifo; 是uvm_sequencer_param_base 的member
  if (m_req_fifo.try_put(param_t) != 1) begin   //这个是放入m_req_fifo
    uvm_report_fatal(get_full_name(), "Concurrent calls to get_next_item() not supported. Consider using a semaphore to ensure that concurrent processes take turns in the driver", UVM_NONE);
  end

  m_num_reqs_sent++;
  // Grant any locks as soon as possible
  grant_queued_locks();
endfunction

virtual class uvm_sequence #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequence_base;
中也有一个send_request

  // Function: send_request
  //
  // This method will send the request item to the sequencer, which will forward
  // it to the driver.  If the rerandomize bit is set, the item will be
  // randomized before being sent to the driver. The send_request function may
  // only be called after <uvm_sequence_base::wait_for_grant> returns.

  function void send_request(uvm_sequence_item request, bit rerandomize = 0);
    REQ m_request;
    
    if (m_sequencer == null) begin
      uvm_report_fatal("SSENDREQ", "Null m_sequencer reference", UVM_NONE);
    end
    if (!$cast(m_request, request)) begin
      uvm_report_fatal("SSENDREQ", "Failure to cast uvm_sequence_item to request", UVM_NONE);
    end
    m_sequencer.send_request(this, m_request, rerandomize);
  endfunction

UVM序列篇之六:sequencer和sequence(下)
在这里插入图片描述

  • 如何区别于接下来讲到的virtual sequence,毕竟它们两者之间的共同点就是对于各个sequence的协调,而它们的不同则在于,hierarhical sequence主要面对的对象是同一个sequencer,即hierarchical sequence本身也会挂载到sequencer上面,而对于virtual sequence而言,它内部的sequence可以允许面向不同的sequencer种类,
  • sequence_lib 作用和hierarhical sequence类似,针对同一个sequencer

就之前的sequence和sequencer而言,它们的差别在于:

  • virtual sequence可以承载不同目标sequencer的sequence群落。而组织协调这些sequence的方式则类似于高层次的hiearchical sequence。virtual sequence一般只会挂载到virtual sequencer上面。

  • virtual sequencer与普通的sequencer相比有着很大的不同,那就是它们只是起到了桥接其它sequencer的作用,即virtual sequencer是一个指示所有底层sequencer句柄的地方,它是一个中心化的路由器。同时virtual sequencer本身并不会传送数据对象例如item,因此,virtual sequencer不需要与任何的driver进行TLM连接。所以,UVM用户需要在顶层的connect阶段,做好virtual sequencer中各个悬空句柄与各个底层实体对象的一一对接,避免句柄的悬空。

UVM序列篇之七:sequence的层次化(上)

virtual sequence和virutal sequencer

UVM初学者在一开始学习virtual sequence和virutal sequencer时容易出现编译和运行时句柄悬空的错误,还有一些概念上的偏差

  • 需要区分virtual sequence同其它普通sequence(element sequence、hierarchical sequence)。

  • 需要区分virtual sequencer同其它底层负责传送数据对象的sequencer。

  • 在virtual sequence中记得使用宏`uvm_declare_p_sequencer来创建正确类型的p_sequencer变量方便后面的各个目标sequencer的索引。

  • 在顶层环境中记得创建virtual sequencer并且完成virtual sequencer中各个sequencer句柄从底层到顶层的跨层次连接。

UVM序列篇之八(终):sequence的层次化(下)

Layering Sequence/layered

一些关于如何实现sequencer layer协议转换的方法。

  • 无论有多少抽象层次的transaction类别定义,都应该有对应的sequencer作为transaction的路由通道。例如layer_sequencer和phy_master_sequencer分别作为bus_trans和layer_trans的通道。

  • 在各个抽象级的sequencer中,需要有相应的转换方法,将从高层次的transaction从高层次的sequencer获取,继而转换为低层次的transaction,最终通过低层次的sequencer发送出去。例如adapter_seq负责从layer_sequencer获取layer_trans,再将其转换为对应的phy_master_sequencer一侧对应的sequence或者transaction,最后将其从phy_master_sequencer发送出去。

  • 这些adaption sequence应该运行在低层次的sequencer一侧,作为“永动”的sequence应该时刻做好准备,来服务从高层次的sequencer获取transaction,通过转化将其从低层次的sequencer一侧送出。例如上面在test1中,adapter.start(phy_agt.sqr)用来做好adapter sequence的服务。

  • 至于哪一个层次的transaction item的定义,上面的例子仅仅是为了说明layer sequence的一般方法,对于实际中的层次定义和对应的transaction item的定义,我们还需要具体问题具体处理。

从中我们既可以看到各个sequence类别对应的sequencer,同时也有sequence item发送和转换的方向。经过层层的转化,最终高层次的transaction内容可以落实到低层次的protocol agent和physical interface上面。上面的例码中没有给出读回路的处理,即从physical interface穿过physical agent,最终抵达layering sequencer的通信。在实际中,我们可以通过目前以后的回路,经过附加的response item返回来实现,也可以通过一级级的monitor采集response transaction,最终通过monitor转化抽象级返回item的方式来实现。至于选择哪一种反馈回路,这与底层agent反馈回路的实现方式有关,即如果原有的方式通过driver一侧返回response,那么我们建议继续在该反馈链条上进行从低级transaction到高级transaction的转化,如果原有的方式通过monitor一侧返回response,那么我们也建议创建对应的高层次的monitor,实现层次转化。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值