芯有所想

精益求精

uvm_blocking_put_port/uvm_blocking_put_imp实现的简单模型

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关系示意图如下:

tlm_put

运行结果如下:


# [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