Quick Start
以下步骤帮助你在30分钟内跑通第一个UVM验证环境,并看到仿真通过日志。
- [object Object]
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (XC7A35T) | 用于FPGA上板验证,UVM环境独立于器件 | Intel Cyclone V / Lattice ECP5 |
| EDA版本 | Questa 2024.2 / VCS 2024.06 | 支持SystemVerilog 2012和UVM 1.2 | Xcelium 23.09 / Riviera-PRO 2024.04 |
| 仿真器 | QuestaSim(ModelSim升级版) | 免费版支持UVM基本特性 | VCS MX / Xcelium |
| 时钟/复位 | 100MHz时钟,异步低电平复位 | DUT典型接口 | 50MHz / 200MHz;高电平复位 |
| 接口依赖 | AXI4-Stream (axis) | 示例DUT使用,UVM代理通用 | AHB / APB / Wishbone |
| 约束文件 | 无(仿真阶段不需要XDC) | 综合时才需要 | — |
| 操作系统 | Ubuntu 22.04 LTS / CentOS 7 | EDA工具官方支持 | Windows 10/11 (WSL2) |
目标与验收标准
- 功能点:UVM环境能生成随机事务,驱动DUT输入,监测DUT输出,并与参考模型比对。
- 性能指标:仿真速度不低于10K事务/秒(以100MHz时钟、事务长度64拍计算)。
- 资源/Fmax:不适用(仿真阶段)。
- 验收方式:运行
vsim -c -do "run -all; exit"后,日志最后一行包含“UVM_INFO: Test completed with 0 errors”。 - 关键波形:在GUI模式下打开波形,观察
axis_tvalid与axis_tready握手,数据正确。
实施步骤
阶段1:工程结构与DUT
- 创建目录树:
uvm_fpga_demo/{rtl, tb, sim}。 - 编写DUT:一个简单的AXI4-Stream FIFO,深度16,数据位宽8位。
- 常见坑:确保DUT的时钟与复位端口与UVM接口匹配;避免在DUT中使用UVM特有的类型。
// rtl/axis_fifo.sv
module axis_fifo (
input logic clk,
input logic rst_n,
input logic s_axis_tvalid,
input logic [7:0] s_axis_tdata,
output logic s_axis_tready,
output logic m_axis_tvalid,
output logic [7:0] m_axis_tdata,
input logic m_axis_tready
);
localparam DEPTH = 16;
logic [7:0] mem [0:DEPTH-1];
logic [$clog2(DEPTH):0] wr_ptr, rd_ptr;
logic full, empty;
assign full = (wr_ptr - rd_ptr) == DEPTH;
assign empty = (wr_ptr == rd_ptr);
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
wr_ptr <= 0;
rd_ptr <= 0;
end else begin
if (s_axis_tvalid && s_axis_tready) begin
mem[wr_ptr[$clog2(DEPTH)-1:0]] <= s_axis_tdata;
wr_ptr <= wr_ptr + 1;
end
if (m_axis_tvalid && m_axis_tready) begin
rd_ptr <= rd_ptr + 1;
end
end
end
assign s_axis_tready = !full;
assign m_axis_tvalid = !empty;
assign m_axis_tdata = mem[rd_ptr[$clog2(DEPTH)-1:0]];
endmodule逐行说明
- 第1行:模块声明,端口包括时钟、复位、AXI4-Stream从接口(s_axis_*)和主接口(m_axis_*)。
- 第2-3行:localparam定义FIFO深度为16,内部存储数组mem。
- 第4行:读写指针,宽度为log2(深度)+1,用于判断满空。
- 第5-6行:满标志:写指针减去读指针等于深度;空标志:两指针相等。
- 第7-17行:时序逻辑,复位时指针清零;写使能时存入数据并递增写指针;读使能时递增读指针。
- 第18-19行:输出赋值:tready为不满,tvalid为非空,tdata从读指针位置读取。
阶段2:UVM环境搭建
- 创建UVM组件:事务、接口、驱动、监视器、代理、环境、测试。
- 事务定义:继承
uvm_sequence_item,包含tvalid、tdata、tready等字段。 - 接口定义:virtual interface
axis_if,包含时钟块和时钟同步驱动。 - 常见坑:接口必须声明为virtual并在测试中通过config_db设置;事务中的rand变量需加
rand关键字。
// tb/my_transaction.sv
class my_transaction extends uvm_sequence_item;
rand logic tvalid;
rand logic [7:0] tdata;
rand logic tready;
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(tvalid, UVM_ALL_ON)
`uvm_field_int(tdata, UVM_ALL_ON)
`uvm_field_int(tready, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass逐行说明
- 第1行:类声明,继承自
uvm_sequence_item,使其可参与UVM序列/事务机制。 - 第2-4行:
rand关键字使字段可随机化;tvalid/tdata/tready对应AXI4-Stream信号。 - 第5-8行:UVM自动化宏,提供copy/compare/print等方法,UVM_ALL_ON表示所有操作都包含这些字段。
- 第9-11行:构造函数,调用父类构造函数。
// tb/my_if.sv
interface axis_if (input logic clk, input logic rst_n);
logic tvalid;
logic [7:0] tdata;
logic tready;
clocking cb @(posedge clk);
output tvalid, tdata;
input tready;
endclocking
modport DRIVER (clocking cb, output tvalid, tdata, input tready);
modport MONITOR (input tvalid, tdata, tready);
endinterface逐行说明
- 第1行:接口声明,带时钟和复位参数,便于连接到DUT。
- 第2-4行:内部信号声明。
- 第5-8行:时钟块cb,在时钟上升沿驱动/采样;output方向表示驱动到DUT,input表示从DUT采样。
- 第9-10行:modport定义不同角色的信号方向视图;DRIVER使用时钟块,MONITOR直接输入。
// tb/my_driver.sv
class my_driver extends uvm_driver #(my_transaction);
virtual axis_if vif;
`uvm_component_utils(my_driver)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
if (!uvm_config_db #(virtual axis_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "Virtual interface not set")
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
vif.cb.tvalid <= req.tvalid;
vif.cb.tdata <= req.tdata;
@(posedge vif.clk);
seq_item_port.item_done();
end
endtask
endclass逐行说明
- 第1行:参数化驱动类,指定事务类型为my_transaction。
- 第2行:声明virtual接口句柄,用于连接DUT。
- 第3行:UVM组件注册宏。
- 第4-6行:构造函数。
- 第7-10行:build_phase中通过config_db获取virtual接口;若失败则报UVM_FATAL终止仿真。
- 第11-17行:run_phase中无限循环:从序列端口获取下一个事务,驱动到时钟块的输出信号,等待一个时钟周期后通知完成。
阶段3:顶层与仿真运行
- 编写顶层模块
top.sv,例化DUT和接口,调用run_test()。 - 编写测试类
my_test,在build_phase中创建环境,在run_phase中启动序列。 - 常见坑:顶层中必须将接口通过config_db设置给测试;
run_test()参数需与测试类名一致(字符串)。
// tb/top.sv
module top;
logic clk, rst_n;
axis_if if0 (clk, rst_n);
axis_fifo dut (
.clk(clk), .rst_n(rst_n),
.s_axis_tvalid(if0.tvalid),
.s_axis_tdata (if0.tdata),
.s_axis_tready(if0.tready),
.m_axis_tvalid(),
.m_axis_tdata (),
.m_axis_tready(1'b1)
);
initial begin
clk = 0;
forever #5 clk = ~clk; // 100MHz
end
initial begin
rst_n = 0;
#20 rst_n = 1;
end
initial begin
uvm_config_db #(virtual axis_if)::set(null, "uvm_test_top.env.agent.driver", "vif", if0.DRIVER);
uvm_config_db #(virtual axis_if)::set(null, "uvm_test_top.env.agent.monitor", "vif", if0.MONITOR);
run_test("my_test");
end
endmodule逐行说明
- 第1行:模块声明。
- 第2行:声明时钟和复位信号。
- 第3行:实例化接口,连接时钟和复位。
- 第4-12行:例化DUT,将接口信号连接到DUT端口;主接口的tready固定为1(简化)。
- 第13-16行:时钟生成,周期10ns(100MHz)。
- 第17-20行:复位序列,低电平有效,20ns后释放。
- 第21-24行:通过config_db将接口设置给驱动器和监视器;路径字符串需与UVM层次一致;最后调用run_test启动测试。
阶段4:验证与调试
- 运行仿真命令:
cd sim && vlog -sv ../rtl/*.sv ../tb/*.sv +incdir+../tb && vsim -c -do "run -all; exit" work.top。 - 观察日志,确认无UVM_FATAL或UVM_ERROR。
- 常见坑:若出现“Interface not set”错误,检查config_db路径是否与UVM层次匹配;若出现“Transaction not driven”,检查驱动run_phase中的循环。
原理与设计说明
UVM(Universal Verification Methodology)基于SystemVerilog,提供可重用的验证组件架构。其核心思想是:
- 事务级建模(TLM):通过事务(transaction)抽象信号级交互,提高仿真效率。
- 工厂模式:通过
uvm_component_utils和uvm_object_utils注册,支持类型覆盖和配置。 - 层次化结构:test → env → agent(driver + monitor + sequencer) → DUT,便于复用。
- 配置数据库(config_db):解耦组件间依赖,通过字符串路径传递virtual interface和参数。
关键trade-off
- 资源 vs Fmax:UVM环境仅用于仿真,不消耗FPGA资源;但仿真速度受事务粒度影响——事务越细(每拍一个事务),仿真越慢;建议每事务包含多个时钟周期(如64拍),提升吞吐。
- 吞吐 vs 延迟:UVM的TLM端口(put/get)有阻塞和非阻塞模式,阻塞模式代码简单但可能降低仿真器调度效率;非阻塞模式(try_put/try_get)可避免死锁,但需要额外状态管理。
- 易用性 vs 可移植性:使用UVM 1.2标准库可移植到任何支持SystemVerilog 2012的仿真器;但某些厂商扩展(如Questa的uvm_ml)会降低可移植性,应避免。
验证与结果
| 测量项 | 结果(示例) | 测量条件 |
|---|---|---|
| 仿真时间 | 12秒(1000事务) | Questa 2024.2,Ubuntu 22.04,i7-12700 |
| 事务吞吐 | ~83事务/秒 | 每个事务64个时钟周期,100MHz |
| UVM错误数 | 0 | 日志最后一行确认 |
| 波形正确性 | 所有事务数据匹配 | 比对DUT输出与参考模型 |
说明:以上数据基于示例配置,实际结果以用户工程为准。
故障排查(Troubleshooting)
- 现象:编译错误“Unknown type 'uvm_*'”。原因:未包含UVM库。检查:编译命令是否加了
+incdir+$UVM_HOME/src或使用-uvm开关。修复:在vlog命令中加入-uvm或手动指定UVM源文件路径。 - 现象:UVM_FATAL “Virtual interface not set”。原因:config_db未正确设置。检查:顶层中set的路径字符串是否与UVM层次完全匹配。修复:打印
uvm_top.get_name()确认层次,修正路径。 - 现象:仿真卡住或超时。原因:驱动run_phase中未调用
item_done(),或序列未发送完成。检查:驱动循环中是否有seq_item_port.item_done()。修复:添加item_done调用。 - 现象:事务随机化失败。原因:rand变量约束冲突。检查:是否所有rand变量都有合法范围。修复:添加constraint
valid { tvalid dist {1:=90, 0:=10}; }等约束。 - 现象:波形中信号为X。原因:未正确驱动或复位未释放。检查:复位时序是否足够长;驱动是否在复位后开始。修复:确保rst_n释放后至少一个时钟周期再启动事务。
- 现象:UVM_ERROR “TLM port not connected”。原因:未连接monitor到scoreboard或参考模型。检查:env中是否调用了
monitor.ap.connect(scoreboard.analysis_export)。修复:在env的connect_phase中添加连接。 - 现象:仿真速度极慢。原因:事务粒度过细(每拍一个事务)。检查:每个事务包含的时钟周期数。修复:在sequence中生成事务时,将多个时钟周期的数据打包到一个事务中。
- 现象:不同仿真器结果不一致。原因:UVM库版本或SystemVerilog标准支持差异。检查:仿真器是否支持UVM 1.2。修复:统一使用UVM 1.2标准,避免使用厂商扩展。
- 现象:run_test()未找到测试类。原因:测试类名与字符串参数不匹配。检查:
run_test("my_test")中的字符串是否与类名一致。修复:确保类名和字符串完全一致(大小写敏感)。
扩展与进阶
本指南提供了一个最小可用的UVM验证环境。在此基础上,你可以:
- 添加功能覆盖率收集(covergroup)。
- 实现参考模型与自动比对(scoreboard)。
- 引入序列库(sequence library)以生成复杂激励。
- 集成断言(SVA)进行协议检查。
- 迁移到多DUT或多agent环境。
参考
- IEEE Std 1800-2017: SystemVerilog Language
- UVM 1.2 Class Reference
- Accellera UVM 1.2 User’s Guide
附录
附录A:完整工程文件列表(略)。附录B:常见EDA工具编译选项速查表(略)。



