UVM--sequence和item 序列通信

1 新手上路

本节将主要围绕下面几个核心词, 阐述它们的作用、 分类以及相互之间的互动关系:

•    sequence item
•    sequence
•    sequencer
•    driver

•    sequence 对象会产生目标数量的 sequence item 对象。借助于SV 的随机化和 sequence item 对随机化的支持,产生的每个 sequence item 对象中的数据内容都不相同。

• 产生的 sequence item 经过 sequencer 流向 driver。

driver 陆续得到每一个sequence item,经过数据解析, 将数据按照与 DUT 的物理接口协议写入到接口上,对 DUT 形成有效激励。

• 必要时,driver 在每解析并消化一个sequence item后,将最后的状态信息写回sequence item 对象再返回给 sequencer, 最终抵达sequence对象 。 这么做的目的在于, 有时sequence需要得知如driver与DUT互动的状态, 而这需要driver有 一个回路将更新的sequence item对象写回至sequence。

equence item是driver与DUT每一次互动的最小粒度内容 。例如,DUT若为一个slave端,driver扮演master访问DUT的寄存器,那么 sequence item要定义的数据信息至少包括访问地址、 命令码、 数据和状态值 。 driver取得这样的信息后, 通过时序方式在 interface一侧发起激励送至DUT。 按照一般总线做寄存器访问的习惯, 这种访问在时序上大致保持几个时钟周期, 直至数据传送完毕, 而由driver再准备发起下一次操作 。 用户除了可以在声明sequence item时添加必要的成员变量, 也可以添加对这些成员变量进行操作的成员方法 。这些添加了的成员变量, 需要充分考虑在通过sequencer传递到如driver前是否需要随机化,例如上面例子中的访问地址和数据等都应该通过SV关键词rand声明为随机化变量, 以便后期进行随机化处理 。

一个sequence 产生多个sequence item, 也可以产生多个sequence从产生层次来看, sequence item是最小粒度, 它可以由sequence生成而相关sequence也可以进一步组织继而 实现层次化, 最终由更上层的sequence进行调度。 这么看来 , sequence可以看做是产生激励内容的载体。
在sequence与driver之间起桥梁作用的是sequencer。 sequencer与driver都是 component组件,它们之间的通信是通过TLM端口实现的 。TLM端口在例化中要指定通信参数driver与sequencer之间的TLM通信参数就是sequence item类。 这 一限制使得不能改变sequencer到driver的传输数据类型,同时与sequencer挂接的sequence创建的sequence item类型也应为指定类型。

激励驱动链的最后一道关卡是 driver。  对于常见用法, driver往往是消化完一个sequence item, 报告给sequencer和sequence, 同时再请求消化下一个sequence item。所以 driver看起来永远喂不饱。在消化每一个sequence item之前, 该 item中的数据是已经随机化好的,所以每个 item内容一般各不相同driver自己并不轻易修改item中的值,它把item中的数据按照与DUT的物理协议时序关系驱动到接口上。 例如, 对于一个标准的写操作, driver 不但需要按照时序依次驱动地址总线、 命令码总线和数据总线, 还应该等待从端的返回信号和状态值, 这样才算完成一次数据写传输 。又如果是一个读操作, driver 还应该在驱动完地址总线和命令码总线之后, 等待返回信号、 状态值和读出的数据, 并且在有需要的情况下,将读出的数据再写回到sequence item中, 通过sequencer最后返回给sequence。

uvm_sequence _item 和 uvm_ sequence 都基于 uvm_object, 与 uvm_ component 只在 build 阶段作为 UVM 环境的“ 不动产 ”进行创建和配置不同,它们可以在任何阶段创建。这种类的继承带来的 UVM 应用区别在于:

•    因为无法判定环境在 run 阶段的什么时间点创建 sequence 和将其挂载 (attach) 到 sequencer 上, 所以无法通过 UVM 环境结构或 phase 机制识别 sequence 的运行阶段。
•    uvm_object 独立于build 阶段, 这使得用户可有选择地、 动态地在合适时间点挂载所需的 sequence 和 item。
•    考虑到 uvm_ sequence 和 uvm_ sequence _item 并不处于 UVM 结构当中,所以顶层在做配置时,无法按照层次关系直接配置到sequence,sequence一旦活动起来, 必须挂载到一个 sequencer 上, 这样 sequence 可以依赖于 sequencer 的结构关系, 间接通过 sequencer 来获取顶层的配置和更多信息。

sequence 只负责生成 item 的内容, 而不应该控制 item 消化的方式和时序, 而驱动激励时序的任务应当由 driver 来完成。 请注意, item 的生成和传送并不表示最终的接口驱动时序, 决定这一点的还包括 sequencer 和driver。 sequencer 之所以作为一个 “ 路由" 管道设立在 sequence 和 driver 之间, 是因为我们看重它的两个特点:

•    作为一个组件, sequencer 可以通过 TLM 端口与 driver 传送 item 对象。
•    在面向多个并行 sequence 时, sequencer 有充分的仲裁机制来合理分配和传送 item, 继而实现并行)item 数据传送至 driver 的测试场景。

数据传送机制采用的是 get 模式而不是 put 模式。 我们在 TLM 传输中介绍了两种典型的数据传送场景,put 模式下是 sequencer将数据put至driver, get模式下则是driver从sequencer获取 item 。之所以选择get 模式, UVM 是基于下面的考虑:

•    get 模式下, 当 item 从 sequence 产生、 穿过 sequencer 到达driver 时, 我们可以结束该传输(假如不需要返回值的话)。而如果是put 模式,则必须是 sequencer将 item 传送至 driver, 必须收到返回值才可以发起下一次的传输。 这从效率上看,是有差别的。

•    如果需要让 sequencer 拥有仲裁特性, 以使多个 sequence 同时挂载到 sequencer 上,那么 get 模式更符合 ” 工学设计 ”。 这是因为 driver 作为 initiator, 一旦发出 get 请求, 会先通过 sequencer, 继而获得仲裁后的 item 。

2 sequence 和 item

item 是基于 uvm_object 类的,这表明它具备 UVM 核心基类所必需的数据操作方法,例如 copy()、 clone()、 compare()、 record()等, 这里不再赘述。 还要了解的是, item 通常应具备什么类型的数据成员。 我们将它们划分为如下几类:

•    控制类。 比如总线协议上的读写类型、 数据长度、 传送模式等。
•    负载类。 一般指数据总线上的数据包
•    配置类。 用来控制 driver 的驱动行为, 例如命令 driver 的发送间隔或有无错误插入。
•    调试类。 用来标记一些额外信息方便调试, 例如该对象的实例序号、 创建时间、 被driver 解析的时间始末等

class bus_trans extends uvm_sequence_item;
rand bit write; 
rand int data; 
rand int addr; 
rand int delay; 
static int id_num;
`uvm_object_utils_begin(bus_trans);
`uvm_field_int ...   //域的自动化
`uvm object utils end
...
endclass 
class testl extends uvm_test;
`uvm_component_utils(test1)
...
task run_phase(uvm_phase phase);
bus_trans tl, t2; 
phase.raise_objection(phase); 
#l00ns; 
tl = new("tl"); //先例化再方法
tl. print() ; 
#200ns; 
t2 = new("t2"); 
void'(t2.randomize()); //随机化 
t2. print() ; 
phase.drop_objection(phase); 
endtask
endclass

结果: 

item使用特点:

•    如果数据域属于需要用来做驱动, 应考虑定义为 rand 类型, 同时按照驱动协议给出合适的 constraint。
•    由于 item 本身的数据属性, 为了充分利用 UVM 域声明的特性, 建议通过'uvm_field_ xxx 宏声明必要的数据成员, 以便日后 uvm_object 的基本数据方法的自动实现, 例如上面的 print() 函数。
•    在上面的例子中, t1没有被随机化而 t2 被随机化了, 这种差别在 item 通往 sequencer 之前是很明显的。UVM 要求 item 的创建和随机化都发生在sequence 的 body()任务中,而不是发生在 sequencer 或 driver 中

•    按 item 对象的生命周期,它的生命开始于 sequence 的 body()方法, 经历了随机化并穿越 sequencer 最终到达 driver, 直到被 driver 消化之后,它的生命一般才会结束。之所以要突出这一点,是因为一些用户在使用中会不恰当地直接操作 item 对象,直接修改其中的数据,或者将它的句柄发送给其他组件使用,这会无形中修改 item 的数据基因,或延长 item 对象的寿命。 需要注意这种不合适的对象操作方式, 替代的方式则是合理利用 copy()和 clone()等数据方法。

接下来我们需要理清 item 和 sequence 以及 sequence 群落之间的关系。 简而言之,一个sequence 可以包含一些有序组织起来的 item 实例, 考虑到 item 在创建后需要被随机化,sequence 在声明时需预留一些可供外部随机化的变量,这些随机变量部分用来通过层级传递约束来控制 item 对象的随机变量,一部分用来对 item 对象之间加以组织和时序控制的。为了区分几种常见的 sequence 定义方式,我们在介绍 sequence 之前首先将其分类为

•    扁平类 (flat sequence)。这类往往只用来组织更细小的粒度,即 item 实例构成的组织。

•    层次类 (hierarchical sequence)。这类是由更高层的 sequence 用来组织底层的sequence, 进而让这些 sequence 或按照顺序方式或按照并行方式,挂载到同一个sequencer 上。

•    虚拟类 (virtual sequence)。这一类则是最终控制整个测试场景的方式,鉴于整个环境中往往存在不同种类的 sequencer 和其对应的 sequence, 我们需要一个虚拟的 sequence来协调顶层的测试场景。之所以称这个方式为 virtual sequence, 是因为该序列本身并不会固定挂载于某一种 sequencer 类型上, 而是将其内部不同类型 sequence 最终挂载到不同的目标 sequencer 上面。这也是 virtual sequence 不同于 hierarchical sequence 的最大一点。

flat sequence

一个 flat sequence 往往由细小的 sequence item 群落构成,在此之上 sequence 还有更多的信息来完备它需要实现的激励场景。 flat sequence一般包含的信息有:

•    sequence item 以及相关的 constraint 用来关联生成的 item 之间的关系,从而完善出个 flat sequence 的时序形态。

•    除了限制 sequence item 的内容, 各个 item 之间的时序信息也需要由 flat sequence 给定,例如何时生成下一个 item 并且发送至driver。

•   对于需要与 driver 握手的情况(例如读操作),或等待 monitor 事件从而做出反应(例如 slave 的 memory response 数据响应操作),都需要 sequence 在收到另一侧组件的状态后,再决定下一步的操作即响应具体事件从而创建对应的 item 并且发送出去
 

class bus_trans extends uvm_sequence_item;
rand bit write; 
rand int data; 
rand int addr; 
rand int delay; 
static int id_num;
`uvm_object_utils_begin(bus_trans);
`uvm field int ...   //域的自动化
`uvm object utils end
...
endclass 

class flat_seq extends uvm_sequence;
rand int length; 
rand int addr; 
rand int data[]; 
rand bit write; 
rand int delay; 
constraint cstr{data.size() == length; 
  foreach(data[i]) soft data[i] == i; soft addr == 'hlOO; 
  soft write == l; 
  delay inside { [1: 5]};};
`uvm_component_utils(flat_seq)
...
task body() ;
bus_trans tmp; 
foreach(data[i]) begin
tmp = new (); 
tmp.randomize () with {data == local::data[i];
addr == local::addr + i<<2; 
write == local::write;
delay == local::delay; } ; 
tmp.print();
end 
endtask 

endclass 
class testl extends uvm_test;
`uvm_component_utils(uvm_test)
...
task run_phase(uvm_phase phase);
lat_seq seq; 
phase.raise_objection(phase); 
seq = new(); 
seq.randomize() with {addr == 'h200; length == 2;); 
seq.body() ; 
phase.drop_objection(phase); 
endtask
endclass

结果: 

在这个例子中, 我们暂时没有使用 sequence 的宏或其他发送 item 的宏来实现 sequence/item 与sequencer 之间的传送, 而是用更直白的方式来描述这种层次关系 。 flat_seq类可以看做是一个更长的数据包, 数据包的具体内容、 长度、 地址等信息都包含在 flat_seq中。 在生成 item过程中, 通过将自身随机变量作为 constraint 内容来限定 item随机变量, 这是 flat sequence 的大致处理方法。 上面例码没有给出例如'uvm_do/'uvm _do_ with/'uvm _ create 等宏是为了让读者首先认清 sequence与item之间的关系。因此该例也只给出在flat_seq:: body() 任务中创建和随机化 item, 而省略了发送 item 。 

class bus_trans extends uvm_sequence_item;
rand bit write; 
rand int data; 
rand int addr; 
rand int delay; 
static int id_num;
constraint cstr{data.size() == length; 
  foreach(data[i]) soft data[i] == i; soft addr == 'hlOO; 
  soft write == l; 
  delay inside { [1: 5]};};
`uvm_component_utils_begin(bus_trans)
`uvm_field_...
`uvm_component_utils_end
...
endclass

class flat_seq extends uvm_sequence;
rand int length; 
rand int addr; 
`uvm_object_utils(flat_seq)
...
task body() ;
bus_trans tmp; 
tmp = new(); 
tmp. randomize() with { length == local: : length;
                addr == local: : addr; } ; 
tmp.print(); 
endtask 
endclass 

class testl extends uvm_test; 
`uvm_object_utils(test1)
...
task run_phase(uvm_phase phase);
flat_seq seq; 
phase.raise_objection(phase); 
seq = new(); 
seq.randomize() with {addr == 'h200; length == 3;}; seq. body() ; 
phase.drop_objection(phase); 
endtask 
endclass 

 结果:

 从修改后的例码可以看到, 我们可以将一段完整发生在数据传输中的、 更长的数据 “ 收编 ” 在一个 bus_trans 类中,提高这个 item 粒度的抽象层次,让它变得更有 “ 气质 ”。而一旦拥有了更成熟的、 更合适切割的 item, 上层的 flat sequence 在使用过程中就会更顺手一些。 比如在上面的例子中,flat_seq 类不再操闲心考虑数据内容,只考虑这个数据包的长度、地址等信息, 因为扩充随机数据的责任一般由 item 负责就足够了,使用 flat_seq 的用户不需要考虑多余的数据约束。

Hierarchical Sequence

hierarchical sequence 区别于 flat sequence 的地方在于,它可以使用其他 sequence, 当然还有item, 这么做是为了创建更丰富的激励场景。 通过层次嵌套关系, 可以让 hierarchical sequence 使用其他 hierarchical sequence 、 flat sequence 和 sequence item, 这也就意味着,如果底层的 sequence item 和 flat sequence 的粒度得当,那么用户就可以充分复用这些 sequence/iterm 来构成形式更加多样的 hierarchical sequence。 接下来我们就之前定义的 bus_trans 和 flat_seq 给出一个简单的 hier_seq 类, 帮助大家理解这些 sequence/iterm 类之间的联系:

class hier_seq extends uvm_sequence;
`uvm_object_utils(hier_seq)
function new(string name = "hier_seq"); 
super.new(name); 
endfunction 
task body() ; 
bus_trans t1, t2; 
flat_seq s1, s2; 
`uvm_do_with(t1,{length == 2;})
fork
`uvm_do_with(sl, {length == 5;}) 
`uvm_do_with (s2, {length == 8;})
join 
`uvm_do_with(t2, {length == 3;})
endtask 
endclass

从hier_seq::body()来看,它包含有 bus_trans t1, t2 和 flat_seq s1, s2, 而它的层次关系就体现在了对于各个 sequence/item 的协调上面。例码中使用了'uvm_do_ with 宏,这个宏完成了三个步骤

•    sequence 或 item 的创建
•    sequence 或 item 的随机化
•    sequence 或 item 的传送
区别于之前的例码,这个例码通过'uvm_do_with 宏帮助大家理解,所谓的 sequence 复用就是通过高层的 sequence 来嵌套底层的 sequence/item, 最后创建期望的场景。 在上面的例子中, 既有串行的激励关系,也有并行的激励关系, 在更复杂的场景中还可以考虑加入事件同步(通过 uvm_event 、 uvm_barrier 或 interface 上的信号变化),或者一定的延迟关系(最好基于时钟)来构成 sequence/item 之间的时序关系。

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

创芯人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值