Quick Start
本指南以 2026 年全国大学生 FPGA 创新设计竞赛(FPGA大赛)为背景,提供一套可复现的 UVM 验证环境搭建与覆盖率提升方案。目标是在 30 分钟内从零跑通一个 UVM 测试用例,并看到覆盖率报告。
- [object Object]
前置条件与环境
| 项目 | 推荐值 | 说明 | 替代方案 |
|---|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (XC7A35T) | 竞赛指定平台之一 | Altera Cyclone V |
| EDA 版本 | Vivado 2025.2 | 支持 UVM 1.2 内建库 | Modelsim SE-64 2025.1 |
| 仿真器 | Vivado Simulator (xsim) | 与 Vivado 深度集成 | QuestaSim 2025.1 |
| 时钟/复位 | 100 MHz 系统时钟,低电平有效异步复位 | 竞赛板卡标准 | 50 MHz 或 125 MHz |
| 接口依赖 | AXI4-Stream (视频流) | 竞赛典型接口 | Wishbone / Avalon |
| 约束文件 | XDC 时序约束(主时钟、输入输出延迟) | 确保仿真与上板一致 | SDC(Altera) |
| UVM 库 | UVM 1.2 (Vivado 内建) | 无需额外安装 | UVM 1.1d (需手动下载) |
目标与验收标准
- 功能点:UVM testbench 能对 DUT(一个简单的 AXI4-Stream 滤波器)发送 1000 个随机激励,并自动比对输出。
- 性能指标:仿真运行时间 < 5 分钟(1000 个事务);无死锁或超时。
- 资源/Fmax:不涉及综合,仅仿真。
- 覆盖率目标:行覆盖率 ≥ 90%,翻转覆盖率 ≥ 75%,FSM 覆盖率 ≥ 85%(以 DUT 的 FSM 为准)。
- 验收方式:执行 vcover report -details -html coverage.ucdb 生成 HTML 报告,打开后逐项检查。
实施步骤
工程结构
project_root/
├── rtl/
│ └── axis_filter.sv # DUT:AXI4-Stream 滤波器
├── tb/
│ ├── tb_top.sv # 顶层 testbench(含接口与时钟生成)
│ ├── test_lib.sv # 测试用例(base_test, test_simple)
│ ├── env.sv # UVM environment
│ ├── agent.sv # UVM agent(含 driver、monitor)
│ ├── driver.sv # UVM driver
│ ├── monitor.sv # UVM monitor
│ ├── scoreboard.sv # UVM scoreboard
│ └── sequence_lib.sv # 激励序列
├── sim/
│ ├── run.do # Modelsim 运行脚本
│ └── run_xsim.tcl # Vivado 运行脚本
└── cov/
└── coverage.ucdb # 覆盖率数据库(运行后生成)逐行说明
- 第 1 行:工程根目录,所有代码和脚本在此组织。
- 第 2–3 行:rtl/ 存放 DUT 的 SystemVerilog 源代码,这里是 axis_filter.sv,实现一个简单的 AXI4-Stream 滤波器(如均值滤波)。
- 第 4–11 行:tb/ 存放 UVM 验证组件,包括顶层 testbench、测试用例、环境、代理、驱动、监视器和记分板。每个文件对应一个 UVM 类。
- 第 12–14 行:sim/ 存放运行脚本,run.do 用于 Modelsim,run_xsim.tcl 用于 Vivado。
- 第 15–16 行:cov/ 存放覆盖率数据库文件,运行后由仿真器生成。
关键模块:DUT(axis_filter.sv)
module axis_filter #(
parameter DATA_WIDTH = 8
) (
input logic clk,
input logic rst_n,
input logic [DATA_WIDTH-1:0] s_axis_tdata,
input logic s_axis_tvalid,
output logic s_axis_tready,
output logic [DATA_WIDTH-1:0] m_axis_tdata,
output logic m_axis_tvalid,
input logic m_axis_tready
);
logic [DATA_WIDTH-1:0] buf;
logic [1:0] state;
localparam IDLE = 2'b00, FILTER = 2'b01, OUT = 2'b10;
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
state <= IDLE;
buf <= '0;
m_axis_tvalid <= 1'b0;
end else begin
case (state)
IDLE: begin
if (s_axis_tvalid && s_axis_tready) begin
buf <= s_axis_tdata;
state <= FILTER;
end
end
FILTER: begin
buf <= buf + 1; // 简单滤波:加 1
state <= OUT;
end
OUT: begin
if (m_axis_tready) begin
m_axis_tdata <= buf;
m_axis_tvalid <= 1'b1;
state <= IDLE;
end
end
endcase
end
end
assign s_axis_tready = (state == IDLE);
endmodule逐行说明
- 第 1–2 行:模块声明,带参数 DATA_WIDTH 默认 8 位,便于复用。
- 第 3–10 行:端口列表:时钟、复位(低电平有效)、AXI4-Stream 从接口(s_axis_*)和主接口(m_axis_*)。
- 第 11–13 行:内部寄存器:buf 存储数据,state 为状态机,localparam 定义状态编码。
- 第 15–37 行:时序逻辑块,敏感列表为时钟上升沿和复位下降沿。
- 第 16–20 行:复位逻辑:状态回到 IDLE,buf 清零,输出无效。
- 第 21–35 行:状态机:IDLE 等待输入有效且 ready;FILTER 执行加 1 操作;OUT 等待下游 ready 后输出。
- 第 38 行:组合逻辑:仅当状态为 IDLE 时拉高 s_axis_tready,表示可接收数据。
关键模块:UVM Driver
class axis_driver extends uvm_driver #(axis_transaction);
`uvm_component_utils(axis_driver)
virtual axis_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_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);
drive_transaction(req);
seq_item_port.item_done();
end
endtask
task drive_transaction(axis_transaction tr);
@(posedge vif.clk);
vif.s_axis_tdata <= tr.data;
vif.s_axis_tvalid <= 1'b1;
@(posedge vif.clk);
wait (vif.s_axis_tready);
vif.s_axis_tvalid <= 1'b0;
endtask
endclass逐行说明
- 第 1 行:类声明,继承自 uvm_driver #(axis_transaction),参数化为事务类型。
- 第 2 行:UVM 工厂注册宏,实现类注册。
- 第 4 行:声明虚拟接口句柄,用于驱动 DUT 信号。
- 第 6–8 行:构造函数,调用父类。
- 第 10–13 行:build_phase 通过 uvm_config_db 获取虚拟接口;若失败则报致命错误。
- 第 15–20 行:run_phase 循环:从 sequencer 获取事务,驱动,然后通知完成。
- 第 22–28 行:drive_transaction 任务:在时钟上升沿后设置数据与 valid,等待 ready 后撤销 valid。
覆盖率收集配置
// 在 tb_top.sv 中添加
initial begin
$coverage_on;
// 设置覆盖率选项
$set_coverage_db_name("cov/coverage.ucdb");
#10000;
$coverage_save("cov/coverage.ucdb");
$coverage_off;
end逐行说明
- 第 2 行:$coverage_on 开启覆盖率收集。
- 第 4 行:$set_coverage_db_name 指定覆盖率数据库文件名。
- 第 5 行:等待 10000 个时间单位(仿真时间),确保所有事务完成。
- 第 6 行:$coverage_save 保存覆盖率数据到文件。
- 第 7 行:$coverage_off 关闭收集,避免后续仿真干扰。
验证结果
| 指标 | 测量值(示例) | 测量条件 |
|---|---|---|
| 行覆盖率 | 92.3% | 1000 个随机事务,Vivado 2025.2 |
| 翻转覆盖率 | 78.1% | 同上 |
| FSM 覆盖率 | 100% | 所有状态与转移均被覆盖 |
| 仿真运行时间 | 3 分 12 秒 | Intel i7-12700, 32GB RAM |
| 无死锁/超时 | 通过 | 仿真日志无 UVM_FATAL |
注意:以上数值为示例,实际结果以具体 DUT 和约束求解器配置为准。建议在提交竞赛报告前,使用 vcover report -details 生成详细报告,并截图附在文档中。
故障排查(Troubleshooting)
- 现象 1:仿真启动后立即退出,无任何输出。
原因:UVM 库未正确加载。
检查点:确认 UVM_HOME 环境变量设置正确。
修复建议:在脚本中显式指定 -sv_lib $UVM_HOME/lib/uvm_dpi。 - 现象 2:报错 uvm_fatal:虚拟接口未设置。
原因:uvm_config_db 的 set 与 get 路径不匹配。
检查点:打印 get_full_name() 确认组件层次。
修复建议:在 build_phase 中使用 uvm_config_db #(virtual axis_if)::set(this, “*”, “vif”, vif)。 - 现象 3:覆盖率报告为空或全 0%。
原因:仿真时未启用覆盖率选项。
检查点:确认命令行包含 -coverage 或 +cover。
修复建议:在 vsim 命令中添加 -coverage。 - 现象 4:仿真长时间无响应(死锁)。
原因:driver 等待 s_axis_tready 一直为低。
检查点:检查 DUT 状态机是否卡在非 IDLE 状态。
修复建议:添加超时断言,如 assert property (@(posedge clk) s_axis_tready |-> ##[1:10] 1);。 - 现象 5:波形中数据错误。
原因:driver 与 monitor 采样边沿不一致。
检查点:确认两者均在 @(posedge clk) 后驱动/采样。
修复建议:统一使用时钟块(clocking block)避免竞争。 - 现象 6:UVM 报告大量 UVM_WARNING 关于未连接端口。
原因:DUT 某些端口未在 testbench 中连接。
检查点:检查 tb_top.sv 中接口例化。
修复建议:将所有未用端口接地或接高阻。 - 现象 7:仿真速度极慢(>10 分钟)。
原因:随机约束导致大量回溯。
检查点:检查 randcase 权重是否合理。
修复建议:减少事务数量或使用 rand_mode(0) 关闭部分随机。 - 现象 8:HTML 覆盖率报告无法打开。
原因:报告生成命令错误。
检查点:确认 vcover report -html 输出路径有写权限。
修复建议:使用绝对路径。
原理与设计说明
UVM(Universal Verification Methodology)基于 SystemVerilog,提供了一套标准化的验证组件架构。其核心思想是“事务级建模”(Transaction-Level Modeling, TLM),将信号级驱动抽象为事务级操作,从而提升验证效率与可复用性。为什么选择 UVM 而非传统 Verilog testbench?因为 FPGA 大赛中 DUT 复杂度逐年上升(如视频处理、神经网络加速器),传统方法在激励随机化、覆盖率驱动和自动化比对方面力不从心。
关键权衡
- 资源 vs Fmax:UVM 仅用于仿真,不消耗 FPGA 逻辑资源;但仿真时间随事务数量线性增长。建议在仿真服务器上运行,而非个人笔记本。
- 吞吐 vs 延迟:覆盖率驱动的随机激励会生成大量事务,吞吐高但仿真延迟大。可通过约束求解器优化(如 randcase 权重)平衡。
- 易用性 vs 可移植性:UVM 1.2 与 Vivado 深度集成,但迁移到其他 EDA 工具(如 Synopsys VCS)需调整编译选项。建议使用标准 UVM 宏,避免厂商扩展。
扩展与下一步
- 参数化 UVM 环境:通过 uvm_config_db 传递参数(如数据宽度、事务数量),实现一键切换不同 DUT。
- 带宽提升:使用 AXI4-Stream 的 tkeep 和 tlast 信号,支持包传输而非单拍。
- 跨平台:将 UVM 环境迁移到 Synopsys VCS 或 Cadence Xcelium,注意编译选项差异。
- 加入断言:使用 SystemVerilog Assertions (SVA) 覆盖时序协议,如 assert property (@(posedge clk) s_axis_tvalid |-> ##[1:10] s_axis_tready)。
- 形式验证:对关键 FSM 使用形式化工具(如 OneSpin)进行数学证明,补充动态仿真不足。
- 覆盖率驱动调试:使用 vcover merge 合并多次运行结果,快速定位未覆盖分支。
参考与信息来源
本指南参考了 UVM 1.2 用户手册、Vivado 2025.2 仿真文档以及 2026 年 FPGA 大赛官方技术规范。具体实现细节请以官方文档为准。
附录:常见坑与排查速查表
- 坑 1:虚拟接口未正确传递。检查 uvm_config_db 的路径与 set 的组件层次是否匹配。
- 坑 2:时序不匹配导致死锁。确保 driver 与 monitor 使用相同的时钟边沿。
- 坑 3:覆盖率报告为空。确认仿真时加了 -coverage 选项,且 $coverage_save 正确调用。



