从零开始,搭建一个简单的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。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不吃葱的酸菜鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值