UVM中的虚拟序列:为什么,如何?

描述

大多数UVM测试平台由可重复使用的验证组件组成,除非我们正在对像MIPI-CSI这样的简单协议进行块级验证。考虑验证简单协议的场景;在这种情况下,我们可以忍受只有一个音序器将刺激发送给驱动器。顶级测试将使用此序列器来处理序列(如上一篇博客文章中所述)。在这里,我们可能不需要虚拟序列(或虚拟序列器)。

但是,当我们尝试将此IP集成到我们的SOC(或顶级块)中时,我们肯定要考虑重用用于验证这些块的测试平台组件。让我们考虑一个简单的案例,其中我们正在集成两个这样的块:两个音序器驱动这两个块。从顶级测试来看,我们需要一种方法来控制这两个音序器。

这可以通过使用虚拟序列器和虚拟序列来实现。另一种方法是通过将序列器传递给启动方法,从顶级测试显式调用序列的启动方法。

我将通过一个例子来解释这种用法,其中 USB 主机集成在 AXI 环境中。让我们看看如何从顶级测试中控制USB音序器和AXI音序器。对于此特定测试,我想配置 AXI 寄存器,然后发送 USB 传输。对于配置 AXI 寄存器,我使用序列说axi_cfg_reg_sequence,对于发送 USB 传输,我使用我在上一篇博客文章中使用的序列 (usb_complex_sequence)。下面是一个示例,其中在不使用虚拟序列的情况下控制多个序列器。

 

//Top-level test where multiple sequencers are controlled from the
//phase method.
class axi_cfg_usb_bulk_test extends uvm_test;
  `uvm_component_utils(usb_ltssm_bulk_test)

  //Sequences which needs to be exercised
  usb_reset_sequence u_reset_seq;
  axi_reset_sequence a_reset_seq;
  usb_complex_sequence u_bulk_seq;
  axi_cfg_reg_sequence a_cfg_reg_seq;

  function new (strint name=”axi_cfg_usb_bulk_test”,
                uvm_component parent=null);
    …
  endfunction: new

  //Call the reset sequences in the reset_phase
  virtual task reset_phase (uvm_phase phase);
    phase.raise_objections(this);
    …
    //Executing sequences by calling the start method directly by passing the
    //corresponding sequencer
    a_reset_seq.start(env.axi_master_agent_obj.sequencer);
    u_reset_seq.start(env.usb_host_agent_obj.sequencer);
    …
    phase.drop_objections(this);
  endtask:reset_phase

  virtual task main_phase (uvm_phase phase);
    phase.raise_objections(this);
    …
    //Executing sequences by calling the start method directly by passing the
    //corresponding sequencer
    a_cfg_reg_seq.start(env.axi_master_agent_obj.sequencer);
    u_bulk_seq.start(env.usb_host_agent_obj.sequencer);
    …
    phase.drop_objections(this);
  endtask:main_phase
endclass: axi_cfg_usb_bulk_test

 

这不是控制序列器的最有效方法,因为我们直接在测试中使用简单的序列并使其变得复杂。通过这样做,我们无法进一步重用这些复杂的场景来开发更复杂的场景。相反,如果我们尝试创建一个序列并在测试中使用该序列,那么我们也可以在其他测试(或序列)中重用这些序列。此外,与在顶级测试中创建整个方案相比,维护和调试这些序列将更容易。

在理解了为什么我们需要虚拟序列和虚拟序列器之后,让我们看看如何通过上面显示的相同示例来实现这一点。

我们需要做的第一件事是创建一个虚拟序列器。请注意,虚拟序列只能与虚拟序列器关联(但不能与非虚拟序列器关联)。虚拟排序器也像任何其他非虚拟排序器一样派生自uvm_sequencer,但不附加到任何驱动程序。虚拟音序器引用了我们尝试控制的音序器。这些引用从顶部环境分配给非虚拟序列器。

 

//Virtual sequencer having references to non-virtual sequencers
Class system_virtual_sequencer extends uvm_sequencer;
  //References to non-virtual sequencer
  usb_sequencer usb_seqr;
  axi_sequencer axi_seqr;

  function new (string name=”usb_ltssm_bulk_test”,
                uvm_component parent=null);
    …
  endfunction: new

  `uvm_component_utils(system_virtual_sequencer)

endclass: system_virtual_sequencer

//Top level environment, where virtual sequencer’s references
//are connected to non-virtual sequencers
class system_env extends uvm_env;
  //Agents where the non-virtual sequencers are present
  usb_host_agent usb_host_agent_obj;
  axi_master_agent axi_master_agent_obj;
  //Virtual sequencer
  system_virtual_sequencer sys_vir_seqr;

  `uvm_component_utils(system_env)

  function new (string name=”system_env”, uvm_component parent=null);
   …
  endfunction: new

  function void connect_phase(uvm_phase phase);
    //Assigning the virtual sequencer’s references to non-virtual sequencers
    sys_vir_seqr.usb_seqr = usb_host_agent_obj.sequencer;
    sys_vir_seqr.axi_seqr = axi_master_agent_obj.sequencer;
  endfunction: connect_phase

endclass: system_virtual_sequencer

 

现在我们有虚拟序列器,其中包含对非虚拟序列器的引用,我们想要控制这些序列,让我们看看如何使用虚拟序列控制这些非虚拟序列器。

虚拟序列与任何其他序列相同,但与非虚拟序列不同,它与虚拟序列器相关联,因此它需要指示它必须使用哪个非虚拟序列来执行基础序列。另请注意,虚拟序列只能执行序列或其他虚拟序列,而不能执行项目。使用“uvm_do_on/”uvm_do_on_with执行非虚拟序列,使用“uvm_do/”uvm_do_with执行其他虚拟序列。

 

//virtual sequence for reset operation
class axi_usb_reset_virtual_sequence extends uvm_sequence;

  `uvm_object_utils(axi_usb_reset_virtual_sequence)

  //non-virtual reset sequences
  usb_reset_sequence u_reset_seq;
  axi_reset_sequence a_reset_seq;

  function new (string name=” axi_usb_reset_virtual_sequence”,
                uvm_component parent=null);
    …
  endfunction: new

  …

  task body();
    …
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_reset_seq, p_sequencer.axi_seqr)
    a_reset_seq.get_response();
    `uvm_do_on(u_reset_seq, p_sequencer.usb_seqr)
    u_reset_seq.get_response();
  endtask: body

endclass: axi_usb_reset_virtual_sequence

//virtual sequence for doing axi register configuration
//followed by USB transfer
class axi_cfg_usb_bulk_virtual_sequence extends uvm_sequence;

  `uvm_object_utils(axi_cfg_usb_bulk_virtual_sequence)
  `uvm_declare_p_sequencer(system_virtual_sequencer)

  //Re-using the non-virtual sequences
  usb_complex_sequence u_bulk_seq;
  axi_cfg_reg_sequence a_cfg_reg_seq;

  function new (string name=” axi_cfg_usb_bulk_virtual_sequence”,
                uvm_component parent=null);
    …
  endfunction: new

  task body();
    …
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_cfg_reg_seq, p_sequencer.axi_seqr)
    a_cfg_req_seq.get_response();
    `uvm_do_on(u_bulk_seq, p_sequencer.usb_seqr)
    u_bulk_seq.get_response();
  endtask: body

endclass: axi_cfg_usb_bulk_virtual_sequence

 

在上面的虚拟序列中,我们执行axi_cfg_reg_sequence然后执行usb_complex_sequence。现在虚拟序列和虚拟序列器已经准备就绪,让我们看看如何从顶级测试中执行此虚拟序列。

 

//virtual sequence for reset operation
class axi_usb_reset_virtual_sequence extends uvm_sequence;

  `uvm_object_utils(axi_usb_reset_virtual_sequence)

  //non-virtual reset sequences
  usb_reset_sequence u_reset_seq;
  axi_reset_sequence a_reset_seq;

  function new (string name=” axi_usb_reset_virtual_sequence”,
                uvm_component parent=null);
    …
  endfunction: new

  …

  task body();
    …
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_reset_seq, p_sequencer.axi_seqr)
    a_reset_seq.get_response();
    `uvm_do_on(u_reset_seq, p_sequencer.usb_seqr)
    u_reset_seq.get_response();
  endtask: body

endclass: axi_usb_reset_virtual_sequence

//virtual sequence for doing axi register configuration
//followed by USB transfer
class axi_cfg_usb_bulk_virtual_sequence extends uvm_sequence;

  `uvm_object_utils(axi_cfg_usb_bulk_virtual_sequence)
  `uvm_declare_p_sequencer(system_virtual_sequencer)

  //Re-using the non-virtual sequences
  usb_complex_sequence u_bulk_seq;
  axi_cfg_reg_sequence a_cfg_reg_seq;

  function new (string name=” axi_cfg_usb_bulk_virtual_sequence”,
                uvm_component parent=null);
    …
  endfunction: new

  task body();
    …
    //executingnon-virtual sequence on the corresponding
    //non-virtual sequencer using `uvm_do_on
    `uvm_do_on(a_cfg_reg_seq, p_sequencer.axi_seqr)
    a_cfg_req_seq.get_response();
    `uvm_do_on(u_bulk_seq, p_sequencer.usb_seqr)
    u_bulk_seq.get_response();
  endtask: body

endclass: axi_cfg_usb_bulk_virtual_sequence

 

到目前为止,我们了解为什么以及如何使用虚拟序列。在使用虚拟序列和虚拟序列器时,我们还应该记住一些事情,以节省大量的调试时间。

1. 在配置序列中的变量(使用虚拟序列执行)时,我们必须使用通过虚拟序列的路径。在上面的示例中,使用非虚拟序列器路径在较低级别的序列中设置变量将不起作用。

uvm_config_db#(int unsigned)::set(this,“env.usb_host_agent_obj.sequencer.u_bulk_sequence”,“sequence_length”,10);

即使u_bulk_sequence在 usb_host_agent_obj.sequencer 上运行,这也不起作用,因为此序列是由虚拟序列创建的,因此分层路径应来自虚拟序列,但不使用非虚拟序列器。因此,设置变量的正确方法是使用虚拟序列路径。

uvm_config_db#(int unsigned)::set(this,“env.sys_vir_seqr.axi_cfg_usb_bulk_virtual_sequence.u_bulk_sequence”,“sequence_length”,10);

对于工厂覆盖也是如此。例如,由于上述原因相同,下面的工厂覆盖将不起作用。

set_inst_override_by_type(“env.usb_host_agent_obj.*”,usb_transfer_item::get_type(), cust_usb_transfer_item::get_type());

在上面的示例中,我们尝试使用顶级测试中的新派生类型更改基础序列项。为此,我们需要使用虚拟序列器路径。

set_inst_override_by_type(“env.sys_vir_seqr.*”,usb_transfer_item::get_type(), cust_usb_transfer_item::get_type());

经验法则是:
• 如果序列是由虚拟序列直接或间接创建的,则工厂覆盖或配置中的任何分层路径都应使用虚拟序列器的分层路径。
• 如果序列是由非虚拟序列创建的,则工厂覆盖或配置中的任何分层路径都应使用非虚拟序列器的分层路径。

2. 即使我们有虚拟序列器来控制多个序列器,在某些测试中,我们可能只需要一个序列器(例如单独的 USB 序列器)。在这种情况下,我们必须直接使用非虚拟序列器的分层路径(而不是虚拟序列器的引用路径)来配置变量或工厂覆盖。使用虚拟序列器的引用路径将不起作用,因为非虚拟序列器的层次结构不正确。

uvm_config_db#(uvm_object_wrapper)::set(this, “env.sys_vir_seqr.usb_seqr.main_phase”, “default_sequence”, usb_complex_sequence::type_id::get());

上述配置将不起作用,因为非虚拟序列器 (usb_seqr/usb_host_agent_obj.sequencer) 实际上是在代理中创建的,因此此排序器的父级是代理,而不是虚拟序列器,尽管引用在虚拟序列器中。因此,在尝试在实际序列器中设置变量时,我们不应使用虚拟序列器路径,而是必须使用通过代理的分层路径(序列器的实际父级)。

uvm_config_db#(uvm_object_wrapper)::set(this, “env.usb_host_agent_obj.sequencer.main_phase”, “default_sequence”, usb_complex_sequence::type_id::get());

3. 每当我们使用虚拟音序器并希望从虚拟音序器控制非虚拟音序器时,请确保将所有实际音序器中的default_sequence设置为 null。

uvm_config_db#(uvm_object_wrapper)::set(this, “env.usb_host_agent_obj.sequencer.main_phase”, “default_sequence”, null);
uvm_config_db#(uvm_object_wrapper)::set(this, “env.axi_master_agent_obj.sequencer.main_phase”, “default_sequence”, null);

这很重要,因为如果有任何default_sequence集,那么我们的非虚拟序列器将同时运行虚拟序列中的default_sequence和序列。要仅从虚拟序列器控制非虚拟序列器,我们需要将非虚拟序列器的default_sequence设置为 null。

我希望您发现这篇文章有助于理解虚拟序列并通过概述的指南节省调试时间。我相信在使用虚拟序列时会还有其他准则,我们学习调试复杂环境的更难的方法;请与我分享任何此类准则。

审核编辑:郭婷

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分