目录
- driver同sequencer之间的TLM通信采取get模式,即由driver发起请求,从sequencer一端获得item,再由sequencer将其传递至driver。
- 作为driver,永远停不下来,只要它可以从sequencer获取item,它就一直工作。
- 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 #(REQ, 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)
完成;这类端口的功能是主要用来实现driver与sequencer的request获取和response返回。 - 这一种类型的TLM端口支持如下方法:
-
task get_next_item(output REQ req_arg)
//采取blocking的方式等待从sequence获取下一个item。 -
task try_next_item(output REQ req_arg)
//采取blocking方式等待从sequence获取下一个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则返回1,否则返回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。
-
- 关于REQ和RSP类型的一致性,由于
uvm_sequencer
与uvm_driver
实际上都是参数化的类:uvm_sequencer #(type REQ=uvm_sequence_item, RSP=REQ)
uvm_driver #(type REQ=uvm_sequence_item, RSP=REQ)
- 这有潜在的类型转换要求,即driver得到REQ对象后进行下一步处理时需要进行动态的类型转换,将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();
uvm_sequence_item tmp;
bus_trans req, rsp;
tmp = create_item(bus_trans::get_type(), m_sequencer, "req");
void'($cast(req, tmp));
start_item(req);
req.randomize with {data == 10;};
`uvm_info("SEQ", $sformatf("sent a item \n %s", req.sprint()), UVM_LOW)
finish_item(req);
get_response(tmp);
void'($cast(rsp, tmp));
`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);
void'($cast(req, tmp));
`uvm_info("DRV", $sformatf("got a item \n %s", rsp.sprint()), UVM_LOW)
void'($cast(rsp, req.clone()));
rsp.set_sequence_id(req.get_sequence_id());
rsp.data += 100;
seq_item_port.item_done(rsp);
`uvm_info("DRV", $sformatf("sent a item \n %s", rsp.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 test1 extends uvm_test;
env e;
`uvm_component_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);
phase.drop_objection(phase);
endtask
endclass
输出结果:
- 上例展示了从item定义到sequence定义,最后到sequencer与driver的连接,即sequencer和driver之间的item传输过程,帮助理解传输过程的起点、各节点和终点。
- 一旦理解了这其中的朴素原理,那么这两个组件之间的握手也就不再那么神秘了。
- 对于理解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
对象返回给sequencer
。
- 通过
- 对于
uvm_sequence::get_response(RSP)
和uvm_driver::item_done(RSP)
这种成对的操作,是可选的而不是必须的,即可以选择uvm_driver
不返回response item
,同时sequence
也无需获取response item
。 - 在高层环境中应在
connect_phase
中完成driver到sequencer的TLM端口连接,比如通过drv.seq_item_port.connect(sqr.seq_item_export)
完成了driver
与sequencer
的连接。 - 完成flat_seq、sequencer、driver和env的定义后,到
test1
层需考虑挂起objection
防止提前退出,便可利用uvm_sequence类的方法uvm_sequence::start(SEQUENCER)
实现sequence到sequencer的挂载。
通信时序
- 无论是sequence还是driver,它们通话的对象都是sequencer。当多个sequence试图挂载到同一个sequencer上时,涉及sequencer的仲裁功能。
- 对sequence而言,无论是flat sequence还是hierarchical sequence;进一步切分,流向sequencer的都是sequence item,所以就每个item的”成长周期“来看,它起始于
create_item()
,继而通过start_item()
尝试从sequencer获取可以通过的权限。 - driver一侧将一直处于”吃不饱“的状态,如果没有item可以使用,将调用
get_next_item()
尝试从sequencer一侧获取item。 - 在sequencer将通过权限交给某一个底层的sequence前,目标sequence中的item应该完成随机化,继而在获取sequencer的通过权限后,执行
finish_item()
。 - 接下来sequence中的item将穿过sequencer到达driver一侧,这个重要节点标志着sequencer第一次充当通信桥梁的角色已经完成。
- driver得到新的item后会提取有效的数据信息,将其驱动到与DUT连接的接口上面。
- 完成驱动后driver通过
item_done()
告知sequence已经完成数据传送,而sequence获取该消息后则表示driver与sequence双方完成了这一次item的握手传输。 - 在这次传递中driver可选择将RSP作为状态返回值传递给sequence,而sequence也可以选择调用
get_response(RSP)
等待从driver一侧获取返回的数据对象。
握手建议
- 在多个sequence同时向sequencer发送item时,就需要有ID信息表明该item从哪个sequence来,ID信息在sequence创建item时就赋值了。
- 到达driver以后这个ID也可用来跟踪它的sequence信息,使运输和使用更安全,sequencer可根据ID信息来分发这些response item返回至正确的sequence源头。
- 建议在driver中通过
clone()
方式单独创建response item,保证request item和response item两个对象的独立性。 - 有的为了“简便”,在使用request item之后就直接修改其数据并作为要返回给sequence的response item。这么做看来似乎省事,但实际上可能埋下隐患,一方面它延长了本来应该丢进垃圾桶的request item寿命,同时无法再对request item原始生成数据做出有效记录。
- 为统一起见,用户可以不在定义sequencer或driver时指定sequence item类型,使用默认类型
REQ=uvm_sequence_item
,但需要注意在driver一侧的类型转换,例如对get_next_item(REQ)
的返回值REQ句柄做出动态类型转换,待得到正确类型之后再进行接下来的操作。 - 有时如果要复用一些验证IP,用户需修改原有的底层sequence item。从验证复用的角度,建议通过继承于原有sequence item的方式定义新的item子类,同时在顶层通过factory override的方式用新的item类型替换原有的item类型。