想查看SystemVerilog和UVM提示和小技巧吗?
20200429 SystemVerilog的静态方法
前言
在我的上一篇博客中,你已经学习了如何创建具有静态属性的类。这类变量的作用类似于全局变量,因为无论你创建多少个对象,都仅存在一个副本。这篇展示了如何创建访问那些静态属性的方法。
方法
任何类方法都可以读取和写入这些静态属性,就像动态属性一样。但是,如果将方法声明为静态方法,则可以调用该方法而无需构造对象。以下示例是带有静态方法的Thing类,用于打印Thing对象的数量。即使没有构造Thing对象,也可以调用Thing :: print_count()。
class Thing;
int id; // Dynamic, unique to each object
static int count; // Static, associated with the class
static function void print_count();
$display(“Thing::count = %0d.”, count);
endfunction
// The rest of the class
endclass
静态方法只能访问静态属性。请记住,静态属性的作用就像全局变量,因为它始终存在。动态属性存储在对象中。 id属性仅存在于单个对象中,因此print_count()无法看到它。
这是一个简单的错误处理类,该类跟踪已打印的错误消息数,并在达到最大值时退出。请注意,没有创建ErrorH句柄或对象。复制这段代码,然后在模拟器上尝试一下。
class ErrorH;
static int count=0, max_count=10;
static function void print(input string s);
$error(s);
if (++count >= max_count) begin
$display("*** Error max %0d exceeded", max_count);
$finish(0);
end
endfunction
static function void set_max_count(input int m);
max_count = m;
endfunction
endclass
initial begin
ErrorH::set_max_count(2); // Limit the number of errors
ErrorH::print("one");
ErrorH::print("two - should end sim");
ErrorH::print("three - should not print");
end
更多小技巧
uvm_config_db类是基于静态方法构建的,因此你需要使用以下语法进行调用。
uvm_config_db::set(...)
uvm_config_db::get(...)
如果没有静态方法,则必须构造一个数据库(DB)对象,并将其句柄传递到测试平台的每个角落。这违反了简化配置信息共享的整个目标。
这部分没有特意展示。期待在即将发布的博文中发现更多详细信息。
20200421 具有静态属性的SystemVerilog类
前言
与传统的面向过程的编程相反,使用面向对象(Object Oriented Programming,OOP)的编程来创建测试平台的优点之一是,你的数据和代码包含在一个类中,从而减少了对全局变量的需求。这些很有用,但是你需要谨慎使用它们以限制访问它们的方式,从而避免数据损坏。另外,你需要为全局变量创建唯一的名称,以避免冲突。是否有一种OOP方法可以使一个变量可以跨多个类访问并解决命名问题?
对象计数
当你调试带有多个对象的代码时,为每个对象使用唯一的名称或ID十分方便,因此你可以轻松地在系统中跟踪它们。那该如何自动设置ID呢?你可以用一个全局变量来计这种类型的对象的数量,但是你试图避免使用全局变量。 OOP解决方案是一个静态变量,其中的存储与类声明相关联,而不是与动态构造的多个对象相关联。
class Thing;
int id; // Dynamic, unique to each object
static int count; // Static, associated with the class
function new();
id = count++; // Initialize ID, update count
endfunction
// The rest of the class
endclass
每次构造新对象时,其ID都会获取当前计数,然后计数增加。 count属性保存创建的对象数。由于此属性是静态的,因此即使没有创建任何对象,该属性也存在。下面显示的冒号-语法称为作用域解析运算符,它表示要在名称空间Thing中查找名称计数。
Thing t0, t1, t2;
initial begin
// Print the count, before any objects constructed
$display("count=%0d. before", Thing::count);
t0 = new();
// Print count after the first object constructed
$display("count=%0d. after", Thing::count);
t1 = new();
t2 = new();
end
下图展示了刚才创建对象的过程。静态计数属性与该类相关联,并且仅存在一个副本。句柄t0,t1和t2指向单独的对象,每个对象都有单独的存储空间和唯一的id值。
局部全局变量
啊哈-你刚刚创建了一个“全局”变量count,该变量保存到目前为止创建的Thing对象的数量,但对于Thing类而言是“本地”的。用OOP的术语,你可以说该属性在Thing类的名称空间中。
每个类都有其自己的名称空间,因此Thing类中称为“ count”的属性不会与另一个类中的“count”冲突。每个类都可以有自己的计数变量,如果用全局变量来完成,则不会造成任何混乱。
20200416 SystemVerilog参数化的类
SystemVerilog允许你创建参数化的模块和类。这使它们更加灵活,并且能够处理多种数据类型,而不仅仅是一种。此概念已在UVM中广泛使用,尤其是利用uvm_config_db来配置数据库。你可以自己动手尝试下面这些示例。
按值参数化
让我们从一个简单类——比特位向量开始。该类具有向量宽度的参数。 (良好的编程习惯是始终为你的参数设置默认值。)
class Vector #(parameter WIDTH=1);
bit [WIDTH-1:0] data;
endclass
现在,你可以为具有各种宽度的向量的类声明句柄。
Vector v1; // Default: data width=1
Vector #(8) v8; // 8-bit data
Vector #(.WIDTH(16)) v16; // 16-bit data
initial begin
v1 = new();
$display("v1 ", $typename(v1.data)); // v1 bit[0:0]
v8 = new();
$display("v8 ", $typename(v8.data)); // v8 bit[7:0]
end
每次你指定一个值时,实际上就是在创建一个新类。因此,你可以想象上面包含v8的那行实际上创建了以下类声明。
class Vector__8;
bit [8-1:0] data;
endclass
v1,v8和v16句柄类型不兼容,因为它们分别用于单独的类Vector_1,Vector_8和Vector_16。即使使用$ cast(),也无法在这些句柄之间进行赋值。
按类型参数化
假设你的老板要求你在SystemVerilog中为堆栈编写公用类。你可能会想到这样的事情。
class Stack;
int items[64], idx=0;
function void push(input int val);
items[idx++] = val;
endfunction
function int pop();
return items[idx--];
endfunction
endclass
你写的类如此受欢迎,以至于你项目中的其他人要求你提供字节堆栈,实数值堆栈等等。无需编写几十个新类,只需修改你的旧类即可。 push()和pop()的代码保持不变,只需适用于其他类型。
class Stack #(parameter type T=int);
T items[64], idx=0;
function void push(input T val); …
function T pop(); …
endclass
Stack int_stack; // Default: Stack of ints
Stack #(bit[7:0]) byte_stack; // Stack of 8-bit values
Stack #(real) real_stack; // Stack of real values
再者,这三个声明实际上创建了三个新类。因此,这些句柄类型不兼容。
当然,你本可以告诉老板使用System Verilog的队列数据类型,但是这样做的还有什么乐趣呢?
更多小技巧
类标题中的关键字“parameter”是可选的。
参数名称采用大写以表示它不是变量。
20200401 给UVM新用户的小贴士(又名:我在类中遗漏了什么)
当我第一次学习UVM时,有很多事情让我感到困惑。在修完UVM课程之后,还有什么模糊的地方?
这里有一个图表,展示不同测试平台层次,其中橘色表示事务从测试级序列流入代理项。
- 序列发生器与序列。这个“ 发生器”使很多人感到困惑。如果你不熟悉UVM,请记住:
- 发生器基本上是一个智能管道,可将事务从测试级传输到驱动程序。 (是的,它可以做更多……)
- 多个事务被称为序列。一次传输不会发现很多错误,但是大量的传输可能很危险!
- 为什么要在一个任务中生成多个事务?
- 好的,我正在生成事务。我为什么不只是将它们放入数组呢?
- 测试平台的第四个维度是时间!如果我只是制作了一系列事务句柄,那要如何添加延迟?如果我有两个序列并排运行,传输到两个不同的接口中,如何能使它们同步?
- 每个序列都需要在body()任务中运行,因此我可以完成所有这些操作。 (牢记,function中不能有任何延迟。)
- 激励的反馈呢?事务句柄的数组是静态的。如果要发送事务A,检查结果并在成功时发送B,或在失败时发送C,该怎么办?或者如何从读取的事务中获取值并将其写入新位置?这在SystemVerilog的任务中很容易,但是对于句柄数组却很难。
- 我的UVM类构造函数什么时候该有一个还是两个参数?这是因为UVM有两种主要类型的类。
- 监测器(monitor)和驱动(driver)等组件(component)是测试平台层次结构的一部分,即上图中用框表示的部分。
- 每个测试有一个环境,这个环境包含一个代理(agent),该代理由监测器(monitor),驱动(driver)和序列生成器(sequencer)构成。创建组件时,它需要知道其名称和上一级。因此它们的new()函数必须具有这两个参数。
- 事物或序列项,即上图的橙色圆圈。这些对象(object)是在测试级别创建的,并发送给代理,或由监视器创建并发送到记分板或由专家进行分析。他们在测试平台上没有固定的位置。这就是为什么他们的new()仅具有单个name参数的原因。
- 监测器(monitor)和驱动(driver)等组件(component)是测试平台层次结构的一部分,即上图中用框表示的部分。
- “开始序列”是什么意思?
- 序列产生事务。那它们去哪儿了?如果它们是UART事务,则很明显,它们将转到UART代理。如果你的设计中有4个UART,该怎么办?然后,您的测试台具有4个UART代理。你的测试项需要选择一个。
- 代理只是其他组件的容器。因此,一个序列需要连接到驱动。但是,如果两个序列都想发送到单个驱动,该怎么办。啊哈-你需要一个智能连接器,一个序列生成器。这就是为什么你的测试类将序列生成器句柄传递到序列start()任务中的原因。
- 我更喜欢说“用序列生成器启动序列”,而不是“用序列生成器开始音序”。序列生成器不执行序列,虚拟序列(virtual sequences)通常没有序列生成器。
- 好的,这个“ 生成器”是挺烦的。你也看到这一切了。
想查看更多SystemVerilog和UVM提示吗?报名参加我即将举行的网络研讨会"UVM编码指南:你可能不知道的提示和技巧。"
享受你的验证旅程!
原文链接:https://blogs.mentor.com/verificationhorizons/blog/author/cspear/
点击【阅读原文】可直达课程页面,马上试听
往期精彩:
理解UVM-1.2到IEEE1800.2的变化,掌握这3点就够