用于介绍UVM入门的最佳用书,形象介绍了UVM必要的组件,并结合简单的实验进行实现。非常基础,适合没有UVM基础的人快速入门从UVM的源码分析 ,为什么UVM要这样写;让你知其然,更知其所以然。
作者:Salemi, Ray
译者:ajzcr
英文版下载链接:https://kdocs.cn/l/cmxbbr5BU4Id
第二十三章
UVM Sequence
UVM Primer 通篇都在把验证平台的功能拆分来创建更小更简单的设计单元. 这个过程的代码是灵活的, 随着代码的增长, 它会让验证平台更加强大, 也可以在未来的验证平台中重用.
前面的UVM agent章节中, 我们关注于结构. 我们把所有与TinyALU相关的组件取出来封装到一个我们可以轻易重用的组件.
在 UVM transaction 这章, 我们关注于数据. 我们创建了类和对象来更方便的生成, 对比并传输数据. 我把数据类从结构类中分离.
在本章, 我们要关注数据和结构之间的终极混合点: 测试激励. 因为尽管我们已经分离了数据和结构, 我们还没有把数据激励从结构中分离出来. 这个验证平台设计是不好的, 我们的tester 类就有这个问题.
tester 创建了新的 transaction 并发送到验证平台. 这表示 tester 做了两件事: 选择transaction 的顺序, 把 transaction 发送到验证平台. 这会引起重用的问题, 后来的设计者(或是后来的你)可能会认为 tester 是创建新 transaction 的理想方案, 但是因为 tester 有着终止激励的副作用, 很可能这个 tester 是用不了的.
我们可以重载 transaction 类型来控制数据随机化, 但是要改变 transaction 的数量和发送方式的话得重载整个 tester 类. 这就像你要开车到不同目的地需要换方向盘.
好的验证平台把 transaction 的序列(测试激励)从验证平台的结构中分离. transaction 的序列改变, 结构应该不受影响. 我们会展示怎么在 TinyALU 里做到这点, 我们将创建三个 Test:
· 斐波那契 Test, 用 TinyALU 来计算斐波那契数列
· 完全随机 Test, 用带约束随机实现覆盖率目标
· 混合斐波那契 Test 和完全随机 Test 到一个激励流里
这些 Test 表明了我们为何要把激励生成从结构中分离. 当我们创建斐波那契 Test 和完全随机 Test 的时候, 我们得重新编写包含它们的 tester. 如果我们加入更多的测试用例和组合, tester类会爆炸性的增多. 这是很难维护的.
UVM sequence 把激励从验证平台结构中分离出来, 让我们得以创建同个验证平台来运行不同的数据, 这样为我们的 UVM 旅程画上句号.
我们将通过把当前的 transaction 级验证平台转换成使用 sequence 的验证平台来学习 UVM sequence. 这个过程分为 7 步:
· 第一步: 创建携带数据的 TinyALU sequence 项目.
· 第二步: 把 tester 替换成 uvm_sequencer.
· 第三步: 对 driver 进行支持 sequence 的升级.
· 第四步: 在环境里例化 driver 和 sequence 然后相互连接.
· 第五步: 编写 UVM sequence.
· 第六步: 编写用 sequencer 启动 sequence 的 Test.
每一步都是简单的, 到我们进行到最后的时候, 我们就会有一个完整的灵活的验证平台啦.
第一步: 创建携带数据的 TinyALU sequence 项目
两章之前我们学习了 uvm_transaction 类. UVM 从 uvm_transaction 继承创建了uvm_sequence_item 类. uvm_sequence_item 类携带了 uvm_sequence 的从 uvm_sequence 到 uvm_driver 的数据.
首先, 我们把 command_transaction 转换成 sequence_item:
图 178. TinyALU sequence item
sequence_item 类跟 command_transaction 类的区别在以下两点:
· 从 uvm_sequence_item 而不是 uvm_transaction 继承.
· 加入了 result 到 tinyalu_item, 原因后面再解释.
剩下的类都是一样的, 然后我们进入第二步.
第二步: 把 tester 替换成 uvm_sequencer.
在之前的例子里, 我们把所有 TinyALU 组件封装进一个 TinyALU agent 组件. 由于本章跟agent 无关, 我们还是回到所有组件在 env 类里例化的验证平台.
当前版本的 env 例化了 tester 类的 tester_h 对象. 我们之前说过, tester 是有毛病的类. 它做了太多的事情, 是难以维护和重用的. 最坏的情况是我们不得不重写.
我们需要 tester 做的只是把 sequence 传送到 driver, sequence 项目的顺序选择由验证平台的其他部分来做. 所以再把这个类叫做 tester 是不对的, 我们来用 sequencer 这个名字来代替.
sequencer 类 从 sequence 里 取 出 sequence_item, 传 给 driver. UVM 提供了uvm_sequencer 基类. 我们用下面的方法来使用:
图 179. 声明 sequencer
我在 tinyalu_pkg 包里定义所有的类和类型. 上面的代码行中我们定义 sequence 为带sequence_tiems 类型的 uvm_sequencer. 用 typedef 设定参数类可以简化这个类名在设计中的后续使用.
第三步: 对 driver 进行支持 sequence 的升级.
在整篇教程中 , 我 们 使 用 泛 指 的 “driver” 来指代跟 BFM 交 互 的 对 象 . 我们从uvm_component 派生出 driver 然后在环境里例化.
UVM 有一个比我们更专业的”driver”的概念, 它定义了派生自 uvm_component 的跟uvm_sequencer 交互的 uvm_driver. 我们通过继承 uvm_driver 并改变它的 run_phase()方法来升级 driver.
首先我们从 uvm_driver 派生, 把工作参数设为 sequence_item:
图 180. 派生 uvm_driver 类
在派生 uvm_driver 的时候我们继承了 seq_item_port 对象及其所有的功能. 现在我们修改 run phase 来使用新的 seq_item_port:
图 181. driver 里的 run phase
run phase 调用了 seq_item_port 的 get_next_item()方法. 这个方法是阻塞性的, 它从port 里取得 sequence 发送数据, 然后我们从 sequence_item 对象里取得 cmd.
取得指令之后, 我们调用 BFM 中的 send_op 并得到结果. 然后, 我们把结果存到 cmd 对象里, 来把结果返回给调用者. 我们假定传给我们 cmd 的代码还保留着 driver 的句柄, 然后把数据存进 cmd 的话就能把结果返回给调用者了.
我们调用 seq_item_port 对象的 item_done()方法来指示 sequencer 可以发送下一个sequence 项目了.
等一下, seq_item_port 是哪里来的?
这引出了我们的下一步: 升级 driver.
第四步: 在环境里例化 driver 和 sequence 然后相互连接.
我们在 env 用 sequencer 替换 tester 然后连接到 driver 类:
图 182. 用 sequencer 替换 tester
新的 environment 有两处改变:
· tester 替换成 uvm_sequencer.
· sequencer 不需要 FIFO 来连接 driver. driver 本来就是连接到 sequencer 的, 可以直接连接, 我们后面会看到.
下面是 env 中的关键部分代码, 声明了 sequencer 和其他组件:
图 183. TinyALU env 里的声明
例化 sequencer 要注意构造器中的名字要正确:
图 184.
连接 sequencer 到 driver:
图 185. 连接 sequencer 到 driver
我们不需要用 TLM FIFO 来连接 sequencer 和 driver. uvm_sequencer 自带了seq_item_export 对象(类似之前见过的 put_export 和 analysis_export). 通过调用 driver的 seq_item_port 的 connect()方法, 我们将 driver 连接到 sequencer.
之后所有的事情就是升级验证平台来处理 sequence. 我们已经准备好创建 UVM sequence来发送 sequence 项目了.
第五步: 编写 UVM sequence.
uvm_sequence 类在 UVM 层级之外(构造器里没有 parent 参数), 不过可以通过uvm_sequencer 向 UVM 层级发送数据. 从 uvm_sequence 派生的类都继承了三个东西来使得它们可以向 sequencer 发送数据:
· m_sequencer—这个数据成员保存着接收 sequence 项目的 sequencer 句柄.
· body() task—UVM 在开始发送 sequence 的时候启动这个 task.
· start_item()/finish_item()方法对—这两个方法得到对 sequencer 的控制并向其发送 sequence 项目.
我 们 通 过 派 生 uvm_sequence 定 义 uvm_sequence, 将 其 参 数 化 为 我 们 需 要 的sequence_item, 然后编写 body()方法来创建 sequence_itrm 并发送给 sequencer. 我们来通过生成著名的斐波那契数列来学习吧.
斐波那契数列通过对前两个数字相加生成后面的数字序列. 这个序列中的数字在大自然中普遍存在, 比如行星到太阳的距离, 比如雏菊中每一圈的花瓣数量.下面是 8 位数据可以存储的斐波那契数值:
0 1 1 2 3 5 8 13 21 34 55 89 144 233
我们的 sequence 用 TinyALU 中的加法器生成这些数字.
下面是代码:
图 186. 派生 uvm_sequence 来创建斐波那契数列
我们派生了 uvm_sequence 并将其参数化使之可以作用于 sequence_item. 我们用uvm_object_utils()宏而不是像我们用 uvm_component 一样用 uvm_component_utils()宏, 还用了一个单参数的构造器.
我们所有的工作在 body() task 里完成. UVM 在有人启动 sequence 的时候调用 body()task:
图 187. 斐波那契 command 对象的操作
上面的片段展示了 uvm_sequence 的 body() task 的基础. 在 12 行, 我们声明了sequence_item 对象句柄, 然后例化了指令对象.
start_item()方法在uvm_sequencer准备好接收sequence项目前会阻塞. 解除阻塞的时候, 我们就知道验证平台已经准备好接收指令了. 这里的指令是简单的复位.
finish_item()方法在 driver 完成指令前是阻塞的, 运行通过 finish_itrm()时, 我们就地址 TinyALU 复位完成, 准备好了斐波那契操作了:
图 188. 输出斐波那契数列
上面的斐波那契循环展示了怎么从 DUT 中读取结果. 我们调用 start_item()来等待sequencer, 之后我们载入两个之前的斐波那契数值跟 add_op 指令一起到 ALU. 然后我们调用finish_item()方法来等待 driver 调用 item_done(), 在指令完成前都等待.
记得前面的 driver 把结果写回指令 transaction, 我们保留着 transaction 的句柄, 所有可以在finish_item() task 完成返回之后读取结果.
这就是编写 sequence 的全部过程. 下面我们来探讨怎么在 test 里启动 sequence.
第六步: 编写用 sequencer 启动 sequencer 的 Test.
所有我们 验证平台 中 的 test 都有着同样的 build_phase() 和end_of_elaboration_phase()方法, 我们要用这些方法来创建基类, 然后派生出我们的 test:
图 189. TinyALU base test
end_of_elaboration_phase()方法从 environment 里拷贝了 sequencer. 现在任何派生base test 的 test 都有 sequencer 的句柄了.
斐波那契 Test
斐波那契 test 例化了 fibonacci_sequence 然后用 sequencer 启动了它:
图 190. 斐波那契 test
所有的 uvm_sequence 都用 start()方法, 这个方法以 uvm_sequencer 为参数并在sequence 完成之后返回. 这个例子里, start()方法在我们生成斐波那契数列之后就返回了. 这 个 Test 的输出如下:
图 191. 生成斐波那契数
我给 fibonacci_sequence 的 start()方法传递了一个 sequencer 然后就得到了斐波那契数字. 由于我们验证平台里有一个 sequencer, 我们就可以在不改变结构的情况下随意的运行组合激励.
指定了 sequencer 的句柄之后, 我们可以通过调用 start()方法传入 sequencer 参数来随意启动任何 sequence. 不过传入 sequencer 到 start()方法也不是必需的. UVM 给我们提供了另外的使用”virtual”的机会, 就是将不用 sequencer 的 sequence 取名为 virtual sequence.
Virtual Sequence
我们的斐波那契 sequence 是简单的一次性的 sequence, 它运行它的激励然后结束. 不过, 我们经常会需要通过顺序或并行的运行多个 sequence 来得到混合的行为. 我们来用runall_sequence对TinyALU进行完全Test吧. runall_sequence是一个virtual sequence, 没有通过 sequencer 调用, 是通过得到获取 sequencer 的句柄然后用这个句柄来调用其他 sequence.
uvm_pkg 提供了 uvm_top 对象解决这个问题. uvm_top 对象的类型是 uvm_root, 它提供了向其他功能组件的访问. 其中一个功能组件是 find()方法.
uvm_top.find()方法以组件实例名的字符串为输入返回这个组件的句柄. 我们可以在find 字符串里用通配符来在不知道整个层级的情况下查找组件.
uvm_top.find()方法返回的是 uvm_component 类型的对象, 所以我们需要把返回的基类值转换成需要的类. 这个例子里, 我们要通过将”*.env_h.sequencer_h”传入 uvm_top.find()方法得到 sequencer 的句柄然后转换返回的值到 sequencer 类型变量.
下面是 runall_sequence 类:
图 192. virtual runall sequence 的顶部
上面的代码为了教学目的将获取句柄的流程拆分为三步. 首先我们用 uvm_top.find()来得到 sequencer 的 uvm_component 句柄. 然后我们判断是否获取成功, 如果没成功, 表示字符串中有拼写错误. 得到句柄之后我们将其转换成 sequencer 句柄, 并判断是否转换成功. 转换失败的话表示我们得到的句柄不是 sequencer 组件.
runall_sequence 使 用 三 个 其 他 的 sequence 来完成工作 : reset_sequence, maxmult_sequence 和 random_sequence. 我们用构造器例化它们, 然后在 body()方法里启动:
图 193. virtual sequence 的 body 方法
runall_sequence virtual sequence 按顺序启动其他的 sequence. 先复位 TinyALU, 然后进行最大值的乘法, 最后运行随机 sequence.
不用 Sequencer 启动 Virtual Sequence
UVM 开发者称 virtual sequence 为”virtual”是因为它们可以是不用 sequencer 启动的. 这里是一个 full_test 的不用 sequencer 启动 sequence 的例子:
图 194. 不用 sequencer 启动 sequence
runall_seq 用 null 传入 start()方法. 如我们所见, runall_sequence 有自己的方式来得到 sequencer.
多线程里的 Virtual Sequence
我们经常会需要在多线程里运行 sequence. 可以通过这个检查数据从不同端口进入是否会造成冲突. 我们用 SystemVerilog 的 fork/join 结构来创建多线程. fork 表达式启动不同的线程, join 表达式在所有任务完成之前阻塞. 这里的”任务可以是 SystemVerilog 表达式, task 调用或者sequence 启动.
我们来创建同时运行斐波那契 sequence 和随机 sequence 的一个 sequence.
这里是启动并行运行的 test:
图 195. 用 sequencer 启动 virtual sequence
这个例子里我们在顶层 sequence 里传入 sequencer, 它会在启动 fibonacci_sequence 和short_random_sequence 的时候使用 sequencer.
这里是 parallel_sequence:
图 196. 使用 m_sequencer 启动并行的 sequence
start()方法存储了 m_sequencer 数据参数, 然后调用 body() task. m_sequencer 用来调用其他 sequence 的 start().
我们首先调用 reset_sequence 的 start(), 然后用 fork/join 结构来并行启动fibonacci_sequence 和 short_random_sequence. sequencer 会对两个 sequence 仲裁, 保证它们能用到 DUT.
uvm_sequencer 支持很多仲裁机制. 默认的是 FIFO, 就是我们在运行仿真时所见的穿插sequence 项目的方式:
图 197. 并行 sequence 发出的交错操作
我们可以看到斐波那契指令和随机 sequence 中的随机指令穿插到一起了. 我们检查过了, 这些指令没有互相干扰, 斐波那契数列还是跟往常一样.
总结
在本章, 我们学习了 UVM 技术的最后一篇: UVM sequence. Sequence 让我们得以拆分测试激励和验证平台结构, 混合不同的激励创建多个 test.
我们学习了怎样将验证平台转换成 sequence 驱动的验证平台, 怎样派生正确的类来使用sequence, 怎样把数据从 DUT 传回验证平台. 然后我们见识了怎样在 sequence 里启动其他sequence, 怎样并行启动 sequence.
我们也见识里创建多个激励行为并在 test 和 sequence 里混合的强大.
相关资源文档
笔试题汇总
没有回复内容