UVM-- Sequencer和driver

1 Sequencer和Driver

sequencer和item只在合适的时间点产生需要的数据,至于怎么处理数据,则由driver来实现。为了便于item传输,UVM专门定义了匹配的TLM端口供sequencer和driver使用:

uvm_seq_item_pull_port #(type REQ=int, type RSP=REQ) 
uvm_seq_item_pull_export #(type REQ=int, type RSP=REQ) 
uvm_seq_item_pull_imp #(type REQ=int, type RSP=REQ, type imp=int)

由于driver是请求发起端,所以在driver 一侧例化了下面两种端口: 

uvm_seq_item_pull_port #(REP, RSP) seq_item_port

uvm_analysis_port #(RSP) rsp_port

而sequencer 一侧则为请求的响应端,在sequencer 一侧例化了对应的两种端口:

uvm_seq_item_pull_imp #(REQ, RSP, this_type) seq_item_export

uvm_analysis_export #(RSP) rsp_export 

通常情况下,用户可以通过匹配的第 一对 TLM 端口 完成item 的完整传送,即driver::seq_ item_port和sequencer::seq_ item_ export。这一对端口在连接时同其他端口连接方式一样,即通过 driver::seq_item_port.connect(sequencer::seq_item_export)完成。这一种类型的TLM端口支持如下方法:

•    task get_next_item(output REQ req_arg): 采取blocking的方式等待从sequence获取下一个item。

•    task try_next_item(output REQ req_arg): 采取nonblocking 的方式从 sequencer获取item, 如果立即返回的结果req_arg为null, 则表示sequence还没有准备好。
•    function void item_done(input RSP rsp_arg=null): 用来通知sequence当前的sequence item已经消化完毕,可以选择性地传递RSP参数,返回状态值。

•    task wait_for_sequences(): 等待当前的sequence直到产生下一个有效的item。

•    function bit has_do_available(): 如果当前的 sequence 准备好而且可以获取下一个有效的 item, 则返回 I, 否则返回 0。
•    function void put_response(input RSP rsp_arg): 采取 nonblocking 方式发送 response, 如果成功返回1, 否则返回0。
•    task get(output REQ req_ arg): 采用 get 方式获取 item。
•    task peek(output REQ req_arg): 采用 peek 方式获取 item。
•    task put(input RSP rsp_arg): 采取 blocking 方式将 response 发送回 sequence。

上面这一类端口功能主要用来实现 driver 与 sequencer 的 request 获取和 response 返回。读者在这里需要了解关于 REQ 和 RSP 类型的一致性, 由于 uvm_sequencer 与 uvm_driver 实际上都是参数化的类:

uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)

uvm_driver #(type REQ=uvm_sequence_item, RSP=REQ) 

自定义 sequencer或driver 时, 可以使用默认类型 type REQ = uvm_sequence_item, 以及 RSP 与 REQ 类型保持一致。 这会带来一个潜在的类型转换要求,即 driver 得到 REQ 对象 (uvm_sequence_item) 在进行下一步处理时, 需要进行动态的类型转换, 将 REQ 转换为 uvm_sequence_item 的子类型才可以从中获取有效的成员数据;而另一种可行的方式是在自定义 sequencer 和 driver 时就标明其传递的具体 item 类型, 这样就不用再进行额外的类型转换了。 通常情况下 RSP 类型与 REQ 类型保持一致, 这么做的好处是便于统一处理, 方便 item 对象的复制、 修改等操作。
driver 消化完当前的 request 后, 可以通过 item_done(input RSP rsp_arg=null)方法来告知 sequence此次传输已经结束, 参数中的 RSP 可以选择填入, 返回相应的状态值。 driver 也可以通过 put_response()或 put()方法来单独发送 response。此外发送 response 还可以通过成对的 uvm_ driver::rsp _port和uvm_driver: :rsp_export端口来完成,方法为 uvm_driver::rsp_port::write (RSP)。

事务传输实例:

class bus_trans extends uvm_sequence_item;
rand int data; 
`uvm_object_utils_begin(bus_trans)
`uvm_field_int(data, UVM_ALL_ON) 
`uvm_object_utils_end
...
endclass 

class flat_seq extends uvm_sequence;
`uvm_object_utils(flat_seq)
...
task body();    //相当于run()
uvm_sequence_item tmp;   //父类句柄
bus_trans req, rsp;      //子类句柄
tmp = create_item (bus_trans::get_type(), m_sequencer, "req"); //创建item
void'($cast (req, tmp));   //父类句柄指向子类句柄,cast转换
start_item(req);       //开始发送item  立即返回等待driver.get_next_item
reg.randomize with {data == 10;};  //item 随机化
`uvm_info ("SEQ", $sformatf ("sent a item \n %s", req. sprint()), UVM_LOW)
finish_item(req);      //结束发送   等待仲裁
get_response(tmp);      //得到response数据tmp 等待drive.item_done 父类句柄
void'($cast (rsp, tmp));   //父类句柄指向子类句柄,cast转换
`uvm_info("SEQ", $sformatf("got a item \n %s", rsp.sprint()), UVM_LOW)
endtask 
endclass 

class sequencer extends uvm_sequencer;
`uvm_component_utils(sequencer)
...
endclass 

class driver extends uvm_driver; 
`uvm_component_utils(driver)
...
task run_phase(uvm_phase phase);
REQ tmp;  //默认父类句柄
bus_trans req, rsp; 
seq_item_port.get_next_item(tmp); // 从 sequencer 获取有效的 request item
void'($cast (req, tmp));          
`uvm_info("DRV", Ssformatf ("got a item \n %s", req. sprint()), UVM_LOW)
void'(Scast(rsp, req.clone()));    //子类对象克隆完返回父类句柄
rsp.set_sequence_id(req.get_sequence_id()); //便于数据返回
rsp.data += 100; 
seq_item_port.item_done(rsp);   //response item 对象返回给 sequence
`uvm_info("DRV", Ssformatf ("sent a item \n %s", req. sprint()), UVM_LOW)
endtask 
endclass 

class env extends uvm_env;
sequencer sqr; 
driver drv; 
`uvm_component_utils(env)
...
function void build_phase(uvm_phase phase);
sqr = sequencer::type_id::create("sqr", this); 
drv = driver::type_id::create("drv", this); 
endfunction 
function void connect_phase(uvm_phase phase); 
drv.seq_item_port.connect(sqr.seq_item_export);//连接
endfunction 
endclass 

class testl extends uvm_test; 
env e;
`uvm_cornponent_utils(test1)
function void build_phase(uvm_phase phase); 
e = env::type_id::create("e", this); 
endfunction 
task run_phase(uvm_phase phase); 
flat_seq seq; 
phase.raise_objection(phase); 
seq = new (); 
seq.start(e.sqr); //sequence 挂载到env的sequencer上 就得到m.sequencer,且开始body开始自动执行
phase.drop_objection(phase); 
endtask 
endclass 

//输出结果:
UVM INFO@ 0: uvm_test_top.e.sqr@@flat_seq [SEQ] sent a item 
UVM INFO@ 0: uvm_test_top.e.drv [ORV] got a item 
UVM INFO@ 0: uvm_test_top.e.drv [ORV] sent a item
UVM INFO@ 0: uvm_test_top.e.sqr@@flat_seq [SEQ] got a item 

上面这段例码展示了从 item 定义,到 sequence 定义, 最后到 sequencer 与 driver 的连接。这段精简的代码对于大家理解 driver 从 sequencer 获取 item, 经过时序处理再返回给 sequence 的握手过程很有帮助。 为此做出详细的分析, 帮助大家理清例码中的关键处理:

•    在定义 sequencer 时,默认了 REQ 类型为 uvm_ sequence _item类型,这与稍后定义 driver时采取默认 REQ 类型保持一致。

•    flat_seq 作为动态创建的数据生成载体,它的主任务 flat_seq::body()做了如下的几件事情:
􀀻    通过方法 create_item()创建 request item 对象。
􀀻    调用 start_item()准备发送 item。
􀀻    在完成发送 item 之前对 item 进行随机处理。
􀀻    调用 finish_item()完成 item 发送。
􀀻    有必要的情况下可以从 driver 那里获取 response item。

•    在定义 driver 时, 它的主任务 driver::run _phase()也应通常做出如下处理:
􀀻    通过 seq_item _port. get_ next_item(REQ)从 sequencer 获取有效的 request item。
􀀻    从 request item 中获取数据, 进而产生数据激励。
􀀻    对 request item 进行克隆生成新的对象 response item。
􀀻    修改 response item 中的数据成员, 最终通过 seq_item_port.item_ done(RSP) 将 response item 对象返回给 sequence。

•    对于 uvm_ sequence: :get_response(RSP) 和 uvm_ driver: :item_ done(RSP)这种成对的操作,是可选的而不是必需的,即用户可以选择uvrn_driver不返回response item, 同时sequence也无须获取response item。

•    在高层环境中,应该在connectphase中完成driver到sequencer的TLM端口连接, 比如例码在env: :connect_phase()中通过drv.seq_item_port.connect(sqr.seq_item_export) 完成了driver与sequencer之间的连接。
•    在完成了flat_seq、 sequencer 、 driver和env的定义之 后, 到了test层, 除了需要考虑挂起 objection 防止提前退出, 便可 利用 uvm_sequence 类的方法uvm_sequence::start (SEQUENCER)来实现sequence到sequencer的挂载 。

通信时序 

•    对于sequence而言,无论是flat sequence还是hierarchical sequence, 进一步切分的话, 流向sequencer的都是sequence item, 所以就每个item的 “成长周期 ” 来看, 它起始于create_item() , 继而通过start_item() 尝试从sequencer获取可以通过的权限
•    对于sequencer的仲裁机制和使用方法我们暂且略过, 而driver 一侧将一直处于“ 吃不饱 "的状态,如果它没有了item 可以使用 ,将调用get_next_item()来尝试从sequencer一侧获取item。

•    在sequencer将通过权限交给某一个底层的sequence前,目标sequence中的item应该完成随机化, 继而在获取sequencer的通过权限后, 执行finish_item()。
•    接下来sequence中的 item将穿过sequencer到达driver 一侧, 这个重要节点标志着sequencer 第一次充当通信桥梁的角色已经完成。 driver 在得到新的 item 之后, 会提取有效的数据信息,将其驱动到与 OUT 连接的接口上面。

•    在完成驱动后,driver 应当通过 item_done()来告知 sequence 已经完成数据传送,而sequence 在获取该消息后,则表示 driver 与 sequence 双方完成了这一次 item 的握手传输。 在这次传递中,driver 可以选择将 RSP 作为状态返回值传递给 sequence, 而sequence 也可以选择调用 get_response(RSP)等待从driver 一侧获取返回的数据对象。 

 sequence 与 driver 之间握手的代码建议:

•    在多个 sequence 同时向 sequencer 发送 item 时,就需要有 ID 信息表明该 item 从哪个sequence 来, 这个 ID 信息在 sequence 创建 item 时就赋值了,而在到达 driver 以后, 这个 ID 也可以用来跟踪它的 sequence 信息,这就像食品加工从源头就标记二维码一样, 可使运输和使用更加安全。 这个 ID 信息在稍后 driver 返回 response item 时, 需要给定正确的信息,如上面例码通过函数 set_sequence _id()来标记, 这也就使得sequencer 可以根据 ID 信息来分发这些 response item 返回至正确的 sequence 源头。
•    建议在 driver 中通过 clone()方式单独创建 response item, 保证 request item 和 response item 两个对象的独立性。也许有的用户为了 “ 简便 “,使用 request item 后直接修改它的数据并作为要返回给 sequence 的 response item。这么做看来似乎节能环保,但实际一上可能埋下隐患, 一方面延长了本来应该丢进垃圾桶的 request item 寿命, 另一方面无法对 request item 原始生成数据做出有效记录。 所以讲到这里,请大家记住一点, clone()和 copy()是个好习惯,虽然多了那么几行代码, 但无形中却提高了你的代码稳健性,这种代码方式可以帮助减少一些可能的隐患。
•    为了统一,可以不在定义 sequence 或 driver 时指定 sequence item 类型,使用默认类型 REQ = uvm_sequence_item, 但要注意, 在 driver 侧的类型转换, 例如对get_ next_item(REQ)的返回值 REQ 句柄做出动态类型转换,待得到正确类型之后再进行接下来的操作。
•    有的时候如果要复用一些验证 IP, 用户需要修改原有的底层 sequence item。从处于验证复用的角度, 我们建议通过继承于原有 sequence item 的方式定义新的 item 子类, 同时在顶层通过 factory override 的方式用新的 item 类型替换原有的 item 类型。 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

创芯人

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

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

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

打赏作者

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

抵扣说明:

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

余额充值