搭建一个简单的UVM验证平台

转载自  https://zhuanlan.zhihu.com/p/713891980

一. UVM 方法学简介

UVM(Universal Verification Methodology),又称作通用验证方法学。它起源于OVM(Open Verification Methdology),是由Cadence,Mentor和Synopsys联合推出的主流验证方法学;UVM方法学可以帮助我们搭建验证平台、编写测试用例,而它自身提供的基础类库(basic class library)和基本验证结构可以让具有不同程度编程经验的芯片验证工程师们快速构建起一个结构良好可信的验证框架。

验证方法学的发展历程

二. 基本组件编写和平台搭建

1.平台基本结构

首先,我们将一个非常简单的设计模块作为待验证的DUT,其代码如下:

module simple_design input[1:0]   selinput[7:0]   ainput[7:0]   binput[7:0]   coutput[31:0] d
);
  
   reg[31:0]  d_reg;

   always@(*) begin
     case(sel)
       0:  d_reg = a;
       1:  d_reg = b;
       2:  d_reg = c;
       3:  d_reg = (a+b+c);
     endcase
   end
   
   assign d = d_reg;

endmodule

基于该模块,可规划出验证平台如下,Driver负责将数据传输至接口,进而发送给DUT;Monitor负责从接口采集数据;Env作为更高层级的组件,可容纳Driver和Monitor,并控制Driver和Monitor相互配合(在这里使用了mailbox作为媒介,具体控制方式见组件中的描述);test_top则是整个UVM树形结构的顶层,对应我们的仿真用例。

验证平台结构图

2.m_transaction

在验证环境中,一般需要对数据流进行事务级的建模,因此我们可编写m_transaction的类,将需要发送的激励进行封装,并加以约束,代码如下:

//file_name: m_transcation.sv
class m_transcation extends uvm_object;
   rand bit[1:0]  tr_sel;
   rand bit[7:0]  tr_a;
   rand bit[7:0]  tr_b; 
   rand bit[7:0]  tr_c;
   `uvm_object_utils(m_transcation)

   constraint sel_c {
      tr_sel dist{3:/40, [0:2]:/60};
   }
endclass

3.m_ctrl_if

由于Driver和Monitor中都需要对interface中的信号进行操作,因此我们在实现编写Driver和Monitor之前,需编写interface文件,代码如下:

//file_name: m_ctrl_if.sv
interface m_ctrl_if(input logic clk, rst);
   logic[1:0]   sel;
   logic[7:0]   a;
   logic[7:0]   b;
   logic[7:0]   c;
   logic[31:0]  d;
endinterface

4.m_driver

Driver组件中,将transaction进行随机化并驱动至接口,由于DUT所需的数据并不复杂,本平台中的Driver承担了产生激励的功能,后续可适当进行优化;

除了需要发送激励,Driver还应与Monitor进行通信,告知其何时进行采集,因此我们使用了mailbox完成flag的交互(mailbox是一种在进程之间交换消息的机制,数据可以通过一个进程发送到mailbox,然后由另一个进程获取,所传输的数据可以是任何有效的SystemVerilog数据类型以及class),mailbox的主要方法如下,我们在Driver中使用了带阻塞的put方法。

mailbox主要方法

Driver的完整代码如下:

//file_name: m_driver.sv
class m_driver extends uvm_driver;
   int                flag;
   int                set_times;
   m_transcation      m_tr;
   mailbox#(int)      flag_fifo;
   virtual m_ctrl_if  m_if;
   `uvm_component_utils(m_driver)

   function new(string name, uvm_component parent);
      super.new(name, parent);
      m_tr = new();
      flag_fifo = new();
      flag = 0;
   endfunction

   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      uvm_config_db#(virtual m_ctrl_if)::get(this, "", "m_if", m_if);
      uvm_config_db#(int)::get(this, "", "set_times", set_times);
   endfunction

   function void connect_phase(uvm_phase phase);
      super.contect_phase(phase);
   endfunction

   task main_phase(uvm_phase phase);
      int times;
      super.main_phase();
      phase.raise_objection(this);
      times = set_times;
      for(int i=0; i<times; i++) begin
         #60ns;
         `uvm_info(get_type_name(),$sformatf("The times[%0d] send pkt",i),UVM_NONE)
         send_one_pkt();
         flag = flag + 1;
      end
      phase.drop_objection(this);
   endtask

   task set_flag(mailbox#(int) flag_fifo);
      this.flag_fifo = flag_fifo;
      this.flag_fifo.put(flag);
   endtask

   task send_one_pkt();
      m_tr.randomize();
      `uvm_info(get_type_name(),$sformatf("Send pkt as follow:::   \
                                           \n tr_sel   ---%0d      \
                                           \n tr_a     ---%0d      \
                                           \n tr_b     ---%0d      \
                                           \n tr_c     ---%0d",
                                           m_tr.tr_sel,
                                           m_tr.tr_a,
                                           m_tr.tr_b,
                                           m_tr.tr_c),UVM_NONE)
      m_if.sel = m_tr.tr_sel;
      m_if.a   = m_tr.tr_a;
      m_if.b   = m_tr.tr_b;
      m_if.c   = m_tr.tr_c;
   endtask

endclass

5.m_monitor

Monitor组件中,主要的task包括:从接口获取获取数据、checker数据是否正确、从mailbox信箱中获取flag(使用get方法),其代码如下:

//file_name: m_monitor.sv
class m_monitor extends uvm_monitor;
   int                flag;
   int                set_times;
   mailbox#(int)      flag_fifo;
   virtual m_ctrl_if  m_if;
   `uvm_component_utils(m_monitor)

   function new(string name, uvm_component parent);
      super.new(name, parent);
      flag_fifo = new();
      flag = 0;
   endfunction

   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      uvm_config_db#(virtual m_ctrl_if)::get(this, "", "m_if", m_if);
      uvm_config_db#(int)::get(this, "", "set_times", set_times);
   endfunction

   function void connect_phase(uvm_phase phase);
      super.contect_phase(phase);
   endfunction

   task main_phase(uvm_phase phase);
      int times;
      super.main_phase();
      phase.raise_objection(this);
      times = set_times;
      for(int i=0; i<times; i++) begin
         while(flag == i) begin
            #5ns;
         end
         `uvm_info(get_type_name(),$sformatf("The times[%0d] get pkt",i),UVM_NONE)
         get_one_pkt();
      end
      phase.drop_objection(this);
   endtask

   task get_flag(mailbox#(int) flag_fifo);
      this.flag_fifo = flag_fifo;
      this.flag_fifo.get(flag);
   endtask

   task data_checker(bit[1:0] mode, bit[7:0] data_i0, bit[7:0] data_i1, bit[7:0] data_i2, bit[31:0] data_o);
      case(mode)
      0: begin
            if(data_o == data_i0)
            `uvm_info(get_type_name(),$sformatf("The data check pass!!!"),UVM_NONE)
            else
            `uvm_error(get_type_name(),$sformatf("The data check fail!!!"))
         end
      1: begin
            if(data_o == data_i1)
            `uvm_info(get_type_name(),$sformatf("The data check pass!!!"),UVM_NONE)
            else
            `uvm_error(get_type_name(),$sformatf("The data check fail!!!"))
         end
      2: begin
            if(data_o == data_i2)
            `uvm_info(get_type_name(),$sformatf("The data check pass!!!"),UVM_NONE)
            else
            `uvm_error(get_type_name(),$sformatf("The data check fail!!!"))
         end
      3: begin
            if(data_o == (data_i0+data_i1+data_i2))
            `uvm_info(get_type_name(),$sformatf("The data check pass!!!"),UVM_NONE)
            else
            `uvm_error(get_type_name(),$sformatf("The data check fail!!!"))
         end
      endcase
   endtask

   task get_one_pkt();
      bit[1:0]   sel;
      bit[7:0]   a;
      bit[7:0]   b;
      bit[7:0]   c;
      bit[31:0]  d;

      sel = m_if.sel;
      a   = m_if.a;
      b   = m_if.b;
      c   = m_if.c;
      d   = m_if.d;

      `uvm_info(get_type_name(),$sformatf("Get pkt as follow:::   \
                                           \n tr_sel   ---%0d     \
                                           \n tr_a     ---%0d     \
                                           \n tr_b     ---%0d     \
                                           \n tr_c     ---%0d     \
                                           \n tr_d     ---%0d",
                                           sel,
                                           a,
                                           b,
                                           c,
                                           d),UVM_NONE)
      data_checker(sel, a, b, c, d);
      $display();
   endtask

endclass

6.m_env

Env组件中,创建了top_flag_fifo,即Driver和Monitor之间的mailbox,将其传入两个底层组件中,调用Driver的set_flag和Monitor的get_flag后可实现正常通信,Env代码如下:

//file_name: m_env.sv
class m_env extends uvm_env;
   m_driver           m_drv;
   m_monitor          m_mon;
   mailbox#(int)      top_flag_fifo;
   `uvm_component_utils(m_env)

   function new(string name, uvm_component parent);
      super.new(name, parent);
      top_flag_fifo = new();
   endfunction

   function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      m_drv = m_driver::type_id::create("m_drv",this);
      m_mon = m_monitor::type_id::create("m_mon",this);
   endfunction

   function void connect_phase(uvm_phase phase);
      super.contect_phase(phase);
   endfunction

   task main_phase(uvm_phase phase);
      super.main_phase();
      phase.raise_objection(this);
      repeat(3000) begin
         fork
            m_drv.set_flag(top_flag_fifo);
            m_mon.get_flag(top_flag_fifo);
         join
         1ns;
      end
      phase.drop_objection(this);
   endtask

endclass

7.m_base_test

在编写base_test之前,可以编写一个test_pkg文件,将编译所需的uvm_pkg以及sv文件进行打包,后续在base_test中include即可,具体如下:

//file_name: m_test_pkg.sv
`include "uvm_macros.svh"
import uvm_pkg::*;

`include "m_transcation.sv"
`include "m_driver.sv"
`include "m_monitor.sv"
`include "m_env.sv"

base_test中通过config_db机制向下set了virtual interface以及发送激励的次数,底层组件利用config_db::get后可进行相应操作,具体的代码如下:

//file_name: m_base_test.sv
`include "m_test_pkg.sv"

class m_base_test extends uvm_test;
   int     set_tims;
   m_env   top_env;
   `uvm_component_utils(m_base_test)

   function new(string name, uvm_component parent);
      super.new(name, parent);
   endfunction

   virtual function void build_phase(uvm_phase phase);
      super.build_phase(phase);
      set_times = $urandom_range(10,35);
      uvm_config_db#(int)::set(this, "top_env.m_drv", "set_times", set_times);
      uvm_config_db#(int)::set(this, "top_env.m_mon", "set_times", set_times);
      top_env = m_env::type_id::create("top_env",this);
   endfunction

   virtual function void connect_phase(uvm_phase phase);
      super.contect_phase(phase);
   endfunction

   virtual task pre_main_phase(uvm_phase phase);
       super.pre_main_phase();
       `uvm_info(get_type_name(),$sformatf("--------------------"),UVM_NONE)
       `uvm_info(get_type_name(),$sformatf("-----TEST START-----"),UVM_NONE)
       `uvm_info(get_type_name(),$sformatf("The set_times is %0d", set_times),UVM_NONE)
       $display();
   endtask

   virtual task main_phase(uvm_phase phase);
      super.main_phase();
   endtask

   virtual task post_main_phase(uvm_phase phase);
       super.post_main_phase();
       `uvm_info(get_type_name(),$sformatf("-----TEST END-------"),UVM_NONE)
       `uvm_info(get_type_name(),$sformatf("--------------------"),UVM_NONE)
       $display();
   endtask

endclass

8.tb_top

在上述各文件都编写完成之后,我们在tb_top中实例化接口,完成验证平台与DUT的连接,通过run_test全局函数指定用例,UVM会自动实例化case,名字固定为uvm_test_top,并自动执行case中的build phase,自顶向下,从而形成完整的UVM树形结构,之后依次执行各个phase,完成仿真。这里需注意的是:在tb_top的initial块中对于run_test的调用应在uvm config db传递接口之后,这样才能保证接口信号在build_phase之前已经传递;

此外,还可在其他的initial块中加入dump波形等$fsdb函数,具体代码如下:

//file_name: tb_top.sv
`timescale 1ns/1ns
`include "m_ctrl_if.sv"
`include "m_base_test.sv"

module simple_design input[1:0]   selinput[7:0]   ainput[7:0]   binput[7:0]   coutput[31:0] d
);
  
   reg[31:0]  d_reg;

   always@(*) begin
     case(sel)
       0:  d_reg = a;
       1:  d_reg = b;
       2:  d_reg = c;
       3:  d_reg = (a+b+c);
     endcase
   end
   
   assign d = d_reg;

endmodule


module tb_top;
   reg clk;
   reg rst;
   m_ctrl_if  m_if(clk,rst);

   simple_design  u_simple_design(
      .sel   (m_if.sel);
      .a     (m_if.a);
      .b     (m_if.b);
      .c     (m_if.c);
      .d     (m_if.d)
   );
   
   initial begin
      rst = 1; clk = 0;
      #10ns;
      rst = 0;
   end

   always #10 clk = ~clk;

   initial begin
      uvm_config_db#(virtual m_ctrl_if)::set(uvm_root::get(), "*", "m_if", m_if);
      run_test("m_base_test");
   end

   initial begin
      $fsdbDumpfile("simple_platform0.fsdb");
      $fsdbDumpvars(0, tb_top);
      $fsdbDumpSVA;
      $fsdbDumpMDA;
   end

endmodule

至此,一个简易的UVM验证平台已搭建完成,仿真时通过Makefile脚本控制即可,Makefile写法不是我们本文的重点,为防止文章篇幅过长,在这里暂不给出,若有需要的同学可联系我!

posted @ 2025-01-02 10:29  大地丶  阅读(1012)  评论(0)    收藏  举报