刚开始接触UVM验证方法学时,很多工程师都会感到无从下手。我当年也是这样,看着一堆陌生的术语和复杂的类库,完全不知道该怎么开始。直到我的导师告诉我:"从加法器开始练手吧,这是最简单的DUT(被测设计),但包含了UVM验证的所有核心要素。"
加法器虽然简单,但它完美展示了UVM验证平台的关键组件:接口信号需要驱动和监控,计算结果需要比对验证,测试场景需要灵活配置。更重要的是,加法器的功能明确,我们可以把精力集中在验证方法本身,而不是纠结于复杂的设计逻辑。
记得我第一次搭建加法器验证平台时,花了整整三天才让第一个测试用例通过。但当看到终端打印出"Comparator Match"的那一刻,那种成就感至今难忘。这就是为什么我建议所有UVM新手都从加法器开始——它足够简单,能让你快速获得正向反馈;又足够完整,能让你掌握UVM的核心工作流程。
在开始编码前,我们需要准备好开发环境。主流的选择有VCS+Veridi或者QuestaSim,我个人更推荐VCS,因为它在工业界应用更广泛。安装完成后,记得设置以下环境变量:
bash复制export VCS_HOME=/path/to/vcs
export PATH=$VCS_HOME/bin:$PATH
export UVM_HOME=$VCS_HOME/etc/uvm
对于小型项目,一个简单的Makefile就能管理整个工作流程。下面是我常用的模板:
makefile复制COMPILE_OPTIONS = -full64 -sverilog -ntb_opts uvm -debug_access+all
SIM_OPTIONS = +UVM_TESTNAME=simple_test +UVM_VERBOSITY=HIGH
all: compile simulate
compile:
vcs $(COMPILE_OPTIONS) top.sv -l compile.log
simulate:
./simv $(SIM_OPTIONS) -l simulate.log
清晰的目录结构能大幅提升工作效率。这是我的推荐结构:
code复制/project_root
/src # DUT源代码
/tb # 测试平台代码
/env # UVM环境组件
/tests # 测试用例
/seq_lib # 序列库
/sim # 仿真脚本和Makefile
/doc # 文档
特别提醒:在tb目录下,建议按照UVM组件类型创建子目录,比如agents、scoreboards等。这样当项目规模扩大时,代码仍然能保持良好组织。
加法器的接口相对简单,但定义时仍需考虑周全。下面是一个经过优化的接口定义:
systemverilog复制interface adder_if(input logic clk, rst);
// 输入信号
logic [31:0] operand_a;
logic [31:0] operand_b;
logic valid_in;
logic ready_in;
// 输出信号
logic [31:0] result;
logic valid_out;
logic ready_out;
// 主设备端modport
modport master(
input clk, rst, ready_in,
output operand_a, operand_b, valid_in
);
// 从设备端modport
modport slave(
input clk, rst, valid_out,
output ready_out
);
endinterface
这个接口定义有几个优化点:
事务类是UVM验证平台的数据载体。对于加法器,我们需要定义输入和输出两种事务:
systemverilog复制class adder_input extends uvm_sequence_item;
rand int unsigned operand_a;
rand int unsigned operand_b;
rand int delay;
`uvm_object_utils_begin(adder_input)
`uvm_field_int(operand_a, UVM_ALL_ON)
`uvm_field_int(operand_b, UVM_ALL_ON)
`uvm_field_int(delay, UVM_ALL_ON)
`uvm_object_utils_end
constraint valid_range {
operand_a inside {[0:100]};
operand_b inside {[0:100]};
delay inside {[0:5]};
}
function new(string name = "adder_input");
super.new(name);
endfunction
endclass
输出事务相对简单,但需要特别注意比较方法的实现:
systemverilog复制class adder_output extends uvm_sequence_item;
int unsigned result;
`uvm_object_utils_begin(adder_output)
`uvm_field_int(result, UVM_ALL_ON)
`uvm_object_utils_end
function bit compare(adder_output rhs);
return (this.result == rhs.result);
endfunction
endclass
驱动器(Driver)负责将事务级数据转换为信号级激励。以下是加法器驱动器的关键代码:
systemverilog复制class adder_driver extends uvm_driver #(adder_input);
virtual adder_if vif;
`uvm_component_utils(adder_driver)
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(adder_input tr);
@(posedge vif.clk);
vif.operand_a <= tr.operand_a;
vif.operand_b <= tr.operand_b;
vif.valid_in <= 1'b1;
wait(vif.ready_in == 1'b1);
@(posedge vif.clk);
vif.valid_in <= 1'b0;
endtask
endclass
监视器(Monitor)则执行相反的工作,将信号转换回事务:
systemverilog复制class adder_monitor extends uvm_monitor;
virtual adder_if vif;
uvm_analysis_port #(adder_output) ap;
`uvm_component_utils(adder_monitor)
task run_phase(uvm_phase phase);
forever begin
adder_output tr = adder_output::type_id::create("tr");
@(posedge vif.clk iff vif.valid_out && vif.ready_out);
tr.result = vif.result;
ap.write(tr);
end
endtask
endclass
参考模型是验证平台的金标准。对于加法器,我们可以直接用SystemVerilog实现:
systemverilog复制class adder_reference extends uvm_component;
uvm_analysis_imp #(adder_input, adder_reference) input_imp;
uvm_analysis_port #(adder_output) output_ap;
`uvm_component_utils(adder_reference)
function void write(adder_input tr);
adder_output out_tr = adder_output::type_id::create("out_tr");
out_tr.result = tr.operand_a + tr.operand_b;
output_ap.write(out_tr);
endfunction
endclass
记分板负责比较DUT输出和参考模型:
systemverilog复制class adder_scoreboard extends uvm_scoreboard;
uvm_analysis_imp #(adder_output, adder_scoreboard) dut_imp;
uvm_analysis_imp #(adder_output, adder_scoreboard) ref_imp;
adder_output ref_queue[$];
int match_count, mismatch_count;
`uvm_component_utils(adder_scoreboard)
function void write(adder_output tr);
// 处理参考模型输出
if(tr.get_name() == "ref_output") begin
ref_queue.push_back(tr);
end
// 处理DUT输出
else begin
if(ref_queue.size() == 0) begin
`uvm_error("SCBD", "Unexpected DUT output")
return;
end
adder_output ref_tr = ref_queue.pop_front();
if(!tr.compare(ref_tr)) begin
`uvm_error("SCBD", $sformatf("Mismatch! DUT:%0d REF:%0d",
tr.result, ref_tr.result))
mismatch_count++;
end else begin
`uvm_info("SCBD", "Match!", UVM_LOW)
match_count++;
end
end
endfunction
endclass
序列(Sequence)是测试场景的具体实现。我们先创建一个基础序列:
systemverilog复制class basic_sequence extends uvm_sequence #(adder_input);
`uvm_object_utils(basic_sequence)
task body();
adder_input tr;
repeat(50) begin
tr = adder_input::type_id::create("tr");
start_item(tr);
assert(tr.randomize());
finish_item(tr);
end
endtask
endclass
进阶的边界测试序列可以这样实现:
systemverilog复制class edge_case_sequence extends basic_sequence;
`uvm_object_utils(edge_case_sequence)
task body();
adder_input tr;
// 测试0+0
tr = adder_input::type_id::create("tr");
start_item(tr);
tr.operand_a = 0;
tr.operand_b = 0;
finish_item(tr);
// 测试最大值相加
tr = adder_input::type_id::create("tr");
start_item(tr);
tr.operand_a = 32'hFFFF_FFFF;
tr.operand_b = 32'hFFFF_FFFF;
finish_item(tr);
endtask
endclass
在搭建加法器验证平台时,我遇到过几个典型问题:
systemverilog复制task reset_phase(uvm_phase phase);
vif.operand_a <= 0;
vif.operand_b <= 0;
vif.valid_in <= 0;
@(posedge vif.rst);
endtask
事务比对失败:确保参考模型和DUT使用相同的算法。曾经因为参考模型忽略了进位导致误报。
死锁问题:当ready/valid握手协议实现不当时,经常会出现仿真挂起。可以通过超时机制检测:
systemverilog复制task run_phase(uvm_phase phase);
fork
drive_transactions();
begin
#100ns;
`uvm_error("TIMEOUT", "Driver stuck waiting for ready")
end
join_any
disable fork;
endtask
makefile复制COVERAGE_OPTIONS = -cm line+cond+fsm+tgl+branch -cm_dir ./coverage
使用urg工具生成覆盖率报告:
bash复制urg -dir coverage.vdb -report coverage_report