UVM实战指南——第3部分.pdf

上传人:tbuqq 文档编号:4765620 上传时间:2019-12-10 格式:PDF 页数:12 大小:202.09KB
返回 下载 相关 举报
UVM实战指南——第3部分.pdf_第1页
第1页 / 共12页
亲,该文档总共12页,到这儿已超出免费预览范围,如果喜欢就下载吧!
资源描述

《UVM实战指南——第3部分.pdf》由会员分享,可在线阅读,更多相关《UVM实战指南——第3部分.pdf(12页珍藏版)》请在三一文库上搜索。

1、(*)题外话: TLM可能是 UVM中最重要的概念,掌握了 TLM ,就可以开始尝试编写一些 小程序了。翻译这篇文章,也是为了巩固加强对TLM 的理解。 (*)几个名词:transaction翻译为事务或者交易;packet翻译为封包,packet属于 transaction;monitor翻译为监视器;driver翻译为驱动器;scoreboard翻译为记分 牌;有些词汇直接被运用到UVM源代码上,所以有时候用英文更容易描述清楚。 (*)语言的目的是为了交流,翻译不是为了纯粹的语言转换,而是为了传递思想。 4.6 UVM中事务级建模 (TLM) 20 多年前,设计者从门级转向RTL 级。这次

2、转换来自于标准Verilog/VHDL的 RTL 编码 风格,以及RTL 综合实现工具的推出。使用RTL 最大的好处是让设计者更多的专注于时序 行为的设计以及功能的正确性,而很少考虑门级相关设计。 TLM( 事务级建模 )同样在抽象级别上更进了一步,在设计和验证领域都有出现。通过TLM, 中心放在系统级别的各种事务流的建模,而更少关心时钟级别的行为。 TLM 在测试向量中已经使用多年。通常,在产生激励和覆盖率检查的时候使用事务而不是 用时钟级别建模,这种方式就是TLM. 为了验证RTL 级别的 DUT( 需要测试的模块),测试 向量使用事务发生器(transactor)(有时也称为总线功能模型

3、(BFM),将 RTL 级和事务级 进行转换。在UVM 中,此事务发生器也被叫做驱动(driver)或者收集器 (collector)。 TLM 中,事务通过方法调用和类对象来建模。使用事务级而不是信号级别来建模有几个显 著的好处: TLM 比 RTL 更简洁,仿真速度快。 TLM 模型的抽象级别更高,更加契合验证工程师或设计工程师对内部功能的考虑, 从而使得建模更简单,并且更容易被其他工程师理解。 TLM 模型将不符合复用的部分移到模型之外,因此TLM 很适合复用。并且,TLM 使 用面向对象的技术,比如继承、实现和接口分离的技术。 TLM 的采纳依赖于标准的TLM 建模技术的出现, 就像

4、RTL 综合流程的采纳归功于标准RTL 编码风格的实现。幸运的是,近些年来,几个重要的标准TLM 应用程序接口(API) 得到定 义。在 EDA 和 ESL 领域,两个最重要的标准是开放SystemC计划( OSCI )的 TLM1.0 以及 TLM2.0标准。 OSCI TLM 1.0标准是一个简单通用的TLM API, 用来建模消息传递。在消息传递时,对 象( 事务 ) 在组件之间传递的方式和封包在网络之间传递的方式类似。 在发送封包的消息传递中,发送端和接收端之间没有共享的状态,他们之间的通讯讯息仅仅 包含在消息中。 The OSCI TLM 2.0标准能够用来开发SystemC中的高速

5、虚拟平台模型。TLM2.0标准 特别被用作片上存储映射的总线系统,包含许多能够进行片上总线互联的整合复用模块. OSCI TLM 1.0和 TLM 2.0是互相独立的标准,满足不同的需要。有人可能通过其命名方 式认为 TLM2.0优于 TLM1.0 ,但是实际上并不是这样。 UVM 提供的 TLM 类和 API 是基于 TLM1.0标准的。这是因为TLM 通用消息传递语法很 好的满足了多种验证组件的事务级建模。TLM1.0也适合多种语言之间的通信建模,比如 SystemVerilog, SystemC以及 e 语言之间的建模。UVM 中 TLM1.0接口甚至可以用来 和 SystemC中的 T

6、LM2.0模型进行通讯。 这一章节阐述了UVM 中 TLM 的几个重要概念, 让读者理解如何使用TLM 来构造可复用的 验证组件。关于TLM 各种类的更详细说明请参阅UVM 参考手册。 4.6.1 UVM中 TLM 的关键概念 4.6.1.1 对事务建模 在 UVM 中, 从 uvm_sequence_item继承而来的任何类都是事务。用户根据需要定义事 务类的字段和方法,用来在验证环境中不同组件之间进行信息交换。例如, 一个简单的包如 下所示: 1.class simple_packet extends uvm_sequence_item; 2.rand int src_addr; 3.ra

7、nd int dst_addr; 4.rand byte unsigned data; 5.constraint addr_constraint src_addr != dst_addr; 6 7.endclass 事务通常包含足够多的数据字段让驱动器(driver)或者事务产生器能够产生事务的真实信 号级别的动作表示。事务也可以包含更多的数据字段,来控制数据的随机产生,或者是验证 环境中的其他目的。可以通过继承方式来增加更多的数据成员,方法以及约束。 后续章节将 会说明,如何通过继承事务,从而花费最小的代价来完成特定的验证任务。 4.6.1.2 TLM调用端口 (Ports)和实现端口 (E

8、xports) UVM 中的 TLM 使用一系列特殊的方法调用来进行模型之间的事务通讯。在 UVM 中,一个 port对象定义了一系列可以被调用的方法,而 export对象提供了对这些方法的实现。在构 建验证环境的时候,port和 export通过 connect()函数进行连接,之后,调用port端的 TLM 方法将会执行export中对此 TLM 方法的实现。 实例 4.7: 使用 put方法将事务从生产者传递给消费者 在 UVM 的 TLM 中, put 接口能够被用来将transaction从生产者发送给消费者。一个简 单的生产者示例如下: class producer extends

9、 uvm_component; uvm_blocking_put_port #(simple_packet) put_port; function new(string name, uvm_component parent); put_port = new(“put_port“, this); endfunction virtualtask run(); simple_packet p = new(); put_port.put (p); endtask endclass 之前有提到, put port通过调用 connect()函数连接到put export. 对上面的put 方法的 实现将

10、由消费者组件来完成,如下: 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 通讯流程的模

11、块图如下: 图 4-2:简单的生产者 /消费者的put 通讯 TLM 接口定义了一些生产者和消费者都必须遵循的简单规则,在这个示例中,对于put 接 口,规则如下: put 方法的实现在执行时有可能阻塞,因此对put 方法调用的对象必须负责确保在 put 方法阻塞的时候能够正常工作。 生产者负责创建封包,而消费者不能修改封包(如果需要修改, 必须先拷贝一份新的) 满足了上述规则, 能够很容易的将生产者或者消费者替换成其他的模型,只要这些模型满足 相同的 TLM 接口即可。 TLM API提供了一个简单的能够互相操作的接口协议,类似硬件世 界中的 USB, 以太网标准一样。由于能够容易的替换模型

12、,UVM 的 TLM 在满足模型复用和 验证目标上发挥了关键性的作用,我们可以在后续章节进一步了解。 上述示例, 在生产者中存在单独一个进程,当调用 put方法时, 控制流转到消费者中的put 方法中。 put 方法将事务沿着方法调用控制流相同的方向进行传送。 在某些情况,由于消费者中包含一个需要事务数据的进程,希望将事务沿着TLM 方法调用 控制流相反的方向传送。在这种情形下, 生产者 /消费者将使用get 接口来实现, 示例如下: 1.class producer_2 extends uvm_component; 2.uvm_blocking_get_imp #(simple_packet

13、, producer_2) get_export; 3.task get (output simple_packet p); 4.simple_packet p_temp = new(); 5. . 6.p = p_temp; 7.endtask 8.endclass 9.class consumer_2 extends uvm_component; 10.uvm_blocking_get_port #(simple_packet) get_port; 11.function new(string name, uvm_component parent); 12.get_port = new(“

14、get_port“, this); 13.endfunction 14.virtual task run(); 15.simple_packet p; 16. . 17.get_port.get (p); 18.endtask 19. endclass 在上面的 put接口示例中, UVM 对使用 put 接口的生产者和消费者设定了如下规则: get 方法实现可能被阻塞。因此调用方必须确保当此方法阻塞的时候也能够正确工 作。 get 方法的实现必须创建并返回一个事务对象给get 的调用方。 get 接口通讯的图形化示意如下: 图 4-3:消费者调用生产者中的get 方法 4.6.1.3 连接

15、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); en

16、dfunction endclass 连接 port和 export的通用准则是: 子组件中port的 connect方法以子组件export作为 参数进行调用 . 4.6.1.4 port和 port的连接以及export和 export的连接 Verilog RTL中,模块的端口(port)代表信号级别的界面。Verilog RTL模块的内部也可以 包含子模块,子模块也有各自的信号端口。然而,只有父模块的端口代表整个模块的接口, 子模块的接口被当作实现细节而被隐藏。 同样的, UVM 的 TLM 中,组件的port和 export代表了组件的TLM 的对外接口。其子组 件以及子组件的por

17、t和 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 ch

18、ild_producer_inst; function new(string name, uvm_component parent); put_port = new(“put_port“, this); child_producer_inst = new(“child_producer_inst“, this); endfunction virtual function void connect(); child_producer_inst.put_port.connect(put_port); endfunction endclass 通用的规则是:当连接子组件的port到父组件的时候,子组

19、件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_in

20、st = new(“child_consumer_inst“, this); endfunction virtual function void connect(); put_export.connect(child_consumer_inst.put_export); endfunction endclass 通用的规则是: 当连接子组件的export到父组件的export时, 父组件 export的 connect 函数被调用, 其调用参数是子组件的export. 请注意此方式和上述的子组件port和父组件 port的连接方式不同。 4.6.1.5 使用 uvm_tlm_fifo 在最前面

21、的生产者/消费者示例1 中,在生产者中有一个进程,而消费者中没有任何进程。 消费者中的put 方法在生产者的put 方法被调用的时候执行。 接下来的生产者/ 消费者的示例2 中,消费者中有一个进程,消费者有一个get port用来 获得封包。(生产者中没有进程) 我们有可能会遇到这种情况:需要将示例1 中的生产者组件和示例2 中的消费者组件相连 接。 如何做到这两个组件相连接呢?一个非常常用的方法是使用UVM 的 uvm_tlm_fifo来完成。 uvm_tlm_fifo是一个参数化的FIFO( 先进先出队列 ),此 FIFO 同时拥有put export和 get export. uvm_t

22、lm_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_compone

23、nt 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.put_port.connect(fifo_inst.put_export); consumer2_inst.get_port.connect(fifo

24、_inst.get_export); endfunction endclass 运行此模块时,生产者组件中的进程创建封包,同时将封包放入FIFO, 消费者组件在调用 get 方法的时候取出封包。 由于 FIFO 的使用,这两个进程的同步耦合关系被分开。每个进程的执行都可以任意延时, 使用 FIFO 以及阻塞性put/get调用可以确保没有任何封包丢失。许多验证环境建模时, 对延时不敏感的特性以及确保事务(封包 ) 能够完好不丢失传送的特性是非常需要的。UVM 的 TLM 使得对此类系统建模非常容易。 FIFO 连接的图形化示意如下: 图 4-4:使用 uvm_tlm_fifo 4.6.1.6

25、分析 port和分析 export(analysis port/analysis export) 到目前为止, put/get port要求在仿真开始之前必须有且只有一个export与之相连接。 如果 port没有连接, UVM 会报告错误信息,要求你将之相连。 有时候,我们需要构建类似监控器(monitor)的组件,需要将一个port要么不连接,要么 连接到一个或者多个组件上。这是因为监控器一般在整个验证环境中是被动组件,他们不会 影响仿真激励的产生,也不会影响DUT( 被测试模块 ) 的同步关系。监控器只是被动的收集 事务数据,并将之发送给其他已经注册的需要此数据的组件。 分析 port因

26、此应运而生。分析 port和其他 TLM port类似, 但是允许将其空接,也允许连 接任意多个分析export。 图 4-5:分析端口通讯 对那些熟悉回调(callback)的人来说,分析port本质上就是结构化的回调函数(使用port 连接的回调 ) 每个分析 port有一个 void 类型的 write()函数,其参数是一个transaction(事务 )。每个 分析 port将维护一个与之相连接的分析export列表。当write方法以某个transaction 作为参数被调用的时候,分析port将使用相同的transaction参数调用每个与之相连的分 析 port中的 write函

27、数。因为 write方法是一个函数(function),所以分析port的 write 函数将不会被阻塞,直接返回。另外,由于此write方法是一个void 类型,在write函数 返回后将不会传递任何状态给组件。对包含分析port的组件的整体影响来说,可以不必去 知道和关心与此分析port相连接的任何组件。 实例 4-11:使用分析port的监控器 (Monitor) class packet_monitor extends uvm_component; uvm_analysis_port #(simple_packet) analysis_port; function new(string

28、 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

29、 packet_checker extends uvm_component; uvm_analysis_imp #(simple_packet, packet_checker) analysis_export; 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 这

30、两个组件可以在父组件中被创建,然后使用UVM 通用的 TLM 连接规则将分析port和分 析 exort相连接。上面提到,既然分析port允许多个分析export与之相连,可以例化多 个具有分析export的组件,将他们连接到packet_monitor组件的分析port上。 有时候,通过分析port传送的交易不能够马上被与之相连的下游组件处理,这些交易需要 存储一段时间之后才能够被消耗处理掉。比如,当记分牌组件需要将DUT 产生的真实封包 和参考模型产生的封包进行比较的情形。在这种情况下,由于DUT 需要延时,从参考模型 产生的封包需要存储下来。 uvm_tlm_fifo能够在需要的时候存储

31、封包,似乎可以很好的解决上述问题。然而, uvm_tlm_fifo并没有一个分析export,所以不能直接将它连接到分析port上去。一个重 要的原因是:在分析export中对 write方法的实现要求传递transaction之后马上返回, 但是如果 FIFO 是有限的固定深度,就不是总能够满足这一点。UVM 使用 uvm_tlm_analysis_fifo来解决此问题。uvm_tlm_analysis_port拥有一个分析 export,因此可以直接和分析port相连,并且它的FIFO 具有无限深度,所以write方法 调用可以立即成功返回。 4.6.1.7 uvm_*_imp_decl宏

32、 有些情形,组件需要对相同的接口拥有多种实现。例如一个记分牌类需要对多个接口(比如 两个 input和一个 output)进行监测。 在这种情况下, 必须提供处理多个接口的方法。有 3 种潜在的解决方案: 为每个特定的接口创建一个组件来实现. 如果每个接口的交易类型相同,可以使用一个实现;这个需要交易对象提供一个可以 区分其来源的机制. 对每个 port创建 _imp类型,每个 _imp类型调用不同的功能实现函数. 在 UVM 中,由于使用了uvm_*_imp_decl宏,使得第3 种方式最简单。这些宏用来创 建新的实现类型, 用来转到不同的实现函数。比如,使用 uvm_analysis_im

33、p_decl(_1), 将会得到一个uvm_analysis_imp_1 #(type T)的类实现, 此类中实现了对函数write_1 的实现。 小技巧 : uvm_*_imp_decl宏的使用准则 : 由于此宏创建了新的类定义,最好将此宏放到一个共享的作用域,比如 systemverilog包. 当组件确实需要对同一接口进行多种实现的时候,使用这些新类 (类似标准的记分牌 组件) 使用通用的有意义的后缀。例如,后缀_inport1和_outport1就比较合理,因为它 们提供了一些连接实现相关的信息 后缀以下划线 _ 开头,让实现函数名能够通过下划线分隔(比如write_inport1)

34、实例 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) 8.endpackage: my_analysis_

35、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#

36、(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); 23. in_exp

37、ort_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

38、. 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(!pare(t.transform1() 38. uvm_error(“SBFAILED“, $psprintf(“Expected: %s Got: %s“, t1.sprint(), t2.sprint() 39. endfunction: write_outport1 40. functi

39、on 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(!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上 去,从而完成不同的监控动作)

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 其他


经营许可证编号:宁ICP备18001539号-1