数字IC验证23912--寄存器模型

寄存器模型的集成

在这里插入图片描述

总线UVC的实现

  • MCDF访问寄存器的总线接口时序较为简单。控制寄存器接口首先需要在每一个时钟解析cmd。
  • 当cmd为写指令时,即需要把数据cmd_data_in写入到cmd_addr对应的寄存器中。
  • 当cmd为读指令时,即需要从cmd_addr对应的寄存器中读取数据,在下一个周期,cmd_addr对应的寄存器数据被输送至cmd_data_out接口。
  • 我们给出一段8位地址线,32位数据线的总线UVC实现代码。

总线UVC的示例

class mcdf_bus_trans extends uvm_sequence_item;
	rand bit[1:0] cmd;
	rand bit[7:0] addr;
	rand bit[31:0] wdata;
	bit[31:0] rdata;
	'uvm_object_utils_begin (mcdf_bus_trans)
	.......
	'uvm_object_utils_end
endclass
class mcdf_bus_sequencer extends uvm_sequencer;
	virtual mcdf_if vif;
	'uvm_component_utils (mcdf_bus_sequencer)
	...
	function void build phase (uvm_phase phase) ;
		if ( ! uvm_config_db# (virtual mcdf_if) : :get(this, "","vif", vif) ) begin
			'uvm_error("GETVIF", "no virtual interface is assigned")
		end
	endfunction
endclass

class mcdf bus_monitor extends uvm_monitor;
	virtual mcdf_if vif ;
	uvm _analysis _port # (mcdf_bus_trans) ap;
	'uvm_component_utils (mcdf bus_monitor)
	...
	function void build_phase (uvm phase phase) ;
		if (!uvm_config_db#(virtual mcdf_if) : :get(this, "","vif", vif)) begin
			'uvm_error("GETVIF", "no virtual interface is assigned" )
		end
		ap = new ("ap" , this);
	endfunction
	task run_ phase(uvm phase phase);
		forever begin
			mon_trans();
		end
	endtask
	task mon_trans ();
		mcdf_bus_trans t;
		@(posedge vif.clk) ;
		if(vif.cmd ==`WR工TE) begin
			t = new();
			t.cmd =`WRITE;t.addr = vif.addr;
			t.wdata = vif.wdata ;ap . write(t) ;
		end
		else if (vif .cmd ==`READ) begin
			t = new () ;
			t.cmd = `READ ;
			t.addr = vif.addr ;
			fork
				begin
					@(posedge vif.clk);
					#10ps ;
					t.rdata = vif.rdata;
					ap.write (t) ;
				end
			join_none
		end
	endtask
endclass: mcdf_bus_monitor

class mcdf_bus_driver extends uvm_driver;
	virtual mcdf_if vif;
	'uvm_component_utils (mcdf_bus_driver)
	...
	function void build phase(uvm phase phase) ;
		if (!uvm_config_db# (virtual mcdf_if)::get(this, "","vif", vif)) begin
			'uvm_error( "GETVIF", "no virtual interface is assigned" )
		end
	endfunction
	task run phase (uvm_phase phase);
		REQ tmp ;
		mcdf bus_trans req, rsp;
		reset_listener () ;
		forever begin
			seq_item_port.get_next_item ( tmp) ;
			void' ($cast(req, tmp)) ;
			uvm_info("DRV",$sformatf ("got a item \n %s", req.sprint() ),UVM_LOW)
			void' ($cast(rsp, req.clone())) ;
			rsp.set_sequence_id(req.get_sequence_id() ) ;
			rsp.set_transaction_id(req.get_transaction_id() );
			drive_bus (rsp) ;
			seq_item port.item_done(rsp) ;
			uvm_info( "DRV",$sformatf( "sent a item \n %s",rsp.sprint()),UVM_LOW)
		end
	endtask
	task drive_ read(mcdf bus_trans t);
		@(posedge vif.clk);
		vif .cmd <= t.emd;vif.addr <= t.addr ;e(posedge vif.clk);
		#10ps;
		t.rdata = vif.rdata;
	endtask
	task drive_idle (bit is_sync =0) ;
		if(is_sync) e(posedge vif.clk) ;
		vif.cmd <= 'h0 ;
		vif.addr <= 'h0 ;vif.wdata <= 'h0;
	endtask
endclass

示例囊括了mdf_bus_agent的所有组件:sequence item、sequencer、driver、monitor和agent。我们对这些代码的部分实现给出解释:

  • mcdf_bus_trans包括了可随机化的数据成员cmd、addr、wdata和不可随机化的rdata。rdata之所以没有声明为rand类型,是因为它应从总线读出或者观察,不应随机化。
  • mcdf_bus_monitor会观测总线,其后通过analysis port写出到目标analysis组件,在本节中它稍后将连接到uvm_reg_predictor.
  • mcdf_bus_driver主要实现了总线驱动和复位功能,通过模块化的方法reset_listener()、drive_bus()、drive_write()、drive_read()和drive_idle()可以解析三种命令模式IDLE、WRITE和READ,并且在READ模式下,将读回的数据通过item_done(rsp)写回到sequencer和sequence—侧。建议读者在通过clone()命令创建RSP对象后,通过set_sequence_id()和 set_transaction_id()两个函数保证REQ和RSP的中保留的ID信息一致。

Adapter的实现

在具备了MCDF总线UVC之后,需要实现adapter。每一个总线对应的adapter所完成的桥接功能即是在uvm_reg _bus_op和总线transaction之间的转换。用户在开发某一个总线adapter类型时,需要实现下面几点:

  • uvm_reg_bus_op与总线transaction中各自的数据映射。
  • 实现reg2bus()和bus2reg()两个函数,这两个函数即实现了两种transaction的数据映射。
  • 如果总线支持byte访问,可以使能supports_byte_enable;如果总线UVC要返回response数据,则应当使能provides_responses。在本例中,mcdf_bus_driver在读数时会将读回的数据填入到RSP并返回至sequencer,因此需要在adapter中使能provides_responses。由此使得bus2reg()函数调用时得到的数据是总线返回时的transaction,但读者需要注意如果总线UVC不支持返回RSP(没有调用put_response(RSP)或者item_done(RSP)),那么不应该置此位,否则adapter将会使得验证环境挂起。默认情况下,上述的两个成员的复位值都是0.
class reg2mcdf_adapter extends uvm_reg_adapter;
	`uvm_object_utils(reg2mcdf_adapter)
	function new (string name = "mcdf_bus_trans" );
		super.new (name) ;
		provides_responses = 1;
	endfunction
	function uvm_sequence_item reg2bus(const ref uvm_reg_bus_op  rw);
		mcdf _bus_trans t = mcdf_bus_trans : : type_id : :create ("t") ;
		t.cmd = (rw.kind == UVM_WRITE)?`WRITE : ‘READ;
		t.addr = rw .addr ;
		t.wdata = rw.data;
		return t;
	endfunction
	function void bus2reg(uvm_sequence_item bus_item,ref uvm_reg_bus_op rw);
		mcdf_bus_trans t;
		if (!$cast(t, busmitem)) begin
			'uvm_ fatal( "Nor_MCDF_BUs_TYPE", "Provided bus_item is not of the correct type")
			return;
		end
		rw. kind = (t.cmd ==`WRITE) ? UVM_WRITE : UVM_READ;
		rw . addr = t.addr ;
		rw.data = (t.cmd ==`WRITE) ? t.wdata : t.rdata;
		rw.status = UVM_IS_OK;
	endfunction
endclass

关于uvm_reg_bus_op 有以下几个属性:
在这里插入图片描述

  • 该类在构建函数中使能了provide_responses,这是因为mcdf_bus_driver在发起总线访问之后会将RSP一并返回至sequencer。
  • reg2bus()完成的桥接场景是,如果用户在寄存器级别做了操作,那么寄存器级别操作的信息uvm_reg_bus_op会被记录,同时调用uvm_reg_adapter:reg2bus()函数。
  • 在完成了将uvm_reg_bus_op的信息映射到mcdf_bus_trans之后,函数将mcdf_bus_trans实例返回。而在返回mcdf_bus_trans之后,该实例将通过mcdf_bus_sequencer传入到mcdf_bus_driver。这里的transaction传输是后台隐式调用的,不需要读者自己发起。
  • 寄存器无论读写,都应当知道总线操作后的状态返回,对于读操作时,也需要知道总线返回的读数据,因此uvm_reg_adapter::bus2reg()即是从mcdf_bus_driver()将数据写回至mcdf_bus_sequencer,而一直保持监听的reg2mcdf_adapter一旦从sequencer获取了RSP (mcdf_bus_trans)之后,就将自动调用bus2reg()函数。
  • bus2reg()函数的功能与reg2bus()相反,完成了从mcdf_bus_trans到uvm_reg_bus_op的内容映射。在完成映射之后,更新的uvm_reg_bus_op数据最终返回至寄存器操作场景层。
  • 对于寄存器操作,无论读操作还是写操作,都需要经历调用reg2bus(),继而发起总线事务,而在完成事务发回反馈之后,又需要调用bus2reg(),将总线的数据返回至寄存器操作层面。

Adapter的集成

在具备了寄存器模型mcdf_rgm、总线UVC mcdf_bus_agent和桥接reg2mcdf_adapter之后,就需要考虑如何将adapter集成到验证环境中去:。

  • 对于mcdf_rgm的集成,我们倾向于顶层传递的方式,即最终从test层传入寄存器模型句柄。这种方式有利于验证环境mcdf_bus_env的闭合性,在后期不同test如果要对rgm做不同的配置,都可以在顶层例化,而后通过uvm_config_db来传递。
  • 寄存器模型在创建之后,还需要显式调用build()函数。需要注意uvm_reg_block是uvm_object类型,因此其预定义的build()函数并不会自动执行,还需要单独调用。
  • 在还没集成predictor之前,我们采用了auto prediction的方式,因此调用了函数set_auto_predict()。
  • 在顶层环境的connect阶段中,需要将寄存器模型的map组件与bus_sequencer和adapter连接。这么做的必要性在于将map (寄存器信息)、sequencer(总线侧激励驱动和adapter(寄存器级别和硬件总线级别的桥接关联在一起。也只有通过这一步,adapter的桥接功能才可以工作。
class mcdf_bus_env extends uvm_env;
	mcdf_bus_agent agent;
	mcdf_rgm rgm ;
	reg2mcdf_adapter reg2mcdf ;
	'uvm_component_utils (mcdf_bus_env)
	...
	function void build_ phase(uvm _phase phase) ;
		agent = mcdf_bus_agent : : type_id : : create ( "agent", this) ;
		if(!uvm_config_db# (mcdf_rgm): :get(this,"","rgm",rgm)) begin
			'uvm_info( "GETRGM","no top-down RGM handle is assigned",UVM_LOW)
			rgm = mcdf_rgm : : type_id: : create ("rgm" , this) ;
			'uvm_info("NEWRGM","created rgm instance locally",UVM_IOW)
		end
		rgm . build() ;
		rgm . map.set_auto_predict() ;
		reg2mcdf = reg2mcdf_adapter : : type_id: : create ( "reg2mcdf" );
  	endfunction
	function void connect _phase (uvm phase phase) ;
		rgm.map.set_sequencer (agent.sequencer, reg2mcdf);
	endfunction
endclass

访问方式

利用寄存器模型,我们可以更方便地对寄存器做操作。我们分成两种访问寄存器的方式,即前门访问(front-door)和后门访问(back-door)。

  • 前门访问,顾名思义指的是在寄存器模型上做的读写操作,最终会通过总线UVC来实现总线上的物理时序访问,因此是真实的物理操作。
  • 后门访问,指的是利用UVM DPI (uvm_hdl_read()、uvm_hdl_deposit()),将寄存器的操作直接作用到DUT内的寄存器变量,而不通过物理总线访问。

前门访问

  • 接下来前门访问的示例中的sequence继承于uvm_reg _sequence.uvm_reg_sequence除了具备一般uvm_sequence的预定义方法外,还具有跟寄存器操作相关的方法。
  • 在对寄存器操作的示例中,用户可以看到两种方式:
    ·第一种即uvm_reg:read()/write()。在传递时,用户需要注意将参数path指定为UVM_FRONTDOOR。uvm_reg:read()/write()方法可传入的参数较多,除了status和value两个参数需要传入,其它参数如果不指定,可采用默认值。
    ·第二种即uvm_reg_sequence:read_reg()/write_reg()。在使用时,也需要将path指定为UVM_FRONTDOOR。
class mcdf_example_seq extends uvm_reg_sequence ;
	mcdf_rgm rgm ;
	`uvm_object_utils (mcdf_example_seq)
	'uvm_declare_p_sequencer (mcdf_bus_sequencer)
	...
	task body ( ) ;
		uvm_status_e status ;uvm_reg_data_t data ;
		if (!uvm_config_db#(mcdf_rgm) : : get(null,get_full_name(),"rgm",rgm)) begin
			'uvm_error ("GETRGM","no top-down RGM handle is assigned")
		end
		//register model access write()/read()
		rgm. chn10_ctrl_reg.read (status,data,UVM_FRONTDOOR,parent(this));
		rgm.chn10_ctrl_reg.write(status,'h11,UVM_FRONTDOOR,parent(this));
		rgm. chn10_ctrl_reg.read (status,data,uVM_FRONTDOOR,.parent(this));
		//pre-defined methods access
		read_reg (rgm. chnl1_ctrl_reg,status, data,uVM_FRONTDOOR);
		write_reg (rgm. chnl1_ctrl_reg, status,'h22,uVM_PRONTDOOR);
		read_reg (rgm.chnl1_ctrl_reg,status,data,UVM_FRONTDOOR);
	endtask
endclass

后门访问

  • 在进行后门访问时,用户首先需要确保寄存器模型在建立时,是否将各个寄存器映射到了DUT一侧的HDL路径。
  • 下面的例码即实现了寄存器模型与DUT各个寄存器的路径映射:
class mcdf_rgm extends uvm_reg_block ;
... //寄存器成员和map声明
	virtual function build() ;
	...//寄存器成员和map创建
	  //关联寄存器模型和HDL
	add_hdl_path ( "reg_backdoor_access.dut") ;
	chn10_ctrl_reg.add_hdl path_slice($sformatf ("regs[%0d] ",‘SLVO_Rm_REG),0,32);
	chnll_ctrl_reg.add_hdl_path_slice($sformatf ( "regs[%0d]",‘sLV1_RW_REG),0,32);
	chn12_ctrl_reg.add_hdl _path_slice($sformatf("regs[%0d] ",‘SLV2_RW_REG),0,32);
	chn10_stat_reg.add_hdl_path_slice($sformatf ( "regs[%0d]",‘SLVo_R_REG ),0,32);
	chnll_stat_reg.add_hdl_path_slice($sformatf ("regs[%0d] ",‘SLV1_R_REG ),0,32);
	chn12_stat_reg.add_hdl path_slice($sformatf ( "regs[%0d]",‘SIV2_R_REG ) ,0,32);
	lock_model () ;
	endfunction
endclass
  • 示例中通过uvm_reg_blockadd_hdl_path(),将寄存器模型关联到了DUT一端,而通过uvm_reg:add_hdl_path_slice完成了将寄存器模型各个寄存器成员与HDL一侧的地址映射。
  • 另外,寄存器模型build()函数最后以lock_model()结尾,该函数的功能是结束地址映射关系,并且保证模型不会被其它用户修改。
  • 在寄存器模型完成了HDL路径映射后,我们才可以利用uvm_reg或者uvm_reg_sequence自带的方法进行后门访问。后门访问也有几类方法提供:
    . uvm_reg:read()/write(),在调用该方法时需要注明UVM_BACKDOOR的访问方式。
    . uvm_reg_sequenceread_reg()/write_reg(),在使用时也需要注明UVM_BACKDOOR的访问方式。
    ·另外,uvm reg::peek()/poke()两个方法,也分别对应了读取寄存器(peek)和修改寄存器(poke)两种操作,而用户无需指定访问方式尾UVM_BACKDOOR,因为这两个方法本来就只针对于后门访问。
class mcdf_example_seq extends uvm_reg_sequence ;
	mcdf_rgm rgm ;
	'uvm_object_utils (mcdf_example_seq)
	'uvm_declare _p_sequencer(mcdf_bus_sequencer)
	task body () ;
		uvm status_e status ;uvm _reg_data_t data ;
		uvm_status_e status ;uvm_reg_data_t data;
		if (!uvm_config_db# (madf_rgm) : : get(null,get_full_name (), "rgm",rgm)) begin
			'uvm_error("GETRGM","no top-down RGM handle is assigned")
		end
		//register model access write ( ) /read()
		rgm.chn10_ctrl_reg.read (status,data,UVM_BACKDOOR,parent(this)) ;
		rgm. chn10_ctrl_reg.write(status,'h11,,UVM_BACKDOOR,parent (this) );
		rgm. chn10_ctrl_reg.read (status,data,UVM_BACKDOOR,.parent(this) );
		//register model access poke( ) /peed ()
		rgm. chnl1_ctrl_reg.peek (status,data,.parent (this));
		rgm. chnl1_ctrl_reg.poke (status, 'h22,.parent(this));
		rgm. chnl1_ctrl_reg.peek (status, data,.parent(this));
		//pre-defined methods read_reg()/write_reg ()
		read_reg (rgm.chn12_ctrl_reg,status,data,UVM_BACKDOOR);
		write_reg (rgm.chn12_ctrl_reg,status,'h22,UVM_BACKDOOR);
		read_reg (rgm.chn12_ctrl_reg,status,data,UVM_BACKDOOR);
		//pre-defined methods peek _reg ()/poke_reg()
		peek_reg (rgm.chnl2_ctr1_reg, status, data);
		poke_reg (rgm.chnl2_ctrl_reg, status, 'h33);
		peek_reg (rgm. chnl2_ctrl_reg, status, data) ;

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值