一、Sequencer 内部FIFO深度
1. 两个核心FIFO(很多人混淆!)
uvm_sequencer 内部维护两套FIFO,不是一个:
- request FIFO(req_fifo):存放sequence发过来的transaction(req),给driver拉取。
- response FIFO(rsp_fifo):存放driver回传的response事务,给sequence取回。
默认:两个FIFO默认深度都是1!
uvm_sequencer_base里宏定义默认深度UVM_SEQUENCER_DEFAULT_FIFO_DEPTH = 1
含义:
req_fifo深度=1:sequencer最多缓存1个还没有被driver取走的req。
- 场景:driver正在处理第1笔item;sequence发第2笔item,此时req_fifo满,
finish_item()阻塞。 - 所以默认情况下,sequencer不会预存一大堆激励,基本是一进一出的流水线。
修改FIFO深度
// 在test/env的build_phase,修改sequencer的FIFO深度 seqr.set_depth(8); // req_fifo和rsp_fifo同时改成深度8注意:
set_depth(N)同时修改 req_fifo 和 rsp_fifo。
只改rsp_fifo:seqr.set_rsp_depth(N)
什么时候调大FIFO深度?
- Driver处理item很慢,DUT处理时延很长,想让sequence提前产生多笔激励预存在sequencer,提高仿真效率;
- 做burst连续激励,避免sequence频繁阻塞等待driver;
⚠️ 坑:
FIFO深度只是缓存上限,不改变握手规则。
就算FIFO深度设为8,driver调用get_next_item拿到item之后,必须item_done才能取下一笔。FIFO只是“仓库”,不是跳过握手。
一句话记忆:
FIFO是缓存,默认深度1;set_depth同时改req和rsp,set_rsp_depth只改响应FIFO。
二、req/rsp 应答模型(两种模式)
两种工作模式
- NO_RSP 无应答模式(前面我们示例代码用的)
sequence只发req,driver处理完item直接item_done(),不返回response。item_done()不带参数。seq_item_port.item_done(); //无rsp - WITH_RSP 带应答模式(req/rsp完整模型)
driver处理完DUT事务,构造response事务,回传给sequencer,sequence接收rsp。seq_item_port.item_done(rsp); //把rsp传给sequencer
适用场景:读操作!DUT返回读数据、返回状态、error标志。
req:我要读地址0x10;rsp:地址0x10读出的数据=0x55,status=ok。
底层端口:
driver除了seq_item_port(pull req),内部自带response通路:put_response(put端口,push模型,driver推rsp回sequencer的rsp_fifo)。
三、response返回通路:put_response / get_response
1. driver侧:put_response()
两种写法等价:
//写法1:item_done带rsp(推荐,简洁) my_item rsp; rsp = my_item::type_id::create("rsp"); rsp.data = read_data_from_dut; rsp.status = 1'b1; seq_item_port.item_done(rsp); //内部自动调用 put_response(rsp);//写法2:手动put_response,再调用不带参数item_done seq_item_port.put_response(rsp); seq_item_port.item_done();⚠️ 重点:不要同时写item_done(rsp) + put_response,会重复发送两份response,引发报错!
2. sequence侧:get_response() 接收应答
sequence发送完finish_item(req)之后,可以调用get_response(rsp)阻塞等待driver返回的rsp。get_response(rsp):阻塞任务,直到rsp_fifo收到对应response才返回。
完整sequence示例(带rsp):
virtual task body(); my_item req, rsp; req = my_item::type_id::create("req"); start_item(req); req.is_read = 1; req.addr = 16'h0010; finish_item(req); //发送读请求,阻塞等待driver处理 get_response(rsp); //【等待driver返回response】阻塞! `uvm_info("SEQ",$sformatf("read data = %0h, status=%0b",rsp.data,rsp.status),UVM_MEDIUM) endtask3. req和rsp如何配对?(uvm_sequence_item::transaction_id)
核心:依靠 transaction_id 事务ID匹配req和rsp!
- sequence发送req时,UVM自动给req分配唯一
transaction_id - driver构造rsp的时候,必须把rsp.transaction_id = req.transaction_id
如果用
item_done(rsp),UVM底层自动帮你拷贝transaction_id,不用手动赋值。
sequencer收到rsp,根据transaction_id找到对应的等待中的sequence,把rsp交给它。
坑:
如果手动put_response,忘记拷贝transaction_id → rsp无法匹配,rsp留在rsp_fifo,get_response永久阻塞卡死。
4. 非阻塞版本:try_get_response()
和try_get_next_item思路一致,function,不阻塞:
if(!this.try_get_response(rsp)) begin //暂时没有rsp,可以做别的事情 end5. 两个重要开关:set_response_handler / disable_item_done_response_auto
p_sequencer.set_response_handler(callback_func)
注册回调函数,sequencer收到rsp自动调用回调,不用sequence主动调用get_response(异步回调方式)。适合需要异步接收rsp场景。use_sequence_item_response:控制是否开启rsp通路。默认开启。
四、完整带RSP的握手时序图(Mermaid)
五、两种rsp使用模式对比
| 模式 | 说明 | 使用场景 |
|---|---|---|
| get_response 阻塞等待 | finish_item之后调用get_response,同步等待rsp,一一对应 | 同步读写,发一个req等一个rsp(最常用) |
| response_handler回调 | 注册回调函数,rsp到达自动触发函数,不需要get_response | 异步响应,rsp回来时间不确定 |
六、高频坑(面试必问)
- item_done(rsp) 等价于 put_response(rsp),二选一,不能两个一起写,否则会收到两份rsp,报匹配错误。
- transaction_id是req/rsp配对的钥匙;手动put_response必须手动赋值rsp.transaction_id = req.transaction_id。
- rsp_fifo满了的时候,driver调用put_response会阻塞。rsp_fifo深度独立可控
set_rsp_depth()。 - 一个req只能对应一个rsp,不能返回多个rsp;如果DUT返回多个应答,要设计成多个独立transaction。
- 不要在NO_RSP模式调用get_response → 永久阻塞卡死。
面试速记
- sequencer两个FIFO:req_fifo、rsp_fifo,默认深度都是1;set_depth同时修改两者,set_rsp_depth只修改rsp。
- rsp通路:driver用put_response或者item_done(rsp)把response推回sequencer的rsp_fifo。
- sequence通过get_response()阻塞获取应答,依靠transaction_id完成req-rsp配对。
- 两种rsp模式:同步get_response;异步response_handler回调。
继续往下我们可以讲:
- uvm_sequence_item的transaction_id、sequence_id、parent sequence
uvm_sequencer#(REQ,RSP)带两个参数的模板,REQ和RSP类型不同的场景- 异常场景:rsp丢失、rsp乱序、transaction_id冲突仿真报错分析
如果需要请评论或私信~