UVM验证基础知识——基于Systemverilog
SystemVerilog 与 UVM 验证实践要点(基于本工程)
面向 I2C Slave 验证场景整理。重点覆盖:SystemVerilog 常用语法、UVM 类体系、工厂机制、phase 机制、TLM 连接与
config_db配置传递。
1. SystemVerilog 变量与数据结构
1.1 常用变量类型
| 类型 | 典型场景 | 说明 |
|---|---|---|
bit / logic |
接口信号、临时标志 | bit 为二值(0/1);logic 为四值(0/1/X/Z),接口与 RTL 连接建议优先 logic |
int / integer |
计数器、配置参数 | int:32 位有符号;integer:四值有符号(旧代码常见);验证中地址/长度常用 int unsigned |
byte / shortint / longint |
数据缓存 | 分别 8/16/64 位,按需选位宽可节省内存 |
real / shortreal |
模拟参数 | 实数建模使用,纯数字逻辑验证较少 |
string |
日志、路径名 | uvm_info 消息、uvm_config_db 路径、文件路径 |
enum |
状态机、操作类型 | 可读性高,适合 opcode/state(如 IDLE/START/DATA/STOP) |
| 队列 / 动态数组 / 关联数组 | 数据收集与模型存储 | 队列适合流式缓存; 动态数组适合可变长度 payload; 关联数组适合稀疏/键值映射模型 |
1.2 典型声明示例
1.2.1 枚举定义
typedef enum {I2C_WRITE, I2C_READ} i2c_op_e;
参数/语义说明:
typedef:类型定义关键字,为枚举类型创建别名。enum:定义命名常量集合,提升可读性与可维护性。I2C_WRITE, I2C_READ:枚举成员,默认从 0 递增。i2c_op_e:枚举类型名,_e 是常见命名后缀。
1.2.2 非随机动态数组
bit [7:0] rdata[];
参数/语义说明:
bit [7:0]:元素类型为 8 位二值数据。rdata[]:动态数组,长度运行时分配。- 未加
rand:不参与随机化,常用于保存 DUT 返回数据。
1.2.3 队列
bit ack_bits[$];
参数/语义说明:
bit:队列元素类型。[$]:队列大小可动态变化。- 典型用途:记录每个 byte 对应的 ACK/NACK 位。
常用方法:
q.push_back(value):在队列末尾添加元素q.push_front(value):在队列开头添加元素q.size():返回队列大小q.pop_back():移除并返回末尾元素q.pop_front():移除并返回开头元素q.insert(index, value):在队列在指定位置前插入元素q.delete(index):删除指定位置元素
对于队列,其索引为升序排列:
q[$] = {0,2,5,8}
q[0]=0 q[1]=2
1.2.4 关联数组
byte unsigned model_mem[byte unsigned];
参数/语义说明:
- 左侧
byte unsigned:值类型(存储的内容)。 model_mem:数组名。- 右侧
[byte unsigned]:索引类型(键类型)。 - 适合做寄存器/存储器参考模型(稀疏地址空间友好)。
常用方法:
array.size():返回元素个数array.rsort():降序排列array.reverse():反转顺序array.exists(key):检查键是否存在value = array[key]; 直接通过键获取对应键值array.delete([key]):删除元素array.first(ref key):获取第一个键foreach(array[key, value]):遍历所有键值
2. UVM 类体系与职责划分
2.1 类别总览
| 类别 | 典型基类 | 核心职责 | 工程实践建议 |
|---|---|---|---|
| Transaction(事务) | uvm_sequence_item |
封装一次传输的数据字段与约束 | 实现 copy/compare/convert2string,必要时实现 pack/unpack |
| Sequence(序列) | uvm_sequence |
组织和生成激励流 | 在 body() 中构造场景,可复用、可嵌套 |
| Driver(驱动) | uvm_driver #(T) |
事务级到引脚级转换 | 在 run_phase() 中执行握手、时序驱动 |
| Monitor(监视) | uvm_monitor |
引脚级到事务级采样重建 | 通过 analysis 端口广播给 scoreboard/coverage |
| Sequencer(序列器) | uvm_sequencer #(T) |
调度 sequence 与 driver 握手 | 管理事务发放与仲裁 |
| Agent(代理) | uvm_agent |
封装 sqr+drv+mon |
支持 active/passive,可配置复用 |
| Scoreboard(记分板) | uvm_scoreboard |
预期/实测比对 | 建议使用队列或关联数组做参考模型 |
| Env(环境) | uvm_env |
集成多个 agent 与检查组件 | 在 connect_phase 完成 TLM 连线 |
| Test(测试) | uvm_test |
顶层场景控制 | 构建环境、下发配置、启动 sequence |
| Config(配置对象) | uvm_object |
参数集中管理 | 通过 uvm_config_db 分发给下层组件 |
2.2 继承示例
2.2.1 类继承
class i2c_item extends uvm_sequence_item;
语句解释:
class:定义类。i2c_item:事务类名,表示一次 I2C 传输抽象。extends:继承关键字。uvm_sequence_item:UVM 事务基类,提供随机化、打印、比较等基础能力。
2.2.2 参数化继承
class i2c_sequencer extends uvm_sequencer #(i2c_item);
class i2c_driver extends uvm_driver #(i2c_item);
语句解释:
#(i2c_item):参数化类型,指定 sequencer/driver 处理的事务类型。uvm_sequencer #(i2c_item):调度并向 driver 发放i2c_item。uvm_driver #(i2c_item):从 sequencer 获取i2c_item后驱动到引脚时序。
3. new 与对象创建方式
| 用法 | 语法 | 说明 |
|---|---|---|
| 直接构造对象 | obj = new(); / obj = new("name"); |
不经过工厂,不能 type override |
| 动态数组创建 | arr = new[size]; |
元素按类型默认值初始化 |
| 动态数组复制 | arr2 = new[n](arr1); |
新数组可与旧数组长度不同 |
| 随机化 | assert(obj.randomize()); |
先 new 再随机化 |
| 工厂创建(推荐) | T::type_id::create("name", parent) |
支持 override 与统一管理 |
说明:在 UVM 环境中,组件与事务对象优先使用工厂创建。
4. UVM 工厂机制(Factory)
4.1 注册宏选择
| 宏 | 用于 | 示例 |
|---|---|---|
`uvm_object_utils(T) |
非参数化 uvm_object 派生类 |
item、cfg、sequence |
`uvm_component_utils(T) |
非参数化 uvm_component 派生类 |
driver、monitor、agent、env、test |
`uvm_object_param_utils(T) |
参数化 object | packet #(DWIDTH) |
`uvm_component_param_utils(T) |
参数化 component | agent #(CFG_T) |
作用:
- 类型注册:将类名与一个可创建的“类型句柄”在工厂中关联起来,将类告知工厂。
- 支持覆盖:使工厂能够根据覆盖设置,将请求创建的A类型对象动态替换为B类型对象。
自动字段宏(*_utils_begin/end)可减少样板代码,但有性能成本,建议按需使用。
4.2 构造函数
对象构造函数
class i2c_item extends uvm_sequence_item;
`uvm_object_utils(i2c_item)
function new(string name = "i2c_item");
super.new(name);
endfunction
endclass
关键说明:
- 对象类构造函数通常为
new(string name = "...")。 super.new(name)用于初始化父类对象信息。- 对象类无
parent,不参与 UVM 组件层次树。
组件构造函数
class i2c_sequencer extends uvm_sequencer #(i2c_item);
`uvm_component_utils(i2c_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
endclass
关键说明:
- 组件类构造函数必须带
parent:new(string name, uvm_component parent)。 super.new(name, parent)建立组件层次关系。- 组件存在于拓扑中,受 phase 机制统一调度。
4.3 工厂创建示例
cfg = i2c_cfg::type_id::create("cfg"); // object:不需要 parent
env = i2c_env::type_id::create("env", this); // component:需要 parent
# 循环创建多个工厂多个事务
for (rr = 0; rr < eff_rounds; rr++) begin
wr = i2c_item::type_id::create($sformatf("wr_%0d", rr));
cfg传递给构造函数的形参name,而工厂使用标准的new构造函数来实例化最终确定的类
假设当前工厂注册宏为:
class i2c_base_test extends uvm_test;
`uvm_component_utils(i2c_base_test)
i2c_env env;
i2c_cfg cfg;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
则this指的就是i2c_base_test,即将当前对象(i2c_base_test)的句柄传给工厂,明确告知:“请将新创建的 env对象,挂载到我的名下。”
5. UVM Phase 机制(重点)
5.1 build_phase(uvm_phase phase)——构建阶段
- 作用:创建组件/对象、读取配置(零时间)。
- 类型:
function。 - 典型内容:
type_id::create(...)uvm_config_db::get(...)
5.2 connect_phase(uvm_phase phase)——连接阶段
- 作用:连接 TLM 端口(零时间)。
- 类型:
function。 - 典型内容:
drv.seq_item_port.connect(sqr.seq_item_export);mon.ap.connect(scb.imp);
5.3 run_phase(uvm_phase phase)——运行阶段
-
作用:执行有时间行为(驱动、采样、等待)。
-
类型:
task。 -
典型内容:
forever循环@(...)/#...时序行为objection 控制(通常在 test 里)
-
objection 控制
phase.raise_objection(this);- 通知UVM框架测试开始工作,防止run_phase提前结束
phase.drop_objection(this);- 通知UVM框架测试工作完成,允许run_phase结束
5.4 重写构建阶段(示例)
function void build_phase(uvm_phase phase);
super.build_phase(phase);
cfg.scl_low_extra = 300;
endfunction
说明:
- 先调用
super.build_phase(phase),保证父类构建流程完整执行。 - 再覆盖/细化配置参数(如
cfg.scl_low_extra)。 - 在
build_phase中修改配置,可确保在后续连接与运行前生效。
6. 常用操作符与语法速查
6.1 比较与集合判断
| 符号/语法 | 作用 | 示例 |
|---|---|---|
=== / !== |
四态严格比较(含 X/Z) | 1'bz === 1'bz |
==? / !=? |
通配比较(右侧 X/Z 视为通配) | a ==? 4'b1x0x |
inside |
集合成员判断 | x inside {[1:10],20} |
6.2 位操作、拼接与数据重排
| 符号/语法 | 作用 | 示例 |
|---|---|---|
<< / <<< |
逻辑左移 / 算术左移 | a << 2, sa <<< 2 |
{} / {{}} |
拼接 / 复制拼接 | {a,b}, {4{a}} |
{<<{}} / {>>{}} |
流式打包/解包 | {<<8{data}} |
'1 / '0 / 'x |
填充值 | logic [7:0] a='1; |
6.3 赋值与自增
| 符号/语法 | 作用 | 示例 |
|---|---|---|
++ |
自增 | i++, ++i |
+= / -= / *= / %= / ^= |
复合赋值 | a += b, a ^= b |
6.4 作用域与面向对象相关
| 符号/语法 | 作用 | 示例 |
|---|---|---|
:: |
作用域解析 | cls::var |
-> |
类句柄成员访问 | p->data |
virtual |
虚方法/虚接口 | virtual task ..., virtual i2c_if vif; |
typedef |
类型别名 | typedef logic [7:0] byte_t; |
6.5 过程块关键字
| 符号/语法 | 作用 | 示例 |
|---|---|---|
always_comb |
组合逻辑过程块 | 自动敏感列表 |
always_latch |
锁存逻辑过程块 | always_latch begin ... end |
always_ff |
时序逻辑过程块 | @(posedge clk) |
6.6 常见系统函数
| 符号/语法 | 作用 | 示例 |
|---|---|---|
\$sformatf | 格式化字符串 | $sformatf("v=%0d", v) |
||
格式说明符(%[flags][width][.precision]type) |
0: flags - 用0填充不足的宽度不指定width: 使用实际需要的宽度d: type - 十进制整数 |
%0d |
\$random |
有符号随机数 | val = \$random(); |
\$clog2 |
向上取整对数 | int w = \$clog2(depth); |
6.7 随机化详解
//随机变量定义
rand int unsigned burst_len;
rand bit [7:0] start_reg;
rand int unsigned rounds;
rand int unsigned illegal_pct;
//约束块
constraint c_burst {
burst_len == 5;
start_reg inside {[8'h00:8'hF0]};
rounds inside {[1:10]};
illegal_pct inside {[0:100]};
}
randomize():随机化当前序列类对象中的随机变量,可带约束wr.randomize():对wr实例内的随机变量随机化std::randomize(start_reg) with { start_reg inside {[reg_lo:reg_hi]}; }:随机化局部变量std::randomize(start_reg):标准作用域操作符指定使用随机化函数randomize()with { ... }:内联约束块(为本次随机化添加临时约束条件,只对本次随机化有效)
- int random_num =
$urandom_range(0,99);:只对单个变量随机化,无约束只有范围
6.8 功能覆盖率定义基本语法
覆盖组
covergroup cg_i2c_func(覆盖组名) with function sample(采样参数列表);
option.per_instance = 1; // 选项(每个实例单独收集覆盖率)
覆盖点定义;
交叉覆盖定义;
endgroup
cg_i2c_func = new(); //实例化覆盖组
cg_i2c_func.sample(); //覆盖率采样
func_cov_pct = cg_i2c_func.get_inst_coverage(); //获取覆盖率
覆盖点
cp_name : coverpoint 变量名 {
bins 仓名1 = {值或范围};
bins 仓名2 = {[最小值:最大值]};
ignore_bins 忽略仓 = {值};
illegal_bins 非法仓 = {值};
}
bins:是关键字,用于在coverpoint中定义仓,并不是变量
交叉覆盖
cx_name : cross 覆盖点名1, 覆盖点名2 {
bins 组合仓 = binsof(覆盖点名1.仓名) && binsof(覆盖点名2.仓名);
ignore_bins 忽略组合 = 条件;
}
6.9 命令行额外参数引入
virtual task body();
uvm_cmdline_processor clp; //命令行处理器
string arg_val; //存储命令行参数值
int blen;
bit [7:0] reg_lo; //寄存器地址范围边界
bit [7:0] reg_hi;
int unsigned eff_burst_len; //实际使用值
clp = uvm_cmdline_processor::get_inst(); //获取命令行处理器实例
reg_lo = 8'h00;
reg_hi = 8'hF0;
if (clp.get_arg_value("+BURST_LEN=", arg_val)) begin //从命令行获取突发长度参数
blen = arg_val.atoi(); //将字符串转换为整数
if (blen >= 1 && blen <= 16) //参数有效性检查
eff_burst_len = blen;
end
-
uvm_cmdline_processor clp;:命令行处理器 -
string arg_val;:存储命令行参数值 -
clp = uvm_cmdline_processor::get_inst();:获取命令行处理器实例 -
clp.get_arg_value("+BURST_LEN=", arg_val):从命令行获取突发长度参数blen = arg_val.atoi();:将字符串转换为整数
7. UVM 常用 API 速查
| 类别 | 方法/宏 | 说明 |
|---|---|---|
| 对象创建 | type_id::create / new |
工厂创建 / 直接创建 |
| 配置管理 | uvm_config_db::set/get |
跨层传参 |
| 报告机制 | `uvm_info / `uvm_warning / `uvm_error / `uvm_fatal |
日志与错误处理 |
| 相位 | build_phase/connect_phase/run_phase |
生命周期入口 |
| objection | raise_objection/drop_objection |
控制测试结束时机 |
| 序列驱动握手 | get_next_item/item_done |
sequencer-driver 通道 |
| 事务工具 | copy/compare/print/convert2string |
调试与比对 |
| 随机化 | randomize |
约束随机激励 |
报告宏默认行为
| 宏 | 默认 verbosity | 是否终止仿真 |
|---|---|---|
`uvm_fatal |
UVM_NONE |
是 |
`uvm_error |
UVM_NONE |
否 |
`uvm_warning |
UVM_MEDIUM |
否 |
`uvm_info |
UVM_MEDIUM |
否 |
8. TLM 连接模型:Analysis 与 Seq-Drv 握手
8.1 Analysis 广播(发布/订阅)
- 端口类型:
uvm_analysis_port/uvm_analysis_imp - 回调约定:
write(T t) - 典型链路:
monitor(or driver) -> scoreboard/coverage
关键关系:
- 发布端定义:
uvm_analysis_port #(i2c_item) ap; - 订阅端定义:
uvm_analysis_imp #(i2c_item, i2c_scoreboard) imp; - 拓扑连接:
agent.drv.ap.connect(scb.imp); - 发布事务:
ap.write(tr);(框架回调到订阅者write())
关键语句解释:
uvm_analysis_port #(i2c_item) ap;:声明广播发布端。uvm_analysis_imp #(i2c_item, i2c_scoreboard) imp;:声明订阅入口,回调到i2c_scoreboard::write()。agent.drv.ap.connect(scb.imp);:把发布端和接收端连通。ap.write(tr);:发布事务给所有订阅者(1 对多)。
补充
- 接收多个广播发布
`uvm_analysis_imp_decl(_drv) // 创建 uvm_analysis_imp_drv 类
`uvm_analysis_imp_decl(_mon) // 创建 uvm_analysis_imp_mon 类
class i2c_scoreboard extends uvm_component;
`uvm_component_utils(i2c_scoreboard)
uvm_analysis_imp_drv#(i2c_item, i2c_scoreboard) imp_drv;
uvm_analysis_imp_mon#(i2c_item, i2c_scoreboard) imp_mon;
...
endclass
uvm_analysis_imp_decl:这是一个预定义的宏,用于声明分析端口的实现类(_mon):自定义后缀,用于创建唯一的类名
8.2 Sequencer-Driver 事务握手
- 端口类型:
seq_item_port/seq_item_export - 关键方法:
seq_item_port.get_next_item(req);seq_item_port.item_done();
连接语句:
drv.seq_item_port.connect(sqr.seq_item_export);
未连接时,get_next_item() 无法正常取到事务,导致仿真异常或停滞。
关键语句解释:
seq_item_port.get_next_item(req):driver 阻塞等待 sequencer 发下一个事务。seq_item_port.item_done():通知 sequencer 当前事务处理完成。drv.seq_item_port.connect(sqr.seq_item_export):建立 sequencer → driver 握手通道。
9. uvm_config_db 配置传递机制
9.1 配置下发(set)
uvm_config_db#(i2c_cfg)::set(this, "env.agent*", "cfg", cfg);
含义:
- 类型为
i2c_cfg - 从
this作用域发布 - 路径匹配
env.agent* - 键名
cfg - 值为配置对象
cfg
参数解释(set(cntxt, inst_name, field_name, value)):
this:发布配置的起始上下文。"env.agent*":目标实例路径(支持通配符*)。"cfg":配置键名。cfg:实际下发的配置对象。
9.2 配置读取(get)
if (!uvm_config_db#(virtual i2c_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "vif not set")
说明:
- 获取
virtual i2c_if类型的虚接口 - 键名为
vif - 未取到则
uvm_fatal终止仿真
参数解释(get(cntxt, inst_name, field_name, value_out)):
this:当前组件上下文(谁来取)。"":实例名不过滤(在当前可见范围内查找)。"vif":要读取的键名。vif:输出变量(取到后写入该句柄)。
10. 示例
class i2c_driver extends uvm_driver #(i2c_item);
`uvm_component_utils(i2c_driver)
virtual i2c_if vif;
i2c_cfg cfg;
uvm_analysis_port #(i2c_item) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if (!uvm_config_db#(virtual i2c_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "vif not set")
if (!uvm_config_db#(i2c_cfg)::get(this, "", "cfg", cfg)) begin
cfg = i2c_cfg::type_id::create("cfg");
`uvm_warning("NOCFG", "cfg not set, use default")
end
endfunction
task run_phase(uvm_phase phase);
i2c_item tr;
vif.init_bus();
forever begin
seq_item_port.get_next_item(tr);
`uvm_info("DRV", $sformatf("Drive: %s", tr.convert2string()), UVM_MEDIUM)
case (tr.op)
I2C_WRITE: do_write(tr);
I2C_READ : do_read(tr);
endcase
ap.write(tr);
seq_item_port.item_done();
end
endtask
endclass
关键语句逐行说明:
virtual i2c_if vif;:类里保存虚接口句柄,不能直接例化 interface 实体。uvm_analysis_port #(i2c_item) ap;:用于把 driver 事务广播给 scoreboard/coverage。ap = new("ap", this);:在构造函数中创建 analysis 端口。super.build_phase(phase);:先执行父类构建逻辑。uvm_config_db::get(..."vif", vif):从配置库提取虚接口。`uvm_fatal("NOVIF", ...):接口缺失时立即终止,避免空句柄驱动。uvm_config_db::get(..."cfg", cfg):读取配置对象;缺失时创建默认配置并告警。seq_item_port.get_next_item(tr);:获取待驱动事务。`uvm_info("DRV", $sformatf(...), UVM_MEDIUM):打印调试日志。case (tr.op):按事务操作类型选择写/读驱动流程。ap.write(tr);:把“已执行事务”发送到分析通道。seq_item_port.item_done();:完成握手,允许发下一个事务。
uvm_info 详细级别补充:
- 常见等级:
UVM_NONE / UVM_LOW / UVM_MEDIUM / UVM_HIGH / UVM_FULL / UVM_DEBUG。 - 建议默认使用
UVM_MEDIUM,调试难题时临时提升 verbosity。
11. 易错点
build_phase()忘记super.build_phase(phase)。driver未拿到vif却继续仿真。seq_item_port与seq_item_export未连接。item_done()漏调用导致 sequence 卡死。- object/component 的
new()形参写错:- object:
new(string name="...") - component:
new(string name, uvm_component parent)
- object:
- 组件类与对象类注册宏混用。

浙公网安备 33010602011771号