如上表所示,由于我们比在SystemVerilog中读取文本且更频繁地输出文本,因此我们创建了更多返回字符串的函数。 尽管如此,如果需要,用户可以在SystemVerilog中使用其他函数进行类似Python的文本处理。
C_find函数创建了一个std::string对象并且调用了它的find方法来匹配相应字符。
虽然很多函数都是受到其他编程语言的启发,但是还有一些基于验证思想的新加入的函数,比如下面这个例子。
很多命令终端都是支持彩色显示的,colorize函数就有色彩处理能力。验证人员可以可以使用这个函数在日志文件中用红色突出显示错误信息。该函数使用ANSI转义码来更改指定文本和背景颜色。 此外,这个函数还能加黑字体,给文本添加下划线,如果终端支持的话甚至能使其闪烁。 函数的形式参数如下所示:
一个使用它的例子:
(四)容器类
容器类提供了一种数据结构用来收集其他的对象。他包含了4组类:1、pair,2、ruple,3、aggregantes,4、collections。
(1)pair class
pair类是包含两个参数的类,这两个参数可以是不同的类型:
pair类提供了表3中的函数:
(2)tuple class
tuple类扩展了Pair类的概念,它携带了不只两个参数作为一个单独的单元。由于SystemVerilog不支持不确定数量的类作为参数,我们创立了一个可以包含10个参数的tuple类,如下表。
(3)aggregate class
aggregate类为各种不同的数据类型提供了一些函数。它包含6种不同的类。表4到表6展示了每种aggregate类支持的函数列表。
(a)Packed Array Class: packed_array是一个带有参数的类,它提供了一些用于打包数组的函数。这些类可以根据数组的数据类型和位宽进行设定。数据类型必须是比特类型(bit,logic,reg)、枚举类型或者打包数组、打包结构体。以下示例展示了如何利用其中的一个函数将打包的数组转化为解包的数组。
(b)Unpacked Array Class: unpacked_array是一个带有参数的类,它提供了一些用于解包的函数。这些类可以根据数据类型和数组的大小进行设定。数据类型可以是任意类型。以下示例展示了一个颠倒被解包数组元素的reverse函数。
(c)Dynamic Array Class: dynamic_arry是一个带有参数的类,它提供了一些用于动态数组的函数。这些类可以传入任何数据类型。下列代码展示了使用一个用户提供的比较器来比较两个动态数组的代码。
其中my_comparator 是一个策咯类,它至少包含一个ne函数,当两个动态数组不相等时返回1。如果没有给出comparator,则会使用默认的comparator,它的ne函数使用逻辑不等式运算符(!=)比较两个对象。
(d)Queue Class: queue是一个带有参数的类,它提供了一些用于队列的函数。这些类能够传入任何数据类型。这个类提供的函数与动态数组的类提供的函数类似(表5)。
(e)Data Stream Class:data_stream是一个带有参数的类,它用来管理压缩数组的数据流。Data_stream是dynamic_arry类的一个子类(图2)。
与packed_arry类相似,这个类可以根据数据类型和位宽进行设定。这种相似的限制同样适用于这种数据类型的数据流。这个类有一个叫做to_string的函数,它可以以多种方式显示它的内容。例如,以下代码仅显示数据流开头的四个元素和数据流结尾的六个元素。
输出结果如下:
其中group传入的参数决定几个元素为一组输出,num_head的参数决定开头几个元素的个数,num_tail的参数决定最后几个元素的个数。
数据流可以伴随其数据使能共同使用。 例如,如果相应的数据使能(de [i])为1,则显示数组元素(ds [i])的值。否则,将显示“ - ”。
输出结果如下:
data_stream类还有丰富的数据生成函数的合集。例如,下面代码生成了一个含有16个元素的动态数组,它们具有随机化的初值。
生成的数组可以用上文所述的方式显示出来:
(f)BiteStream Class:bit_stream是一个带有参数的类,他用来管理比特流。bit_stream是data_stream类一个子类(图2),它具有1比特的位宽。验证人员可以使用这个类来管理单比特的数据流。
(4)collection类
collection类提供了数据结构,总共创建了7个collection类和1个iterator类(图3)。
collection类是一个抽象的基类,他定义了一些其子类应该实现的函数。一些collection类是使用SystemVerilog中的队列和关联数组类型实现的。队列和关联数组中有一些相关的方法。知道这些方法的性能是很重要的,因为选择不合适方法可能会降低collection类的性能。例如,使用insert函数将元素插入到队列的前面,或者使用delete函数从队列的后面删除元素比其他操作花费更长的时间。这是因为对于这些操作队列中所有的元素都必须在队列中进行移位。我们在创建下面描述的这些collection类时考虑到了这些底层结构。
(a)Set Class:set是一个不包含重复元素的collection类。它通过使用关联数组来实现。
(b)Deque Class:deque是一个双端队列类,支持两端的push和pop。它使用队列实现。
(c)Linked List Class: linked_list是一个双向链表类。 它使用新创建的链接元素类型实现。 该类提供了比deque类更好的插入和删除元素的性能。
(d)Priority Queue Class:priority_queue类跟据元素的优先级对它们进行排序。他使用新创建的优先级堆来实现。
(e)Map Class:map类将键值映射到数值上。他通过关联数组来实现。
(f)Stack Class:stack类实现了后进先出的结构。他是通过队列实现的。
(g)Iterator Class:iterator类为calss类提供了与它们功能无关的统一的访问方法。以下代码显示了对集合中的每个元素进行迭代操作的示例。
(五)策略类
策略类提供了供其他的类使用的方法族。总共包含两组类:比较器和格式转换器。
(1)比较器
比较器用于比较两个对象。 它们主要由容器类使用。两个对象需要进行深度比较时,则应创建一个新的比较器。例如,默认比较器的eq函数使用逻辑等式运算符(“==”)进行对象比较:
默认比较器适用于比较整数数据类型的两个对象,例如int。 但是,如果类型是pair,则可能需要比较pair中的元素,而不是对象句柄。 pair_comparator是策略类提供的比较器之一:
(2)格式转换器
格式转换器用于将对象转化为字符串格式。一次仿真通常使用大量的整数值。comma_formater类的to_string函数可以将整数数据的格式转换为一个字符串,该字符串以逗号作为千分位的分隔符以增加可读性。例如,下面代码显示“123,456,789”。
(六)验证特定类
验证特定类提供了一些用于验证的函数,我们创建了三组类:随机类、定时器类和日志类。
(1)随机类
(a)Random Number Classes:随机数字类提供了用于随机化数字的预定义分布仓。定义了具有2、4、8、16和32个分布仓的类。具有4个仓的数字随机化类如下所示。
其中dist_bin是一个如下定义的结构体:
例如,如下代码创建4个随机分布仓:
变量n.db随机化的值介于100到200之间、300到400之间、500到600之间、700到800之间,加权比为1:2:3:4。
(b)Random Utility Class:random_util提供了一个具有一位返回值的函数,(1)为真,(0)为假。它根据给定的百分比进行随机化。下面的例子中,条件表达式随机分配1的概率为70%。
(2)定时器类
表7列举了定时器类的函数。
(a)Kitchen Timer Class:kitchen_timer类用具计算仿真时间(而不是系统时间),并在定时器到达指定时间时触发相应的事件。kitchen_timer可以看作看门狗定时器或者稍后启动时事件的触发器。
(b)Stopwatch Class:stopwatch类也是用于计算仿真时间。与kitchen_timer类不同,stopwatch类主要用于监测内部进程的性能。用户可以测量两个transaction末尾的延时,或者两个事件之间持续的时间。
(c)Random Delay Class:random_delay类用于等待用于指定范围内的随机化时间。用户可以在随机等待延时结束之前指定一个事件来退出等待状态。
(3)日志类
日志类提供了一个统一的方法来记录事务日志。他可以为后处理保存单独的日志文件。
(七)域特定类
下面描述的域特定类提供了一些针对特定目标时钟域的函数,但是这些通用函数已经足够我们使用了。
(1) CRC Class
crc类用于计算循环冗余校验值。他提供了42中不同的常用CRC函数,其中一个函数声明如下:
(2) Scrambler Class
扰码器是一个带有参数的类,它对比特流提供了一个干扰函数。一个多功能扰码器等同于18个通用的扰码器。下面代码声明了一个干扰函数:
该函数返回被干扰的比特流和现行反馈位移寄存器(LFSR)的值,该值可以用作此函数下次调用的种子值。
(3)8b/10b Encoding Class
8b/10b编码器类目前正在开发中。该类提供了用于串行
通信的8b / 10b编码和解码功能。
(八)封装
(1)所有库文件都包含在一个名为cl_pkg.sv的文件中。 它将类定义打包到一个名为cl的SystemVerilog包中。 我们为包选择了这个短名字,但是如果它与其他命名空间冲突,可以更改名称。 在包中声明的类可以使用下列方式之一进行访问:
(a)类范围解析运算符“::”
(b)显式的import声明:
(c)隐式的import声明(通常是这种):
很多函数都是如同static一样定义的,以便在不实例化的情况下使用。
(2)授权
该库是在MIT/X Window System License下授权的,这意味着你可以任意使用这个库即使它正在进行专利审查。
(九)结果和讨论
我们总共创建了38个类,32个函数和任务。为了评估可以被该库替换掉的代码行数,我们调查了9个验证项目。
共调查了489,875行现有SystemVerilog代码。 这个行数包括基于类的代码,例如任务,验证组件和验证对象,但排除基于模块的代码,例如被测设计(DUT)和顶层测试台。 注释行和空白行也被排除。 初步调查显示,约有2%的代码可以被库取代。
通用库的主要目标之一是通过重用公共函数来减少行数。 然而,2%的代码减少比我们的预期有点不足。 这是因为类的种类不足以涵盖广泛的验证功能? 或者,这是因为验证工作本身固有地包括一个特定于项目的唯一的代码集,因此不能创建额外的通用库? 为了回答这些问题,进一步的调查,以揭示其他98%的代码执行。
(1)代码细分
我们将我们的代码分为两组:固定模式组和项目特定组。 分类为固定模式组的代码使用固定样式,通常不能由公共库替换。 固定模式组包括编译器指令,接口定义,类型定义,声明,约束,功能覆盖,寄存器抽象层(RAL)和样板代码。样板代码是那些必须进行验证工作时包括的函数,比如VMM的copy, compare, byte_pack, and byte_unpack函数。另外一部分代码则被分到项目特定组中。
有趣的是,调查结果显示约60%的代码被分为固定模式组(图4)。 如果我们排除固定模式代码,减少率上升到约5%。 考虑到大多数库都包含低层次函数,这个数字似乎是合理的。即使固定模式代码的数量占整个代码的60%,并且库可能对它们没有什么益处,固定模式代码相对容易开发,甚至可以自动生成。由于项目特定组的代码是至关重要的部分,所以这个结果是令人满意的。
(2)该库的局限性
虽然我们的库支持扩展出更多的功能,但是有些函数在SystemVerilog中可能并不适用,这是由语言的性能造成的。这样的函数可能包括正则表达式处理函数、图像处理函数、像JSON(JavaScript. Object Notation)这样的外部格式解析函数。克服这些限制的一种方法是通过DPI使用另外一种编程语言。 SystemVerilog仅仅为C编程语言定义了一种外语支持,但是通过一些努力,我们可以将SystemVerilog连接到C++上,如第三节所示。
(十)结论
我们创建了一个库来处理常见的验证任务,并把它到验证社区中。 我们的库独立于任何验证方法。未来的方向是开发一个针对于特定验证方法的库,比如UVM。比如添加一个extension of uvm_tlm_generic_payload类的扩展类,一个事件等待器来等待具有timeout机制的uvm_event,一个report_phase函数来收集对仿真的统计数据。
Python爱好者经常使用短语“batteries included”来描述它的标准库,它涵盖了许多实用的函数。 Python中的编码很有趣的一个原因就是因为这套丰富的库。 我们希望我们的库可能成为共享通用库的起点,使SystemVerilog中的编码再次有趣。