3. sequence的层次化组织:嵌套sequence、子sequence的调用与同步,sequence的层次化启动方式

好,咱们今天聊点硬核的。sequence的层次化组织,说白了就是怎么把一堆小的sequence拼成一个大的,再让它们协同工作。我在项目里见过不少新人,写sequence就是一个大平层,所有激励逻辑全塞在一个body里,结果代码又臭又长,复用性基本为零。嗯,今天咱们就来治治这个毛病。

3.1 为什么需要层次化sequence?

你想想看,一个复杂的验证场景,比如DMA传输、多通道数据交织,如果只用一个sequence去描述,那body函数得写几百行。调试的时候,你根本分不清是哪个环节出了问题。

我个人习惯是把激励拆成多个小sequence,每个小sequence只干一件事。比如:

  • 配置寄存器用一个sequence
  • 发送数据包用一个sequence
  • 等待中断再用一个sequence

然后把这些小sequence组合起来,形成一个大的测试场景。这就是层次化的核心思想——分而治之

核心原则:每个sequence只负责一个原子操作,组合逻辑交给上层sequence去编排。

3.2 嵌套sequence:子sequence的调用

UVM里调用子sequence,最常用的就是`uvm_do系列宏,或者手动调用start()方法。咱们先看一个简单的例子。

// 子sequence:发送一个数据包
class seq_send_packet extends uvm_sequence #(packet);
  `uvm_object_utils(seq_send_packet)

  virtual task body();
    req = packet::type_id::create("req");
    start_item(req);
    // 随机化数据包内容
    assert(req.randomize());
    finish_item(req);
  endtask
endclass

// 父sequence:组合多个子sequence
class seq_complex_scenario extends uvm_sequence #(packet);
  `uvm_object_utils(seq_complex_scenario)

  virtual task body();
    seq_send_packet seq1;
    seq_send_packet seq2;

    // 方式一:使用`uvm_do宏
    `uvm_do(seq1)
    `uvm_do(seq2)

    // 方式二:手动创建并启动
    // seq1 = seq_send_packet::type_id::create("seq1");
    // seq1.start(m_sequencer, this);
  endtask
endclass

这里要注意,`uvm_do宏会自动帮你创建、随机化、启动子sequence,并且把父sequence的sequencer传递给子sequence。我在项目中遇到过一个问题:如果父sequence和子sequence用的sequencer不一样,那`uvm_do就会报错。这时候就得手动指定sequencer了。

小技巧:如果你需要子sequence在另一个sequencer上运行,用seq.start(other_sequencer),别用`uvm_do

3.3 子sequence的同步:等待与握手

有时候,子sequence之间需要同步。比如,先发一个配置包,等DUT处理完,再发数据包。这时候就需要用到uvm_event或者uvm_barrier

我曾经在一个多通道项目中,需要两个子sequence交替发送数据。一个发通道0,一个发通道1,必须严格交替。我当时用了uvm_event来做握手。

class seq_ch0 extends uvm_sequence #(packet);
  uvm_event e_done_ch0;

  virtual task body();
    repeat(10) begin
      `uvm_do(req)
      e_done_ch0.trigger();  // 通知通道0完成
      e_done_ch1.wait_trigger(); // 等待通道1完成
    end
  endtask
endclass

class seq_ch1 extends uvm_sequence #(packet);
  uvm_event e_done_ch1;

  virtual task body();
    repeat(10) begin
      e_done_ch0.wait_trigger(); // 等待通道0完成
      `uvm_do(req)
      e_done_ch1.trigger();  // 通知通道1完成
    end
  endtask
endclass

嗯,这里要注意,uvm_event是全局的,你得确保两个子sequence拿到的是同一个event对象。我一般会在父sequence里创建event,然后通过set_config或者构造函数传进去。

避坑指南:我曾经犯过一个错误——在子sequence的body里用new()创建event,结果两个子sequence各用各的event,永远等不到对方。记住,同步对象必须共享

3.4 sequence的层次化启动方式

启动一个层次化的sequence,有几种常见方式。我按使用频率排个序:

启动方式 适用场景 个人评价
在test中直接启动顶层sequence 大多数情况,简单直接 最常用,推荐
在virtual sequence中启动多个子sequence 需要跨sequencer的场景 灵活,但要注意同步
在sequence的body中递归启动 树形结构的sequence 小心死循环

举个例子,在test中启动顶层sequence:

class test_complex extends uvm_test;
  `uvm_component_utils(test_complex)

  virtual function void build_phase(uvm_phase phase);
    // 创建sequencer等
  endfunction

  virtual task run_phase(uvm_phase phase);
    seq_complex_scenario seq;
    seq = seq_complex_scenario::type_id::create("seq");
    phase.raise_objection(this);
    seq.start(m_sequencer);  // 启动顶层sequence
    phase.drop_objection(this);
  endtask
endclass

你看,启动顶层sequence后,它内部会自动调用子sequence。你根本不用关心子sequence是怎么组织的——这就是层次化的好处。

3.5 实战经验:一个多通道DMA测试的例子

我记得有一次做DMA验证,有8个通道,每个通道要独立发送数据,但最后要一起完成。我设计了一个三层结构:

  • 底层:每个通道一个子sequence,只负责发数据
  • 中间层:一个virtual sequence,同时启动8个子sequence
  • 顶层:一个控制sequence,负责配置、启动、等待完成

中间层的virtual sequence大概长这样:

class vseq_dma_channels extends uvm_sequence;
  `uvm_object_utils(vseq_dma_channels)

  virtual task body();
    seq_dma_ch ch_seq[8];
    for(int i=0; i<8; i++) begin
      ch_seq[i] = seq_dma_ch::type_id::create($sformatf("ch_seq_%0d", i));
      // 每个子sequence在不同的sequencer上启动
      ch_seq[i].start(p_sequencer.ch_sequencer[i]);
    end
    // 等待所有子sequence完成
    // 可以用uvm_barrier或者简单的fork-join
    fork
      for(int i=0; i<8; i++) begin
        ch_seq[i].get_response(rsp);
      end
    join
  endtask
endclass

这里有个关键点:p_sequencer是virtual sequence特有的,它让你能访问到virtual sequencer下的子sequencer。如果你用m_sequencer,那就只能拿到一个sequencer,没法做多通道分发。

总结一下:层次化sequence的核心就三点——拆分、组合、同步。拆分要细,组合要稳,同步要准。做到这三点,再复杂的验证场景你也能驾驭。

好了,这一章就到这里。下一章咱们聊聊sequence的进阶用法——如何用sequence控制DUT的复位行为。到时候我会分享一个我踩过的坑,保证让你印象深刻。