TooyamaYuuouji

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

现在,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的流程是:先延时一段时间;为amplifier设定放大倍数,然后根据随机数burst_num的值,为amplifier连续设定基数并计算结果;延时一段时间后结束。

三、第一个测试用例

有了这个比较完善的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
driver由于涉及到时序,因此内容与其他代码文件相比,显得更加复杂。但由于之前设计的枚举变量的作用,简化了编写逻辑,因此整体并不难理解。

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文件夹,查看一下运行情况。

image

当然,如果想看更加详细的信息,可以在amp_tb.sv中添加代码修改全局的冗余度,比如下面代码所示的那样:

点击查看代码
initial begin
    uvm_root::get().set_report_verbosity_level_hier(UVM_FULL);
    run_test("amp_testcase1");
end
再次运行do文件,然后打开日志,可以看到更多详细的信息。

image

posted on 2022-09-27 19:57  TooyamaYuuouji  阅读(796)  评论(0)    收藏  举报