白皮书《UVM实战》学习摘要(一) ---第一章~第二章

  • 第一章        与UVM的第一次接触

■ SV具有所有面向对象语言的特性:封装继承多态

■ SV还有还有独有的特性:约束(constraint)、功能覆盖率(function coverage);

■ SV提供内存管理机制,用户不用担心内存泄露的问题;

■ SV支持系统函数$system,可以直接调用外部可执行程序,就像在Linux的shell下直接调用一样;

■ 从UVM的角度来看,方法学是一个库;

■ 无论任何抽象的概念,一个程序员要使用它,唯一的方法是把其用代码实现

■ UVM中包括sequence机制factory机制callback机制寄存器模型(register model)等;

  • 第二章      一个简单的UVM验证平台
2.1  验证平台的组成

■ 验证平台要模拟DUT的各种真实使用情况,即要给DUT施加各种激励,有正常的激励,也有异常的激励,有各种模式的激励,激励的功能是有driver来实现的;

■ scoreboard:根据DUT的输出来判断DUT的行为是否与预期相符合,;

monitor:收集DUT的输出并把他们传递给scoreboard,;

■ refm:在验证平台中,实现和DUV一样的功能;

2.2  只有driver的验证平台
2.2.1  最简单的验证平台

driver是验证平台最基本的组件,是这个验证平台数据流的源泉

■ UVM是一个库,所有的东西都是用(class)来实现,driver、monitor、reference model、scoreboard等组成部分都是class);

■ 类(class)中有函数(function)、任务(task),通过这些函数和任务可以完成driver的输出激励功能,完成monitor的检测功能,完成参考模型refm的计算功能,完成scoreboard的比功能;

■ 类(class)中有成员变量,通过这些成员变量可以控制类的行为,如driver的行为等;

■ 当要实现一个功能时,首先要想到是从UVM的某个类派生出一个新的类,在这个类中实现所期望的功能;

UVM的第一条原则:验证平台中所有的组件应该派生UVM中的类

 UVM验证平台中的driver应该派生自uvm_driver;

   eg:  class PB_lbit_input_driver #(INPUT_DELAY = 1 , OUTPUT_DELAY = 1) extends uvm_dirver #(PB_lbit_input_item);

   ...

          endclass : PB_1bit_input_driver

■ 每一个派生自uvm_component或其派生类的类在其new函数中,要说明两个参数:nameparent,这是uvm_component类的一大特征;

          

  class my_driver extends uvm_driver;

    function new(string name = “my driver”,uvm_component parent = nPBl);

        super . newnameparent);

    endfunction

    extern virtual task main_phase(uvm_phase phase);

endclass

task my_dirver::main_phase(uvm phase phase);

                ...     

            endtask

■ UVM是由phase来管理验证平台的运行,这些phase统一以xxx_phase来命名,且都有一个类型uvm_phase名字phase的参数

        eg: // function declare

             extern virtual function void build_phase( uvm_phase phase);

             extern virtual function void connect_phase(uvm_phase phase);

             function void PB_1bit_input_agent::build_phase(uvm_phase phase);

                   super.build_phase(phase);         

....

             endfunction:build_phase

  

             function void PB_lbit_input_agent::connect_phase(uvm_phase phase);

                ...

             endfunction:connect_phase

■ main_phase是uvm_driver中预先定义好的一个任务,实现一个driver等于实现其main_phase;

■ `uvm_info (“my_driver”,“data is driverd”,“UVM_LOW”)中:

◆ 第一个参数是字符串,用于把打印信息归类

◆ 第二个参数也是字符串,是具体要打印的信息

◆ 第三个参数则是冗余级别

◆ 本节uvm_info宏打印结果如下:

       @ 45800000:drv[my_driver] data is drived;

◆ UVM采用树形结构,对于树中任何一个结点,都有一个与其相应的字符串类型的路径索引,路径索引可以通过get_fPBl_name函数来获取:

  

$display(the fPBl name of current component is :%s , get_fPBl_name( ) );

■ 类的定义与类的实例化区别:

    ◆ 类的定义

       class A;

        ...

       endclass

   ◆ 类的实例化:是指通过new创造出A的一个实例

      A a_inst;

      a_inst = new( );

   ◆ 类的定义就是写一纸条文,然后告知SV的仿真器,验证平台可能用到这样一个类;

      类的实例化在于通过new( )来告知SV的仿真器:请创建一个A的实例,仿真器接到new的指令后,就会在内存中划分一块空间, 在划分前,会首先检查是否已经预先定义过这个类,在已经定义过的情况下,按照定义中所指定的“条文”分配空间,并把这块空间的指针返回给a_inst,之后就可以通过a_inst来查看类中各个成员变量,调用成员函数/任务等;

    (如果不定义,直接实例化,仿真器会报错;)

■ PB_tb_top.sv的文件编写

`ifndef   PB_TB_TOP

`define   PB_TB_TOP

//参数定义及模块路径声明

module PB_tb_top( );

pb123_top duv( );

endmodule

`endif  // PB_TB_TOP

■ 要正常引用UVM中的库文件,需要在UVM平台中,导入库文件的相关的代码,这是在pb_top.svh文件`include进来的;

pb_top.svh的文件编写

             `ifdef   PB_TOP

                `define  PB_TOP

                    import uvm_pkg::*;  //通过import语句将整个uvm_pkg导入验证平台中,只有导入了这个库,编译器在编写SV文件时,才会识别其中的uvm_driver等类名;

                    `include uvm_macros.svh     //这是uvm中的一个文件,里面包含了众多的宏定义,只需要包含一次;

                    

                   `include “vip_axi_pkg.svh”

                    import vip_axi_pkg::*;

                   `include “vip_public_pkg.svh”

                    import vip_public_pkg::*;

                  

                    `include “vip_dif_pkg.svh”

                    import vip_dif_pkg::*;

                   

                    `include “aip_public_pkg.svh::*;

                     import aip_public_pkg::*;

                     // pb_define

                     // reg_model

                     //scenario

                    //reg_cfg

                           pb_base_cfg.sv

                           pb_reg_cfg.sv

                     //agent

                    //refm

                   //scoreboard

                   //coverage

                  //sva

                 //reg_seq

                //vir_seqr and vir_seq

               //env_cfg and env

       `endif   

    ■ 通过调用$finish( )函数,可结束整个仿真,这是一个Verilog中提供的函数;  

2.2.2  加入factory机制

■ 通过引入UVM的factory机制,可以自动创建一个类的实例,并调用其中的函数(function)和任务(task);

■ factory机制的实现被集成在一个宏中:uvm_component_utils,只要定义一个新的类class时使用这个宏,就相当于把这个类class注册到了这张表中;

//这张表是factory功能实现的基础;

       class my_driver extends uvm_driver;

            `uvm_component_utils(my_driver)

            function new(string name = “my_driver” , uvm_component parent = null) ;

               super.new(name ,parent);

                `uvm_info(“my_driver”,”new is calld” ,UVM_LOW);

             endfunction

           extern virtual task main_phase(uvm_phase phase);

      endclass

■ 在driver中加入factory机制后,还需要对top_tb做一些改动,即在initial begin ...end语句中,用run_test()语句,替换掉my_driver的实例化及main_phase的显示调用;

  // 一个run_test语句会创建一个my_driver的实例,并且会自动调用my_driver的main_phase;

old:  initial begin

           my_driver drv;

           drv = new(“drv” , null);

           drv.main_phase(null);

           $finish( );

       end

new:   initial begin

           run_test( my_driver); //传递的是一个类名字符串,UVM根据这个字符串类名创建了其所代表的类class的一个实例,并自动调用这个类中的main_phase;

       end

■ 根据类名,创建一个类的实例,这是uvm_component_utils宏所带来的效果,只有在类class定义声明了这个宏,才能使用这个功能,即这个宏起到了注册的作用;(只有经过注册的类class,才能使用这个功能)

 所有派生自uvm_component及其派生类的类,都应该使用uvm_component_utils宏注册;

■ 在UVM验证平台中,只要一个类使用uvm_component_utils注册此类被实例化了,那么这个类的main_phase就会自动被调用;(eg中mark红色的代码,就是类的注册且实例化过程

  eg: class my_driver extends uvm_driver;

            `uvm_component_utils(my_driver)

            function new(string name = my_driver , uvm_component parent = null) ;

               super.new(name ,parent);

                `uvm_info(“my_driver”,”new is calld” ,UVM_LOW);

             endfunction

           extern virtual task main_phase(uvm_phase phase);

      endclass

2.2.3  加入objection机制

   ■ UVM中通过objection机制来控制验证平台关闭

   ■ 在每个phase中,UVM会检查是否有objection被提起(raise_objection),如果有,那么等待这个objection被撤销(drop_objection)停止仿真如果没有,则马上结束当前phase

   ■ 在drop_objection语句之前,必须先调用raise_objection语句,raise_objection和drop_object总是成对出现;

eg:  task my_driver::main_phase(uvm_phase phase);

      phase.raise_objection(this);

     `uvm_info(“my_driver”, “main_phase is called” ,UVM_LOW);

      ...

    phase.drop_objection(this);

endtask

    ■ raise_objection语句必须在main_phase中第一个消耗仿真时间的语句之前,如$display语句是不消耗仿真时间的,这些语句可以放在raise_objection之前,但类似@(posedge top.clk)等语句是要消耗仿真时间//否则raise_objection语句是无法起到作用

2.2.4  加入virtual interface

■ 应该尽量杜绝在验证平台中使用绝对路径,避免绝对路径的一个方法是使用宏

    (`define TOP top_tb)

■ 避免绝对路径的另外一种方式是使用interface

    eg:  interface my_intf (input clk,input rst_n);

              logic[7:0] data;

              logic valid;

          endinterface

       

  module top_tb( );

     my_if  input_if(clk,rst_n);

     my_if  output_if(clk,rst_n);  //在module模块中,可以这样声明intf

     ...

  endmodule

     ■ 在类class中使用virtual interface进行接口的声明;

          class my_driver extends uvm_driver;

              virtual my_intf vif;

                task my_driver::main_phase(uvm_phase phase);

                phase.raise_objection(this);

                ...

                phase.drop_objection(this);

                endtask

           endclass

     

    ■ UVM通过run_test()语句实例化了一个脱离top_tb层次结构的实例,建立了一个新的层次结构;// 这个新的层次结构是什么样子?

■ 对于脱离了top_tb层次结构,同时又期望在top_tb中对其进行某些操作的实例,UVM引入了config_db机制;在config_db机制中,分给setget两步操作;

         //config_db通常都成对出现,在top_tb中通过set设置virtual interface,而在driver或monitor中通过get函数得到virtual interface;

     eg:在top_tb中执行set操作

        module top_tb;

        ...

           initial begin 

                uvm_config_db #(virtual my_if)::set(null,“uvm_test_top”,“vif”,input_if);

   // 由于top_tb不是一个类,无法使用this指针,所以设置set的第一个参数为null,第二个参数使用绝对路径uvm_test_top.xxx;

           end

        endmodule

       在my_driver中,执行get操作

         virtual function void build_phase(uvm_phase phase)

            super.build_phase(phase);//显示地调用并执行父类中的build_phase中的一些必要操作

            `uvm_info(“my_driver”,“build_phase is called”,UVM_LOW);

             if(!uvm_config_db # (virtual my_if)::get(this,“”,“vif”,vif))

              `uvm_fatal(“my_driver” , “virtual interface must be set for vif !!!”)

         endfunction

   

■ 注意一下,在systemverilog中,往top_tb中传的是一个实际的物理接口virtual interface是在program、class中定义的。

   ■ 当UVM启动之后,会自动执行build_phase,build_phasenew函数之后main_phase之前执行;

   ■ 在build_phase中主要通过config_db的set和get操作来传递一些数据,以及实例化成员变量等;

   ■  build_phase是一个函数phase,而main_phase值一个任务phase,build_phase不消耗仿真时间,build_phase总是在仿真时间($time函数打印出的时间)为0时执行;

   ■ `uvm_fatal宏只有两个参数,当打印第二个参数所示的信息后,会直接调用Verilog的finish函数结束仿真

   ■ config_db的set和get函数都有四个参数,这两个函数的第三个参数必须完全一致;

  

  具体的实现过程:

      在top_tb中通过run_test创建一个my_driver实例,实例的名字为uvm_test_top,无论传递给run_test的参数是什么,创建的实例的名字都为uvm_test_top;由于set的操作目标是my_driver,所以set函数的第二个参数是uvm_test_top;

      uvm_config_db #(virtual my_if)则是一个参数化的类,其参数就是要寄信类型

■ 通过config_db可以传递两个不同类型的数据,也可以传递相同类型的不同数据;

2.3  为验证平台加入各个组件
2.3.1  加入transaction

  ■ 在refm、monitor、scb等验证平台的其它组件之间,信息的传递是基于transaction的;

■ 一般来说,物理协议中的数据交换都是以或者为单位的,通常在一帧或者一个包中要定义好各项参数,每个包的大小不一样,很少会有协议是以bit或者byte为单位来进行数据交换的;

■ 一笔transaction就是一个

class my_transaction extends uvm_sequence_item

   ...

   `uvm_object_utils(my_transaction)

...

    endclass

■ post_randomize是SV中提供的一个函数,当某个类的实例的randomize函数被调用后,post_randomize会紧随其后无条件地被调用

■ 在transaction定义中:一是my_transaction的基类uvm_sequence_item,在UVM中,所有的transaction都要从uvm_sequence_item派生,只有从uvm_sequence_item派生的transaction才能使用UVM中强大的sequence机制;

  二是,在这里 没有使用uvm_component_utils宏来实现factory机制,二是使用了uvm_object_utils;

■ my_transaction具有生命周期,它在仿真的某一时间产生,经过driver驱动,再经过refm处理,最终由scb比较完成后,其生命周期就结束了;

■ 这种派生自uvm_object或者uvm_object的派生类,uvm_sequence_item的祖先就是uvm_object,具有这种特征的类都要使用uvm_object_utils宏来实现;

eg:    

task my_driver::main_phase(uvm_phase phase);

    my_transaction tr;

   ...

  for (int i = 0; i < 2 ;i++) begin

     tr = new(“tr”);

   assert(tr.randomize( ) with {pload.size == 200;});

      driver_one_pkt(tr);

   end

       ...

  endtask

task my_driver::driver_one_pkt(my_transaction tr);

           bit [47:0]    tmp_data;

           bit [7:0]     data_q[$];

           // push dmac to data_q

           tmp_data = tr . dmac;

          for(int i = 0 ; i < 6; i++) begin

            data_q.push_back(tmp_data[7:0]);    //将tr中的数据压入队列data_q中,这个过程类似于打包成一个byte流的过程;

            tmp_data = (tmp_data >> 8);  

          end

          ...

           while(data_q.size( ) > 0)begin

           @(posedge vif .clk);

           vif.valid <= 1’b1;

           vif.data <= data_q . pop_front();//将data_q中所有的数据弹出并驱动

           end

      endtask

■ 在main_phase中定义class的method因此只用调class中的main_phase函数,即可实现class中的相应method

2.3.2  加入env

在验证平台的哪里对reference model、scoreboard进行实例化?

■ 一些不能进行实例化的地方:

 run_test,不行,它只能实例化一个实例;

◆ top_tb,run_test是在top_tb结构层次之外建立的一个新的结构层次,那在top_tb中引用就没有意义了

◆ driver,更不行了,哈哈哈,都不是一个东东。

结论:引入一个容器类,在这个类中实例化所有的driver、monitor、reference model和scoreboard等。在调用run_test时,传递的参数不是再是my_driver,而是这个容器类,即让UVM自动创建这个容器类(uvm_env)实例

class my_env entends 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::creat(“drv,this);//只有使用过factory机制注册过的类才能使用这种方式进行实例化,只有使用这种实例化的实例,才能使用后文要讲述的factory机制中最为强大的重载功能, this指针表示my_env

endfunction

‘uvm_component_utils(my_env); //注册factory机制

endclass

    ■ 验证平台中的组件在实例化时都应该使用type_name::type_id::create的方式;

        回顾下my_driver的new函数:

            function new (string name = “my_driver”, uvm_component parant = null);

               super.new(name,parent);

            endfunction

  1. new函数有两个参数,第一个参数是实例的名字,第二个则是parent
  2. 由于my_driver在uvm_env中实例化,所以my_driver的父节点(parent)就是my_env
  3. 通过parent的形式,UVM建立起了树形组织结构,在这种树形的组织结构中,由run_test创建的实例是树根(这里是my_env),并且树根的名字是固定的为uvm_test_top

        

■ 在UVM的树形结构中,build_phase的执行遵照从树根到树叶的顺序,当把整棵树的build_phase都执行外之后,再执行后面的phase;

2.3.3  加入monitor

   ■ 验证平台中实现检测DUT行为的组件是monitor;

   ■ driver负责把transaction级别的数据转变成DUT的端口级别,并驱动给DUT,monitor的行为与其相对,用于收集 DUT的端口数据,并将其转换成transaction交给后续的组件,如refm、scb等处理;

     一个monitor的定义如下:

      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);

       endfucntion

        ...

     task my_monitor::main_phase(uvm_phase phase);

         my_transaction  tr ;

         while(1) begin   //monitor需要时刻收集数据,永不停歇,因此用while(1)循环来实现这一目的

           tr = new (“tr”) ;

           collect_one_pkt(tr);

         end

     endtask

     

task my_monitor::collect_one_pkt(my_transaction tr);

...

    endtask

■ 由于transaction是由driver产生输出到DUT的端口上,所以driver可以直接将其交给后面的refm

■ 在大型项目中,driver根据某一协议发送数据,而monitor根据这种协议收集数据

■ 在env中实例化monitor之后,要在top_tb中使用config_db将input_if和output_if传递给两个monitor

    

2.3.4  封装成agent

   ■ UVM通常将monitor和driver两者封装在一起,成为一个agent,不同的agent代表了不同的协议

   ■ 把driver和monitor封装成agent后,在env中需要实例化agent,而不需要直接实例化driver和monitor了;

   ■ 只有uvm_component才能作为树的结点,像uvm_object_utils宏实现的类不能作为UVM树的结点;

      ◆在my_env的build_phase中, 创建i_agt和o_agt的实例是在build_phase中;

      ◆在agent中,创建driver和monitor的实例也是在build_phase中;

      ◆build_phase按照从树树叶执行顺序;

       UVM要求UVM树最晚build_phase时段完成;

      ◆ is_active的默认值UVM_ACTIVE

■ UVM只能在build_phase之前完后phase的build工作,在build_phase之后就不要再使用类似于my_agent::type_id::create(“i_agt”,this);来重建一个新的phase了;

◆还应当注意UVM中约定俗成的是在build_phase中完成实例化工作的,仅在build_phase中完成实例化好了;

  ■ 在JF的用例中,seq_cfg的例化是在new函数中完成的,在build_phase中完成的是configure_sequence和config_db的set操作;

     eg:在pb_test_random用例中:

     

      `ifndef PB_TEST_RANDOM

      `define PB_TEST_RANDOM

      `include “../../../testcase/pb_test_base.sv”

class pb_test_random extends pb_test_base;

  pb_1bit_input_seq_cfg     mo_alm_crm_seq_cfg;

  ...

  vip_dif_ch_seq_cfg        mo_dlpimc_in_seq_cfg[4];

// UVM Factory Register Macro

   // public member register

   `uvm_component_utils_begin(pb_test_random)

      `uvm_field_object  (mo_alm_crm_seq_cfg   , UVM_ALL_ON)

       ...

      `uvm_field_sarray_object(mo_dlpimc_in_seq_cfg , UVM_ALL_ON)

   `uvm_component_utils_end

//methods

extern function new (string name = “pb_test_random” , uvm_component parent = null);

extern virtual function void build_phase (uvm_phase phase);

endclass:pb_test_random

      function pb_test_random::new(string name = “pb_test_random” , uvm_component parent = null);

      super.new(name.parent);

      mo_alm_crm_seq_cfg=pb_1bit_input_seq_cfg::type id :create($sformatf(“mo_alm_crm_seq_cfg”));

     foreach(mao_dlpimc_in_seq_cfg[i])

     begin mao_dlpimc_in_seq_cfg[i]=vip_dif_ch_seq_cfg::type_id::create($sformatf(“mao_dlpimc_in_seq_cfg[%0d]”,i));

     end

   endfunction:new}}}}

   function void pb_test_random::build_phase(uvm_phase phase);

          super.build_phase(phase);

      mo_env_cfg.mo_reg_cfg.gen_reg_cfg(mo_scenario);

      mo_env_cfg.mo_reg_cfg.gen_base_cfg(mo_scenario);

      mo_env_cfg.configure_env(mo_scenario,mo_env_cfg.mo_reg_cfg);

     //seq_cfg

     mo_alm_crm_seq_cfg.configure_sequence(mui_send_100us_time*73728/10000,1,10000,50000);

uvm_config_db#(pb_1bit_input_seq_cfg)::set(this,“mo_env.mo_alm_crm_agent.mo_seqr”,“mo_seq_cfg”,mo_alm_crm_seq_cfg);

...

foreach(mao_dlpimc_in_seq_cfg[i])

begin

     mao_dlpimc_in_seq_cfg[i].configure_sequence(mui_send_100us_time*64,

                                                73728/64,

                                                {VIP_DIF_FDDLTE},

                                                {0},

                                                7372800,

                                                1);

       uvm_config_db#(vip_dif_ch_seq_cfg)::set(this, $sfortmaf(“mo_env.mao_dlpimc_in_agent[%0d].mo_seqr”,i),“mo_seq_cfg”,mao_dlpimc_in_seq_cfg[i]);

  end

  // set default sequence

     uvm_config_db # (uvm_object_wrapper)::set(this , “mo_env.mo_vir_seqr.main_phase”,“default_sequence”,ul_ch_vir_seq::type_id::get( ));

   //set scenario,used for vir_seq

     uvm_config_db #(pb_scenario)::set(this , “*”,“mo_scenario”,mo_scenario);

      agent_cfg;

      scoreboard_cfg;

endfunction:build_phase

`endif

 super.phase的内容://详见5.1.5章节

     ◆ build_phase,自动获取通过config_db::set 设置的参数。如果要关闭此功能,可以不用super.build_phase;

     ◆ 扩展自uvm_component的类,除了build_phase 外,其他phase几乎没做任何相关的事,其他phase完全可以不必加上super.xxx_phase 语句;

◆如果是扩展自用户自定义的类,如test_base类,且在其某个phase,如connect_phase中定义了一些重要内容,那么在具体测试用例的connect_phase中就不应该省略super.connect_phase;

2.3.5  加入reference model

■ 在UVM中,通常使用TLM(Transaction Level Modeling)实现component之间transaction级别的通信

■ 要实现通信,需要考虑:第一,数据是如何发送的?第二,数据是如何接收的?

   在UVM的transaction级别的通信中,数据的发送方式有多种方式,其中一种是使用uvm_analysis_port

class my_model extends uvm_component;

uvm_blocking_get_port  #(my_transaction)  port;   //获取my_transaction数据

uvm_analysis_port  #(my_transaction)   ap;    //发送ap数据

    ...

endclass

    ◆ uvm_analysis_port是一个参数化的类,其参数就是这个analysis_port需要传递的数据的类型,在本节中是my_transaction;

    ◆ 发送ap数据是这样做的:

//定义一个参数化的类,并声明ap口

uvm_analysis_port #(my_transaction) ap;

//声明后需要实例化

ap = new(ap,this);

//收集完一个transaction之后,将其写入ap

ap.wirte(tr);  // write是uvm_analysis_port的一个内建函数;

uvm_blocking_get_port是一种transaction级别数据的接收方式,也是一个参数化的类,其参数是要在其中传递的transaction的类型;

◆ 接收数据是这样做的:

uvm_blocking_get_port #(my_transaction),port;

port = new(“port”,this);

port.get(tr);  //得到从i_agt的monitor中发出的transaction;

之后,在env的connect_phase中将fifo分别与my_monitor中的analysis_port和my_model中的blocking_get_port相连即可。

■ connect_phase在build_phase执行完之后马上执行,与build_phase不同,执行顺序是从树叶到树根---先执行driver和monitor的connect_phase,再执行agent的connect_phase,最后执行env的connect_phase;  //当UVM启动之后,会自动执行build_phase,build_phasenew函数之后main_phase之前执行;

■ analysis_port是非阻塞性质的,ap.write函数调用完成后,马上返回,不会等待数据被接收;

2.3.6  加入scoreboard

    ■ 由于DUT处理数据需要延时,而reference model是基于高级语言的处理,一般不需要延时;

■ my_scoreboard要比较的数据一来自于reference model,另外一个来自于o_agt的monitor,前者通过exp_port获取,后者通过act_port来获取;

2.3.7  加入field_automatic机制

■ 对于不同的transaction来说,my_print()函数、my_copy( )函数、my_compare()函数都是需要逐字段地对transaction进行某些操作;

■ 通过UVM中的field_automatic机制,可以自动实现以上三个函数;

class my_transaction  extends uvm_sequence_item;

    rand bit [47:0]  dmac;

    rand bit [47:0]  smac;

    rand bit [15:0]  ether_type;

    rand byte    pload[ ];

    rand bit[31:0]  crc;

      ...

   `uvm_object_utils_begin(my_transaction)

     `uvm_field_int(dmac,UVM_ALL_ON)

     `uvm_field_int(smac, UVM_ALL_ON)

      ...

    `uvm_object_utils_end

      ...

  endclass

 ■ 使用uvm_object_utils_begin和uvm_object_utils_end来实现my_transaction(类名的factory注册,在这两个宏中间,使用uvm_field宏注册所有字段声明的成员变量);

     ◆ 当使用上述宏注册之后,可以直接调用copy、compare、print函数,而无需自己定义,极大地地简化了验证平台的搭建,提高了效率;  

2.4  UVM的终极大作:sequence
2.4.1  在验证平台中加入sequencer

     ■ sequence机制用于产生激励,是UVM最重要的机制之一;

     ■ driver只负责驱动transaction,而不负责产生transaction;

     ■ sequence机制有两大组成部分,一是sequence,二是sequencer;

     ■ sequencer产生transaction,而driver负责接收transaction;

     class my_sequencer extends uvm_sequencer #(my_transaction);   //是一个参数化的类

class my_agent  extends uvm_agent;

    my_sequencer   sqr;

    my_driver      drv;

    my_monitor    mon;

`uvm_analysis_port  #(my_transaction)  ap;

...

endclass

...

2.4.2  sequence机制

■ sequence不属于验证平台的任何一部分,在sequencer的帮助下,sequence产生出的transaction才能最终送给driver;同样,sequencer只有在sequence出现的情况下才能体现其价值;

■ sequence就像一个弹夹,里面的子弹是transaction,而sequencer是一把枪;

■ sequencer是一个uvm_component,而sequence是一个uvm_object,一个sequence应该使用uvm_object_utils宏注册到factory中;

■ 与my_transaction一样,sequence也有其声明周期,其生命周期比my_transaction要更长一些,其内的transaction全部发送完毕,其声明周期也就结束了;

class my_sequence extends uvm_sequence  #(my_transaction) ;

   my_transaction  m_trans;     //实例

        function new(string name = “my_sequence”);

        super.new(name);

   endfunction   

   virtual task body( );     //每一个sequence都有一个body任务,当一个sequence启动之后,会自动执行body中的代码;

        repeat (10) begin

        `uvm_do(m_trans

        end

        #1000;

    endtask

   `uvm_object_utils(my_sequence)

endclass

■  uvm_do宏的作用:

  • 创建一个my_transaction的实例m_trans;
  • 将其随机化;  
  • 最终将其送给sequencer;

     ■ 如果不使用uvm_do宏,也可以直接使用start_itemfinish_item的方式产生transaction,6.3.4节讲述;

     ■ 一个sequence在向sequencer发送transaction前,需要向sequencer发送一个请求,sequencer把这个请求放在一个仲裁队列中;作为sequencer,它需做两件事情:第一,检测仲裁队列里是否有某个sequence发送transaction的请求;第二,检测driver是否申请transaction

  1. 如果仲裁队列例有发送请求,但是driver没有申请transaction,那么sequence将会一直处于等待driver的状态,直到driver申请新的transaction。此时,sequencer同意sequence的发送请求,sequence在得到sequencer的批准后,产生出一个transaction并交给sequencer,后者把这个transaction交给driver;
  2. 如果仲裁队列中没有发送请求,但是driver向sequencer申请新的transaction,那么sequence将会处于等待sequence的状态,一直到有sequence递交发送请求,sequencer马上同意这个请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction;
  3. 如果仲裁队列中有发送请求,同时driver也在向sequencer申请新的transaction,那么将会同意发送请求,sequence产生transaction并交给sequencer,最终driver获得这个transaction;

   ■ 在uvm_driver中有成员变量seq_item_port,在uvm_sequencer中有成员变量seq_item_export,在agent中,使用connect函数把两者联系在一起:

             

      function  void my_agent::connect_phase(uvm_phase phase);

              super.connect_phase(phase);

              if(is_active == UVM_ACTIVE)begin

                  drv.seq_item_port.connect(sqr.seq_item_export); //当两者连接好之后,就可以在driver中通过get_next_item任务向sequencer申请新的transaction;

              end

              ap = mon .ap;   //monitor的analysis_port与agent的analysis_port相连

       endfunction

eg: task my_driver::main_phase(uvm_phase phase);

     vif.data <= 8`b0;

     vif.valid <= 1`b0;

     while(!vif.rst_n)

          @(posedge vif.clk);

    while(1) begin

    seq_item_port.get_next_item(req); //通过get_next_item任务来得到一个新的req

    driver_one_pkt(req); //驱动这个req

    seq_item_port.item_done( );  //驱动完成,调用item_done告知sequencer

    end

endtask

◆ 如上代码最显著的特征是使用了while(1)循环,因为driver只负责驱动transaction,而不负责产生,只要有transaction就驱动,所有必须做成一个无线循环的形式;

■ 在sequence中,向sequencer发送transaction的是uvm_do宏。uvm_do宏产生了一个transaction并交给sequencer,driver取走这个transaction后,uvm_do并不会立刻返回执行下一次uvm_do宏,直到driver返回item_done信号。此时uvm_do宏才算是执行完毕,返回并开始执行下一个uvm_do,并产生新的transaction。

■sequence如何向sequencer中送出transaction?只需要在某个component(如my_sequencer、my_env)的main_phase中启动这个sequence即可启动sequence向sequencer中送出transaction。

eg1:在env中启动sequence

task my_env::main_phase(uvm_phase phase);

my_sequence seq;      //创建一个my_sequence的实例seq

phase.raise_objection(this);   

seq = my_sequence::type_id::create(“seq”);

seq.start(i_agt.sqr);    //调用start任务,参数是一个sequencer指针,指明sequence将产生的transaction交给哪个sequencer

phase.drop_objection(this);

endtask

       在UVM中, objection一般伴随着sequence,通常只在sequence出现的地方才提起撤销objection

eg2:在sequencer中启动sequence

   task my_sequencer::main_phase(uvm_phase phase);

       my_sequence seq ;

       phase.raise_objection(this);

   seq = my_sequence::type_id::create(“seq”);

       seq.start (this);  //参数变成this

       phase.drop_objection(this);

   endtask

■ driver中申请:seq_item_port.get_next_item(req),除get_next_item之外,还可以选择使用try_next_item;  

    ◆ get_next_item是阻塞的,它会一直等到新的transaction才会返回;

    ◆try_next_item则是非阻塞的,它尝试着询问sequencer是否有新的transaction,如果有,则得到此transaction,否则就直接返回;

        ◆ try_next_item的行为更接近真实driver的行为:当有数据时,就驱动数据,否则总线将一直处于空闲状态;

2.4.3  default_sequence的使用

■ 在实际应用中,更多使用default_sequence方式启动sequence

■ 使用default_sequence的方法:只需在某个component(如my_env)的build_phase中设置如下代码即可:

         eg:my_env.sv

               virtual function void build_phase(uvm_phase phase);

                     super.build_phase(phase);

                   ...

                uvm_config_db #(uvm_object_wrapper)::set(this,“i_agt.sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));

          //此代码是在my_env中,而my_env本身已经是uvm_test_top,且第一个参数被设置为了this,所以第二个参数中就不需要uvm_test_top;

            endfunction

          ◆ 这是除了在top_tb中通过config_db设置virtual interface后再一次用到config_db的功能;

          ◆ 第二个路径参数,出现了main_phase,这是UVM在设置default_sequence时的要求,必须指定是那个phase,从而使sequencer知道在那个phase启动这个sequence

          ◆ 第三个和第四个参数,以及uvm_config_db#(uvm_object)是UVM的规定;

■ 除了在my_env的build_phase中设置default_sequence外,还可以在top_tb中设置:

        eg:

               module top_tb;

                  ...

                initial begin

                    uvm_config_db # (uvm_object_wrapper) :: set (null,“uvm_test_top.i_agt.sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));

                end

                endmodule

   

■ 除了在my_env的build_phase中、top_tb中设置default_sequence外,还可以在其他的component里设置,如my_agentbuild_phase里设置:

        eg:

                function void my_agent::build_phase(uvm_phase phase);

                     super.build_phase(phase);

                     ...

                    uvm_config_db#(uvm_object_wrapper)::set(this,sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ))

                 endfunction

           ◆ 只需正确地设置set的第二个参数即可;

  ■ 无需在sequencer中手写一些get相关的代码,UVM已经做好了这些;

  ■ 在uvm_phase这个基类中,有个变量名为starting_phase,类型是uvm_phase,sequencer在启动default_sequence时,会自动做如下相关操作;

        task my_sequencer::main_phase(uvm_phase phase);

              ...

           seq.starting_phase == phase;

           seq.start(this);

             ...

        endtask

因此,可以在sequence中使用starting_phase进行提起和撤销objection

     class my_sequence extends uvm_sequence  #(my_transaction);

          my_transaction  m_trans;

           ...

          virtual  task body( );

             if (starting_phase != null)

                starting_phase.raise_objection(this);

                 repeat (10)  begin

                  `uvm_do (m_trans)

                 end

              #1000;

            if (starting_phase != null)

                 starting_phase.drop_objection(this);

            endtask

            `uvm_object_utils(my_sequence)

         endclass

   

 从而objection完全与sequence关联在一起,在其它任何地方都不必再设置objection;        

2.5  建造测试用例
2.5.1  加入base_test

        ■ 通常来说,树根是一个基于uvm_test派生的类;

        ■ 真正的测试用例都是基于base_test派生的一个

            eg:

             class base_test  extends uvm_test;

                ...

                function  void base_test::build_phase(uvm_phase phase);

                   super.build_phase(phase);

                    env = my_env::type_id::create(“env”,this);

                    uvm_config_db#(uvm_object_wrapper)::set(this,“env.i_agt.sqr.main_phase”,“default_sequence”,my_sequence::type_id::get( ));

           endfunction

              ...          

             endclass

            

           在build_phase中实例化my_env,并设置sequencer的default_sequence,这里设置了default_sequence,其它地方就不需要再设置了;

          ◆ report_phase是UVM内建的一个phase,它在main_phase结束之执行;

        

■ 通常在base_test中做如下的事情:第一,设置整个验证平台的超时退出时间;第二,通过config_db设置验证平台中某些参数的值;

2.5.2  UVM中测试用例的启动

■ 要测试一个DUT是否按照预期工作,需要对其施加不同的激励,这些激励被称为测试向量或pattern;一种激励作为一个测试用例不同的激励就是不同的测试用例

■ uvm_do_with宏,用于在随机化时提供某些字段的约束;

■ UVM提供对不加参数的run_test的支持,在这种情况下,UVM会利用UVM_TEST_NAME从命令行中寻找测试用例名字,创建它的实例并运行;(+UVM_TEST_NAME = my_case1)

            eg:top_tb.sv

                    initial begin

                         run_test( );//不加用例名等参数

                    end

   

     ■ 测试用例的启动及执行流程:

                            module top_tb

                                

                           全局的run_test

                                

                            启动验证平台

                           启动testcase

               

                           依次执行build_phase

       

              顺序执行UVM树各结点的connect_phase、main_phase等

                      所有phase执行完毕,结束仿真

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值