一、UVM世界初探:验证方法学演进、UVM核心思想、第一个UVM验证平台搭建
1.1 从定向测试到随机约束:验证方法学这二十年
做芯片验证十几年了,我亲眼见证了验证方法学的几次大变革。早年间大家写Verilog testbench,说白了就是「给信号、看波形」。那时候的验证,基本靠工程师手动敲测试向量,一个模块几百个case,敲到怀疑人生。
后来出现了定向测试方法学,比如e语言和Vera。它们引入了随机化和覆盖率的概念。嗯,这是个巨大的进步。但问题是,各家工具不互通,换个平台就得重写验证环境,太痛苦了。
直到2008年左右,Accellera组织推出了UVM(Universal Verification Methodology)。它的核心思想其实很简单:把验证环境标准化、组件化、可复用。你想想看,如果所有公司都用同一套框架写验证环境,那IP复用、团队协作的效率能提升多少?
我个人习惯把UVM比作「验证界的乐高」。每个组件(driver、monitor、scoreboard)都是标准积木块,你只需要按照规则把它们拼起来,就能搭出一个完整的验证平台。我在项目中遇到过不少团队,一开始觉得UVM学习曲线陡,但一旦上手,后期维护和迭代的效率提升是肉眼可见的。
1.2 UVM核心思想:你只需要记住这三件事
很多初学者一上来就被UVM的类库结构吓到了——factory、sequence、driver、monitor……几十个类,看着就头大。其实你只需要抓住三个核心思想,其他的都是锦上添花。
1.2.1 基于组件的层次化结构
UVM把验证环境拆成一个个组件,每个组件有明确的职责。比如:
- driver:负责把激励信号驱动到DUT接口上
- monitor:负责监视接口上的信号变化
- scoreboard:负责比对期望值和实际值
- agent:把driver和monitor打包成一个可复用的单元
这种分层设计的好处是——你改driver的逻辑,不会影响到monitor和scoreboard。我曾经在一个项目中,因为DUT接口时序变了,只需要改agent里的driver代码,其他组件完全不动,两天就搞定了。要是放在以前那种大杂烩式的testbench里,估计得重写一半。
1.2.2 基于factory的灵活创建
UVM的factory机制,说白了就是「你告诉工厂你要什么类型的对象,工厂帮你造出来」。这样做的好处是:你可以在不修改原有代码的情况下,替换掉某个组件的行为。
举个例子:你有一个标准的driver,但某个特殊测试用例需要driver在发送数据时插入错误。你只需要写一个扩展类,然后用factory的override机制替换掉原来的driver。测试结束后,再换回来。整个过程不需要动一行原有代码。
1.2.3 基于sequence的激励生成
UVM把「激励生成」和「激励驱动」分开了。sequence负责生成激励数据,driver负责把数据驱动到接口上。这样做的最大好处是:激励生成逻辑可以独立复用。
你想想看,如果你有10个测试用例,每个用例都需要发送同样的数据包,只是包的某些字段不同。在传统方法里,你可能要写10个不同的driver。但在UVM里,你只需要写一个sequence基类,然后派生出10个子类,每个子类只修改需要变化的字段。代码量减少80%,维护成本直线下降。
1.3 第一个UVM验证平台:从零开始搭一个
光说不练假把式。我们来搭一个最简单的UVM验证平台。这个平台只有一个DUT——一个简单的加法器。别小看它,麻雀虽小五脏俱全。
1.3.1 平台结构图
先看看我们要搭的平台长什么样:
1.3.2 代码实现:最简单的UVM testbench
好,我们开始写代码。先从最顶层的test开始:
// 文件:test.sv
class my_test extends uvm_test;
`uvm_component_utils(my_test)
my_env env;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
env = my_env::type_id::create("env", this);
endfunction
task run_phase(uvm_phase phase);
my_sequence seq;
phase.raise_objection(this);
seq = my_sequence::type_id::create("seq");
seq.start(env.agent.sequencer);
#100;
phase.drop_objection(this);
endtask
endclass
然后是env层:
// 文件:env.sv
class my_env extends uvm_env;
`uvm_component_utils(my_env)
my_agent agent;
my_scoreboard sb;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
agent = my_agent::type_id::create("agent", this);
sb = my_scoreboard::type_id::create("sb", this);
endfunction
function void connect_phase(uvm_phase phase);
agent.monitor.ap.connect(sb.analysis_export);
endfunction
endclass
agent层把driver和monitor打包在一起:
// 文件:agent.sv
class my_agent extends uvm_agent;
`uvm_component_utils(my_agent)
my_driver driver;
my_monitor monitor;
uvm_sequencer#(my_transaction) sequencer;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
driver = my_driver::type_id::create("driver", this);
monitor = my_monitor::type_id::create("monitor", this);
sequencer = uvm_sequencer#(my_transaction)::type_id::create("sequencer", this);
endfunction
function void connect_phase(uvm_phase phase);
driver.seq_item_port.connect(sequencer.seq_item_export);
endfunction
endclass
最后是driver和monitor:
// 文件:driver.sv
class my_driver extends uvm_driver#(my_transaction);
`uvm_component_utils(my_driver)
virtual adder_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
if(!uvm_config_db#(virtual adder_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);
vif.a <= req.a;
vif.b <= req.b;
vif.valid <= 1;
@(posedge vif.clk);
seq_item_port.item_done();
end
endtask
endclass
// 文件:monitor.sv
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
uvm_analysis_port#(my_transaction) ap;
virtual adder_if vif;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
ap = new("ap", this);
if(!uvm_config_db#(virtual adder_if)::get(this, "", "vif", vif))
`uvm_fatal("NOVIF", "virtual interface not set")
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
forever begin
@(posedge vif.clk);
if(vif.valid) begin
tr = my_transaction::type_id::create("tr");
tr.a = vif.a;
tr.b = vif.b;
tr.sum = vif.sum;
ap.write(tr);
end
end
endtask
endclass
1.4 运行你的第一个UVM测试
代码写完了,怎么跑起来?你需要一个top模块来实例化DUT和接口:
// 文件:top.sv
module top;
logic clk;
logic rst_n;
adder_if vif(clk, rst_n);
adder dut(.clk(clk), .rst_n(rst_n), .a(vif.a), .b(vif.b),
.valid(vif.valid), .sum(vif.sum));
initial begin
clk = 0;
forever #5 clk = ~clk;
end
initial begin
uvm_config_db#(virtual adder_if)::set(null, "uvm_test_top.env.agent.driver", "vif", vif);
uvm_config_db#(virtual adder_if)::set(null, "uvm_test_top.env.agent.monitor", "vif", vif);
run_test("my_test");
end
endmodule
然后在命令行执行:
vlog -sv *.sv
vsim -c -do "run -all; quit" top
如果一切顺利,你会看到UVM打印出仿真开始和结束的信息。虽然这个平台什么都没做,但它已经是一个完整的UVM验证环境了。
1.5 本章小结
这一章我们聊了验证方法学的演进,从定向测试到UVM的标准化。UVM的核心思想其实就三个:组件化分层、factory灵活创建、sequence与driver分离。最后我们亲手搭了一个最简单的UVM验证平台,虽然功能简单,但结构完整。
记住:UVM不是背出来的,是用出来的。下一章我们会深入UVM的组件家族,看看每个组件到底该怎么用、什么时候用。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。
可可精品资料库