热度 1| |||
本小节主要为大家介绍UVM中的寄存器模型,包括寄存器模型的结构以及利用寄存器测试序列进行寄存器测试的方法。寄存器模型中主要涉及:
uvm_reg_field,uvm_reg,uvm_reg_block
uvm_reg_adapter,uvm_reg_predictor,uvm_reg_sequence
如图5.1所示,图中红色箭头所标注的就是构成寄存器模型的主要部分。
图5.1 UVM验证平台的整体结构图
上一篇短文主要介绍了UVM中的virtual sequence和configuration机制。以下是上一篇短文中主要介绍的内容。
virtual sequence:class jelly_bean_recipe_virtual_sequenceextends uvm_sequence#(uvm_sequence_item);configuration:classjelly_bean_agent_config extends uvm_object;class jelly_bean_env_configextends uvm_object;
一、DUT内的寄存器定义
在前几节中,DUT中还没有可以访问的寄存器。本小节为DUT中添加了一些记录彩虹糖配方信息以及彩虹糖口味评价信息的寄存器。另外DUT还增加了一个控制读写的命令输入端口command,使得我们可以把彩虹糖的配方信息写入只写的配方寄存器,并可以从只读的口味评价寄存器中读取出口味评价信息。如图5.2所示是我们的寄存器描述示意图。
配方寄存器(RECIPE)是一个只写寄存器,只允许写入,其地址是0x00,位宽是8位。第0-2位构成一个域代表着彩虹糖的风味信息,第3-4位构成一个域代表着彩虹糖的颜色信息,第5位构成一个域代表着彩虹糖的甜度信息,第6位构成一个域代表着彩虹糖的酸度信息,第7位是保留位无法写入。
口味评价寄存器(TASTE)是一个只读寄存器,只允许进行读操作,其地址是0x01,位宽是8位。第0-1位构成一个域代表着彩虹糖评测员也就是我们的DUT对彩虹糖口味的评价信息。第2-7位是保留位无法进行读操作。
图5.2 寄存器描述图
当我们通过命令输入端口command写入WRITE命令,对DUT的寄存器进行写操作时,输入端口上的flavor,color,sugar_free,sour等信息就会被写入RECIPE寄存器中。当command端口输入的是READ命令时,TASTE寄存器内的内容就会被读出来,并通过DUT的taste输出端口传递出去。更新后的DUT的实现如代码5.1所示。
第21-25行:通过jb_slave_if来更新彩虹糖的配方寄存器。当command端口上接收到WRITE命令时,会将interface上的flavor,color,sugar_free,sour端口上的信息写入到DUT的RECIPE寄存器相应的位置上。
第26-27行:通过jb_slave_if来读取彩虹糖的口味评价寄存器。当command端口上接收到READ命令时,会将TASTE寄存器中的taste域的内容通过interface读取出去。
代码5.1 [module] jelly_bean_taster的实现
二、寄存器模型(RegisterModel)
以RECIPE寄存器为例,RECIPE寄存器模型的构建是基于uvm_reg。RECIPE寄存器内的各个域使用uvm_reg_filed来定义,由于各个域的内容是可以改变的,所以有rand来进行补充定义。每个域都需要进行配置,包括域的位宽,域的起始位置等。域的配置是在uvm_reg中的build函数内完成的。注意该build函数并非uvm_component的build_phase,因为uvm_reg实际上只是uvm_object并非uvm_component。另外该build函数并不会自动执行,而是需要手工调用。RECIPE寄存器模型的具体实现,如代码5.2所示。
第4-7行:使用uvm_reg_field声明RECIPE寄存器中的各个域。
第22-66行:在build函数中创建并配置各个域。
第23行:在RECIPE寄存器中创建flavor域,使用type_id::create的方式。
第24-32行:配置flavor域在RECIPE寄存器中的各个内容。第一个参数parent表示此域的上层,也就是此域位于哪个寄存器内。第二个参数size表示此域的位宽,flavor的宽度是3bit,所以该处为3。第三个参数lsb_pos表示此域的最低位在整个RECIPE寄存器中的位置,可以从寄存器描述图中看出flavor的最低位从0开始的。第四个参数access表示此域的存取方式,WO表示只可以写入,读取时会出错。第五个参数volatile表示该域是否是易失的,这个参数一般使用默认值0。第六个参数reset表示此域的复位值。第七个参数has_reset表示复位时此域是否会被复位,一般使用默认值1,表明会被复位。第八个参数is_rand表示此域的值是否可以随机,由于flavor的值并不固定,所以设置为可以随机,便于对其进行随机写测试。第九个参数individually_accessible表示此域是否可以被单独地进行读或者写。
代码5.2 [class] jelly_bean_recipe_reg的实现
与RECIPE寄存器模型的定义类似,如代码5.3所示,是TASTE寄存器模型的具体实现。
第4行:声明了寄存器中的taste域。
第11-20行:在build函数中创建并配置了TASTE寄存器中的taste域。
代码5.3 [class] jelly_bean_taste_reg的具体实现
三、寄存器块(RegisterBlock)
多个寄存器模型构成一个寄存器块,寄存器块主要完成各个寄存器模型的创建,然后再创建一个uvm_reg_map来将各个寄存器模型联系起来。如代码5.4所示,是包含RECIPE寄存器和TASTE寄存器的寄存器块jelly_bean_reg_block的实现。
第4-5行:声明该寄存器块中包含的寄存器模型。
第6行:声明一个uvm_reg_map用来将各个寄存器模型联系起来。
第11-26行:在build函数中完成寄存器块内容的创建。主要是各个寄存器模型和uvm_reg_map的创建。
第13-15行:完成RECIPE寄存器模型在寄存器块中的创建。首先使用type_id::create的方式创建RECIPE寄存器模型,然后使用uvm_reg中的configure函数指定该寄存器模型所在的寄存器块,最后手动调用build函数完成RECIPE寄存器模型内容的创建。
第17-19行:完成TASTE寄存器模型及其内容的创建。
第21行:通过create_map的方式,完成uvm_reg_map的创建。第一个参数name表示这个map的名字。第二个参数base_addr表示map的基地址,本小节中只包含两个寄存器,寄存器的地址从0x00开始,所以map的基地址为8’h00。第三个参数n_bytes是系统总线的宽度,以byte为单位表示,本小节中一个寄存器的宽度正好是1个byte故该参数的值为1。第四个参数endian表示寄存器的存储方式是按大端模式还是小端模式,UVM_LITTLE_ENDIAN表示存储方式为小端模式即数据的高位保存在高地址中,低位保存在低地址中。
第23-24行:将实例化的寄存器模型加入创建好的uvm_reg_map中,使得可以通过map索引到各个寄存器模型。第一个参数rg表示要加入map的寄存器模型。第二个参数offset表示寄存器的偏移地址。第三个参数right表示该寄存器的读写方式。WO表示该寄存器只能进行写操作,RO表示该寄存器只能进行读操作。
第25行:使用lock_model()函数锁存整个寄存器块的结构。
代码5.4 [class] jelly_bean_reg_block的实现
四、Adapter的实现
当我们建立好DUT的寄存器模型之后,还需要完成寄存器模型和DUT的桥接,这样才能够通过寄存器模型去对DUT中的寄存器进行测试。完成该功能的就是Adapter即本文的jelly_bean_reg_adapter。Adapter主要完成寄存器模型的操作uvm_reg_bus_op和对DUT寄存器进行实际总线操作的transaction:jelly_bean_transaction之间的转换。Adapter中主要实现两个函数。reg2bus():将uvm_reg_bus_op中包含的信息转化成jelly_bean_transaction。bus2reg():将jelly_bean_transaction中的信息转化成uvm_reg_bus_op。如代码5.5所示,是jelly_bean_reg_adapter的具体实现。
第10-20行:实现reg2bus函数。uvm_reg_bus_op中包含了寄存器模型对寄存器操作的所有信息,比如其内部的表示操作类型的kind成员变量以及表示读取或者写入数据的data成员变量等。第11行创建了一个jelly_bean_transaction用来接收uvm_reg_bus_op中包含的寄存器操作信息,随后jelly_bean_transaction会经由jelly_bean_sequencer传递给jelly_bean_driver,最后由driver去驱动DUT进而更新DUT中的寄存器。
第14-18行根据uvm_reg_bus_op中的信息相应地更新jelly_bean_transaction中的信息。比如当uvm_reg_bus_op中kind成员变量的值为UVM_READ时,总线transaction中的command就是READ。
第22-37行:实现bus2reg函数。将jelly_bean_driver通过jelly_bean_sequencer反馈回来的jelly_bean_transaction转化为uvm_reg_bus_op,进而被寄存器模型读取,最终更新寄存器模型。
第32-37行:根据jelly_bean_transaction中的内容更新uvm_reg_bus_op。
代码5.5 [class] jelly_bean_reg_adapter的实现
五、RegisterPredictor
为了保证寄存器模型和DUT内实际寄存器值的一致性,寄存器验证方案中还会加入一个RegisterPredictor用来监测DUT寄存器读写总线上的数据传输,进而根据监测到的数据更新寄存器模型内部的寄存器值。由于彩虹糖工厂的registeradapter没有引入任何自定义的功能,所以选择使用UVM原生的uvm_reg_predictor。如代码5.6所示,是jelly_bean_reg_predictor的具体实现。
代码5.6 [class] jelly_bean_reg_predictor的实现
六、包含adapter的agent
由于adapter与具体的bus transaction有关,所以选择将adapter放置在agent中。如代码5.7所示,是agent的具体实现。
第10行:声明一个jelly_bean_reg_adapter的句柄。
第35行:在agent中创建jelly_bean_reg_adapter。
代码5.7 [class] jelly_bean_agent的实现
七、UVM环境(Environment)
我们在jelly_bean_env中会创建包含adapter的jelly_bean_agent以及jelly_bean_reg_predictor,并且在其中完成寄存器模型相关内容的连接。
首先在connect_phase()中通过registermap的set_sequencer()完成adapter和jelly_bean_sequencer的连接,使得adapter中的jelly_bean_transaction可以经由sequencer最后驱动到DUT上。在利用uvm_reg_sequence构建寄存器测试序列之前必须要先调用set_sequencer()完成连接工作。另外值得注意的是,一个寄存器块(registerblock)可能包含不止一个registermap。
其次register map和registeradapter还要完成和registerpredictor的连接。Registerpredictor需要通过registermap和registeradapter来将监测到的DUT总线上的寄存器操作jelly_bean_transaction转化为寄存器模型的操作进而更新寄存器模型内的值。
最后register adapter还要和jelly_bean_agent完成连接以便实时获得agent中monitor监测到的jelly_bean_transaction。UVM环境的具体实现如代码5.8所示。
第28行:在env的build_phase()中完成agent的创建。
第30行:在env的build_phase()中完成predictor的创建。
第45行:通过register map中的set_sequencer()完成jelly_bean_reg_adapter和jelly_bean_sequencer的连接。
第48行:由于我们使用专门的register predictor来更新寄存器模型中的寄存器的值,所以通过registermap中的set_auto_predict()关闭map的自动预测。
第49行:将register predictor中的map连接到寄存器模型中的map。
第50行:将register predictor中的adpter连接到jelly_bean_reg_adapter。
第51行:将register predictor中的bus_in端口与agent中的analysis_port连接,来获得最新的DUT寄存器操作内容。
代码5.8 [class] jelly_bean_env的实现
八、UVM环境的配置信息(Environment Configuration)
我们在UVM的环境配置信息里面引入了jelly_bean_reg_block的句柄,所以jelly_bean_env就可以通过jelly_bean_env_config来访问寄存器模型。如代码5.9所示,是更新后的UVM环境配置信息。
代码5.9 [class] jelly_bean_env_cfg的实现
九、测试用例基础
本文选择在base test一层创建我们的寄存器模型,并将句柄存入UVM验证环境的配置文件jelly_bean_env_config中,通过config_db的方式传递到ENV中,使得jelly_bean_env可以通过句柄访问寄存器模型。如代码5.10所示,是basetest的具体实现。
第16行:创建寄存器块jelly_bean_reg_block。
第17行:调用寄存器块的build函数,uvm_reg_block是一个uvm_object类型,因此其预定义的build()函数并不会自动执行,需要单独调用才能创建整个寄存器模型。
第19行:创建UVM验证环境配置文件jelly_bean_env_config。
第20行:将env_cfg中寄存器模型块的句柄链接到已经创建完成的寄存器块上。
第33-36行:将创建好的jelly_bean_env_config通过uvm_config_db::set()方式上传到uvm_config_db库中,便于jelly_bean_env获取包含寄存器块句柄的配置信息。
代码5.10 [class] jelly_bean_base_test的实现
十、普通的测试序列
以上我们已经创建好了包含寄存器模型在内的所有与寄存器测试相关的组件,已经可以通过创建寄存器测试序列来对DUT进行测试了。在此之前,我们希望先回顾一下普通测试序列的结构和内容以便于读者和下面的标准寄存器测试序列进行对比。如代码5.11所示,普通测试序列jelly_bean_sequence产生的是一个绿色的酸苹果味的彩虹糖。在其body()任务中,创建了一个彩虹糖配方jelly_bean_transaction,然后通过start_item()和finish_item()的方式完成了该transaction的发送。
第9-10行:创建jelly_bean_transaction。
第12行:通过start_item()向jelly_bean_sequencer申请授权许可。
第13-16行:完成sequence内容的填充。
第17行:使用finish_item()方式完成测试序列jelly_bean_transaction的发送。
代码5.11 [class] jelly_bean_sequence的实现
十一、使用寄存器模型的测试序列
通过寄存器模型配套的寄存器测试序列同样也可以产生绿色的酸苹果味的彩虹糖。该测试序列jelly_bean_reg_sequence扩展自uvm_reg_sequence,所以我们可以很方便地使用其提供的write_reg()和read_reg()方法来构建测试序列。在jelly_bean_reg_sequence的body()任务中将彩虹糖配方内容写入了RECIPE寄存器,然后又从TASTE寄存器中读取了DUT的taste口味评价信息。如代码5.12所示,是使用寄存器模型的测试序列的构建。
第14行:声明一个uvm_status_e类型的变量status,用来存储寄存器操作成功与否的结果。
第15行:声明一个uvm_reg_data_t类型的变量value,用来存储寄存器读取或者写入的数据。
第23行:通过使用uvm_reg_sequence::write_reg()方式来将配方内容写入RECIPE寄存器。第一个参数表示目标寄存器的路径,第二参数用来表示本次对寄存器进行写操作动作是否成功,第三个参数是写入寄存器的具体数据。
第24行:通过使用uvm_reg_sequence::read_reg()方式来将TASTE寄存器中口味评价信息读取出来。同样第一个参数表示目标寄存器的路径,第二个参数表示本次对寄存器进行读操作动作是否成功,第三个参数表示读取的数据。
代码5.12 [class] jelly_bean_reg_sequence的实现
十二、寄存器测试用例
创建好寄存器测试序列jelly_bean_reg_sequence之后,接下来就是创建包含该测试序列的测试用例,具体的实现如代码5.13所示。
第12行:创建jelly_bean_reg_sequence。
第14行:将jelly_bean_reg_sequence中的寄存器模型句柄链接到在base test中创建的寄存器模型上。
第15行:使用start方式发送jelly_bean_reg_sequence。
代码5.13 [class] jelly_bean_reg_test的实现
十三、仿真结果
加载上述的jelly_bean_reg_test,得到的仿真结果如图5.3所示。可以看出通过寄存器模型写入的配方在彩虹糖工厂中确实生效了,产生了绿色的酸苹果味的彩虹糖。通过寄存器模型读取出来的口味评价也是正确的。
图5.3 仿真结果记录
十四、本节小记
[Note 1] jelly_bean_reg_adapter的几点说明:
uvm_reg_adapter只是一个uvm_object不是一个uvm_component。
当寄存器模型通过调用reg2bus()进行读写操作时,实际上是uvm_reg_map调用uvm_sequence_base::start_item()来将reg2bus()内创建并返回的uvm_sequence_item发送出去。
reg2bus()传递的参数是const ref uvm_reg_bus_op rw。值得注意的是uvm_reg_bus_op是一个structure类型而并非是一个句柄。而如果把一个structure类型的变量作为函数的参数时,每次调用都需要把structure里面所有的内容都复制过去,这样会浪费很多的内存,所以选择使用ref来以reference的形式传递,另外为了避免reg2bus()对其内容的改变,所以使用一个const进行修饰。
[Note 2]DUT中存在两个或者多个数据总线访问同一个寄存器的验证场景时寄存器模型的构建方案:
创建包含所有寄存器信息的寄存器模型块。
在寄存器块中使用create_map()创建两个registermap:reg_map_A和reg_map_B。在定义寄存器地址时reg_map_A定义bus_A访问寄存器时定义的地址,reg_map_B定义bus_B访问寄存器时定义的地址。
分别建立两个register adapter:reg_adapter_A和reg_adapter_B。reg_adapter_A将寄存器模型操作转化成bus_A上对应的transaction。reg_adapter_B将寄存器模型操作转化成bus_B上对应的transaction。
在register map的set_sequencer()函数中把reg_adapter_A和reg_map_A联系起来,把reg_adapter_B和reg_map_B联系起来。
在构建寄存器测试序列时指定register map。比如通过bus_A来读取reg_X的值,可以这样做:
reg_block.reg_X.read(status, value, .map(reg_block.reg_map_A))。
[彩虹糖带你入门UVM] 第2节 验证组件之Agent ——彩虹糖工厂的小车间
[彩虹糖带你入门UVM] 第3节 验证组件之Environment——彩虹糖工厂的全貌
[彩虹糖带你入门UVM] 第4节 UVM基础之Virtual Sequence和Configuration——彩虹糖出新口味啦