UVM实战指南第部分

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

(*)题外话:TLM可能是UVM中最重要的概念,掌握了TLM,就可以开场尝试编写一些小程序了。

翻译这篇文章,也是为了稳固加强对TLM的理解。

(*)几个名词:transaction翻译为事务或者交易;packet翻译为封包,packet属于transaction;monitor翻译为监视器;driver翻译为驱动器;scoreboard翻译为记分牌;有些词汇直接被运用到UVM源代码上,所以有时候用英文更容易描述清楚。

(*)语言的目的是为了交流,翻译不是为了纯粹的语言转换,而是为了传递思想。

4.6 UVM中事务级建模(TLM)
20多年前,设计者从门级转向RTL级。

这次转换来自于标准Verilog/VHDL的RTL编码风格,以及RTL综合实现工具的推出。

使用RTL最大的好处是让设计者更多的专注于时序行为的设计以及功能的正确性,而很少考虑门级相关设计。

TLM(事务级建模)同样在抽象级别上更进了一步,在设计与验证领域都有出现。

通过TLM, 中心放在系统级别的各种事务流的建模,而更少关心时钟级别的行为。

TLM在测试向量中已经使用多年。

通常,在产生鼓励与覆盖率检查的时候使用事务而不是用时钟级别建模,这种方式就是TLM. 为了验证RTL级别的DUT(需要测试的模块),测试向量使用事务
第 1 页
发生器(transactor)(有时也称为总线功能模型(BFM)),将RTL 级与事务级进展转换。

在UVM中,此事务发生器也被叫做驱动(driver)或者收集器(collector)。

TLM中,事务通过方法调用与类对象来建模。

使用事务级而不是信号级别来建模有几个显著的好处:
•TLM比RTL更简洁,仿真速度快。

•TLM模型的抽象级别更高,更加契合验证工程师或设计工程师对内部功能的考虑,从而使得建模更简单,并且更容易被其他工程师理解。

•TLM模型将不符合复用的局部移到模型之外,因此TLM很适合复用。

并且,TLM使用面向对象的技术,比方继承、实现与接口别离的技术。

TLM的采纳依赖于标准的TLM建模技术的出现,就像RTL综合流程的采纳归功于标准RTL编码风格的实现。

幸运的是,近些年来,几个重要的标准TLM应用程序接口(API)得到定义。

在EDA与ESL 领域,两个最重要的标准是开放SystemC方案〔OSCI〕的以及标准。

标准是一个简单通用的TLM API, 用来建模消息传递。

在消息传递时,对象(事务)在组件之间传递的方式与封包在网络之间传递的方式类似。

第 2 页
在发送封包的消息传递中,发送端与接收端之间没有共享的状态,他们之间的通讯讯息仅仅包含在消息中。

标准能够用来开发SystemC中的高速虚拟平台模型。

标准特别被用作片上存储映射的总线系统,包含许多能够进展片上总线互联的整合复用模块.
与是互相独立的标准,满足不同的需要。

有人可能通过其命名方式认为优于,但是实际上并不是这样。

UVM提供的TLM 类与API是基于标准的。

这是因为TLM通用消息传递语法很好的满足了多种验证组件的事务级建模。

也适合多种语言之间的通信建模,比方SystemVerilog, SystemC以及e语
言之间的建模。

UVM中接口甚至可以用来与SystemC中的模型进展通讯。

这一章节阐述了UVM中TLM的几个重要概念,让读者理解如何使用TLM来构造可复用的验证组件。

关于TLM各种类的更详细说明请参阅UVM参考手册。

4.6.1 UVM中TLM的关键概念
4.6.1.1 对事务建模
第 3 页
在UVM中, 从uvm_sequence_item继承而来的任何类都是事务。

用户根据需要定义事务类的字段与方法,用来在验证环境中不同组件之间进展信息交换。

例如,一个简单的包如下所示:
1.class simple_packet extends uvm_sequence_item;
2.rand int src_addr;
3.rand int dst_addr;
4.rand byte unsigned data[];
5.constraint addr_constraint { src_addr != dst_addr; }
6.endclass
事务通常包含足够多的数据字段让驱动器(driver)或者事务产
生器能够产生事务的真实信号级别的动作表示。

事务也可以包含更多的数据字段,来控制数据的随机产生,或者是验证环境中的其他目的。

可以通过继承方式来增加更多的数据成员,方法以及约束。

后续章节将会说明,如何通过继承事务,从而花费最小的代价来完成特定的验证任务。

4.6.1.2 TLM调用端口(Ports)与实现端口(Exports)
UVM中的TLM使用一系列特殊的方法调用来进展模型之间的事务通讯。

在UVM中,一个port对象定义了一系列可以被调用的方法,而export对象提供了对这些方法的实现。

在构建验证环境
第 4 页
的时候,port与export通过connect()函数进展连接,之后,
调用port端的TLM方法将会执行export中对此TLM方法的实现。

实例4.7: 使用put方法将事务从生产者传递给消费者
在UVM的TLM中,put接口能够被用来将transaction从生产者发送给消费者。

一个简单的生产者例如如下:
class producer extends uvm_component;
uvm_blocking_put_port#(simple_packet) put_port;
function new(string name, uvm_component parent);
put_port = new("put_port", this);
endfunction
virtual task run();
simple_packet p = new();
..
put_port.put(p);
endtask
endclass
之前有提到,put port通过调用connect()函数连接到put export. 对上面的put方法的实现将由消费者组件来完成,如下:
第 5 页
class consumer extends uvm_component;
uvm_blocking_put_imp #(simple_packet, consumer)
put_export;
task put(simple_packet p);
// consume the packet
endtask
endclass
将port连接到export之后,调用生产者的put方法将会触发消费者的put方法实现得到执行,从而使得simple_packet对象从生产者传递到了消费者。

TLM也引入了标准的图形化示意来描述不同类型的通讯。

put通讯流程的模块图如下:
图4-2:简单的生产者/消费者的put通讯
TLM接口定义了一些生产者与消费者都必须遵循的简单规那么,在这个例如中,对于put接口,规那么如下:
•put方法的实现在执行时有可能阻塞,因此对put方法调用的对象必须负责确保在put方法阻塞的时候能够正常工
作。

第 6 页
•生产者负责创立封包,而消费者不能修改封包〔如果需要修改,必须先拷贝一份新的〕
满足了上述规那么,能够很容易的将生产者或者消费者替换成其他的模型,只要这些模型满足一样的TLM接口即可。

TLM API提供了一个简单的能够互相操作的接口协议,类似硬件世界中的USB,以太网标准一样。

由于能够容易的替换模型,UVM的TLM在满足模型复用与验证目标上发挥了关键性的作用,我们可以在后续章节进一步了解。

上述例如,在生产者中存在单独一个进程,当调用put方法时,控制流转到消费者中的put方法中。

put方法将事务沿着方法调用控制流一样的方向进展传送。

在某些情况,由于消费者中包含一个需要事务数据的进程,希望将事务沿着TLM方法调用控制流相反的方向传送。

在这种情形下,生产者/消费者将使用get接口来实现,例如如下:
1.class producer_2 extends uvm_component;
2.uvm_blocking_get_imp#(simple_packet,
producer_2) get_export;
3. task get(output simple_packet p);
4. simple_packet p_temp = new();
5. p = p_temp;
第 7 页
6. endtask
7.endclass
8.class consumer_2 extends uvm_component;
9.uvm_blocking_get_port#(simple_packet)
get_port;
10. function new(string name, uvm_component
parent);
11. get_port = new("get_port", this);
12. endfunction
13. virtual task run();
14. simple_packet p;
15. get_port.get(p);
16. endtask
17.e ndclass
在上面的put接口例如中,UVM对使用put接口的生产者与消费者设定了如下规那么:
•get方法实现可能被阻塞。

因此调用方必须确保当此方法阻塞的时候也能够正确工作。

•get方法的实现必须创立并返回一个事务对象给get的调用方。

第 8 页
get接口通讯的图形化示意如下:
图4-3:消费者调用生产者中的get方法
4.6.1.3 连接port与export
上面例子中,port对export的连接是通过调用connect方法完成的。

用户需要在消费者/生产者的父组件中的connect回调函数[仿真阶段函数connect())中调用此connect方法:
class parent_comp extends uvm_component;
producer producer_inst;
consumer consumer_inst;
...
virtual function void connect();
producer_inst.put_port.connect(consumer_inst.put _export);
endfunction
endclass
连接port与export的通用准那么是:子组件中port的connect 方法以子组件export作为参数进展调用.
4.6.1.4 port与port的连接以及export与export的连接
第 9 页
Verilog RTL中,模块的端口(port)代表信号级别的界面。

Verilog RTL模块的内部也可以包含子模块,子模块也有各自的信号端口。

然而,只有父模块的端口代表整个模块的接口,子模块的接口被当作实现细节而被隐藏。

同样的,UVM的TLM中,组件的port与export代表了组件的TLM 的对外接口。

其子组件以及子组件的port与export被看作是实现细节而被隐藏。

此种隐藏内部构造的方式加强了整个验证环境的模块化,能够更加容易的复用以及被替换。

但是,如果当需要子组件的port/export能够被外部看到的时候,该如何处理呢?这种情况下,需要通过将子组件的port连接到
父组件的port上,将子组件的export连接到父组件的export 上。

实例4-8:连接子组件的port到父组件的port
class parent_producer extends uvm_component;
uvm_blocking_put_port #(simple_packet) put_port;
producer child_producer_inst;
function new(string name, uvm_component parent);
put_port = new("put_port", this);
child_producer_inst = new("child_producer_inst",
this);
第 10 页
endfunction
virtual function void connect();
child_producer_inst.put_port.connect(put_port);
endfunction
endclass
通用的规那么是:当连接子组件的port到父组件的时候,子组件port的connect函数被调用,其调用参数是父组件的port.实例4-9:连接子组件的export到父组件的export
class parent_consumer extends uvm_component;
uvm_blocking_put_export #(simple_packet)
put_export;
consumer child_consumer_inst;
function new(string name, uvm_component parent);
put_export = new("put_export", this);
child_consumer_inst = new("child_consumer_inst", this);
endfunction
第 11 页
virtual function void connect();
put_export.connect(child_consumer_inst.put_expor t);
endfunction
endclass
通用的规那么是:当连接子组件的export到父组件的export时,父组件export的connect函数被调用,其调用参数是子组件的export. 请注意此方式与上述的子组件port与父组件port的连接方式不同。

4.6.1.5 使用uvm_tlm_fifo
在最前面的生产者/消费者例如1中,在生产者中有一个进程,
而消费者中没有任何进程。

消费者中的put方法在生产者的put 方法被调用的时候执行。

接下来的生产者/消费者的例如2中,消费者中有一个进程,消
费者有一个get port用来获得封包。

〔生产者中没有进程〕
我们有可能会遇到这种情况:需要将例如1中的生产者组件与例如2中的消费者组件相连接。

如何做到这两个组件相连接呢?一个非常常用的方法是使用UVM 的uvm_tlm_fifo来完成。

uvm_tlm_fifo是一个参数化的
FIFO(先进先出队列),此FIFO同时拥有put export与get export.
第 12 页
uvm_tlm_fifo实例化的参数就是需要在此FIFO中存储的数据对象类型。

其构造函数的参数代表了此FIFO的最大深度(缺省值是1).
实例4-10:uvm_tlm_fifo的使用
class producer_consumer_2 extends uvm_component;
producer producer_inst;
consumer_2 consumer2_inst;
uvm_tlm_fifo#(simple_packet) fifo_inst; // fifo stores simple_packets
function new(string name, uvm_component parent);
producer_inst = new("producer_inst", this);
consumer2_inst = new("consumer2_inst", this);
fifo_inst = new("fifo_inst", this, 16); // set fifo depth to 16
endfunction
virtual function void connect();
producer_inst.(fifo_inst.put_export);
consumer2_inst.(fifo_inst.get_export);
第 13 页
endfunction
endclass
运行此模块时,生产者组件中的进程创立封包,同时将封包放入FIFO, 消费者组件在调用get方法的时候取出封包。

由于FIFO的使用,这两个进程的同步耦合关系被分开。

每个进程的执行都可以任意延时,使用FIFO以及阻塞性put/get调用可以确保没有任何封包丧失。

许多验证环境建模时,对延时不敏感的特性以及确保事务(封包)能够完好不丧失传送的特性是非常需要的。

UVM的TLM使得对此类系统建模非常容易。

FIFO连接的图形化示意如下:
图4-4:使用uvm_tlm_fifo
4.6.1.6 分析port与分析export〔analysis port/analysis export)
到目前为止,put/get port要求在仿真开场之前必须有且只有一个export与之相连接。

如果port没有连接,UVM会报告错误信息,要求你将之相连。

有时候,我们需要构建类似监控器(monitor)的组件,需要将一个port要么不连接,要么连接到一个或者多个组件上。

这是因为监控器一般在整个验证环境中是被动组件,他们不会影响仿真
第 14 页
鼓励的产生,也不会影响DUT(被测试模块)的同步关系。

监控器只是被动的收集事务数据,并将之发送给其他已经注册的需要此数据的组件。

分析port因此应运而生。

分析port与其他TLM port类似,但是允许将其空接,也允许连接任意多个分析export。

图4-5:分析端口通讯
对那些熟悉回调(callback)的人来说,分析port本质上就是构造化的回调函数〔使用port连接的回调)
每个分析port有一个void类型的write()函数,其参数是一个transaction(事务)。

每个分析port将维护一个与之相连接的分析export列表。

当write方法以某个transaction作为参数被调用的时候,分析port将使用一样的transaction参数调用每个与之相连的分析port中的write函数。

因为write方法是一个函数(function),所以分析port的write函数将不会被阻塞,直接返回。

另外,由于此write方法是一个void类型,在write 函数返回后将不会传递任何状态给组件。

对包含分析port的组件的整体影响来说,可以不必去知道与关心与此分析port相连接的任何组件。

实例4-11:使用分析port的监控器(Monitor)
第 15 页
class packet_monitor extends uvm_component;
uvm_analysis_port#(simple_packet)
analysis_port;
function new(string name, uvm_component parent);
analysis_port = new("analysis_port", this);
endfunction
virtual task run();
simple_packet p = new();
.. // reassemble packet here from lower level protocol
analysis_port.write(p); // write the collected packet to the analysis port
endtask
endclass
实例4-12:使用分析export的组件
class packet_checker extends uvm_component;
uvm_analysis_imp#(simple_packet, packet_checker) analysis_export;
第 16 页
function new(string name, uvm_component parent);
analysis_export = new("analysis_export", this); endfunction
function void write(simple_packet p);
// check the packet here
endfunction
endclass
这两个组件可以在父组件中被创立,然后使用UVM通用的TLM连接规那么将分析port与分析exort相连接。

上面提到,既然分析port允许多个分析export与之相连,可以例化多个具有分析export的组件,将他们连接到packet_monitor组件的分析port 上。

有时候,通过分析port传送的交易不能够马上被与之相连的下游组件处理,这些交易需要存储一段时间之后才能够被消耗处理掉。

比方,当记分牌组件需要将DUT产生的真实封包与参考模型产生的封包进展比拟的情形。

在这种情况下,由于DUT需要延时,从参考模型产生的封包需要存储下来。

uvm_tlm_fifo能够在需要的时候存储封包,似乎可以很好的解决上述问题。

然而,uvm_tlm_fifo并没有一个分析export,所以不能直接将它连接到分析port上去。

一个重要的原因是:在
第 17 页
分析export中对write方法的实现要求传递transaction之后马上返回,但是如果FIFO是有限的固定深度,就不是总能够满足这一点。

UVM使用uvm_tlm_analysis_fifo来解决此问题。

uvm_tlm_analysis_port拥有一个分析export,因此可以直接与分析port相连,并且它的FIFO具有无限深度,所以write方法调用可以立即成功返回。

4.6.1.7 `uvm_*_imp_decl宏
有些情形,组件需要对一样的接口拥有多种实现。

例如一个记分牌类需要对多个接口(比方两个input与一个output)进展监测。

在这种情况下,必须提供处理多个接口的方法。

有3种潜在的解决方案:
•为每个特定的接口创立一个组件来实现.
•如果每个接口的交易类型一样,可以使用一个实现;这个需要交易对象提供一个可以区分其来源的机制.
•对每个port创立_imp类型,每个_imp类型调用不同的功能实现函数.
在UVM中,由于使用了`uvm_*_imp_decl宏,使得第3种方式最简单。

这些宏用来创立新的实现类型,用来转到不同的实现函数。

比方,使用`uvm_analysis_imp_decl(_1),将会得到一个
第 18 页
uvm_analysis_imp_1 #(type T)的类实现,此类中实现了对函数write_1的实现。

小技巧:`uvm_*_imp_decl宏的使用准那么:
•由于此宏创立了新的类定义,最好将此宏放到一个共享的作用域,比方systemverilog包.
•当组件确实需要对同一接口进展多种实现的时候,使用这些新类〔类似标准的记分牌组件〕
•使用通用的有意义的后缀。

例如,后缀_inport1与_outport1就比拟合理,因为它们提供了一些连接实现相关的信息•后缀以下划线'_'开头,让实现函数名能够通过下划线分隔〔比方write_inport1)
实例4-13:`uvm_*_imp_decl宏
以下是使用宏的记分牌(scoreboard)的简单例如.
1.package my_analysis_imps_pkg;
2. import uvm_pkg::*;
3. `include "uvm_macros.svh"
4.`uvm_analysis_imp_decl(_inport1)
5.`uvm_analysis_imp_decl(_inport2)
6.`uvm_analysis_imp_decl(_outport2)
7.`uvm_analysis_imp_decl(_outport2)
第 19 页
8.endpackage: my_analysis_imps_pkg
9.package scoreboard_pkg:
10. import uvm_pkg::*;
11. `include "uvm_macros.svh"
12. import my_analysis_imps_pkg::*;
13. import mytx_pkg::*;
14. mytx q1[$], q2[$];
15. class myscoreboard extends uvm_component;
16.uvm_analysis_imp_inport1#(my_tx,
myscoreboard) in_export_1;
17.uvm_analysis_imp_inport2#(my_tx,
myscoreboard) in_export_2;
18.uvm_analysis_imp_outport1#(my_tx,
myscoreboard) out_export_1;
19.uvm_analysis_imp_outport2#(my_tx,
myscoreboard) out_export_2;
20. function new(string name, uvm_component
parent);
21. super.new(name,parent);
22. in_export_1 = new("in_export_1",
this);
第 20 页
23. in_export_2 = new("in_export_2",
this);
24. out_export_1 = new("out_export_1",
this);
25. out_export_2 = new("out_export_2",
this);
26. endfunction
27. function void write_inport1(mytx t);
28. q1.push_back(t);
29. endfunction
30. function void write_inport2(mytx t);
31. q2.push_back(t);
32. endfunction
33. function void write_outport1(mytx t);
34. mytx t1, t2;
35. t1 = q1.pop_front();
36. t2 = t.transform1(); //execute some
transformation function
37. if(!t1 pare(t.transform1()))
38. `uvm_error("SBFAILED",
$psprintf("Expected: %s Got: %s", t1.sprint(),
t2.sprint()))
第 21 页
39. endfunction: write_outport1
40. function void write_outport2(mytx t);
41. mytx t1, t2;
42. t1 = q2.pop_front();
43. t2 = t.transform2(); //execute some
other transformation function
44. if(!t1 pare(t.transform1()))
45. `uvm_error("SBFAILED",
$psprintf("Expected: %s Got: %s", t1.sprint(),
t2.sprint()))
46. endfunction
47. endclass
48.endpackage: scoreboard_pkg;
〔译者补充:将来可以根据需要将任意一个port连接到相应的analysis port上去,从而完成不同的监控动作〕
第 22 页。

相关文档
最新文档