如何实现复杂的分层序列(hierarchical sequence)?

解读

国内SoC验证团队普遍采用UVM,面试时问“分层序列”并不是想听背诵class语法,而是考察三件事:

  1. 能否把真实业务场景拆成“高-中-低”三层激励,并说清楚每层到底干什么;
  2. 能否用UVM factory、config_db、sequence library机制把三层拼起来,同时保证可复用、可随机、可回归;
  3. 能否在性能与可维护性之间做权衡——比如顶层sequence要不要直接启动底层driver sequence?要不要用virtual sequence做跨时钟域同步?这些决定直接影响 nightly regression 的周转时间。

因此,回答时要先给“分层”一个清晰的业务定义,再给出可落地的代码框架,最后交代调试和性能优化经验。面试官最爱追问:“如果今天加一条新feature,你改哪一层?回归要跑多久?”答到这一步,才证明你真正做过复杂系统。

知识点

  1. UVM sequence 分类:flat sequence、hierarchical sequence、virtual sequence;
  2. uvm_do/uvm_create/uvm_send 宏与 uvm_sequence_utils 注册机制;
  3. Factory override 与 config_db 传递,实现“同一场景不同配置”;
  4. Sequence 的 body() 与 pre_body()/post_body() 回调,用于插入同步点;
  5. Grab/ungrab 和 lock/unlock 机制,解决多主口仲裁;
  6. Phase ready/phase ended 事件,跨时钟域sequence握手;
  7. Sequence 性能:random stability、objection 机制、仿真器对 allocate/clone 的优化;
  8. 国内主流回归平台(自研flow 或 Jenkins + LSF)对 long sequence 的 timeout 策略。

答案

下面给出一套在国内28 nm AI 芯片ISP子系统验证中量产过的“三阶”分层序列实现思路,可直接迁移到PCIe、DDR、NoC等复杂接口。

  1. 业务分层
    1.1 场景层(scenario sequence)
    ‑ 描述“一次完整的ISP算法加速”:DMA搬入→中断→CPU下发寄存器→ISP运算→DMA搬出。
    ‑ 只关心数据长度、帧格式、中断间隔,不关心AXI burst怎么拆。
    1.2 事务层(transaction sequence)
    ‑ 把场景拆成可随机的事务:AXI4-IN write、AXI4-OUT read、寄存器配置、中断等待。
    ‑ 通过config_db拿到基地址、max burst length、outstanding depth,随机产生合法事务。
    1.3 微指令层(micro sequence)
    ‑ 对应driver能识别的最小粒度:一次AXI burst、一次寄存器写、一次时钟沿等待。
    ‑ 与DUT时序强相关,可插入wait_cycle、back-to-back、slave ready拉低等corner。

  2. 代码骨架

// 微指令层
class axi_single_burst_seq extends uvm_sequence #(axi_item);
  rand bit [9:0] burst_len;
  constraint c_len { burst_len inside {[1:256]}; }
  `uvm_object_utils(axi_single_burst_seq)
  task body();
    req = axi_item::type_id::create("req");
    start_item(req);
    assert(req.randomize with {len == local::burst_len;});
    finish_item(req);
  endtask
endclass

// 事务层
class dma_in_transaction_seq extends uvm_sequence;
  rand int unsigned num_burst;
  constraint c_num { num_burst inside {[1:32]}; }
  `uvm_object_utils(dma_in_transaction_seq)
  task body();
    axi_single_burst_seq burst_seq;
    repeat(num_burst) begin
      `uvm_do(burst_seq)  // 宏会自动randomize
    end
  endtask
endclass

// 场景层(virtual sequence,不生产item,只协调子序列)
class isp_algorithm_scenario_vseq extends uvm_sequence;
  `uvm_declare_p_sequencer(uvm_virtual_sequencer)
  dma_in_transaction_seq  dma_in_seq;
  dma_out_transaction_seq dma_out_seq;
  reg_config_seq          cfg_seq;
  event                   isp_done;
  task body();
    // 1. 配置寄存器
    `uvm_do(cfg_seq)
    // 2. 启动DMA搬入
    fork
      `uvm_do(dma_in_seq)
    join_none
    // 3. 等待中断,再启动搬出
    @(p_sequencer.isp_interrupt);
    `uvm_do(dma_out_seq)
    // 4. 检查scoreboard
    #1us;
  endtask
endclass
  1. 工厂替换与随机控制
    在test层通过factory override把dma_in_transaction_seq换成dma_in_stress_seq,无需改动scenario层即可实现压力模式;
    uvm_config_db#(int)::set(this, "*", "num_burst", 64)在顶层随机控制事务数量,保证回归多样性。

  2. 跨时钟域握手
    场景层使用uvm_eventuvm_barrier,把ISP中断同步到AXI时钟域,避免sequence在fast clock域空转造成仿真性能损失。

  3. 性能与调试
    ‑ 微指令层关闭objection,防止每发一个burst都加/减objection,仿真速度提升20%;
    ‑ 在scenario层统一raise/drop objection,保证仿真器能正确统计run_phase结束点;
    ‑ 每层sequence在pre_body()打印uvm_info("SEQ_TRACE", $sformatf("Enter %s", get_full_name()), UVM_MEDIUM),配合Verdi UVM-ML调试,可直接在波形里看到sequence树状调用。

拓展思考

  1. 如果DUT支持多通道并行ISP,场景层需要再抽象一个“通道调度序列”,用fork...join_any实现通道间随机抢占,如何确保scoreboard不丢序?
  2. 当验证平台迁移到硬件仿真(Palladium),sequence仍跑在软件仿真器,item通过DPI-C送到硬件侧,分层序列的随机种子如何保持deterministic?
  3. 国内项目普遍要求“一晚跑完所有子系统用例”,若scenario层sequence过长导致超时,你会把哪一层拆成sub-sequence并加checkpoint,从而支持断点续跑?
  4. 形式验证(Formal)兴起后,部分corner改用SVA+Formal证明,分层序列里哪些微指令序列可以被Formal替代?如何与动态仿真保持互补?