UVM序列篇之一:新手上路
- sequencer和driver之间使用TLM连接
- sequencer和driver之间采用get 模式;driver作为发起方;主动去向sequencer要item
UVM序列篇之二:sequence和item(上)
item中包含的内容
- 控制类。譬如总线协议上的读写类型、数据长度、传送模式等。
- 负载类。一般即数据总线上的数据包。
- 配置类。这往往是用来控制driver的驱动行为,例如命令driver的发送间隔或者有无错误插入。
- 调试类。用来标记一些额外的信息,用来方便调试,例如该对象的实例序号、创建时间、被driver解析的时间始末等。
sequence可以分类为:
- 扁平类(flat sequence)。这一类中往往只用来组织更细小的粒度,即item示例的组织。
- 层次类(hierarchical sequence)。这一类则是由更高层的sequence用来组织底层的sequence,进而让这些sequence或者按照顺序的方式,或者按照并行的方式,挂载到同一个sequencer上。
- 虚拟类(virtual sequence)。这一类则是最终控制整个测试场景的方式,鉴于整个环境中往往存在不同种类的sequencer和其对应的sequence,我们需要一个虚拟的sequence来协调顶层的测试场景。之所以称这个方式为virtual sequence,是因为该序列本省并不固定挂载于某一种sequencer类型上,而是它会将其内部的各种不同类型的sequence最终挂载到不同的目标sequencer上面。这也是最大的不同于hierarchical sequence的一点。
UVM序列篇之三:sequence和item(下)
-
sequence中也可以做constraint对item中的field做修改;但是这个一般建议在item中完成;下图中仅是个在seq中做约束的例子;
-
test中也可以加约束;
- hierarchical sequence可以同时使用其它hierarchical sequence、flat sequence和sequence item;
UVM序列篇之四:sequencer和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
用户在自定义sequencer或者driver的时候,它们可以使用缺省类型type REQ=uvm_sequence_item,以及RSP与REQ类型一致。同时这会带来一个潜在的类型转换要求,即driver得到REQ对象(uvm_sequence_item)在进行下一步处理时,需要进行动态的类型转换,将REQ转换为uvm_sequence_item的子类型才可以从中获取有效的成员数据;而另外一种可行的方式是在自定义sequencer和driver时就标明了其传递的具体item类型,这样也就不用再进行二次的类型转换了。通常情况下,RSP类型与REQ类型保持一致,这么做的好处是为了便于统一处理,方便item对象的拷贝、修改等操作。
-
在定义sequencer时,默认了REQ类型为uvm_sequence_item类型,这与稍后定义driver时采取默认REQ类型保持一致。
-
实际发送的类型是用户自定义的一个user_item类型;
-
多个sequence都试图要挂载到同一个sequencer上时,涉及sequencer的仲裁功能,
-
可以指定sequencer和driver中传递的类型是user_item, 或者默认uvm_sequence_item;如果选择默认的话,就要进行类型转换
代码建议: -
在多个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的方式定义子类,同时在顶层通过factory override的方式用新的sequence item替换原有的sequence item类型。
UVM序列篇之五:sequencer和sequence(上)
关于发送sequence/item的几点建议:
-
无论sequence处于什么层次,都应当让sequence最终在test结束前执行完毕。
-
尽量避免使用fork-join_any或者fork-join_none来控制sequence的发送顺序。因为这背后隐藏的风险是,如果用户想终止在背后运行的sequence线程而简单使用disable的方式,那么就可能在不恰当的时间点上锁住sequencer。一旦sequencer被锁住,而又无法释放,接下来也就无法无法其它的sequence。所以,如果用户想实现类似fork-join_any或者fork-join_none的发送顺序,还应当在使用disable前,对各个sequence线程的后台保持关注,尽量在发送完item完成握手之后,再终止sequence, 这样才能避免一些问题。
-
如果用户要使用fork-join的方式,那么应当确保有方法可以让sequence线程在满足一些条件后停止发送item。否则,只要有一个sequence线程无法停止,则整个fork-join无法退出。面对这种情况,仍然需要用户考虑监测合适的时间点,才能够使用disable来关闭线程。
在上面谈到的建议中,用户需要认识到,disable的手段对于这种需要严格完成握手的传送方式,是需要额外处理的。否则,轻易的停止sequence可能导致整个sequence与sequencer之间的传送瘫痪。
UVM序列篇之六:sequencer和sequence(下)
- 优先级
- lock()与unlock() 部分没看
sequence如何将item放入 sequencer中
uvm_sequence_base.svh
mid_do是一个callback;
finish_item调用sequencer.send_request()
virtual task finish_item (uvm_sequence_item item,
int set_priority = -1);
uvm_sequencer_base sequencer;
sequencer = item.get_sequencer();
if (sequencer == null) begin
uvm_report_fatal("STRITM", "sequence_item has null sequencer", UVM_NONE);
end
mid_do(item);
sequencer.send_request(this, item);
sequencer.wait_for_item_done(this, -1);
`ifndef UVM_DISABLE_AUTO_ITEM_RECORDING
sequencer.end_tr(item);
`endif
post_do(item);
endtask
uvm_sequencer_base.svh
// Function: send_request
//
// Derived classes implement this function to send a request item to the
// sequencer, which will forward it to the driver. If the rerandomize bit
// is set, the item will be randomized before being sent to the driver.
//
// This function may only be called after a <wait_for_grant> call.
extern virtual function void send_request(uvm_sequence_base sequence_ptr,
uvm_sequence_item t,
bit rerandomize = 0);
// send_request
// ------------
function void uvm_sequencer_base::send_request(uvm_sequence_base sequence_ptr,
uvm_sequence_item t,
bit rerandomize = 0);
return;
endfunction
finish_item中使用的是下边这个函数;
class uvm_sequencer_param_base #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequencer_base;
中
// send_request
// ------------
function void uvm_sequencer_param_base::send_request(uvm_sequence_base sequence_ptr,
uvm_sequence_item t,
bit rerandomize = 0);
REQ param_t;
if (sequence_ptr == null) begin
uvm_report_fatal("SNDREQ", "Send request sequence_ptr is null", UVM_NONE);
end
if (sequence_ptr.m_wait_for_grant_semaphore < 1) begin
uvm_report_fatal("SNDREQ", "Send request called without wait_for_grant", UVM_NONE);
end
sequence_ptr.m_wait_for_grant_semaphore--;
if ($cast(param_t, t)) begin
if (rerandomize == 1) begin
if (!param_t.randomize()) begin
uvm_report_warning("SQRSNDREQ", "Failed to rerandomize sequence item in send_request");
end
end
if (param_t.get_transaction_id() == -1) begin
param_t.set_transaction_id(sequence_ptr.m_next_transaction_id++);
end
m_last_req_push_front(param_t);
end else begin
uvm_report_fatal(get_name(),$sformatf("send_request failed to cast sequence item"), UVM_NONE);
end
param_t.set_sequence_id(sequence_ptr.m_get_sqr_sequence_id(m_sequencer_id, 1));
t.set_sequencer(this);
// uvm_tlm_fifo #(REQ) m_req_fifo; 是uvm_sequencer_param_base 的member
if (m_req_fifo.try_put(param_t) != 1) begin //这个是放入m_req_fifo
uvm_report_fatal(get_full_name(), "Concurrent calls to get_next_item() not supported. Consider using a semaphore to ensure that concurrent processes take turns in the driver", UVM_NONE);
end
m_num_reqs_sent++;
// Grant any locks as soon as possible
grant_queued_locks();
endfunction
virtual class uvm_sequence #(type REQ = uvm_sequence_item, type RSP = REQ) extends uvm_sequence_base;
中也有一个send_request
// Function: send_request
//
// This method will send the request item to the sequencer, which will forward
// it to the driver. If the rerandomize bit is set, the item will be
// randomized before being sent to the driver. The send_request function may
// only be called after <uvm_sequence_base::wait_for_grant> returns.
function void send_request(uvm_sequence_item request, bit rerandomize = 0);
REQ m_request;
if (m_sequencer == null) begin
uvm_report_fatal("SSENDREQ", "Null m_sequencer reference", UVM_NONE);
end
if (!$cast(m_request, request)) begin
uvm_report_fatal("SSENDREQ", "Failure to cast uvm_sequence_item to request", UVM_NONE);
end
m_sequencer.send_request(this, m_request, rerandomize);
endfunction
UVM序列篇之六:sequencer和sequence(下)
- 如何区别于接下来讲到的virtual sequence,毕竟它们两者之间的共同点就是对于各个sequence的协调,而它们的不同则在于,hierarhical sequence主要面对的对象是同一个sequencer,即hierarchical sequence本身也会挂载到sequencer上面,而对于virtual sequence而言,它内部的sequence可以允许面向不同的sequencer种类,
- sequence_lib 作用和hierarhical sequence类似,针对同一个sequencer
就之前的sequence和sequencer而言,它们的差别在于:
-
virtual sequence可以承载不同目标sequencer的sequence群落。而组织协调这些sequence的方式则类似于高层次的hiearchical sequence。virtual sequence一般只会挂载到virtual sequencer上面。
-
virtual sequencer与普通的sequencer相比有着很大的不同,那就是它们只是起到了桥接其它sequencer的作用,即virtual sequencer是一个指示所有底层sequencer句柄的地方,它是一个中心化的路由器。同时virtual sequencer本身并不会传送数据对象例如item,因此,virtual sequencer不需要与任何的driver进行TLM连接。所以,UVM用户需要在顶层的connect阶段,做好virtual sequencer中各个悬空句柄与各个底层实体对象的一一对接,避免句柄的悬空。
UVM序列篇之七:sequence的层次化(上)
virtual sequence和virutal sequencer
UVM初学者在一开始学习virtual sequence和virutal sequencer时容易出现编译和运行时句柄悬空的错误,还有一些概念上的偏差
-
需要区分virtual sequence同其它普通sequence(element sequence、hierarchical sequence)。
-
需要区分virtual sequencer同其它底层负责传送数据对象的sequencer。
-
在virtual sequence中记得使用宏`uvm_declare_p_sequencer来创建正确类型的p_sequencer变量方便后面的各个目标sequencer的索引。
-
在顶层环境中记得创建virtual sequencer并且完成virtual sequencer中各个sequencer句柄从底层到顶层的跨层次连接。
UVM序列篇之八(终):sequence的层次化(下)
Layering Sequence/layered
一些关于如何实现sequencer layer协议转换的方法。
-
无论有多少抽象层次的transaction类别定义,都应该有对应的sequencer作为transaction的路由通道。例如layer_sequencer和phy_master_sequencer分别作为bus_trans和layer_trans的通道。
-
在各个抽象级的sequencer中,需要有相应的转换方法,将从高层次的transaction从高层次的sequencer获取,继而转换为低层次的transaction,最终通过低层次的sequencer发送出去。例如adapter_seq负责从layer_sequencer获取layer_trans,再将其转换为对应的phy_master_sequencer一侧对应的sequence或者transaction,最后将其从phy_master_sequencer发送出去。
-
这些adaption sequence应该运行在低层次的sequencer一侧,作为“永动”的sequence应该时刻做好准备,来服务从高层次的sequencer获取transaction,通过转化将其从低层次的sequencer一侧送出。例如上面在test1中,adapter.start(phy_agt.sqr)用来做好adapter sequence的服务。
-
至于哪一个层次的transaction item的定义,上面的例子仅仅是为了说明layer sequence的一般方法,对于实际中的层次定义和对应的transaction item的定义,我们还需要具体问题具体处理。
从中我们既可以看到各个sequence类别对应的sequencer,同时也有sequence item发送和转换的方向。经过层层的转化,最终高层次的transaction内容可以落实到低层次的protocol agent和physical interface上面。上面的例码中没有给出读回路的处理,即从physical interface穿过physical agent,最终抵达layering sequencer的通信。在实际中,我们可以通过目前以后的回路,经过附加的response item返回来实现,也可以通过一级级的monitor采集response transaction,最终通过monitor转化抽象级返回item的方式来实现。至于选择哪一种反馈回路,这与底层agent反馈回路的实现方式有关,即如果原有的方式通过driver一侧返回response,那么我们建议继续在该反馈链条上进行从低级transaction到高级transaction的转化,如果原有的方式通过monitor一侧返回response,那么我们也建议创建对应的高层次的monitor,实现层次转化。