UVM实战 卷I学习笔记12——UVM中代码的可重用性(3)


模块级到芯片级的代码重用

基于env的重用

现代芯片的验证通常分为两个层次:模块级别(block level,也称IP级别、unit级别)验证和芯片级别(也称SOC级别)验证。一个大的芯片在开发时是分成多个小模块来开发的。每个模块开发一套独立的验证环境,通常每个模块有专门的验证人员负责。当在模块级别验证完成后需要做整个系统的验证。

为简单起见,假设某芯片分成了三个模块,如图所示:
在这里插入图片描述
这三个模块在模块级别验证时分别有自己的driver和sequencer,如下图所示:
在这里插入图片描述
在芯片级别验证时如果采用env级别的重用,那么B和C中的driver分别取消,可通过设置各自i_agt的is_active来控制,如下图所示:o_agt(A)和i_agt(B)两者监测的是同一接口,即二者应是同一个agent。在模块级别验证时,i_agt(B)被配置为active模式,在图中被配置为passive模式。被配置为passive模式的i_agt(B)其实和o_agt(A)一样监测同一接口,对外发出同样的transaction。或可以将i_agt(B)取消,model(B)的数据来自o_agt(A)。
在这里插入图片描述
o_agt(B)和i_agt©也是同样的情况。取消了i_agt(B)和i_agt©的芯片级别验证平台如下图所示:
在这里插入图片描述
在验证平台中每个模块验证环境需要在其env中添加一个analysis_port用于数据输出;添加一个analysis_export用于数据输入;在env中设置in_chip用于辨别不同的数据来源:

class my_env extends uvm_env;
	…
	bit in_chip;
	uvm_analysis_port#(my_transaction) ap;
	uvm_analysis_export#(my_transaction) i_export;
	…
	virtual function void build_phase(uvm_phase phase);
		super.build_phase(phase);
		if(!in_chip) begin
			i_agt = my_agent::type_id::create("i_agt", this);
			i_agt.is_active = UVM_ACTIVE;
		end
		…
		if(in_chip)
			i_export = new("i_export", this);
	endfunction
	…
endclass
function void my_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	ap = o_agt.ap;
	if(in_chip) begin
		i_export.connect(agt_mdl_fifo.analysis_export);
	end
	else begin
		i_agt.ap.connect(agt_mdl_fifo.analysis_export);
	end
	…
endfunction

在chip_env中实例化env_A、env_B、env_C,将env_B和env_C的in_chip设置为1,并将env_A的ap口与env_B的i_export相连,将env_B的ap与env_C的i_export相连接:

class chip_env extends uvm_env;
	`uvm_component_utils(chip_env)
	my_env env_A;
	my_env env_B;
	my_env env_C;
	…
endclass
function void chip_env::build_phase(uvm_phase phase);
	super.build_phase(phase);
	env_A = my_env::type_id::create("env_A", this);
	env_B = my_env::type_id::create("env_B", this);
	env_B.in_chip = 1;
	env_C = my_env::type_id::create("env_C", this);
	env_C.in_chip = 1;
endfunction
function void chip_env::connect_phase(uvm_phase phase);
	super.connect_phase(phase);
	env_A.ap.connect(env_B.i_export);
	env_B.ap.connect(env_C.i_export);
endfunction

上面两种芯片级别验证平台各有其优缺点。前者的验证平台的各个env之间没有数据交互,从而各个env不必设置analysis_port及analysis_export,在连接上简单些。但推荐使用后者的验证平台

  • 整个验证平台中消除了冗余的monitor,这在一定程度上可以加快仿真速度
  • 不同模块的验证环境之间有数据交互时,可以互相检查对方接口数据是否合乎规范。如A的数据送给了B,而B无法正常工作,那么要么是A收集的数据是错的,不符合B的要求,要么就是A收集的数据是对的,但B对接口数据理解有误。

寄存器模型的重用

上节的重用中并没有考虑总线的重用。一般每个模块会有自已的寄存器配置总线。在集成到芯片时芯片有自己的配置总线,这些配置总线经过仲裁之后分别连接到各个模块,如下图所示:
在这里插入图片描述
在这里插入图片描述
但从上图可以看出这样的一个env是不可重用的。

在前面章节中bus_agt是作为env的一部分的,如下图所示:
在这里插入图片描述
为提高可重用性,在模块级别时上图的bus_agt应该从env中移到base_test中,如下图所示:
在这里插入图片描述
与bus_agt对应的是寄存器模型。在模块级别验证时每个模块有各自的寄存器模型。很多用户习惯在env中实例化寄存器模型:

class my_env extends uvm_env;
	reg_model rm;
	…
endclass
function void my_env::build_phase(uvm_phase phase);
	super.build_phase(phase);
	rm = reg_model::type_id::create("rm", this);
	…
endfunction

但如果要实现env级别的重用,就不能在env中实例化寄存器模型。每个模块都有其偏移地址,如A的偏移地址可能是’h0000,B是’h4000,C是’h8000(即16位地址的高两位用于辨识不同模块)。如果在env级例化寄存器模型,那么在芯片级时不能指定其偏移地址。因此在模块级验证时需要在base_test中实例化寄存器模型,在env中设置一个寄存器模型的指针,在base_test中对它赋值

为了在芯片级别使用寄存器模型,需建立一个新的寄存器模型:

class chip_reg_model extends uvm_reg_block;
	rand reg_model A_rm;
	rand reg_model B_rm;
	rand reg_model C_rm;
	virtual function void build();
		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0);
		…
		default_map.add_submap(A_rm.default_map, 16'h0);
		…
		default_map.add_submap(B_rm.default_map, 16'h4000);
		…
		default_map.add_submap(C_rm.default_map, 16'h8000);
	endfunction
	…
endclass

这个新的寄存器模型中只需加入各个不同模块的寄存器模型并设置偏移地址和后门访问路径。建立芯片级寄存器模型的方式与建立多层次的寄存器模型一致。

在chip_env中实例化此寄存器模型,并将各个模块的寄存器模型的指针赋值给各个env的p_rm

function void chip_env::build_phase(uvm_phase phase);
	super.build_phase(phase);
	env_A = my_env::type_id::create("env_A", this);
	env_B = my_env::type_id::create("env_B", this);
	env_B.in_chip = 1;
	env_C = my_env::type_id::create("env_C", this);
	env_C.in_chip = 1;
	bus_agt = bus_agent::type_id::create("bus_agt", this);
	bus_agt.is_active = UVM_ACTIVE;
	chip_rm = chip_reg_model::type_id::create("chip_rm", this);
	chip_rm.configure(null, "");
	chip_rm.build();
	chip_rm.lock_model();
	chip_rm.reset();
	reg_sqr_adapter = new("reg_sqr_adapter");
	env_A.p_rm = this.chip_rm.A_rm;
	env_B.p_rm = this.chip_rm.B_rm;
	env_C.p_rm = this.chip_rm.C_rm;
endfunction

加入寄存器模型后,整个验证平台的框图变为下图所示的形式:
在这里插入图片描述

virtual sequence与virtual sequencer

每个模块的virtual sequencer分为两种情况,一种是只适用于模块级别,不能用于芯片级别;另外一种是适用于模块和芯片级别。前者的代表是B和C的virtual sequencer,后者的代表是A中的virtual sequencer。B和C的virtual sequencer不能出现在芯片级的验证环境中,所以不应在env中实例化virtual sequencer,而应在base_test中实例化。A模块比较特殊,它是一个边界模块,所以它的virtual sequencer可以用于芯片级别验证中。

但前面只是一个简单的例子。现代的大型芯片可能不只一个边界输入,如下图所示:
在这里插入图片描述
D和F分别是边界输入模块。在整个芯片的virtual sequencer中,应该包含A、D和F的sequencer。因此A、D和F的virtual sequencer是不能直接用于芯片级验证的。无论是像B、C、E这样的内部模块还是A、D、F这样的边界输入模块,统一推荐其virtual sequencer在base_test中实例化在芯片级建立自己的virtual sequencer

相对应的virtual sequence,通常来说virtual sequence都使用uvm_declare_p_sequencer宏来指定sequencer。这些sequence在模块级别是存在的,但在芯片级根本不存在,所以这些virtual sequence无法用于芯片级别验证

有两种模块级别的sequence可以直接用于芯片级别的验证

一种如A、D和F这样的边界输入端的普通的sequence(不是virtual sequence),以A的某sequence为例,在模块级可以这样使用它:

class A_vseq extends uvm_sequence;
	virtual task body();
		A_seq aseq;
		`uvm_do_on(aseq, p_sequencer.p_sqr)
		…
	endtask
endclass

在芯片级这样使用它:

class chip_vseq extends uvm_sequence;
	virtual task body();
		A_seq aseq;
		D_seq dseq;
		F_seq fseq;
		fork
			`uvm_do_on(aseq, p_sequencer.p_a_sqr)
			`uvm_do_on(aseq, p_sequencer.p_d_sqr)
			`uvm_do_on(aseq, p_sequencer.p_f_sqr)
		join
		…
	endtask
endclass

另外一种是寄存器配置的sequence。这种sequence一般在定义时不指定transaction类型如果这些sequence做成如下形式,也是无法重用的

class A_cfg_seq extends uvm_sequence;
	virtual task body();
		p_sequencer.p_rm.xxx.write();
		…
	endtask
endclass

要想能够在芯片级重用,需要使用如下的方式定义:

class A_cfg_seq extends uvm_sequence;
	A_reg_model p_rm;
	virtual task body();
		p_rm.xxx.write();
		…
	endtask
endclass

模块级以如下的方式启动它:

class A_vseq extends uvm_sequence;
	virtual task body();
		A_cfg_seq c_seq;
		c_seq = new("c_seq");
		c_seq.p_rm = p_sequencer.p_rm;
		c_seq.start(null);
	endtask
endclass

芯片级以如下方式启动:

class chip_vseq extends uvm_sequence;
	virtual task body();
		A_cfg_seq A_c_seq;
		A_c_seq = new("A_c_seq");
		A_c_seq.p_rm = p_sequencer.p_rm.A_rm;
		A_c_seq.start(null);
		…
	endtask
endclass

除这种指针传递的形式外,还可通过get_root_blocks来获得。在芯片级root block已经和模块级不同,单纯靠get_root_blocks已无法满足要求。此时需要find_blocks、find_block、get_blocks和get_block_by_name等函数,这里不再一一介绍。

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值