资源描述
Callback——最简单的callback
2012-01-21 22:06:01| 分类: SystemVerilog|字号 订阅
在RVM、VMM、OVM/UVM中,常常提到callback这个概念。指在事先设置好的地方留下一个接口,通过向这个接口添加一些函数对象,来达到不改变代码结构而动态修改代码行为。下面用systemverilog来举一个简单的callback例子:
1 class callback;
2
3 virtual task cb_pre_run( );
4 $display("base callback run");
5 endtask:cb_pre_run
6
7 endclass:callback
8
9 class widget;
10
11 callback cb_queue[$];
12
13 function void add_cb(callback cb);
14 cb_queue.push_back(cb);
15 endfunction:add_cb
16
17 task run();
18 // add callback here
19 foreach(cb_queue[i]) begin
20 cb_queue[i].cb_pre_run();
21 end
22 $display("widget run....");
23 endtask:run
24 endclass:widget
25
26 module top;
27
28 class ext_callback extends callback;
29 task cb_pre_run();
30 $display("ext callback run");
31 endtask:cb_pre_run
32 endclass:ext_callback
33 widget w;
34 callback cb0;
35 ext_callback cb_ext;
36
37
38 initial begin
39 w = new;
40 cb0 = new;
41 cb_ext = new;
42 w.run;
43 w.add_cb(cb0);
44 $display("=========== After Add base Callback");
45 w.run;
46 w.add_cb(cb_ext);
47 $display("=========== After Add extention Callback");
48 w.run;
49 end
50
51 endmodule; // top
在sysverilog中,没有函数指针的概念,因此必须将函数包装成为一个对象,就是上面例子中的callback class. 而为了在widget的对象中使用这个函数对象,必须事先在设计好的调用点对callback对象中的函数进行逐个调用(代码19-21行)。
widget的对象事先并不知道有多少callback对象,而是将所有的callback对象放到自己的一个callback对象队列中(cb_queue)。然后逐个对这个队列中的所有对象进行函数调用。
上面的程序编译执行后,结果如下:
widget run....
=========== After Add base Callback
base callback run
widget run....
=========== After Add extention Callback
base callback run
ext callback run
widget run....
可以看出,在执行的过程中,可以对widget对象进行动态的添加callback,从而动态的改变widget对象的动作。
上面的例子非常简单,仅仅是输出一些讯息而已,有一些局限:
1. 这个callback结构并不能够真的改变widget对象的内部成员,以及处理的数据内容,仅仅能够输出一些讯息。
2. 对每一个widget的对象,都需要单独添加相关callback对象,假如程序中又创建了一个新的widget对象,那么这个对象的callback queue初始是空的,也就是没有callback。必须再次添加才能让这个新的widget调用相应的callback功能。
3. callback只有一个地方,可以扩展到多个地方。另外也可以使用function,而不仅仅是task.
工厂模式的简单理解
首先,如果一个客户要用到一款手机,一般的做法是客户去创建一款手机,然后拿来用:
这时,客户需要知道怎么去创建一款手机,客户和手机就紧密耦合在一起了.为了降低耦合,就出现了工厂类,把创建手机的操作放到了工厂里面去,客户直接使用工厂的创建手机方法,传入想要的手机型号就行了,而不必去知道创建的细节.
随着手机种类越来越多,简单工厂模式出现了弊端,每次新加入手机品种,工厂类都要加入新的创建逻辑.这时我们就可以把工厂类定义成了接口,而每增加一种新的手机,就增加该手机对应工厂类的实现,这样工厂的设计就可以扩展了,而不必去修改原来的代码:
随着工厂规模的继续扩大,工厂开始生产充电器了.这时候工厂有二个系列的产品:手机和充电器.而手机必须使用对应的充电器才能使用.这时候分别使用一个手机工厂和一个充电器工厂都不能满足我们的需求,我们必须确认手机跟充电器的对应关系.我们把工厂改造一下,把手机工厂跟充电器工厂联系在一起:
抽象工厂模式 工厂方法模式 简单工厂模式
UVM实战指南——第1部分
2010-10-31 21:54:15| 分类: SystemVerilog | 标签:uvm |字号 订阅
验证的复杂度已经明显超过设计。为了处理复杂度,面向对象,设计模式等软件设计的思想逐渐渗透到了硬件设计领域。UVM的适时提出,有望能够尽早一统江湖,不再有各门各派军阀割据。
最近,出了一本UVM新书,工作之余,我抽空进行翻译,供大家参考。
英文来源:http://www.low-
UVM实战指南——第1部分
这篇文章是新书《A Practical Guide to Adopting the Universal Verification Methodology》的第四章 UVM library基础的节选。这一章的后续会在之后4周的周一连续发表。如果等不及,可以到网站购买。
作者:Sharon Rosenberg and Kathleen A. Meade, Cadence Design Systems
UVM是功能验证的第一个最佳实践和方法学。如之前提到,UVM实现了成熟的高级验证方法。尽管其类库可以任意使用,我们强烈建议按照后续章节描述的方式来使用,因为这些方法源自于成功经验。
这一章讲解库的结构以及基本功能,重点放在大多数验证环境所需要的基本特征上。
注意:为了简化,例子并没有完全遵循UVM建议的架构和方法学。
这一章主要包括:
· 使用UVM库
· 基本类
· TLM端口
· 工厂模式
· 消息和汇报
· 配置机制
4.1 使用UVM库
为了使用UVM库,用户需要:
· 编译UVM包的顶层文件:uvm_pkg.sv
· 在所需要的地方导入uvm_pkg
· 包含UVM宏
4.1.1 Hello World例子
下面的例子功能是在屏幕上显示消息:"Hello World!”
1 // Compile the UVM package
2 `include “uvm_pkg.sv”
3 module hello_world_example;
4 // Import the UVM library and include the UVM macros
5 import uvm_pkg::*;
6 `include “uvm_macros.svh”
7 initial begin
8 `uvm _info (“info1”,“Hello World!”, UVM _LOW)
9 end
10 endmodule: hello_world_example
第1-2行:注释用来提醒需要编译UVM库。uvm_pkg.sv是UVM顶层文件,此文件将所有的UVM文件放在一个systemverilog文件包中。注意:SystemVerilog包需要在模块以外进行编译。建议用命令行方式进行编译。由于包常被多个单元使用,不要将包的编译绑定到某个指定的单元。
第5行:当库已经编译,用户可以在任意编译单元导入此systemverilog包
第6行:必须分别包含UVM宏,因为宏属于编译指令,不会在多个编译步骤中保持存在。为了避免重复编译整个库,宏被分开包含。(每个需要用的宏的文件都必须包含)
第8行:`uvm_info宏属于UVM消息打印功能的一部分。UVM消息打印允许格式化以及控制屏幕显示。在这里,我们仅仅打印消息“Hello World!”
4.1.2 UVM库使用指南
· 为了避免命名冲突,避免在全局环境中导入uvm_pkg. 其他的package也同样适用此原则(避免全局环境中导入)
· 顶层文件一般如下格式
`ifndef <FILE _NAME>_SVH
`define <FILE _NAME>_SVH
... 文件内容
`endif
这种方式允许UVM库被多个地方引用而避免重复声明,一起编译的时候仅编译一次。建议用户在自己的UVM文件中也使用此方式。
· 在Cadence的Incisive Enterprise Simulator(IES)仿真器中运行仿真器自带的UVM库:
% irun -uvm myfile.sv
· 使用非仿真器自带的UVM库,使用命令行:
% irun -uvmhome $UVM _HOME myfile.sv
4.2 基本类
图4-1 类图描绘了UVM库中的一些类。这些类提供了一些自动机制,可以用来衍生出用户自定义的类以及类的行为。除了自动机制,这些基类还提供了支持统一和重用的API。
注意:此图没有显示uvm_transaction类。 尽管这个类仍然属于库,Accellera TSC并不建议使用它。(建议使用uvm_sequence_item)
后续章节讨论 uvm_object和uvm_component抽象类
4.3 uvm_object类
抽象类uvm_object是所有UVM数据类型的基类。他的主要功能是定义完成了一系列通用的操作方式,比如创建, 复制, 打包/解包(pack/unpack), 比较, 打印, 以及记录(record). 从uvm_object衍生出来的类必须实现类似create()和get_type_name()这样的纯虚函数。以下代码演示了一个没有使用UVM object类的AMBA APB传输的例子
实例4–1: 非UVM类定义
1 typedef enum bit { APB _READ, APB_WRITE} apb _direction _enum;
2 class apb_transfer;
3 rand bit [ 31:0] addr;
4 rand bit [ 31:0] data;
5 rand apb _direction _enum direction;
6 function void print();
7 $display("%s transfer: addr=%h data=%h", direction.name(), addr, data);
8 endfunction : print
9 endclass : apb_transfer
上面简单的例子包含了一个几乎所有交易(transaction)都有的print()方法。 大部分数据项都需要打印,复制,比较,打包,解包以及其他功能。如果让开发者去自已去定义这些功能,将不方便复用,环境整合者必须学习从多个地方得到的不同类的使用规则以及行为特征。UVM库通过引入uvm_object基类解决了这个问题。基类定义了一些通用的服务。测试向量中所有的类都必须间接或直接由uvm_object派生出来。UVM类库也实现了一些宏,这些宏能够自动实现打印,复制,克隆,打包(pack),解包(unpack)方法等等。
实例 4–2: 从uvm_object继承而来的APB传输类
1 typedef enum bit { APB _READ, APB_WRITE} apb_direction_enum;
2 class apb_transfer extends uvm_object;
3 rand bit [ 31:0] addr;
4 rand bit [ 31:0] data;
5 rand apb_direction_enum direction;
6 // Control field - does not translate into signal data
7 rand int unsigned transmit_delay; //delay between transfers
8 //UVM automation macros for data items
9 `uvm_object_utils_begin (apb_transfer)
10 `uvm _field _int (addr, UVM _DEFAULT)
11 `uvm _field _int (data, UVM _DEFAULT)
12 `uvm _field _enum(apb _direction _enum, direction, UVM _DEFAULT)
13 `uvm _field _int(transmit _delay, UVM _DEFAULT | UVM_NOCOMPARE)
14 `uvm_obj ect_utils_end
15 // Constructor - required UVM syntax
16 function new (string name="apb _transfer");
17 super.new(name);
18 endfunction : new
19 endclass : apb_transfer
第9-14行: UVM自动宏
第16-18行:在数据对象中构造函数并不是必须的。如果使用构造函数,它的所有的参数都必须有缺省值. (针对uvm_object如此,但是uvm_component并不是这样,其parent参数并没有缺省值,因为不可能在类中知道其父组件)
4.3.1 UVM自动域
使用`uvm_object_utils_begin(TYPE)和`uvm_object_utils_end宏来完成UVM对象的基本操作声明。
· 实现get_type_name()函数,此函数返回内容为这个对象类型名字的字符串
· 实现create()函数,此函数通过调用指定类型的构造函数来创建一个对象
· 在工厂中注册此类型,以便在测试向量中的其他地方能够被重载
· 实现一个静态方法get_type(),此方法在工厂操作中需要被用到
`uvm_field_*宏将自动实现指定域的一些对象方法:print(), copy(), clone(), pack(), unpack(), compare(),以及record()。支持嵌套对象以及用户自定义。宏语法如下:
语法:`uvm_field_*(field_name,flag)
“field_name”必须是类的某个属性的标识符;“flag”用来定制此域的自动化。“flag”是一数字,可以用或“|”或者“+”进行组合。此语法适合:对象、字符串、事件(event)、实数、队列、以及不同类型的数组。
域自动有多种形式,请参考UVM参考手册。
4.3.2 uvm_object定义指南
· 建议从uvm_sequence_item继承对象,此方式能够给对象增加一些额外的域,同时允许对象成为uvm_sequence随机的一部分。
· 对`uvm_field_*宏使用UVM_DEFAULT作为“flag”参数(而不是UVM_ALL_ON)。目的是为了让UVM架构增加一些缺省下并不打开的自动功能。其他一些标志是用来去掉一些自动化功能的。
· 在构造函数中的参数中将类名字设置为缺省值
· 不要忘记调用super.new(name),作为构造函数的第一个语句
· 另一个不同的宏包含了类型参数:`uvm_object_param_utils*
· `uvm_field_object缺省是深度操作。如果你使用另外一个对象,请使用UVM_REFERENCE标志来禁止深度操作。
实例4-3演示了域自动宏的更多用法,包括类组合和动态数组。此例子中的yapp_packet类包含一个pkt_header域。
实例4–3:uvm_object自动域
1 class packet_header extends uvm_object; // Packet Header class
2 rand bit [ 5:0] length;
3 rand bit [ 1:0] addr;
4 `uvm_object_utils_begin (packet_header)
5 `uvm_field_int (length, UVM _DEFAULT)
6 `uvm _field_int(addr, UVM _DEFAULT)
7 `uvm_object_utils_end
8 endclass : packet_header
9 typedef enum bit { BAD _PARITY, GOOD _PARITY } parity_e;
10 class yapp_packet extends uvm_object;
11 // Physical Data
12 rand packet_header header; // pkt_header class contains: addr, length
13 rand bit [7:0] payload [] ; // dynamic array in the range [1:63]
14 bit [7:0] parity; // calculated in post _randomize()
15 // Control Knob
16 rand parity_e parity_type; // randomized to determine parity type
17 rand int packet_delay;
18 // UVM macros for built-in automation - These declarations enable automation
19 // of the data_item fields and implement create() and get _type _name()
20 `uvm_object_utils_begin (yapp_packet)
21 `uvm _field _object (header, UVM _DEFAULT)
22 `uvm _field _array _int (payload, UVM _DEFAULT)
23 `uvm _field _int (parity, UVM _DEFAULT)
24 `uvm_field_enum(parity_e, parity_type, UVM _DEFAULT)
25 `uvm _field _int(packet _delay, UVM _DEFAULT | UVM_DEC | UVM_NOCOMPARE)
26 `uvm_object_utils_end
27 // Constructor - required syntax for UVM automation and utilities
28 function new (string name = "yapp_packet");
29 super.new(name);
30 header = packet_header::type_id::create("header"); // allocation using the factory
31 endfunction : new
32 endclass : yapp_packet
第1行:header_packet由uvm_object继承而来
第21行:如果这是一个参考类,那么我们需要使用UVM_REFERENCE标志来避免深度操作。payload是一个动态数组,使用一个特殊的宏可以打印数组的所有成员。
第24行:枚举类型语法有一个额外参数:`uvm_field_enum(<enum_type>,<field_name>,<flags>)
第30行:不用“header=new(“header”)”,我们使用工厂模式。用`uvm_object_utils实现的create()方法允许在另外项目和测试环境中扩充交易(transaction)。请参考第62页的“UVM工厂”了解更多信息。
4.3.3 UVM对象自动用法实例
下面的例子演示了UVM库内建的自动特性。假定实例4-2中的apb_transfer已经定义。
实例4–4:UVM对象的自动特性的用法
1 module automation_example;
2 // Import the UVM library and include the UVM macros
3 import uvm_pkg::*;
4 `include “uvm_macros.svh”
5 // Include the APB transfer class
6 `include “apb_transfer.sv”
7 apb_transfer my_xfer, tx1, tx2, tx3;
8 initial begin
9 my_xfer = apb_transfer::type _id::create("my_xfer");
10 if (!my_xfer.randomize())
11 `uvm_fatal(“RANDFAIL”,”can not randomize my_xfer”)
12 tx1 = my_xfer; // tx1 and my_xfer share the same memory
13 // Create a new apb_transfer
14 tx2 = apb_transfer::type _id::create("tx2");
15 tx2.copy(tx1); // Copies fields from tx1 to tx2
16 $cast(tx3, tx1.clone()); // Creates a new apb_transfer and copy all
17 // specified fields from tx1 to tx3
18 if(!pare(tx2))
19 `uvm_error(“CompareFailed”, “The comparison failed”)
20 my_xfer.print(); // Prints my_xfer in a table format
21 my_xfer.print(uvm_default_tree_printer); // Prints in “tree” format
22 end
23 endmodule: automation_example
为了使用UVM库,必须导入UVM包,并且包含文件"uvm_macros.svh”.此例子创建、随机化、复制、克隆、并且打印apb_transfer. 使用宏自动实现一个列表以及树形格式化的打印方法print()。
缺省的表格格式如下:
Name
Type
Size
Value
my_xfer
apb_transfer
-
@560
addr
integral
32
'hb2cbb864
data
integral
32
'heba598d7
direction
apb_direction_enum
1
APB _WRITE
transmit_delay
integral
32
'h6
缺省的树形格式如下:
my_xfer: (apb_transfer@560) {
addr: 'hb2cbb864 data: 'heba598d7 direction: APB _WRITE
transmit_delay: 'h6
}
下面的输出显示了第40页实例4-3中随机的yapp_packet。此封包包括一个头对象,一个具有动态数组类型的payload。下面表格中数值很容易格式化并打印如下表格:
Name
Type
Size
Value
my_packet
yapp_packet
-
@570
header
pkt_header
-
@596
length
integral
6
'h30
addr
integral
2
'h2
payload
da(integral)
48
-
[0]
integral
8
'h7b
·
integral
8
'he7
·
integral
8
'h8c
...
...
...
...
·
integral
8
'hf 3
·
integral
8
'h78
[47]
integral
8
'he1
parity
integral
8
'h74
parity_type
parity_e
1
GOOD _PARITY
packet_delay
integral
32
'd8
自动域宏将实现一个字符串打印方法:sprint(),此方法将返回一个可以用来格式化显示的字符串。打印格式是可以配置的,详细请参考UVM参考手册。
注意:表格打印使用的是最复杂的格式化功能,因此运行代价最高。如果你的仿真器要打印大批量的交易(transaction), 最好使用树形格式。
4.3.4 使用UVM自动域
使用UVM 自动域是可选的。尽管用户可以自己实现这部分功能,我们还是建议使用自动宏来实现。使用宏有以下优点:
· 高效率:如果自己实现,会需要很多时间来实现这些子例程以及丰富的可选项
· 可扩展性:在嵌套的对象中增加一个域需要了解UVM的原始实现方式,这常常需要很强的技巧。而用自动宏只需要一行语句即可
· 实现的一致性对复用很重要——例如,如果每个开发者都是用自行开发的print()方法,那么使用多个包的整合人员将不得不分析各不一样的日志文件,包括域头,值,基,以及结构,这些都属于开发者的各自喜好。
· 可维护性——维护少数行数的代码简化了UVM开发者的工作
· 代码正确性——对于大量的数据对象,很难确保所有的例程都是正确的,BUG会出现在不恰当的时间
请注意,这些宏是属于库的内部实现,已经很成熟,不需要用户来调试。对缺省的对象函数print()进行调试就好像对verilog仿真器中的$display函数进行调试。总之,我们见过一些用户最初比较担心宏的使用,后来一旦他们尝试使用宏,就变得十分喜欢它,成为宏的爱好者,并感叹道:“他们真好用!”
UVM实战指南——第2部分
2010-12-24 20:54:22| 分类: SystemVerilog | 标签:uvm |字号 订阅
英文来源:http://www.low-
译者序:UVM的核心组件,以及UVM仿真阶段的控制是整个验证环境的基本零部件,必须掌握。UVM的参数config机制对验证增加了许多灵活性和复用性,在参数配置和SystemVerilog Interface信号传递上起到了关键作用。
4.4 uvm_component类
所有的UVM验证环境的组件,包括环境和测试项都是直接或者间接由uvm_component类继承而来。此类是一个准静态类,只能在仿真开始的时候创建(构建[build]阶段). 从此类继承的用户自定义类继承了许多内建的自动化功能。
注意: 一般来说,通过从某个方法学构建的一系列类继承来设计自己的类,这系列类本身都是由uvm_component继承而来。由于提供各种功能的类都是由此类派生,理解uvm_component类是非常重要的。
下面章节描述uvm_component类的一些功能,以及如何使用。关键功能是:
· Phasing and execution control 阶段(phase)执行控制
· Hierarchy information functions 层次信息功能
· Configuration methods 配置方法
· Factory convenience methods 工厂方法
· Hierarchical reporting control 层次化汇报控制
4.4.1 仿真阶段控制方法
在Verilog/VHDL类似的硬件描述语言中,在仿真开始之前静态构建各个实例的层次结构,保证了在跑仿真之前,所有的实例都连接存在且正确连接。在Systemverilog中,类对象是在运行时被创建,这就引出一些问题:什么时候进行传输交易产生和执行比较安全?什么时候能够确保所有的UVM组件都已经被创建?可以连接什么TLM端口?
尽管每个开发者都能够设计各自的同步方式,SystemVerilog UVM类库提供了一系列的阶段(phase)方法进行环境同步,而不需要事先设计计划。这些阶段给用户提供了一些钩子,实现在关键的时间点执行一些逻辑。比如,如果需要在仿真结束的时候执行检查逻辑,可以扩展check()阶段方法,在其中嵌入执行代码。那么这些代码在仿真过程中将会在想要的时间点被执行。除了run阶段方法之外,剩下所有的内建阶段方法的执行是在零时间内完成。请参考UVM类参考手册中的uvm_phase文档获得更多的信息。
从高层次来看,已有的阶段方法是(以仿真执行的顺序):
new() — 尽管他不是一个UVM阶段函数,但是组件的构造函数是组件被创建生成时第一个执行的函数。uvm_component构造函数包含两个参数:一个字符串name参数代表组件的名称,以及一个对组件父亲的引用。建议不要使用构造函数参数的缺省值,这样可以保证用户提供有意义的名字和父类。构造函数格式如下:
class street extends uvm_component;
`uvm_component_utils(street)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction : new
endclass : street
技巧:关于构造函数的使用指南
· 所有的组件类型的构造函数必须使用名称参数和父类参数。因为工厂需要提供名称参数和父类参数来构建组件。
· 不要使用缺省参数,以确保用户必须给这两个参数提供有意义的值
· 使用简洁,意思清晰的名称
· 对于组件数组,名字使用索引:agent[index], 比如 env.agent[0].monitor, env.agent[1].monitor,等等
build()—第一个UVM阶段方法是build()方法,对每个组件会自顶向下自动调用。此方法可以对子组件进行配置,然后创建子组件。由于构造函数不是完全多态的,因此我们使用此方法来构建在构造函数中没有被创建的子组件。 因为build()方法是自顶向下调用,所以父类的构造配置将在子类build()方法之前完成。
一些关于build()使用指南:
· 每个build()的实现必须先调用super.build(), 除非此组件明确关闭了自动配置功能。调用super.build()会通过apply_config_settings方法调用来完成更新组件的配置域。尽管不建议使用,父组件可以显式的在parent.build()中调用子组件的build(). 调用super.build()确保了build()不被调用两次。
· 在build()中分配子组件,而不是在构造函数中分配子组件。这样可以允许组件在分配层次结构之前可以配置。由于构造函数不是多态的,所以除非在build()函数中来创建结构,否则一个继承类不可能改变结构。
· 如果组件在build()阶段之后被分配,UVM将会报错。
· 子组件的配置需要在build()方法中,子组件被创建之前完成。
· 如果通过重载build方法来改变组件的结构,此时不能够调用super.build(),因为这会导致父类的build被执行。如果你仍然想要uv
展开阅读全文