文章目录
以下是 UVM(Universal Verification Methodology)的简化实现机制,帮助你理解其核心原理并快速上手:
一、UVM 核心机制的简化理解
1. 组件层次与继承
基础类:uvm_component(所有组件的基类)、uvm_object(事务类的基类)。
关键组件(从简单到复杂):
2. 事务级通信(TLM)
-
简化实现:使用 uvm_analysis_port 和 uvm_analysis_export 实现单向通信。
-
发送方(如 Monitor):
uvm_analysis_port#(my_transaction) ap; // 声明端口
ap.write(tr); // 发送事务
- 接收方(如 Scoreboard):
uvm_analysis_export#(my_transaction) exp; // 声明导出
function void write(my_transaction tr); // 实现回调
// 处理接收到的事务
endfunction
3. Phase 机制
- 核心 Phases(按执行顺序):
- build_phase:创建组件实例(new + uvm_config_db 配置)。
- connect_phase:连接 TLM 端口。
- run_phase:执行激励(如 Sequence 发送事务)。
- 简化使用:在 run_phase 中启动线程或任务。
二、简化 UVM 实现示例,并附有详细注释
1. 事务类(Transaction)
`ifndef MY_TRANSACTION__SV
`define MY_TRANSACTION__SV
class my_transaction extends uvm_sequence_item;
// 定义一个48位宽的随机位字段dmac,用于存储目的MAC地址
rand bit[47:0] dmac;
// 定义一个48位宽的随机位字段smac,用于存储源MAC地址
rand bit[47:0] smac;
// 定义一个16位宽的随机位字段ether_type,用于存储以太网类型
rand bit[15:0] ether_type;
// 定义一个字节类型的数组pload,用于存储负载数据
rand byte pload[];
// 定义一个32位宽的随机位字段crc,用于存储CRC校验码
rand bit[31:0] crc;
// 约束条件
// 约束pload数组的大小,使其在46到1500之间
constraint pload_cons{
pload.size >= 46;
pload.size <= 1500;
}
// 计算CRC校验码的函数
function bit[31:0] calc_crc();
return 32'h0;
endfunction
// 在随机化之后调用的函数,用于计算CRC校验码
function void post_randomize();
crc = calc_crc;
endfunction
// 使用宏定义注册类,并指定字段在UVM的打印和比较操作中的行为
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(dmac, UVM_ALL_ON)
`uvm_field_int(smac, UVM_ALL_ON)
`uvm_field_int(ether_type, UVM_ALL_ON)
`uvm_field_array_int(pload, UVM_ALL_ON)
`uvm_field_int(crc, UVM_ALL_ON)
`uvm_object_utils_end
// 构造函数,用于初始化对象
function new(string name = "my_transaction");
super.new();
endfunction
endclass
`endif
2. 驱动类(Driver)
`ifndef MY_DRIVER__SV
`define MY_DRIVER__SV
class my_driver extends uvm_driver#(my_transaction);
virtual my_if vif;
`uvm_component_utils(my_driver)
function new(string name = "my_driver", uvm_component parent = null);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_driver", "virtual interface must be set for vif!!!")
endfunction
extern task main_phase(uvm_phase phase);
extern task drive_one_pkt(my_transaction tr);
endclass
task my_driver::main_phase(uvm_phase phase);
vif.data <= 8'b0;
vif.valid <= 1'b0;
while(!vif.rst_n)
@(posedge vif.clk);
while(1) begin
seq_item_port.get_next_item(req);
drive_one_pkt(req);
seq_item_port.item_done();
end
endtask
task my_driver::drive_one_pkt(my_transaction tr);
byte unsigned data_q[];
int data_size;
data_size = tr.pack_bytes(data_q) / 8;
//`uvm_info("my_driver", "begin to drive one pkt", UVM_LOW);
repeat(3) @(posedge vif.clk);
for ( int i = 0; i < data_size; i++ ) begin
@(posedge vif.clk);
vif.valid <= 1'b1;
vif.data <= data_q[i];
end
@(posedge vif.clk);
vif.valid <= 1'b0;
//`uvm_info("my_driver", "end drive one pkt", UVM_LOW);
endtask
`endif
3. 监控类(Monitor)
`ifndef MY_MONITOR__SV
`define MY_MONITOR__SV
class my_monitor extends uvm_monitor;
// 定义一个虚拟接口vif,用于与DUT进行通信
virtual my_if vif;
// 定义一个分析端口ap,用于将事务发送到分析器
uvm_analysis_port #(my_transaction) ap;
// 使用宏定义注册类
`uvm_component_utils(my_monitor)
function new(string name = "my_monitor", uvm_component parent = null);
super.new(name, parent);
endfunction
// 构建阶段,用于获取配置信息
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
// 从配置数据库中获取虚拟接口vif
if(!uvm_config_db#(virtual my_if)::get(this, "", "vif", vif))
`uvm_fatal("my_monitor", "virtual interface must be set for vif!!!")
// 创建分析端口
ap = new("ap", this);
endfunction
// 主阶段,用于收集事务
extern task main_phase(uvm_phase phase);
// 收集一个事务
extern task collect_one_pkt(my_transaction tr);
endclass
// 主阶段任务
task my_monitor::main_phase(uvm_phase phase);
// 定义一个事务tr
my_transaction tr;
// 无限循环,收集并发送事务
while(1) begin
tr = new("tr");
collect_one_pkt(tr);
ap.write(tr);
end
endtask
// 收集一个事务任务
task my_monitor::collect_one_pkt(my_transaction tr);
// 定义一个字节类型的队列data_q,用于存储事务数据
byte unsigned data_q[$];
// 定义一个字节类型的数组data_array,用于存储事务数据
byte unsigned data_array[];
// 定义一个8位宽的逻辑类型data,用于存储单个字节的数据
logic [7:0] data;
// 定义一个逻辑类型valid,用于指示数据有效
logic valid = 0;
// 定义一个整数data_size,用于存储事务数据的大小
int data_size;
// 等待数据有效
while(1) begin
@(posedge vif.clk);
if(vif.valid) break;
end
//`uvm_info("my_monitor", "begin to collect one pkt", UVM_LOW);
// 收集数据
while(vif.valid) begin
data_q.push_back(vif.data);
@(posedge vif.clk);
end
// 计算数据大小
data_size = data_q.size();
// 创建数据数组
data_array = new[data_size];
// 将队列中的数据复制到数组中
for ( int i = 0; i < data_size; i++ ) begin
data_array[i] = data_q[i];
end
// 创建负载数据数组
tr.pload = new[data_size - 18]; //da sa, e_type, crc
// 将数据数组解包到事务中
data_size = tr.unpack_bytes(data_array) / 8;
//`uvm_info("my_monitor", "end collect one pkt", UVM_LOW);
endtask
`endif
4. 序列类(Sequence)
`ifndef MY_SEQUENCER__SV
`define MY_SEQUENCER__SV
class my_sequencer extends uvm_sequencer #(my_transaction);
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
`uvm_component_utils(my_sequencer)
endclass
`endif
5. 记分板(Scoreboard)
`ifndef MY_SCOREBOARD__SV
`define MY_SCOREBOARD__SV
class my_scoreboard extends uvm_scoreboard;
// 定义一个事务队列expect_queue,用于存储期望的事务
my_transaction expect_queue[$];
// 定义一个阻塞获取端口exp_port,用于从期望的事务队列中获取事务
uvm_blocking_get_port #(my_transaction) exp_port;
// 定义一个阻塞获取端口act_port,用于从实际的事务队列中获取事务
uvm_blocking_get_port #(my_transaction) act_port;
// 使用宏定义注册类
`uvm_component_utils(my_scoreboard)
// 构造函数
extern function new(string name, uvm_component parent = null);
// 构建阶段函数
extern virtual function void build_phase(uvm_phase phase);
// 主阶段任务
extern virtual task main_phase(uvm_phase phase);
endclass
// 构造函数
function my_scoreboard::new(string name, uvm_component parent = null);
super.new(name, parent);
endfunction
// 构建阶段函数
function void my_scoreboard::build_phase(uvm_phase phase);
super.build_phase(phase);
// 创建阻塞获取端口exp_port
exp_port = new("exp_port", this);
// 创建阻塞获取端口act_port
act_port = new("act_port", this);
endfunction
// 主阶段任务
task my_scoreboard::main_phase(uvm_phase phase);
// 定义两个事务get_expect和get_actual,用于存储从端口获取的事务
my_transaction get_expect, get_actual, tmp_tran;
// 定义一个结果位result,用于存储比较结果
bit result;
super.main_phase(phase);
// 创建两个无限循环,分别从exp_port和act_port获取事务
fork
while (1) begin
exp_port.get(get_expect);
// 将获取的事务加入期望的事务队列
expect_queue.push_back(get_expect);
end
while (1) begin
act_port.get(get_actual);
// 如果期望的事务队列不为空
if(expect_queue.size() > 0) begin
// 从期望的事务队列中取出一个事务
tmp_tran = expect_queue.pop_front();
// 比较获取的事务和期望的事务
result = get_actual.compare(tmp_tran);
if(result) begin
// 如果比较成功,打印信息
`uvm_info("my_scoreboard", "Compare SUCCESSFULLY", UVM_LOW);
end
else begin
// 如果比较失败,打印错误信息
`uvm_error("my_scoreboard", "Compare FAILED");
$display("the expect pkt is");
tmp_tran.print();
$display("the actual pkt is");
get_actual.print();
end
end
else begin
// 如果期望的事务队列为空,打印错误信息
`uvm_error("my_scoreboard", "Received from DUT, while Expect Queue is empty");
$display("the unexpected pkt is");
get_actual.print();
end
end
join
endtask
`endif
6. 环境类(Environment)
`ifndef MY_ENV__SV
`define MY_ENV__SV
class my_env extends uvm_env;
my_agent i_agt;
my_agent o_agt;
my_model mdl;
my_scoreboard scb;
uvm_tlm_analysis_fifo #(my_transaction) agt_scb_fifo;
uvm_tlm_analysis_fifo #(my_transaction) agt_mdl_fifo;
uvm_tlm_analysis_fifo #(my_transaction) mdl_scb_fifo;
function new(string name = "my_env", uvm_component parent);
super.new(name, parent);
endfunction
virtual function void build_phase(uvm_phase phase);
super.build_phase(phase);
i_agt = my_agent::type_id::create("i_agt", this);
o_agt = my_agent::type_id::create("o_agt", this);
i_agt.is_active = UVM_ACTIVE;
o_agt.is_active = UVM_PASSIVE;
mdl = my_model::type_id::create("mdl", this);
scb = my_scoreboard::type_id::create("scb", this);
agt_scb_fifo = new("agt_scb_fifo", this);
agt_mdl_fifo = new("agt_mdl_fifo", this);
mdl_scb_fifo = new("mdl_scb_fifo", this);
endfunction
extern virtual function void connect_phase(uvm_phase phase);
`uvm_component_utils(my_env)
endclass
function void my_env::connect_phase(uvm_phase phase);
super.connect_phase(phase);
i_agt.ap.connect(agt_mdl_fifo.analysis_export);
mdl.port.connect(agt_mdl_fifo.blocking_get_export);
mdl.ap.connect(mdl_scb_fifo.analysis_export);
scb.exp_port.connect(mdl_scb_fifo.blocking_get_export);
o_agt.ap.connect(agt_scb_fifo.analysis_export);
scb.act_port.connect(agt_scb_fifo.blocking_get_export);
endfunction
`endif
7. 测试类(Test)
`ifndef BASE_TEST__SV
`define BASE_TEST__SV
class base_test extends uvm_test;
// 定义一个环境env
my_env env;
// 构造函数
function new(string name = "base_test", uvm_component parent = null);
super.new(name,parent);
endfunction
// 构建阶段函数
extern virtual function void build_phase(uvm_phase phase);
// 报告阶段函数
extern virtual function void report_phase(uvm_phase phase);
// 使用宏定义注册类
`uvm_component_utils(base_test)
endclass
// 构建阶段函数
function void base_test::build_phase(uvm_phase phase);
super.build_phase(phase);
// 创建环境env
env = my_env::type_id::create("env", this);
endfunction
// 报告阶段函数
function void base_test::report_phase(uvm_phase phase);
// 定义一个报告服务器server
uvm_report_server server;
// 定义一个整数err_num,用于存储错误数量
int err_num;
super.report_phase(phase);
// 获取报告服务器
server = get_report_server();
// 获取错误数量
err_num = server.get_severity_count(UVM_ERROR);
// 如果错误数量不为0,打印测试失败信息
if (err_num != 0) begin
$display("TEST CASE FAILED");
end
// 否则,打印测试成功信息
else begin
$display("TEST CASE PASSED");
end
endfunction
`endif
三、简化 UVM 框架的关键技巧
1. 避免复杂特性
- 不使用:TLM 2.0、工厂机制(Factory)、寄存器模型(Register Model)。
- 简化替代:
直接实例化组件(跳过工厂注册)。
使用 uvm_config_db 传递配置参数。
手动连接 TLM 端口(不依赖 uvm_component_utils)。
2. 最小化 UVM 依赖
- 使用 uvm_pkg:😗 代替完整 UVM 库,减少编译时间。
对于简单验证,可自定义基础类(如 my_component)替代 uvm_component。
3. 手动实现关键功能
- Phase 控制:手动调用 build_phase、run_phase 等方法。
- 消息机制:自定义 info、error 函数替代 UVM 宏。
4. 简化测试启动
`timescale 1ns/1ps
`include "uvm_macros.svh"
import uvm_pkg::*;
`include "my_if.sv"
`include "my_transaction.sv"
`include "ip_transaction.sv"
`include "my_sequencer.sv"
`include "my_driver.sv"
`include "my_monitor.sv"
`include "my_agent.sv"
`include "my_model.sv"
`include "my_scoreboard.sv"
`include "my_env.sv"
`include "base_test.sv"
`include "my_case0.sv"
module top_tb;
// 定义时钟信号
reg clk;
// 定义复位信号
reg rst_n;
// 定义接收数据信号
reg[7:0] rxd;
// 定义接收使能信号
reg rx_dv;
// 定义发送数据信号
wire[7:0] txd;
// 定义发送使能信号
wire tx_en;
// 定义输入接口
my_if input_if(clk, rst_n);
// 定义输出接口
my_if output_if(clk, rst_n);
// 实例化DUT
dut my_dut(.clk(clk),
.rst_n(rst_n),
.rxd(input_if.data),
.rx_dv(input_if.valid),
.txd(output_if.data),
.tx_en(output_if.valid));
// 初始化时钟信号
initial begin
clk = 0;
forever begin
#100 clk = ~clk;
end
end
// 初始化复位信号
initial begin
rst_n = 1'b0;
#1000;
rst_n = 1'b1;
end
// 运行测试
initial begin
run_test();
end
// 设置配置数据库
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.drv", "vif", input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.i_agt.mon", "vif", input_if);
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.o_agt.mon", "vif", output_if);
end
endmodule
四、验证流程优化
1. 快速调试技巧
在 Monitor 中添加 $display 打印关键信号。
在 Scoreboard 中添加简单比对逻辑(如检查数据范围)。
2. 覆盖率收集
// 在Monitor中添加覆盖率组
covergroup cg_trans @(posedge vif.clk);
cp_addr : coverpoint vif.addr { bins addr_low = {[0:31]}; bins addr_high = {[32:100]}; }
cp_wr : coverpoint vif.wr;
cr_addr_wr : cross cp_addr, cp_wr;
endgroup
// 在Monitor的run_phase中采样
task run_phase(uvm_phase phase);
cg_trans = new();
forever begin
@(posedge vif.clk);
// 采样事务...
cg_trans.sample(); // 采样覆盖率
end
endtask