从零开始,搭建一个简单的UVM验证平台(三)

前言:

        这篇系列将从0开始搭建一个UVM验证平台,来帮助一些学习了SV和UVM知识,但对搭建完整的验证环境没有概念的朋友。

UVM前置基础:

1.UVM基础-factory机制、phase机制

2.UVM基础-组件(driver、monitor、agent...)

3.UVM基础-TLM通信机制(一)

4.UVM基础-TLM通信机制(二)

​​​​​​​...还在更新

从零搭建一个UVM验证平台:

从零开始,搭建一个简单的UVM验证平台(一)

从零开始,搭建一个简单的UVM验证平台(二)

从零开始,搭建一个简单的UVM验证平台(三)

从零开始,搭建一个简单的UVM验证平台(四)

...还在更新


目录

env设计

添加monitor组件

monitor定义

monitor实例化

封装成agent


        在上个上个文章中,已经搭建了一个基于transaction的driver的验证平台,涉及到的知识有interface、virtual interface、config_db机制、transaction设计等内容。下面我们要对之前的平台进行进一步完善,添加env环境、monitor以及封装agent操作。

env设计

        run_test函数只能实例化一个实例,验证平台中有各个组件:monitor、driver、scoreboard等等,使用run_test函数是无法将所有组件全部实例化的,因此我们就需要一个大的容器,把上述提到的所有组件都包起来,然后让run_test函数去实例化这个容器。在UVM中,这个容器类称为uvm_env

一个简单的uvm_env可以如下描述:

`include "uvm_macros.svh"
`include "my_driver.sv" 

import uvm_pkg::*;
class my_env extends uvm_env;
  my_driver drv;
  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);
    drv = my_driver::type_id::create("drv", this);
  endfunction

  `uvm_component_utils(my_env);

endclass

        整个env的编写还是比较简单的,首先,因为我们需要把我们写的my_driver放到my_env中,所以我们要将my_driver.sv给include到my_env.sv文件中;其次,我们令我们的my_env类继承与uvm_env;更进一步的,编写new函数,继承父类的new方法;接着再编写main_phase,在其中把my_driver实例化;最后在对my_env进行factory的注册。由此完成一个简单的env的创建。

        所有的env都应该派生自um_env,且与my_driver一样,容器类在仿真中也是一直存在的,所以使用uvm_component_utils()实现factory注册。

        这里值得一提的是实例化的代码:

drv = my_driver::type_id::create("drv", this);

        只有使用factory机制注册过的类才能使用这种方式实例化;只有使用这种方式实例化的实例才能使用factory机制中最强大的重载功能。验证平台中的组件在实例化时都应该使用:type_name::type_id::create的方式来创建。

        在drv实例化时,传递了两个参数,一个是名字drv,一个是this指针表示my_env。加入了my_env之后,整个验证平台就存在两个build_phase,一个是my_env的,一个是my_driver的。两个build_phase的执行顺序是先执行顶层的,再执行其底层的build_phase,所以是先执行my_env的build_phase再执行my_driver的build_phase,当所有的build_phase都执行完之后,才会执行后面的phase

        由于我们加入了my_env,导致整个环境的层次结构发生了变化,所以在top_tb中使用config_db机制传递virtual interface时就需要改变对应的路径。

initial begin
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top", "vif", input_if);
end
//上面的config_db修改为
initial begin
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", input_if);
end

如果在实例化drv时,传递的名字是my_drv,那么set函数的第二个参数也应该是my_drv

class my_env extends uvm_env;
    ...
    drv = my_driver::type_id::create("my_drv", this); //传递my_drv
    ...
endclass
...
initial begin
    uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.my_drv", "vif", input_if); //接收使用my_drv
end

添加monitor组件

        验证平台中监测DUT行为的组件是monitor。driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT,monitor的行为与其相对,用于收集DUT的端口数据,并将其转换成transaction交给后续的组件如reference model、scoreboard等处理。

monitor定义

        一个monitor可以如下定义

`include "uvm_macros.svh"
`include "my_transaction.sv"

import uvm_pkg::*;

class my_monitor extends uvm_monitor;
  virtual my_if vif;
  `uvm_component_utils(my_monitor)
  function new(string name = "my_monitor", 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))  //get interface
      `uvm_fatal("my_monitor", "virtual interface must be set for vif!!")
  endfunction

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

task my_monitor::main_phase(uvm_phase phase);
  my_transaction tr;
  while(1)begin
    tr = new("tr");
    collect_password_trans(tr);
  end
endtask

task my_monitor::collect_password_trans(my_transaction tr);
  bit [7:0] data_q[$];
  while(1)begin
    @(posedge vif.clk);
    if(vif.valid) break;
  end

  `uvm_info("my_monitor", "begin to collect password", UVM_LOW)

  while(vif.valid)begin
    data_q.push_back(vif.data_if);
    @(posedge vif.clk);
  end
  
  //get password from transaction 
  for (int i=0;i<8;i=1+1)begin
    tr.password = {tr.password, data_q.pop_front()};
  end
  
  `uvm_info("my_monitor", "end collect password", UVM_LOW)
  tr.my_print(); //my_print函数需要在transaction中额外定义,当收集完一笔transaction之后,通过tr.print来把收集的结果打印出来
endtask

需要注意的是;       

        ① 所有的monitor类应该派生自uvm_monitor;

        ② 在my_monitor中也得有一个virtual my_if;

        ③ uvm_monitor在整个仿真过程中是一直存在的,需要使用uvm_component_utils()注册。

        ④ monitor需要时刻收集数据,不停歇,所以在main_phase中使用while(1)循环来实现这一目的。

monitor实例化

完成monitor的定义后,再在env中对其进行实例化,重新编写my_env.sv:

`include "uvm_macros.svh"
`include "my_driver.sv" 

import uvm_pkg::*;
class my_env extends uvm_env;
  my_driver drv;
  
  my_monitor i_mon;
  my_monitor o_mon;
  
  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);
    drv = my_driver::type_id::create("drv", this);
    i_mon = my_monitor::type_id::create("i_mon", this);
    o_mon = my_monitor::type_id::create("o_mon", this);
  endfunction

  `uvm_component_utils(my_env);
endclass

        这里实例化了两个monitor。一个用于监测DUT的输入口,一个用于监测DUT的输出口。

        DUT的输出我们安排一个monitor来监测结果是必要的,输入部分driver生成transaction其实可以直接送给reference model进行计算,还有必要用monitor监测吗?建议要有,在大型项目中,driver根据某一协议发送数据,而monitor根据这个协议接收数据,如果driver和monitor由不同人员实现,那么可以大大减少其中任意一方对协议理解的错误。此外,在代码复用时,使用monitor也是很有必要的。

        然后我们还需要在顶层的top_tb中把两个定义的两个输入输出interface传递给monitor:

initial begin
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.drv", "vif", input_if);
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_mon", "vif", input_if);
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_mon", "vif", output_if);
end

现在,目前的架构图可以被如下描述: 

封装成agent

        从代码中可以看到,monitor和driver是高度相似的。其本质是因为两者处理的是同一种协议,由于二者的这种相似性,UVM中通常将二者封装在一起,成为一个agent,因此不同的agent就代表了不同的协议。

`include "uvm_macros.svh"

import uvm_pkg::*;

`include "my_driver.sv"
`include "my_monitor.sv"

class my_agent extends uvm_agent;
  my_driver drv;
  my_monitor mon; //instance driver and monitor

  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
    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);
  super.connect_phase(phase);
endfunction

        所有的agent都要派生自uvm_agent类,其实一个component类,所以使用uvm_component_utils宏进行fatory注册。

        代码中出现了一个新的变量:is_active,这是uvm_agent的一个成员变量,这是自带的枚举类型变量,仅有两个值:UVM_PASSIVE和UVM_ACTIVE,默认值为UVM_ACTIVE。

uvm_active_passive_enum is_active = UVM_ACTIVE

        这里UVM默认了is_active值为UVM_ACTIVE,其中uvm_active_passive_enum为变量类型,如果要用config_db传参,使用config_db#(uvm_active_passive_enum)进行set和get

        之所以在agent中设置一个if判断语句来决定是否实例化drv的原因是因为,我们在输入和输出的端口都有agent,输入的端口有drv生成激励和mon监测数据,但是在输出的端口可以不用drv,因此在输出的agent我们可以通过外部传参config_db令is_active为UVM_PASSIVE来让输出端口的agent不实例化drv来节省计算资源。

        按照我们的设计,重新写my_env的实例化部分,这时候由于我们在agent中已经实例化了drv和mon,所以在env中只需要实例化agent就行了:

`include "uvm_macros.svh"
import uvm_pkg::*;
`include "my_agent.sv" 

class my_env extends uvm_env;
  my_agent i_agt;
  my_agent o_agt;
  
  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;
    i_agt.is_active = UVM_PASSIVE;
  endfunction

  `uvm_component_utils(my_env);

endclass

环境变了,需要修改top_tb传入virtual interface的路径:

initial begin
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.drv", "vif", input_if);
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.i_agt.i_mon", "vif", input_if);
  uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.o_agt.o_mon", "vif", output_if);
end

加入agent后,我们的验证平台又更近了一步,思路也十分清晰了:

         在这篇文章里,我们封装了monitor来监测输入及输出的信号,还将monitor和driver封装成了一个可配置是否实例化driver的功能性agent,再将输入输出的两个agent封装到一个更大的env环境中,里面没有涉及新的知识,主要出了增添模块外,在原来的基础上修改了一些实例化的代码,以及数据传参的路径。下篇文章我们将着重讲解如何给我们的验证模块添加reference model以及scoreboard。

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 在搭建UVM验证平台之前,您需要了解以下内容:UVM语法和结构,UVM验证框架,UVM注册机制,UVM构建和配置方法,UVM测试库,UVM系统级验证UVM统计测试,UVM随机测试,以及UVM文件格式。接下来,你需要使用UVM API来构建一个基本验证环境,然后进行测试和验证结果的记录和分析。最后,你需要根据实际需要对验证平台进行调整和优化,以满足你的验证要求。 ### 回答2: 搭建一个UVM(Universal Verification Methodology)验证平台可以帮助我们有效地进行硬件验证。下面是搭建UVM验证平台的一些建议。 首先,一个UVM验证平台需要一个良好的环境设置。这包括设置合适的操作系统和相应的验证工具。验证工具可以是仿真器(如ModelSim、VCS等)和波形查看器(如DVE、Verdi等)等。同时需要安装UVM库,它包含了许多UVM验证环境所需的类和函数。 其次,我们需要定义测试环境和验证组件。测试环境是一个UVM对象,它包含了验证环境中的各个组件,如顶级模块、输入接口、输出接口等。验证组件是属于测试环境的子对象,它们有不同的功能,如驱动器(driver)、监视器(monitor)、代理(agent)等。每个验证组件负责特定的任务,并且彼此之间可以通过消息传递进行交互。 接下来,我们需要编写测试用例和配置文件。测试用例是验证平台的核心,它们用于模拟各种场景和情况,以测试我们设计的硬件是否符合预期的行为。配置文件则用于配置验证环境和测试组件的参数和属性。 然后,我们需要编写测试启动脚本。测试启动脚本用于指定我们要运行的测试用例以及其他必要的设置,如仿真时间、波形文件保存位置等。 最后,我们可以运行仿真来验证我们的设计。在仿真过程中,UVM验证平台将执行我们编写的测试用例,并通过各个验证组件和消息传递机制来收集和分析波形数据。 总之,搭建一个UVM验证平台需要正确安装验证工具和UVM库,定义测试环境和验证组件,编写测试用例和配置文件,以及编写测试启动脚本。这样,我们就可以使用UVM验证平台验证我们的硬件设计。 ### 回答3: 要搭建一个UVM验证平台,需要以下步骤: 1. 首先,确定验证平台的需求和目标,包括验证环境、测试用例、复用性需求和时间限制等。 2. 设计验证环境。选择适合验证目标的硬件平台和工具。设计验证环境包括顶层模块、测试控制器、驱动器、监控器和代理等。 3. 编写测试用例。根据验证目标和需求编写测试用例,包括功能测试、边界测试、性能测试等。测试用例应涵盖各种正常和异常情况。 4. 实现UVM验证组件。根据验证环境设计和测试用例编写UVM验证组件,包括顶层模块的连接、各个组件的功能实现和接口设置等。 5. 集成和运行验证环境。将验证组件连接到验证环境中,并进行集成测试。确保各个组件的正确互连和功能正常。 6. 运行测试用例。在验证平台上运行测试用例,并收集和分析测试结果。根据测试结果进行调试和修复。 7. 持续改进和优化。根据使用过程中发现的问题和需求,不断改进和优化验证平台,提高验证效率和质量。 8. 文档和培训。及时编写和更新验证平台的文档,包括使用手册、API文档等。培训团队成员,使其熟悉验证平台的使用和开发流程。 总之,搭建一个UVM验证平台需要确定需求、设计验证环境、编写测试用例、实现验证组件、集成和运行验证环境,持续改进和优化,并进行文档和培训。这样可以提高验证效率和质量,有效降低验证成本。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不吃葱的酸菜鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值