ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

UVM sequence-driver 通信模型(详细讲解)

UVM sequence-driver 通信模型(详细讲解)

UVM sequence-driver 通信模型(详细讲解)

核心一句话:sequence 负责产生激励事务(item),driver 负责把事务变成硬件信号打给DUT;两者通过uvm_seq_item_pull_port/export握手通信,属于pull模型(driver主动拉取item),这是UVM默认标准模型。

一、整体角色分工

  1. sequence(uvm_sequence):激励发生器,写在body()里,创建seq_item,把item交给sequencer。不直接访问DUT信号
  2. sequencer(uvm_sequencer):中间“快递中转站”。有FIFO缓存,接收sequence发来的item,等待driver来取。
    • 端口:uvm_seq_item_pull_imp #(TR, this_type) seq_item_export;
  3. driver(uvm_driver):从sequencer拿transaction,解析item,驱动接口信号到DUT。
    • 端口:uvm_seq_item_pull_port #(TR) seq_item_port;
  4. seq_item(uvm_sequence_item):事务数据包,存放激励数据、控制位(addr/data/wr_en等),纯数据载体

UVM树简易结构(Mermaid)

seq_item_port ↔ seq_item_export

uvm_test_top: test_case

my_sequencer

my_driver

my_sequence

virtual interface -> DUT

连接关系:driver.seq_item_port.connect(sequencer.seq_item_export);
sequence 挂载到 sequencer:seq.start(seqr);

二、通信端口类型:pull端口(重点!)

uvm_seq_item_pull_port包含两组API,对应两个握手动作:

  1. get_next_item(req):driver向sequencer申请拿新item(阻塞)
  2. 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 endclass

2. 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 endclass

3. 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 endclass

4. 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 endclass

5. 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. 死锁场景1:调用get_next_item,忘记写item_done()
    → sequencer永远等待ack,sequence卡在finish_item,仿真卡死。
  2. 死锁场景2:多次连续调用get_next_item,中间没有item_done
    → 一次只能持有1个transaction,不支持预取多个。
  3. sequence不调用start_item/finish_item,直接seq_item_port.put
    → put是push模型(sequence主动推item),不是UVM默认pull模型,很少用。
  4. 端口方向搞反: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被动接收,很少在激励通路使用。

七、面试常问总结

  1. sequence和driver不直接相连,中间必须经过sequencer
  2. 通信端口是uvm_seq_item_pull_port,标准是pull模型
  3. 标准两步握手:get_next_item()拿item → 驱动DUT →item_done()确认完成
  4. sequencer内置FIFO缓存transaction,可以支持多个sequence并发(virtual sequence)
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进