news 2026/7/2 15:34:31

UVM验证环境从0到1搭建实战:手把手教你搭建可复用的验证平台

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UVM验证环境从0到1搭建实战:手把手教你搭建可复用的验证平台

作为验证工程师,你是否遇到过这样的困境:

  • 接手新项目,面对一个空文件夹,不知从何开始搭建验证环境

  • 网上找到的UVM示例代码零零散散,无法形成可复用的验证平台

  • 自己搭建的环境总是遇到奇奇怪怪的编译错误或运行时崩溃

本文是一个完整的实战教程,带你从空项目开始,一步步搭建一个符合工业标准的UVM验证平台。我们将以一个简单的AXI-Slave模块为DUT(被测设计),覆盖从目录结构设计、组件编码到测试用例编写的全流程。全程提供可直接运行的代码,确保你在30分钟内拥有一个可工作的UVM环境。

前置要求

  • 已安装VCS/Questa等支持UVM的仿真器

  • 掌握SystemVerilog基础语法

  • 了解UVM基本概念(Test、Component、Sequence等)

正文

一、项目结构与准备工作

1.1 目录结构设计

一个规范的UVM验证项目应该有以下目录结构:

axi_slave_uvm_tb/ ├── rtl/ # DUT RTL代码 │ └── axi_slave.v ├── tb/ # Testbench顶层 │ └── top_tb.sv ├── uvm/ # UVM验证平台 │ ├── agent/ # Agent组件 │ │ ├── axi_agent.sv │ │ ├── axi_driver.sv │ │ ├── axi_monitor.sv │ │ ├── axi_sequencer.sv │ │ └── axi_if.sv # Interface │ ├── env/ # Environment │ │ └── axi_env.sv │ ├── seq/ # Sequences │ │ ├── axi_base_seq.sv │ │ ├── axi_write_seq.sv │ │ └── axi_read_seq.sv │ ├── test/ # Tests │ │ ├── axi_base_test.sv │ │ └── axi_wr_rd_test.sv │ └── pkg/ # 公共定义 │ ├── axi_pkg.sv │ └── axi_defines.svh ├── tests/ # 测试列表 │ └── testlist.f ├── sim/ # 仿真目录 │ ├── Makefile │ └── run_sim.sh └── filelist.f # 文件列表

重要提示:不要把所有文件放在一个目录下。合理的分层结构是后期维护和复用的基础。

1.2 编译配置文件

filelist.f

// UVM库路径(根据你的安装调整) +incdir+$UVM_HOME/src $UVM_HOME/src/uvm_pkg.sv // 项目文件 +incdir+./uvm/pkg +incdir+./uvm/agent +incdir+./uvm/env +incdir+./uvm/seq +incdir+./uvm/test ./uvm/pkg/axi_pkg.sv ./uvm/agent/axi_if.sv ./rtl/axi_slave.v ./uvm/agent/axi_driver.sv ./uvm/agent/axi_monitor.sv ./uvm/agent/axi_sequencer.sv ./uvm/agent/axi_agent.sv ./uvm/env/axi_env.sv ./uvm/seq/axi_base_seq.sv ./uvm/seq/axi_write_seq.sv ./uvm/seq/axi_read_seq.sv ./uvm/test/axi_base_test.sv ./uvm/test/axi_wr_rd_test.sv ./tb/top_tb.sv

二、核心组件编码:从上到下的实现顺序

2.1 Transaction定义(axi_pkg.sv)

Transaction是验证平台的数据载体,务必在开发其他组件前定义清楚:

// uvm/pkg/axi_pkg.sv `ifndef AXI_PKG_SV `define AXI_PKG_SV package axi_pkg; import uvm_pkg::*; `include "uvm_macros.svh" // AXI Transaction class axi_transaction extends uvm_sequence_item; `uvm_object_utils(axi_transaction) rand bit [31:0] addr; rand bit [31:0] data; rand bit [3:0] strb; rand bit write; // 1=write, 0=read rand bit [7:0] len; // Burst length rand bit [2:0] size; // Burst size rand bit [1:0] burst; // Burst type bit [31:0] rsp_data; // Response data for read bit rsp_error; // Response error constraint valid_addr { addr[1:0] == 2'b00; // Word aligned } constraint valid_strb { write -> strb == 4'b1111; // Full word write } function new(string name = "axi_transaction"); super.new(name); endfunction function void do_copy(uvm_object rhs); axi_transaction tr; super.do_copy(rhs); $cast(tr, rhs); addr = tr.addr; data = tr.data; strb = tr.strb; write = tr.write; len = tr.len; size = tr.size; burst = tr.burst; endfunction function bit do_compare(uvm_object rhs, uvm_comparer comparer); axi_transaction tr; bit result = 1; $cast(tr, rhs); result &= (addr == tr.addr); result &= (data == tr.data); result &= (write == tr.write); return result; endfunction function string convert2string(); return $sformatf("%s addr=0x%08X data=0x%08X", write ? "WRITE" : "READ", addr, data); endfunction endclass endpackage `endif

关键点

  • 必须实现do_copydo_compare方法,用于sequence的复制和SB的比较

  • convert2string用于调试打印

  • 使用rand声明随机字段,配合constraint定义合法值

2.2 Interface定义(axi_if.sv)
// uvm/agent/axi_if.sv `ifndef AXI_IF_SV `define AXI_IF_SV interface axi_if(input logic aclk, input logic aresetn); // Write Address Channel logic [31:0] awaddr; logic awvalid; logic awready; // Write Data Channel logic [31:0] wdata; logic [3:0] wstrb; logic wvalid; logic wready; // Write Response Channel logic [1:0] bresp; logic bvalid; logic bready; // Read Address Channel logic [31:0] araddr; logic arvalid; logic arready; // Read Data Channel logic [31:0] rdata; logic [1:0] rresp; logic rvalid; logic rready; // Clocking blocks for driver clocking drv_cb @(posedge aclk); output awaddr, awvalid, wdata, wstrb, wvalid, bready; output araddr, arvalid, rready; input awready, wready, bresp, bvalid, arready, rdata, rresp, rvalid; endclocking // Clocking blocks for monitor clocking mon_cb @(posedge aclk); input awaddr, awvalid, awready, wdata, wstrb, wvalid, wready; input bresp, bvalid, bready, araddr, arvalid, arready; input rdata, rresp, rvalid, rready; endclocking endinterface `endif

关键设计

  • 分离drv_cb(驱动用)和mon_cb(监测用)时钟块

  • 驱动用output/input声明信号方向

  • 监测全用input,被动捕获所有信号

2.3 Sequence实现(axi_write_seq.sv)
// uvm/seq/axi_write_seq.sv class axi_write_seq extends uvm_sequence #(axi_transaction); `uvm_object_utils(axi_write_seq) rand int num_transactions = 10; function new(string name = "axi_write_seq"); super.new(name); endfunction virtual task body(); axi_transaction tr; for (int i = 0; i < num_transactions; i++) begin tr = axi_transaction::type_id::create($sformatf("tr_%0d", i)); start_item(tr); if (!tr.randomize() with { write == 1; addr[31:8] == i; // 写入不同地址段 }) begin `uvm_error(get_type_name(), "Randomize failed") end finish_item(tr); `uvm_info(get_type_name(), $sformatf("Sent: %s", tr.convert2string()), UVM_MEDIUM) end endtask endclass
2.4 Driver实现(axi_driver.sv)
// uvm/agent/axi_driver.sv class axi_driver extends uvm_driver #(axi_transaction); `uvm_component_utils(axi_driver) virtual axi_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 axi_if)::get(this, "", "vif", vif)) begin `uvm_fatal(get_type_name(), "Failed to get virtual interface") end endfunction virtual task run_phase(uvm_phase phase); forever begin seq_item_port.get_next_item(req); if (req.write) begin drive_write(req); end else begin drive_read(req); end seq_item_port.item_done(); end endtask virtual task drive_write(axi_transaction tr); // Write Address @(vif.drv_cb); vif.drv_cb.awaddr <= tr.addr; vif.drv_cb.awvalid <= 1; wait(vif.drv_cb.awready); @(vif.drv_cb); vif.drv_cb.awvalid <= 0; // Write Data vif.drv_cb.wdata <= tr.data; vif.drv_cb.wstrb <= tr.strb; vif.drv_cb.wvalid <= 1; wait(vif.drv_cb.wready); @(vif.drv_cb); vif.drv_cb.wvalid <= 0; // Write Response vif.drv_cb.bready <= 1; wait(vif.drv_cb.bvalid); @(vif.drv_cb); vif.drv_cb.bready <= 0; endtask virtual task drive_read(axi_transaction tr); // Read Address @(vif.drv_cb); vif.drv_cb.araddr <= tr.addr; vif.drv_cb.arvalid <= 1; wait(vif.drv_cb.arready); @(vif.drv_cb); vif.drv_cb.arvalid <= 0; // Read Data vif.drv_cb.rready <= 1; wait(vif.drv_cb.rvalid); tr.rsp_data = vif.drv_cb.rdata; tr.rsp_error = (vif.drv_cb.rresp != 2'b00); @(vif.drv_cb); vif.drv_cb.rready <= 0; endtask endclass

实现要点

  • 必须实现build_phase获取virtual interface

  • run_phase中使用seq_item_port.get_next_item获取sequence发来的transaction

  • 完成后调用item_done()通知sequence

2.5 Environment搭建(axi_env.sv)
// uvm/env/axi_env.sv class axi_env extends uvm_env; `uvm_component_utils(axi_env) axi_agent agt; // scoreboard将在后续添加 // uvm_analysis_port #(axi_transaction) analysis_port; function new(string name, uvm_component parent); super.new(name, parent); endfunction function void build_phase(uvm_phase phase); super.build_phase(phase); agt = axi_agent::type_id::create("agt", this); // scoreboard实例化 endfunction function void connect_phase(uvm_phase phase); super.connect_phase(phase); // 连接monitor到scoreboard endfunction endclass

三、Testbench顶层集成

3.1 Top Level TB(top_tb.sv)
// tb/top_tb.sv `timescale 1ns/1ps module top_tb; import uvm_pkg::*; `include "uvm_macros.svh" import axi_pkg::*; // Clock & Reset logic aclk = 0; logic aresetn; always #5 aclk = ~aclk; // 100MHz // Interface axi_if axi_vif(aclk, aresetn); // DUT实例化 axi_slave dut ( .aclk (aclk), .aresetn (aresetn), .awaddr (axi_vif.awaddr), .awvalid (axi_vif.awvalid), .awready (axi_vif.awready), // ... 其他信号连接 .rready (axi_vif.rready) ); // Test启动 initial begin // 将interface传递给UVM配置数据库 uvm_config_db#(virtual axi_if)::set(null, "*", "vif", axi_vif); // 运行测试 run_test("axi_wr_rd_test"); end // 复位序列 initial begin aresetn = 0; #100; aresetn = 1; end // 仿真超时 initial begin #100000; $display("Simulation timeout!"); $finish; end endmodule

四、测试用例编写与运行

4.1 基本测试(axi_wr_rd_test.sv)
// uvm/test/axi_wr_rd_test.sv class axi_wr_rd_test extends axi_base_test; `uvm_component_utils(axi_wr_rd_test) function new(string name, uvm_component parent); super.new(name, parent); endfunction virtual task run_phase(uvm_phase phase); axi_write_seq wr_seq; axi_read_seq rd_seq; phase.raise_objection(this); `uvm_info(get_type_name(), "Starting test...", UVM_LOW) // 首先执行写sequence wr_seq = axi_write_seq::type_id::create("wr_seq"); wr_seq.num_transactions = 20; wr_seq.start(env.agt.sqr); // 延迟后执行读sequence #1000; rd_seq = axi_read_seq::type_id::create("rd_seq"); rd_seq.num_transactions = 20; rd_seq.start(env.agt.sqr); #1000; phase.drop_objection(this); `uvm_info(get_type_name(), "Test completed!", UVM_LOW) endtask endclass
4.2 运行仿真

Makefile示例:

# sim/Makefile VCS = vcs UVM_HOME = /eda/uvm-1.1d COMP_FLAGS = -sverilog -ntb_opts uvm -debug_access+all -kdb comp: $(VCS) $(COMP_FLAGS) -f ../filelist.f +define+UVM_REPORT_DISABLE_FILE_LINE run: ./simv +UVM_TESTNAME=axi_wr_rd_test +UVM_VERBOSITY=UVM_MEDIUM clean: rm -rf simv* csrc* *.vpd ucli.key vc_hdrs.h *.log verdiLog

运行命令

cd sim make comp # 编译 make run # 运行仿真

五、进阶技巧与常见问题

5.1 调试技巧

1. 开启UVM详细log

./simv +UVM_VERBOSITY=UVM_DEBUG

2. 使用Verdi/UCLI调试

./simv -verdi # 启动Verdi

3. 定位config_db错误: 如果报错Failed to get virtual interface,检查:

  • set/get的路径是否一致

  • 路径通配符格式是否正确

  • 实例化顺序问题

5.2 常见错误清单
错误现象可能原因解决方案
UVM_FATAL @ get virtual interface路径不匹配检查config_db路径通配符
sequence卡死不执行未调用start_item/finish_item确保sequence body正确
Driver收不到transactionsequencer未连接检查agent的connect_phase
时序违规时钟块使用不当drv_cb/mon_cb区分清楚
编译报错uvm_pkg not foundUVM_HOME未设置检查环境变量和filelist
5.3 下一步扩展

环境搭建完成后,建议按以下顺序扩展功能:

  1. 添加Scoreboard:接收monitor的analysis_port,实现参考模型和自动比较

  2. 增加Coverage:定义covergroup,收集功能覆盖率

  3. 实现Virtual Sequence:协调多个agent的sequence执行

  4. 添加UVM Report处理:自定义日志格式和输出控制

  5. 集成CI/CD:编写自动化回归测试脚本

六、进阶技巧与最佳实践

6.1 UVM验证平台的性能优化

当验证平台规模增大时,以下优化技巧能显著提升仿真效率:

并行度提升:通过配置UVM配置参数提高sequence的并行发送度。在axi_write_seq中可以使用fork-join实现多个transaction的并行传输,减少等待时间。

消息筛选:在调试阶段使用UVM_DEBUG级别,正式回归时切换到UVM_MEDIUMUVM_LOW级别减少日志输出量,可以将仿真时间缩短20-30%。

加速器缓存:对于大规模测试,可以将transaction保存到文件中使用uvm_packer进行序列化,避免重复构建相同的数据。

6.2 常见错误的排查清单

在UVM验证平台搭建过程中,以下问题最常见的几个需要注意:

错误现象根本原因排查方法
编译报不找展开路径+incdir+配置错误检查filelist.f中的目录包含路径
驱动卡死不返回clocking block使用错误确保driver使用drv_cb,不是vif直接
Sequence发送失败sequencer未正确实例化检查agent的connect_phase中的连接
覆盖率收集为0covergroup未采样确保在run_phase中调用sample()
时序违规没有正确等待响应检查所有wait语句和@(边沿触发)

在遇到仿真异常时,建议从顶层逐步向下排查:test → env → agent → driver。先确保组件实例化正确,再检查连接关系,最后检查数据传输。

6.3 学习资源推荐

想要深入学习UVM验证平台开发,以下资源是非常值得参考的:

官方资源:Accellera发布的UVM标准文档是最权威的参考资料,建议先完成UVM 1.1d的基础内容学习,再根据需要了解UVM 1.2的新特性。UVM官方提供的示例代码也是很好的学习素材。

在线课程:Verification Academy提供了系统化的UVM学习路径,从基础概念到高级应用都有详细的视频教程和练习题目,非常适合自学。

书籍推荐:《The UVM Primer》是入门的很好选择,它通俗易懂地介绍了UVM的各个组件和工作流程,特别适合初学者。

示例代码:GitHub上有很多开源的UVM验证平台示例,建议先阅读完整的代码再尝试自己动手实践搭建学习。

总结

本文提供了一个完整的UVM验证平台搭建教程,涵盖了从目录结构设计、组件编码到测试运行的全流程。关键步骤总结:

  1. 合理组织目录:agent/env/seq/test分层明确

  2. 先定义Transaction:数据载体是平台的基础

  3. Interface定义时钟块:分离drv_cb和mon_cb

  4. Driver-Sequencer握手:get_next_item/item_done成对出现

  5. Top TB传递vif:config_db在initial中set

  6. Test启动sequence:通过start方法挂载到sequencer

checklist:搭建完成后,用以下清单进行自检确认:

  • 编译无error,所有package已导入

  • Transaction的do_copy/compare已实现

  • Interface的clocking block正确定义

  • Driver能正确获取sequence发来的transaction

  • 仿真成功结束,有UVM_LOW级别的完成日志

  • 波形中能观察到正确的AXI协议交互

如果你在搭建过程中遇到问题,或对某个组件的实现有疑惑,欢迎在评论区留言交流分享。

参考来源

  1. Accellera, “Universal Verification Methodology (UVM) 1.1d/1.2 Standard”

  2. Verification Academy (Synopsys), “UVM Basics Course”

  3. Doulos, “UVM Adopter Course Training Material”

  4. Cadence, “UVM Connect and TLM Tutorial”

  5. Siemens EDA, “Advanced UVM Verification Techniques”

  6. Ray Salemi, “The UVM Primer”, 2013

  7. AXI Protocol Specification v4, ARM Limited

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 15:33:54

IMU与MCU协同设计:6DoF运动追踪硬件实现

1. 从3D到6DoF&#xff1a;IMU与MCU的硬件协同设计在运动追踪和空间定位领域&#xff0c;6自由度&#xff08;6DoF&#xff09;数据正成为新一代交互设备的核心需求。相比传统的3D空间数据&#xff08;X/Y/Z三轴位置&#xff09;&#xff0c;6DoF增加了俯仰&#xff08;Pitch&a…

作者头像 李华
网站建设 2026/7/2 15:33:13

PCF8591与PIC32MZ2048EFM100的硬件协同设计与同步采样实现

1. PCF8591与PIC32MZ2048EFM100的硬件协同设计 在嵌入式信号处理系统中&#xff0c;ADC&#xff08;模数转换器&#xff09;和DAC&#xff08;数模转换器&#xff09;是连接模拟世界与数字世界的桥梁。PCF8591作为一款经典的8位AD/DA转换芯片&#xff0c;与高性能的PIC32MZ2048…

作者头像 李华
网站建设 2026/7/2 15:31:23

100皇后问题的遗传算法Python实战:从零跑通完整流程

1. 这不是教科书里的遗传算法&#xff0c;而是一次真实跑通100皇后问题的全过程复盘你有没有试过&#xff0c;在深夜盯着一段Python代码&#xff0c;看着它在控制台里一行行输出“fitness: 0.001”、“fitness: 0.002”……然后突然跳到“Woowww, the model could find the sol…

作者头像 李华
网站建设 2026/7/2 15:30:30

中小商家必备AI工具:从买笔到搭流,1人跑通内容工厂

别再迷信单点工具了&#xff01;中小商家必备 AI 工具&#xff1a;从“买笔”转向“搭流”的逻辑拆解 最近&#xff0c;AI 圈又被 Claude 3.5 Sonnet 这类新型智能体模型刷屏了。 作为 Builder&#xff0c;我们看到的不仅仅是模型逻辑能力的又一次跳跃&#xff0c;更是对“个体…

作者头像 李华
网站建设 2026/7/2 15:30:08

学习 深度学习7-VGGNet总结

VGGNet是由牛津大学视觉几何组&#xff08;Visual Geometry Group&#xff09;于2014年提出的经典卷积神经网络模型。相较于此前占据主导地位的AlexNet&#xff0c;VGGNet通过统一使用小尺寸卷积核与模块化的堆叠思想&#xff0c;显著加深了网络结构&#xff0c;参数总计约1.38…

作者头像 李华