精益求精
UVM中TLM的使用非常方便,通过简单的connect()函数,就可以将两个不相关的对象紧紧联系在一起。为什么会如此简单,其背后的原理是什么呢?今天抽空分析了UVM的源代码,基本上了解了put_port/put_imp的实现机制。于是借用UVM的实现原理,我简单了编写了一个小程序,抛掉不必要的繁文缛节,基本上能够展示其背后的实现原理, 请参考代码注释。
代码如下:
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
/**
* * tlm.sv
* * 说明: 通过简单的模型来阐述 UVM/OVM中TLM uvm_put_port
* * 和uvm_port_imp直接连接的基本原理。直接编译运行即可。
* * 作者:Zhu,YH
*/
virtual class port_base#(type T=int) ; // 构造一个抽象类
typedef port_base#(T) this_type;
protected this_type port_handle; // 申明一个对象句柄(指针)
pure virtual task put_t(T trans); // 纯虚函数,put_port和put_imp都必须以它为基类
function void connect(this_type port); // connect仅仅是实现对象句柄的赋值
port_handle = port;
endfunction // connect
endclass // port_base
class put_port#(type T=int) extends port_base#(T);
virtual task put_t(T trans);
port_handle.put_t(trans);
endtask // put_t
endclass // put_port
class put_imp#(type T=int, type IMP=int) extends port_base#(T);
local IMP m_imp; // imp表示具体实现put_t()函数的对象句柄。
function new(IMP imp);
m_imp = imp;
endfunction // new
virtual task put_t(T trans);
m_imp.put_t(trans); // 调用实现对象对应的put_t()函数
endtask // put_t
endclass // put_imp
class producer;
put_port#(int) m_put_port;
function new();
m_put_port = new();
endfunction // new
task run();
for(int i=0; i<10; i++) begin // 顺序产生10个int类型的transaction.
m_put_port.put_t(i);
end
endtask // run
endclass // producer
class consumer;
put_imp#(int, consumer) m_put_export;
function new();
m_put_export = new(this); //将consumer句柄传递给put_imp中imp变量,从而实现接管put_t()最终实现
endfunction // new
task put_t(int trans);
$display("[INFO]: Got the transaction:%d", trans);
endtask // put_t
endclass // consumer
module top;
producer p;
consumer c;
initial begin
p = new();
c = new();
p.m_put_port.connect(c.m_put_export); //通过句柄赋值进行连接
p.run();
#10 $finish;
end
endmodule
总共需要5个类,其中一个是抽象类(port_base),必须保证相互连接的两个对象port/export/imp都属于同一个基类,这样句柄赋值才能够类型一致。另外,要注意的是,为了调用consumer的真正put_t()的实现,需要借用put_imp这个桥梁,put_imp对象对put_t()的实现仅仅是调用其m_imp对象的put_t()(第31-33行),用这种方式实现了函数层次的传递。
完成整个TLM put_port/put_imp用到了面向对象设计中的继承、组合、多态的技术。其中抽象类 port_base提供了一个数据接口port_handle,和两个任务接口:put_t()和connect(). put_t()表示需要进行连接的任务名称,connect()的目的是进行对象句柄赋值。第13-15行可以看出,connect()的实现非常简单。其中用到了systemverilog中对象的特性:参考文章:systemverilog语法浏览。
类对象通过引用传递。其他Systemverilog类型通过值传递。
因此,port_handle看作一个指针,用它来指向最终想要的目标对象,通过调用目标对象的相应方法,就可以完成方法调用的层次化传递,从而达成TLM的功能。
UVM中,实现了port_base, put_port, put_imp三个类,分别对应于uvm_port_base, uvm_blocking_put_port, uvm_blocking_put_imp,他们之间的UML关系示意图如下:
运行结果如下:
# [INFO]: Got the transaction: 0
# [INFO]: Got the transaction: 1
# [INFO]: Got the transaction: 2
# [INFO]: Got the transaction: 3
# [INFO]: Got the transaction: 4
# [INFO]: Got the transaction: 5
# [INFO]: Got the transaction: 6
# [INFO]: Got the transaction: 7
# [INFO]: Got the transaction: 8
# [INFO]: Got the transaction: 9
# ** Note: $finish : tlm.sv(68)
# Time: 10 ns Iteration: 0 Instance: /top