UVM验证平台深度实战:从零构建可复用的AXI Stream验证环境
写在前面:验证工程师的困境
“验证覆盖率到92%了,但流片回来还是出问题”——这是许多验证工程师的痛。问题的根源往往不是测试用例不够多,而是验证架构本身缺乏系统性和可复用性。
本文将以一个工业级AXI Stream数据通路为例,手把手带你构建一套完整的UVM验证平台。你将学到:
-
如何设计符合UVM规约的Agent架构
-
Sequence与Sequencer的解耦策略
-
Scoreboard的自检机制实现
-
覆盖率收集的最佳实践
前置要求:具备SystemVerilog基础,了解UVM基础概念(组件层级、phase机制)。
一、验证架构设计:从需求到蓝图
1.1 DUT(被测设计)分析
我们的测试目标是AXI Stream数据通路模块,其接口定义如下:
module axi_stream_fifo #(
parameter int DATA_WIDTH = 32,
parameter int DEPTH = 16
)(
input logic aclk,
input logic aresetn,
// 输入AXI-Stream slave接口
input logic [DATA_WIDTH-1:0] s_axis_tdata,
input logic s_axis_tvalid,
output logic s_axis_tready,
input logic s_axis_tlast, // 包结束标记
// 输出AXI-Stream master接口
output logic [DATA_WIDTH-1:0] m_axis_tdata,
output logic m_axis_tvalid,
input logic m_axis_tready,
output logic m_axis_tlast
);
// FIFO实现...
endmodule
验证要点:
-
数据完整性:输入数据与输出数据完全一致
-
协议合规性:AXI Stream握手时序约束
-
边界场景:FIFO满/空状态、背压(backpressure)处理
-
性能指标:吞吐率、延迟分布
1.2 UVM Testbench架构
[UVM Testbench拓扑]
┌─────────────────────────┐
│ uvm_top │
└──────────┬──────────────┘
│
┌──────────▼──────────────┐
│ test_base (uvm_test) │
└──────────┬──────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌──────▼──────┐ ┌────────▼────────┐ ┌──────▼──────┐
│ env │ │ vseqr │ │ coverage │
│ (uvm_env) │ │ (virtual seqr) │ │ collector │
└──────┬──────┘ └─────────────────┘ └─────────────┘
│
┌─────┴─────┐
│ │
┌────▼───┐ ┌────▼───┐
│input │ │output │
│agent │ │agent │
└────┬───┘ └────┬───┘
│ │
┌────▼───┐ ┌────▼───┐ ┌─────────────┐
│driver │ │monitor │ │ scoreboard │
│ │ │ │ │ │
└────┬───┘ └────┬───┘ └─────────────┘
│ │ ▲
┌────▼───┐ │ │
│sequencer│ └───────────┘
└─────────┘ (TLM analysis port)
二、构建输入Agent:从Transaction到Driver
2.1 Transaction定义
Transaction是UVM中的基本数据单元,必须继承uvm_sequence_item:
// axi_stream_transaction.sv
class axi_stream_transaction extends uvm_sequence_item;
`uvm_object_utils(axi_stream_transaction)
// 数据字段
rand logic [31:0] data;
rand logic tlast; // 包结束标记
rand int delay_cycles; // 发送前等待周期(模拟真实场景)
// 约束
constraint c_delay { delay_cycles inside {[0:10]}; }
// 常用函数
function new(string name = "axi_stream_transaction");
super.new(name);
endfunction
function string convert2string();
return $sformatf("data=0x%0h tlast=%0b delay=%0d",
data, tlast, delay_cycles);
endfunction
function void do_copy(uvm_object rhs);
axi_stream_transaction rhs_;
if(!$cast(rhs_, rhs)) `uvm_fatal(get_name(), "Cast failed")
super.do_copy(rhs);
data = rhs_.data;
tlast = rhs_.tlast;
delay_cycles = rhs_.delay_cycles;
endfunction
function bit do_compare(uvm_object rhs, uvm_comparer comparer);
axi_stream_transaction rhs_;
if(!$cast(rhs_, rhs)) return 0;
return (super.do_compare(rhs, comparer) &&
data == rhs_.data &&
tlast == rhs_.tlast);
endfunction
endclass
紫霄提示:重写do_copy和do_compare是实现Transaction深拷贝和比较的关键,pack/unpack方法用于序列化场景。
2.2 Sequencer实现
Sequencer是Sequence与Driver之间的协调器,UVM提供了标准基类:
// axi_stream_sequencer.sv
class axi_stream_sequencer extends uvm_sequencer #(axi_stream_transaction);
`uvm_component_utils(axi_stream_sequencer)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
// 可在此添加自定义方法,如配置参数
function void build_phase(uvm_phase phase);
super.build_phase(phase);
`uvm_info(get_name(), "Sequencer build phase done", UVM_MEDIUM)
endfunction
endclass
2.3 Driver实现
Driver负责将Transaction转换为DUT接口信号:
// axi_stream_driver.sv
class axi_stream_driver extends uvm_driver #(axi_stream_transaction);
`uvm_component_utils(axi_stream_driver)
// 虚拟接口
virtual interface axi_stream_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_stream_if)::get(
this, "", "vif", vif)) begin
`uvm_fatal(get_name(), "Virtual interface not found")
end
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req); // 从sequencer获取事务
drive_item(req);
seq_item_port.item_done(); // 通知完成
end
endtask
task drive_item(axi_stream_transaction tr);
// 模拟发送前延迟
repeat(tr.delay_cycles) @(posedge vif.aclk);
// 驱动信号
@(posedge vif.aclk);
vif.s_axis_tdata <= tr.data;
vif.s_axis_tvalid <= 1'b1;
vif.s_axis_tlast <= tr.tlast;
// 等待握手完成(tvalid && tready)
do begin
@(posedge vif.aclk);
end while(!(vif.s_axis_tvalid && vif.s_axis_tready));
// 解复位
vif.s_axis_tvalid <= 1'b0;
`uvm_info(get_name(), $sformatf("Driven: %s", tr.convert2string()), UVM_HIGH)
endtask
endclass
2.4 Monitor实现
Monitor从DUT接口采样数据,不驱动信号,独立于Driver运行:
// axi_stream_monitor.sv
class axi_stream_monitor extends uvm_monitor;
`uvm_component_utils(axi_stream_monitor)
virtual interface axi_stream_if vif;
// Analysis Port:向Scoreboard广播采样到的Transaction
uvm_analysis_port #(axi_stream_transaction) item_collected_port;
function new(string name, uvm_component parent);
super.new(name, parent);
item_collected_port = new("item_collected_port", this);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual axi_stream_if)::get(
this, "", "vif", vif))
`uvm_fatal(get_name(), "Virtual interface not found")
endfunction
task run_phase(uvm_phase phase);
axi_stream_transaction tr;
forever begin
@(posedge vif.aclk);
// 检测有效传输(valid && ready)
if(vif.s_axis_tvalid && vif.s_axis_tready) begin
tr = axi_stream_transaction::type_id::create("tr");
tr.data = vif.s_axis_tdata;
tr.tlast = vif.s_axis_tlast;
item_collected_port.write(tr); // 广播
`uvm_info(get_name(), $sformatf("Monitored: %s",
tr.convert2string()), UVM_HIGH)
end
end
endtask
endclass
2.5 Agent封装
Agent将Sequencer、Driver、Monitor封装为可复用单元:
// axi_stream_agent.sv
class axi_stream_agent extends uvm_agent;
`uvm_component_utils(axi_stream_agent)
axi_stream_sequencer sqr;
axi_stream_driver drv;
axi_stream_monitor mon;
// 配置参数
uvm_active_passive_enum is_active = UVM_ACTIVE;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = axi_stream_monitor::type_id::create("mon", this);
if(is_active == UVM_ACTIVE) begin
sqr = axi_stream_sequencer::type_id::create("sqr", this);
drv = axi_stream_driver::type_id::create("drv", this);
end
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(is_active == UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export);
end
endfunction
endclass
三、Sequence编写:测试向量的艺术
3.1 基础Sequence
// axi_stream_base_sequence.sv
class axi_stream_base_sequence extends uvm_sequence #(axi_stream_transaction);
`uvm_object_utils(axi_stream_base_sequence)
rand int num_transactions = 100; // 默认发送100个事务
function new(string name = "axi_stream_base_sequence");
super.new(name);
endfunction
task body();
axi_stream_transaction tr;
repeat(num_transactions) begin
tr = axi_stream_transaction::type_id::create("tr");
start_item(tr);
// 可在预处理阶段修改约束
if(!tr.randomize()) `uvm_error(get_name(), "Randomize failed")
finish_item(tr);
end
endtask
endclass
3.2 带TLAST的突发传输Sequence
// axi_stream_burst_sequence.sv
class axi_stream_burst_sequence extends axi_stream_base_sequence;
`uvm_object_utils(axi_stream_burst_sequence)
rand int burst_length = 8; // 突发长度
function new(string name = "axi_stream_burst_sequence");
super.new(name);
endfunction
task body();
axi_stream_transaction tr;
for(int i = 0; i < burst_length; i++) begin
tr = axi_stream_transaction::type_id::create("tr");
start_item(tr);
// 约束:最后一个事务tlast=1
if(!tr.randomize() with {
tlast == (i == burst_length - 1);
data == i; // 可预测的数据模式
}) `uvm_error(get_name(), "Randomize failed")
finish_item(tr);
end
endtask
endclass
3.3 性能压力Sequence
// axi_stream_stress_sequence.sv
class axi_stream_stress_sequence extends axi_stream_base_sequence;
`uvm_object_utils(axi_stream_stress_sequence)
// 零延迟背靠背传输
constraint c_no_delay { num_transactions == 1000; }
task body();
axi_stream_transaction tr;
for(int i = 0; i < num_transactions; i++) begin
tr = axi_stream_transaction::type_id::create("tr");
start_item(tr);
if(!tr.randomize() with {
delay_cycles == 0; // 无延迟
tlast == (i % 10 == 9); // 每10个一组
}) `uvm_error(get_name(), "Randomize failed")
finish_item(tr);
end
endtask
endclass
四、Scoreboard自检机制
4.1 参考模型与比较器
Scoreboard接收输入和输出Transaction,进行端到端校验:
// axi_stream_scoreboard.sv
class axi_stream_scoreboard extends uvm_scoreboard;
`uvm_component_utils(axi_stream_scoreboard)
// TLM Analysis FIFO:缓存Monitor发送的Transaction
uvm_tlm_analysis_fifo #(axi_stream_transaction) input_fifo;
uvm_tlm_analysis_fifo #(axi_stream_transaction) output_fifo;
// 统计
int compare_count = 0;
int error_count = 0;
function new(string name, uvm_component parent);
super.new(name, parent);
input_fifo = new("input_fifo", this);
output_fifo = new("output_fifo", this);
endfunction
task run_phase(uvm_phase phase);
axi_stream_transaction in_tr, out_tr;
forever begin
// 阻塞等待两边都有数据
fork
input_fifo.get(in_tr);
output_fifo.get(out_tr);
join
// 比较
if(!in_tr.compare(out_tr)) begin
`uvm_error(get_name(), $sformatf(
"Mismatch!\nInput: %s\nOutput: %s",
in_tr.convert2string(), out_tr.convert2string()))
error_count++;
end else begin
`uvm_info(get_name(), $sformatf("Match: %s",
in_tr.convert2string()), UVM_MEDIUM)
end
compare_count++;
end
endtask
function void report_phase(uvm_phase phase);
`uvm_info(get_name(), $sformatf(
"Scoreboard Report:\n Compared: %0d\n Errors: %0d\n Pass Rate: %0.2f%%",
compare_count, error_count,
100.0*(compare_count-error_count)/compare_count), UVM_LOW)
endfunction
endclass
4.2 异步FIFO模型
如果DUT内部逻辑复杂,可在Scoreboard中嵌入参考模型:
// 简化FIFO模型(仅示意)
class ref_fifo #(int DEPTH = 16) extends uvm_component;
// 内部队列模拟FIFO行为
axi_stream_transaction queue[$];
function bit write(axi_stream_transaction tr);
if(queue.size() >= DEPTH) return 0; // FIFO满
queue.push_back(tr);
return 1;
endfunction
function bit read(output axi_stream_transaction tr);
if(queue.size() == 0) return 0; // FIFO空
tr = queue.pop_front();
return 1;
endfunction
endclass
五、覆盖率收集
5.1 Covergroup定义
// coverage_collector.sv
class coverage_collector extends uvm_component;
`uvm_component_utils(coverage_collector)
// 接收Monitor的Transaction
uvm_analysis_imp #(axi_stream_transaction, coverage_collector) analysis_export;
// Covergroup定义
covergroup axi_stream_cg with function sample(axi_stream_transaction tr);
// 数据值覆盖(分bins)
data_cp: coverpoint tr.data {
bins zeros = {0};
bins low = {[1:100]};
bins mid = {[101:1000]};
bins high = {[1001:$]};
bins all_ones = {32'hFFFFFFFF};
}
// 延迟覆盖
delay_cp: coverpoint tr.delay_cycles {
bins immediate = {0};
bins short = {[1:5]};
bins long = {[6:10]};
}
// tlast交叉覆盖
tlast_cp: coverpoint tr.tlast {
bins asserted = {1};
bins deasserted = {0};
}
// 交叉覆盖:数据类型 x tlast
data_x_tlast: cross data_cp, tlast_cp;
endgroup
function new(string name, uvm_component parent);
super.new(name, parent);
axi_stream_cg = new();
analysis_export = new("analysis_export", this);
endfunction
// Write方法:Monitor调用通知
function void write(axi_stream_transaction tr);
axi_stream_cg.sample(tr);
endfunction
function void report_phase(uvm_phase phase);
`uvm_info(get_name(), $sformatf("Coverage: %0.2f%%",
axi_stream_cg.get_coverage()), UVM_LOW)
endfunction
endclass
六、Test整合
6.1 Environment封装
// testbench_env.sv
class testbench_env extends uvm_env;
`uvm_component_utils(testbench_env)
axi_stream_agent input_agent; // ACTIVE模式(驱动输入)
axi_stream_agent output_agent; // PASSIVE模式(仅监控)
axi_stream_scoreboard scb;
coverage_collector cov;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
input_agent = axi_stream_agent::type_id::create("input_agent", this);
input_agent.is_active = UVM_ACTIVE;
output_agent = axi_stream_agent::type_id::create("output_agent", this);
output_agent.is_active = UVM_PASSIVE;
scb = axi_stream_scoreboard::type_id::create("scb", this);
cov = coverage_collector::type_id::create("cov", this);
endfunction
function void connect_phase(uvm_phase phase);
super.connect_phase(phase);
// 连接Monitor到Scoreboard
input_agent.mon.item_collected_port.connect(scb.input_fifo.analysis_export);
output_agent.mon.item_collected_port.connect(scb.output_fifo.analysis_export);
// 连接Coverage
input_agent.mon.item_collected_port.connect(cov.analysis_export);
endfunction
endclass
6.2 Base Test
// test_base.sv
class test_base extends uvm_test;
`uvm_component_utils(test_base)
testbench_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
env = testbench_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
axi_stream_base_sequence seq;
phase.raise_objection(this); // 阻止phase结束
seq = axi_stream_base_sequence::type_id::create("seq");
seq.start(env.input_agent.sqr);
phase.drop_objection(this); // 允许phase结束
endtask
endclass
6.3 特定测试(并发背压场景)
// test_backpressure.sv
class test_backpressure extends test_base;
`uvm_component_utils(test_backpressure)
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
task run_phase(uvm_phase phase);
// 并行启动:输入侧全速发送 + 输出侧随机反压
phase.raise_objection(this);
fork
begin
axi_stream_stress_sequence seq;
seq = axi_stream_stress_sequence::type_id::create("seq");
seq.start(env.input_agent.sqr);
end
begin
// 随机控制输出tready
repeat(1000) begin
env.output_agent.vif.m_axis_tready <= $urandom_range(0,1);
@(posedge env.output_agent.vif.aclk);
end
end
join
phase.drop_objection(this);
endtask
endclass
七、仿真与调试技巧
7.1 Makefile示例
# UVM Makefile示例
SIMULATOR = vcs # 或xrun, vsim
UVM_HOME = /path/to/uvm
all: comp run
comp:
$(SIMULATOR) -full64 -sverilog -ntb_opts uvm \
+incdir+$(UVM_HOME)/src \
-f filelist.f \
-top test_base
run:
./simv +UVM_TESTNAME=test_backpressure +UVM_VERBOSITY=UVM_MEDIUM
clean:
rm -rf csrc simv* vc_hdrs.h ucli.key
7.2 调试技巧
-
波形优化:使用
$wlfdumpvars()按需记录,避免波形过大 -
Transaction Debug:在Transaction中添加
print()函数,日志输出友好格式 -
Phase调试:使用
+UVM_PHASE_TRACE追踪phase执行 -
超时保护:在build_phase中设置
uvm_top.set_timeout(1ms)防止死锁
总结
本文完整展示了从底层Transaction到顶层Test的UVM平台构建过程。关键要点:
-
标准化接口:Agent封装确保复用性,通过
is_active参数灵活切换模式 -
解耦通信:TLM Analysis Port实现Monitor→Scoreboard/Coverage的无耦合连接
-
层次化Sequence:继承体系支持从基础测试到压力测试的快速扩展
-
自动化校验:Scoreboard参考模型确保功能正确性,覆盖率收集量化验证完备性
下一步建议:
-
使用UVM Register Model添加寄存器配置验证
-
引入形式验证(JasperGold)检查AXI协议断言
-
集成CI/CD(Jenkins + Verilator)实现回归测试自动化
更多推荐

所有评论(0)