现在,UVM的大致框架已经搭建起来了。之后要做的内容,就是往框架里面填充相应的内容,来真正实现对DUT的功能验证。
话说真的需要两块屏幕,一块屏幕的话看文档也太不方便了。
一、完善interface
在之前的文件中,只对进行了信号的定义。这里设定了两个时钟块对信号的方向进行规定。时钟块能保证testbench更精准地驱动和采样信号,来消除竞争问题。
点击查看代码
`ifndef AMP_INTERFACE_SV
`define AMP_INTERFACE_SV
interface amp_interface(input bit clk_i, bit rstn_i);
logic wr_en_i;
logic set_scaler_i;
logic [15:0] wr_data_i;
logic rd_val_o;
logic [31:0] rd_data_o;
logic [15:0] scaler_o;
clocking ck_drv @(posedge clk_i);
default input #1ps output #1ps;
output wr_en_i, set_scaler_i, wr_data_i;
input rd_val_o, rd_data_o, scaler_o;
endclocking : ck_drv
clocking ck_mon @(posedge clk_i);
default input #1ps output #1ps;
input wr_en_i, set_scaler_i, wr_data_i, rd_val_o, rd_data_o, scaler_o;
endclocking : ck_mon
endinterface : amp_interface
`endif // AMP_INTERFACE_SV
二、完善激励
2.1 transaction
transaction作为验证的“子弹”,其决定着激励的内容。transaction中的变量和DUT所需的变量大致相同,但又不必完全一样,因为有driver会对数据进行转换。
点击查看代码
`ifndef AMP_SEQ_ITEM_SV
`define AMP_SEQ_ITEM_SV
class amp_seq_item extends uvm_sequence_item;
typedef enum{IDLE, SET_SCALER, SET_BASE_NUMBER} trans_type;
rand trans_type ttype;
rand bit [7:0] no;
rand bit [7:0] base_number;
rand bit [15:0] scaler;
rand int delay;
bit [15:0] rd_scaler;
bit [31:0] rd_data;
bit rd_valid;
// Constraints
constraint delay_bounds {
soft delay dist {0:=50, 1:=25, 2:=25};
soft base_number inside {[1:50]};
soft scaler dist {1:/50, [2:5]:/50};
}
`uvm_object_utils_begin(amp_seq_item)
`uvm_field_enum (trans_type, ttype, UVM_ALL_ON)
`uvm_field_int (no, UVM_ALL_ON)
`uvm_field_int (base_number, UVM_ALL_ON)
`uvm_field_int (scaler, UVM_ALL_ON)
`uvm_field_int (delay, UVM_ALL_ON)
`uvm_field_int (rd_scaler, UVM_ALL_ON)
`uvm_field_int (rd_data, UVM_ALL_ON)
`uvm_field_int (rd_valid, UVM_ALL_ON)
`uvm_object_utils_end
extern function new(string name = "amp_seq_item");
endclass : amp_seq_item
function amp_seq_item::new(string name = "amp_seq_item");
super.new(name);
`uvm_info(get_type_name(), "created", UVM_HIGH)
endfunction : new
`endif // AMP_SEQ_ITEM_SV
在这里,设定了一个枚举变量trans_type,其含有三个元素:IDLE、SET_SCALER和SET_BASE_NUMBER。枚举变量是根据DUT的功能设定的。对amplifier而言,可以分成三个操作:设定基数(base_number)、设定放大倍数(scaler)、空闲一段时间(idle)。枚举变量的设置与这三个操作一一对应。
2.2 sequence
sequence的目的在于产生一定数目的transaction对象,并对其内容进行约束,根据sequence的作用不同,约束的条件也不同。在上一节已经知道,该DUT可分出三个操作,因此sequence也可分别由三个子sequence组成。
2.2.1 sequence(idle)
在agent文件夹下创建一个名为amp_seq_idle.sv的文件,主要用来约束transaction中的变量delay,根据delay来控制延时长度。
点击查看代码
`ifndef AMP_SEQ_IDLE_SV
`define AMP_SEQ_IDLE_SV
class amp_seq_idle extends uvm_sequence #(amp_seq_item);
`uvm_object_utils(amp_seq_idle)
rand int delay;
extern function new(string name = "amp_seq_idle");
extern virtual task body();
endclass : amp_seq_idle
function amp_seq_idle::new(string name = "amp_seq_idle");
super.new(name);
`uvm_info(get_type_name(), "created", UVM_HIGH)
endfunction : new
task amp_seq_idle::body();
`uvm_do_with(req, {
ttype == amp_seq_item::IDLE;
no == 0;
delay == local::delay;
})
get_response(rsp);
endtask : body
`endif // AMP_SEQ_IDLE_SV
2.2.2 sequence(set_scaler)
在agent文件夹下创建一个名为amp_seq_set_scaler.sv的文件,主要用来约束transaction中的变量scaler,将该scaler的值作为amplifier的放大倍数。
点击查看代码
`ifndef AMP_SEQ_SET_SCALER_SV
`define AMP_SEQ_SET_SCALER_SV
class amp_seq_set_scaler extends uvm_sequence #(amp_seq_item);
`uvm_object_utils(amp_seq_set_scaler)
rand bit [15:0] scaler;
extern function new(string name = "amp_seq_set_scaler");
extern virtual task body();
endclass : amp_seq_set_scaler
function amp_seq_set_scaler::new(string name = "amp_seq_set_scaler");
super.new(name);
`uvm_info(get_type_name(), "created", UVM_HIGH)
endfunction : new
task amp_seq_set_scaler::body();
`uvm_do_with(req, {
ttype == amp_seq_item::SET_SCALER;
no == 0;
base_number == 0;
scaler == local::scaler;
delay == 0;
})
get_response(rsp);
endtask : body
`endif // AMP_SEQ_SET_SCALER_SV
2.2.3 sequence(set_base_number)
在agent文件夹下创建一个名为amp_seq_set_base_number.sv的文件,主要用来约束transaction中的变量base_number,将该base_number的值作为amplifier的基数。
点击查看代码
`ifndef AMP_SEQ_SET_BASE_NUMBER_SV
`define AMP_SEQ_SET_BASE_NUMBER_SV
class amp_seq_set_base_number extends uvm_sequence #(amp_seq_item);
`uvm_object_utils(amp_seq_set_base_number)
rand bit [7:0] base_number;
rand int delay;
extern function new(string name = "amp_seq_set_base_number");
extern virtual task body();
endclass : amp_seq_set_base_number
function amp_seq_set_base_number::new(string name = "amp_seq_set_base_number");
super.new(name);
`uvm_info(get_type_name(), "created", UVM_HIGH)
endfunction : new
task amp_seq_set_base_number::body();
`uvm_do_with(req, {
ttype == amp_seq_item::SET_BASE_NUMBER;
no == 0;
base_number == local::base_number;
scaler == 0;
delay == 0;
})
get_response(rsp);
endtask : body
`endif // AMP_SEQ_SET_BASE_NUMBER_SV
2.3 第一个完善的sequence
有了基本激励,其实就可以生成真正能用的激励了。
将amp_seq.sv重命名为amp_case1_seq.sv,该类的目的就是生成一个比较常规的测试场景:多次发送数据。使用字段case1是因为tests文件夹中有一个名为amp_testcase1.sv的文件,说明该sequence是供该测试用例使用的。
点击查看代码
`ifndef AMP_CASE1_SEQ_SV
`define AMP_CASE1_SEQ_SV
class amp_case1_seq extends uvm_sequence #(amp_seq_item);
`uvm_object_utils(amp_case1_seq)
randc bit [7:0] base_number;
bit [7:0] no;
randc int burst_num;
amp_seq_set_scaler seq_scaler;
amp_seq_set_base_number seq_base_number;
amp_seq_idle seq_idle;
extern function new(string name = "amp_case1_seq");
extern task body();
endclass : amp_case1_seq
function amp_case1_seq::new(string name = "amp_case1_seq");
super.new(name);
`uvm_info(get_type_name(), "sequence created", UVM_LOW)
endfunction : new
task amp_case1_seq::body();
if(starting_phase != null)
starting_phase.raise_objection(this);
`uvm_do_with(seq_idle, {delay == 10;})
no = 0;
repeat(10) begin
`uvm_do(seq_scaler)
`uvm_do_with(seq_idle, {delay == 1;})
void'(std::randomize(burst_num) with {burst_num inside {[1:5]};});
`uvm_info(get_type_name(), $sformatf("burst_num is: %0d", burst_num), UVM_LOW)
repeat(burst_num) begin
no += 1;
`uvm_do_with(seq_base_number, {
no == local::no;
base_number == local::base_number;
})
end
`uvm_do_with(seq_idle, {delay == 1;})
end
repeat(3) `uvm_do_with(seq_idle, {delay == 1;})
if(starting_phase != null)
starting_phase.drop_objection(this);
endtask : body
`endif // AMP_CASE1_SEQ_SV
三、第一个测试用例
有了这个比较完善的sequence,其实就可以生成测试用例了。打开amp_testcase1.sv,在build_phase()中将这个sequence和i_agt中的sequencer绑定起来。
点击查看代码
`ifndef AMP_TESTCASE1_SV
`define AMP_TESTCASE1_SV
class amp_testcase1 extends amp_base_test;
`uvm_component_utils(amp_testcase1)
extern function new(string name = "amp_testcase1", uvm_component parent = null);
extern function void build_phase(uvm_phase phase);
endclass : amp_testcase1
function amp_testcase1::new(string name = "amp_testcase1", uvm_component parent = null);
super.new(name, parent);
`uvm_info(get_type_name(), "created", UVM_LOW)
endfunction : new
function void amp_testcase1::build_phase(uvm_phase phase);
super.build_phase(phase);
uvm_config_db #(uvm_object_wrapper)::set(this, "env.i_agt.sqr.main_phase", "default_sequence", amp_case1_seq::type_id::get());
endfunction : build_phase
`endif // AMP_TESTCASE1_SV
四、测试用例的使用
4.1 driver和sequencer的连接
经过以上步骤,sequencer已经知道了需要使用哪个sequence。但这还不够。为了能运行该测试用例,需要完成driver和sequencer之间的连接工作,即让两者的端口进行连接。连接工作在agent的connect_phase()中完成。
点击查看代码
`ifndef AMP_AGENT_SV
`define AMP_AGENT_SV
class amp_agent extends uvm_agent;
virtual amp_interface vif;
amp_sequencer sqr;
amp_driver drv;
amp_monitor mon;
`uvm_component_utils(amp_agent)
extern function new(string name = "amp_agent", uvm_component parent = null);
extern function void build_phase(uvm_phase phase);
extern function void connect_phase(uvm_phase phase);
endclass : amp_agent
function amp_agent::new(string name = "amp_agent", uvm_component parent = null);
super.new(name, parent);
`uvm_info(get_type_name(), "created", UVM_LOW)
endfunction : new
function void amp_agent::build_phase(uvm_phase phase);
super.build_phase(phase);
if(!uvm_config_db #(virtual amp_interface)::get(this, "", "vif", vif))
`uvm_error("NO VIF", {"virtual interface must be set for: ", get_full_name()})
mon = amp_monitor::type_id::create("mon", this);
mon.vif = vif;
if(is_active == UVM_ACTIVE) begin
sqr = amp_sequencer::type_id::create("sqr", this);
drv = amp_driver::type_id::create("drv", this);
drv.vif = vif;
end
endfunction : build_phase
function void amp_agent::connect_phase(uvm_phase phase);
super.connect_phase(phase);
if(is_active == UVM_ACTIVE) begin
drv.seq_item_port.connect(sqr.seq_item_export);
`uvm_info(get_type_name(), "driver and sequencer connected", UVM_LOW)
end
endfunction : connect_phase
`endif // AMP_AGENT_SV
与之前相比,不同之处就在与多了一个connect_phase()函数,sequencer和driver的连接工作就在此完成。两者的端口是由UVM早就设置好的,直接使用即可。
4.2 driver的完善
sequencer只是发送的数据,其不能被DUT直接使用,将数据转换为电平信号的媒介就是driver,因此需要完善driver。
点击查看代码
`ifndef AMP_DRIVER_SV
`define AMP_DRIVER_SV
class amp_driver extends uvm_driver #(amp_seq_item);
virtual amp_interface vif = null;
`uvm_component_utils(amp_driver)
extern function new(string name = "amp_driver", uvm_component parent = null);
extern function void build_phase(uvm_phase phase);
extern task run_phase(uvm_phase phase);
extern protected task reset_dut();
extern protected task idle(amp_seq_item t);
extern protected task set_scaler(amp_seq_item t);
extern protected task set_base_number(amp_seq_item t);
extern protected task do_transaction(amp_seq_item t);
endclass : amp_driver
function amp_driver::new(string name = "amp_driver", uvm_component parent = null);
super.new(name, parent);
`uvm_info(get_type_name(), "created", UVM_LOW)
endfunction : new
function void amp_driver::build_phase(uvm_phase phase);
super.build_phase(phase);
endfunction : build_phase
task amp_driver::run_phase(uvm_phase phase);
super.run_phase(phase);
vif.wr_en_i = 1'b0;
vif.set_scaler_i = 1'b0;
vif.wr_data_i = 16'd0;
vif.rd_val_o = 1'b0;
vif.rd_data_o = 32'd0;
vif.scaler_o = 16'd0;
while(!vif.rstn_i)
@(posedge vif.clk_i);
fork
this.reset_dut();
forever begin
seq_item_port.get_next_item(req);
`uvm_info(get_type_name(), {"req item:\n", req.sprint()}, UVM_FULL)
this.do_transaction(req);
void'($cast(rsp, req.clone()));
rsp.set_sequence_id(req.get_sequence_id());
seq_item_port.item_done(rsp);
end
join
endtask : run_phase
task amp_driver::reset_dut();
forever begin
@(negedge vif.rstn_i);
vif.wr_en_i = 1'b0;
vif.set_scaler_i = 1'b0;
vif.wr_data_i = 16'd0;
end
endtask : reset_dut
task amp_driver::idle(amp_seq_item t);
repeat(t.delay) @vif.ck_drv;
vif.ck_drv.wr_en_i <= 1'b0;
vif.ck_drv.wr_data_i <= 16'd0;
endtask : idle
task amp_driver::set_scaler(amp_seq_item t);
@vif.ck_drv;
vif.ck_drv.wr_en_i <= 1'b1;
vif.ck_drv.set_scaler_i <= 1'b1;
vif.ck_drv.wr_data_i <= t.scaler;
endtask : set_scaler
task amp_driver::set_base_number(amp_seq_item t);
@vif.ck_drv;
vif.ck_drv.wr_en_i <= 1'b1;
vif.ck_drv.set_scaler_i <= 1'b0;
vif.ck_drv.wr_data_i <= {t.no, t.base_number};
t.rd_scaler <= vif.ck_drv.scaler_o;
endtask : set_base_number
task amp_driver::do_transaction(amp_seq_item t);
case(t.ttype)
amp_seq_item::IDLE: this.idle(t);
amp_seq_item::SET_SCALER: this.set_scaler(t);
amp_seq_item::SET_BASE_NUMBER: this.set_base_number(t);
default: `uvm_error("ERROR STATE", "an undefined state in do_transaction")
endcase
endtask : do_transaction
`endif // AMP_DRIVER_SV
4.3 索引文件的修改
由于修改了文件名和文件数量,因此amp_pkg.svh也需要同步修改。
点击查看代码
`include "../agent/amp_interface.sv"
`include "../agent/amp_seq_item.sv"
`include "../agent/amp_seq_idle.sv"
`include "../agent/amp_seq_set_scaler.sv"
`include "../agent/amp_seq_set_base_number.sv"
`include "../agent/amp_case1_seq.sv"
`include "../agent/amp_sequencer.sv"
`include "../agent/amp_agent_config.sv"
`include "../agent/amp_driver.sv"
`include "../agent/amp_monitor.sv"
`include "../agent/amp_agent.sv"
`include "../env/amp_env_refm.sv"
`include "../env/amp_env_scb.sv"
`include "../env/amp_env.sv"
`include "../tests/amp_base_test.sv"
`include "../tests/amp_testcase1.sv"
以上步骤都做好后,修改amp_tb.sv中的UVM的入口函数——run_test()的参数,将amp_base_test改为amp_testcase1后,就可以使用do文件进行仿真。这里需要注意,将do文件中的run 100ns修改为run -all,不然无法获得完整的运行日志。
等待程序运行完成后,打开log文件夹,查看一下运行情况。

当然,如果想看更加详细的信息,可以在amp_tb.sv中添加代码修改全局的冗余度,比如下面代码所示的那样:
点击查看代码
initial begin
uvm_root::get().set_report_verbosity_level_hier(UVM_FULL);
run_test("amp_testcase1");
end

本文来自博客园,作者:TooyamaYuuouji,转载请注明原文链接:https://www.cnblogs.com/Lexington-CV2/articles/16637142.html
浙公网安备 33010602011771号