4. Transaction与Sequence:掌握uvm_sequence_item的定义,以及sequence如何生成并发送transaction

好,咱们今天聊聊UVM验证平台里最核心的两个概念——Transaction和Sequence。说白了,整个验证平台就是在跟数据打交道,而Transaction就是数据的载体,Sequence就是数据的生产者。这两个搞明白了,UVM就算入门一半了。

4.1 什么是Transaction?为什么需要它?

Transaction,翻译过来叫“事务”或“数据包”。在UVM的世界里,它代表了一次完整的数据交换。比如一个AXI写操作、一个APB读操作、一个以太网帧,都可以抽象成一个Transaction。

我个人习惯把Transaction想象成一个“快递包裹”。包裹里装着地址、数据、控制信号等所有信息。driver拿到这个包裹,就知道该怎么驱动接口信号;monitor抓到接口上的信号,也能打包成一个Transaction送给reference model。

核心要点:Transaction是验证平台中数据流动的最小单元。所有组件之间的通信,本质上都是在传递Transaction。

4.2 定义自己的uvm_sequence_item

UVM里,所有的Transaction都必须从uvm_sequence_item派生。为什么?因为uvm_sequence_item提供了很多好用的机制,比如自动化的复制、比较、打印、记录等等。

来看一个简单的例子。假设我们要验证一个SPI接口的IP,先定义一个SPI的Transaction:

class spi_transaction extends uvm_sequence_item;

  // 声明字段
  rand bit [7:0]  data;
  rand bit [7:0]  addr;
  rand bit        write_en;
  rand int        delay_cycles;

  // 约束
  constraint c_delay {
    delay_cycles inside {[0:15]};
  }

  // 工厂注册
  `uvm_object_utils_begin(spi_transaction)
    `uvm_field_int(data, UVM_ALL_ON)
    `uvm_field_int(addr, UVM_ALL_ON)
    `uvm_field_int(write_en, UVM_ALL_ON)
    `uvm_field_int(delay_cycles, UVM_ALL_ON)
  `uvm_object_utils_end

  // 构造函数
  function new(string name = "spi_transaction");
    super.new(name);
  endfunction

endclass

嗯,这里要注意几个关键点:

  • rand关键字:让字段可以随机化。这是验证的核心——用随机来覆盖各种场景。
  • 约束块:控制随机值的范围。我在项目中遇到过,如果不加约束,delay_cycles可能随机出很大的值,导致仿真跑半天都跑不完一个transaction。
  • 字段宏`uvm_field_*宏自动实现了copy、compare、print、record等方法。省去了手写这些函数的麻烦。

我的建议:刚开始学UVM时,很多人觉得字段宏可有可无。但等你需要调试、需要对比两个transaction是否相等时,就知道它的好了。我建议所有自定义的transaction都加上字段宏。

4.3 Sequence是什么?它怎么生成Transaction?

Sequence,直译是“序列”。它的任务就是生成一串Transaction,然后发给driver去执行。

你想想看,如果每次都要手动创建Transaction、设置字段、再发送,那得多累?Sequence就是帮我们自动化这个过程的。它可以从一个简单的循环开始,也可以包含复杂的随机策略和时序控制。

看一个最简单的sequence:

class spi_simple_seq extends uvm_sequence #(spi_transaction);

  `uvm_object_utils(spi_simple_seq)

  function new(string name = "spi_simple_seq");
    super.new(name);
  endfunction

  virtual task body();
    spi_transaction tr;

    repeat(10) begin
      tr = spi_transaction::type_id::create("tr");
      start_item(tr);
      if (!tr.randomize()) begin
        `uvm_error("RAND_FAIL", "Randomization failed!")
      end
      finish_item(tr);
    end
  endtask

endclass

这段代码里,最关键的就是body()任务里的三步走:

  1. 创建:用factory创建transaction对象。
  2. start_item:告诉sequencer“我要开始发送一个transaction了”。sequencer会做仲裁,决定哪个sequence先发。
  3. randomize + finish_item:随机化transaction的字段,然后发送给driver。

我曾经踩过的坑:一开始我总忘记在start_itemfinish_item之间做randomize。结果发出去的transaction全是默认值,仿真跑得飞快但啥也没测到。记住:randomize一定要放在start_item之后、finish_item之前。

4.4 更复杂的Sequence:带约束和时序控制

实际项目中,sequence不会这么简单。我们经常需要生成特定场景的transaction。比如,要测试SPI写操作,可以这样:

class spi_write_seq extends uvm_sequence #(spi_transaction);

  `uvm_object_utils(spi_write_seq)

  function new(string name = "spi_write_seq");
    super.new(name);
  endfunction

  virtual task body();
    spi_transaction tr;

    tr = spi_transaction::type_id::create("tr");
    start_item(tr);

    // 约束写操作
    tr.write_en.constraint_mode(1);
    tr.write_en = 1;
    tr.addr.rand_mode(1);
    tr.data.rand_mode(1);

    if (!tr.randomize()) begin
      `uvm_error("RAND_FAIL", "Randomization failed!")
    end

    finish_item(tr);
  endtask

endclass

这里我用了constraint_moderand_mode来控制哪些字段随机、哪些固定。说白了,就是让sequence可以灵活地组合出各种测试场景。

4.5 Sequence的层次化:嵌套与复用

UVM的sequence还有一个很强大的特性——可以嵌套。一个sequence可以启动另一个sequence。这在实际项目中非常有用。

举个例子,我们可以先定义一个“写一个寄存器”的sequence,再定义一个“配置所有寄存器”的sequence,后者循环调用前者:

class spi_cfg_all_regs_seq extends uvm_sequence #(spi_transaction);

  `uvm_object_utils(spi_cfg_all_regs_seq)

  virtual task body();
    spi_write_seq write_seq;

    // 配置寄存器0x00
    `uvm_do_with(write_seq, {addr == 8'h00; data == 8'hA5;})
    // 配置寄存器0x01
    `uvm_do_with(write_seq, {addr == 8'h01; data == 8'h5A;})
    // 配置寄存器0x02
    `uvm_do_with(write_seq, {addr == 8'h02; data == 8'hFF;})
  endtask

endclass

你看,用`uvm_do_with宏,一行代码就完成了创建、随机化、发送的全过程。而且可以内联约束,非常方便。

我的习惯:在项目中,我会把常用的操作封装成小的sequence,比如“写寄存器”、“读寄存器”、“等待中断”。然后上层sequence像搭积木一样组合它们。这样既提高了复用性,也让测试用例的代码更清晰。

4.6 总结一下关键点

概念 作用 关键方法/宏
uvm_sequence_item 定义数据包的结构和约束 字段宏、constraint、rand
uvm_sequence 生成并发送transaction body()、start_item()、finish_item()
嵌套sequence 组合复用已有sequence `uvm_do、`uvm_do_with

最后说一句,Transaction和Sequence是UVM验证平台的基石。你花时间把它们搞透,后面的driver、monitor、scoreboard学起来就会轻松很多。下一章,咱们就聊聊driver怎么接收sequence发来的transaction,并驱动到接口上。