UVM sequence-driver 通信模型(详细讲解)
核心一句话:sequence 负责产生激励事务(item),driver 负责把事务变成硬件信号打给DUT;两者通过
uvm_seq_item_pull_port/export握手通信,属于pull模型(driver主动拉取item),这是UVM默认标准模型。
一、整体角色分工
- sequence(uvm_sequence):激励发生器,写在
body()里,创建seq_item,把item交给sequencer。不直接访问DUT信号 - sequencer(uvm_sequencer):中间“快递中转站”。有FIFO缓存,接收sequence发来的item,等待driver来取。
- 端口:
uvm_seq_item_pull_imp #(TR, this_type) seq_item_export;
- 端口:
- driver(uvm_driver):从sequencer拿transaction,解析item,驱动接口信号到DUT。
- 端口:
uvm_seq_item_pull_port #(TR) seq_item_port;
- 端口:
- seq_item(uvm_sequence_item):事务数据包,存放激励数据、控制位(addr/data/wr_en等),纯数据载体
UVM树简易结构(Mermaid)
连接关系:
driver.seq_item_port.connect(sequencer.seq_item_export);
sequence 挂载到 sequencer:seq.start(seqr);
二、通信端口类型:pull端口(重点!)
uvm_seq_item_pull_port包含两组API,对应两个握手动作:
get_next_item(req):driver向sequencer申请拿新item(阻塞)item_done():driver告知sequencer当前item传输完成
✅ 这就是经典2步握手,是sequence-driver最核心通信机制。
完整通信时序(标准2步握手流程)
1. sequence 执行 `start_item(item)` → 请求sequencer分配权限,阻塞等待sequencer空闲 2. sequence 执行 `finish_item(item)` → 将item发送给sequencer的内部FIFO,等待driver取走 ===== 上面是sequence侧;下面driver侧循环 ===== 3. driver 中 `seq_item_port.get_next_item(req);` → driver阻塞,去sequencer拉取item。 如果sequencer还没有item,driver停在这里等(阻塞) 4. driver拿到req(seq_item),解析里面的数据,驱动DUT接口信号 5. 驱动完成后,调用 `seq_item_port.item_done();` → 通知sequencer:这个item处理完毕! → sequencer收到item_done后,唤醒sequence,sequence继续产生下一笔激励 6. driver回到循环,再次调用 get_next_item,取下一个item,往复循环⚠️ 关键点:
get_next_item()和item_done()必须成对出现,不能丢、不能乱序!get_next_item拿到item之后,在item_done之前,sequencer不会给driver下一个item
三、代码最简可运行Demo(带逐行注释)
1. 事务 my_item.sv
class my_item extends uvm_sequence_item; `uvm_object_utils(my_item) rand bit [7:0] data; function new(string name="my_item"); super.new(name); endfunction endclass2. Sequence (产生激励)
class my_seq extends uvm_sequence#(my_item); `uvm_object_utils(my_seq) `uvm_declare_p_sequencer(my_sequencer) //绑定sequencer类型 function new(string name="my_seq"); super.new(name); endfunction virtual task body(); my_item req; repeat(3) begin //产生3笔transaction req = my_item::type_id::create("req"); start_item(req); //【1】请求sequencer权限,阻塞 assert(req.randomize()); finish_item(req); //【2】把item发给sequencer,等待driver取完+item_done `uvm_info("SEQ",$sformatf("send item data = %0d",req.data),UVM_MEDIUM) end endtask endclass3. Sequencer
class my_sequencer extends uvm_sequencer#(my_item); `uvm_component_utils(my_sequencer) function new(string name="my_sequencer",uvm_component parent=null); super.new(name,parent); endfunction endclass4. Driver(从sequencer拉item,驱动DUT)
class my_driver extends uvm_driver#(my_item); `uvm_component_utils(my_driver) virtual my_intf vif; function new(string name="my_driver",uvm_component parent=null); super.new(name,parent); endfunction virtual task run_phase(uvm_phase phase); my_item req; forever begin //【3】driver主动拉取item,没有item就阻塞等待 seq_item_port.get_next_item(req); // 把item数据驱动到DUT接口 vif.data <= req.data; #10ns; `uvm_info("DRV",$sformatf("drive DUT data=%0d",req.data),UVM_MEDIUM) //【5】通知sequencer:当前item处理完成! seq_item_port.item_done(); end endtask endclass5. Environment + Test
class my_env extends uvm_env; `uvm_component_utils(my_env) my_sequencer seqr; my_driver drv; function new(string name="my_env",uvm_component parent=null); super.new(name,parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); seqr = my_sequencer::type_id::create("seqr",this); drv = my_driver::type_id::create("drv",this); endfunction virtual function void connect_phase(uvm_phase phase); super.connect_phase(phase); //【端口连接!】driver端口连sequencer的export drv.seq_item_port.connect(seqr.seq_item_export); uvm_config_db#(virtual my_intf)::set(this,"drv","vif",top.vif); endfunction endclass class test1 extends uvm_test; `uvm_component_utils(test1) my_env env; function new(string name="test1",uvm_component parent=null); super.new(name,parent); endfunction virtual function void build_phase(uvm_phase phase); super.build_phase(phase); env = my_env::type_id::create("env",this); endfunction virtual task run_phase(uvm_phase phase); my_seq seq; phase.raise_objection(this); seq = my_seq::type_id::create("seq"); seq.start(env.seqr); //sequence挂载sequencer启动 phase.drop_objection(this); endtask endclass四、两种握手方式:2步握手 vs 1步get模型
✅ 模型A:2步握手(get_next_item + item_done,最常用)
优点:driver拿到item后可以花费任意长时间驱动DUT(多周期激励)
缺点:必须成对调用,写错容易卡死仿真(死锁)
✅ 模型B:一步getseq_item_port.get(req)
等价于:get_next_item+ 自动内部调用item_done
适合:单周期简单激励,driver拿到item立刻处理完。
限制:拿到item之后不能跨周期驱动,不适合复杂时序激励。
seq_item_port.get(req); //内部自动完成 item_done,不需要手动写item_done五、常见坑(面试高频考点)
- 死锁场景1:调用
get_next_item,忘记写item_done()
→ sequencer永远等待ack,sequence卡在finish_item,仿真卡死。 - 死锁场景2:多次连续调用
get_next_item,中间没有item_done
→ 一次只能持有1个transaction,不支持预取多个。 - sequence不调用
start_item/finish_item,直接seq_item_port.put
→ put是push模型(sequence主动推item),不是UVM默认pull模型,很少用。 - 端口方向搞反:
drv.seq_item_export.connect(seqr.seq_item_port)直接报错port(请求方driver)连 export(提供方sequencer)
六、Pull模型 vs Push模型(区分)
- Pull模型(默认,sequence-seqr-driver):Driver主动拉
Driver是主控,什么时候取激励由driver决定,适合DUT有backpressure(反压)场景,DUT忙的时候driver可以慢一点取item。 - Push模型:sequence主动put推送给driver
sequence主控,强制把item塞给driver,driver被动接收,很少在激励通路使用。
七、面试常问总结
- sequence和driver不直接相连,中间必须经过sequencer
- 通信端口是
uvm_seq_item_pull_port,标准是pull模型 - 标准两步握手:
get_next_item()拿item → 驱动DUT →item_done()确认完成 - sequencer内置FIFO缓存transaction,可以支持多个sequence并发(virtual sequence)