UVM

UVM比较机制

学习uvm的比较机制

Zee
2025-08-20
12分钟
3 个标签
UVMSystemverilog数字芯片验证

UVM 比较机制深度解析:从一个 do_compare 的问题谈起

我在最近的芯片验证项目开发中,遇到了一个关于 UVM 比较机制的问题。我希望实现一个 uvm_sequence_item 的子类,在特 定条件下才对其内部的某个域(field)进行比较。然而,我最初的实现方式并没有达到预期的效果,这促使我对 UVM 的内建比较机制进行了深入的源码学习和探究。

  1. 我遇到的问题

我的需求是创建一个名为 my_transaction 的 transaction 类,它包含多个需要随机化的域。在验证过程中,我需要将两个 my_transaction 对象进行比较,但我希望 dmac 这个域的比较是受一个外部 flag 控制的。

我最初的代码是这样写的:

1 class my_transaction extends uvm_sequence_item;
2
3    rand bit[47:0] dmac;
4    rand bit[47:0] smac;
5    // ... 其他域
6
7    // 字段自动化注册
8`uvm_object_utils_begin(my_transaction)     9       `uvm_field_int(dmac, UVM_ALL_ON) // 问题所在

10 uvm_field_int(smac, UVM_ALL_ON) 11 // ... 其他域的注册 12 uvm_object_utils_end 13 14 function new(string name = "my_transaction"); 15 super.new(name); 16 endfunction 17 18 // 重写 do_compare 方法 19 function bit do_compare(uvm_object rhs, uvm_comparer comparer); 20 my_transaction rhs_; 21 bit result; 22 23 // 调用父类的比较 24 result = super.do_compare(rhs, comparer); 25 26 if (!$cast(rhs_, rhs)) 27 return 0; 28 29 // 期望的逻辑:只有当 flag 为 1 时才比较 dmac 30 if (flag == 1) begin 31 result &= comparer.compare_field("dmac", this.dmac, rhs_.dmac, $bits(dmac)); 32 end 33 34 // 对其他域的常规比较... 35 result &= comparer.compare_field("smac", this.smac, rhs_.smac, $bits(smac)); 36 37 return result; 38 endfunction 39 40 endclass

我的设想是,通过重写 do_compare 方法,我就能完全接管比较逻辑。当 flag 为 0 时,dmac 的比较代码块就不会被执行,从而跳过对 dmac 的比较。

然而,在实际仿真中,我发现无论 flag 的值是什么,当 dmac 域不匹配时,UVM 总会报出比较失败的错误。这让我非常困惑,为什么我重写的 do_compare 方法没有完全生效?

  1. 解决方案

经过一番调试和查阅资料,我找到了解决方案。我需要对 dmac 域的自动化注册宏进行修改,显式地告诉 UVM 的自动化机制在比较时忽略这个域。

修改后的代码如下:

1 class my_transaction extends uvm_sequence_item;
2
3    // ... 域定义不变 ...
4
5    // 字段自动化注册
6`uvm_object_utils_begin(my_transaction)     7       // **关键修改:添加 UVM_NOCOMPARE 标志位**     8       `uvm_field_int(dmac, UVM_ALL_ON | UVM_NOCOMPARE)
9       `uvm_field_int(smac, UVM_ALL_ON)    10       // ... 其他域的注册    11    `uvm_object_utils_end

12 13 // ... new 和 do_compare 方法保持不变 ... 14 function bit do_compare(uvm_object rhs, uvm_comparer comparer); 15 my_transaction rhs_; 16 bit result; 17 18 result = super.do_compare(rhs, comparer); 19 20 if (!$cast(rhs_, rhs)) 21 return 0; 22 23 // 现在这段逻辑可以完全控制 dmac 的比较 24 if (flag == 1) begin 25 result &= comparer.compare_field("dmac", this.dmac, rhs_.dmac, $bits(dmac)); 26 end 27 28 result &= comparer.compare_field("smac", this.smac, rhs_.smac, $bits(smac)); 29 30 return result; 31 endfunction 32 33 endclass

通过在 uvm_field_int 宏中为 dmac 添加 UVM_NOCOMPARE 标志,我成功地实现了条件比较。现在,只有当 flag 为 1 时,do_compare 中的自定义逻辑才会执行 dmac 的比较,UVM 的自动化机制不再干预。

  1. 原理探究:为什么必须使用 UVM_NOCOMPARE?

要理解这个问题的根源,我们需要深入 UVM 对象比较的内部工作流程。UVM 的对象比较 (uvm_object::compare) 是一个两阶段的过程:

  1. 自动化字段比较:由 uvm_field_* 宏自动生成的代码执行。
  2. 用户自定义比较:由用户重写的 do_compare 虚方法执行。

关键在于,自动化字段比较发生在用户自定义比较之前。

当我调用 my_transaction_a.compare(my_transaction_b) 时,UVM 框架内部的 uvm_object::compare 方法被触发。我们来看一下 uvm-1.2/base/uvm_object.svh 中这个函数的核心逻辑(简化后):

1 // In class uvm_object
2 function bit compare (uvm_object rhs, uvm_comparer comparer=null);
3   // ... 准备工作,如创建 comparer ...
4
5   // 1. 执行自动化字段比较
6   __m_uvm_field_automation(rhs, UVM_COMPARE, "");
7
8   // 2. 执行用户自定义的比较
9   dc = do_compare(rhs, comparer);

10 11 // ... 结果汇总和报告 ... 12 return (comparer.result == 0 && dc == 1); 13 endfunction

从上面的代码可以清晰地看到,__m_uvm_field_automation 函数的调用在 do_compare 之前。而 _m_uvm_field_automation 正是所有 uvm_field* 宏展开其代码的地方。

我们再来看 uvm-1.2/macros/uvm_object_defines.svh 中 uvm_field_int 宏的定义(简化后):

1`define uvm_field_int(ARG,FLAG) \     2   begin \     3     case (what__) \     4       // ... 其他操作如 COPY, PACK ...     5       UVM_COMPARE: \     6         begin \     7           if(!((FLAG)&UVM_NOCOMPARE)) begin \     8             if(ARG !== local_data__.ARG) begin \     9                void'(__m_uvm_status_container.comparer.compare_field(`"ARG`", ARG,
  local_data__.ARG, $bits(ARG))); 

10 // ... 错误报告逻辑 ... 11 end 12 end 13 end 14 // ... 其他操作 ... 15 endcase 16 end

这里的 what__ 参数在 compare 方法中被设为 UVM_COMPARE。宏展开后的代码会检查 FLAG 中是否包含 UVM_NOCOMPARE 位。

  • 在我最初的实现中,我使用了 UVM_ALL_ON,它不包含 UVM_NOCOMPARE。因此,if(!((FLAG)&UVM_NOCOMPARE)) 条件为真,自动化代码会直接比较 dmac 域。即使 dmac 不匹配,比较流程也会继续执行到我的 do_compare 方法,但此时 comparer.result 已经不为 0 了,比较结果已经注定是失败。
  • 在解决方案中,我添加了 UVM_NOCOMPARE 标志。这使得 if(!((FLAG)&UVM_NOCOMPARE)) 条件为假,自动化代码直接跳过了对 dmac 的比较。随后,执行流程进入我的 do_compare 方法,此时我写的条件判断逻辑 (if (flag == 1)) 成为了唯一决定 dmac 是否被比较的地方,从而实现了我的初衷。
  1. UVM 比较机制完整流程

基于以上的分析,我们可以总结出 UVM 完整的比较流程。

  1. 入口: 用户调用 objectA.compare(objectB, my_comparer)。
  2. 比较器准备:
    • 如果用户没有提供 my_comparer,compare 方法会使用全局默认的 uvm_default_comparer。
    • 比较器内部的状态(如 result 和 miscompares 字符串)被重置。
  3. 周期检查: 比较器使用内部的 compare_map 记录已经比较过的对象句柄,防止在对象图中出现循环引用导致无限递归。
  4. 类型检查: 如果 comparer.check_type 为 1(默认值),则会比较 objectA 和 objectB 的类型名称 (get_type_name()) 是否一致。不一致则记录一个失配。
  5. 自动化字段比较:
    • 调用内部的 __m_uvm_field_automation 方法,并将操作类型设置为 UVM_COMPARE。
    • 此方法会执行所有通过 uvm_field_* 宏注册的字段的比较逻辑。
    • 对于每一个字段,它会检查 FLAG。如果 FLAG 中不包含 UVM_NOCOMPARE,则执行该字段的比较。
    • 如果发现失配,comparer.result 会递增,并记录详细的失配信息。
  6. 用户自定义比较:
    • 调用用户重写的 do_compare 虚方法。
    • 这是用户插入自定义、复杂或条件性比较逻辑的地方。
    • 为了完全控制某个字段的比较,必须首先在第 5 步中通过 UVM_NOCOMPARE 标志将其从自动化流程中排除。
  7. 结果汇总与返回:
    • compare 方法检查 comparer.result 的值。如果为 0,并且 do_compare 返回 1,则整个比较成功,返回 1。
    • 否则,比较失败,返回 0。
    • 在最顶层的 compare 调用结束时,可能会打印出所有失配的汇总信息。

流程图

下面是 UVM 比较机制的简化流程图:

1 graph TD
2     A[uvm_object.compare(rhs)] --> B{comparer 是否为 null?};
3     B -- 是 --> C[使用 uvm_default_comparer];
4     B -- 否 --> D[使用用户传入的 comparer];
5     C --> E;
6     D --> E;
7     E[重置 comparer 状态] --> F{类型检查};
8     F --> G[执行 __m_uvm_field_automation];
9     G --> H{遍历所有`uvm_field_*` 宏注册的字段};

10 H -- 对每个字段 --> I{FLAG 中是否包含 UVM_NOCOMPARE?}; 11 I -- 否 --> J[比较该字段的值]; 12 J --> K{是否匹配?}; 13 K -- 否 --> L[comparer.result++, 记录失配]; 14 K -- 是 --> M; 15 I -- 是 --> M[跳过自动化比较]; 16 L --> M; 17 M --> H; 18 H -- 遍历结束 --> N[执行 do_compare(rhs, comparer)]; 19 N --> O{comparer.result == 0 && do_compare() == 1?}; 20 O -- 是 --> P[返回 1 (匹配)]; 21 O -- 否 --> Q[返回 0 (失配)];

  1. 总结

UVM 的 compare 机制是一个强大但有其特定规则的系统。关键在于理解其“自动化”和“用户自定义”相结合的两阶段执行流程。

  • uvm_field_* 宏提供了极大的便利,可以自动处理大多数数据成员的 copy、compare、print 等操作。

  • 当需要对某个字段进行复杂的、有条件的或完全自定义的比较时,必须采取两步措施:

    1. 在 uvm_field_* 宏中,使用 UVM_NOCOMPARE 标志来“关闭”对该字段的自动化比较。
    2. 在 do_compare 方法中,编写完整的自定义比较逻辑。

    只有这样,才能确保我们的自定义逻辑能够完全控制该字段的比较行为,避免被自动化机制“抢先一步”。这次的探索不仅解决 了我遇到的问题,也让我对 UVM 的核心数据操作方法有了更深刻的理解。