4. 激励生成与场景建模

在UVM验证平台中,sequence(序列)是激励生成的核心机制。它把“产生什么事务、以什么顺序、带什么约束”从driver中彻底解耦出来,使激励可以像软件模块一样被复用、组合和随机化。理解sequence机制,本质上就是理解UVM如何把“测试意图”翻译成“总线事务流”。

一、sequence、sequencer、driver三者的职责边界

UVM的激励路径由三个角色构成:sequence负责产生transaction并施加约束;sequencer作为仲裁与转发中枢,管理多个sequence的启动、优先级和挂起;driver从sequencer取到transaction后,按协议时序驱动到DUT接口。三者通过TLM端口连接,典型连接方式是sequence在sequencer上启动,driver通过seq_item_port向sequencer请求item。

一个常见的误区是把约束写在driver里。正确做法是:driver只关心“怎么把transaction变成引脚波形”,所有“产生什么样的transaction”都放在sequence中。这样当需要改变激励分布时,只需替换或继承sequence,无需触碰driver。

UVM序列激励生成与场景协调结构 Test 启动virtual sequence Virtual Sequence 协调多接口场景 Seq_A (AXI) 定向激励 Seq_B (APB) 随机激励 Sequencer_A 仲裁/转发 Sequencer_B 仲裁/转发 Driver_A 协议时序驱动 Driver_B 协议时序驱动 DUT (多接口设计) 场景库 定向场景 随机场景 错误注入场景 性能压力场景

二、定向激励与随机激励的构建方式

定向激励用于验证明确的协议行为或边界条件。例如要验证AXI写通道的4KB边界拆分,可以写一个定向sequence,固定addr = 12'hFFC、len = 4,让burst跨越4KB边界。定向sequence通常继承自uvm_sequence #(trans_type),在body()任务中通过start_item()/finish_item()或宏`uvm_do_with发送事务。

class axi_4k_boundary_seq extends uvm_sequence #(axi_trans);
  `uvm_object_utils(axi_4k_boundary_seq)
  task body();
    `uvm_do_with(req, {
      addr == 12'hFFC;
      len  == 4'd4;
      burst == 2'b01; // INCR
    })
  endtask
endclass

随机激励则依赖SystemVerilog的rand与constraint。关键不是“随便随机”,而是通过约束控制随机空间。例如让地址按4字节对齐、len在1~16之间、写数据与读回数据保持一致性约束。常用做法是在transaction中定义rand字段和约束块,在sequence中通过uvm_do_with叠加场景级约束。

class axi_trans extends uvm_sequence_item;
  rand bit [31:0] addr;
  rand bit [7:0]  len;
  rand bit [2:0]  size;
  constraint c_addr { addr[1:0] == 2'b00; }
  constraint c_len  { len inside {[1:16]}; }
  constraint c_size { size == 3'b010; } // 4 bytes
endclass

随机激励的价值在于覆盖率驱动。建议把addr、len、burst、resp等字段加入covergroup,用coverpoint和cross观察随机是否真正覆盖了边界组合。若某个bin长期为0,说明约束过紧或随机种子不合适,需要调整约束或增加定向补充。

三、虚拟序列协调多接口场景

当DUT有多个接口(如AXI、APB、中断线)时,单个sequence只能驱动一个sequencer。此时需要virtual sequence作为“总指挥”:它本身不产生transaction,而是持有多个sequencer的句柄,按场景需要启动不同的子sequence。

典型写法是在virtual sequence中通过p_sequencer或uvm_config_db获取各接口sequencer句柄,然后用fork...join或fork...join_none控制并发。例如一个“配置APB寄存器后发起AXI突发传输”的场景:先启动APB配置sequence,等待其完成,再启动AXI数据sequence;若需要模拟中断与数据并发,则用fork同时启动两个sequence。

class cfg_then_burst_vseq extends uvm_sequence;
  `uvm_object_utils(cfg_then_burst_vseq)
  `uvm_declare_p_sequencer(virtual_sequencer)
  task body();
    apb_cfg_seq  cfg_seq;
    axi_burst_seq burst_seq;
    `uvm_do_on(cfg_seq, p_sequencer.apb_sqr)
    `uvm_do_on(burst_seq, p_sequencer.axi_sqr)
  endtask
endclass

虚拟序列的关键在于同步点。多接口场景中,各接口的时序推进速度不同,必须明确“谁等谁”。常用同步手段包括:用uvm_event在scoreboard或monitor中触发事件,virtual sequence等待该事件后再发起下一段激励;或用wait fork等待所有并发子序列完成。若同步点设置错误,容易出现“配置还没生效就发数据”的假失败。

四、场景库的组织与管理

随着验证推进,sequence数量会快速增长。建议按场景维度而非文件类型组织场景库,典型分层如下:

  • 基础事务层:单个transaction的约束与随机化,如axi_trans、apb_trans。
  • 接口序列层:面向单一接口的定向/随机sequence,如axi_4k_boundary_seq、axi_random_seq。
  • 虚拟场景层:跨接口协调的virtual sequence,如cfg_then_burst_vseq、irq_during_burst_vseq。
  • 测试用例层:在test中通过uvm_config_db选择启动哪个virtual sequence,并设置超时和退出条件。

管理上建议为每个sequence添加`uvm_object_utils并注册到factory,这样可以在不修改test代码的情况下,通过set_type_override替换sequence实现。例如调试阶段用短序列替换长随机序列,加速问题复现。同时,为每个场景维护一份“场景-覆盖率-状态”对照表,明确哪些场景已通过、哪些覆盖率bin未命中,避免重复劳动。

场景库不是越多越好。优先覆盖协议边界、寄存器配置组合、并发冲突和错误注入四类场景,再根据覆盖率缺口补充随机约束。每个sequence应有明确的验证目标,避免“为了随机而随机”。
虚拟序列中若使用fork...join_none启动子序列,必须确保在virtual sequence结束前用wait fork或事件同步回收,否则可能出现子序列仍在运行而test已进入phase跳转,导致仿真挂起或误报。

总结来说,UVM序列机制把激励生成拆成了“事务约束—接口序列—虚拟协调—场景管理”四层。定向激励保证关键路径可控,随机激励扩大覆盖空间,虚拟序列解决多接口时序协调,场景库则让这些激励资产可复用、可替换、可追踪。把这四层组织清楚,激励生成就不再是零散的uvm_do堆砌,而是一套可维护的验证资产体系。