UVM验证
UVM 基础入门
UVM验证方法学入门,基于实例讲解基础概念
UVM 验证
UVM 基础入门
本册基于
design.sv和testbench.sv示例讲解 UVM 基础概念,包括架构、 Component 关系、Transaction、Sequence、Sequencer、数据流和约束。 掌握这些概念后,可继续阅读《UVM 工程化实践》,从验证计划、平台装配、连接机制、 检查闭环和覆盖收敛的角度学习可维护验证平台的搭建方法。
1. UVM 是什么
UVM (Universal Verification Methodology) = 通用验证方法学
UVM = 一套标准 + 一套工具库 + 一套最佳实践
目的:让不同公司的验证环境可以复用
为什么需要 UVM
没有 UVM:
├── 每家公司自己写验证环境
├── 代码不通用
└── 复用性差
有 UVM:
├── 统一标准
├── 代码可复用
└── 大量现成组件
2. UVM 架构总览
┌──────────────────────────────────────────────────┐
│ Test (测试用例) │
│ ┌────────────────────────────────────────────┐ │
│ │ Environment (环境) │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Agent (代理) │ │ │
│ │ │ │ │ │
│ │ │ Sequence ──→ Sequencer ──→ Driver ──│──│──│──→ Interface ──→ DUT
│ │ │ │ │ │
│ │ │ Monitor ←───────────────│──│──│←── Interface ←── DUT
│ │ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
本例的架构
┌─────────────────────────────────────┐
│ adpcm_test │
│ │
│ adpcm_tx_seq │
│ │ │
│ ▼ │
│ adpcm_env │
│ └── adpcm_agent │
│ ├── adpcm_sequencer │
│ ├── adpcm_driver ──────────│──→ adpcm_if ──→ DUT
│ └── adpcm_monitor ←────────│─── adpcm_if
└─────────────────────────────────────┘
3. UVM Component 关系详解
Component 是什么?
Component = 有生命周期的"活的"组件
特点:
├── 有 build_phase(创建阶段)
├── 有 run_phase(运行阶段)
├── 有 parent(父组件)
├── 可以创建子组件
└── 仿真结束才销毁
Component vs Object
| 特性 | Component | Object |
|---|---|---|
| 生命周期 | 隶属 UVM 组件树,贯穿各 phase | 由句柄引用决定,可短暂也可贯穿仿真 |
| 有 parent | ✅ 有层次 | ❌ 独立 |
| 有 Phase | ✅ 完整 | ❌ 无 |
| 基类 | uvm_component | uvm_object |
| 例子 | Driver, Monitor, Test | Sequence Item |
类比:
Component = 员工(长期在职,有上级)
Object = 快递包裹(用完就扔)
Component 层次结构
uvm_component ← 根(所有 component 的祖先)
│
├── uvm_test ← 测试用例
│ │
│ └── uvm_env ← 环境
│ │
│ └── uvm_agent ← 代理
│ │
│ ├── uvm_driver ← 驱动器
│ ├── uvm_monitor ← 监控器
│ └── uvm_sequencer ← 序列器
│
└── uvm_scoreboard ← 记分板
Parent-Child 关系
// 创建组件时,指定 parent
class my_test extends uvm_test;
my_env env;
function void build_phase(uvm_phase phase);
// this = my_test(自己是 parent)
env = my_env::type_id::create("env", this); // env 的 parent 是 test
endfunction
endclass
class my_env extends uvm_env;
my_agent agent;
function void build_phase(uvm_phase phase);
// this = my_env(自己是 parent)
agent = my_agent::type_id::create("agent", this); // agent 的 parent 是 env
endfunction
endclass
层次结构图
my_test (parent = null)
│
└── my_env (parent = my_test)
│
└── my_agent (parent = my_env)
│
├── my_driver (parent = my_agent)
├── my_monitor (parent = my_agent)
└── my_sequencer (parent = my_agent)
层级路径:
my_driver 的全名 = "uvm_test_top.env.agent.driver"
为什么需要层次?
1. 仿真结束判断
└── run phase 中的 objection 全部撤销后,UVM 才可结束运行阶段
2. Phase 传播
└── test.build_phase → env.build_phase → agent.build_phase → ...
3. 配置传递
└── test 设置参数 → 子组件可以读取
4. 打印层次
└── 便于调试,知道组件在哪个位置
完成 Agent 和 Environment 扩展后的层次
top_tb ← 顶层模块(不是 component)
│
└── adpcm_test ← component(parent = null)
│
└── env ← adpcm_env(parent = test)
│
└── agent ← adpcm_agent(parent = env)
│
├── driver ← Active 模式创建
├── monitor ← Active/Passive 都创建
└── sequencer ← Active 模式创建
// 各层只创建自己的直接子组件:
// Test:
env = adpcm_env::type_id::create("env", this);
// Environment:
agent = adpcm_agent::type_id::create("agent", this);
// Active Agent:
driver = adpcm_driver::type_id::create("driver", this);
monitor = adpcm_monitor::type_id::create("monitor", this);
sequencer = adpcm_sequencer::type_id::create("sequencer", this);
Component 的 Phase 顺序
uvm_test (顶层)
│
├── build_phase 从 test 开始,向下传播
│ └── test.build_phase
│ └── env.build_phase
│ └── agent.build_phase
│ └── driver.build_phase
│
├── connect_phase 从底层开始,向上传播
│ └── driver.connect_phase
│ └── agent.connect_phase
│ └── env.connect_phase
│ └── test.connect_phase
│
└── run_phase 所有 component 并行运行
├── driver.run_phase ← 并行
├── monitor.run_phase ← 并行
└── sequencer.run_phase ← 并行
4. 事务层 (Sequence Item)
testbench.sv 第 17-72 行
class adpcm_seq_item extends uvm_sequence_item;
rand logic[31:0] data; // 32位数据(随机生成)
rand int delay; // 发送前延迟(随机生成)
constraint c_delay { delay > 0; delay <= 20; } // 约束:1-20
// 以下都是 UVM 需要的固定写法
`uvm_object_utils(adpcm_seq_item) // 注册到工厂
function new(string name = "adpcm_seq_item");
super.new(name); // 调用父类构造函数
endfunction
function void do_copy(uvm_object rhs); // 拷贝功能
adpcm_seq_item rhs_;
if(!$cast(rhs_, rhs)) // 类型转换
`uvm_fatal("COPY", "adpcm_seq_item 类型转换失败")
data = rhs_.data;
delay = rhs_.delay;
endfunction
function bit do_compare(uvm_object rhs, uvm_comparer comparer); // 比较功能
adpcm_seq_item rhs_;
do_compare = $cast(rhs_, rhs) &&
data == rhs_.data &&
delay == rhs_.delay;
endfunction
function string convert2string(); // 转字符串(打印用)
return $sformatf(" data:\t%0h\n delay:\t%0d", data, delay);
endfunction
endclass
这里的 $cast(目标变量, 源句柄) 不是普通赋值,而是运行时类型检查。do_copy() 和 do_compare() 的参数类型都是通用的 uvm_object;只有当 rhs 实际指向 adpcm_seq_item(或其子类)对象时,转换才成功并返回 1,此后才能安全访问 data 和 delay。转换只让两个句柄指向同一个对象,不会创建或复制对象。
在 do_compare() 中,&& 具有短路求值特性:若 $cast 返回 0,后面的 rhs_.data 和 rhs_.delay 不会被访问。若转换失败属于不应继续的错误,则像 do_copy() 一样检查返回值并用 `uvm_fatal 报告。更完整的向上转换、向下转换和失败行为见《SystemVerilog 验证专用语法》的“安全的向下转换:$cast”。
类比理解
Sequence Item = 快递包裹
┌─────────────────┐
│ data = 32'hAA │ ← 包裹里的东西
│ delay = 5 │ ← 发送前等待的时间
└─────────────────┘
rand = 随机生成(每次仿真值不同)
constraint = 约束(delay 必须在 1-20 之间)
关键方法
| 方法 | 作用 | 什么时候调用 |
|---|---|---|
new() | 构造函数 | 创建对象时 |
do_copy() | 复制数据 | 需要拷贝事务时 |
do_compare() | 比较数据 | scoreboard 比对时 |
convert2string() | 返回便于日志显示的字符串 | 显式调用或由 sprint() 使用时 |
5. 序列层 (Sequence)
testbench.sv 第 136-165 行
class adpcm_tx_seq extends uvm_sequence #(adpcm_seq_item);
`uvm_object_utils(adpcm_tx_seq)
adpcm_seq_item req;
int unsigned no_reqs = 10; // 默认发送 10 个事务
task body();
for(int i = 0; i < no_reqs; i++) begin
req = adpcm_seq_item::type_id::create($sformatf("req_%0d", i));
start_item(req); // 1. 开始
if (!req.randomize())
`uvm_fatal("RAND", "adpcm_seq_item 随机化失败")
finish_item(req); // 4. 发送
$display("发送第 %0d 帧", i);
end
endtask
endclass
Sequence 的作用
Sequence = 测试计划
"我要发 10 个随机包"
"每个包的数据随机"
"每个包的延迟随机"
它不关心"怎么发",只关心"发什么"
Sequence 的 API
| API | 作用 |
|---|---|
type_id::create() | 创建对象(工厂模式) |
start_item(req) | 通知 Sequencer 准备发送 |
finish_item(req) | 等待 Driver 处理完 |
$urandom_range(1,20) | 生成随机数 |
6. 序列器 (Sequencer)
testbench.sv 第 121-129 行
class adpcm_sequencer extends uvm_sequencer #(adpcm_seq_item);
`uvm_component_utils(adpcm_sequencer)
function new(string name = "adpcm_sequencer", uvm_component parent = null);
super.new(name, parent);
endfunction
endclass
7. 完整数据流
时间线:
T0: adpcm_test.build_phase
└── 创建 adpcm_env
└── env 创建 adpcm_agent
└── agent 创建 Driver、Monitor、Sequencer
T1: connect_phase
├── agent 内部连接 Driver ←→ Sequencer
└── env 连接 Monitor → 检查/覆盖组件(加入这些组件后)
T2: adpcm_test.run_phase
└── 在 env.agent.sequencer 上启动 adpcm_tx_seq
T3: adpcm_tx_seq.body
└── 创建 req,随机化 data/delay
T4: start_item(req)
└── 通知 Sequencer 准备
T5: finish_item(req)
└── Sequencer 把 req 传给 Driver
T6: Driver 与 Monitor 并发工作
├── adpcm_driver
│ ├── get_next_item(req) 拿到 req
│ ├── repeat(delay) 等待
│ ├── frame <= 1 帧开始
│ └── 发送 8 个 nibble 数据传输
│
└── adpcm_monitor
├── 等待 frame == 1
├── 采样 8 个 nibble
└── 重组 tr.data
T7: 一帧完成
├── Driver: frame <= 0,item_done()
└── Input Monitor: analysis_port.write(tr)
└── Reference Model: 生成 expected transaction
T8-T12: 重复 T3-T7 共 10 次
---
## 8. 总结对照表
| UVM 概念 | 本例对应 | 作用 |
|-----------------------|-----------------------------|----------------------|
| **Interface** | `adpcm_if` | 信号打包 |
| **Virtual Interface** | `virtual adpcm_if.drv_mp` | 软件访问硬件的桥梁 |
| **Sequence Item** | `adpcm_seq_item` | 数据包定义 |
| **Driver** | `adpcm_driver` | 信号时序驱动 |
| **Monitor** | `adpcm_monitor` | 采样信号并重组事务 |
| **Agent** | `adpcm_agent` | 封装一个协议接口的组件 |
| **Environment** | `adpcm_env` | 组装和连接验证组件 |
| **Reference Model** | `adpcm_reference_model` | 根据真实输入预测 expected |
| **Sequencer** | `adpcm_sequencer` | 中转调度 |
| **Sequence** | `adpcm_tx_seq` | 测试激励 |
| **Test** | `adpcm_test` | 测试用例 |
| **Factory** | `type_id::create()` | 对象创建 |
| **Factory Override** | `set_type_override_by_type` | 不改代码替换组件 |
| **Phase** | `build/connect/run` | 执行顺序 |
| **objection** | `raise/drop` | 防止仿真提前结束 |
| **Config_db** | `set/get` | 跨层次传参 |
| **Scoreboard** | `check_phase` | 比对 DUT 输出 vs 预期 |
| **TLM** | `analysis_port/imp` | 组件间事务级通信 |
| **Constraint** | `constraint c_xxx` | 随机约束 |
| **Coverage** | `covergroup` | 功能覆盖率 |
| **Field Macro** | `uvm_field_int` | 自动注册字段 |
| **Virtual Sequence** | `fork/join` | 跨 Agent 协调 |
| **Register Model** | `reg.write/read` | 寄存器抽象层 |
### UVM 验证流程口诀
- 定义接口 (Interface)
- 定义虚拟接口 (Virtual Interface)
- 定义数据包 (Sequence Item)
- 写驱动器 (Driver)
- 写监控器 (Monitor)
- 写参考模型 (Reference Model)
- 写记分板 (Scoreboard)
- 写序列器 (Sequencer)
- 写测试序列 (Sequence)
- 组装代理 (Agent)
- 组装测试环境 (Environment)
- 组装测试用例 (Test)
- 配置参数 (Config_db)
- 设置覆盖率 (Coverage)
- 跑仿真看结果
---
## 9. Constraint(约束)
### 什么是 Constraint?
Constraint = 随机约束(控制随机变量的取值范围)
rand data; // 随机变量 constraint c_data { // 约束条件 data inside {[0:255]}; }
### 代码示例
```verilog
class my_seq_item extends uvm_sequence_item;
rand logic [7:0] data;
rand int delay;
rand bit [2:0] burst_len;
// 约束:delay 在 1-20 之间
constraint c_delay {
delay > 0;
delay <= 20;
}
// 约束:burst_len 只能是 1, 2, 4, 8
constraint c_burst {
burst_len inside {1, 2, 4, 8};
}
// 约束:data 不能是 0
constraint c_data {
data != 0;
}
endclass
常用约束类型
| 约束类型 | 语法 | 作用 |
|---|---|---|
| 范围约束 | inside {[min:max]} | 指定范围 |
| 列表约束 | inside {a, b, c} | 指定列表 |
| 条件约束 | if (条件) {约束} | 条件生效 |
| 唯一约束 | unique {变量} | 变量唯一 |
| 权重约束 | dist {1:=50, 2:=50} | 指定权重 |
Inline Constraint
// 在调用时添加额外约束
my_seq_item item = my_seq_item::type_id::create("item");
item.randomize() with {
data == 8'hFF; // 临时约束:data 必须是 FF
delay inside {[1:5]}; // 临时约束:delay 在 1-5
};
简单类比
Constraint = 抽奖规则
rand:抽奖(随机)
constraint:规则(限制范围)
"从 1-100 中随机抽一个数"
→ rand int num;
→ constraint c { num inside {[1:100]}; }
最后更新: 2026-08-20