UVMDemo学习笔记
UVM实战:一个简单的UVM验证平台
[TOC]
跑通Demo 使用2.5.2
的例子作为文件。需要对应自己的环境进行修改,修改如下。
1、setup.vcs修改
对应修改到bash shell下,参照~/.bashrc修改即可,很简单。
对应修改VCS_HOME等自己实际的环境变量
bash和csh都是shell脚本语言。
Linux终端的核心组件是Shell,它是一个解释器,负责解析用户输入的命令并将其发送给操作系统执行。Linux中有多种Shell可用,其中最常见的是Bash(Bourne Again Shell),它是许多Linux发行版的默认Shell。其他常见的Shell包括csh。
bash 的 shell 默认用户下面的配置文件是:.bashrc
, 用户登陆之后,默认执行该配置文件内容,让环境变量生效;csh 的 shell 默认用户下面的配置文件是:.cshrc
, 用户登陆之后,默认执行该配置文件内容。很多脚本文件在第一行都需要指定所使用的shell,如:#!/bin/bash
为什么有的terminal使用bash,而有的终端使用csh?
Linux发行决定。也可以通过指定更换shell。
2、修改运行脚本
加入-full64选项
好奇:执行脚本指定csh,但是也可以执行,是以为语法正确的原因吗?配置环境的语法有问题所以不能执行而已。
还要注意,使用虚拟机时不要把代码放在共享文件夹内编译
修改后,运行一下demo:
执行source setup.vcs
使环境变量生效。进入代码目录,执行run_tc my_case0
,编译运行demo
Demo学习 根据几张图,对应例程代码来学习理解这个验证平台。
典型UVM验证平台框图(P8)
UVM树状图/路径(P55,66,86)
UVM常用类的继承关系(P57)
UVM验证平台执行流程(P55)
学习整理:
验证平台各模块基本功能(P7)
代码与框图的对应(不涉及寄存器模型)
代码注释讲解(重点)
第一张图:典型UVM验证平台框架
先介绍平台中每一个模块的功能作用(P7)
DUT是我们的待测设计模块,reference model就是参考模型,与DUT完成相同的过程。
sequence负责产生激励。sequencer则是负责将sequence生产的激励发送到driver。
driver将sequencer发送过来的数据,按照数据协议驱动到DUT上。
monitor则是采集激励和响应数据。
scoreboard是计分板,自动对比DUT输出和参考模型输出。
env:提供一个容器,
当然除了这些模块,还会涉及到一些其他的。如虚接口(virtual interface)、agent等以及一些机制。
第二张图:UVM树状图/路径 这张图在P55,66也都出现过,这张图信息最完整,以此图UVM树 进行学习理解。
这个便于理解代码的层次结构,也和执行顺序是相关的。
矩形框内第一行是模块名,实例名,也是每个模块路径。第二行括号内是UVM每个模块的类。矩形框旁边的new是例化模块时指定的名字。也是通过树的结点去访问UVM树中的每个模块。
UVM是以树的形式组织在一起的,作为一棵树来说,UVM中真正的树根是一个称为uvm_top 的东西,在测试用例里实例化env,在env里实例化scoreboard、reference model、agent,在agent里面实例化sequencer、driver和monitor。scoreboard、reference model、sequencer、driver和monitor都是树的叶子。
uvm_top:
uvm_top是uvm_root类的唯一实例,由UVM创建和管理。所在的域是uvm_pkg。 uvm_top是所有test组件的顶层。所有验证环境中的组件在创建时都需要指明它的父一级 (Parent)。 如果某些组件在创建时指定父一级的参数为“null”,那么它将直接隶属于uvm_top。 在实例化变量时指定parent变量,可以形成一个树状结构从而起到建立层次的作用 。 uvm_top提供一系列的方法来控制仿真,例如phase机制、objection防止仿真退出机制等。
uvm_test:
test类是用户自定义类的顶层结构。所有的test类都应该继承于uvm_test ,否则,uvm_top将不识别。 test的目标包括:
提供不同的配置,包括环境结构配置、测试模式配置等,然后再创建验证环境。
例化测试序列,并且挂载(attach)到目标sequencer,使其命令driver发送激励。
UVM树对应的框图上,也是自顶向下的。
树的作用就是便于组织验证。
第三张图:UVM常用类的继承关系
这是描述UVM类的,对应的宏也要学习一下
可以在第二张图上标注出每个模块的父类
如图所示,验证平台的所有类都继承于uvm_object
和uvm_commponent
,而uvm_commponent
又是继承于uvm_object
的。
派生与uvm_object类的是transaction、sequence这些非模块的东西。
派生于uvm_component类的才可以是UVM树的节点。
白皮书上分子、血液、生命的例子比较好。
对应下这个平台各个成员的父类,如下图所示。
uvm_component这边: reference直接派生与uvm_component,sequencer派生于uvm_component的子类uvm_sequencer。其他的都直接派生与uvm_component的直接子类。
uvm_object这边: 其实都是transaction服务的,有uvm_transaction继续向下派生。
分析代码里面的继承关系:
相关的宏:
用的再补充
第四张图:UVM验证平台执行流程 ==这里要深入分析,结合前后的知识内容。==
UVM测试用例的启动及执行流程如下图所示:
由顶层的测试模块top_tb开始,
1)top_tb:
例化接口;例化DUT;生成仿真时钟、复位信号;通过uvm_config_db将接口传递给driver和monitor
import uvm_pkg::*;
在导入uvm_pkg文件包时,会自动创建一个顶层类uvm_root所例化的对象,及uvm_top
执行run_test(): run_test是在uvm_globals.svh中定义的一个task,用于启动UVM,创建测试用例的实例并运行。也就启动了验证平台。 run_test语句会创建一个my_case0的实例,得到正确的test_name 。然后依次执行uvm_test容器中的各个component组件中的phase机制 。具体工作见:uvm中run_test
2)build_phase:
依次执行build_phase(自顶而下),形成完整的UVM树。
在phase机制和objection机制的控制下,执行phase。
phase机制:
UVM验证平台由phase机制和objection机制来控制验证平台的运行。
phase自上而下执行(时间):安装P132图,不同phase的执行顺序。
phase自上而下执行/自下而上(空间):按照UVM树的路径,每个phase,如build_phase,先执行my_case中的build_phase,其次是env中的build_phase,一层层往下执行。对于tast phase,不同component中同名phase会同时运行P136 。深度优先,一条支路走到叶子才会回头头别的P139。同级别则按字典顺序执行。
所以结合起来,phase执行顺序就是,先自顶向下执行build_phase。完成后再自顶向下执行connect_phase。
UVM的设计哲学就是在build_phase中做实例化工作。
phase执行顺序
objection机制:
只是对task phase的控制,一般在sequence里面挂起和取消。
3)执行UVM树各节点的其他phase:
自顶而下还是自下而上看上面的图
4)所有phase执行完毕,结束仿真
等待所有的phase执行结束,关闭phase控制进程。
报告总结和仿真结束。
关于Transaction transaction:事务,个人理解为激励、数据。叫事务,会不会是因为一个transaction除了data还有function。
P20页有详细介绍:
transaction是一组数据或者是一个数据包,其基类是uvm_sequence_item。
transaction有生命周期,从sequence产生到scoreboard比较后,transaction的生命周期就结束了。那需要追踪下产生一个激励需要构建了多少个transaction,transaction的传输路径是怎样的,分析从产生到结束的过程。
用到transaction的代码文件:(可以看出事务的传输流程,框图中线上都是transaction?)
==在uvm环境中,所有组件之间的通信都是通过transaction类型连通。==
分析transaction传输过程:(基于验证平台执行过程)
tr到DUT端口的转换:(driver)
将tr打包到数组中,在放入DUT的端口中。
DUT端口到tr的转换:(monitor)
将DUT的端口数据打包成tr的形式。
关于Vif dut和验证平台之间的端口使用虚接口连接(P16)
好处就是避免绝对路径。
以一个interface为例进行理解分析:
1 2 3 4 5 interface my_if(input clk, input rst_n); logic [7 :0 ] data; logic valid; endinterface
按照module进行理解的话,它只声明了接口,但是没有使用。而且,接口的参数是input clk, input rst_n
,而接口内部是两个信号。那么接口参数和接口内部的信号怎么区分理解呢?
想了想我的理解是这样:同样的用电路思维去考虑。一个接口电路要连接到两个module上,接口内部信号就是模块直接的连接信号,而输入的参数信号可以理解为接口模块外部给接口的信号。同样,接口作为module直接连接的模块,那这些模块的clk也是外部给进来的。
使用时是先定义interface接口interface my_if
,在类中使用时虚接口virtual my_if vif
自己写代码的顺序-自己搭建平台 那知道了代码的层次结构,继承关系以及执行机制和顺序。那么要是自己搭建一个UVM验证平台,设计的顺序应该是怎么样的呢?
其实还是从DUT出发,为了测试DUT,我们要如何如测试它。
首先应该想到给DUT施加激励,构建driver模块 。此时为了测试top_tb模块 也可以先建立起来。在driver给DUT施加激励时又考虑到端口路径问题,还要设计一个接口interface 。然后要考虑加入monitor和sequence,那么就要考虑组件之间信息传递,设计一个transaction模块 。然后就可以设计monitor模块 、sequencer模块 。在此基础上设计agent ,设计大的容器env模块 。最后再加上reference model模块和scoreboard模块,整个模块就成型了。为了测试,还要写测试用例my_casen ,这里面会包含sequence 。
补充一下transaction,在和DUT交互时,主要时driver和monitor。driver是将tr转换成DUT的端口级数据,而monitor则是将DUT的端口级数据转换成tr形式的。
factory机制:自动创建一个类的实例并调用其中的function和task。
不同派生类使用不同的宏去注册。
uvm_conponent使用uvm_component_utils()去注册
派生于uvm_object的使用uvm_object_utils()去注册
factory注册的组件例化时也要用以下形式,验证平台的组件都是:
drv=my_driver::type_id::create("drv",this);
==Todo:ch8==
自己从头搭,一步步来: 从零开始,搭建一个简单的UVM验证平台(一)
UVM相关宏 在UVM库里能看到
运行相关机制 ch5 已在上方整理
UVM验证平台执行流程
代码阅读+注释+思考: 带着疑问去学会看的更仔细,解决自己的疑问,这个知识点也就了解明白许多了。
按运行顺序还是构建顺序去注释。
构建吧,也就是按构建顺序的逻辑来的。执行顺序还会设计到很多的机制。
DUT: 很简单rxd接受数据,然后通过txd发送出去。还有发送使能信号和接受有效信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 module dut(clk, rst_n, rxd, rx_dv, txd, tx_en); input clk;input rst_n; input [7 :0 ] rxd; input rx_dv;output [7 :0 ] txd;output tx_en;reg [7 :0 ] txd;reg tx_en;always @(posedge clk) begin if (!rst_n) begin txd <= 8'b0 ; tx_en <= 1'b0 ; end else begin txd <= rxd; tx_en <= rx_dv; end end endmodule
Driver: driver的功能就是将sequencer传递过来的数据驱动到DUT上。与DUT使用虚接口进行连接。
拿到sequencer是数据是:seq_item_port.get_next_item(req);
驱动是通过:drive_one_pkt(req);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 `ifndef MY_DRIVER__SV `define MY_DRIVER__SV class my_driver extends uvm_driver#(my_transaction); // 继承与uvm_driver,#(my_transaction) 和verilog一样,是参数化的类/模块。这里是传入transaction类。 virtual my_if vif; `uvm_component_utils(my_driver) function new (string name = "my_driver" , 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)) `uvm_fatal("my_driver" , "virtual interface must be set for vif!!!" ) endfunction extern task main_phase(uvm_phase phase); extern task drive_one_pkt(my_transaction tr); endclass 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); drive_one_pkt(req); seq_item_port.item_done (); end endtask task my_driver::drive_one_pkt(my_transaction tr); byte unsigned data_q[]; int data_size; data_size = tr.pack_bytes (data_q) / 8 ; `uvm_info("my_driver" , "begin to drive one pkt" , UVM_LOW); repeat (3 ) @(posedge vif.clk ); for ( int i = 0 ; i < data_size; i++ ) begin @(posedge vif.clk ); vif.valid <= 1'b1 ; vif.data <= data_q[i]; end @(posedge vif.clk ); vif.valid <= 1'b0 ; `uvm_info("my_driver" , "end drive one pkt" , UVM_LOW); endtask `endif
Interface: 好处是使用信号接口不受路径变化影响。
在类中使用要是虚拟接口。
1 2 3 4 5 6 7 8 9 10 `ifndef MY_IF__SV `define MY_IF__SV interface my_if(input clk, input rst_n); logic [7 :0 ] data; logic valid; endinterface `endif
那他是如何和DUT的数据对应上的呢?看下DUT例化部分就明白了。
DUT也是用了两组接口,input一组,output一组。
那么接口信号更复杂的话,我们该如何设计接口,需要学习一下。多看几个例子应该能明白。
1 2 3 4 5 6 7 8 9 my_if input_if(clk, rst_n); my_if output_if(clk, rst_n); dut my_dut(.clk (clk), .rst_n (rst_n), .rxd (input_if.data ), .rx_dv (input_if.valid ), .txd (output_if.data ), .tx_en (output_if.valid ));
Transaction: 定义数据包内容,随机约束和相关处理方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 `ifndef MY_TRANSACTION__SV `define MY_TRANSACTION__SV 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; constraint pload_cons{ pload.size >= 46 ; pload.size <= 1500 ; } function bit [31 :0 ] calc_crc(); return 32'h0 ; endfunction function void post_randomize(); crc = calc_crc; endfunction `uvm_object_utils_begin(my_transaction) `uvm_field_int(dmac, UVM_ALL_ON) `uvm_field_int(smac, UVM_ALL_ON) `uvm_field_int(ether_type, UVM_ALL_ON) `uvm_field_array_int(pload, UVM_ALL_ON) `uvm_field_int(crc, UVM_ALL_ON) `uvm_object_utils_end function new (string name = "my_transaction" ); super .new (); endfunction endclass `endif
Monitor: monitor是负责采集DUT的输入输出,用于scoreboard的自动比较。
重点还是下面几行代码?手机DUT的数据,转换成tr,再写出去。
1 2 3 tr = new ("tr" ); collect_one_pkt(tr); ap.write (tr);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 `ifndef MY_MONITOR__SV `define MY_MONITOR__SV class my_monitor extends uvm_monitor; virtual my_if vif; uvm_analysis_port #(my_transaction) ap; `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)) `uvm_fatal("my_monitor" , "virtual interface must be set for vif!!!" ) ap = new ("ap" , this ); endfunction extern task main_phase(uvm_phase phase); extern task collect_one_pkt(my_transaction tr); endclass task my_monitor::main_phase(uvm_phase phase); my_transaction tr; while (1 ) begin tr = new ("tr" ); collect_one_pkt(tr); ap.write (tr); end endtask task my_monitor::collect_one_pkt(my_transaction tr); byte unsigned data_q[$]; byte unsigned data_array[]; logic [7 :0 ] data; logic valid = 0 ; int data_size; while (1 ) begin @(posedge vif.clk ); if (vif.valid ) break ; end `uvm_info("my_monitor" , "begin to collect one pkt" , UVM_LOW); while (vif.valid ) begin data_q.push_back (vif.data ); @(posedge vif.clk ); end data_size = data_q.size (); data_array = new [data_size]; for ( int i = 0 ; i < data_size; i++ ) begin data_array[i] = data_q[i]; end tr.pload = new [data_size - 18 ]; data_size = tr.unpack_bytes (data_array) / 8 ; `uvm_info("my_monitor" , "end collect one pkt" , UVM_LOW); endtask `endif
Sqeuencer sequencer是负责发送sequence的,代码比较简单。
重点在于sequence的设计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 `ifndef MY_SEQUENCER__SV `define MY_SEQUENCER__SV class my_sequencer extends uvm_sequencer #(my_transaction) ; function new (string name, uvm_component parent); super .new (name, parent); endfunction `uvm_component_utils(my_sequencer) endclass `endif
Agent: agent这里是将sqr、drv、mon三个组件封装到一起了。再agent里根据uvm成员变量,决定哪些组件是需要的。然后再通过connect_phase将组件连接再一起。
通过agent也可以知道,之前sequencer的sequencer是发给drv的,或者说drv从sqr处拿到tr。
mon生产的tr也是通过ap向上发送的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 `ifndef MY_AGENT__SV `define MY_AGENT__SV class my_agent extends uvm_agent ; my_sequencer sqr; my_driver drv; my_monitor mon; uvm_analysis_port #(my_transaction) ap; 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 sqr = my_sequencer::type_id::create("sqr" , this ); 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); if (is_active == UVM_ACTIVE) begin drv.seq_item_port .connect (sqr.seq_item_export ); end ap = mon.ap ; endfunction `endif
Reference model: 参考模型这里还存在一些疑问:
1、高层次语言写的,时序关系需要吗?能实现到时序级别吗?
2、参考模型如何保证正确性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 `ifndef MY_MODEL__SV `define MY_MODEL__SV class my_model extends uvm_component; uvm_blocking_get_port #(my_transaction) port; uvm_analysis_port #(my_transaction) ap; extern function new (string name, uvm_component parent); extern function void build_phase(uvm_phase phase); extern virtual task main_phase(uvm_phase phase); `uvm_component_utils(my_model) endclass function my_model::new (string name, uvm_component parent); super .new (name, parent); endfunction function void my_model::build_phase(uvm_phase phase); super .build_phase (phase); port = new ("port" , this ); ap = new ("ap" , this ); endfunction task my_model::main_phase(uvm_phase phase); my_transaction tr; my_transaction new_tr; super .main_phase (phase); while (1 ) begin port.get (tr); new_tr = new ("new_tr" ); new_tr.copy (tr); `uvm_info("my_model" , "get one transaction, copy and print it:" , UVM_LOW) new_tr.print (); ap.write (new_tr); end endtask `endif
Scoreboard: scoreboard比较固定化,没有什么要特殊处理的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 `ifndef MY_SCOREBOARD__SV `define MY_SCOREBOARD__SV class my_scoreboard extends uvm_scoreboard; my_transaction expect_queue[$]; uvm_blocking_get_port #(my_transaction) exp_port; // 期望数据(来自ref model) uvm_blocking_get_port #(my_transaction) act_port; // 实际数据(来自oagt.mon) `uvm_component_utils(my_scoreboard) extern function new (string name, uvm_component parent = null ); extern virtual function void build_phase(uvm_phase phase); extern virtual task main_phase(uvm_phase phase); endclass function my_scoreboard::new (string name, uvm_component parent = null ); super .new (name, parent); endfunction function void my_scoreboard::build_phase(uvm_phase phase); super .build_phase (phase); exp_port = new ("exp_port" , this ); act_port = new ("act_port" , this ); endfunction task my_scoreboard::main_phase(uvm_phase phase); my_transaction get_expect, get_actual, tmp_tran; bit result; super .main_phase (phase); fork while (1 ) begin exp_port.get (get_expect); expect_queue.push_back (get_expect); end while (1 ) begin act_port.get (get_actual); if (expect_queue.size () > 0 ) begin tmp_tran = expect_queue.pop_front (); result = get_actual.compare (tmp_tran); if (result) begin `uvm_info("my_scoreboard" , "Compare SUCCESSFULLY" , UVM_LOW); end else begin `uvm_error("my_scoreboard" , "Compare FAILED" ); $display ("the expect pkt is" ); tmp_tran.print (); $display ("the actual pkt is" ); get_actual.print (); end end else begin `uvm_error("my_scoreboard" , "Received from DUT, while Expect Queue is empty" ); $display ("the unexpected pkt is" ); get_actual.print (); end end join endtask `endif
Env: 主要做了两件事,例化 和连接。平台的所有组件都是在此处例化的。env实在哪里呢?
还用到了tlm中的fifo就行数据传输。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 `ifndef MY_ENV__SV `define MY_ENV__SV class my_env extends uvm_env; my_agent i_agt; my_agent o_agt; my_model mdl; my_scoreboard scb; uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo; uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo; uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo; 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; o_agt.is_active = UVM_PASSIVE; mdl = my_model::type_id::create("mdl" , this ); scb = my_scoreboard::type_id::create("scb" , this ); agt_scb_fifo = new ("agt_scb_fifo" , this ); agt_mdl_fifo = new ("agt_mdl_fifo" , this ); mdl_scb_fifo = new ("mdl_scb_fifo" , this ); endfunction extern virtual function void connect_phase(uvm_phase phase); `uvm_component_utils(my_env) endclass function void my_env::connect_phase(uvm_phase phase); super .connect_phase (phase); i_agt.ap .connect (agt_mdl_fifo.analysis_export ); mdl.port .connect (agt_mdl_fifo.blocking_get_export ); mdl.ap .connect (mdl_scb_fifo.analysis_export ); scb.exp_port .connect (mdl_scb_fifo.blocking_get_export ); o_agt.ap .connect (agt_scb_fifo.analysis_export ); scb.act_port .connect (agt_scb_fifo.blocking_get_export ); endfunction `endif
Case: base_test,测试用例基于base_test。
属于测试用例的基础,是下面的顶层,例化env。同时还有打印信息。用例发送不同的激励在每个case中去实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 `ifndef BASE_TEST__SV `define BASE_TEST__SV class base_test extends uvm_test; my_env env; function new (string name = "base_test" , uvm_component parent = null ); super .new (name,parent); endfunction extern virtual function void build_phase(uvm_phase phase); extern virtual function void report_phase(uvm_phase phase); `uvm_component_utils(base_test) endclass function void base_test::build_phase(uvm_phase phase); super .build_phase (phase); env = my_env::type_id::create("env" , this ); endfunction function void base_test::report_phase(uvm_phase phase); uvm_report_server server; int err_num; super .report_phase (phase); server = get_report_server(); err_num = server.get_severity_count (UVM_ERROR); if (err_num != 0 ) begin $display ("TEST CASE FAILED" ); end else begin $display ("TEST CASE PASSED" ); end endfunction `endif
my_case0:
这里面有两个类,一个是sequence一个是case。
case启动:
1 2 3 4 uvm_config_db#(uvm_object_wrapper) ::set(this , "env.i_agt.sqr.main_phase" , "default_sequence" , case0_sequence::type_id::get());
执行sequence中的``uvm_do(m_trans) 宏。就将tr发送到sequencer。driver通过
seq_item_port.get_next_item(req);` 获取sequencer中的tr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 `ifndef MY_CASE0__SV `define MY_CASE0__SV class case0_sequence extends uvm_sequence #(my_transaction) ; my_transaction m_trans; function new (string name= "case0_sequence" ); super .new (name); endfunction virtual task body(); if (starting_phase != null ) starting_phase.raise_objection (this ); repeat (10 ) begin `uvm_do(m_trans) end #100 ; if (starting_phase != null ) starting_phase.drop_objection (this ); endtask `uvm_object_utils(case0_sequence) endclass class my_case0 extends base_test; function new (string name = "my_case0" , uvm_component parent = null ); super .new (name,parent); endfunction extern virtual function void build_phase(uvm_phase phase); `uvm_component_utils(my_case0) endclass function void my_case0::build_phase(uvm_phase phase); super .build_phase (phase); uvm_config_db#(uvm_object_wrapper) ::set(this , "env.i_agt.sqr.main_phase" , "default_sequence" , case0_sequence::type_id::get()); endfunction `endif
Top: top_tb.sv
这是仿真的最顶层
设置:时间精度,引入宏、库、包,发送虚接口
例化:例化DUT
执行:执行run_test(),启动仿真
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 `timescale 1ns/1ps `include "uvm_macros.svh" import uvm_pkg::*; `include "my_if.sv" `include "my_transaction.sv" `include "my_sequencer.sv" `include "my_driver.sv" `include "my_monitor.sv" `include "my_agent.sv" `include "my_model.sv" `include "my_scoreboard.sv" `include "my_env.sv" `include "base_test.sv" `include "my_case0.sv" `include "my_case1.sv" module top_tb; reg clk;reg rst_n;reg [7 :0 ] rxd;reg rx_dv;wire [7 :0 ] txd;wire tx_en;my_if input_if(clk, rst_n); my_if output_if(clk, rst_n); dut my_dut(.clk (clk), .rst_n (rst_n), .rxd (input_if.data ), .rx_dv (input_if.valid ), .txd (output_if.data ), .tx_en (output_if.valid )); initial begin clk = 0 ; forever begin #100 clk = ~clk; end end initial begin rst_n = 1'b0 ; #1000 ; rst_n = 1'b1 ; end initial begin run_test(); end initial begin uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if) ; uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif", input_if) ; uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon", "vif", output_if) ; end endmodule