3. UVM验证平台骨架搭建
UVM验证平台的骨架搭建,本质上是把SystemVerilog的类继承体系与验证方法学的层次结构对应起来。UVM把验证平台中的每一个功能单元都抽象为uvm_component或uvm_object两类基类的派生类,理解这两条继承链的差异,是搭建骨架的第一步。
uvm_component代表验证平台中"长期存在、有固定层次位置"的部件,例如driver、sequencer、monitor、agent、env、test。它有两个关键特征:一是必须在new时传入name和parent两个参数,从而在UVM的层次树中确定自己的位置;二是它随仿真开始而创建、随仿真结束而销毁,生命周期贯穿整个测试。而uvm_object代表"临时产生、在组件之间传递"的数据对象,例如transaction、sequence_item、sequence。它不需要parent,可以随时new、随时丢弃,支持copy、clone、compare、pack等操作,是激励数据的载体。
下面这张图给出了本节要搭建的完整层次结构,以及transaction在组件之间的流动方向。图中实线框为uvm_component派生类,虚线框为uvm_object派生类,箭头表示数据或句柄的传递关系。
搭建骨架时,推荐按"自底向上"的顺序编写代码:先写transaction,再写driver、monitor、sequencer,然后组装agent,再组装env,最后写test。这样每一步都能立即编译,避免一次性写完大量代码后面对成堆的语法错误。
第一步:定义transaction
transaction是激励的最小单元。它继承自uvm_sequence_item,用rand修饰需要随机化的字段,并用`uvm_object_utils注册到factory。
class my_transaction extends uvm_sequence_item;
rand bit [7:0] addr;
rand bit [31:0] data;
rand bit wr;
`uvm_object_utils_begin(my_transaction)
`uvm_field_int(addr, UVM_ALL_ON)
`uvm_field_int(data, UVM_ALL_ON)
`uvm_field_int(wr, UVM_ALL_ON)
`uvm_object_utils_end
function new(string name = "my_transaction");
super.new(name);
endfunction
endclass
注意transaction的new只有一个name参数,没有parent,这正是uvm_object与uvm_component在构造函数上的根本区别。`uvm_field_int宏让transaction自动获得print、copy、compare等能力,调试时非常有用。
第二步:编写driver与sequencer
driver继承自uvm_driver,内部通过一个uvm_seq_item_port从sequencer获取transaction,再驱动到虚拟接口上。driver的new必须带name和parent。
class my_driver extends uvm_driver #(my_transaction);
`uvm_component_utils(my_driver)
virtual my_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 my_if)::get(this, "", "vif", vif))
`uvm_fatal("DRV", "virtual interface not set")
endfunction
task run_phase(uvm_phase phase);
forever begin
seq_item_port.get_next_item(req);
@(posedge vif.clk);
vif.addr <= req.addr;
vif.data <= req.data;
vif.wr <= req.wr;
seq_item_port.item_done();
end
endtask
endclass
sequencer继承自uvm_sequencer,参数化类型必须与driver一致,否则端口连接会报类型不匹配。sequencer本身几乎不需要写额外代码,它的作用是仲裁多个sequence产生的transaction并交给driver。
第三步:编写monitor与agent
monitor继承自uvm_monitor,负责被动采样DUT接口上的信号,把观察到的行为还原成transaction,再通过analysis_port广播出去。monitor不驱动任何信号,这是它与driver的本质区别。
class my_monitor extends uvm_monitor;
`uvm_component_utils(my_monitor)
virtual my_if vif;
uvm_analysis_port #(my_transaction) ap;
function new(string name, uvm_component parent);
super.new(name, parent);
ap = new("ap", this);
endfunction
task run_phase(uvm_phase phase);
my_transaction tr;
forever begin
@(posedge vif.clk);
if (vif.wr) begin
tr = my_transaction::type_id::create("tr");
tr.addr = vif.addr;
tr.data = vif.data;
tr.wr = vif.wr;
ap.write(tr);
end
end
endtask
endclass
agent把driver、sequencer、monitor封装在一起,通过一个is_active参数决定是否例化driver和sequencer。is_active为UVM_ACTIVE时三者都建,为UVM_PASSIVE时只建monitor,用于纯监测场景。
class my_agent extends uvm_agent;
`uvm_component_utils(my_agent)
my_driver drv;
my_sequencer sqr;
my_monitor mon;
function new(string name, uvm_component parent);
super.new(name, parent);
endfunction
function void build_phase(uvm_phase phase);
super.build_phase(phase);
mon = my_monitor::type_id::create("mon", this);
if (get_is_active() == UVM_ACTIVE) begin
drv = my_driver::type_id::create("drv", this);
sqr = my_sequencer::type_id::create("sqr", this);
end
endfunction
function void connect_phase(uvm_phase phase);
if (get_is_active() == UVM_ACTIVE)
drv.seq_item_port.connect(sqr.seq_item_export);
endfunction
endclass
第四步:组装env与test
env是验证平台的容器,通常例化一个或多个agent、scoreboard、coverage collector。test位于层次树的最顶层,负责配置env、启动sequence。test的run_phase中通过raise_objection/drop_objection控制仿真时长,否则仿真会在0时刻直接结束。
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);
super.build_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.agt.sqr);
phase.drop_objection(this);
endtask
endclass
第五步:在VCS中跑通第一个测试
顶层module中需要例化DUT、例化接口、调用run_test()。run_test()会根据命令行参数+UVM_TESTNAME选择要运行的test类,这是UVM的factory机制在起作用。
module tb_top;
import uvm_pkg::*;
`include "uvm_macros.svh"
`include "my_transaction.sv"
`include "my_driver.sv"
`include "my_sequencer.sv"
`include "my_monitor.sv"
`include "my_agent.sv"
`include "my_env.sv"
`include "my_sequence.sv"
`include "my_test.sv"
reg clk = 0;
always #5 clk = ~clk;
my_if intf(clk);
initial begin
uvm_config_db#(virtual my_if)::set(null, "uvm_test_top.env.agt.*", "vif", intf);
run_test();
end
endmodule
VCS编译与运行命令如下,其中-ntb_opts uvm-1.2指定UVM库版本,+UVM_TESTNAME指定要跑的test:
vcs -full64 -sverilog -ntb_opts uvm-1.2 \
-debug_access+all tb_top.sv -o simv
./simv +UVM_TESTNAME=my_test +UVM_VERBOSITY=UVM_LOW
运行成功的判断依据有三点:一是日志中出现"UVM_INFO ... UVM_TEST_DONE"或"UVM_FATAL"数量为0;二是打印出的UVM层次树中能看到uvm_test_top.env.agt.drv、sqr、mon等节点,说明层次结构正确;三是波形中能看到vif.addr、vif.data在时钟沿发生变化,说明driver确实把transaction驱动到了接口上。如果仿真在0时刻就结束,通常是run_phase中忘记raise_objection;如果报"virtual interface not set",则是config_db的路径字符串与实际层次不匹配,需要检查set的第二个参数是否覆盖了driver的完整路径。