UVM序列的乐趣——编码和调试

原文作者:Rich Edelman – Mentor, A Siemens Business

软文标题:UVM序列的乐趣——编码和调试

软文摘要:概述如何构建和编写基本序列,然后扩展到更高级的用法。

在SystemVerilog UVM测试平台中,大多数活动是由编写序列产生的。本文将概述如何构建和编写基本序列,然后扩展到更高级的用法。读者将学习有关产生序列项的序列,这些序列将导致其他序列的发生,并管理其他sequencer上序列的序列。异常事务的序列将会被研究生成,自检程序也将会被编写。

介绍

UVM序列是SystemVerilog代码的集合,该代码运行则会导致“事情发生”。通常,序列会创建一个事务,将其随机化,然后将其发送到sequencer,之后再发送到driver。在driver中,生成的事务通常会引起接口引脚上的某些活动。例如图1所示,WRITE_READ_SEQUENCE可以生成随机的WRITE事务并将其发送到sequencer和driver。driver将会解释说明WRITE事务的有效负载,并使用指定的地址和数据进行写操作。

图1.

创建一个序列

UVM序列只是通过调用new构造的SystemVerilog对象。它可以在许多不同的地方构建,但是通常情况下,测试可以构建序列然后运行它们 – 它们体现了测试。例如,一个测试可被伪编码为:

这里可能有一个序列将所有存储位置从A写入B,还有另一个序列将所有存储位置从A读取到B,或更简单的方法是:WRITE_READ_SEQUENCE它首先写入所有存储位置,然后读取所有存储位置。

下面的测试在fork/join_none内部创建了一个序列,将有四个并行运行的序列。每个序列都设置了一个LIMIT变量,并在fork/join_none的末尾开始运行。一旦完成所有fork里的内容,测试就完成了。


运行一个序列——创建和发送序列项目

下面的序列“my_sequence”是一个简单的序列,它创建事务并将其发送给sequencer和driver。在下面的代码中,body()任务得以实现。这是一个简单的for循环,它会循环LIMIT次。LIMIT是序列中的变量,可以从外部设置。

在for循环中,通过调用new()函数或使用工厂来构建事务对象。然后,调用start_item以开始与sequencer的交互。此时,sequencer将停止执行序列,直到driver准备就绪为止。一旦driver准备就绪,sequencer将使用“start_item”返回。一旦start_item返回,则该序列已被授予使用driver的权限。start_item应该被真正称为“REQUEST_TO_SEND”。现在,该序列具有使用driver的权限,它可以使事务随机化,或者根据需要设置数据值。这就是所谓的“后期随机化”这一理想功能。事务应尽可能接近执行随机化,通过这种方式它们可以捕获任何约束中的最新状态信息。

在将事务随机化并设置了数据值之后,它会使用“finish_item”发送给driver进行处理,finish_item应该被真正称为“EXECUTE_ITEM”。此时,driver获取句柄并执行它。一旦driver调用“item_done()”,然后finish_item将返回并且事务将会被执行。


执行一个序列——The Driver(驱动器)

Driver的代码相对简单,它从uvm_driver派生并包含一个run_phase。Run_phase是由UVM核心自动启动的线程。Run_phase是作为一个永远的开始-结束循环被实现的。在开始-结束块中,driver调用seq_item_port.get_next_item (t)。这是一个任务,它将导致sequencer中的执行,即实质上是向sequencer询问应执行的下一个事务。也许是没有可用的事务,在这种情况下,此调用将阻止,即使用了阻塞调用。(可以使用其他一些非阻塞调用,但它们不在本文讨论的范围之内,因此不建议使用。)当sequencer有要执行的事务时,get_next_item调用将解除阻塞并返回任务参数列表中的事务句柄(在下面的示例中为变量“t”)。现在,driver可以执行事务了。

在此示例中,执行是简单的,它使用事务的covert2string()调用打印一条消息,并等待由事务“duration(持续时间)”类成员变量所控制的一段时间。

完成“执行”后,将进行seq_item_port.item_done()调用,以发信号通知sequencer,并依次返回已执行事务的序列。


控制其他序列

序列可以具有其他序列的句柄;毕竟,一个序列只是一个具有数据成员和“task body()”的类对象,它将作为线程运行。

虚拟序列(Virtual Sequences)

所谓的“virtual sequence”,即其可能不会产生序列项目,而是在其他sequencer上启动序列的序列。这是从一个控制点创建并行操作的便捷方法。

 

虚拟序列仅具有其他序列和定序器(sequencer)的句柄,它启动它们或以其他方式管理它们。通过从上面分配虚拟序列,或使用配置数据库查找,抑或其他方式,虚拟序列可能已获取了sequencer的句柄。它可能已经构造了序列对象,或者已经通过类似的其他方式获取了sequencer的句柄。这个虚拟序列就像一个木偶大师,控制着其他序列。

 

虚拟序列可能类似于:

相关序列(Ralated Sequences)

在下面的代码片段中,有两个序列:ping和pong。它们每个序列都有各自的句柄。它们旨在轮流使用,第一个发送五个事务,然后另一个发送,依此类推。有关完整的代码,请参见附录。

 

首先,构造句柄并设置运行极限。

然后,句柄得到它们的“伙伴”句柄。

最后,两个序列并行启动。

 

写一个自检序列

自检序列是导致某些活动然后检查结果是否正确的序列。最简单的自检序列在一个地址上发出写指令(WRITE),然后从同一地址读取(READ)。现在将读取的数据与写入的数据进行比较,在某些方面,该序列成为GOLDEN模型。

 
写一个流量生成器序列

可以编写视频流量生成器以生成背景流量流,该流模仿或模拟视频显示器可能需要的数据量。视频有最低带宽要求,该计算将随接口或总线上的负载而变化,但是对于此示例而言,简单的计算就足够了。视频流量将每秒产生60次“屏幕”的数据,每个屏幕将具有1920×1024像素,每个点由一个32位字表示。使用这些数字,流量生成器必须每秒创建471MB。

更加完整的流量生成器将根据当前条件调整到达率,即随着其他流量的上升或下降,应调整视频流量的生成率。

 

写与彼此同步的序列

序列将用于同步其他序列(所谓的虚拟序列)。通常,两个序列之间必须有正式的关系。例如,它们不能一起进入其临界区域,即它们必须成为单一文件。或者,它们只能在某个公共临界区域通过后才能运行。

 

下面的代码声明了两个要同步的序列(synchro_A_h和synchro_B_h),它还声明了一个同步器。这些类没有什么特别的,它们只是同意使用某种技术进行同步。

 

同步后的序列获得同步器的句柄和起始地址。

 

同步器控制非常简单。它只是说“GO”代表20个滴答声,“STOP”代表100个滴答声。

 

同步序列开始,它们运行完成,然后就会重新启动。它们永远运行。

 

具有两个状态的简单同步器——GO和STOP。

 

使用同步器的类只有在被告知执行后才能执行。

在仿真中,序列等待直到同步器处于GO状态。进入GO状态后,同步的代码将使用new生成一个事务,然后调用start_item/finish_item执行该事务。在等待访问driver并执行之后,同步的序列返回到循环的顶部并检查同步器状态。它将再次运行,或停止/等待,如下图2所示。

  

图2.

用序列实现中断服务程序

序列将用于提供“中断服务程序”。中断服务程序“睡眠”直至需要。这是一种独特的序列。在此示例的实现中,它创建了一个“中断服务事务”并执行start_item和finish_item。这样,它可以将该ISR事务句柄发送给driver。然后,driver将会保持该句柄,直到发生中断。

 

处理SystemVerilog接口作为driver工作的一部分,它将处理中断。在这种情况下,处理中断意味着将一些数据放入“保持的句柄”中,然后将该句柄标记为完成。同时,中断服务序列一直在等待将事务标记为DONE。用某种说法,这被称为ITEM REALLY DONE。在UVM中,还有其他机制可以处理此类问题,但它们比这种解决方案更不可靠且更复杂。


 

具有“实用程序库”的序列

“实用程序库”序列将被创建和使用。实用程序库是对序列编写器有用的简单代码段,即辅助功能或验证过程的其他抽象。

下面的open_door序列正如其名,它为sequencer和driver打开了大门。现在可以使用序列对象句柄(例如seq.read()和seq.write())随意进行外部调用。

 

构建open_door并使用常规方法启动。然后,测试程序可以像下面红色行一样简单地进行读写操作。

 

从序列中调用C代码

从序列调用C代码(使用DPI-C)很容易,但是有一些限制。DPI导入和导出语句不能放置在类内部,因此在文件、全局或包范围内,它们必须在类之外。因此,它们没有设计或类对象范围。

 

DPI-C导出函数或任务只是使用导出命令已经“导出”的SystemVerilog函数或任务。

 

DPI-C导入函数或任务是具有返回值的C函数。对于一个任务,返回值为int(有关详细信息,请参见SystemVerilog LRM)。对于一个函数,返回值就是返回的值本应该是什么就是什么。

下面定义了一个简单的void函数c_code_add()。它有两个输入,并在指针*z中“返回”一个值。此C函数将导出的SystemVerilog函数称为“sv_code()”。

 

dpiheader.h是为DPI-C检查API的便捷方法。在此示例中,dpiheader.h(下面)非常简单。

这个序列没什么特别之处,它生成事务,但是它确实调用了C函数。(下面的c_code_add红色行)。就编写调用C代码的序列而言,实际上没有什么特别的事情要做。DPI-C代码必须正确编写,并且必须在适当范围内声明。

从C代码调用序列

从C代码调用序列比从序列调用C代码困难。这是因为序列是类对象,类对象没有“DPI-C范围”,因此,为了调用一个序列(或开始一个序列),必须使用其他方法。关于此技术的实现还有很多其他的参考。

  

图3.

序列和事务记录

在本文所讨论的示例代码中,每个序列都并行运行,即在单个sequencer上同时运行。序列和记录的流在下面作为该sequencer的子级列出。

每行是一个执行的序列。在下面的两个屏幕截图(图4和5)中,可以很容易地看到序列如何轮流发送并在driver上执行事务

图4.

  

图5.

结论

现在,本文的读者知道序列不是神秘的或值得担心的事情,而是序列只是“代码”,通常是激励或测试代码从原始的“随机事务生成”到同步再到中断服务程序,可以编写该代码以执行许多不同的操作。序列只是引发激励产生和结果检查的重要代码。

参考文献

[1] SystemVerilog, 1800-2017 – IEEE Standard for SystemVerilog–Unified Hardware Design, Specification, and Verification Language

[2] UVM LRM: IEEE Standard for Universal Verification Methodology Language Reference Manual

 

所有的源代码可以联系作者获得。联系rich_edelman@mentor.com以访问或从Verification Academy下载。

本文先前在DVCon US 2019上发表过。

原文出处:https://verificationacademy.com/verification-horizons/june-2019-volume-15-issue-2/fun-with-uvm-sequences-coding-and-debugging

 

扫描上图二维码可直达课程页面,马上试听

往期精彩:

V2Pro春季班普遍学撑了,秋季班7月报名你还敢来么

30w+还送股送房?60+IC企业2019薪资全面攀升!

UVM RAL模型:用法和应用

我们准备做第二期线下培训,依旧认真且严肃

如果你突然被裁员了,你的Plan B是什么?

[彩虹糖带你入门UVM]

理解UVM-1.2到IEEE1800.2的变化,掌握这3点就够

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注