【UVM实战练习项目】2、UVM验证环境基本框架搭建(实例一)(纯软件环境,方便日后测试使用)

本文详细介绍了如何使用UVM搭建验证环境,包括数据建模、sequence创建、driver连接、agent集成、env构建以及测试用例的编写。重点讨论了sequence_item、sequence、driver、sequencer、agent和env的交互,并提供了Makefile的编译步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本节基于DUT完成UVM验证环境的基本框架搭建,实现对UVM理论知识点进行巩固练习,具体内容包括:如何创建激励、如何建立sequencer、如何连接sequencer和driver,如何集成agent、如何构建env等。

正式开始之前让我们再来回顾下搭建验证环境的过程:首先进行数据建模sequence_item(transaction),建好之后再将其放置到sequence,之后通过agent将sequence嵌入到sequencer中,然后将sequencer与driver连接起来,之后在env里面去集成agent,最后是编写testcase顶层。等到验证环境大体搭建完毕后,编写Makefile文件。

编写程序注意点:

  • 1、function new 这是固定的格式,没有任何返回值关键字
    • 本篇的uvm_object(如:sequence_item或者sequence)的new写了缺省的name值,比如:function new(string name = "packet_sequence");
    • 本篇的uvm_component的new没写缺省的name和parent,但是写了也不会错,比如:function new(string name, uvm_component parent);
  • 2、除了new之外的其他function和task尽量都用virtual声明;function基本都是virtual function void xxx,task基本都是virtual task xxx
  • 3、body没有super,其他的函数,尽量都继承一下,虽然对于uvm基类来说,没什么区别,养成习惯,以后对于派生类会很有用!【注意sequence的body无super继承、main_phase如果不想继承也可以】
  • 4、sformatf是系统函数,别忘了加$
  • 5、sequence的sequence参数要和sequence_item class名字一样,此处是packet;sequence_item,依次需要通过packet_sequence packet_sequencerdriver,所以它们三个的类参数都需要定义成packet
  • 6、env启动sequence时,需要定义并实例化sequence。可以在类中像agent一样在最上面定义句柄,在build_phase阶段实例化;更常见的是在main_phase阶段定义并实例化,注意,一定要放在main_phase第一句定义句柄(实例化与否无所谓,但是定义一定在开头)?(这个是为什么,有点像fork…join,我其实有点不理解)

1.1、数据建模(packet.sv)

注:sequence_item 别忘了我们的比喻,相当于手枪的“子弹”。

  • 在注册数据包后,声明随机变量、同时可添加约束,也可对进行变量权限管理。
`ifndef PACKET_SV
`define PACKET_SV

class packet extends uvm_sequence_item;

	//1.声明随机变量; $是队列
    rand bit [3:0] sa;
    rand bit [3:0] da;
    rand bit [7:0] payload[$];

	//2.注册; sequence_item 属于 object ,所以使用`uvm_object_utils注册
    `uvm_object_utils_begin(packet) //UVM的域自动化机制(field automatic),可以直接调用预定义的print/copy/compare/... 详细见《UVM实战》P71
        `uvm_field_int(sa, UVM_ALL_ON) //UVM_ALL_ON 表示 print/copy/compare/... 这些内置函数可以直接通过 packet 的实例化对象调用,本例是在driver中seq.print,将域自动化中的变量全部打印
        `uvm_field_int(da, UVM_ALL_ON) //如果域自动化中的变量不想做某个操作,比如打印,可以这样写uvm_field_int(da, UVM_ALL_ON | UVM_NOPRINT)
        `uvm_field_queue_int(payload, UVM_ALL_ON) //还需要注意的一点是,不同变量类型在使用域自动化机制注册时,对应调用的注册宏也会不同。详见《UVM实战》P69
    `uvm_object_utils_end						  //常见的整数类型 uvm_field_int; 队列整数 uvm_field_queue_int; 动态数组整数 uvm_field_array_int; 静态数组整数 uvm_field_sarray_int

	//3.添加约束
    constraint c_payload{
        payload.size() inside {[0:10]};
    }
	
	//4.new()构造函数
	function new(string name = "packet");
        super.new(name);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	//信息归类、打印信息、冗余级别
    endfunction
	
	//$sformatf("%m") 解释 
	//它是一个格式化字符串函数,其中 %m 表示当前文件名和行号。
	//因此,这段代码将会输出一个形如 "TRACE [文件名:行号]" 格式的信息。
	
	//UVM_HIGH是信息的冗余度等级,主要用来过滤信息。详细介绍见《UVM实战》P76 UVM_NONE < UVM_LOW < UVM_MIDDLE < UVM_HIGH < UVM_FULL < UVM_DEBUG
	//一般会在程序中每个需要打印的语句中都加入冗余度等级,Makefile中也会设置一个全局的冗余度等级
	//当程序中的冗余度等级小于等于全局冗余度时,比如全局冗余度为UVM_MIDDLE,那么程序中的打印冗余度为UVM_NONE、UVM_LOW、UVM_MIDDLE的信息都可以显示,其他的会被过滤。


endclass


`endif

1.2、生产数据(packet_sequence.sv)

注:sequence 别忘了我们的比喻,相当于手枪的“弹夹”。

  • 首先将上一步创建的数据包(packet)通过参数化的方式引入到sequence中,接着进行注册(注意sequence_item和sequence都是object注册),然后建立构造函数new,编写任务body(),设置发包数目,用以生产数据。【生产数据可以调用uvm_do()系列宏,也可以通过start_item和finish_item来实现(其实uvm_do本质上就是调用start_item和finish_item,建议直接用uvm_do即可)】
`ifndef PACKET_SEQUENCE_SV
`define PACKET_SEQUENCE_SV

class packet_sequence extends uvm_sequence#(packet); //1.通过参数化继承的方式把packet放进来

    `uvm_object_utils(packet_sequence) //2.注册packet_sequence类,注意是object类型注册

    function new(string name = "packet_sequence"); //3.new()构造函数,注意new函数没有返回值void,name的缺省值可以写,也可以不写
        super.new(name);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	
    endfunction

    virtual task body(); //4. 编写body函数,注意body不需要super继承
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	

        repeat(10) begin //5. 定义发包数目,调用uvm_do发送packet类型的req
            `uvm_do(req); //req是packet_sequence父类uvm_sequence中定义并例化好的transaction对象(uvm中叫sequence_item),类型是我们上面传进去的packet类型
        end
        
    endtask

endclass
    


`endif


//上面直接调用uvm_do的方式是显式启动sequence,还需要在顶层模块,如env/testcase的main_phase中,调用start()函数启动sequence,示例如下:(本例子主要使用的就是这种方式)
/*
//env启动代码如下:
task my_env::main_phase(uvm_phase phase);
   my_sequence seq = my_sequence::type_id::create("seq");
   phase.raise_objection(this);
   seq.start(i_agt.sqr); 
   phase.drop_objection(this);
endtask
*/


//除了显式启动,还有隐式启动的方式。是在sequence中除了调用uvm_do,还加上starting_phase,一个示例的body函数如下。启动是在顶层模块,如env的build_phase中,使用config_db调用default_sequence隐式启动sequence
/*
    //sequence 的body函数如下
    virtual task body(); //4. 编写body函数,注意body不需要super继承
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	
        
        if(starting_phase != null)    //在env中使用default_sequence隐式启动seq时,采用starting_phase变量
            starting_phase.raise_objection(this,"starting"); //挂起/落下objection,下文采用显式start调用,使用了phase变量

        repeat(10) begin //5. 定义发包数目,调用uvm_do发送packet类型的req
            `uvm_do(req); //req是packet_sequence父类uvm_sequence中定义并例化好的transaction对象(uvm中叫sequence_item),类型是我们上面传进去的packet类型
        end

        if(starting_phase != null)
            starting_phase.drop_objection(this,"done"); //落下objection
        
    endtask

    //env启动代码如下
    uvm_config_db#(uvm_object_wrapper)::set(this, "i_agt.sqr.main_phase", "default_sequence", my_sequence::type_id::get());
*/

知识点:

  • 1、sequence使用参数化继承时,可以直接使用基类uvm_sequence中包含的req,如下图:

在这里插入图片描述

1.3、驱动数据(driver.sv)

  • 注册后,在run_phase阶段采用TLM端口seq_item_port(源码内置)接收sequencer发来的数据,并将其按照协议时序处理。
`ifndef DRIVER_SV
`define DRIVER_SV

class driver extends uvm_driver#(packet);

    `uvm_component_utils(driver) //1.注册
    
    function new(string name, uvm_component parent);  //2.new()构造函数
        super.new(name, parent);
        `uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
    endfunction
    
    virtual task main_phase(uvm_phase phase); //3.按照协议处理数据
        `uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)

        forever begin
            seq_item_port.get_next_item(req); //通过TLM端口申请数据
            //req.print();  //数据按时序协议处理(如AHB协议),这里仅仅作为示例,打印了tr
            seq_item_port.item_done(); //数据处理完毕,通知driver开始下一次数据传输
        end

    endtask

endclass


`endif

1.4、packet_sequencer.sv

`ifndef PACKET_SEQUENCER_SV
`define PACKET_SEQUENCER_SV

class packet_sequencer extends uvm_sequencer#(packet); //别忘了传入 sequence_item 类
    `uvm_component_utils(packet_sequencer)

    function new(string name, uvm_component parent);
        super.new(name, parent);
        `uvm_info("TRACE", $sformatf("%m"), UVM_HIGH)
    endfunction

endclass


`endif

1.5、代理(input_agent.sv)

  • agent组件中包含了子组件,注册后应该先声明子组件,并在build_phase阶段实例化子组件,在connect_phase阶段建立子组件之间的连接。
`ifndef INPUT_AGENT_SV
`define INPUT_AGENT_SV


//typedef uvm_sequencer#(packet) packet_sequencer; //如果不想再单独定义sequencer,可以直接这样参数化sequencer类

class input_agent extends uvm_agent;
    `uvm_component_utils(input_agent) //1.注册,声明子组件

    packet_sequencer    seqr;
    driver              drv;
    
    function new(string name, uvm_component parent); //2.new()构造函数,确定父子关系
        super.new(name, parent);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	
    endfunction
    
    virtual function void build_phase(uvm_phase phase); //3.在build_phase阶段实例化子组件
        super.build_phase(phase);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)

        seqr = packet_sequencer::type_id::create("seqr", this); //实例化子组件
        drv = driver::type_id::create("drv", this);

    endfunction
    
    virtual function void connect_phase(uvm_phase phase);  //4.在connect_phase阶段建立seqr与drv之间的连接
        super.connect_phase(phase);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	

        drv.seq_item_port.connect(seqr.seq_item_export); //建立drv与seqr之间的连接   

    endfunction

endclass


`endif

在这里插入图片描述

在这里插入图片描述

1.6、环境层(router_env.sv)

  • 在env中实例化agent子组件后,在main_phase阶段调用start()方法显式启动sequence,使用phase变量挂起和落下objection,控制发包。
`ifndef ROUTER_ENV_SV
`define ROUTER_ENV_SV


class router_env extends uvm_env;
    `uvm_component_utils(router_env)  //1.注册,声明子组件

    input_agent i_agt;
    
    function new(string name, uvm_component parent); //2.new()构造函数,确定父子关系
        super.new(name, parent);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)	
    endfunction

    virtual function void build_phase(uvm_phase phase); //3.在build_phase阶段实例化子组件
        super.build_phase(phase);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)

        i_agt = input_agent::type_id::create("i_agt", this);  //实例化子组件
        
    endfunction


    virtual task main_phase(uvm_phase phase);        
        packet_sequence seq = packet_sequence::type_id::create("seq"); //注意,seqeunce是object,但是用的是type_id注册,并且无第二个参数
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
        phase.raise_objection(this);
        seq.start(i_agt.seqr); //显式调用start,启动seq发数
        phase.drop_objection(this);

    endtask


endclass



`endif

1.7、测试用例(test_base.sv)

  • 实例化子组件env,打印拓扑结构;
`ifndef TEST_BASE_SV
`define TEST_BASE_SV

class test_base extends uvm_test;
    `uvm_component_utils(test_base) //1.注册,声明子组件

    router_env env;
    
    function new(string name, uvm_component parent); //2.new()构造函数,确定父子关系
        super.new(name, parent);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)
    endfunction

    virtual function void build_phase(uvm_phase phase); //3.在build_phase阶段实例化子组件
        super.build_phase(phase);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)

        env = router_env::type_id::create("env", this);
    endfunction

    virtual function void start_of_simulation_phase(uvm_phase phase); //4.打印拓扑结构
        super.start_of_simulation_phase(phase);
        `uvm_info("TRACE",$sformatf("%m"),UVM_HIGH)

        //uvm_top.print_topology(); //打印uvm的拓扑结构
        //factory.print(); //打印覆盖类型
    endfunction

endclass



`endif

1.8、主代码,启动UVM(test.sv)

  • 主程序代码,启动UVM平台,运行run_test()方法,在命令行中根据+UVM_TESTNAME指定的测试用例,实例化对象uvm_test_top,然后开始自动自上而下执行各个phase,先build_phase,接着顺序执行其余phase,直到所有objection落下后结束仿真。
program automatic test;
    import uvm_pkg::*;
    `include "uvm_macros.svh"

    initial begin
        run_test();
    end

endprogram

1.9、package包文件

`ifndef ROUTER_PKG_SV
`define ROUTER_PKG_SV

package router_pkg;
    import uvm_pkg::*;
    `include "uvm_macros.svh"

    `include "packet.sv"
    `include "packet_sequence.sv"
    `include "driver.sv"
    `include "packet_sequencer.sv"
    `include "input_agent.sv"
    `include "router_env.sv"
    `include "test_base.sv"

endpackage


`endif

1.10、make编译(Makefile)

TB_TOP = ./test.sv
test = test_base
VFILES  = ./router_pkg.sv
verbosity = UVM_HIGH
uvm_ver = uvm-1.1
seed = 1
defines = UVM_NO_DEPRECATED+UVM_OBJECT_MUST_HAVE_CONSTRUCTOR
SOLVER = 2

all: compile run

compile:
	vcs -full64 -sverilog -ntb_opts ${uvm_ver} -timescale=1ns/1ns -l comp.log -debug_acc+all +vcs+vcdpluson $(VFILES) ${TB_TOP} +define+${defines}

run:
	./simv -l simv.log +ntb_random_seed=${seed} +UVM_TESTNAME=${test} +ntb_solver_mode=${SOLVER} +UVM_VERBOSITY=${verbosity}

clean:
	rm -rf simv* csrc* *.tmp *.vpd *.key *.log *.h .vcs* DVE*

1.11、打印结果分析

1.11.1、phase运行先后分析

直接通过make命令编译,打印结果如下:

在这里插入图片描述

从上图可以大致看出各个phase的运行先后(不管new,new是哪里例化哪里调用!)

1.11.2、打印transaction

取消代码中driver.svreq.print();注释,其打印信息如下:

在这里插入图片描述上图可以看到在packet中(继承自sequence_item)除了我们自定义的sa/da/payload,还有一些分类的成员,如begin_timedepthparent sequence(name)parent sequence (full name)sequencer成员信息。

1.11.3、打印拓扑结构

uvm_top.print_topology();:打印拓扑结构,对应的部分如下:

在这里插入图片描述

1.11.4、打印工厂机制覆盖类型

factory.print(); :打印工厂机制覆盖类型,对应的部分如下:

在这里插入图片描述

1.12、小结

整个UVM验证环境的搭建,是先编写完成底层组件的代码,然后向上一级一级地编写上层组件代码。对于验证环境的搭建,只有自己动手做了,才能对语法有更透彻的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

ReRrain

觉得写的不错,不妨请我喝杯~

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

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

打赏作者

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

抵扣说明:

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

余额充值