UVM比较机制
学习uvm的比较机制
UVM 比较机制深度解析:从一个 do_compare 的问题谈起
我在最近的芯片验证项目开发中,遇到了一个关于 UVM 比较机制的问题。我希望实现一个 uvm_sequence_item 的子类,在特 定条件下才对其内部的某个域(field)进行比较。然而,我最初的实现方式并没有达到预期的效果,这促使我对 UVM 的内建比较机制进行了深入的源码学习和探究。
- 我遇到的问题
我的需求是创建一个名为 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 方法没有完全生效?
- 解决方案
经过一番调试和查阅资料,我找到了解决方案。我需要对 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 的自动化机制不再干预。
- 原理探究:为什么必须使用 UVM_NOCOMPARE?
要理解这个问题的根源,我们需要深入 UVM 对象比较的内部工作流程。UVM 的对象比较 (uvm_object::compare) 是一个两阶段的过程:
- 自动化字段比较:由 uvm_field_* 宏自动生成的代码执行。
- 用户自定义比较:由用户重写的 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 是否被比较的地方,从而实现了我的初衷。
- UVM 比较机制完整流程
基于以上的分析,我们可以总结出 UVM 完整的比较流程。
- 入口: 用户调用 objectA.compare(objectB, my_comparer)。
- 比较器准备:
- 如果用户没有提供 my_comparer,compare 方法会使用全局默认的 uvm_default_comparer。
- 比较器内部的状态(如 result 和 miscompares 字符串)被重置。
- 周期检查: 比较器使用内部的 compare_map 记录已经比较过的对象句柄,防止在对象图中出现循环引用导致无限递归。
- 类型检查: 如果 comparer.check_type 为 1(默认值),则会比较 objectA 和 objectB 的类型名称 (get_type_name()) 是否一致。不一致则记录一个失配。
- 自动化字段比较:
- 调用内部的 __m_uvm_field_automation 方法,并将操作类型设置为 UVM_COMPARE。
- 此方法会执行所有通过 uvm_field_* 宏注册的字段的比较逻辑。
- 对于每一个字段,它会检查 FLAG。如果 FLAG 中不包含 UVM_NOCOMPARE,则执行该字段的比较。
- 如果发现失配,comparer.result 会递增,并记录详细的失配信息。
- 用户自定义比较:
- 调用用户重写的 do_compare 虚方法。
- 这是用户插入自定义、复杂或条件性比较逻辑的地方。
- 为了完全控制某个字段的比较,必须首先在第 5 步中通过 UVM_NOCOMPARE 标志将其从自动化流程中排除。
- 结果汇总与返回:
- 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 (失配)];
- 总结
UVM 的 compare 机制是一个强大但有其特定规则的系统。关键在于理解其“自动化”和“用户自定义”相结合的两阶段执行流程。
-
uvm_field_* 宏提供了极大的便利,可以自动处理大多数数据成员的 copy、compare、print 等操作。
-
当需要对某个字段进行复杂的、有条件的或完全自定义的比较时,必须采取两步措施:
- 在 uvm_field_* 宏中,使用 UVM_NOCOMPARE 标志来“关闭”对该字段的自动化比较。
- 在 do_compare 方法中,编写完整的自定义比较逻辑。
只有这样,才能确保我们的自定义逻辑能够完全控制该字段的比较行为,避免被自动化机制“抢先一步”。这次的探索不仅解决 了我遇到的问题,也让我对 UVM 的核心数据操作方法有了更深刻的理解。