2.1 hello_world
文章目录
前言
本文以uvm-1.2/examples/simple/hello_world为例,通过代码了解UVM中的一些用法。通过这个例子可以基本了解以下知识点:
- $timeformat的用法
- config_db机制
- TLM通信
- faild automation机制
- $cast的用法
- semaphore的用法
一、基本介绍
hello_world这个例子的整体数据流如下图所示,例化了p1,p2,c,f四个组件,并通过TLM1.0通信,将其连接起来。其中,p1直接连接到了c,p2先经过一个uvm_tlm_fifo,再到c。
代码结构如下所示:
.
├── consumer.sv
├── hello_world.sv
├── Makefile.ius
├── Makefile.questa
├── Makefile.vcs
├── packet.sv
├── producer.sv
└── top.sv
UVM树形结构如下所示。
UVM_INFO ../../../src/base/uvm_root.svh(579) @ 0 ns: reporter [UVMTOP] UVM testbench topology:
-------------------------------------------------------
Name Type Size Value
-------------------------------------------------------
top top - @342
consumer consumer #(T) - @438
in uvm_blocking_put_imp - @446
out uvm_get_port - @455
count integral 32 'd1
fifo uvm_tlm_fifo #(T) - @394
get_ap uvm_analysis_port - @429
get_peek_export uvm_get_peek_imp - @411
put_ap uvm_analysis_port - @420
put_export uvm_put_imp - @402
producer1 producer #(T) - @350
out uvm_blocking_put_port - @362
proto packet - @358
num_packets integral 32 'd2
count integral 32 'd0
producer2 producer #(T) - @372
out uvm_blocking_put_port - @384
proto packet - @380
num_packets integral 32 'd4
count integral 32 'd0
-------------------------------------------------------
二、代码分析
下面自顶向下分别介绍每个文件。
1、hello_world.sv
hello_world.sv文件中的代码如下所示。
`timescale 1ns / 1ns
module hello_world;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "packet.sv"
`include "producer.sv"
`include "consumer.sv"
`include "top.sv"
top mytop;
initial begin
$timeformat(-9,0," ns",5);
//uvm_default_table_printer.knobs.name_width=20;
//uvm_default_table_printer.knobs.type_width=50;
//uvm_default_table_printer.knobs.size_width=10;
//uvm_default_table_printer.knobs.value_width=14;
uvm_config_int::set(null, "top.producer1","num_packets",2);
uvm_config_int::set(null, "top.producer2","num_packets",4);
//uvm_config_int::set(null, "*","recording_detail",UVM_NONE);
uvm_config_int::set(null, "*","recording_detail",UVM_LOW);
//uvm_default_printer = uvm_default_tree_printer;
//uvm_default_printer.knobs.reference=0;
mytop = new("top");
//uvm_default_table_printer.knobs.type_width=20;
run_test();
end
endmodule
第1行,定义了这个模块的时间标度,斜杠前面表示时间的基本单位,斜杠后面表示时间的精度。
第3和31行,例化了hello_world这个模块。
第4到12行,导入了UVM的包和用到的文件。
第13行,声明了top,top的实现是在top.sv中。
第15行, $timeformat的格式描述如下:
$timeformat(units_number, precision_number, suffix_string, minimum_field_wdith)。
- units_number 是 0 到-15 之间的整数值,表示打印的时间值的单位:0 表示秒,-3 表示毫秒,-6 表示微秒,-9 表示纳秒, -12 表示皮秒, -15 表示飞秒,中间值也可以使用:例如-10表示以100ps为单位。其默认值为`timescalse所设置的仿真时间单位。
- precision_number 是在打印时间值时,小数点后保留的位数。其默认值为0。
- suffix_string 是在时间值后面打印的一个后缀字符串。其默认值为空字符串。
- MinFieldWidth 是时间值字符串与后缀字符串合起来的这部分字符串的最小长度,若这部分字符串不足这个长度,则在这部分字符串之前补空格。其默认值为20。
第21,22,24行,调用的是UVM中的config_db机制,往相应的组件传递值。set函寄信,get函数收信,通常成对出现。第一个参数何第二个参数联合起来组成目标路径,与此路径相符合的目标才能收信,第三个参数表示一个记号,记号对的上才能收信,第四个参数是需要传递的值。具体可以参考张强《UVM实战》3.5.2节。
uvm_config_int
Convenience type for uvm_config_db#(uvm_bitstream_t)
typedef uvm_config_db#(uvm_bitstream_t) uvm_config_int;
第27行,调用top的构造函数new,将前面13行声明的top实体化。
第29行,执行仿真。
2、top.sv
top.sv的代码如下所示。
class top extends uvm_component;
producer #(packet) p1;
producer #(packet) p2;
uvm_tlm_fifo #(packet) f;
consumer #(packet) c;
`uvm_component_utils(top)
function new (string name, uvm_component parent=null);
super.new(name,parent);
p1 = new("producer1",this);
p2 = new("producer2",this);
f = new("fifo",this);
c = new("consumer",this);
p1.out.connect( c.in );
p2.out.connect( f.blocking_put_export );
c.out.connect( f.get_export );
endfunction
task run_phase(uvm_phase phase);
phase.raise_objection(this);
uvm_top.print_topology();
#1us;
phase.drop_objection(this);
endtask
endclass
第1行,top继承自uvm_component。
第3到6行,分别声明了p1,p2,f和c,带的参数都是packet。
第8行,将top注册到uvm的factory机制中,一般UVM的组件都需要注册到uvm factory中去,这样方便后期需要改动这个组件时进行重载。
第10到21行,重新定义了构造函数new,这个函数在hello_world.sv文件的27行被调用。在这个函数中,13到16行,分别对上面3到6行中声明的组件实体化,18到20行,完成这几个组件的连接。
第23到28行,定义了run_phase。UVM验证平台的运行,通过自动调用这些phase来实现。phase的介绍,可以参考《UVM实战》第5.1节。24和27行分别对应“举手”和“放下手”,调用的是objection机制,具体可以参考《UVM实战》第5.2节。第25行打印整个当前这个测试用例的UVM树形结构。
3、packet.sv
packet.sv的代码如下所示。
class packet extends uvm_transaction;
`ifndef NO_RAND
rand
`endif
int addr;
`uvm_object_utils_begin(packet)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_object_utils_end
constraint c { addr >= 0 && addr < 'h100; }
function new(string name="packet");
super.new(name);
endfunction
endclass
第1行,packet继承自uvm_transaction,如果说继承自uvm_component的组件相当于人体的骨架,那么继承自uvm_transaction的组件就相当于与血液,它能在各个组件之间流动。
第3到6行,定义了一个变量,这个变量可以通过NO_RAND这个宏,来实现是否是随机变量的切换。
第8到10行,调用的是UVM中的field automation机制,简单的说,就是将这个变量注册到UVM中去,带来的好处是,UVM可以为这个变量提供自带的一些简单函数,例如copy(),compare(),clone(),print()等。更多关于field automation机制可以参考《UVM实战》3.3节。
15到17行是这个类的构造函数,取名叫“packet”。
4、producer.sv
producer.sv的代码如下所示。
class producer #(type T=packet) extends uvm_component;
uvm_blocking_put_port #(T) out;
function new(string name, uvm_component parent=null);
super.new(name,parent);
out = new("out",this);
void'(uvm_config_int::get(this, "", "num_packets", this.num_packets));
endfunction
protected T proto = new;
protected int num_packets = 1;
protected int count = 0;
`uvm_component_utils_begin(producer #(T))
`uvm_field_object(proto, UVM_ALL_ON + UVM_REFERENCE)
`uvm_field_int(num_packets, UVM_ALL_ON + UVM_DEC)
`uvm_field_int(count, UVM_ALL_ON + UVM_DEC + UVM_READONLY)
`uvm_component_utils_end
task run_phase(uvm_phase phase);
T p;
string image, num;
`uvm_info("producer", "Starting.", UVM_MEDIUM)
for (count =0; count < num_packets; count++) begin
$cast(p, proto.clone());
num.itoa(count);
p.set_name({get_name(),"-",num});
p.set_initiator(this);
if (uvm_verbosity'(recording_detail)!=UVM_NONE)
p.enable_recording(get_tr_stream("packet_stream"));
void'(p.randomize());
`uvm_info("producer", $sformatf("Sending %s",p.get_name()), UVM_MEDIUM)
if(uvm_report_enabled(UVM_HIGH,UVM_INFO,""))
p.print();
out.put(p);
#10;
end
`uvm_info("producer", "Exiting.", UVM_MEDIUM)
endtask
endclass
第1行,producer 继承自uvm_component,并且带类型参数T=packet。
第3行,声明了一个TLM1.0中,带参数的port。
5到9行,定义了producer 这个类的构造函数。其中,第6行把name传进去,第7行实体化第3行声明的port,第8行是收信,接收来自hello_world.sv中第21和22行传递过来的值。
11到13行,其中,11行声明并实体化了一个packet的包,12和13行分别声明并初始化了变量,其中声明前面的修饰关键字包括:public,local和protected。
public: 默认为public,子类和类外皆可访问。
local:表示的成员或方法只对该类的对象可见,子类以及类外不可见。
protected: 表示的成员或方法对该类以及子类可见,对类外不可见。
15到19行,UVM中的field automation机制,这个在前面有介绍。
UVM_REFERENCE Only object handles are copied.
UVM_BIN Selects binary (%b) format.
UVM_READONLY Do not allow setting of this field from the set_*_local
methods.
21到54行,定义run_phase这个task。
22行声明了一个packet的变量,T的参数类型就是packet,在第一行作为参数传下来的;
23行声明了两个字符串变量;
25行这里调用了UVM中的一个打印的宏,具体介绍可以参考《UVM实战》3.4节。
27到50行,利用for循环实现了发包。
29行这里先是利用packet中的field automation机制,调用clone()函数,实现了proto的克隆,再利用$cast函数,将克隆好的包赋给p,这里因为是同一个类型,也可以直接赋值,等价于p=proto.clone()。
关于int = $cast(dest_var, source_exp)
1、将source_exp强制赋值给dest_var,成功返回1,失败返回0;
2、子类可以直接赋值给父类,因为子类派生自父类,子类本质上就是父类;
3、父类必须通过$case才能强制转换成子类,因为子类中包含父类中不具备的方法和属性,有可能转换失败;
31行itoa这个函数将整数转换成字符串;
32行设置这个包的名字;
34行为这个包加入一个initiator变量,通过这个变量可以区别包的发起者是哪个组件;
36行将recording_detail这个变量做了强制类型转换;
39行对packet这个包进行随机;
41行打印这个包的名字;
43行uvm_report_enabled这个函数,比较第一个参数与第二个参数的等级,如果高则返回1,否则返回0;
44行打印这个包,由于这个包在packet.sv中,有变量已经注册到了field automation机制中,所以这里能够直接调用这个打印函数,打印格式如下;
UVM_INFO producer.sv(41) @ 0 ns: top.producer1 [producer] Sending producer1-0
---------------------------------------
Name Type Size Value
---------------------------------------
producer1-0 packet - @484
addr integral 32 'h66
initiator producer #(T) -1 @350
---------------------------------------
46行将这个包发送出去,这里用到了TLM1.0机制,这个put函数是在接收方定义的。对于例化的p1来说,put函数是在c中定义的,对于p2来说,put函数是在f中内置的。
52行打印一下Exiting,便于环境的debug。
5、consumer.sv
consumer.sv的代码如下所示。
class consumer #(type T=packet) extends uvm_component;
uvm_blocking_put_imp #(T,consumer #(T)) in;
uvm_get_port #(T) out;
function new(string name, uvm_component parent=null);
super.new(name,parent);
in=new("in",this);
out=new("out",this,0);
endfunction
protected int count=0;
local semaphore lock = new(1);
`uvm_component_utils_begin(consumer #(T))
`uvm_field_int(count,UVM_ALL_ON + UVM_READONLY + UVM_DEC)
`uvm_component_utils_end
task run_phase(uvm_phase phase);
T p;
while(out.size()) begin
out.get(p);
put(p);
end
endtask
task put (T p);
lock.get();
count++;
// void'(accept_tr(p));
accept_tr(p);
#10;
void'(begin_tr(p));
#30;
end_tr(p);
`uvm_info("consumer", $sformatf("Received %0s local_count=%0d",p.get_name(),count), UVM_MEDIUM)
if(uvm_report_enabled(UVM_HIGH,UVM_INFO,""))
p.print();
lock.put();
endtask
endclass
第3和4行,声明了两个TLM1.0的组件;
6到10行,在构造函数中实体化了3,4行声明的两个TLM组件;
12行,定义一个变量用于记录接收到包的个数;
13行,声明并初始化了一个旗语,存放了一个key,定义这个旗语的目的,是为了防止,这个文件的23行和p1同时调用put函数。
semaphore是一俩小车,这个车可以自定义有多少个钥匙,也就是key,多个线程都可以去通过拿一把key来使用这辆车,但这辆车同一时间只能被一个人用,并且一定要在代码中确保车用完了之后使用者把钥匙还回去了,不然下一个人可能就没法用这个车了。
15到17行利用field automation机制,将count变量注册到UVM中;
19到25行是run_phase,在这个task中会去判断out是否为空,out中的数据来自p2,通过uvm_tlm_fifo传递,如果不为空,则调用get函数,将uvm_tlm_fifo中的数据取过来,再调用put函数。
27到40行是put函数,前面定义旗语的时候提到,这个put函数会被两个地方调用,一个是p1,因为p1直接连接到了c,另一个是c的23行。不管哪个先调用put函数,28行先利用旗语取出其中的key,将其锁定。29行count++表示接受到了一个包。31行在这个包中插入接收到这个包的时间accept_time。33行在这个包中插入开始时间begin_time。35行在这个包中插入结束时间end_time。36行打印接收到的包的名字和count。38行打印这个包,打印格式如下。39行将旗语的key送回去,释放对这个函数的占用。
UVM_INFO consumer.sv(57) @ 40 ns: top.consumer [consumer] Received producer1-0 local_count=1
-----------------------------------------
Name Type Size Value
-----------------------------------------
producer1-0 packet - @484
addr integral 32 'h66
accept_time time 64 0 ns
begin_time time 64 10 ns
end_time time 64 40 ns
initiator producer #(T) -1 @350
-----------------------------------------
6、Makefile.vcs
共用的makefile文件如下:
x: all
#
# Include file for VCS Makefiles
#
UVM_VERBOSITY = UVM_HIGH
#
# Note that +acc and +vpi have an adverse impact on performance
# and should not be used unless necessary.
#
# They are used here because they are required by some examples
# (backdoor register accesses).
#
TEST = /usr/bin/test
N_ERRS = 0
N_FATALS = 0
VCS = vcs -sverilog -timescale=1ns/1ns \
+acc +vpi \
+incdir+$(UVM_HOME)/src $(UVM_HOME)/src/uvm.sv \
$(UVM_HOME)/src/dpi/uvm_dpi.cc -CFLAGS -DVCS
SIMV = ./simv +UVM_VERBOSITY=$(UVM_VERBOSITY) -l vcs.log
URG = urg -format text -dir simv.vdb
CHECK = \
@$(TEST) \( `grep -c 'UVM_ERROR : $(N_ERRS)' vcs.log` -eq 1 \) -a \
\( `grep -c 'UVM_FATAL : $(N_FATALS)' vcs.log` -eq 1 \)
clean:
rm -rf *~ core csrc simv* vc_hdrs.h ucli.key urg* *.log
hello_world这个case的makefile如下,其中在第3行调用了上面共用的makefile文件。
UVM_HOME = ../../..
include ../../Makefile.vcs
all: comp run
comp:
$(VCS) +incdir+. \
hello_world.sv
run:
$(SIMV)
$(CHECK)
7、仿真结果
仿真结果如下。
UVM_INFO @ 0 ns: reporter [RNTST] Running test ...
UVM_INFO producer.sv(46) @ 0 ns: top.producer1 [producer] Starting.
UVM_INFO producer.sv(62) @ 0 ns: top.producer1 [producer] Sending producer1-0
---------------------------------------
Name Type Size Value
---------------------------------------
producer1-0 packet - @484
addr integral 32 'h66
initiator producer #(T) -1 @350
---------------------------------------
UVM_INFO producer.sv(46) @ 0 ns: top.producer2 [producer] Starting.
UVM_INFO producer.sv(62) @ 0 ns: top.producer2 [producer] Sending producer2-0
---------------------------------------
Name Type Size Value
---------------------------------------
producer2-0 packet - @498
addr integral 32 'h4d
initiator producer #(T) -1 @372
---------------------------------------
UVM_INFO ../../../src/base/uvm_root.svh(579) @ 0 ns: reporter [UVMTOP] UVM testbench topology:
------------------------------------------------------------
Name Type Size Value
------------------------------------------------------------
top top - @342
consumer consumer #(T) - @438
in uvm_blocking_put_imp - @446
recording_detail uvm_verbosity 32 UVM_LOW
out uvm_get_port - @455
recording_detail uvm_verbosity 32 UVM_LOW
count integral 32 'd1
recording_detail uvm_verbosity 32 UVM_LOW
fifo uvm_tlm_fifo #(T) - @394
get_ap uvm_analysis_port - @429
recording_detail uvm_verbosity 32 UVM_LOW
get_peek_export uvm_get_peek_imp - @411
recording_detail uvm_verbosity 32 UVM_LOW
put_ap uvm_analysis_port - @420
recording_detail uvm_verbosity 32 UVM_LOW
put_export uvm_put_imp - @402
recording_detail uvm_verbosity 32 UVM_LOW
recording_detail uvm_verbosity 32 UVM_LOW
producer1 producer #(T) - @350
out uvm_blocking_put_port - @362
recording_detail uvm_verbosity 32 UVM_LOW
proto packet - @358
num_packets integral 32 'd2
count integral 32 'd0
recording_detail uvm_verbosity 32 UVM_LOW
producer2 producer #(T) - @372
out uvm_blocking_put_port - @384
recording_detail uvm_verbosity 32 UVM_LOW
proto packet - @380
num_packets integral 32 'd4
count integral 32 'd0
recording_detail uvm_verbosity 32 UVM_LOW
recording_detail uvm_verbosity 32 UVM_LOW
------------------------------------------------------------
UVM_INFO producer.sv(62) @ 10 ns: top.producer2 [producer] Sending producer2-1
---------------------------------------
Name Type Size Value
---------------------------------------
producer2-1 packet - @529
addr integral 32 'h78
initiator producer #(T) -1 @372
---------------------------------------
UVM_INFO producer.sv(62) @ 20 ns: top.producer2 [producer] Sending producer2-2
---------------------------------------
Name Type Size Value
---------------------------------------
producer2-2 packet - @535
addr integral 32 'h2
initiator producer #(T) -1 @372
---------------------------------------
UVM_INFO consumer.sv(57) @ 40 ns: top.consumer [consumer] Received producer1-0 local_count=1
-----------------------------------------
Name Type Size Value
-----------------------------------------
producer1-0 packet - @484
addr integral 32 'h66
accept_time time 64 0 ns
begin_time time 64 10 ns
end_time time 64 40 ns
initiator producer #(T) -1 @350
-----------------------------------------
UVM_INFO producer.sv(62) @ 50 ns: top.producer1 [producer] Sending producer1-1
---------------------------------------
Name Type Size Value
---------------------------------------
producer1-1 packet - @547
addr integral 32 'h66
initiator producer #(T) -1 @350
---------------------------------------
UVM_INFO consumer.sv(57) @ 80 ns: top.consumer [consumer] Received producer2-0 local_count=2
-----------------------------------------
Name Type Size Value
-----------------------------------------
producer2-0 packet - @498
addr integral 32 'h4d
accept_time time 64 40 ns
begin_time time 64 50 ns
end_time time 64 80 ns
initiator producer #(T) -1 @372
-----------------------------------------
UVM_INFO producer.sv(62) @ 90 ns: top.producer2 [producer] Sending producer2-3
---------------------------------------
Name Type Size Value
---------------------------------------
producer2-3 packet - @568
addr integral 32 'h96
initiator producer #(T) -1 @372
---------------------------------------
UVM_INFO consumer.sv(57) @ 120 ns: top.consumer [consumer] Received producer1-1 local_count=3
------------------------------------------
Name Type Size Value
------------------------------------------
producer1-1 packet - @547
addr integral 32 'h66
accept_time time 64 80 ns
begin_time time 64 90 ns
end_time time 64 120 ns
initiator producer #(T) -1 @350
------------------------------------------
UVM_INFO producer.sv(73) @ 130 ns: top.producer1 [producer] Exiting.
UVM_INFO consumer.sv(57) @ 160 ns: top.consumer [consumer] Received producer2-1 local_count=4
------------------------------------------
Name Type Size Value
------------------------------------------
producer2-1 packet - @529
addr integral 32 'h78
accept_time time 64 120 ns
begin_time time 64 130 ns
end_time time 64 160 ns
initiator producer #(T) -1 @372
------------------------------------------
UVM_INFO producer.sv(73) @ 170 ns: top.producer2 [producer] Exiting.
UVM_INFO consumer.sv(57) @ 200 ns: top.consumer [consumer] Received producer2-2 local_count=5
------------------------------------------
Name Type Size Value
------------------------------------------
producer2-2 packet - @535
addr integral 32 'h2
accept_time time 64 160 ns
begin_time time 64 170 ns
end_time time 64 200 ns
initiator producer #(T) -1 @372
------------------------------------------
UVM_INFO consumer.sv(57) @ 240 ns: top.consumer [consumer] Received producer2-3 local_count=6
------------------------------------------
Name Type Size Value
------------------------------------------
producer2-3 packet - @568
addr integral 32 'h96
accept_time time 64 200 ns
begin_time time 64 210 ns
end_time time 64 240 ns
initiator producer #(T) -1 @372
------------------------------------------
UVM_INFO ../../../src/base/uvm_objection.svh(1270) @ 1000 ns: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO ../../../src/base/uvm_report_server.svh(847) @ 1000 ns: reporter [UVM/REPORT/SERVER]
--- UVM Report Summary ---
** Report counts by severity
UVM_INFO : 20
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[UVM/RELNOTES] 1
[UVMTOP] 1
[consumer] 6
[producer] 10
总结
通过仿真结果可以看到,由于p1直接连接到了c,所以p1发出的包最先被c接收到,但由于c中put函数利用了旗语进行分时管理,p1发出的包只有在接收后,才能再次发出,而p2发出的包由于经过了uvm_tlm_fifo再到的c,所以哪怕下游c的put函数来不及处理,但是p2仍然可以发出不止一个包,可以看到fifo的深度默认为3。