《UVM实战》学习笔记——第七章 UVM中的寄存器模型1——寄存器模型介绍、前门/后门访问

本文介绍了寄存器模型在硬件验证中的作用,包括作为DUT寄存器的镜像,简化读写操作。文章详细阐述了前门和后门访问的概念,以及它们在不同场景下的应用,并提供了创建和集成寄存器模型到验证平台的步骤。此外,讨论了前门访问与后门访问的优缺点,以及在哪些情况下需要结合使用。
摘要由CSDN通过智能技术生成


前言

2023.3.8 热
2023.4.23 小雨

寄存器模型其实是DUT内各个寄存器的“拷贝文件”,它还能够根据DUT的变化,及时更新内部属性值,达到和 DUT内部寄存器同步的目的。


一、寄存器模型简介

1.1 带寄存器配置总线的DUT

最简单的DUT:只有一组数据输入输出端口,而没有行为控制口
带寄存器配置总线的DUT:通过总线来配置寄存器,DUT根据寄存器的值来改变其行为

这里的例子是DUT中有一个1bit的寄存器invert,分配地址为16‘h9,如果值为1,DUT在输出时把输入数据取反,如果值为0,直接输出输入的数据。

在这里插入图片描述

1.2 参考模型如何读取寄存器的值

  • 全局事件:在参考模型触发事件,virtual sequence等待事件,启动sequence去采样值(尽量避免使用全局事件)
  • 非全局事件:分别设置object,在object里面去设置事件,再去触发事件,等到这个事件触发就启动sequence去读取寄存器,利用的是config机制
  • 寄存器模型:上面方法都比较麻烦,所以引出了寄存器模型。

UVM寄存器模型的本质就是重新定义了验证平台与DUT的寄存器接口,使验证人员更好地组织及配置寄存器,简化流程、减少工作量。

task my_model::main_phase(uvm_phase phase);
	reg_model.INVERT_REG.read(status, value, UVM_FRONTDOOR);  //一句话来完成读取寄存器的操作
endtask

在这里插入图片描述

  • 任何消耗时间的phase:可以通过寄存器模型以前门或后门的方式来读取寄存器的值(前门访问是需要消耗时间的)

  • 某些不消耗时间的phase:如check_phase,使用后门访问来读取寄存器的值

1.3 寄存器模型的基本概念

一个寄存器一般是32bit位
在这里插入图片描述

uvm_reg_field:寄存器模型中的最小单位,具体存储寄存器数值的变量。针对寄存器功能域来构建的比特位,单个域可能由多个/单一比特位构成

reserved域:表示的是该域包含的比特位暂时保留以作日后功能的扩展使用,无法写入,读出来是复位值

在这里插入图片描述

uvm_reg:与寄存器匹配,内部可以例化和配置多个uvm_reg_field对象,一个寄存器至少包含一个uvm_reg_field

uvm_mem:匹配硬件存储模型

uvm_reg_map:用来指定寄存器列表中各个寄存器的偏移地址访问属性以及对应的总线。当寄存器模型使用前门访问方式来实现读或写操作时,uvm_reg_map就会将地址转换成绝对地址,后启动一个读或写的sequence。并将读或写的结果返回。在每个uvm_reg_block内部,至少有一个(通常也只有一个) uvm_reg_map

uvm_reg_block:比较大的单位,可以容纳多个寄存器(uvm_reg)、存储器(uvm_mem)和存储器列表(uvm_reg_map)。一个寄存器模型中至少包含一个uvm_reg_block

二、简单的寄存器模型

2.1 只有一个寄存器的寄存器模型

(1)为前文提到的invert寄存器创建寄存器模型,从uvm_reg派生出一个类

  • build函数:不同于build_phase,不是自动执行,需要手动调用,例化所有的uvm_reg_field,再调用configure函数进行配置
  • new函数:三个参数,名称,寄存器宽度,覆盖率相关
class reg_invert extedns uvm_reg;
	`uvm_object_utils(reg_invert)
	rand uvm_reg_field reg_data;

	virtual function void build();
		reg_data = uvm_reg_field::type_id::create("reg_data");
		//parent,size,lsb_pos,access,volatile,resetvalue,has_reset,is_rand,
		reg_data.configure(this, 1, 0, "RW", 1, 0, 1, 1, 0);
	endfunction
	
	function new(input string name = "reg_invert");
		//name size:整个寄存器的宽度,一般和总线宽度相同,不是实际使用的宽度 has_coverage:是否加入覆盖率的支持
		super.new(name, 16, UVM_NO_COVERAGE);
	endfunction
endclass

uvm_reg_field的configure函数的九个参数:

  • 此域的父辈:也就是这个域位于哪个寄存器,此处填this
  • 此域的宽度:这个寄存器宽度为1
  • 此域的最低位在整个寄存器中的位置:从0开始算
  • 此字段的存取方式:有25种,RW的意思是尽量写入,读取时对此域无影响
  • 是否易失,很少使用
  • 上电复位的默认值
  • 是否复位,一般都有复位默认值
  • 是否可以随机化,主要用于对寄存器进行随机写测试,如果是0,则不能随机,是复位值;且这个参数当且仅当第四个参数为RW(读写)、WRC、WRS、WO(只写)、RO(只读)、W1、WO1时才有效
  • 此域是否可以单独存取

(2)定义好这个寄存器后,再从uvm_reg_block派生一个类将其实例化

class reg_model extends uvm_reg_block;
	`uvm_object_utils(reg_model)
	rand reg_invert invert;
	
	virtual function void build();
		default_map = create_map("default_map", 0, 2, UVM_BIG_ENDIAN, 0); //用来存储不同reg在reg block中的地址
		invert = reg_invert::type_id::create("invert", , get_full_name());
		invert.configure(this, null, "invert");  //配置这个寄存器
		invert.build();  //手动调用,实例化各个域
		default_map.add_reg(invert, 'h9, "RW");  //要加入的寄存器,寄存器地址,寄存器的存取方式
	endfunction

	function new(input string name = "reg_model");
		super.new(name, UVM_NO_COVERAGE);
	endfunction
endclass
  • build函数:例化所有的寄存器。
    一个uvm_reg_block中一定要对应一个uvm_reg_map,系统已经有一个声明好的default_map,只需要在build中将其实例化,通过调用uvm_reg_block的create_map来实现。
    随后实例化invert并调用它的configure函数。再手动调用invert的build函数,实例化invert里面的域。
    最后一步是把此寄存器加入default_map,否则无法进行前门访问。
  • new函数:名字,是否支持覆盖率

uvm_reg_block的create_map函数的五个参数:

  • 名字
  • 基地址
  • 系统总线的宽度,单位为byte
  • 大小端
  • 是否按照byte寻址

uvm_reg的configure函数的三个参数:

  • 此寄存器所在的uvm_reg_block的指针,这里填this
  • reg_file的指针,这里暂时填null
  • 此寄存器的后门访问路径

2.2 将寄存器模型集成到验证平台

由于寄存器模型中存储数据的格式和在验证平台中使用的格式不一致,所以需要一个adapter模块负责格式转换,使寄存器模型能够和 sequencer 实现数据交互

寄存器模型的前门访问操作:分为读和写两种

读和写都会通过sequence产生一个uvm_reg_bus_op的变量,里面存储着操作类型和操作地址以及写的数据。通过转换器adapter交给bus_sequencer,随后给bus_driver,由bus_driver来实现最终的前门访问读写操作。

图中的虚线(driver指向adapter):表示并没有实际的transaction的传递

在这里插入图片描述

class my_adapter extends uvm_reg_adapter;
	string tID = get_type_name();
	`uvm_object_utils(my_adapter)  //adapter是object类型
	
	function new(string name = "my_adapter");
		super.new(name);
	endfunction
		
	function uvm_sequence_item reg2bus (const ref uvm_reg_bus_op rw);
		bus_transaction tr;
		tr = new ( "tr" ) ;
		tr.addr = rw.addr;
		tr.bus_op = (rw.kind == UVM_READ) ? BUS_RD : BUS_WR;
		if(tr.bus_op == BUS_WR)
			tr.wr_data = rw.data;
		return tr;
	endfunction : reg2bus
	
	function void bus2reg(uvm_sequence_item bus_item,ref uvm_reg_bus_op rw);
		bus_transaction tr;
		if(!$cast(tr, bus_item))begin  //使父类句柄指向子类对象,才能进行访问
			`uvm_fatal (tID,"Provided bus_item is not of the correct type. Expecting bus_trans actiion")
			return;
		end
		rw.kind = (tr.bus_op == BUS_RD) ? UVM_READ : UVM_WRITE;
		rw.addr = tr.addr ;
		rw.byte_en = 'h3;
		rw.data = (tr.bus_op == BUS_RD) ? tr.rd_data : tr.wr_data;
		rw.status = UVM_IS_OK;
	endfunction : bus2reg
endclass
  • reg2bus函数:将寄存器模型通过sequence发出的uvm_reg_bus_op类型变量转换为bus_sequencer接受的类型
  • bus2reg函数:当检测到总线上有操作时,将收集到的transaction转换为寄存器模型接受的类型,以便寄存器模型去更新相应的寄存器的值

转换器写好后,再base_test里面加入寄存器模型:

class base_test extends uvm_test;
	my_env env;
	my_adapter reg_sqr_adapter;
	my_vsqr v_sqr;
	reg_model rm;

	function void build_phase (uvm_phase phase) ;
		super.build_phase (phase) ;
		env = my_env::type_id::create ("env", this) ;
		v_sqr = my_vsqr::type_id::create ("v_sqr", this ) ;
		rm = reg_model::type_id::create ( "rm", this) ;
		rm.configure ( null, "") ;  //parent block:由于是最顶层的,所以null;后门访问路径
		rm.build ( ) ;  //例化所有的寄存器
		rm.reset ( ) ;  //调用后,所有寄存器的值变成复位值,不调用,则全为0
		rm.lock_model ( );  //调用后不能再加入新的寄存器
		rm.set_hdl_path_root("top_tb.my_dut");  //设置后门访问的绝对路径
		
		reg_sqr_adapter = new ( "reg_sqr_adapter" ); //实例化adapter
		env.p_rm = this.rm;

	endfunction
	
	function void connect_phase (uvm_phase phase);
		super.connect_phase ( phase ) ;
		v_sqr.p_my_sqr = env.i_agt.sqr;
		v_sqr.p_bus_sqr = env.bus_agt.sqr;
		v_sqr.p_rm = this.rm;
		rm.default_map.set_sequencer (env.bus_agt.sqr,reg_sqr_adapter) ; //将sqr和adapter连接起来
		rm.default_map.set_auto_predict (1);  //设置为自动预测状态,意味着reg model中的镜像值时刻和DUT中对应的reg值一样
	endfunction
endclass

寄存器模型的前门访问操作最终都将由uvm_reg_map完成﹐因此在connect _phase中,需要将转换器和bus_sequencer通过set_sequencer函数告知reg_model的default_map,并将default_map设置为自动预测状态。

2.3 在验证平台中使用寄存器模型

可以在sequence和其他component中使用。

以参考模型使用寄存器模型为例,需要在参考模型中有一个指向寄存器模型的指针。

class my_model extneds uvm_component;  //这个model指的是参考模型
	reg_model p_rm;
	...
	
	task my_model::main_phase (uvm_phase phase) ;
		my_transaction tr;
		my_transaction new_tr;
		
		uvm_status_e status;  
		uvm_reg_data_t value;
		
		super.main_phase (phase) ;
		p_rm.invert.read (status, value, UVM_FRONTDOOR);
		while (1) begin
			port.get (tr);
			new_tr = new ( "new_tr");
			new_tr.copy(tr);
			`uvm_info ("my_model""get one transaction,copy and print it:",UVM_LOW)
			new_tr.print ( );
			if (value)
				invert_tr (new_tr) ;
			ap.write (new_tr);
		end
	endtask
endclass

//my_env把p_rm传递给参考模型
	mdl.p_rm = this.p_rm;	

read任务:

  • uvm_status_e:表明读操作是否成功
  • uvm_reg_data_t:读取的数值
  • 读取的方式:前门或者后门

参考模型一般不会写寄存器,因此在virtual sequence里面进行写操作。

class case0_cfg_vseq extends uvm_sequence;
	virtual task body();
		uvm_status_e status;  
		uvm_reg_data_t value;
		
		p_sequencer.p_rm.invert.write(status, 1, UVM_FRONTDOOR)	;
	endtask
endclass

寄存器模型对transaction类型没有要求。因此可以在一个发送my_transaction的sequence中使用寄存器模型来对寄存器进行读写操作。

三、前门访问和后门访问

uvm_reg_sequence:继承于uvm_sequence,所以包含之前预定义的宏,还具有寄存器操作的方法

3.1 前门访问

定义:通过寄存器配置总线来对DUT进行读写操作。在这个过程中,仿真时间($time函数得到的时间)是一直往前走的。是消耗仿真时间的。有两者操作方法。

  • uvm_reg::read()/write():传递时需要注意将参数path指定为UVM_FRONTDOOR。uvm_reg::read()/write()方法可传入的参数较多,除了status和value两个参数需要传入,其它参数如果不指定,可采用默认值。
  • uvm_reg_sequence::read_reg()/write_reg():在使用时,也需要将path指定为UVM_FRONTDOOR。

读操作的完整流程:

  • 参考模型调用寄存器模型的读任务
  • 寄存器模型产生sequence,并产生uvm_reg_item:rw
  • 产生driver能够接受的transaction:bus_req=adapter.reg2bus(rw)
  • 把bus_req交给bus_sequencer
  • driver得到bus_req后驱动它得到读取的值,并将读取值放入bus_req中,调用item_done
  • 寄存器模型调用adapter.bus2reg(bus_req, rw)将bus_req中的读取值传递给rw
  • 将rw中的读数据返回参考模型

如果driver一直发送应答而sequence不收集应答,那么将会导致sequencer的应答队列溢出。因此在adapter中设置了provide_responses选项。

provides_responses:将读入的数据写到rsp并且返回sequencer,要进行设置(调用put_response和item_done的时候要返回rsp),否则两个值默认为0

3.2 后门访问

定义:与前门访问相对的操作,它并不通过总线进行读写操作,而是直接通过层次化的引用来改变寄存器的值。

  • 所有后门访问操作都是不消耗仿真时间(即$time打印的时间)而只消耗运行时间的
  • 从广义上来说,所有不通过DUT的总线而对DUT内部的寄存器或者存储器进行存取的操作都是后门访问操作。
  • 通过设置好每个寄存器的路径进行访问的。配置reg的configur函数以及在base_test里面set_hdl_path_root,两者合在一起就是寄存器的绝对路径。

在这里插入图片描述

后门访问有三种操作方法:

  • uvm_reg::read()/write()
  • uvm_reg_sequence::read_reg()/write_reg():在调用该方法时需要注明UVM_BACKDOOR的访问方式
  • uvm_reg::peek()/poke():分别对应了读取寄存器(peek)和修改寄存器(poke)两种操作,而用户无需指定访问方式为UVM_BACKDOOR,因为这两个方法本来就只针对于后门访问。
reg_model.INVERT_REG.peek(status, value, UVM_BACKDOOR);
// 通过后门访问方式读取寄存器的值,不关心DUT的行为,即使寄存器的读写类型是不能读,也可以将值读出来
reg_model.INVERT_REG.poke(status, 16'h1, UVM_BACKDOOR);
//通过后门访问方式写入寄存器的值,不关心DUT的行为,即使寄存器的读写类型是不能写,也可以将值写进去

3.3 前门访问VS后门访问

前门访问后门访问
通过总线访问需要消耗时间,总线访问结束时才能结束前门访问通过UVM DPI关联硬件寄存器信号路径,直接读取或修改硬件,不需要访问时间,零时刻响应
一般读写只能按字word读写(总线为32位),无法直接读写寄存器域直接读写寄存器或寄存器域
正确反映时序关系不受时序控制,可能访问时发送冲突
依靠监控总线来对寄存器模型内容做预测依靠auto prediction方式对寄存器内容做预测
有效捕捉总线错误,进而验证总线访问路径不受总线时序功能影响

3.4 前门和后门混合应用的场景

(1)只能写一次的寄存器:用物理访问的方式去反应硬件的真实情况
(2)先用前门访问去判断物理通路是否正常,遍历所有寄存器,然后再用后门访问去节约时间
(3)寄存器随机设置:考虑日常不可预测的场景,先通过后门访问随机化整个寄存器列表(在一定的随机限制下),随后再通过前门访问来配置寄存器。
(4)解决地址映射到内部错误寄存器的问题:前门写后门读,或者后门写前门读的方式
(5)状态寄存器:有延时,有时外界的激励条件修改会依赖这些状态寄存器。了需要前门和后门来访问寄存器,也需要映射一些重要的信号来反映第一时间的信息。

四、常见面试题

4.1 为什么需要寄存器模型

DUT的寄存器可以对DUT的行为进行配置,一般通过发送sequence对寄存器进行读写操作,但是当寄存器数目较多时,这样使用起来不方便且容易出错,因此使用寄存器模型,内部分别定义了不同的寄存器,直接对寄存器名字进行索引,就可以读写。而且可以修改访问的方式,是前门还是后门。

下面是通过sequence来读写寄存器,当个数较多是比较复杂

`uvm_do_with(m_trans, {m_trans.addr == 16'h9;
                       m_trans.bus_op == BUS_RD;
                      })//对地址为16'h9的reg发起一笔读操作
 
`uvm_do_with(m_trans, {m_trans.addr == 16'h9;
                       m_trans.bus_op == BUS_WR;
                       m_trans.wr_data == 16'h1;
                      })//对地址为16'h9的reg发起一笔写操作,写入1
reg_model.INVERT_REG.read(status, value, UVM_FRONTDOOR); //对名字是INVERT_REG的寄存器执行读操作
reg_model.INVERT_REG.write(status, 16'h1, UVM_FRONTDOOR); //对名字是INVERT_REG的寄存器执行写操作,写1
  • 13
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
UVM(Universal Verification Methodology)寄存器模型是一用于验证芯片寄存器功能的标准方法。它提供了一个统一的、可重用的框架,用于建立和管理寄存器模型,以及执行寄存器访问和验证。 UVM寄存器模型的主要组成部分包括寄存器模型寄存器层次结构、寄存器操作和寄存器验证环境。 1. 寄存器模型UVM寄存器模型是一个抽象的表示,用于描述芯片内部的寄存器寄存器字段。它提供了一种结构化的方式来定义寄存器的属性、寄存器字段的位宽和访问权限等。 2. 寄存器层次结构:UVM寄存器模型支持多层级的寄存器结构,可以通过层级关系来描述芯片内部的寄存器模块和子模块。这样可以更好地组织和管理寄存器模型,并提供寄存器之间的相互作用和访问。 3. 寄存器操作:UVM提供了一系列的API,用于执行寄存器读写操作。通过这些API,可以向寄存器模型发送读写请求,并获取响应。同时,还可以对寄存器访问进行配置和控制,如重置、写入默认值等。 4. 寄存器验证环境:UVM寄存器模型可以与其他验证环境进行集成,以验证寄存器功能的正确性。通过使用事务级建模(TLM)接口,可以将寄存器操作与其他验证组件进行交互,并进行功能验证、覆盖率分析和错误注入等。 总之,UVM寄存器模型提供了一种规范化的方法来描述和验证芯片寄存器功能。它具有可重用性、灵活性和扩展性,并能与其他验证组件进行集成,从而提高验证效率和可靠性。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值