欢迎来到从零开始的DFT工程师第四期。

上一期学习了简单的测试流程学习和搭建,完成了对简单模块的小测试,这一次进行略微进阶的UVM测试学习。

本次主要内容是:callback, TLM, RAL,barrier, heartbeat.

上文补充

编译顺序

上次写的时候没有强调编译顺序的问题,其实是在run.sh中,各个文件的顺序是比较重要的,顺序写的不对会导致编译失败。如果写代码的时候没有写typedef就会有很多编译顺序的问题。这里可以把需要的文件单独写成一个my_package.sv,在这个文件里面调整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package my_package;
import uvm_pkg::*;
`include "uvm_macros.svh"

`include"interface.sv"
`include"memory.sv"
`include"sequence_item.sv"
`include"sequencer.sv"
`include"sequence.sv"
`include"driver.sv"
`include"monitor.sv"
`include"agent.sv"
`include"scoreboard.sv"
`include"env.sv"
`include"test.sv"
`include"tb_top.sv"
endpackage

super.

关于为什么new和build_phase一般需要写super.:因为父类会有一些关键行为,如果不写不会自动执行。具体来讲,uvm_component的父类new()中进行了建立component hierarchy,注册到UVM factory,phase机制初始化等关键行为,build_phase则是自动调用apply_config_settings配置config_db,不写的话会有uvm_config_db::get()失败,virtual interface找不到等问题。

systemverilog callback

首先来了解一下什么是callback.

callback的作用是在不修改原有代码的情况下改变程序的行为,核心是两个部分:

  • callback hook:是一个在父类预留的接口,一般是一个空的虚拟方法。
  • callback method:这是在派生类中写下的具体逻辑。

对于一般面向对象的设计,要遵守开闭原则,也就是对扩展开放,但是对修改封闭。在测试中我们会有很多不常规的行为,比如说注入错误进行测试,如果直接修改driver等源码,显然是很不工程友好的。

在上一期我们写了一些奇奇怪怪的东西,比如说:

strange

这里,pre_configurepost_configure有没有出现的必要呢?这两个其实完全可以写在configure里面,但是这里分出三块,其实是方便扩展的。正如上面所说,能跑的源码已经把configure阶段写好了,如果想要修改configure阶段的行为还必须去修改源码,这就是一件不仅麻烦而且还有一定风险的事情了。所以这里预定三个methods顺序执行,只不过pre和post是空的,如果有需要修改就可以在pre和post里面写上新的逻辑。(这一段仅供思考所用,实际情况是有一部分callback的pre&post是空的,具体到configure并未查证。)

另外,callback可以被override.

一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef enum {OKAY, EXOKAY, SLVERR, DECERR} resp_type;

class slave_driver;
resp_type resp;

//callback hook
virtual task update_resp; endtask

//send response task
task send_response;
std::randomize(resp) with { resp == OKAY;};
update_resp();
endtask
endclass

这个是driver.sv,展示了常规行为,slave_driver一般情况下会一直发送OKAY的response;但是这里留下了一个update_resp()的hook,以后会被用。

1
2
3
4
5
6
class err_inject extends slave_driver;
virtual task update_resp;
$display("Injecting SLVERR");
resp = SLVERR;
endtask
endclass

这里写了一个继承自slave_driver的新class,在这里给update_resp()实现了具体定义,也就是把应该输出的OKAY改成了SLVERR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
program error_test;
slave_env env;
err_inject err_driver;

initial begin
//Create env
env = new();
err_driver = new();
//Overriding slave_driver by error_driver
env.slv_driver = err_driver;

//Calling run of env
env.run();
end
endprogram

这里通过实例化一个err_inject对象,给env.slv_driver中的hook提供了具体函数逻辑,从而实现了”错误注入”.

从上面我们可以看出,callback可以在不改变原有环境的情况下,实现一些测试中需要的测试需求,在现有逻辑上可以轻松的添加新的功能,组件也可以重复使用。

UVM (component) callback

好的我们现在回到UVM callback. UVM本身提供了一组类,方法,宏完成callack的调用。在UVM的设计中,可以通过扩展自uvm_callback实现callback的定义。

add UVM callback hooks

1
2
3
class driver_callback extends uvm_callback;
...
endclass

对具体行为的定义需要使用虚函数/任务(或者说这里只设置接口不定义逻辑):

1
2
3
4
5
virtual task pre_drive;
endtask

virtual function post_drive;
endfunction

一个完整的user-defined-callback大致长这样:

1
2
3
4
5
6
7
8
9
10
11
12
`include "uvm_macros.svh"
import uvm_pkg::*;
class driver_callback extends uvm_callback;
`uvm_object_utils(driver_callback)

function new(string name = "driver_callback");
super.new(name);
endfunction

virtual task pre_drive; endtask
virtual task post_drive; endtask
endclass

uvm_callback在用前需要注册到object或者component,具体方法是:

1
`uvm_register_cb(driver, driver_callback)

此处语法是:uvm_register_cb(T<object in which CB is used>,CB<user defined callback class>),用于将CB注册到T内。

uvm_callback可以通过`uvm_do_callbacks被调用,就像这样:

1
2
`uvm_do_callbacks(driver, driver_callback, pre_drive());
`uvm_do_callbacks(driver, driver_callback, post_drive());

语法是:uvm_do_callbacks(T,CB,method).

此时的driver代码可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class driver extends uvm_component;

`uvm_component_utils(driver)
`uvm_register_cb(driver, driver_callback)

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

task run_phase(uvm_phase phase);
`uvm_do_callbacks(driver, driver_callback, pre_drive());
drive_pkt();
`uvm_do_callbacks(driver, driver_callback, post_drive());
endtask


task drive_pkt();
`uvm_info("DRIVER", "Inside drive_pkt method", UVM_LOW);
endtask
endclass

add logic to callback methods

现在我们来添加具体执行逻辑,首先还是需要进行扩展用户定义的driver:

1
2
3
class user_callback extends driver_callback;
...
endclass

然后在内部写入具体的执行逻辑:

1
2
3
4
5
6
7
task pre_drive;
`uvm_info("USER_CALLBACK", "Inside pre_drive method", UVM_LOW);
endtask

task post_drive;
`uvm_info("USER_CALLBACK", "Inside post_drive method", UVM_LOW);
endtask

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class user_callback extends driver_callback;

`uvm_object_utils(user_callback)

function new(string name = "user_callback");
super.new(name);
endfunction

task pre_drive;
`uvm_info("USER_CALLBACK","Inside pre_drive method",UVM_LOW);
endtask

task post_drive;
`uvm_info("USER_CALLBACK","Inside post_drive method",UVM_LOW);
endtask
endclass

using callback

当实际需要执行callback的逻辑时,可以实例化一个user_callback对象:

1
2
user_callback callback_1;
callback_1=user_callback::type_id::create("callback_1",this);

然后使用add添加到uvm_callback

1
uvm_callbacks#(driver, driver_callback)::add(env.driv, callback_1);

语法是:uvm_callbacks#(t<object type in which CB is used>,cb<user defined callback class type>)::add(T,CB).
add除了T和CB还有一个参数叫<type: uvm_apprepend> ordering,默认值是UVM_APPEND,代表如果之前也有添加过callback,本次添加的会在之前添加的callback之后执行。

此时的完整test代码应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class user_callback_test extends basic_test;
user_callback callback_1;

`uvm_component_utils(user_callback_test)

function new(string name ="user_callback_test", uvm_component parent=null);
super.new(name, parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
callback_1 = user_callback::type_id::create("callback_1", this);

uvm_callbacks#(driver, driver_callback)::add(env.driv, callback_1);
endfunction
endclass

流程大概是:在test中创建一个callback_1(执行的是user_callback的逻辑),通过add把这个具体的逻辑塞入driver_callback中,然后通过留下的hook把callback的逻辑填充到driver,实现不修改源码的情况下修改执行逻辑。

不使用uvm callback的test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

class basic_test extends uvm_test;
environment env;

`uvm_component_utils(basic_test)

function new(string name = "basic_test", uvm_component parent=null);
super.new(name,parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = environment::type_id::create("env", this);
endfunction
endclass

写下运行脚本和tb,env:

1
2
3
4
5
6
7
8
9
10
11
12
13
vcs -full64 -sverilog -ntb_opts uvm \
driver_callback.sv \
driver.sv \
user_callback.sv \
env.sv \
basic_test.sv \
user_callback_test.sv \
tb.sv \
-debug_access+all > compile.log

./simv +UVM_TESTNAME=basic_test > basic_test.log
./simv +UVM_TESTNAME=user_callback_test > user_callback_test.log

1
2
3
4
5
6
7
`include "uvm_macros.svh"
import uvm_pkg::*;
module tb_top;
initial begin
run_test();
end
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class environment extends uvm_env;
driver driv;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
driv = driver::type_id::create("driv", this);
endfunction

endclass

basic_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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Jan 25 18:28 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test basic_test...
UVM_INFO driver.sv(18) @ 0: uvm_test_top.env.driv [DRIVER] Inside drive_pkt method

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 2
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[DRIVER] 1
[RNTST] 1
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.300 seconds; Data structure size: 0.4Mb
Sun Jan 25 18:28:48 2026

user_callback_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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Jan 25 18:28 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test user_callback_test...
UVM_INFO user_callback.sv(10) @ 0: reporter [USER_CALLBACK] Inside pre_drive method
UVM_INFO driver.sv(18) @ 0: uvm_test_top.env.driv [DRIVER] Inside drive_pkt method
UVM_INFO user_callback.sv(14) @ 0: reporter [USER_CALLBACK] Inside post_drive method

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 4
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[DRIVER] 1
[RNTST] 1
[USER_CALLBACK] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.330 seconds; Data structure size: 0.4Mb
Sun Jan 25 18:28:48 2026

add

关于add,有一些需要补充的内容。对于同一个hook,由于是虚函数占位,我们之后可以去定义多种不同的逻辑method,这些method其实可以注册到同一个callback hook.如果我们对于同一个callback,写了三种不同的具体实现,且把三种都注册到这个hook,那么在调用`uvm_do_callbacks的时候就会按照顺序执行所有已经被注册的方法(虽然这样的定义有点奇怪)。

比如在之前的例子,我们可以从driver_callback这个class派生出两个class:

user_callback_1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class user_callback_1 extends driver_callback;
`uvm_object_utils(user_callback_1)

function new(string name ="user_callback_1");
super.new(name);
endfunction

task pre_drive;
`uvm_info("USER_CALLBACK-1","Inside pre_drive method",UVM_LOW);
endtask

task post_drive;
`uvm_info("USER_CALLBACK-1","Inside post_drive method",UVM_LOW);
endtask
endclass

user_callback_2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class user_callback_2 extends driver_callback;
`uvm_object_utils(user_callback_2)

function new(string name="user_callback_2");
super.new(name);
endfunction
task pre_drive;
`uvm_info("USER_CALLBACK-2","Inside pre_drive method",UVM_LOW);
endtask

task post_drive;
`uvm_info("USER_CALLBACK-2","Inside post_drive method",UVM_LOW);
endtask

endclass

写出test.sv和run.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class user_callback_test extends basic_test;
user_callback_1 callback_1;
user_callback_2 callback_2;

`uvm_component_utils(user_callback_test)

function new(string name = "user_callback_test", uvm_component parent=null);
super.new(name,parent);
endfunction

function void build_phase(uvm_phase phase);
super.build_phase(phase);
callback_1 = user_callback_1::type_id::create("callback_1", this);
callback_2 = user_callback_2::type_id::create("callback_2", this);

uvm_callbacks#(driver,driver_callback)::add(env.driv,callback_1);
uvm_callbacks#(driver,driver_callback)::add(env.driv,callback_2);
endfunction
endclass
1
2
3
4
5
6
7
8
9
10
11
12
13
vcs -full64 -sverilog -ntb_opts uvm \
driver_callback.sv \
driver.sv \
user_callback_1.sv \
user_callback_2.sv \
env.sv \
test.sv \
user_callback_test.sv \
tb.sv \
-debug_access+all >compile.log

#./simv +UVM_TESTNAME=basic_test >basic_test.log
./simv +UVM_TESTNAME=user_callback_test >user_callback_test.log

本次运行结果是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Jan 25 22:26 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test user_callback_test...
UVM_INFO user_callback_1.sv(10) @ 0: reporter [USER_CALLBACK-1] Inside pre_drive method
UVM_INFO user_callback_2.sv(10) @ 0: reporter [USER_CALLBACK-2] Inside pre_drive method
UVM_INFO driver.sv(18) @ 0: uvm_test_top.env.driv [DRIVER] Inside drive_pkt method
UVM_INFO user_callback_1.sv(14) @ 0: reporter [USER_CALLBACK-1] Inside post_drive method
UVM_INFO user_callback_2.sv(14) @ 0: reporter [USER_CALLBACK-2] Inside post_drive method

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 6
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[DRIVER] 1
[RNTST] 1
[USER_CALLBACK-1] 2
[USER_CALLBACK-2] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.270 seconds; Data structure size: 0.4Mb
Sun Jan 25 22:26:40 2026

我们之前讲过add还有一个参数是决定已经注册的method的执行顺序,默认为本次添加的在以前添加的之后执行,这里先加入的是user_callback_1,所以也是1先被执行。

关于不同场景callback的一些关键词使用差异/component & object

在上一期当中有一个已经被提到的细节,就是按照语法规范要不要给类似endfunction:new打标签。我们当时说这是objectcomponent的区别,这个差异还会在callback这里有所体现。

先说结论,driver/monitor/agent属于uvm_component,在设置hook的时候使用的宏是uvm_do_callback,UVM在执行的时候callback会存储在component level的callback pool;sequence/transaction属于uvm_object,使用的宏是uvm_do_obj_callbacks,实际callback存储于object level的callback list.

有这个不同之处的原因除了我们之前就说过的生命周期不同,还有一个原因是早期的验证方法学中,callback的主要作用是 在不修改组件源代码的情况下改变组件的行为,因此一开始就是为了服务于长寿命的组件,而object callback本身处于一个比较尴尬的地位,主要问题是生命周期太短(动态创建,快速销毁)和上下文缺失(组件有明确的上下层级,但是object没有,早期机制难以实现)。

目前来讲,component类型的callback有callback pool集中管理,按照component instance组织,可以add/remove,enable/disenable,callback本身是注册在对应的component上的,UVM会自动遍历component  instance对应的callback list.

但是对于object,这些instance本身不在component tree里面,也没有hierarchical path, 没有全局的callback pool,实际上是objects本身在带着callback list到处跑。

object callback class的定义,虚函数callback方法,注册callback的方法,具体callback逻辑和component是差不多的。

callback hook的放置使用的宏不同:

1
`uvm_do_obj_callbacks(mem_sequencer, mem_callback, p_sequencer, update_pkt(req));

其含义是,在object(mem_sequencer)上遍历所有已经注册的mem_callback,对于每一个callback,强制转化成p_sequencer的类型,调用update_pkt(req)方法。

UVM (object) callback

一个测试样例:
memory.sv:

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


//------------------------------------------------------------------------
// Memory Model RTL - www.verificationguide.com
//------------------------------------------------------------------------
/*
-----------------
| |
addr ---->| |
| |------> rdata
| Memory Model |
wdata ---->| |
| |
-----------------
^ ^
| |
wr_en rd_en

-------------------------------------------------------------------------- */
module memory
#( parameter ADDR_WIDTH = 4,
parameter DATA_WIDTH = 8 ) (
input clk,
input reset,

//control signals
input [ADDR_WIDTH-1:0] addr,
input wr_en,
input rd_en,

//data signals
input [DATA_WIDTH-1:0] wdata,
output logic [DATA_WIDTH-1:0] rdata
);

//reg [DATA_WIDTH-1:0] rdata;

//Memory
reg [DATA_WIDTH-1:0] mem [2**ADDR_WIDTH];

//Reset
always @(posedge reset)
for(int i=0;i<2**ADDR_WIDTH;i++) mem[i]=8'hFF;

// Write data to Memory
always @(posedge clk)
if (wr_en) mem[addr] <= wdata;

// Read data from memory
always @(posedge clk)
if (rd_en) rdata <= mem[addr];

endmodule

interface.sv:

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

interface mem_if(input logic clk, reset);

logic [3:0] addr;
logic wr_en;
logic rd_en;
logic [7:0] wdata;
logic [7:0] rdata;

clocking driver_cb @(posedge clk);
default input #1 output #1;
output addr;
output wr_en;
output rd_en;
output wdata;
input rdata;
endclocking


clocking monitor_cb @(posedge clk);
default input #1 output #1;
input addr;
input wr_en;
input rd_en;
input wdata;
input rdata;
endclocking

modport DRIVER(clocking driver_cb, input clk, reset);
modport MONITOR(clocking monitor_cb, input clk, reset);
endinterface

mem_seq_item.sv:

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
import uvm_pkg::*;
`include "uvm_macros.svh"
class mem_seq_item extends uvm_sequence_item;
rand bit [3:0] addr;
rand bit wr_en;
rand bit rd_en;
rand bit [7:0] wdata;
bit [7:0] rdata;


`uvm_object_utils_begin(mem_seq_item)
`uvm_field_int(addr,UVM_ALL_ON)
`uvm_field_int(wr_en,UVM_ALL_ON)
`uvm_field_int(rd_en,UVM_ALL_ON)
`uvm_field_int(wdata,UVM_ALL_ON)
`uvm_object_utils_end


function new(string name="mem_seq_item");
super.new(name);
endfunction


constraint wr_rd_c {wr_en != rd_en;};
endclass

mem_sequence.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef class mem_sequencer;
class mem_sequence extends uvm_sequence#(mem_seq_item);
`uvm_object_utils(mem_sequence)

function new(string name="mem_sequence");
super.new(name);
endfunction

`uvm_declare_p_sequencer(mem_sequencer)

virtual task body();
req=mem_seq_item::type_id::create("req");
wait_for_grant();
req.randomize();

`uvm_do_obj_callbacks(mem_sequencer, mem_callback, p_sequencer,update_pkt(req));
send_request(req);
wait_for_item_done();
endtask


endclass

mem_sequencer.sv:

1
2
3
4
5
6
7
8
9
class mem_sequencer extends uvm_sequencer#(mem_seq_item);
`uvm_component_utils(mem_sequencer)
`uvm_register_cb(mem_sequencer,mem_callback)

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

endclass:mem_sequencer

mem_driver.sv:

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
class mem_driver extends uvm_driver#(mem_seq_item);
`uvm_component_utils(mem_driver)

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


function void build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction:build_phase


virtual task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive();
seq_item_port.item_done();
end
endtask:run_phase


virtual task drive();
`uvm_info(get_type_name(), "received Drive packet", UVM_LOW)
req.print();
endtask:drive
endclass:mem_driver

mem_agent.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class mem_agent extends uvm_agent;
mem_driver driver;
mem_sequencer sequencer;

`uvm_component_utils(mem_agent)


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


function void build_phase(uvm_phase phase);
super.build_phase(phase);

driver=mem_driver::type_id::create("driver", this);
sequencer=mem_sequencer::type_id::create("sequencer", this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction:connect_phase
endclass:mem_agent

mem_env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class mem_model_env extends uvm_env;
mem_agent mem_agnt;
`uvm_component_utils(mem_model_env)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
mem_agnt=mem_agent::type_id::create("mem_agnt", this);
endfunction:build_phase

endclass:mem_model_env

mem_base_test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class mem_model_base_test extends uvm_test;
`uvm_component_utils(mem_model_base_test)


mem_model_env env;
function new(string name = "mem_model_base_test",uvm_component parent=null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);

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

virtual function void end_of_elaboration();
print();
endfunction
endclass:mem_model_base_test

mem_callback.sv:

1
2
3
4
5
6
7
8
9
class mem_callback extends uvm_callback;
`uvm_object_utils(mem_callback)

function new(string name ="mem_callback");
super.new(name);
endfunction

virtual task update_pkt(ref mem_seq_item pkt); endtask
endclass

这里的ref类似cpp中使用引用,callback里的操作会直接作用于原始的transaction上面。

user_callback.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class user_callback extends mem_callback;
`uvm_object_utils(user_callback)

function new(string name="user_callback");
super.new(name);
endfunction

task update_pkt(ref mem_seq_item pkt);
`uvm_info("USER_CALLBACK","[update_pkt] before packet modification",
UVM_LOW);
pkt.print();
pkt.addr=~pkt.addr;
`uvm_info("USER_CALLBACK","[update_pkt] after packet modification",UVM_LOW);
pkt.print();
endtask
endclass

mem_test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class mem_test extends mem_model_base_test;
`uvm_component_utils(mem_test)

mem_sequence seq;

function new(string name="mem_test",uvm_component parent=null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
seq=mem_sequence::type_id::create("seq");
endfunction:build_phase

task run_phase(uvm_phase phase);
phase.raise_objection(this);
seq.start(env.mem_agnt.sequencer);
phase.drop_objection(this);

phase.phase_done.set_drain_time(this, 50);
endtask:run_phase
endclass:mem_test

user_callback_test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class user_callback_test extends mem_test;
user_callback callback_1;

`uvm_component_utils(user_callback_test)

function new(string name="user_callback_test", uvm_component parent=null);
super.new(name, parent);
endfunction:new

function void build_phase(uvm_phase phase);
super.build_phase(phase);
callback_1=user_callback::type_id::create("callback_1", this);
endfunction:build_phase

function void end_of_elaboration();
super.end_of_elaboration();
uvm_callbacks#(mem_sequencer,mem_callback)::add(env.mem_agnt.sequencer,callback_1);
endfunction:end_of_elaboration
endclass:user_callback_test

testbench.sv:

1
2
3
4
5
6
7
8
import uvm_pkg::*;
`include "uvm_macros.svh"

module tbench_top;
initial begin
run_test();
end
endmodule

run.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vcs -full64 -sverilog -ntb_opts uvm \
memory.sv \
interface.sv \
mem_seq_item.sv \
mem_callback.sv \
mem_sequence.sv \
mem_sequencer.sv \
user_callback.sv \
mem_driver.sv \
mem_agent.sv \
mem_env.sv \
mem_base_test.sv \
mem_test.sv \
user_callback_test.sv \
testbench.sv \
-debug_access+all 2>&1 | tee compile.log

./simv +UVM_TESTNAME=mem_test 2>&1 | tee mem_test.log
./simv +UVM_TESTNAME=user_callback_test 2>&1 | tee user_callback_test.log

我们可以得到运行mem_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
64
65
66
67
68
69
70
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 1 20:48 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test mem_test...
--------------------------------------------------------------
Name Type Size Value
--------------------------------------------------------------
uvm_test_top mem_test - @469
env mem_model_env - @477
mem_agnt mem_agent - @489
driver mem_driver - @501
rsp_port uvm_analysis_port - @518
seq_item_port uvm_seq_item_pull_port - @509
sequencer mem_sequencer - @527
rsp_export uvm_analysis_export - @535
seq_item_export uvm_seq_item_pull_imp - @641
arbitration_queue array 0 -
lock_queue array 0 -
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
--------------------------------------------------------------
UVM_INFO mem_driver.sv(25) @ 0: uvm_test_top.env.mem_agnt.driver [mem_driver] received Drive packet
----------------------------------
Name Type Size Value
----------------------------------
req mem_seq_item - @658
addr integral 4 'hb
wr_en integral 1 'h0
rd_en integral 1 'h1
wdata integral 8 'h19
----------------------------------
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 3
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[mem_driver] 1
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 50
V C S S i m u l a t i o n R e p o r t
Time: 50
CPU Time: 0.270 seconds; Data structure size: 0.4Mb
Sun Feb 1 20:48:33 2026

加入callback的运行结果是:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 1 20:48 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test user_callback_test...
--------------------------------------------------------------
Name Type Size Value
--------------------------------------------------------------
uvm_test_top user_callback_test - @469
env mem_model_env - @477
mem_agnt mem_agent - @490
driver mem_driver - @502
rsp_port uvm_analysis_port - @519
seq_item_port uvm_seq_item_pull_port - @510
sequencer mem_sequencer - @528
rsp_export uvm_analysis_export - @536
seq_item_export uvm_seq_item_pull_imp - @642
arbitration_queue array 0 -
lock_queue array 0 -
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
--------------------------------------------------------------
UVM_INFO user_callback.sv(9) @ 0: reporter [USER_CALLBACK] [update_pkt] before packet modification
----------------------------------
Name Type Size Value
----------------------------------
req mem_seq_item - @660
addr integral 4 'hb
wr_en integral 1 'h0
rd_en integral 1 'h1
wdata integral 8 'h19
----------------------------------
UVM_INFO user_callback.sv(13) @ 0: reporter [USER_CALLBACK] [update_pkt] after packet modification
----------------------------------
Name Type Size Value
----------------------------------
req mem_seq_item - @660
addr integral 4 'h4
wr_en integral 1 'h0
rd_en integral 1 'h1
wdata integral 8 'h19
----------------------------------
UVM_INFO mem_driver.sv(25) @ 0: uvm_test_top.env.mem_agnt.driver [mem_driver] received Drive packet
----------------------------------
Name Type Size Value
----------------------------------
req mem_seq_item - @660
addr integral 4 'h4
wr_en integral 1 'h0
rd_en integral 1 'h1
wdata integral 8 'h19
----------------------------------
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 50: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 5
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[USER_CALLBACK] 2
[mem_driver] 1
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 50
V C S S i m u l a t i o n R e p o r t
Time: 50
CPU Time: 0.330 seconds; Data structure size: 0.4Mb
Sun Feb 1 20:48:33 2026

uvm_event

event一般是一个静态的object,在多数情况下可以用作时序逻辑的同步方式;uvm有一个uvm_event_base的class,它本身完成了一个抽象event的封装。event常用的操作有两个,一个是触发事件来完成时序同步(或者其他合理的逻辑操作),另一个是等待事件被触发(从而解除阻塞)。uvm_event_base和他很多内置method的出现是为了简化event的使用。

uvm_event methods

methods description
new create a new event object, initialize as off state
wait_trigger wait for an event to be triggered(the next trigger atfer the wait_trigger statement); it will keep wait until next trigger
wait_ptrigger wait for a persistent trigger(which means the trigger is race-free) of the event
wait_on wait for the event to be activated for the first time, returns immediately if event has been triggered
wait_off return if the event is off, else wait for the event turned off via reset event call
is_on return 1 if event has triggered
is_off return 1 if event has not been triggered
reset reset the event to its off state
get_trigger_time return the last $time at which the event got triggered.
get_num_waiters return the number of processes waiting on the event

最常用的几种:

1
2
3
4
uvm_event event_name;
event_name = new();
event_name.trigger();
event_name.wait_trigger();

wait_trigger

uvm_events_ex.sv:

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
import uvm_pkg::*;
`include "uvm_macros.svh"

module uvm_events_ex;
uvm_event ev_1;

initial begin
ev_1=new("ev_1");


fork
begin
#40;
$display($time," Triggering the event");
ev_1.trigger();
end


begin
$display($time, " Waiting for the event to be triggered");
ev_1.wait_trigger();
$display($time, " event triggered");
end

join
end
endmodule

run.sh:

1
2
3
4
vcs -sverilog -full64 -ntb_opts uvm \
uvm_events_ex.sv 2>&1 | tee compile.log

./simv 2>&1 | tee result.log

运行结果是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 2 15:26 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 Waiting for the event to be triggered
40 Triggering the event
40 event triggered
V C S S i m u l a t i o n R e p o r t
Time: 40
CPU Time: 0.220 seconds; Data structure size: 0.1Mb
Mon Feb 2 15:26:10 2026

这里就可以看到事件的触发和并行逻辑的执行顺序,同时我们可以发现这是一个delta
cycle操作。

这里的坑是wait_trigger,在上面我们说过wait_trigger是一直等,直到出现下一次的trigger,所以如果是wait_trigger在实际触发之后,就会导致卡死或者逻辑错误;即便是trigger和wait_trigger同时执行,也会因为race condition导致wait_trigger实际上无法接收到trigger信息。

wait_ptrigger

wait_ptrigger可以解决一部分race condition的问题。

下面这个例子中:

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
import uvm_pkg::*;
`include "uvm_macros.svh"

module uvm_events_ex;
uvm_event ev_1;


initial begin
ev_1 = new("ev_1");

fork
begin
$display($time, " Triggering the event");
ev_1.trigger();
end

begin
$display($time, " Waiting for the event to trigger");
ev_1.wait_ptrigger();
$display($time, " Event triggered");
end


join
end

initial begin
#100
$display($time, " Ending simulation");
end
endmodule

运行结果是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 2 16:12 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 Triggering the event
0 Waiting for the event to trigger
0 Event triggered
100 Ending simulation
V C S S i m u l a t i o n R e p o r t
Time: 100
CPU Time: 0.200 seconds; Data structure size: 0.1Mb
Mon Feb 2 16:12:28 2026

如果是把wait_ptrigger改成wait_trigger,结果就会是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 2 16:15 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 Triggering the event
0 Waiting for the event to trigger
100 Ending simulation
V C S S i m u l a t i o n R e p o r t
Time: 100
CPU Time: 0.220 seconds; Data structure size: 0.1Mb
Mon Feb 2 16:15:17 2026

我们可以发现使用wait_ptrigger的就成功接收到的event trigger.

其原因是在同一个时间片内,即使是先被trigger,之后才开始wait,也能被成功接收到,避免race condition;如果我们在wait_ptrigger之前加一个#10,就会发现无法接收到trigger,因为ptrigger并不代表“历史上所有的trigger都能被接收”,仿真中的行为是一过性的,并不会保证已经过去的事件还会被触发,wait_ptrigger只是可以保证等待过程中和触发事件发生在同一时间片内可以被正确处理。

uvm_event_pool

uvm_event_pool存储了uvm_events,如果触发事件和等待事件被触发的两个进程不在同一个组件中,就需要通过uvm_event_pool实现事件的互通。

使用uvm_event_pool::get_global("event_name")就可以获取相关事件的handle.

下面的示例中,comp_a触发了事件ev_ab,comp_b等待事件被触发。

comp_a.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import uvm_pkg::*;
`include "uvm_macros.svh"

class component_a extends uvm_component;
`uvm_component_utils(component_a)

uvm_event ev;

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

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

ev=uvm_event_pool::get_global("ev_ab");
`uvm_info(get_type_name(), $sformatf(" before triggering the event"),UVM_LOW)
#10;

ev.trigger();
`uvm_info(get_type_name(),$sformatf(" after triggering the event"),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase
endclass:component_a

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class component_b extends uvm_component;
`uvm_component_utils(component_b)
uvm_event ev;

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


virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
ev = uvm_event_pool::get_global("ev_ab");
`uvm_info(get_type_name(), $sformatf(" waiting for the event trigger"),UVM_LOW)
ev.wait_trigger();
`uvm_info(get_type_name(), $sformatf(" event got triggered"),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase
endclass:component_b

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class test extends uvm_test;
`uvm_component_utils(test)

component_a comp_a;
component_b comp_b;

function new(string name = "test", uvm_component parent = null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a", this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase


virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass:test

testbench.sv:

1
2
3
4
5
6
7
8
import uvm_pkg::*;
`include "uvm_macros.svh"

module tb;
initial begin
run_test("test");
end
endmodule

run.sv:

1
2
3
4
5
6
7
8
vcs -full64 -sverilog -ntb_opts uvm \
comp_a.sv \
comp_b.sv \
test.sv \
testbench.sv \
-debug_access+all >compile.log

./simv >result.log

运行结果如下:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 2 17:46 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top test - @455
comp_a component_a - @463
comp_b component_b - @471
--------------------------------------
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.comp_a [component_a] before triggering the event
UVM_INFO comp_b.sv(14) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO comp_a.sv(20) @ 10: uvm_test_top.comp_a [component_a] after triggering the event
UVM_INFO comp_b.sv(16) @ 10: uvm_test_top.comp_b [component_b] event got triggered
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 6
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 2
[component_b] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 10
V C S S i m u l a t i o n R e p o r t
Time: 10
CPU Time: 0.260 seconds; Data structure size: 0.4Mb
Mon Feb 2 17:46:46 2026

观查到:

1
2
3
4
5
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.comp_a [component_a]  before triggering the event
UVM_INFO comp_b.sv(14) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO comp_a.sv(20) @ 10: uvm_test_top.comp_a [component_a] after triggering the event
UVM_INFO comp_b.sv(16) @ 10: uvm_test_top.comp_b [component_b] event got triggered
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

成功按照预期完成事件触发和接收。

uvm_event with parameter datatype

在声明uvm_event变量的时候可以通过传入参数决定可以通过trigger传递的data的类型(也就是说可以通过event trigger传递信息)。

1
2
uvm_event#(T)
//T --- user defined data type

T是uvm_object子类,传入不属于uvm_object的数据类型就会出现编译错误。

methods

method description
new create a new event onject
trigger trigger event
get_trigger_data get data provided by last call to trigger(if any)
wait_trigger_data wait_trigger + get_trigger_data
wait_ptrigger_data wait_ptrigger + get_trigger_data

get_trigger_data()返回的是一个object handle.

uvm_event with parameter example

接下来的例子和上面的components之间传递信息几乎一样,不同的是在comp_a中会额外随机生成一个transaction object并且通过trigger传递给comp_b.

有一些关键的部分:

1
2
3
4
ev.trigger(trans) // triggers an event and send transaction.
ev.wait_trigger // wait for an event trigger.
ev.get_trigger_data() // retrive data from event trigger.
$cast(trans, ev.get_trigger_data()) // return type of get_trigger_data is object, $cast used for assignment.

$cast(target variable, source),含义是在运行时进行检查,如果源对象的类型属于目标变量的类型或者是他的子类,就安全地进行赋值

comp_a.sv:

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

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

class component_a extends uvm_component;
`uvm_component_utils(component_a)

uvm_event ev;
transaction trans;

function new(string name, uvm_component parent);
super.new(name, parent);
endfunction:new
//---------------------------------------------------------------------
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

#10;
trans=transaction::type_id::create("trans", this);

trans.randomize();
`uvm_info(get_type_name(), $sformatf(" randomized trans, \n%s",trans.sprint()),UVM_LOW);

ev=uvm_event_pool::get_global("ev_ab");
`uvm_info(get_type_name(),$sformatf(" before triggering the event"),UVM_LOW);

ev.trigger(trans);
`uvm_info(get_type_name(),$sformatf(" after triggering the event"),UVM_LOW);


phase.drop_objection(this);
endtask:run_phase
//----------------------------------------------------------------------
endclass:component_a

comp_b.sv:

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

class component_b extends uvm_component;
`uvm_component_utils(component_b)
uvm_event ev;
transaction trans;

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

//---------------------------------------------------------------------
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

ev=uvm_event_pool::get_global("ev_ab");
`uvm_info(get_type_name(),$sformatf(" waiting for the event trigger"),
UVM_LOW)

ev.wait_trigger();
`uvm_info(get_type_name(), $sformatf(" event got triggered"),UVM_LOW)

$cast(trans, ev.get_trigger_data());
`uvm_info(get_type_name(), $sformatf(" trans received, \n%s",
trans.sprint()),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase
//------------------------------------------------------------------------
endclass:component_b

transaction.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class transaction extends uvm_sequence_item;
rand bit [3:0] addr;
rand bit wr_rd;
rand bit [7:0] wdata;

`uvm_object_utils_begin(transaction)
`uvm_field_int(addr,UVM_ALL_ON)
`uvm_field_int(wr_rd,UVM_ALL_ON)
`uvm_field_int(wdata, UVM_ALL_ON)
`uvm_object_utils_end

function new(string name="transaction");
super.new(name);
endfunction

endclass

运行结果是:

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
65
66
67
68
69
70
71
72
73
74
75
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 2 21:04 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top test - @455
comp_a component_a - @463
comp_b component_b - @471
--------------------------------------
UVM_INFO comp_b.sv(17) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO comp_a.sv(23) @ 10: uvm_test_top.comp_a [component_a] randomized trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @481
addr integral 4 'h9
wr_rd integral 1 'h0
wdata integral 8 'hf5
---------------------------------

UVM_INFO comp_a.sv(26) @ 10: uvm_test_top.comp_a [component_a] before triggering the event
UVM_INFO comp_a.sv(29) @ 10: uvm_test_top.comp_a [component_a] after triggering the event
UVM_INFO comp_b.sv(21) @ 10: uvm_test_top.comp_b [component_b] event got triggered
UVM_INFO comp_b.sv(24) @ 10: uvm_test_top.comp_b [component_b] trans received,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @481
addr integral 4 'h9
wr_rd integral 1 'h0
wdata integral 8 'hf5
---------------------------------

UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 8
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 3
[component_b] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 10
V C S S i m u l a t i o n R e p o r t
Time: 10
CPU Time: 0.220 seconds; Data structure size: 0.4Mb
Mon Feb 2 21:04:53 2026

可以发现在components之间实现了通过event trigger的object传递。

uvm_event array

uvm_event也是可以有数组的,就像systemverilog里面其他数据类型一样,可以有定长数组,动态数组,字典数组。

他们的定义方式如下:

1
2
3
4
5
6
7
8
9
10
11
uvm_event ev[2] // fixed event array, size = 2

uvm_event ev[]; // declare dynamic event array

ev = new[2];
foreach(ev[i]) ev[i] = new();
// create event with size

uvm_event ev[string]; //declare associative event array(key - string).
ev["ev_1"]=new();
ev["ev_2"]=new();

uvm_event_callback

UVM中可以给event添加callback,UVM给trigger添加了pre_trigger和post_trigger作为callback.
我们同样可以写自定义的callback class,也可以override已有的callback,如果一个事件添加了callback,那么在每次事件被触发的时候都会调用一次callback.

basic

依旧是comp_a和comp_b从uvm_event_pool中获取ev_ab,但是这次会在trigger的时候添加pre_trigger和post_trigger.

相关操作如下:

1
2
3
4
5
event_callback ev_cb; //create a event_callback
ev_cb=new("ev_cb");

ev.add_callback(ev_cb); // add ev_cb to event ev

user defined event_callback.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import uvm_pkg::*;
`include "uvm_macros.svh"
class event_callback extends uvm_event_callback;
`uvm_object_utils(event_callback)

function new(string name="event_callback");
super.new(name);
endfunction

virtual function bit pre_trigger(uvm_event e, uvm_object data);
`uvm_info(get_type_name(),$sformatf(" [Callback]Inside event pre_trigger callback"),UVM_LOW)
endfunction

virtual function void post_trigger(uvm_event e, uvm_object data);
`uvm_info(get_type_name(), $sformatf(" [Callback]Inside event post_trigger callback"), UVM_LOW)
endfunction

endclass

在这个callback function中,写bit会返回1/0,在pre_trigger中,如果返回的结果是0,才会允许接下来的event.trigger()执行,如果返回结果是1,就不允许事件被触发,严格来说,函数的最后可以添加一个return 0,不过VCS会继续触发event,是有默认返回0的。

到底是返回1拦截触发还是0这个可能和具体仿真软件有关。

comp_a.sv:

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
class component_a extends uvm_component;
`uvm_component_utils(component_a)

uvm_event ev;
event_callback ev_cb;

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

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
ev_cb=new("ev_cb");

ev=uvm_event_pool::get_global("ev_ab");
ev.add_callback(ev_cb);
`uvm_info(get_type_name(),$sformatf(" Before triggering the event"),UVM_LOW)

#10

ev.trigger();
`uvm_info(get_type_name(),$sformatf(" after triggering the event"),UVM_LOW)


phase.drop_objection(this);
endtask:run_phase
endclass:component_a

comp_b.sv:

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
class component_b extends uvm_component;

`uvm_component_utils(component_b)

uvm_event ev;

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

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

ev = uvm_event_pool::get_global("ev_ab");

`uvm_info(get_type_name(),$sformatf(" waiting for the event trigger"),UVM_LOW)

ev.wait_trigger;

`uvm_info(get_type_name(),$sformatf(" event got triggerd"),UVM_LOW)

phase.drop_objection(this);
endtask : run_phase

endclass : component_b

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

class test extends uvm_test;
`uvm_component_utils(test)

component_a comp_a;
component_b comp_b;

function new(string name = "test", uvm_component parent = null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a", this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase


virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass:test

tb.sv:

1
2
3
4
5
6
7
8
9
import uvm_pkg::*;
`include "uvm_macros.svh"

module tb;
initial begin
run_test("test");
end
endmodule

run.sh:

1
2
3
4
5
6
7
8
9
vcs -full64 -sverilog -ntb_opts uvm \
event_callback.sv \
comp_a.sv \
comp_b.sv \
test.sv \
tb.sv \
-debug_access+all 2>&1 | tee compile.log

./simv 2>&1 | tee result.log

运行结果如下:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 3 15:35 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top test - @455
comp_a component_a - @463
comp_b component_b - @471
--------------------------------------
UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.comp_a [component_a] Before triggering the event
UVM_INFO comp_b.sv(17) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO event_callback.sv(11) @ 10: reporter [event_callback] [Callback]Inside event pre_trigger callback
UVM_INFO event_callback.sv(15) @ 10: reporter [event_callback] [Callback]Inside event post_trigger callback
UVM_INFO comp_a.sv(23) @ 10: uvm_test_top.comp_a [component_a] after triggering the event
UVM_INFO comp_b.sv(21) @ 10: uvm_test_top.comp_b [component_b] event got triggerd
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 8
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 2
[component_b] 2
[event_callback] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 10
V C S S i m u l a t i o n R e p o r t
Time: 10
CPU Time: 0.280 seconds; Data structure size: 0.4Mb
Tue Feb 3 15:35:35 2026

可以发现成功在event trigger中插入了callback.

register two callbacks to a same event

我们可以给同一个event添加多个callback.
event_callback_0.sv:

1
2
3
4
5
6
7
8
9
10
11
class event_callback_0 extends uvm_event_callback;
`uvm_object_utils(event_callback_0)

function new(string name = "event_callback_0");
super.new(name);
endfunction

virtual function void post_trigger(uvm_event e, uvm_object data);
`uvm_info(get_type_name(), $sformatf(" [Callback 0] inside event post_trigger callback"),UVM_LOW)
endfunction
endclass

event_callback_1.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class event_callback_1 extends uvm_event_callback;
`uvm_object_utils(event_callback_1)

function new(string name="event_callback_1");
super.new(name);
endfunction

virtual function bit pre_trigger(uvm_event e, uvm_object data);
`uvm_info(get_type_name(), $sformatf(" [Callback 1]inside event pre_trigger callback"),UVM_LOW)
endfunction

virtual function void post_trigger(uvm_event e, uvm_object data);
`uvm_info(get_type_name(),$sformatf(" [callback 1]inside event post_trigger callback"),UVM_LOW)
endfunction
endclass

comp_a.sv:

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
class component_a extends uvm_component;
`uvm_component_utils(component_a)

uvm_event ev;
event_callback_0 ev_cb_0;
event_callback_1 ev_cb_1;

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

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
ev_cb_0 = new("ev_cb_0");
ev_cb_1 = new("ev_cb_1");
ev=uvm_event_pool::get_global("ev_ab");

ev.add_callback(ev_cb_0);
ev.add_callback(ev_cb_1);

`uvm_info(get_type_name(),$sformatf(" before triggering the event"),UVM_LOW)

#10
ev.trigger();

`uvm_info(get_type_name(),$sformatf(" after triggering the event"),UVM_LOW)

phase.drop_objection(this);
endtask:run_phase
endclass:component_a

运行结果如下:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 3 17:20 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top test - @455
comp_a component_a - @463
comp_b component_b - @471
--------------------------------------
UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.comp_a [component_a] before triggering the event
UVM_INFO comp_b.sv(17) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO event_callback_1.sv(10) @ 10: reporter [event_callback_1] [Callback 1]inside event pre_trigger callback
UVM_INFO event_callback_0.sv(11) @ 10: reporter [event_callback_0] [Callback 0] inside event post_trigger callback
UVM_INFO event_callback_1.sv(14) @ 10: reporter [event_callback_1] [callback 1]inside event post_trigger callback
UVM_INFO comp_a.sv(27) @ 10: uvm_test_top.comp_a [component_a] after triggering the event
UVM_INFO comp_b.sv(21) @ 10: uvm_test_top.comp_b [component_b] event got triggerd
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 9
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 2
[component_b] 2
[event_callback_0] 1
[event_callback_1] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 10
V C S S i m u l a t i o n R e p o r t
Time: 10
CPU Time: 0.330 seconds; Data structure size: 0.4Mb
Tue Feb 3 17:20:54 2026

register callback in tastcase

在有时我们要求在test.sv中为event添加callback而不是在comp_a.sv中,这个时候就要这么写:

test.sv:

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
class test extends uvm_test;
`uvm_component_utils(test)

event_callback ev_cb;
uvm_event ev;

component_a comp_a;
component_b comp_b;

function new(string name = "test", uvm_component parent = null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);

comp_a = component_a::type_id::create("comp_a",this);
comp_b = component_b::type_id::create("comp_b",this);

ev_cb=new("ev_cb");

ev=uvm_event_pool::get_global("ev_ab");

ev.add_callback(ev_cb);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass:test

comp_a.sv:

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
class component_a extends uvm_component;

`uvm_component_utils(component_a)

uvm_event ev;

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

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

ev = uvm_event_pool::get_global("ev_ab");

`uvm_info(get_type_name(),$sformatf(" Before triggering the event"),UVM_LOW)
#10;

ev.trigger();

`uvm_info(get_type_name(),$sformatf(" After triggering the event"),UVM_LOW)

phase.drop_objection(this);
endtask : run_phase

endclass : component_a

comp_b.sv:

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
class component_b extends uvm_component;

`uvm_component_utils(component_b)

uvm_event ev;

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

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

ev = uvm_event_pool::get_global("ev_ab");

`uvm_info(get_type_name(),$sformatf(" waiting for the event trigger"),UVM_LOW)

ev.wait_trigger;

`uvm_info(get_type_name(),$sformatf(" event got triggerd"),UVM_LOW)

phase.drop_objection(this);
endtask : run_phase

endclass : component_b

run.sh:

1
2
3
4
5
6
7
8
vcs -full64 -sverilog -ntb_opts uvm \
event_callback.sv \
comp_a.sv \
comp_b.sv \
test.sv \
tb.sv \
-debug_access+all 2>&1 | tee compile.log
./simv 2>&1 | tee result.log

运行结果如下:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 3 18:00 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
--------------------------------------
Name Type Size Value
--------------------------------------
uvm_test_top test - @455
comp_a component_a - @463
comp_b component_b - @471
--------------------------------------
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.comp_a [component_a] Before triggering the event
UVM_INFO comp_b.sv(17) @ 0: uvm_test_top.comp_b [component_b] waiting for the event trigger
UVM_INFO event_callback.sv(12) @ 10: reporter [event_callback] [Callback]Inside event pre_trigger callback
UVM_INFO event_callback.sv(16) @ 10: reporter [event_callback] [Callback]Inside event post_trigger callback
UVM_INFO comp_a.sv(22) @ 10: uvm_test_top.comp_a [component_a] After triggering the event
UVM_INFO comp_b.sv(21) @ 10: uvm_test_top.comp_b [component_b] event got triggerd
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 10: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 8
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 2
[component_b] 2
[event_callback] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 10
V C S S i m u l a t i o n R e p o r t
Time: 10
CPU Time: 0.260 seconds; Data structure size: 0.4Mb
Tue Feb 3 18:00:54 2026

实现了在test里面完成add callback(实际上之前很多组件连接也是类似这样的方法)。

TLM

Transaction Level Modeling实现的是基于transaction的模块间通信。

UVM提供了TLM的一系列接口,其中TLM interface一般包括发送和接收transaction的method,通过TLM port我们可以连接不同的组件。

UVM里提供了不同的通信类型:

  • TLM1: 本质是函数调用,transaction按值(value)传递;

  • TLM2: 这是有具体事务生命周期的信息传递,有时间概念,事务状态等具体信息;

  • sequencer port: 这是driver和sequencer的专用通信机制;

  • analysis port: 广播通信。

TLM1

TLM1几乎完全是一个函数调用,现在验证需要数据流,解耦,可扩展等特性,它没有数据流模型,不能广播,其实比较少用。

UVM TLM1提供了TLM interfaces, ports, exports, imp ports, analysis ports, FIFOs, 这些都有双向和单向的;TLM interface有阻塞,非阻塞,混合三种。

阻塞的TLM interface是指这里面的TLM method一定要等到当前操作处理完毕才有返回,非阻塞传输transaction会立即返回,混合的interface会同时提供支持这两种方式的函数。

TLM interfaces

TLM interface里面会有很多需要用到的method.

method description
.put(trans) send transaction to another component, blocking
.get(trans) retrive&take(consume) transaction from another component, blocking
.peek(trans) obtain a copy(not consume) of transaction, blocking
.try_put(trans) send trans to another component(if possible), non-blocking. return 1:target is ready, otherwise return 0.
.can_put() if this component is ready to accept trans return 1, otherwise 0. no parameter necessary
.try_get(trans) retrive&take trans from another component, non-blocking. if target is available return 1, otherwise return 0.
.can_get() if this component can get trans immediately return 1, otherwise return 0.
.transport(req, resp) execute req, return response in resp, blocking
.nb_transport(req, resp) if can not executed immediately, return 0, otherwise return 1
.write(trans) broadcast a trans to any number of listeners, non-blocking

TLM ports

TLM port用来发送trans,有单向双向两种,可以连接到所有适配的port, export, imp
port.

TLM port的库内定义如下:

1
2
uvm_*_port#(T)
uvm_*_port#(REQ,RSP)

一般称第一个为单向,第二个为双向,T, REQ, RESP都是对应位置参数的类型。

TLM exports

export是接口转发器,可以连接到所有适配的child export或者是imp port。一个可能的连接是:port -> export -> imp.
比如说我们写:env.port -> agent.export -> driver.imp,含义就是env端口发出信号,agent转发给driver,driver实际执行。

在这个过程中,允许export连接child export,比如port -> export -> (child)export -> imp,只是最后应该连接到至少一个imp,因为他才真正实现函数功能。

TLM export的库内定义如下:

1
2
uvm_*_export#(T)
uvm_*_export#(REQ,RESP)

TLM Imp ports

TLM imp port在发送trans的终点接收它,TLM imp port也有双向和单向。

TLM imp port的库内定义如下:

1
2
uvm_*_imp#(T,IMP)
uvm_*_imp#(REQ,RESP,IMP,REQ_IMP,RESP_IMP)

IMP代表的是实现这个接口的组件的具体类型,这个imp将会委托给这个类型的一个instance;
REQ_IMP代表的是实现接口请求端的组件的类型,默认是IMP, 只适用于主从IMP;
RSP_IMP代表的是接口响应端的组件的类型,同样默认是IMP, 只适用于主从IMP.

TLM FIFO

TLM FIFO提供了两个独立进程之间trans交换的缓冲存储。
通过put_export method可以将trans存入FIFO,通过get_peek_export取出。

除了new之外,FIFO还有一些method:

method description
size() return size of FIFO; 0 mean no limit
used return the number of entries put into FIFO
is_empty return 1 if FIFO is empty, otherwise 0
is_full return 1 if FIFO is full, otherwise 0
flush remove all entries from FIFO

TLM analysis FIFO

analysis FIFO就是无限长度上限并且具有write method的FIFO.

TLM communication

可以接受的连接方式有:

  • port -> imp
  • port -> port
  • port -> export
  • export -> export
  • export -> imp

TLM example

port -> imp port

假设现在有component_a和component_b,a实现transaction的随机初始化并且通过TLM发送给b.

首先要在component_a中声明并且创建一个阻塞的put port,

1
2
uvm_blocking_put_port#(transaction) trans_out;
trans_out=new("trans_out", this);

然后在component_b中创建TLM put imp port:

1
2
uvm_blocking_put_imp#(transaction, component_b) trans_in;
trans_in=new("trans_in", this);

在这里给method起的名字在中间,put,且前后需要一致。
命名规则是:uvm_<blocking_type>_<method_name>_<port_type>

在env中将TLM port和imp port连接:

1
2
3
function void connect_phase(uvm_phase phase);
comp_a.trans_out.connect(comp_b.trans_in);
endfunction:connect_phase

连接完成之后,需要在component_a中随机化trans并将其发送给component_b:

1
2
3
4
5
6
7
8
virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

trans=transaction::type_id::create("trans",this);
void'(trans.randomize()); // call method:randomize, ignore return value.
trans_out.put(trans);
phase.drop_objection(this);
endtask:run_phase

在component_b中接收trans并执行操作:

1
2
3
virtual task put(transaction trans);
`uvm_info(get_type_name(), $sformatf(" received trans on IMP port", UVM_LOW))
`uvm_info(get_type_name(), $sformatf(" print trans, \n%s", trans.print()),UVM_LOW)

明明是接收trans,怎么叫put呢?因为在UVM中一般名字是从发送方的视角命名的(接口是给发送方设计的),而且前后接口method名应该一致(发送端和接收端用的应该是同一个接口)。

完整代码如下:

transaction.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class transaction extends uvm_sequence_item;
rand bit [3:0] addr;
rand bit wr_rd;
rand bit [3:0] wdata;

`uvm_object_utils_begin(transaction)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(wr_rd, UVM_ALL_ON)
`uvm_field_int(wdata,UVM_ALL_ON)
`uvm_object_utils_end

function new(string name="transaction");
super.new(name);
endfunction
endclass

component_a.sv:

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
class component_a extends uvm_component;
transaction trans;
uvm_blocking_put_port#(transaction) trans_out;

`uvm_component_utils(component_a)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_out=new("trans_out", this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

trans=transaction::type_id::create("trans",this);
void'(trans.randomize());
`uvm_info(get_type_name(), $sformatf(" transaction randomized"), UVM_LOW)
trans.print();

`uvm_info(get_type_name(), $sformatf(" before calling port put method"),UVM_LOW)
trans_out.put(trans);
`uvm_info(get_type_name(), $sformatf(" after calling port put method"),
UVM_LOW)
phase.drop_objection(this);
endtask:run_phase
endclass:component_a

component_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class component_b extends uvm_component;
transaction trans;

uvm_blocking_put_imp#(transaction, component_b) trans_in;

`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in", this);
endfunction:new

virtual task put(transaction trans);
`uvm_info(get_type_name(), $sformatf(" received trans on imp port"), UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" printing trans \n%s",trans.sprint()),UVM_LOW)
endtask:put
endclass:component_b

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);

comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.trans_out.connect(comp_b.trans_in);
endfunction:connect_phase
endclass:environment

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test extends uvm_test;
`uvm_component_utils(test)

environment env;

function new(string name="test", uvm_component parent=null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env=environment::type_id::create("env", this);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass:test

testbench.sv:

1
2
3
4
5
6
7
8
import uvm_pkg::*;
`include "uvm_macros.svh"

module tb;
initial begin
run_test("test");
end
endmodule

run.sh:

1
2
3
4
5
6
7
8
9
vcs -full64 -sverilog -ntb_opts uvm \
transaction.sv \
comp_a.sv \
comp_b.sv \
env.sv \
test.sv \
testbench.sv \
-debug_access+all 2>&1 | tee compile.log
./simv 2>&1 | tee result.log

运行结果是:

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
65
66
67
68
69
70
71
72
73
74
75
76
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 6 23:15 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
---------------------------------------------------
Name Type Size Value
---------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_blocking_put_port - @479
comp_b component_b - @488
trans_in uvm_blocking_put_imp - @496
---------------------------------------------------
UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 4 'h9
---------------------------------
UVM_INFO comp_a.sv(21) @ 0: uvm_test_top.env.comp_a [component_a] before calling port put method
UVM_INFO comp_b.sv(15) @ 0: uvm_test_top.env.comp_b [component_b] received trans on imp port
UVM_INFO comp_b.sv(16) @ 0: uvm_test_top.env.comp_b [component_b] printing trans
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 4 'h9
---------------------------------

UVM_INFO comp_a.sv(23) @ 0: uvm_test_top.env.comp_a [component_a] after calling port put method
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 7
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 3
[component_b] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.410 seconds; Data structure size: 0.4Mb
Fri Feb 6 23:15:07 2026

为了证明这个过程是阻塞的,可以在comp_b.sv中添加一段延时:

1
2
3
4
5
6
7
8
9
10
virtual task put(transaction trans);
`uvm_info(get_type_name(), $sformatf(" received trans on IMP port"),UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" before injecting delay"),UVM_LOW)

#100;

`uvm_info(get_type_name(), $sformatf(" after injecting delay"),UVM_LOW)

`uvm_info(get_type_name(), $sformatf(" printing trans \n%s", trans.sprint()),UVM_LOW)
endtask:put

之后在运行:

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

UVM_INFO @ 0: reporter [RNTST] Running test test...
---------------------------------------------------
Name Type Size Value
---------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_blocking_put_port - @479
comp_b component_b - @488
trans_in uvm_blocking_put_imp - @496
---------------------------------------------------
UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 4 'h9
---------------------------------
UVM_INFO comp_a.sv(21) @ 0: uvm_test_top.env.comp_a [component_a] before calling port put method
UVM_INFO comp_b.sv(16) @ 0: uvm_test_top.env.comp_b [component_b] received trans on IMP port
UVM_INFO comp_b.sv(17) @ 0: uvm_test_top.env.comp_b [component_b] before injecting delay
UVM_INFO comp_b.sv(21) @ 100: uvm_test_top.env.comp_b [component_b] after injecting delay
UVM_INFO comp_b.sv(23) @ 100: uvm_test_top.env.comp_b [component_b] printing trans
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 4 'h9
---------------------------------

UVM_INFO comp_a.sv(23) @ 100: uvm_test_top.env.comp_a [component_a] after calling port put method
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 100: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

我们可以看到com_a收到信息完成输出uvm_test_top.env.comp_a [component_a] after calling port put method是在仿真时间100,也就是证明他是被阻塞的。

nonblocking port -> imp port

如果想要实现非阻塞port和imp port的连接,步骤上和上面的差不多,在comp_a中添加的port应该改成:

1
uvm_nonblocking_put_port#(transaction) trans_out;

comp_b中的端口应该是:

1
uvm_nonblocking_put_imp#(transaction, component_b) trans_in;

还有需要注意的是non-blocking的端口应该使用非阻塞的method(这里需要满足的条件是不应该消耗仿真时间),所以在comp_a发送trans的时候要改成try_put:

1
2
void'(trans.randomize());
trans_out.try_put(trans);

对于comp_b来说,他需要try_putcan_put 两个函数

1
2
3
4
5
6
7
8
virtual function try_put(transaction trans);
`uvm_info(get_type_name(), $sformatf(" inside try_put method"),UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" received trans on IMP port"),UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" printing trans \n%s", trans.sprint()),UVM_LOW)
endfunction:try_put

virtual function bit can_put();
endfunction

在这个过程中,comp_b中两个函数都是自己写的,comp_a的try_put()是在调用comp_b的函数;,comp_a中定义的是port,他的作用是发出控制信号,comp_b是implement,具体实现是什么逻辑,需要自己来写。
这里需要有返回,是因为try_put是非阻塞的,虽然不会等执行结束,但是需要告知port是否成功接收请求。

仿真时,执行trans_out.try_put()执之前要先在comp_b中调用trans_out.can_put()来确认现在能否接收trans.在synopsys 2018工具中,如果返回0,就说明可以接收,如果返回的是1,说明此时无法接收trans,就什么都不会发生,并不会自动重试。

一个手动调用can_put()的方式是在comp_a中,try_put()之前先进行一次尝试(当然要在comp_b.sv中提前写好method):

1
2
3
4
5
6
if(trans_out.can_put) begin
........
end
esle begin
........
end

comp_a.sv:

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
class component_a extends uvm_component;
transaction trans;

uvm_nonblocking_put_port#(transaction) trans_out;

`uvm_component_utils(component_a)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_out=new("trans_out", this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
trans=transaction::type_id::create("trans", this);
void'(trans.randomize());

`uvm_info(get_type_name(), $sformatf(" transaction randomized"),UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" printing trans, \n%s", trans.sprint()), UVM_LOW)

`uvm_info(get_type_name(), $sformatf(" before calling port put method"),UVM_LOW)
trans_out.try_put(trans);
`uvm_info(get_type_name(), $sformatf(" after calling port put method"),UVM_LOW)

phase.drop_objection(this);
endtask:run_phase
endclass:component_a

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class component_b extends uvm_component;
transaction trans;
uvm_nonblocking_put_imp#(transaction,component_b) trans_in;

`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name,parent);
trans_in=new("trans_in",this);
endfunction:new

virtual function bit try_put(transaction trans);
`uvm_info(get_type_name(), $sformatf(" inside try_put method"),UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" received trans on IMP port"),UVM_LOW)
`uvm_info(get_type_name(), $sformatf(" printing trans, \n%s",trans.sprint()),UVM_LOW)

endfunction:try_put

virtual function bit can_put();
endfunction
endclass:component_b

运行结果如下:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 7 22:37 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
------------------------------------------------------
Name Type Size Value
------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_nonblocking_put_port - @479
comp_b component_b - @488
trans_in uvm_nonblocking_put_imp - @496
------------------------------------------------------
UVM_INFO comp_a.sv(19) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized
UVM_INFO comp_a.sv(20) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 4 'h9
---------------------------------

UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] before calling port put method
UVM_INFO comp_b.sv(14) @ 0: uvm_test_top.env.comp_b [component_b] inside try_put method
UVM_INFO comp_b.sv(15) @ 0: uvm_test_top.env.comp_b [component_b] received trans on IMP port
UVM_INFO comp_b.sv(16) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 4 'h9
---------------------------------

UVM_INFO comp_a.sv(24) @ 0: uvm_test_top.env.comp_a [component_a] after calling port put method
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 9
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.280 seconds; Data structure size: 0.4Mb
Sat Feb 7 22:37:39 2026

port -> export -> imp port

这次进行的端口连接中间加了一个export端口,具体逻辑是,comp_a设置port,将信号传递给comp_b,comp_b又设置export作为中转,将信号发给具有imp port的sub_comp_b.

对于comp_a,逻辑和之前的差不多,创建port并随机化之后发给comp_b即可;comp_b则需要创建export:

1
2
3
4
5
6
uvm_blocking_put_export#(transaction) trans_in;

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in", this);
endfunction:new

之后再连接到sub_comp_b的imp port端口:

1
trans_in.connect(sub_comp_b.trans_in);

这里comp_b和sub_comp_b中都命名为trans_in,相对于comp_a发出的trans来说,这两个组件都是接收trans。

在sub_comp_b中需要创建imp port,并且在这里写好put()的逻辑实现:

1
2
3
4
5
6
7
8
9
10
uvm_blocking_put_imp#(transaction,sub_comp_b) trans_in;

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in", this);
endfunction:new

virtual task put(transaction trans);
......
endtask:put

最后由于export -> imp port的连接已经在comp_b中写好了,所以env中只写comp_a到comp_b的端口连接即可。

代码如下:

comp_a.sv:

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
class component_a extends uvm_component;
transaction trans;
uvm_blocking_put_port#(transaction) trans_out;

`uvm_component_utils(component_a)

function new(string name, uvm_component parent);
super.new(name,parent);
trans_out=new("trans_out", this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
trans=transaction::type_id::create("trans",this);

void'(trans.randomize());

`uvm_info(get_type_name(),$sformatf(" transaction randomized"),UVM_LOW)
`uvm_info(get_type_name(),$sformatf(" Printing trans, \n %s",trans.sprint()),UVM_LOW)

`uvm_info(get_type_name(),$sformatf(" Before calling port put method"),UVM_LOW)
trans_out.put(trans);
`uvm_info(get_type_name(),$sformatf(" After calling port put method"),UVM_LOW)

phase.drop_objection(this);
endtask:run_phase
endclass:component_a

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class component_b extends uvm_component;
sub_component_b sub_comp_b;
uvm_blocking_put_export#(transaction) trans_in;

`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name,parent);
trans_in=new("trans_in",this);
endfunction:new

function void build_phase(uvm_phase phase);
super.build_phase(phase);

sub_comp_b=sub_component_b::type_id::create("sub_comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
trans_in.connect(sub_comp_b.trans_in);
endfunction:connect_phase
endclass:component_b

sub_comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class sub_component_b extends uvm_component;
transaction trans;
uvm_blocking_put_imp#(transaction, sub_component_b) trans_in;

`uvm_component_utils(sub_component_b)

function new(string name, uvm_component parent);
super.new(name,parent);
trans_in=new("trans_in",this);
endfunction:new

virtual task put(transaction trans);
`uvm_info(get_type_name(),$sformatf(" received trans On IMP Port"),UVM_LOW)
#100;
`uvm_info(get_type_name(),$sformatf(" Printing trans, \n %s",trans.sprint()),UVM_LOW)
endtask:put

endclass:sub_component_b

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);

comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.trans_out.connect(comp_b.trans_in);
endfunction:connect_phase
endclass:environment

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test extends uvm_test;
`uvm_component_utils(test);

environment env;

function new(string name="test",uvm_component parent=null);
super.new(name,parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env=environment::type_id::create("env",this);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass:test

testbench.sv:

1
2
3
4
5
6
7
8
import uvm_pkg::*;
`include "uvm_macros.svh"

module tb;
initial begin
run_test("test");
end
endmodule

run.sh:

1
2
3
4
5
6
7
8
9
10
11
vcs -full64 -sverilog -ntb_opts uvm \
transaction.sv \
comp_a.sv \
sub_comp_b.sv \
comp_b.sv \
env.sv \
test.sv \
testbench.sv \
-debug_access+all 2>&1 | tee compile.log
./simv 2>&1 | tee result.log

运行结果是:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 9 22:18 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
------------------------------------------------------
Name Type Size Value
------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_blocking_put_port - @479
comp_b component_b - @488
sub_comp_b sub_component_b - @505
trans_in uvm_blocking_put_imp - @513
trans_in uvm_blocking_put_export - @496
------------------------------------------------------
UVM_INFO comp_a.sv(19) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized
UVM_INFO comp_a.sv(20) @ 0: uvm_test_top.env.comp_a [component_a] Printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @522
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] Before calling port put method
UVM_INFO sub_comp_b.sv(14) @ 0: uvm_test_top.env.comp_b.sub_comp_b [sub_component_b] received trans On IMP Port
UVM_INFO sub_comp_b.sv(16) @ 100: uvm_test_top.env.comp_b.sub_comp_b [sub_component_b] Printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @522
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(24) @ 100: uvm_test_top.env.comp_a [component_a] After calling port put method
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 100: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 8
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[sub_component_b] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 100
V C S S i m u l a t i o n R e p o r t
Time: 100
CPU Time: 0.290 seconds; Data structure size: 0.4Mb
Mon Feb 9 22:18:46 2026

这样就完成了一次经过export的TLM port连接。

小结

对于以上简单的连接,基本实现方法是,在产生trans的组件处完成初始化并向后发送,中间的组件需要将自己的端口连接到上一步操作的端口,当trans被发送到imp port之后由最后的组件完成实际操作。

get

之前的都是put,对于get来说,就是逻辑正好反过来,比如我们在comp_b中设置port,在comp_a中设置imp port,首先是comp_b运行get(),(comp_b)调用comp_a中写好的get()创建新trans,返回给comp_b.

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class component_b extends uvm_component;
transaction trans;
uvm_blocking_get_port#(transaction) trans_in;

`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info(get_type_name(), $sformatf("requesting transaction."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("before calling port get method."),UVM_LOW)
trans_in.get(trans);
`uvm_info(get_type_name(), $sformatf("after calling port get method."),UVM_LOW)
trans.print();
phase.drop_objection(this);

endtask:run_phase
endclass:component_b

comp_a.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class component_a extends uvm_component;
uvm_blocking_get_imp#(transaction,component_a) trans_out;
`uvm_component_utils(component_a)

function new(string name, uvm_component parent);
super.new(name,parent);
trans_out=new("trans_out", this);
endfunction:new

virtual task get(output transaction trans);
`uvm_info(get_type_name(), $sformatf("received transaction imp port get request"),UVM_LOW)
trans=transaction::type_id::create("trans",this);

void'(trans.randomize());

`uvm_info(get_type_name(),$sformatf(" transaction randomized"),UVM_LOW)
`uvm_info(get_type_name(),$sformatf(" Printing trans, \n %s",trans.sprint()),UVM_LOW)

`uvm_info(get_type_name(),$sformatf(" Sendting trans packet"),UVM_LOW)
endtask:get
endclass:component_a

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_b.trans_in.connect(comp_a.trans_out);
endfunction:connect_phase
endclass:environment

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class test extends uvm_test;
`uvm_component_utils(test);
environment env;

function new(string name="test",uvm_component parent=null);
super.new(name,parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);

env=environment::type_id::create("env",this);

endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass:test

testbench.sv:

1
2
3
4
5
6
7
8
9
import uvm_pkg::*;
`include "uvm_macros.svh"

module tb;
initial begin
run_test("test");
end
endmodule

run.sh:

1
2
3
4
5
6
7
8
9
vcs -full64 -sverilog -ntb_opts uvm \
transaction.sv \
comp_a.sv \
comp_b.sv \
env.sv \
test.sv \
testbench.sv \
-debug_access+all 2>&1 | tee compile.log
./simv 2>&1 | tee result.log

运行结果如下:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 10 22:56 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
---------------------------------------------------
Name Type Size Value
---------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_blocking_get_imp - @479
comp_b component_b - @488
trans_in uvm_blocking_get_port - @496
---------------------------------------------------
UVM_INFO comp_b.sv(15) @ 0: uvm_test_top.env.comp_b [component_b] requesting transaction.
UVM_INFO comp_b.sv(16) @ 0: uvm_test_top.env.comp_b [component_b] before calling port get method.
UVM_INFO comp_a.sv(12) @ 0: uvm_test_top.env.comp_a [component_a] received transaction imp port get request
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] tranaction randomized
UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] Printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'h4
wr_rd integral 1 'h0
wdata integral 8 'hd0
---------------------------------

UVM_INFO comp_a.sv(20) @ 0: uvm_test_top.env.comp_a [component_a] Sendting trans packet
UVM_INFO comp_b.sv(18) @ 0: uvm_test_top.env.comp_b [component_b] after calling port get method.
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'h4
wr_rd integral 1 'h0
wdata integral 8 'hd0
---------------------------------
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 9
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.300 seconds; Data structure size: 0.4Mb
Tue Feb 10 22:56:35 2026

systemverilog parameter direction

这一块好像没写过,就作为之前的systemverilog的补充。
这里类似comp_a.sv中的get函数中有一个output参数,他是用来规定参数传递方向(和读写限制的),方向有四种:input, output, inout, ref.

具体需要关注到的特点有这几个:

类型 进入函数时 在函数内部 退出函数时 内存
input 传入复制 只读 不回传 不共享
output 不传入复制 可以new和修改 回传 不共享
inout 传入复制 可以修改 回传 不共享
ref alias 真引用/直接操作原变量 实时同步 共享

传入复制的含义是:对于基本类型,传入一个一模一样的复制,对于class,传入的是句柄复制(浅复制)。

不传入复制:进入函数时,原值抛弃,变成一个声明但是未定义的状态。

回传:指函数结束之后将变量复制(同样是对于基本类型完整复制,对于class则是handle复制)回调用者。

对于上面的回传,可以看到两个都是@505,说明不是传回了一个深复制(相当于创建一个新对象),而是只将handle复制传回,其本质是同一个对象。

nonblocking get

和之前非阻塞的put一样的是需要换成try_get等非阻塞的method.

在comp_b中non-blocking get port声明,创建,调用方式:

1
2
3
4
5
uvm_nonblocking_get_port#(transaction) trans_in;

trans_in=new("trans_in",this);

trans_in.try_get(trans)

在comp_a中nonblocking get imp port的声明,创建,try_get()的逻辑,以及can_get():

1
2
3
4
5
6
7
8
9
10
11
uvm_nonblocking_get_imp#(transaction,comp_a) trans_out;

trans_out=new("trans_out",this);

virtual function bit try_get(output transaction trans);
.........
return 0;
endfunction

virtual function bit can_get();
endfunction

此处依旧是当comp_b尝试调用trans_in.try_get()之前,会先通过tran_in.can_get()来检查comp_a是否处于可以发送trans的状态,如果返回0,则说明可以发送,是1就不会发送。在实际执行trans_in.try_get()之前可以手动调用:trans_in.can_get().

comp_b.sv:

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
class component_b extends uvm_component;
transaction trans;
uvm_nonblocking_get_port#(transaction) trans_in;

`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info(get_type_name(),$sformatf("requesting transaction."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("before calling try_get method."),UVM_LOW)

if(trans_in.try_get(trans)) begin
`uvm_info(get_type_name(),$sformatf("received transaction."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans \n%s",trans.sprint()),UVM_LOW)

end
else
`uvm_info(get_type_name(), $sformatf("not received transaction."),UVM_LOW)

`uvm_info(get_type_name(),$sformatf("after calling try_get method."),UVM_LOW)

phase.drop_objection(this);

endtask:run_phase
endclass:component_b

comp_a.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class component_a extends uvm_component;
uvm_nonblocking_get_imp#(transaction,component_a) trans_out;
`uvm_component_utils(component_a)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_out=new("trans_out",this);
endfunction:new

virtual function bit try_get(output transaction trans);
`uvm_info(get_type_name(), $sformatf("received transaction imp port get request."),UVM_LOW)
trans=transaction::type_id::create("trans",this);
void'(trans.randomize());

`uvm_info(get_type_name(),$sformatf("transaction randomized."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s", trans.sprint()),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("sending trans."),UVM_LOW)
return 1;
endfunction:try_get

virtual function bit can_get();
endfunction
endclass:component_a

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_b.trans_in.connect(comp_a.trans_out);
endfunction:connect_phase
endclass:environment

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test extends uvm_test;
`uvm_component_utils(test)

environment env;

function new(string name="test", uvm_component parent=null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env=environment::type_id::create("env",this);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass: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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 12 21:02 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
------------------------------------------------------
Name Type Size Value
------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_nonblocking_get_imp - @479
comp_b component_b - @488
trans_in uvm_nonblocking_get_port - @496
------------------------------------------------------
UVM_INFO comp_b.sv(15) @ 0: uvm_test_top.env.comp_b [component_b] requesting transaction.
UVM_INFO comp_b.sv(16) @ 0: uvm_test_top.env.comp_b [component_b] before calling try_get method.
UVM_INFO comp_a.sv(12) @ 0: uvm_test_top.env.comp_a [component_a] received transaction imp port get request.
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'h4
wr_rd integral 1 'h0
wdata integral 8 'hd0
---------------------------------

UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] sending trans.
UVM_INFO comp_b.sv(19) @ 0: uvm_test_top.env.comp_b [component_b] received transaction.
UVM_INFO comp_b.sv(20) @ 0: uvm_test_top.env.comp_b [component_b] printing trans
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @505
addr integral 4 'h4
wr_rd integral 1 'h0
wdata integral 8 'hd0
---------------------------------

UVM_INFO comp_b.sv(26) @ 0: uvm_test_top.env.comp_b [component_b] after calling try_get method.
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 11
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 5
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.340 seconds; Data structure size: 0.4Mb
Thu Feb 12 21:02:40 2026

TLM FIFO

FIFO可以给两个独立进程之间提供trans存储。如果要在comp_a和comp_b中间设置一个FIFO,可以在FIFO设立两个export端口,分别连接到comp_a和comp_b中的port.

假设comp_a生产并发送trans,comp_b接收,在comp_a中需要设置uvm_blocking_put_port#(transaction) trans_out,随机初始化一个trans之后在进行trans_out.put(trans)发往FIFO;在comp_b中设置uvm_blocking_get_port#(transaction) trans_in,通过trans_in.get(trans)从FIFO获取trans.

对于FIFO,首先声明并创建TLM FIFO:

1
2
uvm_tlm_fifo#(transaction) fifo_ab;
fifo_ab=new("fifo_ab",this);

之后连接相应端口:

1
2
comp_a.trans_out.connect(fifo_ab.put_export);
comp_b.trans_in.connect(fifo_ab.get_export);

uvm_tlm_fifo自带put_export和get_export.
其实在put侧有fifo.put_export, fifo.try_put_export, fifo_can_put_export, 在get侧有fifo.get_export, fifo.try_get_export, fifo.can_get_export, fifo.peek_export, 在analysis侧还有fifo.analysis_export.

comp_a.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class component_a extends uvm_component;
transaction trans;
`uvm_component_utils(component_a)
uvm_blocking_put_port#(transaction) trans_out;

function new(string name, uvm_component parent);
super.new(name, parent);
trans_out=new("trans_out", this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
trans=transaction::type_id::create("trans",this);
void'(trans.randomize());

`uvm_info(get_type_name(), $sformatf("transaction randomized."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("before calling put method."),UVM_LOW)
trans_out.put(trans);
`uvm_info(get_type_name(), $sformatf("after calling port put method."),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase

endclass:component_a

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class component_b extends uvm_component;
transaction trans;
uvm_blocking_get_port#(transaction) trans_in;
`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
`uvm_info(get_type_name(), $sformatf("before calling port get method."),UVM_LOW)
trans_in.get(trans);
`uvm_info(get_type_name(), $sformatf("after calling port get method."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s", trans.sprint()),UVM_LOW)

phase.drop_objection(this);
endtask:run_phase
endclass:component_b

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;
uvm_tlm_fifo#(transaction) fifo_ab;
`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
fifo_ab=new("fifo_ab",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.trans_out.connect(fifo_ab.put_export);
comp_b.trans_in.connect(fifo_ab.get_export);
endfunction:connect_phase
endclass:environment

test.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class test extends uvm_test;
`uvm_component_utils(test)
environment env;

function new(string name="test", uvm_component parent=null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env=environment::type_id::create("env",this);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration
endclass: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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 12 23:30 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
---------------------------------------------------------
Name Type Size Value
---------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_blocking_put_port - @479
comp_b component_b - @488
trans_in uvm_blocking_get_port - @496
fifo_ab uvm_tlm_fifo #(T) - @505
get_ap uvm_analysis_port - @540
get_peek_export uvm_get_peek_imp - @522
put_ap uvm_analysis_port - @531
put_export uvm_put_imp - @513
---------------------------------------------------------
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(19) @ 0: uvm_test_top.env.comp_a [component_a] before calling put method.
UVM_INFO comp_a.sv(21) @ 0: uvm_test_top.env.comp_a [component_a] after calling port put method.
UVM_INFO comp_b.sv(14) @ 0: uvm_test_top.env.comp_b [component_b] before calling port get method.
UVM_INFO comp_b.sv(16) @ 0: uvm_test_top.env.comp_b [component_b] after calling port get method.
UVM_INFO comp_b.sv(17) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 9
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.200 seconds; Data structure size: 0.4Mb
Thu Feb 12 23:30:07 2026

为了观察阻塞特性可以把comp_a.sv和comp_b.sv做修改:

comp_a.sv:

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
class component_a extends uvm_component;
transaction trans, transs;
`uvm_component_utils(component_a)
uvm_blocking_put_port#(transaction) trans_out;

function new(string name, uvm_component parent);
super.new(name, parent);
trans_out=new("trans_out", this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
trans=transaction::type_id::create("trans",this);
transs=transaction::type_id::create("transs",this);
void'(trans.randomize());

`uvm_info(get_type_name(), $sformatf("transaction randomized."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("before calling put method."),UVM_LOW)
trans_out.put(trans);
`uvm_info(get_type_name(), $sformatf("after calling port put method."),UVM_LOW)
void'(transs.randomize());
`uvm_info(get_type_name(), $sformatf("randomize again."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing transs, \n%s",transs.sprint()),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("before calling put method."),UVM_LOW)
trans_out.put(transs);
`uvm_info(get_type_name(), $sformatf("after calling port put method."),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase

endclass:component_a

comp_b.sv:

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
class component_b extends uvm_component;
transaction trans,transs;
uvm_blocking_get_port#(transaction) trans_in;
`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
#50
`uvm_info(get_type_name(), $sformatf("before calling port get method."),UVM_LOW)
trans_in.get(trans);
`uvm_info(get_type_name(), $sformatf("after calling port get method."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s", trans.sprint()),UVM_LOW)

#100
`uvm_info(get_type_name(), $sformatf("get again."),UVM_LOW)

`uvm_info(get_type_name(), $sformatf("before calling port get method."),UVM_LOW)
trans_in.get(trans);
`uvm_info(get_type_name(), $sformatf("after calling port get method."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s", trans.sprint()),UVM_LOW)


phase.drop_objection(this);
endtask:run_phase
endclass:component_b

本次运行结果为:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 12 23:39 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
---------------------------------------------------------
Name Type Size Value
---------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_blocking_put_port - @479
comp_b component_b - @488
trans_in uvm_blocking_get_port - @496
fifo_ab uvm_tlm_fifo #(T) - @505
get_ap uvm_analysis_port - @540
get_peek_export uvm_get_peek_imp - @522
put_ap uvm_analysis_port - @531
put_export uvm_put_imp - @513
---------------------------------------------------------
UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(19) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(20) @ 0: uvm_test_top.env.comp_a [component_a] before calling put method.
UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] after calling port put method.
UVM_INFO comp_a.sv(24) @ 0: uvm_test_top.env.comp_a [component_a] randomize again.
UVM_INFO comp_a.sv(25) @ 0: uvm_test_top.env.comp_a [component_a] printing transs,
---------------------------------
Name Type Size Value
---------------------------------
transs transaction - @553
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'h60
---------------------------------

UVM_INFO comp_a.sv(26) @ 0: uvm_test_top.env.comp_a [component_a] before calling put method.
UVM_INFO comp_b.sv(15) @ 50: uvm_test_top.env.comp_b [component_b] before calling port get method.
UVM_INFO comp_b.sv(17) @ 50: uvm_test_top.env.comp_b [component_b] after calling port get method.
UVM_INFO comp_b.sv(18) @ 50: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(28) @ 50: uvm_test_top.env.comp_a [component_a] after calling port put method.
UVM_INFO comp_b.sv(21) @ 150: uvm_test_top.env.comp_b [component_b] get again.
UVM_INFO comp_b.sv(23) @ 150: uvm_test_top.env.comp_b [component_b] before calling port get method.
UVM_INFO comp_b.sv(25) @ 150: uvm_test_top.env.comp_b [component_b] after calling port get method.
UVM_INFO comp_b.sv(26) @ 150: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
transs transaction - @553
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'h60
---------------------------------

UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 150: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 17
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 8
[component_b] 7
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 150
V C S S i m u l a t i o n R e p o r t
Time: 150
CPU Time: 0.300 seconds; Data structure size: 0.4Mb
Thu Feb 12 23:39:15 2026

可以看到是往FIFO里面放了两个transaction,之后才在不同的时间被读出。

这里有一个现象是(可以自己尝试一下),如果同一个transaction(称为trans),被随机化送往FIFO,之后在comp_a中重复以上操作一次,comp_b中只会读出第二次随机化之后的数据,也就是说FIFO中的数据这样操作后其实会被覆盖。其原因是FIFO中存储的其实是对象句柄。

TLM FIFO nonblocking

TLM
FIFO也有非阻塞版本。依旧是需要将对应的method修改为非阻塞版本,同时添加can_函数在操作之前先检测FIFO的状态。不同的是这次的try_get, try_put, can_get等是FIFO内置的。

comp_a.sv:

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
class component_a extends uvm_component;
transaction trans;
`uvm_component_utils(component_a)
uvm_nonblocking_put_port#(transaction) trans_out;

function new(string name, uvm_component parent);
super.new(name, parent);
trans_out=new("trans_out",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
#100;
trans=transaction::type_id::create("trans",this);
void'(trans.randomize());
`uvm_info(get_type_name(), $sformatf("transaction randomized."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)

`uvm_info(get_type_name(), $sformatf("before calling try_put method."),UVM_LOW)
trans_out.try_put(trans);
`uvm_info(get_type_name(), $sformatf("after calling try_put method."),UVM_LOW)

phase.drop_objection(this);
endtask:run_phase
endclass:component_a

comp_b.sv:

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
class component_b extends uvm_component;
transaction trans;
uvm_nonblocking_get_port#(transaction) trans_in;
`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_in=new("trans_in",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
if(trans_in.can_get())begin
`uvm_info(get_type_name(), $sformatf("before calling get methond."),UVM_LOW)
void'(trans_in.try_get(trans));
`uvm_info(get_type_name(), $sformatf("after calling get method."),UVM_LOW)
`uvm_info(get_type_name(), $sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)

end
else begin
`uvm_info(get_type_name(), $sformatf("no entry, not able to get the trans."),UVM_LOW)
end
phase.drop_objection(this);
endtask:run_phase
endclass:component_b

env.sv:

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
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;

uvm_tlm_fifo#(transaction) fifo_ab;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);

fifo_ab =new("fifo_ab",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.trans_out.connect(fifo_ab.put_export);
comp_b.trans_in.connect(fifo_ab.get_export);
endfunction:connect_phase
endclass:environment

运行结果是:

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
65
66
67
68
69
70
71
72
73
74
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 15 21:11 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
------------------------------------------------------------
Name Type Size Value
------------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_nonblocking_put_port - @479
comp_b component_b - @488
trans_in uvm_nonblocking_get_port - @496
fifo_ab uvm_tlm_fifo #(T) - @505
get_ap uvm_analysis_port - @540
get_peek_export uvm_get_peek_imp - @522
put_ap uvm_analysis_port - @531
put_export uvm_put_imp - @513
------------------------------------------------------------
UVM_INFO comp_b.sv(23) @ 0: uvm_test_top.env.comp_b [component_b] no entry, not able to get the trans.
UVM_INFO comp_a.sv(18) @ 100: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(19) @ 100: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(21) @ 100: uvm_test_top.env.comp_a [component_a] before calling try_put method.
UVM_INFO comp_a.sv(23) @ 100: uvm_test_top.env.comp_a [component_a] after calling try_put method.
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 100: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 7
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 1
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 100
V C S S i m u l a t i o n R e p o r t
Time: 100
CPU Time: 0.370 seconds; Data structure size: 0.4Mb
Sun Feb 15 21:11:27 2026

发现a成功完成了发送,但是b没有接收到,原因在于a中发送trans前添加了延迟,这样就导致了b先执行了trans_in.can_get(),检测到fifo为空,无法成功读取。如果我们在b实际执行之前添加一个更大的延迟,其结果就会不同:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 15 21:14 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
------------------------------------------------------------
Name Type Size Value
------------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
trans_out uvm_nonblocking_put_port - @479
comp_b component_b - @488
trans_in uvm_nonblocking_get_port - @496
fifo_ab uvm_tlm_fifo #(T) - @505
get_ap uvm_analysis_port - @540
get_peek_export uvm_get_peek_imp - @522
put_ap uvm_analysis_port - @531
put_export uvm_put_imp - @513
------------------------------------------------------------
UVM_INFO comp_a.sv(18) @ 100: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(19) @ 100: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(21) @ 100: uvm_test_top.env.comp_a [component_a] before calling try_put method.
UVM_INFO comp_a.sv(23) @ 100: uvm_test_top.env.comp_a [component_a] after calling try_put method.
UVM_INFO comp_b.sv(17) @ 200: uvm_test_top.env.comp_b [component_b] before calling get methond.
UVM_INFO comp_b.sv(19) @ 200: uvm_test_top.env.comp_b [component_b] after calling get method.
UVM_INFO comp_b.sv(20) @ 200: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 200: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 9
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 200
V C S S i m u l a t i o n R e p o r t
Time: 200
CPU Time: 0.240 seconds; Data structure size: 0.4Mb
Sun Feb 15 21:14:53 2026

这里的b就完成了读取。

TLM analysis port

一般的TLM port的本质是点对点通信,但是analysis
port可以实现广播通信,广播通信可以使得多个组件同时收到同一份transaction。广播是无阻塞的,但是没有try/can,因为广播不关心你是否收到,只是发送出去;一般默认是handle传递。

analysis_port声明和创建方式是:

1
2
3
uvm_analysis_port#(transaction) analysis_port;

analysis_port=new("analysis_port",this);

关于analysis port的method:analysis port唯一的真正用于发送数据的方法是write()analysis_port.write(transA)的含义就是广播一个transA到所有的analysis_port的subscriber; 至于广播之后具体做什么,imp component中的write()需要自己写。

订阅关系的建立用的是connect().

还有一些可能会用的:

  • is_conneted(): 用来判断是否连接。
  • size(): 返回连接的数量。

这里实现多个组件对同一个analysis port的连接。

comp_a.sv:

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
class component_a extends uvm_component;
transaction trans;
`uvm_component_utils(component_a)
uvm_analysis_port#(transaction) analysis_port;

function new(string name, uvm_component parent);
super.new(name,parent);
analysis_port=new("analysis_port",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
trans=transaction::type_id::create("trans",this);
void'(trans.randomize());
`uvm_info(get_type_name(),$sformatf("transaction randomized."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("before calling write method."),UVM_LOW)


analysis_port.write(trans);
`uvm_info(get_type_name(),$sformatf("after calling write method."),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase

endclass:component_a

在接收端需要设置的端口是analysis_imp_ports:

1
`uvm_analysis_imp_decl(port_identifier)

port_identifier是imp port的标识,比如我们想要声明一个analysis imp port:

1
2
3
`uvm_analysis_imp_decl(_port_a)

uvm_analysis_imp_port_a#(transaction, component_a) analysis_imp_a;

在imp侧还需要写对应的write函数write_port_identifier.

comp_b.sv:

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
`uvm_analysis_imp_decl(_port_a)
`uvm_analysis_imp_decl(_port_b)

class component_b extends uvm_component;
transaction trans;
uvm_analysis_imp_port_a#(transaction,component_b) analysis_imp_a;
uvm_analysis_imp_port_b#(transaction,component_b) analysis_imp_b;

`uvm_component_utils(component_b)


function new(string name, uvm_component parent);
super.new(name, parent);
analysis_imp_a=new("analysis_imp_a",this);
analysis_imp_b=new("analysis_imp_b",this);
endfunction:new

virtual function void write_port_a(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write_port_a. received trans on analysis imp port"),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)

endfunction:write_port_a


virtual function void write_port_b(transaction trans);

`uvm_info(get_type_name(),$sformatf("inside write_port_b. received trans on analysis imp port"),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)

endfunction:write_port_b
endclass:component_b

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class environment extends uvm_env;
component_a comp_a;
component_b comp_b;
`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.analysis_port.connect(comp_b.analysis_imp_a);
comp_a.analysis_port.connect(comp_b.analysis_imp_b);
endfunction:connect_phase
endclass:environment

运行结果是:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 15 23:33 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
----------------------------------------------------------
Name Type Size Value
----------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
analysis_port uvm_analysis_port - @479
comp_b component_b - @488
analysis_imp_a uvm_analysis_imp_port_a - @496
analysis_imp_b uvm_analysis_imp_port_b - @505
----------------------------------------------------------
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @514
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] before calling write method.
UVM_INFO comp_b.sv(20) @ 0: uvm_test_top.env.comp_b [component_b] inside write_port_a. received trans on analysis imp port
UVM_INFO comp_b.sv(21) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @514
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_b.sv(28) @ 0: uvm_test_top.env.comp_b [component_b] inside write_port_b. received trans on analysis imp port
UVM_INFO comp_b.sv(29) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @514
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] after calling write method.
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 10
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 4
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.240 seconds; Data structure size: 0.4Mb
Sun Feb 15 23:33:18 2026

可以发现comp_b的两个端口都接收到了trans.

这里是一个组件中的多个端口(single subscriber + multi ports),我们也可以看一下多个组件,每个组件一个端口(multi subscribers with each single port)。
comp_a由于是发布广播的,就和之前的一样;上面之所以需要写uvm_analysis_imp_decl,是因为在同一个组件中,所有的analysis_imp都会默认调用同一个(同名函数)write,会导致混乱,就需要进行多端口的声明,如果每个组件只有一个imp,就不需要这样做了。

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class component_b extends uvm_component;
uvm_analysis_imp#(transaction,component_b) analysis_imp;
`uvm_component_utils(component_b)

function new(string name,uvm_component parent);
super.new(name,parent);
analysis_imp=new("analysis_imp",this);
endfunction:new

virtual function void write(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write method. trans on analysis imp port."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
endfunction:write
endclass:component_b

comp_c.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class component_c extends uvm_component;
uvm_analysis_imp#(transaction,component_c) analysis_imp;
`uvm_component_utils(component_c)

function new(string name,uvm_component parent);
super.new(name,parent);
analysis_imp=new("analysis_imp",this);
endfunction:new

virtual function void write(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write method. trans on analysis imp port."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
endfunction:write
endclass:component_c

env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class environment extends uvm_env;
component_a comp_a;
component_b comp_b;
component_c comp_c;
`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
comp_c=component_c::type_id::create("comp_c",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.analysis_port.connect(comp_b.analysis_imp);
comp_a.analysis_port.connect(comp_c.analysis_imp);
endfunction:connect_phase
endclass:environment

运行结果:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 15 23:51 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
---------------------------------------------------
Name Type Size Value
---------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
analysis_port uvm_analysis_port - @479
comp_b component_b - @488
analysis_imp uvm_analysis_imp - @496
comp_c component_c - @505
analysis_imp uvm_analysis_imp - @513
---------------------------------------------------
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @522
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] before calling write method.
UVM_INFO comp_b.sv(12) @ 0: uvm_test_top.env.comp_b [component_b] inside write method. trans on analysis imp port.
UVM_INFO comp_b.sv(13) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @522
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_c.sv(12) @ 0: uvm_test_top.env.comp_c [component_c] inside write method. trans on analysis imp port.
UVM_INFO comp_c.sv(13) @ 0: uvm_test_top.env.comp_c [component_c] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @522
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] after calling write method.
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 10
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 2
[component_c] 2
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.260 seconds; Data structure size: 0.4Mb
Sun Feb 15 23:51:35 2026

最后是multi subscribers + multi ports的情况。
comp_a依旧不做改变,在comp_b和comp_c中都各自设置多个analysis imp port:

comp_b.sv:

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
`uvm_analysis_imp_decl(_port_ba)
`uvm_analysis_imp_decl(_port_bb)

class component_b extends uvm_component;
transaction trans;
uvm_analysis_imp_port_ba#(transaction,component_b) analysis_imp_a;
uvm_analysis_imp_port_bb#(transaction,component_b) analysis_imp_b;
`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name,parent);
analysis_imp_a=new("analysis_imp_a",this);
analysis_imp_b=new("analysis_imp_b",this);
endfunction:new

virtual function void write_port_ba(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write_port_ba, received trans."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
endfunction:write_port_ba


virtual function void write_port_bb(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write_port_bb, received trans."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
endfunction:write_port_bb

endclass:component_b

comp_c.sv:

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
`uvm_analysis_imp_decl(_port_ca)
`uvm_analysis_imp_decl(_port_cb)

class component_c extends uvm_component;
transaction trans;
uvm_analysis_imp_port_ca#(transaction,component_c) analysis_imp_a;
uvm_analysis_imp_port_cb#(transaction,component_c) analysis_imp_b;
`uvm_component_utils(component_c)

function new(string name, uvm_component parent);
super.new(name,parent);
analysis_imp_a=new("analysis_imp_a",this);
analysis_imp_b=new("analysis_imp_b",this);
endfunction:new

virtual function void write_port_ca(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write_port_ca, received trans."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
endfunction:write_port_ca


virtual function void write_port_cb(transaction trans);
`uvm_info(get_type_name(),$sformatf("inside write_port_cb, received trans."),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
endfunction:write_port_cb

endclass:component_c

env.sv:

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

class environment extends uvm_env;
component_a comp_a;
component_b comp_b;
component_c comp_c;
`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
comp_c=component_c::type_id::create("comp_c",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.analysis_port.connect(comp_b.analysis_imp_a);
comp_a.analysis_port.connect(comp_b.analysis_imp_b);
comp_a.analysis_port.connect(comp_c.analysis_imp_a);
comp_a.analysis_port.connect(comp_c.analysis_imp_b);
endfunction:connect_phase
endclass:environment

运行结果:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 17 00:47 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
-----------------------------------------------------------
Name Type Size Value
-----------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
analysis_port uvm_analysis_port - @479
comp_b component_b - @488
analysis_imp_a uvm_analysis_imp_port_ba - @496
analysis_imp_b uvm_analysis_imp_port_bb - @505
comp_c component_c - @514
analysis_imp_a uvm_analysis_imp_port_ca - @522
analysis_imp_b uvm_analysis_imp_port_cb - @531
-----------------------------------------------------------
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @540
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] before calling write method.
UVM_INFO comp_b.sv(18) @ 0: uvm_test_top.env.comp_b [component_b] inside write_port_ba, received trans.
UVM_INFO comp_b.sv(19) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @540
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_b.sv(24) @ 0: uvm_test_top.env.comp_b [component_b] inside write_port_bb, received trans.
UVM_INFO comp_b.sv(25) @ 0: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @540
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_c.sv(18) @ 0: uvm_test_top.env.comp_c [component_c] inside write_port_ca, received trans.
UVM_INFO comp_c.sv(19) @ 0: uvm_test_top.env.comp_c [component_c] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @540
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_c.sv(24) @ 0: uvm_test_top.env.comp_c [component_c] inside write_port_cb, received trans.
UVM_INFO comp_c.sv(25) @ 0: uvm_test_top.env.comp_c [component_c] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @540
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] after calling write method.
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 0: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 14
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 4
[component_c] 4
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 0
V C S S i m u l a t i o n R e p o r t
Time: 0
CPU Time: 0.400 seconds; Data structure size: 0.4Mb
Tue Feb 17 00:47:29 2026

TLM analysis FIFO

TLM analysis FIFO可以在consumer内部实现FIFO的功能,让consumer直接连接producer的port.

comp_a作为producer依旧不修改,comp_b中设置uvm_tlm_analysis_fifo:

1
2
uvm_tlm_analysis_fifo#(transaction) analysis_fifo;
analysis_fifo=new("analysis_fifo",this);

analysis_fifo通过get()获取trans:

1
analysis_fifo.get(trans);

comp_b.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class component_b extends uvm_component;
transaction trans;
uvm_tlm_analysis_fifo#(transaction) analysis_fifo;
`uvm_component_utils(component_b)

function new(string name, uvm_component parent);
super.new(name, parent);
analysis_fifo=new("analysis_fifo",this);
endfunction:new

virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);
#100
`uvm_info(get_type_name(),$sformatf("before calling get method."),UVM_LOW)
analysis_fifo.get(trans);
`uvm_info(get_type_name(),$sformatf("printing trans, \n%s",trans.sprint()),UVM_LOW)
`uvm_info(get_type_name(),$sformatf("after calling get method."),UVM_LOW)
phase.drop_objection(this);
endtask:run_phase
endclass:component_b

最后的连接阶段需要把comp_a中的analaysis_port连接到comp_b.analysis_fifo.analysis_export.
env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

class environment extends uvm_env;
component_a comp_a;
component_b comp_b;
`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
comp_b=component_b::type_id::create("comp_b",this);
endfunction:build_phase

function void connect_phase(uvm_phase phase);
comp_a.analysis_port.connect(comp_b.analysis_fifo.analysis_export);
endfunction:connect_phase
endclass:environment

运行结果:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 17 11:01 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test test...
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
uvm_test_top test - @455
env environment - @463
comp_a component_a - @471
analysis_port uvm_analysis_port - @479
comp_b component_b - @488
analysis_fifo uvm_tlm_analysis_fifo #(T) - @496
analysis_export uvm_analysis_imp - @540
get_ap uvm_analysis_port - @531
get_peek_export uvm_get_peek_imp - @513
put_ap uvm_analysis_port - @522
put_export uvm_put_imp - @504
----------------------------------------------------------------
UVM_INFO comp_a.sv(16) @ 0: uvm_test_top.env.comp_a [component_a] transaction randomized.
UVM_INFO comp_a.sv(17) @ 0: uvm_test_top.env.comp_a [component_a] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_a.sv(18) @ 0: uvm_test_top.env.comp_a [component_a] before calling write method.
UVM_INFO comp_a.sv(22) @ 0: uvm_test_top.env.comp_a [component_a] after calling write method.
UVM_INFO comp_b.sv(15) @ 100: uvm_test_top.env.comp_b [component_b] before calling get method.
UVM_INFO comp_b.sv(17) @ 100: uvm_test_top.env.comp_b [component_b] printing trans,
---------------------------------
Name Type Size Value
---------------------------------
trans transaction - @549
addr integral 4 'hf
wr_rd integral 1 'h0
wdata integral 8 'he9
---------------------------------

UVM_INFO comp_b.sv(18) @ 100: uvm_test_top.env.comp_b [component_b] after calling get method.
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 100: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 9
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[RNTST] 1
[TEST_DONE] 1
[component_a] 4
[component_b] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 100
V C S S i m u l a t i o n R e p o r t
Time: 100
CPU Time: 0.290 seconds; Data structure size: 0.4Mb
Tue Feb 17 11:01:32 2026

RAL

Register Abstraction Layer(UVM RAL)是UVM的一层寄存器级设计,他使得我们可以使用DUT的register和memory来执行(面向对象的)测试模型。RAL的主要作用是简化对register和memory的操作。

通过RAL,我们可以:

  • 使用高级的抽象层次实现较为简单的DUT读写操作;
  • UVM提供了一组包含了预先定义好测试用例的test sequence library,可以简单的进行寄存器和内存测试;
  • RAL支持front-door(通过总线协议来访问寄存器)和back-door(通过HDL直接访问变量内容)访问;
  • 不考虑实际电路形式,直接通过read/write等API完成访问,实现访问和物理电路的解耦(这样的话同样的测试可以在不同的总线/接口上不加修改地直接进行);
  • 寄存器模型可以被多线程并发访问;
  • 有自动RAL模型生成,可以利用开源工具和脚本自动生成RAL model.

register model一般由很多block组成,每个block都映射到design的一个部分。一个block可以包括:register, register files, memories, 还可以有其他blocks. 这其中每个部分UVM都提供了基类和内置的method.

UVM RAL block

一个uvm_reg_block包含:uvm_mem(memory),uvm_reg_map(address map),uvm_reg_file(register file).
uvm_reg_file可以包含一个或多个uvm_reg(register),一个uvm_reg内部又可以包含多个uvm_reg_field(fields in a register).

因为一个设计中,寄存器数量一般很多,所以需要通过register model
generator进行spcialization(指把通用的register变成各种具体专用的register). 

register model generator

register model generator本身是UVM library外部的东西,用来实现register
model的自动spcialization.

通过使用register model generator,可以自动实现reg name, width, register fields, access permission等的指定。

部分register model generator:

工具 / 平台 公司 工具定位 输入格式 主要输出 核心特点 典型使用场景
RGM (Register & Memory) Cadence 早期 RAL 生成器 RGM spec / IP-XACT UVM RAL Cadence 旧方案,现多被 IP-XACT flow 取代 维护 legacy 项目
ralgen Synopsys ⭐ 主流 RAL generator RALF / IP-XACT UVM RAL / C header / coverage model 与 VCS + UVM 深度集成,工业界使用率高 Synopsys 验证 flow
SystemRDL + CSRCompiler Semifore ⭐ 寄存器生成标准工具链 SystemRDL RTL / UVM RAL / C header / 文档 单一寄存器描述源(single source of truth) SoC/IP 级统一寄存器管理
IDesignSpec Agnisys ⭐ Excel 驱动的寄存器平台 Excel / XML RTL / UVM RAL / Firmware header / 文档 GUI + 非验证人员友好 大型 SoC 团队协作
Magillem MRV Magillem ⭐ SoC 数据管理平台 IP-XACT RAL / RTL / C header / 系统文档 IP 集成 + 可视化寄存器 + 架构级管理 SoC 架构团队
Bitwise Duolog 图形化寄存器建模工具 GUI spec RAL / RTL / 文档 可视化建模 IP 设计团队
Certes Testbench Studio Mentor (Siemens) 自动验证环境生成平台 寄存器 spec / IP-XACT UVM TB + RAL 不只是 RAL,生成完整验证环境 快速搭建 TB

我这里使用synopsys的ralgen.

还有一些开源的工具比如说RgGen, PeakRDL + SystemRDL.

usage model

uvm_reg_block

extend uvm_reg_block之后可以得到register block, 而register block的实例化就是register module。这个是RAL的顶层容器,是必须的。block映射到一个具体的设计,这个设计可以包括interface, register, register file, memory, sub-block.

标准一点的说法是,一个block就是一个address map domain,对应于DUT中的一个IP/子系统。

uvm_reg_file

extend uvm_reg_file之后可以得到reg file.
reg file可以内含多个reg file, 或者是register, 不过本身是逻辑分组,不会产生新的address map.

uvm_reg

uvm_reg代表了实际上的一个寄存器,每个reg都可以分出一个或者多个reg field(reg
field是可以被单独访问的)。

reg也是register model必须的。

uvm_reg_field

本质是register中的一块连续区域,每个reg field应该对对应一个实际的硬件实体,有自己的地址,访问规则。

access methods

关于寄存器的访问,UVM RAL library有内置的method来访问寄存器,UVM提供的API中有front door和back door两种访问方式。

front door access是通过总线接口来进行访问,会消耗仿真时间;back door access是通过simulator database直接访问,不消耗仿真时间。

read, write

这里首先需要了解RAL中的两个概念,desired value和mirror value.

desired value含义是:本次操作中,我希望DUT(某个寄存器)变成的值;
mirror value含义是:RAL认为当前DUT的值。
这两这本身是register model模型的值,不是DUT中的真实值,需要区分。在register model中设计这两个值可以批量寄存器shadow update,做寄存器检查,自动对比DUT行为等。

一般来讲,front door的访问路线是:reg model -> bus transaction -> driver -> DUT,backdoor就可以直接通过HDL修改。

需要这两个不同的值是因为,当以front door access的形式对DUT的值进行修改的时候,有两个问题,一是不确定是否真的按照意愿成功修改,二是修改需要消耗仿真时间,并不是立即修改的。所以当进行front door access的时候,mirror value不会再发起bus transaction的时候立马修改,而是等monitor观察到总线操作确认更新以后,predictor进行对mirror value的更新;back door access就可以直接在进行修改的时候立刻更新mirror value.

write method要做的事情是,首先发起写操作来修改DUT中的寄存器值,然后更新DUT寄存器的值如果是front door access,就会等predictor修改mirror value, 如果是back door access就会立刻更新。

read method要做的事情是,获取并返回DUT寄存器中的值,然后根据front door/back door来进行对DUT寄存器的更新(也需要更新一次)。

peek, poke

peek()会以back door的形式读取DUT寄存器中的值;
poke()会以back door的形式向DUT寄存器中写入值。

read&write是可以选择front/back door的访问路径的,但是peek&poke只能够使用back door.

它们最大的区别是,read&write会走标准寄存器的访问流程,会更新mirror,access policy还会生效,中间有callback也会触发;但是peek&poke是直接访问DUT,会绕过register model的机制,不走address map/adapter/but transaction/predictor,access policy也不会生效,而且默认情况只对DUT进行操作,不会修改mirror value.

他们的用途一般是debug/initialize/强制修改DUT状态。

其实可以理解为peek poke是back door的read write.

set, get

set()是写,get()是读,同样不经过标准寄存器的访问流程。不过这次操作的对象是desired value(register model),并不会直接写入DUT,不过在更新数据(使用update method)之后,DUT的寄存器值会相应改变。

update

update()的作用是,将desired value的值同步到DUT,可以对单个寄存器或者是整个block使用。

具体流程是:首先将desired value和mirrored value对比,如果两者相同,什么都不做;如果不同,会触发一次front door write,将desired value写入DUT(并且更新mirrored value).

update()这里的mirrored value更新不一定是predictor做的,要取决于是否开启auto prediction或者显式使用predictor. 如果开启了auto prediction,RAL会自己调用predict()更新mirrored value,不经过predictor;如果是显式调用predictor,就会是predictor的行为。

RAL自己的行为,是模型的操作,因此不需要依赖monitor和总线返回,是纯粹的”软件操作”。

mirror

mirror()的操作是,读取DUT中的值,用读取到的值来更新mirrored value.
可以选择front/back door.

randomize

randomize()是随机化register model中的desired value,这个随机化可以带有约束,通过with关键词来实现,

1
2
3
reg.randomize() with{
field_a.value inside {[3:7]};
};

之后一般要接一个update()真正写入DUT并修改mirrored value.

reset

reset()会把对象的所有desired value和mirrored value设置为未定义状态。

一点小小的解释

这里还是需要解释一下register model和DUT的问题。DUT是我们用systemverilog编写的用来模拟真实设备功能的(验证的时候就是代码),可以把他想像成一个真实的器件,register model可以看成是一个用来研究他的(虚拟的)软件模型,这两者要分开考虑。

read&write分front door和back door两种,front door就相当于是我真实地对这个器件进行读写操作,所以需要耗时,走总线,等在这个实际器件上得到结果了(从DUT读出实际数值了),就把实际的结果记录到我这个model里面;backdoor就相当于是我不管DUT,我不是实际对DUT操作,我直接修改我的模型。

我们之前说register model需要desired value和mirrored value:

需要这两个不同的值是因为,当以front door access的形式对DUT的值进行修改的时候,有两个问题,一是不确定是否真的按照意愿成功修改,二是修改需要消耗仿真时间,并不是立即修改的。所以当进行front door access的时候,mirror value不会再发起bus transaction的时候立马修改,而是等monitor观察到总线操作确认更新以后,predictor进行对mirror value的更新;back door access就可以直接在进行修改的时候立刻更新mirror value.

就是从这个角度考虑的。

另外,不同method的使用层级也不同,简单的来说,上面所有的method都可以对register层级使用;read/write/peek/poke/get/set/randomize/reset的本质都是是读写操作,读写的最小操作单位是register field,因此也可以对register field层级使用;update/mirror/randomize/reset递归使用就相当于对整个block操作,因此也可对block层级使用。

constructing register model

关于register model的构建,首先从最小的单位开始。

register field

register field是靠uvm_reg_field声明的:

1
uvm_reg_field reg_name;

reg field需要在register class内部声明[如果是用.而不是::的方式访问的话],且同一个register内部变量名要唯一(很合理).

reg field可以有access policy,通过uvm_reg_field::configure()设定,需要在register的build()[这是一个method,不是build_phase]中调用来完成其构建。

关于access policy:

access policy description
RO read only
RW read, write
RC read&clear(set all bits to 0 aftter read)
WRC write, read&clear
WC write&clear(set all bits to 0 after write)
W1C write 1 to clear(write value bit is 1, the corresponding bit set to 0)
W0C write 0 to clear(write value bit is 0, the corresponding bit set to 0)

还有一种reserved field,它没有access policy,未被建模,默认为全是0的RO register field.

register

extends uvm_reg可以定义一个自己的register class.

1
2
3
4
class my_reg extends uvm_reg;
rand uvm_reg_field field_0;
rand uvm_reg_field field_1;
endclass

register file

extends uvm_reg_file可以得到一个register file class.

1
2
3
class my_reg_file extends uvm_reg_file;
`uvm_object_utils(my_reg_file)
enclass

register file可以包含register file.
在register file的build()里面需要通过get_block()指定这个register file的parent(block/register file):

1
2
3
virtual function build();
uvm_reg_block blk=get_block();
endfunction

build()还需要给register file内部所有的register/register file执行configure():

1
2
3
4
5
6

virtual function build();
...
this.rf.configure(get_block,this,...);
...
endfunction

rf: register file.

configure()的作用是把当前reg_file挂载到他的父层级,建立层级关系和address map归属关系。

register/register file/memory都需要建立这个关系,UVM RAL才能够找到相关的对象。

configure()函数的三个参数是:

  1. block类型,指定register file属于哪个block;
  2. register file类型,指定他在block下的哪个register_file里;
  3. HDL path,可选,用于back door access.

map()

map是一个function,接收两个参数,一个是uvm_reg_map的变量, 一个是偏移量offset.

这个uvm_reg_map类型的变量是uvm_reg_block的成员变量,是block创建的,也由block管理,一般写到register file/block class里面。

map函数需要调用uvm_reg_map::add_reg(),来进行地址映射的记录。uvm_reg_map::add_reg()两个参数,一个是寄存器,一个是(register file的)base_addr + offset.

也可以通过add_hdl_path()添加hdl_path.

1
2
3
4
5
virtual function map(uvm_reg_map mp, uvm_reg_addr_t offset);
mp.add_reg(this.reg_0, base_addr+'h0);
mp.add_reg(this.reg_1, base_addr+'h4);
this.rf.map(mp, base_addr+h'100);
endfunction

最后一句是递归执行,因为rf里面还会有rf.

set_offset()

map()的含义是将寄存器加入address map这个地址映射系统,set_offset()是在修改(已经存入系统中)地址,也要递归执行(为了保证地址系统的合理性)。

1
2
3
4
5
6
virtual function set_offset(uvm_reg_mp, uvm_reg_addr_t offset);
this_reg_0.set_offset(mp, base_addr+'h0);
this_reg_1.set_offset(mp, base_addr+'h8);
this.rf.set_offset(mp, base_addr+'h100)
endfunction

memory

extends uvm_mem可以得到自己的memory class.

1
2
3
class my_mem extends uvm_mem;
`uvm_object_utils(my_mem)
endclass

register block

extends uvm_reg_block可以得到自己的block.

1
2
3
class my_blk extends uvm_reg_block;
`uvm_object_utils(my_blk)
endclass

block以下成员变量每个至少要有一个:

(named)address map, register, register file, memory, sub-block

而且它们必须是rand(这个是因为作为UVM object需要支持uvm_object::copy()/clone()/compare(),作为规范加上的,不是真的需要randomize).

在build()中,要通过uvm_reg_block::create_map()创建所有的address map.

这里有一个伏笔,(尤其是之后的代码中)你会发现有些地方怪怪的,之后会解释。

packaging model

关于register model的编写,一般建议是:

  • 各种类型的模块(block, memory, register等)写到不同文件中;
  • 被多个block共享的register,register file,memory类型要分开写到各自文件中;
  • 写一个header file,包含所有需要的import语句;
  • 冗长的build()分成多个局部定义的子方法。

integrating model

一个register model需要通过but agent来完成集成,最后的集成行为需要root block来完成,同时root block也应该是唯一一个可以和外部访问交流(可以了解外部可见地址映射)的模块。

predictor

之前说过,front door access过程中register model的mirrored value更新方式有一种是通过UVM RAL predictor实现的,而predictor实际上需要根据车市需要自行定义。

UVM提供的基类uvm_reg_predictor的更新是基于monitor发布的observed transaction. register model访问之后的预测和更新是基类可以自动实现的,但是也可以通过显式使用外部的predictor实现;自己写的predictor和自动更新的主要区别是会关注interface bus transaction关注不到的东西。

implicit prediction

隐式的prediction只需要register model至少有一个bus sequencer. 对mirrored value的更新会自动发生在每一次read, write, peek, poke之后。

bus sequencer: 负责生成和调度总线事务(bus transaction). bus transaction通过(模拟的)总线接口和DUT模型交互,控制着一个或者多个bus driver,按照特定顺序发送总线请求和数据。

explicit prediction

显式的prediction除了需要bus sequencer之外,还要对应的bus monitor.

隐式的prediction会被关闭,mirrored value的更新需要通过uvm_reg_predictor实现,每个bus interface都需要一个predictor.

adapter

为了更加方便地对register进行操作,register model会引入一些相对高级/抽象的method实现寄存器操作,但是具体对DUT的操作还是要经过实际的interface和bus进行,所以需要一个adapter实现从RAL method到interface/bus的转化,实现抽象操作和具体实现过程的分离。(register是指register model下的virtual mirror,不是真实的电路,bus代表的是真实的电路。)

对于一个adapter,需要实现read/write等method的interface/bus实现(还有反过来),可以通过extends uvm_reg_adapter,执行reg2bus()/bus2reg()实现。

reg2bus()实现的是从RAL transaction到interface/bus transaction的转化,bus2reg()实现的是interface/bus到RAL.

integrating RAL to bus agent

RAL | agent

RAL在实际执行中需要和实际电路连接。

RAL中一般至少有一个sequener,根据总线数量(sequener数量),会有不同的执行策略。

第一种是直接在总线上运行。多见于如果只有一个sequencer,也就是只有一个总线接口,那么直接挂载到唯一的sequencer上即可(register sequence -> sequencer);

第二种是作为虚拟序列运行。如果有多个sequencer,就代表了芯片有多种总线可以访问寄存器,比如说同时有APB访问外设寄存器,AXI访问高速配置寄存器,或者是同一个寄存器有多种端口进行访问,此时register sequence就不会只属于同一个sequencer,就应该变成virtual sequence,在其内部保存应该发送给那条总线的信息。

第三种是抽象的执行,我们只需要关注于寄存器对象,运行起来之后会自动适配前面两种情况。

RAL与agent的集成体现到以上执行策略。

在UVM RAL语境下,一个sequener代表一个物理总线接口(bus interface).

RAL | bus monitor

我们之前说过,predictor可以关注到RAL无法自动关注到的东西(RAL隐式update是有局限的,RAL只能够看见经过RAL的操作,还会有一些绕过RAL的场景)。如果要实现predictor的观测和更新(为了补全隐式update缺失的部分功能),predictor就需要关注monitor发布的信息。

RAL与bus mmonitor的集成体现在predictor(predictor的实体连接到monitor的analysis port).

UVM register defines

关于register的一些设定参数,UVM有一些defines,也可以被override.

UVM_REG_ADDR_WIDTH

代表了地址的最大位宽,默认是64,用来定义uvm_reg_addr_t这个类型。

关于“定义uvm_reg_addr_t”,其实就是防止magic number,类似于typedef bit [`UVM_REG_ADDR_WIDTH-1:0] uvm_reg_addr_t这样的定义。

UVM_REG_DATA_WIDTH

代表了数据的最大位宽,默认值64,用来定义uvm_reg_data_t.

UVM_REG_BYTENABLE_WIDTH

代表了byte enable的最大位宽,默认值是`UVM_REG_DATA_WIDTH包含的字节数,用来定义uvm_reg_byte_en_t.

byte enable是一个控制信号,决定了在一个数据字中,哪些字节有效或需要修改。例如,be=4’b0101,就代表了0和3位字节需要修改,也因此,他的位宽等于数据量的字节数。

UVM_REG_CVR_WIDTH

代表了RAL的coverage控制位宽(uvm_reg_cvr_t),默认值32.

RAL中不是所有寄存器采集同样的覆盖率,比如有的是采集读写操作,有的是值迭代,uvm_reg_cvt_t用来存放覆盖率控制掩码,用来指示默认32种不同的覆盖类型。

UVM RAL base classes

UVM RAL type description
uvm_reg_data_t 长度为 `UVM_REG_DATA_WIDTH的量,支持2态。
uvm_reg_data_logic_t 长度为 `UVM_REG_DATA_WIDTH的量,支持4态。
uvm_reg_addr_t 长度为 `UVM_REG_ADDR_WIDTH的地址,支持2态。
uvm_reg_addr_logic_t 长度为 `UVM_REG_ADDR_WIDTH的地址,支持4态。
uvm_reg_byte_en_t 长度为 ` UVM_REG_BYTENABLE_WIDTH的字节使能,支持2态。
uvm_reg_cvr_t 长度为 `UVM_REG_CVR_WIDTH的覆盖类型控制信号。
uvm_hdl_path_slice HDL path

2态是指0/1, 4态是0/1/x/z.

以上是RAL model type,用来模拟硬件的寄存器映射。

UVM RAL enums description
uvm_status_e 返回本次寄存器操作的状态。
UVM_IS_OK: 操作成功完成。
UVM_NOT_OK: 发生错误。
UVM_HAS_X: 操作本身顺利完成,但是传回的数据包含X/Z.
uvm_path_e path used for register operation.
UVM_FRONTDOOR: 使用front door方式。
UVM_BACKDOOR: 使用back door方式。
UVM_PREDICT: monitor通过uvm_reg_predictor监听bus信号(捕获到总线事务并交给predictor时)所要进行的操作。这个行为不是主动发起的,而是观察到的外部总线活动导致的。
UVM_DEFAULT_PATH: 由上下文自动决定。 如果没有指定路径,会检查uvm_reg_map是否有制定的默认路径,如果也没有,一般是默认设为UVM_FRONTDOOR.
uvm_check_e 这是一个操作控制参数,定义的是read()要不要顺便对比。
UVM_NO_CHECK: read only.
UVM_CHECK: read and check.
uvm_endianness_e 指定了字节顺序,就是如何将一个大数据分成多个小数据。
UVM_NO_ENDIAN: 不使用大小端,一般是寄存器宽度小于等于总线宽度,可以一次传输完。
UVM_LITTLE_ENDIAN: 小端存储方式。MSB在高位地址,LSB在低位地址。
UVM_BIG_ENDIAN: 大端存储方式。MSB存储在低位地址,LSB在高位地址。
UVM_LITTLE_FIFO: 先发送低位字节,再发送高位字节。
UVM_BIG_FIFO: 先发送高位字节,再发送低位字节。
uvm_elem_kind_e 标记当前操作对象是什么级别的。
UVM_REG: register.
UVM_FIELD: field.
UVM_MEM: memory location.
uvm_access_e 描述的是当前操作的属性。
UVM_READ: read operation.
UVM_WRITE: write operation.
uvm_hier_e 决定是否要获取hierarchical的信息。
UVM_NO_HIER: 提供本地的context信息。
UVM_HIER: 获取hierarchical context.
uvm_predict_e mirrored value如何更新。
UVM_PREDICT_DIRECT: 直接覆盖,忽略寄存器属性,直接修改mirrored value.
UVM_PREDICT_READ: 模拟读的行为进行更新,假设这是一次读操作,根据读到的值来更新。
UVM_PREDICT_WRITE: 模拟写的行为更新,假设这是一次写操作,根据写入的值来更新。
uvm_coverage_model_e 基于bit定义,可以通过|同时开启多个模型。 这是一个bitmask的枚举类型,决定了开启哪些纬度的覆盖率收集。
UVM_NO_COVERAGE: 关闭所有的自动覆盖率收集。
UVM_CVR_REG_BITS: 寄存器位覆盖率。
UVM_CVR_ADDR_MAP: 地址映射/访问覆盖率。
UVM_CVR_FIELD_VALS: 位域数值覆盖率。
UVM_CVR_ALL: 打开所有的覆盖率模型。
uvm_reg_mem_tests_e 基于bit定义,同样可以同时开启多个模型。用于UVM内置的寄存器和内存的built-in sequences,可以通过这个枚举实现自动的硬件检查等任务。
UVM_DO_REG_HW_RESET: 运行序列uvm_reg_hw_reset_seq,复位后立即读取所有寄存器,检查读取值是否和模型中定义的reset_value一致。
UVM_DO_REG_BIT_BASH: 运行序列uvm_reg_bit_bash_seq,对寄存器每个可写bit依次写入1和0,然后读出,检查是否成功/有没有影响其他不相关的位。
UVM_DO_REG_ACCESS: 运行序列uvm_reg_access_seq,通过front door write + back door read或者是back door write + front door read交叉验证路径的正确性。
UVM_DO_MEM_ACCESS: 运行序列uvm_mem_access_seq,对于内存,通过不同的路径进行读写验证。
UVM_DO_SHARED_ACCESS: 运行序列uvm_reg_mem_shared_access_seq,如果寄存器可以在多个总线上被访问,他会测试从一个总线写入后,另一个总线读出是否正确。
UVM_DO_MEM_WALK: 运行序列uvm_mem_walk_seq,对整个内存执行walk测试。
UVM_DO_ALL_REG_MEM_TESTS: 做所有的测试。

这个执行是有顺序的,比如写的是(UVM_DO_REG_HW_RESET | UVM_DO_REG_BIT_BASH),就是先进行复位检查,在进行位翻转测试。

关于check,其实就是操作后顺便再读出一次,对比看到的数据和预想是否一致。如果不一致,desired value保持不变,mirrored value会被强制更新为读回的值。

walking test,含义是让1在全0的环境中走一遍,或者是让0在全1的环境中走一遍。比如说,存储单元是八位,就从0000_0001开始,让这个1从最低位一直移动,直到最高位1000_0000.行走测试分为数据线行走和地址线行走,数据线行走是在同一个地址写入这个”行走的数据”并读回;地址线行走是现在所有的地址写0,然后依次在”行走的地址”写入特定的值检测有没有错误或者其他地址的值被意外改写。

RAL enums规定的是访问行为规则和状态。

register access without RAL model

首先看一下不使用RAL model的寄存器访问。

假设有一个DMA,他有寄存器和对应的接口便于访问。寄存器有四个:INTR, CTRL, IO ADDR, MEM ADDR,具体如下:

1
2
3
4
5
6
7
regname     address            field description

INTR h400 31:16(MASK), 15:0(STATUS)
CTRL h404 31:10(RESVD), 9(IO_MEM), 8:1(W_COUNT), 0(START_DMA)
IO_ADDR h408 31:0(ADDRESS)
MEM_ADDR h40C 31:0(ADDRESS)

dma.sv:

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
//------------------------------------------------------------------------
// DMA RTL - www.verificationguide.com
//------------------------------------------------------------------------

module DMA
#( parameter ADDR_WIDTH = 32,
parameter DATA_WIDTH = 32 ) (
input clk,
input reset,

//control signals
input [ADDR_WIDTH-1:0] addr,
input wr_en,
input valid,

//data signals
input [DATA_WIDTH-1:0] wdata,
output [DATA_WIDTH-1:0] rdata
);

logic [DATA_WIDTH-1:0] t_data;

reg [DATA_WIDTH-1:0] intr;
reg [DATA_WIDTH-1:0] control;
reg [DATA_WIDTH-1:0] io_address;
reg [DATA_WIDTH-1:0] mem_address;

//Reset
always @(posedge reset) begin //{
intr <= 0;
control <= 0;
io_address <= 0;
mem_address <= 0;
end //}

assign rdata = t_data;

always @(posedge clk) begin //{
if (wr_en & valid) begin//{
if (addr == 32'h400) intr <= wdata;
else if (addr == 32'h404) control <= wdata;
else if (addr == 32'h408) io_address <= wdata;
else if (addr == 32'h40C) mem_address <= wdata;
end //}
else if (!wr_en & valid) begin//{
if (addr == 32'h400) t_data = intr ;
else if (addr == 32'h404) t_data = control ;
else if (addr == 32'h408) t_data = io_address ;
else if (addr == 32'h40C) t_data = mem_address ;

//rdata = t_data;
end //}
end

endmodule

测试流程如下(图片和代码来自https://verificationguide.com/uvm-ral-example/uvm-reg-example-without-ral/):

chart

DMA agent的作用是生成激励通过interface发送给DMA,DMA的响应通过interface被monitor获取进行验证。

DMA agent需要:driver, monitor, sequencer, sequence(read/write).

首先还是写dma_interface.sv:

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
interface dma_if(input logic clk, reset);
logic [31:0] addr;
logic wr_en;
logic valid;
logic [31:0] wdata;
logic [31:0] rdata;

clocking driver_cb@(posedge clk);
default input #1 output #1;
output addr;
output wr_en;
output valid;
output wdata;
input rdata;
endclocking

clocking monitor_cb@(posedge clk);
default input #1 output #1;
input addr;
input wr_en;
input valid;
input wdata;
input rdata;
endclocking

modport DRIVER(clocking driver_cb, input clk, reset);
modport MONITOR(clocking monitor_cb, input clk, reset);
endinterface

首先需要定义用于dma的transaction, dma_seq_item.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import uvm_pkg::*;
`include"uvm_macros.svh"
class dma_seq_item extends uvm_sequence_item;
rand bit [31:0] addr;
rand bit wr_en;
rand bit [31:0] wdata;
bit [31:0] rdata;

`uvm_object_utils_begin(dma_seq_item)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(wr_en, UVM_ALL_ON)
`uvm_field_int(wdata, UVM_ALL_ON)
`uvm_object_utils_end

function new(string name = "dma_seq_item");
super.new(name);
endfunction
endclass

这里没有将rdata注册到factory,有一些考量。
首先是addr, wr_en, wdata这几个需要随机化生成发送给driver的,所以需要注册到factory; 同时如果将rdata也注册到factory,在某些compare的情况下会导致失败(rdata是硬件返回的,发送时刻rdata未知,可能有比较失败),此外还有在简单测试场景中,产生无效复制,打印等反而会浪费性能。

但是如果rdata后续需要UVM recorder完成波形数据库记录,或者是需要item.print()确认读回的值是否正确,则需要注册rdata.

然后需要进行基本事务设定,dma_sequence.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef class dma_sequencer;
class dma_sequence extends uvm_sequence#(dma_seq_item);
`uvm_object_utils(dma_sequence)

function new(string name="dma_sequence");
super.new(name);
endfunction

`uvm_declare_p_sequencer(dma_sequencer)

virtual task body();
repeat(2) begin
req=dma_seq_item::type_id::create("req");
wait_for_grant();
req.randomize();
send_request(req);
wait_for_item_done();
end
endtask;
endclass

写事务,write_sequence.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
class write_sequence extends uvm_sequence#(dma_seq_item);
bit [31:0] t_addr, t_data;

`uvm_object_utils(write_sequence)

function new(string name = "write_sequence");
super.new(name);
endfunction

virtual task body();
`uvm_do_with(req, {req.wr_en==1;req.addr==t_addr;req.wdata==t_data;})
endtask
endclass

读事务,read_sequence.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
class read_sequence extends uvm_sequence#(dma_seq_item);
bit [31:0] t_addr;

`uvm_object_utils(read_sequence)

function new(string name="read_sequence");
super.new(name);
endfunction

virtual task body();
`uvm_do_with(req, {req.wr_en==0;req.addr==t_addr;})
endtask
endclass

我们需要把dma_seq_item发往dma_sequencer, dma_sequencer.sv:

1
2
3
4
5
6
7
class dma_sequencer extends uvm_sequencer#(dma_seq_item);
`uvm_component_utils(dma_sequencer)

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

sequencer负责把transaction发给driver, driver.sv:

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
`define DRIV_IF vif.DRIVER.driver_cb
class dma_driver extends uvm_driver#(dma_seq_item);
virtual dma_if vif;
`uvm_component_utils(dma_driver)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);

if(!uvm_config_db#(virtual dma_if)::get(this,"","vif",vif))
`uvm_fatal("NO_VIF",{"virtual interface must be set for:",get_full_name(), ".vif"});
endfunction:build_phase

virtual task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
drive();
seq_item_port.item_done();
end
endtask:run_phase

virtual task drive();
`uvm_info("DRV_RUN", "Driving one item", UVM_LOW)
`DRIV_IF.wr_en<=0;
@(posedge vif.DRIVER.clk);

`DRIV_IF.addr<=req.addr;
`DRIV_IF.valid<=1;
`DRIV_IF.wr_en<=req.wr_en;

if(req.wr_en) begin
`DRIV_IF.wdata<=req.wdata;
@(posedge vif.DRIVER.clk);
end
else begin
@(posedge vif.DRIVER.clk);
`DRIV_IF.wr_en<=0;
@(posedge vif.DRIVER.clk);
req.rdata=`DRIV_IF.rdata;
end

`DRIV_IF.valid<=0;
endtask:drive
endclass:dma_driver

这里的drive()具体做的事情是,首先读写使能置为0然后等一个时钟,保证当下的处理是相对干净不受干扰的;如果是写操作,就设置好地址和wdata,之后再等一个周期就可以完成写操作;如果是读操作,就要设置好地址先等一个时钟,让硬件看到请求,开始查寄存器获取值,之后再等一个时钟,等rdata稳定之后采集。

1
2
write: @(清除干扰) -> 发送请求 -> 后面让DUT处理即可
read: @(清除干扰) -> 发送请求 -> @(DUT获取请求并进行读取) -> @(等待数值稳定) -> 读取

然后是monitor.sv:

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
class dma_monitor extends uvm_monitor;
virtual dma_if vif;
uvm_analysis_port#(dma_seq_item) item_collected_port;

dma_seq_item trans_collected;
`uvm_component_utils(dma_monitor)

function new(string name, uvm_component parent);
super.new(name, parent);
trans_collected=new();
item_collected_port=new("item_collected_port",this);
endfunction:new

function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual dma_if)::get(this,"","vif",vif))
`uvm_fatal("NO VIF", {"virtual interface must be set for:",get_full_name(), ".vif"});
endfunction:build_phase

virtual task run_phase(uvm_phase phase);
forever begin
@(posedge vif.MONITOR.clk);
wait(vif.monitor_cb.valid);
trans_collected.addr=vif.monitor_cb.addr;
trans_collected.wr_en=vif.monitor_cb.wr_en;

if(vif.monitor_cb.wr_en)begin
trans_collected.wr_en=vif.monitor_cb.wr_en;
trans_collected.wdata=vif.monitor_cb.wdata;
@(posedge vif.MONITOR.clk);
end
else begin
@(posedge vif.MONITOR.clk);
@(posedge vif.MONITOR.clk);

trans_collected.rdata=vif.monitor_cb.rdata;
end

item_collected_port.write(trans_collected);
end
endtask:run_phase
endclass:dma_monitor

将各部分连接的dma_agent.sv:

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
class dma_agent extends uvm_agent;
dma_driver driver;
dma_sequencer sequencer;
dma_monitor monitor;

`uvm_component_utils(dma_agent)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
monitor=dma_monitor::type_id::create("monitor",this);

if(get_is_active() == UVM_ACTIVE)begin
driver=dma_driver::type_id::create("driver",this);
sequencer=dma_sequencer::type_id::create("sequencer",this);
end
endfunction:build_phase

function void connect_phase(uvm_phase phase);
if(get_is_active()==UVM_ACTIVE) begin
driver.seq_item_port.connect(sequencer.seq_item_export);
end
endfunction:connect_phase
endclass:dma_agent

完成env.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class dma_model_env extends uvm_env;
dma_agent dma_agnt;

`uvm_component_utils(dma_model_env)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
dma_agnt=dma_agent::type_id::create("dma_agnt",this);
endfunction:build_phase
endclass:dma_model_env

然后就是具体对寄存器的操作,为了避免magic number的问题,需要写一个独立的文件dma_sfr_defines.sv:

1
2
3
4
`define INTR_SFR_ADDR 32'h400
`define CTRL_SFR_ADDR 32'h404
`define IO_ADDR_SFR_ADDR 32'h408
`define MEM_ADDR_SFR_ADDR 32'h40C

SFR: special function register.

然后写具体的register access, dma_reg_test.sv:

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
class dma_reg_test extends uvm_test;
`uvm_component_utils(dma_reg_test)

dma_model_env env;

write_sequence wr_seq;
read_sequence rd_seq;

function new(string name = "dma_reg_test",uvm_component parent=null);
super.new(name, parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
env=dma_model_env::type_id::create("env",this);
wr_seq=write_sequence::type_id::create("wr_seq",this);
rd_seq=read_sequence::type_id::create("rd_seq",this);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration

task run_phase(uvm_phase phase);
`uvm_info("TEST", "Objection Raised!", UVM_LOW)
phase.raise_objection(this);
wr_seq.t_data=32'hFFFF_0F0F;
wr_seq.t_addr=`INTR_SFR_ADDR;
wr_seq.start(env.dma_agnt.sequencer);

wr_seq.t_addr=`CTRL_SFR_ADDR;
wr_seq.t_data=32'h1234_5678;
wr_seq.start(env.dma_agnt.sequencer);
wr_seq.t_addr=`IO_ADDR_SFR_ADDR;
wr_seq.t_data=32'hABCD_EF12;
wr_seq.start(env.dma_agnt.sequencer);

wr_seq.t_addr=`MEM_ADDR_SFR_ADDR;
wr_seq.t_data=32'h9731_2345;
wr_seq.start(env.dma_agnt.sequencer);

rd_seq.t_addr=`INTR_SFR_ADDR;
rd_seq.start(env.dma_agnt.sequencer);

rd_seq.t_addr=`CTRL_SFR_ADDR;
rd_seq.start(env.dma_agnt.sequencer);

rd_seq.t_addr=`IO_ADDR_SFR_ADDR;
rd_seq.start(env.dma_agnt.sequencer);

rd_seq.t_addr=`MEM_ADDR_SFR_ADDR;
rd_seq.start(env.dma_agnt.sequencer);



phase.drop_objection(this);
endtask:run_phase
endclass:dma_reg_test

最后写好testbench.sv:

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
import uvm_pkg::*;
`include "uvm_macros.svh"

module testbench_top;
bit clk;
bit reset;

always #5 clk=~clk;

initial begin
reset=1;
#5 reset=0;
end

dma_if intf(clk, reset);

DMA DUT(.clk(intf.clk),
.reset(intf.reset),
.addr(intf.addr),
.wr_en(intf.wr_en),
.valid(intf.valid),
.wdata(intf.wdata),
.rdata(intf.rdata));

initial begin
uvm_config_db#(virtual dma_if)::set(uvm_root::get(), "*", "vif", intf);
$dumpfile("dump.vcd");
$dumpvars;
end

initial begin
run_test("dma_reg_test");
end
endmodule

运行脚本run.sh:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vcs -full64 -sverilog -ntb_opts uvm \
dma.sv \
dma_interface.sv \
dma_seq_item.sv \
dma_sequence.sv \
read_sequence.sv \
write_sequence.sv \
dma_sequencer.sv \
dma_driver.sv \
dma_monitor.sv \
dma_agent.sv \
dma_model_env.sv \
dma_sfr_defines.sv \
dma_reg_test.sv \
testbench.sv \
-debug_access+all 2>&1 | tee compile.log
./simv 2>&1 | tee result.log

运行结果:

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
65
66
67
68
69
70
71
72
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Feb 28 00:38 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test dma_reg_test...
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
uvm_test_top dma_reg_test - @460
env dma_model_env - @468
dma_agnt dma_agent - @484
driver dma_driver - @517
rsp_port uvm_analysis_port - @534
seq_item_port uvm_seq_item_pull_port - @525
monitor dma_monitor - @496
item_collected_port uvm_analysis_port - @508
sequencer dma_sequencer - @543
rsp_export uvm_analysis_export - @551
seq_item_export uvm_seq_item_pull_imp - @657
arbitration_queue array 0 -
lock_queue array 0 -
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
----------------------------------------------------------------
UVM_INFO dma_reg_test.sv(25) @ 0: uvm_test_top [TEST] Objection Raised!
UVM_INFO dma_driver.sv(27) @ 0: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 15: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 35: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 55: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 75: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 105: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 135: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 165: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 195: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 11
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[DRV_RUN] 8
[RNTST] 1
[TEST] 1
[TEST_DONE] 1
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 195
V C S S i m u l a t i o n R e p o r t
Time: 195
CPU Time: 0.370 seconds; Data structure size: 0.5Mb
Sat Feb 28 00:38:23 2026

生成的波形:

wave1

register access with RAL

这次是使用UVM RAL model的寄存器访问。
依旧是上面的DMA,测试流程和不用RAL的相比,需要加一个RAL model.

register class

RAL model可以从register class开始写。首先来看第一个寄存器,INTR:

1
2
3
regname     address            field description

INTR h400 31:16(MASK), 15:0(STATUS)

首先是经典的构建函数:

1
2
3
4
5
6
7
class intr extends uvm_reg;
`uvm_object_utils(intr)

function new(string name ="intr");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction
endclass

关于这里super.new(name, 32, UVM_NO_COVERAGE);,第二个参数是n_bits,代表的是寄存器的总位宽;第三个参数has_coverge,是覆盖率模型开关,具体参数再上面写过了,选择UVM_NO_COVERAGE是因为寄存器操作比较频繁,都开启的话很吃内存,这里先关闭提高仿真速度。

进行field的定义:

1
2
3
4
5
6
7
rand uvm_reg_field status;
rand uvm_reg_field mask;

function void build();
status=uvm_reg_field::type_id::create("status");
mask=uvm_reg_field::type_id::create("mask");
endfunction

在build()中还需要对register field进行一些配置:

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
...
status=uvm_reg_field::type_id::create("status");

status.configure(.parent(this),
.size(16),
.lsb_pos(0),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));

mask=uvm_reg_field::type_id::create("mask");

mask.configure(.parent(this),
.size(16),
.lsb_pos(16),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));
...
以上出现的配置项 解释
.parent(this) 指定当前field属于哪个register
.size(16) 设置field占据多少位
.lsb_pos(0) 起始位置,含义为lsb在寄存器中的偏移量
.access(“RW”) 设置的是field的存取权限
.volatile(0) 易变性。如果是1,这个值可能被硬件随时修改(比如说状态寄存器)
.reset(0) 复位值,代表了复位后把这个字段设置为多少
.has_reset(1) 有没有复位,1代表支持复位操作
.is_rand(1) 可随机化,randomize()的时候会被随机化
.individually_accessible(0) 独立寻址。当总线支持字节访问,而且field字节对齐,设置为1代表可以单独访问和修改这个字段

intr.sv:

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
class intr extends uvm_reg;
`uvm_object_utils(intr)

rand uvm_reg_field status;
rand uvm_reg_field mask;

function new(string name="intr");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction

function void build();
status=uvm_reg_field::type_id::create("status");
status.configure(.parent(this),
.size(16),
.lsb_pos(0),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));
mask = uvm_reg_field::type_id::create("mask");
mask.configure(.parent(this),
.size(16),
.lsb_pos(16),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));
endfunction
endclass

然后是ctrl.sv:

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
class ctrl extends uvm_reg;
`uvm_object_utils(ctrl)
rand uvm_reg_field start_dma;
rand uvm_reg_field w_count;
rand uvm_reg_field io_mem;
rand uvm_reg_field reserved;

function new(string name="ctrl");
super.new(name,32,UVM_NO_COVERAGE);
endfunction

function void build();
start_dma=uvm_reg_field::type_id::create("start_dma");
start_dma.configure(.parent(this),
.size(1),
.lsb_pos(0),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));

w_count=uvm_reg_field::type_id::create("w_count");
w_count.configure(.parent(this),
.size(8),
.lsb_pos(1),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));

io_mem=uvm_reg_field::type_id::create("io_mem");
io_mem.configure(.parent(this),
.size(1),
.lsb_pos(9),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));

reserved=uvm_reg_field::type_id::create("reserved");
reserved.configure(.parent(this),
.size(22),
.lsb_pos(10),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));
endfunction
endclass

io_addr.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class io_addr extends uvm_reg;
`uvm_object_utils(io_addr)
rand uvm_reg_field addr;

function new(string name="io_addr");
super.new(name, 32, UVM_NO_COVERAGE);
endfunction

function void build();
addr=uvm_reg_field::type_id::create("addr");
addr.configure(.parent(this),
.size(32),
.lsb_pos(0),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));
endfunction
endclass

mem_addr.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class mem_addr extends uvm_reg;
`uvm_object_utils(mem_addr)

rand uvm_reg_field addr;
function new(string name="mem_addr");
super.new(name,32,UVM_NO_COVERAGE);
endfunction

function void build();
addr=uvm_reg_field::type_id::create("addr");

addr.configure(.parent(this),
.size(32),
.lsb_pos(0),
.access("RW"),
.volatile(0),
.reset(0),
.has_reset(1),
.is_rand(1),
.individually_accessible(0));
endfunction
endclass

block class

然后可以把block环境搭建起来,首先是构造函数:

1
2
3
4
5
6
7
class dma_reg_model extends uvm_reg_block;
`uvm_object_utils(dma_reg_model)

function new(string name="");
super.new(name, build_coverage(UVM_NO_COVERAGE));
endfunction
endclass

依旧是选择不开启覆盖率模型。

把已经写好的register在这里面实例化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
rand intr reg_intr;
rand ctrl reg_ctrl;
rand io_addr reg_io_addr;
rand mem_addr reg_mem_addr;

reg_intr=intr::type_id::create("reg_intr");
reg_intr.build();
reg_intr.configure(this);

reg_ctrl = ctrl::type_id::create("reg_ctrl");
reg_ctrl.build();
reg_ctrl.configure(this);

reg_io_addr = io_addr::type_id::create("reg_io_addr");
reg_io_addr.build();
reg_io_addr.configure(this);

reg_mem_addr = mem_addr::type_id::create("reg_mem_addr");
reg_mem_addr.build();
reg_mem_addr.configure(this);

声明,构建对象,调用build函数,.configure(this)是指定寄存器的父节点。configure参数其实很长,但是其他的可以使用默认值。

构建出register之后还需要把它们注册到map映射,首先创建一个memory map:

1
default_map=create_map("my_map",0,4,UVM_LITTLE_ENDIAN);

设置default_map为我们创建的map,名为my_map,第二个参数是base address,代表了映射表的基地址,所有通过这个map访问的寄存器偏移量都是基于base address开始计算的;第三个参数是n_bytes,代表了总线位宽,这里的4就代表是32位宽的,这个也决定了相邻寄存器地址递增的步长;最后一个参数是字节顺序,这里选择的是小端模式。

决定了相邻寄存器地址递增的步长:这个和bus alignment有关。原因是,如果bus每次传输4字节的数据,那么让数据进行4字节对齐(寄存器的offset以4的倍数递增)可以方便数据搬运。

然后把register注册到map中:

1
2
3
4
default_map.add_reg(reg_intr, 'h0,"RW");//reg, offset, access
default_map.add_reg(reg_ctrl, 'h4, "RW");
default_map.add_reg(reg_io_addr, 'h8, "RW");
default_map.add_reg(reg_mem_addr, 'hC, "RW");

dma_reg_model.sv:

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
class dma_reg_model extends uvm_reg_block;
`uvm_object_utils(dma_reg_model)

rand intr reg_intr;
rand ctrl reg_ctrl;
rand io_addr reg_io_addr;
rand mem_addr reg_mem_addr;


function new(string name="");
super.new(name,build_coverage(UVM_NO_COVERAGE));
endfunction

function void build();
reg_intr=intr::type_id::create("reg_intr");
reg_intr.build();
reg_intr.configure(this);

reg_ctrl=ctrl::type_id::create("reg_ctrl");
reg_ctrl.build();
reg_ctrl.configure(this);

reg_io_addr=mem_addr::type_id::create("reg_io_addr");
reg_io_addr.build();
reg_io_addr.configure(this);

reg_mem_addr=mem_addr::type_id::create("reg_mem_addr");
reg_mem_addr.build();
reg_mem_addr.configure(this);


default_map=create_map("my_map", 0,4,UVM_LITTLE_ENDIAN);
default_map.add_reg(reg_intr,'h0,"RW");
default_map.add_reg(reg_ctrl,'h4,"RW");
default_map.add_reg(reg_io_addr, 'h8,"RW");
default_map.add_reg(reg_mem_addr, 'hC,"RW");

lock_model();
endfunction
endclass

lock_model(): 调用这个函数意味着这个uvm_reg_block以及他包含的所有register, field, maps都会进入只读状态,相当于给他打上封条,以后不能够在添加新的寄存器,修改寄存器的offset,用于保护模型的完整性。(这个操作也是递归的,会把block之下的所有子部件都锁住)
另外,他还会触发UVM的一些操作,递归地遍历整个模型,完成以下工作:

  • 地址解析:计算每个寄存器在全局的绝对地址。
  • 层次建立:整理所有的父子引用关系。
  • 属性缓存:一部分常用属性会被缓存。

adapter class

现在我们写好了block和register,为了实现对DUT的实际操作,还需要一个adapter.

首先是构建函数:

1
2
3
4
5
6
7
class dma_adapter extends uvm_reg_adapter;
`uvm_object_utils(dma_adapter)

function new(string name="dma_adapter");
super.new(name);
endfunction
endclass

需要写一个将RAL高层级指令翻译成agent能听懂的transaction的reg2bus函数:

1
2
3
4
5
6
7
8
9
10
function uvm_sequence_item reg2bus(const ref uvm_reg_bus_oprw);
dma_seq_item tx;//tx for transaction
tx=dma_seq_item::type_id::create("tx");

tx.wr_en=(rw_kind==UVM_WRITE);
tx.addr=rw.addr;
if(tx.wr_en) tx.wdata=rw.data;
if(!tx.wr_en) tx.rdata=rw.data;
return tx;
endfunction

哪里体现了从抽象指令到具体协议的翻译?这里的翻译其实是数据重新映射,这里UVM的原始指令是rw,他只知道kind,addr,data这三个信息,其他都不考虑在内。这里最后返回的总线包,他需要知道具体的硬件信号字段,比如wr_en,这是具体的硬件电路信息,抽象指令是不需要知道这个东西的。这里的控制还比较简单,如果是比较复杂的DUT应该会有更复杂的翻译过程。

还有bus2reg:

1
2
3
4
5
6
7
8
9
10
11
12
function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
dma_seq_item tx;

assert($cast(tx, bus_item))
else `uvm_fatal("","a bad thing has just happened in my_adapter")

rw.kind=tx.wr_en?UVM_WRITE:UVM_READ;
rw.addr=tx.addr;
rw.data=tx.rdata;

rw.status=UVM_IS_OK;
endfunction

$cast(): 这个是类似强制转化(caster!月批发癫),这里叫downcasting. 因为这里函数的第一个参数是uvm_sequence_item类型的,very “basic”. 直接使用wr_en, addr等其实父类没有这个成员,直接使用就会报错。这里$cast(tx,bus_item)的含义是检查bus_item是不是属于dma_seq_item(tx的类型),如果是就让tx直接指向bus_item,这样就可以成功访问了。

object-oriented,神奇吧!(不是

dma_adapter.sv:

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
class dma_adapter extends uvm_reg_adapter;
`uvm_object_utils(dma_adapter)

function new(string name = "dma_adapter");
super.new(name);
endfunction

function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op rw);
dma_seq_item tx;
tx = dma_seq_item::type_id::create("tx");

tx.wr_en = (rw.kind == UVM_WRITE);
tx.addr = rw.addr;
if (tx.wr_en) tx.wdata = rw.data;
if (!tx.wr_en) tx.rdata = rw.data;

return tx;
endfunction

function void bus2reg(uvm_sequence_item bus_item, ref uvm_reg_bus_op rw);
dma_seq_item tx;

assert( $cast(tx, bus_item) )
else `uvm_fatal("", "A bad thing has just happened in my_adapter")

rw.kind = tx.wr_en ? UVM_WRITE : UVM_READ;
rw.addr = tx.addr;
rw.data = tx.rdata;

rw.status = UVM_IS_OK;
endfunction
endclass

这里bus2reg传入ref的原因是需要修改rw;
reg2bus传入const ref是因为虽然不修改rw,但是直接传入ref可以节省开销,不用创造一个复制。

integrating block and adapter

这个需要在env中完成。

声明和构建block(regmodel)和adapter:

1
2
3
4
5
6
dma_reg_model regmodel;
dma_adapter m_adapter;

regmodel=dma_reg_model::type_id::create("regmodel",this);
regmodel.build();
m_adapter=dma_adapter::type_id::create("m_adapter",,get_full_name());

突然想起好像一直没说get_full_name()的作用,他是返回当前组件在整个UVM hierarchy的完整路径。

* this, get_full_name()

这两者本质是不同的。this是一个handle,指向了对象本身,get_full_name()返回的是一个字符串,比如说uvm_test_top.uvm_test_env.....,反映的是当前对象的hierarchical path;前者用于将当前对象加入hierarchy,后者反应当前对象在hierarchy中的位置。

* uvm create params

这里还有一个知识,是uvm默认的create函数。这个函数的前三个参数实际上是:(要构建的新对象的)identity,parent(父节点的handle),context(hierarchy path).

如果第三个参数不写,就会默认尝试从第二个参数那里,通过<parent_parameter>.get_full_name()拿到context.

一般将component才有父节点的概念,所以对于adapter,他不需要传入父节点,但是第三个参数context对adapter是有用的(指override),所以要写。

* trick?

之前说过有伏笔,这里回收一下。其实从构造函数就可以看出来,uvm_reg_block其实是component而不是object.(虽然注册的时候写的是object)

我们之前说,UVM主要class有三类,transaction, object, component.

为什么dma_reg_model(uvm_reg_block)其实是component,被注册为object?

这其实是一个特例设计,uvm_reg_block虽然是component,但是实际上不走component的创建流程,正常的component应该是有parent,在build_phase创建,自动进入hierarchy,但是uvm_reg_block是没有parent的。他不参与phase,不当作component使用。

原因是uvm_reg_block需要component的层次能力,需要name path,但是不能真的作为component使用,因此会有这种混血设计。

return to block & adapter

之后是把adapter和agent的sequencer连接起来:

1
regmodel.default_map.set_sequencer(.sequencer(dma_agnt.sequencer), .adapter(m_adapter));

给regmodel address map设置基地址:

1
regmodel.default_map.set_base_addr('h400);

dma_model_env.sv:

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
class dma_model_env extends uvm_env;
dma_agent dma_agnt;

dma_reg_model regmodel;
dma_adapter m_adapter;
`uvm_component_utils(dma_model_env)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
dma_agnt=dma_agent::type_id::create("dma_agnt",this);

regmodel=dma_reg_model::type_id::create("regmodel",this);
regmodel.build();
m_adapter=dma_adapter::type_id::create("m_adapter",,get_full_name());
endfunction:build_phase

function void connect_phase(uvm_phase phase);
regmodel.default_map.set_sequencer(.sequencer(dma_agnt.sequencer),.adapter(m_adapter));
regmodel.default_map.set_base_addr('h400);
endfunction:connect_phase
endclass:dma_model_env

use RAL

然后我们可以写通过RAL实现的register access.

RAL中通过使用write()完成数据写。

1
reg_model.reg_name.write(status, write_data);

uvm_reg::write()这个函数的参数很长,最基础的来说,需要status和write_data参数。

status是uvm_status_e类型,他表示register访问是否成功,在front door access中,这个值是driver/bus返回的response,在back door access中,这个值是由“能否找到HDL path”决定的。

1
2
3
4
5
uvm_status_e status;

reg.write(status,32'h1234);

if(status!=UVM_IS_OK) `uvm_error("RAL","register write failed.")

如果是读的话,就要用read()

1
reg_model_reg_name.read(status, read_data);

可以这样访问寄存器:

1
2
3
4
5
6
7
8
9
10
11
12

//Write to the Registers
regmodel.reg_intr.write(status, 32'h1234_1234);
regmodel.reg_ctrl.write(status, 32'h1234_5678);
regmodel.reg_io_addr.write(status, 32'h1234_9ABC);
regmodel.reg_mem_addr.write(status, 32'h1234_DEF0);

//Read from the register
regmodel.reg_intr.read(status, rdata);
regmodel.reg_ctrl.read(status, rdata);
regmodel.reg_io_addr.read(status, rdata);
regmodel.reg_mem_addr.read(status, rdata);

dma_reg_seq.sv:

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
class dma_reg_seq extends uvm_sequence;
`uvm_object_utils(dma_reg_seq)

dma_reg_model regmodel;

function new(string name="");
super.new(name);
endfunction

task body();
uvm_status_e status;
uvm_reg_data_t incoming;
bit [31:0] rdata;

if(starting_phase !=null)
starting_phase.raise_objection(this);

//Write to the Registers
regmodel.reg_intr.write(status, 32'h1234_1234);
regmodel.reg_ctrl.write(status, 32'h1234_5678);
regmodel.reg_io_addr.write(status, 32'h1234_9ABC);
regmodel.reg_mem_addr.write(status, 32'h1234_DEF0);

//Read from the registers
regmodel.reg_intr.read(status, rdata);
regmodel.reg_ctrl.read(status, rdata);
regmodel.reg_io_addr.read(status, rdata);
regmodel.reg_mem_addr.read(status, rdata);

if(starting_phase !=null)
starting_phase.drop_objection(this);

endtask

endclass

这个raise和drop和phase那个是类似的。starting_phase代表了当前sequence是在哪个phase被启动的,在uvm_sequence_base定义。当通过default_sequence(config_db)中启动sequence时,自动启动starting_phase=main_phase. 但是如果是手动start,默认的starting_phase = null.

在default_sequence中,要让sequence自己控制obejction,不然phase会立即结束;如果是手动start,

1
2
3
phase.raise_objection(this);
seq.start(sequencer);
phase.drop_objection(this);

sequence就不要再raise,否则计数混乱。

最后是test,在这里实现register sequence的定义,构建,和start.

先来写一个dma_model_base_test.sv:

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
class dma_model_base_test extends uvm_test;
`uvm_component_utils(dma_model_base_test)

dma_model_env env;

function new(string name="dma_model_base_test",uvm_component parent=null);
super.new(name,parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
env=dma_model_env::type_id::create("env",this);
endfunction:build_phase

virtual function void end_of_elaboration();
print();
endfunction:end_of_elaboration

function void report_phase(uvm_phase phase);
uvm_report_server svr;
super.report_phase(phase);

svr=uvm_report_server::get_server();
if(svr.get_severity_count(UVM_FATAL)+svr.get_severity_count(UVM_ERROR)>0) begin
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
`uvm_info(get_type_name(), "---- TEST FAIL ----", UVM_NONE)
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE) end
else begin
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
`uvm_info(get_type_name(), "---- TEST PASS ----", UVM_NONE)
`uvm_info(get_type_name(), "---------------------------------------", UVM_NONE)
end
endfunction:report_phase
endclass:dma_model_base_test

report_phase是最后一个phase,他的作用是战后总结。这里需要写他是因为这次需要输出到底有没有通过测试。

uvm_report_server: UVM中所有的uvm_info, uvm_error, uvm_fatal会汇集到uvm_report_server,他是一个类似日志处理器。

首先需要通过:uvm_report_server::get_server();拿到对象,之后就可以调用很多method了,比如get_severity_count(uvm_severity severity)获取指定严重程度的消息总数;get_id_count(string id)获取指定id(info/error里面的第一个字符串)的消息总数。

UVM_ERROR: 实际结果和预期不符,但是可以稍后结算;
UVM_FATAL: 炸缸了,马上退出仿真。

severity: 严重性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class dma_reg_test extends dma_model_base_test;
`uvm_component_utils(dma_reg_test)
dma_reg_seq reg_seq;

function new(string name="dma_reg_test",uvm_component parent=null);
super.new(name,parent);
endfunction:new

virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
reg_seq=dma_reg_seq::type_id::create("reg_seq");
endfunction:build_phase

task run_phase(uvm_phase phase);
phase.raise_objection(this);
if(!reg_seq.randomize()) `uvm_error("","randomize failed.")
reg_seq.regmodel=env.regmodel;
reg_seq.starting_phase = phase;
reg_seq.start(env.dma_agnt.sequencer);
phase.drop_objection(this);

phase.phase_done.set_drain_time(this,50);
endtask:run_phase
endclass:dma_reg_test

phase.phase_done.set_drain_time(this,50);用来设置排水时间(drain time).
它的作用是:当所有主要任务(Sequence)都完成后,让仿真再多跑一会儿,确保剩下的数据处理完再关门,防止立即终止仿真导致的意外发生。

run

写一个run.sh:

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
vcs -full64 -sverilog -ntb_opts uvm \
dma.sv \
dma_interface.sv \
dma_seq_item.sv \
dma_sequence.sv \
read_sequence.sv \
write_sequence.sv \
dma_sequencer.sv \
dma_driver.sv \
dma_monitor.sv \
ctrl.sv \
intr.sv \
io_addr.sv \
mem_addr.sv \
dma_reg_model.sv \
dma_reg_seq.sv \
dma_adapter.sv \
dma_agent.sv \
dma_model_env.sv \
dma_sfr_defines.sv \
dma_model_base_test.sv \
dma_reg_test.sv \
testbench.sv \
-debug_access+all 2>&1 | tee compile.log
./simv 2>&1 | tee result.log

终端输出:

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
65
66
67
68
69
70
71
72
73
74
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 3 15:29 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

UVM_INFO @ 0: reporter [RNTST] Running test dma_reg_test...
----------------------------------------------------------------
Name Type Size Value
----------------------------------------------------------------
uvm_test_top dma_reg_test - @460
env dma_model_env - @468
dma_agnt dma_agent - @480
driver dma_driver - @535
rsp_port uvm_analysis_port - @552
seq_item_port uvm_seq_item_pull_port - @543
monitor dma_monitor - @514
item_collected_port uvm_analysis_port - @526
sequencer dma_sequencer - @561
rsp_export uvm_analysis_export - @569
seq_item_export uvm_seq_item_pull_imp - @675
arbitration_queue array 0 -
lock_queue array 0 -
num_last_reqs integral 32 'd1
num_last_rsps integral 32 'd1
----------------------------------------------------------------
UVM_INFO dma_driver.sv(27) @ 0: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 15: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 35: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 55: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 75: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 105: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 135: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO dma_driver.sv(27) @ 165: uvm_test_top.env.dma_agnt.driver [DRV_RUN] Driving one item
UVM_INFO /opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_objection.svh(1273) @ 245: reporter [TEST_DONE] 'run' phase is ready to proceed to the 'extract' phase
UVM_INFO dma_model_base_test.sv(30) @ 245: uvm_test_top [dma_reg_test] ---------------------------------------
UVM_INFO dma_model_base_test.sv(31) @ 245: uvm_test_top [dma_reg_test] ---- TEST PASS ----
UVM_INFO dma_model_base_test.sv(32) @ 245: uvm_test_top [dma_reg_test] ---------------------------------------

--- UVM Report Summary ---

** Report counts by severity
UVM_INFO : 13
UVM_WARNING : 0
UVM_ERROR : 0
UVM_FATAL : 0
** Report counts by id
[DRV_RUN] 8
[RNTST] 1
[TEST_DONE] 1
[dma_reg_test] 3
$finish called from file "/opt/Synopsys/VCS2018/vcs/O-2018.09-SP2/etc/uvm/base/uvm_root.svh", line 439.
$finish at simulation time 245
V C S S i m u l a t i o n R e p o r t
Time: 245
CPU Time: 0.350 seconds; Data structure size: 0.5Mb
Tue Mar 3 15:29:35 2026

生成的波形:

wave2

UVM barrier

UVM barrier是另一种进程之间的同步机制。

UVM barrier就像是比赛开始前的预备机制,也就是说需要等到所有(被指定的)进程都站到起跑线了才允许它们继续执行。

这里有一个synchronization point的概念,类似为集合点,需要设置我们要等待几个进程到达synchronization point之后才会继续向后执行,如果有进程没有到达,就会一直等待。

UVM barrier有以下几个内置的method:

new()

显然是构造函数。函数原型是:

1
function new(string name="", int threshold=0);

接受的参数是barrier的name和threshold,都有默认值。

wait_for()

这个method的含义就是等待足够多的进程到达synchronization point,之后大家继续执行。

set_threshold()

用于重新设定threshold.

get_threshold()

获取当前的threshold值。

get_num_waiters()

返回当前被卡在barrier的进程数。

reset()

函数原型为:

1
virtual function void reset(bit wakeup=1);

reset()会把waiters的计数清零,barrier会重新生效,也就是后续到达synchronization point的进程会被再次拦截,直到满足阈值条件,阈值不会改变。

关于bit wakeup这个参数,默认情况为1,含义是将所有当前在等的进程唤醒,不再被阻碍,通俗的讲就是把barrier被卡住的进程全部放走;但是如果传入了0,那么已经在barrier中的进程依然会被阻塞,然而计数器已经归零了,也就是说已经在等的进程全都不作数,需要再多等threshold个进程到达。

要小心死锁风险喵,老大!

set_auto_reset()

函数原型:

1
virtual function void set_auto_reset(bit value=1);

这个是自动reset开关,默认的参数是1,也就是说当barrier中进程数达到threshold时,放行已经到达synchronization
point的进程,之后再来的进程会被卡住直到下一次满足threshold条件;如果传入的参数是0,那么barrier放行当前进程之后就会失效,直到下一次的reset.

cancel()

让waiters的计数减少1.
这个一般是被阻塞的进程被终止或者是唤醒导致离开barrier.

uvm_barrier useage

如果要使用uvm_barrier,需要创建uvm_barrier对象,之后设置一些进程让他们会被barrier阻塞,然后在进程内部wait_for().

在一个进程内部,如果调用了wait_for(),就意味着在本行进入了synchronization point.

uvm_barrier example

new & wait_for

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
module uvm_barrier_ex;
uvm_barrier barrier;

initial begin
barrier=new("barrier",3);

fork
begin
$display($time, "inside process a.");
#20;
$display($time, "process a completed.");
$display($time,"process a waiting for barrier.");
barrier.wait_for();
$display($time, "process a after the wait_for.");
end

begin
$display($time,"inside process b.");
#10;
$display($time,"process b completed.");
$display($time,"process b waiting for barrier.");
barrier.wait_for();
$display($time,"process b after the wait_for.");
end

begin
$display($time,"inside process c.");
#30;
$display($time,"process c completed.");
$display($time,"process c waiting for barrier.");
barrier.wait_for();
$display($time,"process c after wait_for");
end

begin
$display($time,"inside process d.");
#5;
$display($time,"process d completed.");
$display($time,"process d waiting for barrier.");
barrier.wait_for();
$display($time,"process d after wait_for");
end
join
end
endmodule

run.sh:

1
2
vcs -full64 -sverilog -ntb_opts uvm uvm_barrier_ex.sv -debug_access+all  2>&1 | tee compile.log
./simv 2>&1 | tee result.log

输出结果是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 3 17:46 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0inside process a.
0inside process b.
0inside process c.
0inside process d.
5process d completed.
5process d waiting for barrier.
10process b completed.
10process b waiting for barrier.
20process a completed.
20process a waiting for barrier.
20process d after wait_for
20process b after the wait_for.
20process a after the wait_for.
30process c completed.
30process c waiting for barrier.
V C S S i m u l a t i o n R e p o r t
Time: 30
CPU Time: 0.240 seconds; Data structure size: 0.4Mb
Tue Mar 3 17:46:50 2026

uvm_barrier in function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name, int delay);
$display($time, " [%s] starting the progress",p_name);
$display($time, " [%s] injecting th delay of %0d",p_name, delay);
#delay;
$display($time, " [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

initial begin
barrier=new("barrier",3);


fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);
join
end
endmodule

运行结果是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 3 21:06 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the progress
0 [A] injecting th delay of 30
0 [B] starting the progress
0 [B] injecting th delay of 10
0 [C] starting the progress
0 [C] injecting th delay of 20
0 [D] starting the progress
0 [D] injecting th delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
20 [C] before the wait_for
20 [D] after the wait_for
20 [B] after the wait_for
20 [C] after the wait_for
30 [A] before the wait_for
V C S S i m u l a t i o n R e p o r t
Time: 30
CPU Time: 0.320 seconds; Data structure size: 0.4Mb
Tue Mar 3 21:06:15 2026

set_threshold

通过set_threshold()设定阈值。

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
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name, int delay);
$display($time, " [%s] starting the progress",p_name);
$display($time, " [%s] injecting th delay of %0d",p_name, delay);
#delay;
$display($time, " [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

initial begin
barrier=new("barrier",3);
barrier.set_threshold(2);


fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);
join
end
endmodule

运行结果:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 3 21:23 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the progress
0 [A] injecting th delay of 30
0 [B] starting the progress
0 [B] injecting th delay of 10
0 [C] starting the progress
0 [C] injecting th delay of 20
0 [D] starting the progress
0 [D] injecting th delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
10 [D] after the wait_for
10 [B] after the wait_for
20 [C] before the wait_for
30 [A] before the wait_for
30 [C] after the wait_for
30 [A] after the wait_for
V C S S i m u l a t i o n R e p o r t
Time: 30
CPU Time: 0.320 seconds; Data structure size: 0.4Mb
Tue Mar 3 21:23:56 2026

get_threshold & get_num_waiters

返回调用当时这一时刻的阈值和正在等待的进程数。

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
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name, int delay);
$display($time, " [%s] starting the progress",p_name);
$display($time, " [%s] injecting th delay of %0d",p_name, delay);
#delay;
$display($time, " [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

task monitor_process();
#15;
$display($time, " [monitor] threshold value of barrier is %0d",barrier.get_threshold());
$display($time, " [monitor] number of process waiting are %0d", barrier.get_num_waiters());
endtask

initial begin
barrier=new("barrier",3);


fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);

monitor_process();
join
end
endmodule

运行结果:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 3 21:31 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the progress
0 [A] injecting th delay of 30
0 [B] starting the progress
0 [B] injecting th delay of 10
0 [C] starting the progress
0 [C] injecting th delay of 20
0 [D] starting the progress
0 [D] injecting th delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
15 [monitor] threshold value of barrier is 3
15 [monitor] number of process waiting are 2
20 [C] before the wait_for
20 [D] after the wait_for
20 [B] after the wait_for
20 [C] after the wait_for
30 [A] before the wait_for
V C S S i m u l a t i o n R e p o r t
Time: 30
CPU Time: 0.200 seconds; Data structure size: 0.4Mb
Tue Mar 3 21:31:34 2026

如果把monitor延迟改成5,结果是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the progress
0 [A] injecting th delay of 30
0 [B] starting the progress
0 [B] injecting th delay of 10
0 [C] starting the progress
0 [C] injecting th delay of 20
0 [D] starting the progress
0 [D] injecting th delay of 5
5 [D] before the wait_for
5 [monitor] threshold value of barrier is 3
5 [monitor] number of process waiting are 1
10 [B] before the wait_for
20 [C] before the wait_for
20 [D] after the wait_for
20 [B] after the wait_for
20 [C] after the wait_for
30 [A] before the wait_for
V C S S i m u l a t i o n R e p o r t

reset

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
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name,int delay);
$display($time, " [%s] starting the process",p_name);
$display($time, " [%s] injecting the delay of %0d",p_name,delay);
#delay;
$display($time," [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

task reset_process();
#32;
barrier.reset(1);
endtask

initial begin
barrier=new("barrier");
barrier.set_threshold(3);

fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);
reset_process();
join
end
endmodule

输出结果是:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 3 23:58 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the process
0 [A] injecting the delay of 30
0 [B] starting the process
0 [B] injecting the delay of 10
0 [C] starting the process
0 [C] injecting the delay of 20
0 [D] starting the process
0 [D] injecting the delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
20 [C] before the wait_for
20 [D] after the wait_for
20 [B] after the wait_for
20 [C] after the wait_for
30 [A] before the wait_for
32 [A] after the wait_for
V C S S i m u l a t i o n R e p o r t
Time: 32
CPU Time: 0.310 seconds; Data structure size: 0.4Mb
Tue Mar 3 23:58:25 2026

我们可以看到这次执行了A的after wait_for.
之前没有执行是因为阈值为3,BCD都放行之后barrier被重置,就会导致A最后被卡住,无法执行after wait_for.

当然这是因为选择了barrier.reset(1),如果是reset(0)的话…

好吧其实是和之前阈值3是一样的,因为#32 reset()的时候已经只剩下A被卡住了。但是如果修改到#15的话…

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
import uvm_pkg::*;
`include "uvm_macros.svh"
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name,int delay);
$display($time, " [%s] starting the process",p_name);
$display($time, " [%s] injecting the delay of %0d",p_name,delay);
#delay;
$display($time," [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

task reset_process();
#15;
barrier.reset(0);
endtask

task info();
#50;
$display($time, " threshold value is %0d",barrier.get_threshold());
$display($time," number of waiters is %0d", barrier.get_num_waiters());
endtask

initial begin
barrier=new("barrier");
barrier.set_threshold(3);

fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);
reset_process();
info();
join


end
endmodule
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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 4 00:11 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the process
0 [A] injecting the delay of 30
0 [B] starting the process
0 [B] injecting the delay of 10
0 [C] starting the process
0 [C] injecting the delay of 20
0 [D] starting the process
0 [D] injecting the delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
20 [C] before the wait_for
30 [A] before the wait_for
50 threshold value is 3
50 number of waiters is 2
V C S S i m u l a t i o n R e p o r t
Time: 50
CPU Time: 0.210 seconds; Data structure size: 0.4Mb
Wed Mar 4 00:11:16 2026

那就是BD进入之后清空计数,同时只有AC后面会进来,计数永远无法到达3的真实…

另:不要试图共情,因为本身就是死锁。

set_auto_reset

如果是set_auto_reset(0),代表着barrier只会生效一次:

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
import uvm_pkg::*;
`include "uvm_macros.svh"
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name,int delay);
$display($time, " [%s] starting the process",p_name);
$display($time, " [%s] injecting the delay of %0d",p_name,delay);
#delay;
$display($time," [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

initial begin
barrier=new("barrier");
barrier.set_threshold(3);
barrier.set_auto_reset(0);

fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);
join
end
endmodule

结果就是A也会after wait_for:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 4 16:14 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the process
0 [A] injecting the delay of 30
0 [B] starting the process
0 [B] injecting the delay of 10
0 [C] starting the process
0 [C] injecting the delay of 20
0 [D] starting the process
0 [D] injecting the delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
20 [C] before the wait_for
20 [D] after the wait_for
20 [B] after the wait_for
20 [C] after the wait_for
30 [A] before the wait_for
30 [A] after the wait_for
V C S S i m u l a t i o n R e p o r t
Time: 30
CPU Time: 0.270 seconds; Data structure size: 0.4Mb
Wed Mar 4 16:14:00 2026

如果是set_auto_reset(1),代表会自动复位,那么A就会被拦截,当然之前没写set_auto_reset(1)默认情况也是自动复位。

cancel

wait_for的计数会被cancel()减1:

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
import uvm_pkg::*;
`include "uvm_macros.svh"
module uvm_barrier_ex;
uvm_barrier barrier;

task automatic process(input string p_name,int delay);
$display($time, " [%s] starting the process",p_name);
$display($time, " [%s] injecting the delay of %0d",p_name,delay);
#delay;
$display($time," [%s] before the wait_for",p_name);
barrier.wait_for();
$display($time, " [%s] after the wait_for",p_name);
endtask

task cancel_process();
#19;
$display($time, " before cancel, the number of waiters is %0d", barrier.get_num_waiters());
barrier.cancel();

$display($time, " after cancel, the number of waiters is %0d", barrier.get_num_waiters());

endtask
initial begin
barrier=new("barrier");
barrier.set_threshold(2);

fork
process("A",30);
process("B",10);
process("C",20);
process("D",5);
join
end
endmodule

运行结果:

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
Chronologic VCS simulator copyright 1991-2018
Contains Synopsys proprietary information.
Compiler version O-2018.09-SP2_Full64; Runtime version O-2018.09-SP2_Full64; Mar 4 16:22 2026
----------------------------------------------------------------
UVM-1.1d.Synopsys
(C) 2007-2013 Mentor Graphics Corporation
(C) 2007-2013 Cadence Design Systems, Inc.
(C) 2006-2013 Synopsys, Inc.
(C) 2011-2013 Cypress Semiconductor Corp.
----------------------------------------------------------------

*********** IMPORTANT RELEASE NOTES ************

You are using a version of the UVM library that has been compiled
with `UVM_NO_DEPRECATED undefined.
See http://www.eda.org/svdb/view.php?id=3313 for more details.

You are using a version of the UVM library that has been compiled
with `UVM_OBJECT_MUST_HAVE_CONSTRUCTOR undefined.
See http://www.eda.org/svdb/view.php?id=3770 for more details.

(Specify +UVM_NO_RELNOTES to turn off this notice)

0 [A] starting the process
0 [A] injecting the delay of 30
0 [B] starting the process
0 [B] injecting the delay of 10
0 [C] starting the process
0 [C] injecting the delay of 20
0 [D] starting the process
0 [D] injecting the delay of 5
5 [D] before the wait_for
10 [B] before the wait_for
10 [D] after the wait_for
10 [B] after the wait_for
20 [C] before the wait_for
30 [A] before the wait_for
30 [C] after the wait_for
30 [A] after the wait_for
V C S S i m u l a t i o n R e p o r t
Time: 30
CPU Time: 0.260 seconds; Data structure size: 0.4Mb
Wed Mar 4 16:22:23 2026

由于计数在C进入之前减少1,那么C进入以后计数依然是2,所以这次A是可以执行after wait_for的。

UVM heartbeat

还有heartbeat证明人还没死

UVM heartbeat可以让environments轻松的确定他的descendants是活着还是死了,可以检测是否存在死锁或者是挂起的component.

UVM heartbeat是在testbench中被设定的,如果他观察到testbench长时间没动静,就会发送fatal信息并且强行终止仿真。

设置UVM heartbeat可以即使终止仿真,释放资源,最重要的还是能够记录仿真结束时的时间,帮助检查是哪个组件发生了死锁。

UVM heartbeat也有很多内置的method:

new()

构造函数,原型为:

1
function new(string name, uvm_component cntxt, uvm_objection objection=null)

name代表了heartbeat在UVM树中的名字;uvm_component cntxt是监控的上下文层级,定义了heartbeat关注的组件范围;uvm_objection objection=null可以将heartbeat和UVM objection的机制捆绑。如果不传入参数,heartbeat是一个独立的监控器,时间长没检测到活跃就会爆出fatal;但是如果传入了objection,heartbeat就会变成objection的daemon,他会进行很多自动处理,同时防止过早结束,只要objection的heart还存在,objection就不会被以外drop导致仿真错误/提前退出。

UVM_objection_callback是heartbeat内部运作的一部分。heartbeat其实是派生自uvm_callback,通过监听通特定事件检查有没有组件调用heartbeat(),如果没有,则判定当前objection已死。

set_heartbeat()

1
2
3
4
function void set_heartbeat(
uvm_event#(uvm_object) e,
ref uvm_component comps[$]
);

相当于派发任务:检测谁,什么时候检测。

uvm_event#(uvm_object) e: 这个是heartbeat的脉搏,因为它本身不会自动运行,是依靠外部事件触发检查逻辑。这里的含义是,每当触发一次事件e,uvm_heartbeat内部计数器加1,如果设置了wait_period为100,如果e触发100次且器件没有任何组件通过heartbeat()汇报,就会出现uvm_fatal.

ref uvm_component comps[$]: 这个是组件队列,用来告诉heartbeat哪些组件是关键路径上的。ref依旧是节省开销。
如果一个component出现在comps中,他就有打卡的义务,一般监控的范围是monitor, scoreboard, driver等,只要它们还在工作就要调用hb.inst.heartbeat(this)打卡。

set_mode()

1
2
3
function uvm_heartbeat_modes set_model(
uvm_heartbeat_modes mode=UVM_NO_HB_MODE
);

设置公司打卡模式:

UVM_ALL_ACTIVE: 在规定的wait_period时间内,,comps队列中的每个组件都必须至少调用一次heartbeat()

UVM_ANY_ACTIVE: 在周期之内,只要名单中有一个组件完成打卡,则心跳继续。

UVM_ONE_ACTIVE: 在周期之内,,必须是有且仅有一个组件调用heartbeat().

默认值UVM_NO_HB_MODE其实是不手动设置modes的话heartbeat不会生效。
返回值是返回设置之前的旧模式类型。

add()

1
function void add(uvm_component comp);

将一个component添加到被检测名单。

remove()

1
function void remove(uvm_component comp);

将指定component从被检测名单中移除。

start()

1
2
3
function void start(
uvm_event#(uvm_object) e=null
);

开启heartbeat.

stop()

停止heartbeat.

UVM heartbeat example

add uvm_heartbeat in testbench

testbench中会启动一个comp_a,comp_a有一个内含50的延迟。

component_a.sv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class component_a extends uvm_component;
`uvm_component_utils(component_a)

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


virtual task run_phase(uvm_phase phase);
phase.raise_objection(this);

`uvm_info(get_type_name(), $sformatf(" [comp_a] objection raised "),UVM_LOW)

for(int i=0;i<10;i++) begin
`uvm_info(get_type_name(),$sformatf(" [comp_a] Idx-%0d raising objection", i),UVM_LOW)

heart.raise.objection(this);
#50;
end
phase.drop_objection(this);
endtask:run_phase
endclass:component_a

还需要在常规的env.sv中加入heartbeat的逻辑:

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
class environment extends uvm_env;
component_a comp_a;

uvm_heartbeat heart_beat;
uvm_event hb_event;

`uvm_component_utils(environment)

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

function void build_phase(uvm_phase phase);
super.build_phase(phase);
comp_a=component_a::type_id::create("comp_a",this);
heart_beat=new("heart_beat",this,obje);
hb_event=new("hb_event");
endfunction:build_phase


function void connect_phase(uvm_phase phase);
uvm_component hb[$];
heart_beat.set_mode(UVM_ALL_ACTIVE);
heart_beat.set_heartbeat(hb_event, hb);
heart_beat.add(comp_a);
endfunction:connect_phase

virtual task run_phase(uvm_phase phase);
heart_beat.start(hb_event);

repeat(10) begin
#100;
`uvm_info(get_type_name(),$sformatf(" [ Env ] Triggering hb_event"),UVM_LOW)
hb_event.trigger();
end
endtask:run_phase
endclass:environment

在以上代码中,

1
2
3
4
5
uvm_heartbeat heart_beat;
uvm_event hb_event;

heart_beat=new("heart_beat",this,obje);
hb_event=new("hb_event");

完成了heartbeat的定义和创建。
heart_beat=new("heart_beat",this,obje)

fatal from heartbeat

add multiple components to heartbeat

从零开始的DFT工程师! homepage

project homepage is here.